UNPKG

@tldraw/store

Version:

tldraw infinite canvas SDK (store).

180 lines (179 loc) • 6.03 kB
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