UNPKG

@tldraw/store

Version:

tldraw infinite canvas SDK (store).

246 lines (245 loc) • 9.38 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var StoreSchema_exports = {}; __export(StoreSchema_exports, { StoreSchema: () => StoreSchema, upgradeSchema: () => upgradeSchema }); module.exports = __toCommonJS(StoreSchema_exports); var import_utils = require("@tldraw/utils"); var import_migrate = require("./migrate"); function upgradeSchema(schema) { if (schema.schemaVersion > 2 || schema.schemaVersion < 1) return import_utils.Result.err("Bad schema version"); if (schema.schemaVersion === 2) return import_utils.Result.ok(schema); const result = { schemaVersion: 2, sequences: { "com.tldraw.store": schema.storeVersion } }; for (const [typeName, recordVersion] of Object.entries(schema.recordVersions)) { result.sequences[`com.tldraw.${typeName}`] = recordVersion.version; if ("subTypeKey" in recordVersion) { for (const [subType, version] of Object.entries(recordVersion.subTypeVersions)) { result.sequences[`com.tldraw.${typeName}.${subType}`] = version; } } } return import_utils.Result.ok(result); } class StoreSchema { constructor(types, options) { this.types = types; this.options = options; for (const m of options.migrations ?? []) { (0, import_utils.assert)(!this.migrations[m.sequenceId], `Duplicate migration sequenceId ${m.sequenceId}`); (0, import_migrate.validateMigrations)(m); this.migrations[m.sequenceId] = m; } const allMigrations = Object.values(this.migrations).flatMap((m) => m.sequence); this.sortedMigrations = (0, import_migrate.sortMigrations)(allMigrations); for (const migration of this.sortedMigrations) { if (!migration.dependsOn?.length) continue; for (const dep of migration.dependsOn) { const depMigration = allMigrations.find((m) => m.id === dep); (0, import_utils.assert)(depMigration, `Migration '${migration.id}' depends on missing migration '${dep}'`); } } } static create(types, options) { return new StoreSchema(types, options ?? {}); } migrations = {}; sortedMigrations; validateRecord(store, record, phase, recordBefore) { try { const recordType = (0, import_utils.getOwnProperty)(this.types, record.typeName); if (!recordType) { throw new Error(`Missing definition for record type ${record.typeName}`); } return recordType.validate(record, recordBefore ?? void 0); } catch (error) { if (this.options.onValidationFailure) { return this.options.onValidationFailure({ store, record, phase, recordBefore, error }); } else { throw error; } } } // TODO: use a weakmap to store the result of this function getMigrationsSince(persistedSchema) { const upgradeResult = upgradeSchema(persistedSchema); if (!upgradeResult.ok) { return upgradeResult; } const schema = upgradeResult.value; const sequenceIdsToInclude = new Set( // start with any shared sequences Object.keys(schema.sequences).filter((sequenceId) => this.migrations[sequenceId]) ); for (const sequenceId in this.migrations) { if (schema.sequences[sequenceId] === void 0 && this.migrations[sequenceId].retroactive) { sequenceIdsToInclude.add(sequenceId); } } if (sequenceIdsToInclude.size === 0) { return import_utils.Result.ok([]); } const allMigrationsToInclude = /* @__PURE__ */ new Set(); for (const sequenceId of sequenceIdsToInclude) { const theirVersion = schema.sequences[sequenceId]; if (typeof theirVersion !== "number" && this.migrations[sequenceId].retroactive || theirVersion === 0) { for (const migration of this.migrations[sequenceId].sequence) { allMigrationsToInclude.add(migration.id); } continue; } const theirVersionId = `${sequenceId}/${theirVersion}`; const idx = this.migrations[sequenceId].sequence.findIndex((m) => m.id === theirVersionId); if (idx === -1) { return import_utils.Result.err("Incompatible schema?"); } for (const migration of this.migrations[sequenceId].sequence.slice(idx + 1)) { allMigrationsToInclude.add(migration.id); } } return import_utils.Result.ok(this.sortedMigrations.filter(({ id }) => allMigrationsToInclude.has(id))); } migratePersistedRecord(record, persistedSchema, direction = "up") { const migrations = this.getMigrationsSince(persistedSchema); if (!migrations.ok) { console.error("Error migrating record", migrations.error); return { type: "error", reason: import_migrate.MigrationFailureReason.MigrationError }; } let migrationsToApply = migrations.value; if (migrationsToApply.length === 0) { return { type: "success", value: record }; } if (migrationsToApply.some((m) => m.scope === "store")) { return { type: "error", reason: direction === "down" ? import_migrate.MigrationFailureReason.TargetVersionTooOld : import_migrate.MigrationFailureReason.TargetVersionTooNew }; } if (direction === "down") { if (!migrationsToApply.every((m) => m.down)) { return { type: "error", reason: import_migrate.MigrationFailureReason.TargetVersionTooOld }; } migrationsToApply = migrationsToApply.slice().reverse(); } record = (0, import_utils.structuredClone)(record); try { for (const migration of migrationsToApply) { if (migration.scope === "store") throw new Error( /* won't happen, just for TS */ ); const shouldApply = migration.filter ? migration.filter(record) : true; if (!shouldApply) continue; const result = migration[direction](record); if (result) { record = (0, import_utils.structuredClone)(result); } } } catch (e) { console.error("Error migrating record", e); return { type: "error", reason: import_migrate.MigrationFailureReason.MigrationError }; } return { type: "success", value: record }; } migrateStoreSnapshot(snapshot) { let { store } = snapshot; const migrations = this.getMigrationsSince(snapshot.schema); if (!migrations.ok) { console.error("Error migrating store", migrations.error); return { type: "error", reason: import_migrate.MigrationFailureReason.MigrationError }; } const migrationsToApply = migrations.value; if (migrationsToApply.length === 0) { return { type: "success", value: store }; } store = (0, import_utils.structuredClone)(store); try { for (const migration of migrationsToApply) { if (migration.scope === "record") { for (const [id, record] of Object.entries(store)) { const shouldApply = migration.filter ? migration.filter(record) : true; if (!shouldApply) continue; const result = migration.up(record); if (result) { store[id] = (0, import_utils.structuredClone)(result); } } } else if (migration.scope === "store") { const result = migration.up(store); if (result) { store = (0, import_utils.structuredClone)(result); } } else { (0, import_utils.exhaustiveSwitchError)(migration); } } } catch (e) { console.error("Error migrating store", e); return { type: "error", reason: import_migrate.MigrationFailureReason.MigrationError }; } return { type: "success", value: store }; } /** @internal */ createIntegrityChecker(store) { return this.options.createIntegrityChecker?.(store) ?? void 0; } serialize() { return { schemaVersion: 2, sequences: Object.fromEntries( Object.values(this.migrations).map(({ sequenceId, sequence }) => [ sequenceId, sequence.length ? (0, import_migrate.parseMigrationId)(sequence.at(-1).id).version : 0 ]) ) }; } /** * @deprecated This is only here for legacy reasons, don't use it unless you have david's blessing! */ serializeEarliestVersion() { return { schemaVersion: 2, sequences: Object.fromEntries( Object.values(this.migrations).map(({ sequenceId }) => [sequenceId, 0]) ) }; } /** @internal */ getType(typeName) { const type = (0, import_utils.getOwnProperty)(this.types, typeName); (0, import_utils.assert)(type, "record type does not exists"); return type; } } //# sourceMappingURL=StoreSchema.js.map