@tldraw/store
Version:
tldraw infinite canvas SDK (store).
180 lines (179 loc) • 6.03 kB
JavaScript
import { assert, objectMapEntries } from "@tldraw/utils";
function squashDependsOn(sequence) {
const result = [];
for (let i = sequence.length - 1; i >= 0; i--) {
const elem = sequence[i];
if (!("id" in elem)) {
const dependsOn = elem.dependsOn;
const prev = result[0];
if (prev) {
result[0] = {
...prev,
dependsOn: dependsOn.concat(prev.dependsOn ?? [])
};
}
} else {
result.unshift(elem);
}
}
return result;
}
function createMigrationSequence({
sequence,
sequenceId,
retroactive = true
}) {
const migrations = {
sequenceId,
retroactive,
sequence: squashDependsOn(sequence)
};
validateMigrations(migrations);
return migrations;
}
function createMigrationIds(sequenceId, versions) {
return Object.fromEntries(
objectMapEntries(versions).map(([key, version]) => [key, `${sequenceId}/${version}`])
);
}
function createRecordMigrationSequence(opts) {
const sequenceId = opts.sequenceId;
return createMigrationSequence({
sequenceId,
retroactive: opts.retroactive ?? true,
sequence: opts.sequence.map(
(m) => "id" in m ? {
...m,
scope: "record",
filter: (r) => r.typeName === opts.recordType && (m.filter?.(r) ?? true) && (opts.filter?.(r) ?? true)
} : m
)
});
}
function sortMigrations(migrations) {
if (migrations.length === 0) return [];
const byId = new Map(migrations.map((m) => [m.id, m]));
const dependents = /* @__PURE__ */ new Map();
const inDegree = /* @__PURE__ */ new Map();
const explicitDeps = /* @__PURE__ */ new Map();
for (const m of migrations) {
inDegree.set(m.id, 0);
dependents.set(m.id, /* @__PURE__ */ new Set());
explicitDeps.set(m.id, /* @__PURE__ */ new Set());
}
for (const m of migrations) {
const { version, sequenceId } = parseMigrationId(m.id);
const prevId = `${sequenceId}/${version - 1}`;
if (byId.has(prevId)) {
dependents.get(prevId).add(m.id);
inDegree.set(m.id, inDegree.get(m.id) + 1);
}
if (m.dependsOn) {
for (const depId of m.dependsOn) {
if (byId.has(depId)) {
dependents.get(depId).add(m.id);
explicitDeps.get(m.id).add(depId);
inDegree.set(m.id, inDegree.get(m.id) + 1);
}
}
}
}
const ready = migrations.filter((m) => inDegree.get(m.id) === 0);
const result = [];
const processed = /* @__PURE__ */ new Set();
while (ready.length > 0) {
let bestCandidate;
let bestCandidateScore = -Infinity;
for (const m of ready) {
let urgencyScore = 0;
for (const depId of dependents.get(m.id) || []) {
if (!processed.has(depId)) {
urgencyScore += 1;
if (explicitDeps.get(depId).has(m.id)) {
urgencyScore += 100;
}
}
}
if (urgencyScore > bestCandidateScore || // Tiebreaker: prefer lower sequence/version
urgencyScore === bestCandidateScore && m.id.localeCompare(bestCandidate?.id ?? "") < 0) {
bestCandidate = m;
bestCandidateScore = urgencyScore;
}
}
const nextMigration = bestCandidate;
ready.splice(ready.indexOf(nextMigration), 1);
result.push(nextMigration);
processed.add(nextMigration.id);
for (const depId of dependents.get(nextMigration.id) || []) {
if (!processed.has(depId)) {
inDegree.set(depId, inDegree.get(depId) - 1);
if (inDegree.get(depId) === 0) {
ready.push(byId.get(depId));
}
}
}
}
if (result.length !== migrations.length) {
const unprocessed = migrations.filter((m) => !processed.has(m.id));
assert(false, `Circular dependency in migrations: ${unprocessed[0].id}`);
}
return result;
}
function parseMigrationId(id) {
const [sequenceId, version] = id.split("/");
return { sequenceId, version: parseInt(version) };
}
function validateMigrationId(id, expectedSequenceId) {
if (expectedSequenceId) {
assert(
id.startsWith(expectedSequenceId + "/"),
`Every migration in sequence '${expectedSequenceId}' must have an id starting with '${expectedSequenceId}/'. Got invalid id: '${id}'`
);
}
assert(id.match(/^(.*?)\/(0|[1-9]\d*)$/), `Invalid migration id: '${id}'`);
}
function validateMigrations(migrations) {
assert(
!migrations.sequenceId.includes("/"),
`sequenceId cannot contain a '/', got ${migrations.sequenceId}`
);
assert(migrations.sequenceId.length, "sequenceId must be a non-empty string");
if (migrations.sequence.length === 0) {
return;
}
validateMigrationId(migrations.sequence[0].id, migrations.sequenceId);
let n = parseMigrationId(migrations.sequence[0].id).version;
assert(
n === 1,
`Expected the first migrationId to be '${migrations.sequenceId}/1' but got '${migrations.sequence[0].id}'`
);
for (let i = 1; i < migrations.sequence.length; i++) {
const id = migrations.sequence[i].id;
validateMigrationId(id, migrations.sequenceId);
const m = parseMigrationId(id).version;
assert(
m === n + 1,
`Migration id numbers must increase in increments of 1, expected ${migrations.sequenceId}/${n + 1} but got '${migrations.sequence[i].id}'`
);
n = m;
}
}
var MigrationFailureReason = /* @__PURE__ */ ((MigrationFailureReason2) => {
MigrationFailureReason2["IncompatibleSubtype"] = "incompatible-subtype";
MigrationFailureReason2["UnknownType"] = "unknown-type";
MigrationFailureReason2["TargetVersionTooNew"] = "target-version-too-new";
MigrationFailureReason2["TargetVersionTooOld"] = "target-version-too-old";
MigrationFailureReason2["MigrationError"] = "migration-error";
MigrationFailureReason2["UnrecognizedSubtype"] = "unrecognized-subtype";
return MigrationFailureReason2;
})(MigrationFailureReason || {});
export {
MigrationFailureReason,
createMigrationIds,
createMigrationSequence,
createRecordMigrationSequence,
parseMigrationId,
sortMigrations,
validateMigrations
};
//# sourceMappingURL=migrate.mjs.map