@tldraw/store
Version:
tldraw infinite canvas SDK (store).
8 lines (7 loc) • 35.8 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/lib/StoreSchema.ts"],
"sourcesContent": ["import {\n\tassert,\n\texhaustiveSwitchError,\n\tgetOwnProperty,\n\tisEqual,\n\tobjectMapEntries,\n\tResult,\n\tstructuredClone,\n} from '@tldraw/utils'\nimport { UnknownRecord } from './BaseRecord'\nimport { devFreeze } from './devFreeze'\nimport {\n\tMigration,\n\tMigrationFailureReason,\n\tMigrationId,\n\tMigrationResult,\n\tMigrationSequence,\n\tparseMigrationId,\n\tsortMigrations,\n\tSynchronousStorage,\n\tvalidateMigrations,\n} from './migrate'\nimport { RecordType } from './RecordType'\nimport { SerializedStore, Store, StoreSnapshot } from './Store'\n\n/**\n * Version 1 format for serialized store schema information.\n *\n * This is the legacy format used before schema version 2. Version 1 schemas\n * separate store-level versioning from record-level versioning, and support\n * subtypes for complex record types like shapes.\n *\n * @example\n * ```ts\n * const schemaV1: SerializedSchemaV1 = {\n * schemaVersion: 1,\n * storeVersion: 2,\n * recordVersions: {\n * book: { version: 3 },\n * shape: {\n * version: 2,\n * subTypeVersions: { rectangle: 1, circle: 2 },\n * subTypeKey: 'type'\n * }\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface SerializedSchemaV1 {\n\t/** Schema version is the version for this type you're looking at right now */\n\tschemaVersion: 1\n\t/**\n\t * Store version is the version for the structure of the store. e.g. higher level structure like\n\t * removing or renaming a record type.\n\t */\n\tstoreVersion: number\n\t/** Record versions are the versions for each record type. e.g. adding a new field to a record */\n\trecordVersions: Record<\n\t\tstring,\n\t\t| {\n\t\t\t\tversion: number\n\t\t }\n\t\t| {\n\t\t\t\t// subtypes are used for migrating shape and asset props\n\t\t\t\tversion: number\n\t\t\t\tsubTypeVersions: Record<string, number>\n\t\t\t\tsubTypeKey: string\n\t\t }\n\t>\n}\n\n/**\n * Version 2 format for serialized store schema information.\n *\n * This is the current format that uses a unified sequence-based approach\n * for tracking versions across all migration sequences. Each sequence ID\n * maps to the latest version number for that sequence.\n *\n * @example\n * ```ts\n * const schemaV2: SerializedSchemaV2 = {\n * schemaVersion: 2,\n * sequences: {\n * 'com.tldraw.store': 3,\n * 'com.tldraw.book': 2,\n * 'com.tldraw.shape': 4,\n * 'com.tldraw.shape.rectangle': 1\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface SerializedSchemaV2 {\n\tschemaVersion: 2\n\tsequences: {\n\t\t[sequenceId: string]: number\n\t}\n}\n\n/**\n * Union type representing all supported serialized schema formats.\n *\n * This type allows the store to handle both legacy (V1) and current (V2)\n * schema formats during deserialization and migration.\n *\n * @example\n * ```ts\n * function handleSchema(schema: SerializedSchema) {\n * if (schema.schemaVersion === 1) {\n * // Handle V1 format\n * console.log('Store version:', schema.storeVersion)\n * } else {\n * // Handle V2 format\n * console.log('Sequences:', schema.sequences)\n * }\n * }\n * ```\n *\n * @public\n */\nexport type SerializedSchema = SerializedSchemaV1 | SerializedSchemaV2\n\n/**\n * Upgrades a serialized schema from version 1 to version 2 format.\n *\n * Version 1 schemas use separate `storeVersion` and `recordVersions` fields,\n * while version 2 schemas use a unified `sequences` object with sequence IDs.\n *\n * @param schema - The serialized schema to upgrade\n * @returns A Result containing the upgraded schema or an error message\n *\n * @example\n * ```ts\n * const v1Schema = {\n * schemaVersion: 1,\n * storeVersion: 1,\n * recordVersions: {\n * book: { version: 2 },\n * author: { version: 1, subTypeVersions: { fiction: 1 }, subTypeKey: 'genre' }\n * }\n * }\n *\n * const result = upgradeSchema(v1Schema)\n * if (result.ok) {\n * console.log(result.value.sequences)\n * // { 'com.tldraw.store': 1, 'com.tldraw.book': 2, 'com.tldraw.author': 1, 'com.tldraw.author.fiction': 1 }\n * }\n * ```\n *\n * @public\n */\nexport function upgradeSchema(schema: SerializedSchema): Result<SerializedSchemaV2, string> {\n\tif (schema.schemaVersion > 2 || schema.schemaVersion < 1) return Result.err('Bad schema version')\n\tif (schema.schemaVersion === 2) return Result.ok(schema as SerializedSchemaV2)\n\tconst result: SerializedSchemaV2 = {\n\t\tschemaVersion: 2,\n\t\tsequences: {\n\t\t\t'com.tldraw.store': schema.storeVersion,\n\t\t},\n\t}\n\n\tfor (const [typeName, recordVersion] of Object.entries(schema.recordVersions)) {\n\t\tresult.sequences[`com.tldraw.${typeName}`] = recordVersion.version\n\t\tif ('subTypeKey' in recordVersion) {\n\t\t\tfor (const [subType, version] of Object.entries(recordVersion.subTypeVersions)) {\n\t\t\t\tresult.sequences[`com.tldraw.${typeName}.${subType}`] = version\n\t\t\t}\n\t\t}\n\t}\n\treturn Result.ok(result)\n}\n\n/**\n * Information about a record validation failure that occurred in the store.\n *\n * This interface provides context about validation errors, including the failed\n * record, the store state, and the operation phase where the failure occurred.\n * It's used by validation failure handlers to implement recovery strategies.\n *\n * @example\n * ```ts\n * const schema = StoreSchema.create(\n * { book: Book },\n * {\n * onValidationFailure: (failure: StoreValidationFailure<Book>) => {\n * console.error(`Validation failed during ${failure.phase}:`, failure.error)\n * console.log('Failed record:', failure.record)\n * console.log('Previous record:', failure.recordBefore)\n *\n * // Return a corrected version of the record\n * return { ...failure.record, title: failure.record.title || 'Untitled' }\n * }\n * }\n * )\n * ```\n *\n * @public\n */\nexport interface StoreValidationFailure<R extends UnknownRecord> {\n\terror: unknown\n\tstore: Store<R>\n\trecord: R\n\tphase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'\n\trecordBefore: R | null\n}\n\n/**\n * Configuration options for creating a StoreSchema.\n *\n * These options control migration behavior, validation error handling,\n * and integrity checking for the store schema.\n *\n * @example\n * ```ts\n * const options: StoreSchemaOptions<MyRecord, MyProps> = {\n * migrations: [bookMigrations, authorMigrations],\n * onValidationFailure: (failure) => {\n * // Log the error and return a corrected record\n * console.error('Validation failed:', failure.error)\n * return sanitizeRecord(failure.record)\n * },\n * createIntegrityChecker: (store) => {\n * // Set up integrity checking logic\n * return setupIntegrityChecks(store)\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface StoreSchemaOptions<R extends UnknownRecord, P> {\n\tmigrations?: MigrationSequence[]\n\t/** @public */\n\tonValidationFailure?(data: StoreValidationFailure<R>): R\n\t/** @internal */\n\tcreateIntegrityChecker?(store: Store<R, P>): void\n}\n\n/**\n * Manages the schema definition, validation, and migration system for a Store.\n *\n * StoreSchema coordinates record types, handles data migrations between schema\n * versions, validates records, and provides the foundational structure for\n * reactive stores. It acts as the central authority for data consistency\n * and evolution within the store system.\n *\n * @example\n * ```ts\n * // Define record types\n * const Book = createRecordType<Book>('book', { scope: 'document' })\n * const Author = createRecordType<Author>('author', { scope: 'document' })\n *\n * // Create schema with migrations\n * const schema = StoreSchema.create(\n * { book: Book, author: Author },\n * {\n * migrations: [bookMigrations, authorMigrations],\n * onValidationFailure: (failure) => {\n * console.warn('Validation failed, using default:', failure.error)\n * return failure.record // or return a corrected version\n * }\n * }\n * )\n *\n * // Use with store\n * const store = new Store({ schema })\n * ```\n *\n * @public\n */\nexport class StoreSchema<R extends UnknownRecord, P = unknown> {\n\t/**\n\t * Creates a new StoreSchema with the given record types and options.\n\t *\n\t * This static factory method is the recommended way to create a StoreSchema.\n\t * It ensures type safety while providing a clean API for schema definition.\n\t *\n\t * @param types - Object mapping type names to their RecordType definitions\n\t * @param options - Optional configuration for migrations, validation, and integrity checking\n\t * @returns A new StoreSchema instance\n\t *\n\t * @example\n\t * ```ts\n\t * const Book = createRecordType<Book>('book', { scope: 'document' })\n\t * const Author = createRecordType<Author>('author', { scope: 'document' })\n\t *\n\t * const schema = StoreSchema.create(\n\t * {\n\t * book: Book,\n\t * author: Author\n\t * },\n\t * {\n\t * migrations: [bookMigrations],\n\t * onValidationFailure: (failure) => failure.record\n\t * }\n\t * )\n\t * ```\n\t *\n\t * @public\n\t */\n\tstatic create<R extends UnknownRecord, P = unknown>(\n\t\t// HACK: making this param work with RecordType is an enormous pain\n\t\t// let's just settle for making sure each typeName has a corresponding RecordType\n\t\t// and accept that this function won't be able to infer the record type from it's arguments\n\t\ttypes: { [TypeName in R['typeName']]: { createId: any } },\n\t\toptions?: StoreSchemaOptions<R, P>\n\t): StoreSchema<R, P> {\n\t\treturn new StoreSchema<R, P>(types as any, options ?? {})\n\t}\n\n\treadonly migrations: Record<string, MigrationSequence> = {}\n\treadonly sortedMigrations: readonly Migration[]\n\tprivate readonly migrationCache = new WeakMap<SerializedSchema, Result<Migration[], string>>()\n\n\tprivate constructor(\n\t\tpublic readonly types: {\n\t\t\t[Record in R as Record['typeName']]: RecordType<R, any>\n\t\t},\n\t\tprivate readonly options: StoreSchemaOptions<R, P>\n\t) {\n\t\tfor (const m of options.migrations ?? []) {\n\t\t\tassert(!this.migrations[m.sequenceId], `Duplicate migration sequenceId ${m.sequenceId}`)\n\t\t\tvalidateMigrations(m)\n\t\t\tthis.migrations[m.sequenceId] = m\n\t\t}\n\t\tconst allMigrations = Object.values(this.migrations).flatMap((m) => m.sequence)\n\t\tthis.sortedMigrations = sortMigrations(allMigrations)\n\n\t\tfor (const migration of this.sortedMigrations) {\n\t\t\tif (!migration.dependsOn?.length) continue\n\t\t\tfor (const dep of migration.dependsOn) {\n\t\t\t\tconst depMigration = allMigrations.find((m) => m.id === dep)\n\t\t\t\tassert(depMigration, `Migration '${migration.id}' depends on missing migration '${dep}'`)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Validates a record using its corresponding RecordType validator.\n\t *\n\t * This method ensures that records conform to their type definitions before\n\t * being stored. If validation fails and an onValidationFailure handler is\n\t * provided, it will be called to potentially recover from the error.\n\t *\n\t * @param store - The store instance where validation is occurring\n\t * @param record - The record to validate\n\t * @param phase - The lifecycle phase where validation is happening\n\t * @param recordBefore - The previous version of the record (for updates)\n\t * @returns The validated record, potentially modified by validation failure handler\n\t *\n\t * @example\n\t * ```ts\n\t * try {\n\t * const validatedBook = schema.validateRecord(\n\t * store,\n\t * { id: 'book:1', typeName: 'book', title: '', author: 'Jane Doe' },\n\t * 'createRecord',\n\t * null\n\t * )\n\t * } catch (error) {\n\t * console.error('Record validation failed:', error)\n\t * }\n\t * ```\n\t *\n\t * @public\n\t */\n\tvalidateRecord(\n\t\tstore: Store<R>,\n\t\trecord: R,\n\t\tphase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests',\n\t\trecordBefore: R | null\n\t): R {\n\t\ttry {\n\t\t\tconst recordType = getOwnProperty(this.types, record.typeName)\n\t\t\tif (!recordType) {\n\t\t\t\tthrow new Error(`Missing definition for record type ${record.typeName}`)\n\t\t\t}\n\t\t\treturn recordType.validate(record, recordBefore ?? undefined)\n\t\t} catch (error: unknown) {\n\t\t\tif (this.options.onValidationFailure) {\n\t\t\t\treturn this.options.onValidationFailure({\n\t\t\t\t\tstore,\n\t\t\t\t\trecord,\n\t\t\t\t\tphase,\n\t\t\t\t\trecordBefore,\n\t\t\t\t\terror,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tthrow error\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Gets all migrations that need to be applied to upgrade from a persisted schema\n\t * to the current schema version.\n\t *\n\t * This method compares the persisted schema with the current schema and determines\n\t * which migrations need to be applied to bring the data up to date. It handles\n\t * both regular migrations and retroactive migrations, and caches results for\n\t * performance.\n\t *\n\t * @param persistedSchema - The schema version that was previously persisted\n\t * @returns A Result containing the list of migrations to apply, or an error message\n\t *\n\t * @example\n\t * ```ts\n\t * const persistedSchema = {\n\t * schemaVersion: 2,\n\t * sequences: { 'com.tldraw.book': 1, 'com.tldraw.author': 0 }\n\t * }\n\t *\n\t * const migrationsResult = schema.getMigrationsSince(persistedSchema)\n\t * if (migrationsResult.ok) {\n\t * console.log('Migrations to apply:', migrationsResult.value.length)\n\t * // Apply each migration to bring data up to date\n\t * }\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic getMigrationsSince(persistedSchema: SerializedSchema): Result<Migration[], string> {\n\t\t// Check cache first\n\t\tconst cached = this.migrationCache.get(persistedSchema)\n\t\tif (cached) {\n\t\t\treturn cached\n\t\t}\n\n\t\tconst upgradeResult = upgradeSchema(persistedSchema)\n\t\tif (!upgradeResult.ok) {\n\t\t\t// Cache the error result\n\t\t\tthis.migrationCache.set(persistedSchema, upgradeResult)\n\t\t\treturn upgradeResult\n\t\t}\n\t\tconst schema = upgradeResult.value\n\t\tconst sequenceIdsToInclude = new Set(\n\t\t\t// start with any shared sequences\n\t\t\tObject.keys(schema.sequences).filter((sequenceId) => this.migrations[sequenceId])\n\t\t)\n\n\t\t// also include any sequences that are not in the persisted schema but are marked as postHoc\n\t\tfor (const sequenceId in this.migrations) {\n\t\t\tif (schema.sequences[sequenceId] === undefined && this.migrations[sequenceId].retroactive) {\n\t\t\t\tsequenceIdsToInclude.add(sequenceId)\n\t\t\t}\n\t\t}\n\n\t\tif (sequenceIdsToInclude.size === 0) {\n\t\t\tconst result = Result.ok([])\n\t\t\t// Cache the empty result\n\t\t\tthis.migrationCache.set(persistedSchema, result)\n\t\t\treturn result\n\t\t}\n\n\t\tconst allMigrationsToInclude = new Set<MigrationId>()\n\t\tfor (const sequenceId of sequenceIdsToInclude) {\n\t\t\tconst theirVersion = schema.sequences[sequenceId]\n\t\t\tif (\n\t\t\t\t(typeof theirVersion !== 'number' && this.migrations[sequenceId].retroactive) ||\n\t\t\t\ttheirVersion === 0\n\t\t\t) {\n\t\t\t\tfor (const migration of this.migrations[sequenceId].sequence) {\n\t\t\t\t\tallMigrationsToInclude.add(migration.id)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst theirVersionId = `${sequenceId}/${theirVersion}`\n\t\t\tconst idx = this.migrations[sequenceId].sequence.findIndex((m) => m.id === theirVersionId)\n\t\t\t// todo: better error handling\n\t\t\tif (idx === -1) {\n\t\t\t\tconst result = Result.err('Incompatible schema?')\n\t\t\t\t// Cache the error result\n\t\t\t\tthis.migrationCache.set(persistedSchema, result)\n\t\t\t\treturn result\n\t\t\t}\n\t\t\tfor (const migration of this.migrations[sequenceId].sequence.slice(idx + 1)) {\n\t\t\t\tallMigrationsToInclude.add(migration.id)\n\t\t\t}\n\t\t}\n\n\t\t// collect any migrations\n\t\tconst result = Result.ok(\n\t\t\tthis.sortedMigrations.filter(({ id }) => allMigrationsToInclude.has(id))\n\t\t)\n\t\t// Cache the result\n\t\tthis.migrationCache.set(persistedSchema, result)\n\t\treturn result\n\t}\n\n\t/**\n\t * Migrates a single persisted record to match the current schema version.\n\t *\n\t * This method applies the necessary migrations to transform a record from an\n\t * older (or newer) schema version to the current version. It supports both\n\t * forward ('up') and backward ('down') migrations.\n\t *\n\t * @param record - The record to migrate\n\t * @param persistedSchema - The schema version the record was persisted with\n\t * @param direction - Direction to migrate ('up' for newer, 'down' for older)\n\t * @returns A MigrationResult containing the migrated record or an error\n\t *\n\t * @example\n\t * ```ts\n\t * const oldRecord = { id: 'book:1', typeName: 'book', title: 'Old Title', publishDate: '2020-01-01' }\n\t * const oldSchema = { schemaVersion: 2, sequences: { 'com.tldraw.book': 1 } }\n\t *\n\t * const result = schema.migratePersistedRecord(oldRecord, oldSchema, 'up')\n\t * if (result.type === 'success') {\n\t * console.log('Migrated record:', result.value)\n\t * // Record now has publishedYear instead of publishDate\n\t * } else {\n\t * console.error('Migration failed:', result.reason)\n\t * }\n\t * ```\n\t *\n\t * @public\n\t */\n\tmigratePersistedRecord(\n\t\trecord: R,\n\t\tpersistedSchema: SerializedSchema,\n\t\tdirection: 'up' | 'down' = 'up'\n\t): MigrationResult<R> {\n\t\tconst migrations = this.getMigrationsSince(persistedSchema)\n\t\tif (!migrations.ok) {\n\t\t\t// TODO: better error\n\t\t\tconsole.error('Error migrating record', migrations.error)\n\t\t\treturn { type: 'error', reason: MigrationFailureReason.MigrationError }\n\t\t}\n\t\tlet migrationsToApply = migrations.value\n\t\tif (migrationsToApply.length === 0) {\n\t\t\treturn { type: 'success', value: record }\n\t\t}\n\n\t\tif (!migrationsToApply.every((m) => m.scope === 'record')) {\n\t\t\treturn {\n\t\t\t\ttype: 'error',\n\t\t\t\treason:\n\t\t\t\t\tdirection === 'down'\n\t\t\t\t\t\t? MigrationFailureReason.TargetVersionTooOld\n\t\t\t\t\t\t: MigrationFailureReason.TargetVersionTooNew,\n\t\t\t}\n\t\t}\n\n\t\tif (direction === 'down') {\n\t\t\tif (!migrationsToApply.every((m) => m.scope === 'record' && m.down)) {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\treason: MigrationFailureReason.TargetVersionTooOld,\n\t\t\t\t}\n\t\t\t}\n\t\t\tmigrationsToApply = migrationsToApply.slice().reverse()\n\t\t}\n\n\t\trecord = structuredClone(record)\n\t\ttry {\n\t\t\tfor (const migration of migrationsToApply) {\n\t\t\t\tif (migration.scope === 'store') throw new Error(/* won't happen, just for TS */)\n\t\t\t\tif (migration.scope === 'storage') throw new Error(/* won't happen, just for TS */)\n\t\t\t\tconst shouldApply = migration.filter ? migration.filter(record) : true\n\t\t\t\tif (!shouldApply) continue\n\t\t\t\tconst result = migration[direction]!(record)\n\t\t\t\tif (result) {\n\t\t\t\t\trecord = structuredClone(result) as any\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error('Error migrating record', e)\n\t\t\treturn { type: 'error', reason: MigrationFailureReason.MigrationError }\n\t\t}\n\n\t\treturn { type: 'success', value: record }\n\t}\n\n\tmigrateStorage(storage: SynchronousStorage<R>) {\n\t\tconst schema = storage.getSchema()\n\t\tassert(schema, 'Schema is missing.')\n\n\t\tconst migrations = this.getMigrationsSince(schema)\n\t\tif (!migrations.ok) {\n\t\t\tconsole.error('Error migrating store', migrations.error)\n\t\t\tthrow new Error(migrations.error)\n\t\t}\n\t\tconst migrationsToApply = migrations.value\n\t\tif (migrationsToApply.length === 0) {\n\t\t\treturn\n\t\t}\n\n\t\tstorage.setSchema(this.serialize())\n\n\t\tfor (const migration of migrationsToApply) {\n\t\t\tif (migration.scope === 'record') {\n\t\t\t\t// Collect updates during iteration, then apply them after.\n\t\t\t\t// This avoids issues with live iterators (e.g., SQLite) where updating\n\t\t\t\t// records during iteration can cause them to be visited multiple times.\n\t\t\t\tconst updates: [string, R][] = []\n\t\t\t\tfor (const [id, state] of storage.entries()) {\n\t\t\t\t\tconst shouldApply = migration.filter ? migration.filter(state) : true\n\t\t\t\t\tif (!shouldApply) continue\n\t\t\t\t\tconst record = structuredClone(state)\n\t\t\t\t\tconst result = migration.up!(record as any) ?? record\n\t\t\t\t\tif (!isEqual(result, state)) {\n\t\t\t\t\t\tupdates.push([id, result as R])\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [id, record] of updates) {\n\t\t\t\t\tstorage.set(id, record)\n\t\t\t\t}\n\t\t\t} else if (migration.scope === 'store') {\n\t\t\t\t// legacy\n\t\t\t\tconst prevStore = Object.fromEntries(storage.entries())\n\t\t\t\tlet nextStore = structuredClone(prevStore)\n\t\t\t\tnextStore = (migration.up!(nextStore) as any) ?? nextStore\n\t\t\t\tfor (const [id, state] of Object.entries(nextStore)) {\n\t\t\t\t\tif (!state) continue // these will be deleted in the next loop\n\t\t\t\t\tif (!isEqual(state, prevStore[id])) {\n\t\t\t\t\t\tstorage.set(id, state)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const id of Object.keys(prevStore)) {\n\t\t\t\t\tif (!nextStore[id]) {\n\t\t\t\t\t\tstorage.delete(id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (migration.scope === 'storage') {\n\t\t\t\tmigration.up!(storage)\n\t\t\t} else {\n\t\t\t\texhaustiveSwitchError(migration)\n\t\t\t}\n\t\t}\n\t\t// Clean up by filtering out any non-document records.\n\t\t// This is mainly legacy support for extremely early days tldraw.\n\t\tfor (const [id, state] of storage.entries()) {\n\t\t\tif (this.getType(state.typeName).scope !== 'document') {\n\t\t\t\tstorage.delete(id)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Migrates an entire store snapshot to match the current schema version.\n\t *\n\t * This method applies all necessary migrations to bring a persisted store\n\t * snapshot up to the current schema version. It handles both record-level\n\t * and store-level migrations, and can optionally mutate the input store\n\t * for performance.\n\t *\n\t * @param snapshot - The store snapshot containing data and schema information\n\t * @param opts - Options controlling migration behavior\n\t * - mutateInputStore - Whether to modify the input store directly (default: false)\n\t * @returns A MigrationResult containing the migrated store or an error\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = {\n\t * schema: { schemaVersion: 2, sequences: { 'com.tldraw.book': 1 } },\n\t * store: {\n\t * 'book:1': { id: 'book:1', typeName: 'book', title: 'Old Book', publishDate: '2020-01-01' }\n\t * }\n\t * }\n\t *\n\t * const result = schema.migrateStoreSnapshot(snapshot)\n\t * if (result.type === 'success') {\n\t * console.log('Migrated store:', result.value)\n\t * // All records are now at current schema version\n\t * }\n\t * ```\n\t *\n\t * @public\n\t */\n\tmigrateStoreSnapshot(\n\t\tsnapshot: StoreSnapshot<R>,\n\t\topts?: { mutateInputStore?: boolean }\n\t): MigrationResult<SerializedStore<R>> {\n\t\tconst migrations = this.getMigrationsSince(snapshot.schema)\n\t\tif (!migrations.ok) {\n\t\t\t// TODO: better error\n\t\t\tconsole.error('Error migrating store', migrations.error)\n\t\t\treturn { type: 'error', reason: MigrationFailureReason.MigrationError }\n\t\t}\n\t\tconst migrationsToApply = migrations.value\n\t\tif (migrationsToApply.length === 0) {\n\t\t\treturn { type: 'success', value: snapshot.store }\n\t\t}\n\t\tconst store = Object.assign(\n\t\t\tnew Map<string, R>(objectMapEntries(snapshot.store).map(devFreeze)),\n\t\t\t{\n\t\t\t\tgetSchema: () => snapshot.schema,\n\t\t\t\tsetSchema: (_: SerializedSchema) => {},\n\t\t\t}\n\t\t)\n\t\ttry {\n\t\t\tthis.migrateStorage(store)\n\t\t\tif (opts?.mutateInputStore) {\n\t\t\t\tfor (const [id, record] of store.entries()) {\n\t\t\t\t\tsnapshot.store[id as keyof typeof snapshot.store] = record\n\t\t\t\t}\n\t\t\t\tfor (const id of Object.keys(snapshot.store)) {\n\t\t\t\t\tif (!store.has(id)) {\n\t\t\t\t\t\tdelete snapshot.store[id as keyof typeof snapshot.store]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn { type: 'success', value: snapshot.store }\n\t\t\t} else {\n\t\t\t\treturn {\n\t\t\t\t\ttype: 'success',\n\t\t\t\t\tvalue: Object.fromEntries(store.entries()) as SerializedStore<R>,\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.error('Error migrating store', e)\n\t\t\treturn { type: 'error', reason: MigrationFailureReason.MigrationError }\n\t\t}\n\t}\n\n\t/**\n\t * Creates an integrity checker function for the given store.\n\t *\n\t * This method calls the createIntegrityChecker option if provided, allowing\n\t * custom integrity checking logic to be set up for the store. The integrity\n\t * checker is used to validate store consistency and catch data corruption.\n\t *\n\t * @param store - The store instance to create an integrity checker for\n\t * @returns An integrity checker function, or undefined if none is configured\n\t *\n\t * @internal\n\t */\n\tcreateIntegrityChecker(store: Store<R, P>): (() => void) | undefined {\n\t\treturn this.options.createIntegrityChecker?.(store) ?? undefined\n\t}\n\n\t/**\n\t * Serializes the current schema to a SerializedSchemaV2 format.\n\t *\n\t * This method creates a serialized representation of the current schema,\n\t * capturing the latest version number for each migration sequence.\n\t * The result can be persisted and later used to determine what migrations\n\t * need to be applied when loading data.\n\t *\n\t * @returns A SerializedSchemaV2 object representing the current schema state\n\t *\n\t * @example\n\t * ```ts\n\t * const serialized = schema.serialize()\n\t * console.log(serialized)\n\t * // {\n\t * // schemaVersion: 2,\n\t * // sequences: {\n\t * // 'com.tldraw.book': 3,\n\t * // 'com.tldraw.author': 2\n\t * // }\n\t * // }\n\t *\n\t * // Store this with your data for future migrations\n\t * localStorage.setItem('schema', JSON.stringify(serialized))\n\t * ```\n\t *\n\t * @public\n\t */\n\tserialize(): SerializedSchemaV2 {\n\t\treturn {\n\t\t\tschemaVersion: 2,\n\t\t\tsequences: Object.fromEntries(\n\t\t\t\tObject.values(this.migrations).map(({ sequenceId, sequence }) => [\n\t\t\t\t\tsequenceId,\n\t\t\t\t\tsequence.length ? parseMigrationId(sequence.at(-1)!.id).version : 0,\n\t\t\t\t])\n\t\t\t),\n\t\t}\n\t}\n\n\t/**\n\t * Serializes a schema representing the earliest possible version.\n\t *\n\t * This method creates a serialized schema where all migration sequences\n\t * are set to version 0, representing the state before any migrations\n\t * have been applied. This is used in specific legacy scenarios.\n\t *\n\t * @returns A SerializedSchema with all sequences set to version 0\n\t *\n\t * @deprecated This is only here for legacy reasons, don't use it unless you have david's blessing!\n\t * @internal\n\t */\n\tserializeEarliestVersion(): SerializedSchema {\n\t\treturn {\n\t\t\tschemaVersion: 2,\n\t\t\tsequences: Object.fromEntries(\n\t\t\t\tObject.values(this.migrations).map(({ sequenceId }) => [sequenceId, 0])\n\t\t\t),\n\t\t}\n\t}\n\n\t/**\n\t * Gets the RecordType definition for a given type name.\n\t *\n\t * This method retrieves the RecordType associated with the specified\n\t * type name, which contains the record's validation, creation, and\n\t * other behavioral logic.\n\t *\n\t * @param typeName - The name of the record type to retrieve\n\t * @returns The RecordType definition for the specified type\n\t *\n\t * @throws Will throw an error if the record type does not exist\n\t *\n\t * @internal\n\t */\n\tgetType(typeName: string) {\n\t\tconst type = getOwnProperty(this.types, typeName)\n\t\tassert(type, 'record type does not exists')\n\t\treturn type\n\t}\n}\n"],
"mappings": "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",
"names": ["result"]
}