@tldraw/store
Version:
tldraw infinite canvas SDK (store).
246 lines (245 loc) • 9.38 kB
JavaScript
;
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