@tldraw/store 4.3.0-canary.bf87ebaf143a → 4.3.0-canary.c08047039e53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/StoreSchema.ts"],
4
- "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\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\tstorage.set(id, result as R)\n\t\t\t\t\t}\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"],
5
- "mappings": "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",
4
+ "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"],
5
+ "mappings": "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",
6
6
  "names": ["result"]
7
7
  }
@@ -281,6 +281,27 @@ export declare class AtomMap<K, V> implements Map<K, V> {
281
281
  [Symbol.toStringTag]: string;
282
282
  }
283
283
 
284
+ /**
285
+ * A drop-in replacement for Set that stores values in atoms and can be used in reactive contexts.
286
+ * @public
287
+ */
288
+ export declare class AtomSet<T> {
289
+ private readonly name;
290
+ private readonly map;
291
+ constructor(name: string, keys?: Iterable<T>);
292
+ add(value: T): this;
293
+ clear(): void;
294
+ delete(value: T): boolean;
295
+ forEach(callbackfn: (value: T, value2: T, set: AtomSet<T>) => void, thisArg?: any): void;
296
+ has(value: T): boolean;
297
+ get size(): number;
298
+ entries(): Generator<[T, T], undefined, unknown>;
299
+ keys(): Generator<T, undefined, unknown>;
300
+ values(): Generator<T, undefined, unknown>;
301
+ [Symbol.iterator](): Generator<T, undefined, unknown>;
302
+ [Symbol.toStringTag]: string;
303
+ }
304
+
284
305
  /**
285
306
  * The base record interface that all records in the store must extend.
286
307
  * This interface provides the fundamental structure required for all records: a unique ID and a type name.
@@ -1,5 +1,6 @@
1
1
  import { registerTldrawLibraryVersion } from "@tldraw/utils";
2
2
  import { AtomMap } from "./lib/AtomMap.mjs";
3
+ import { AtomSet } from "./lib/AtomSet.mjs";
3
4
  import { devFreeze } from "./lib/devFreeze.mjs";
4
5
  import { IncrementalSetConstructor } from "./lib/IncrementalSetConstructor.mjs";
5
6
  import {
@@ -28,11 +29,12 @@ import {
28
29
  } from "./lib/StoreSideEffects.mjs";
29
30
  registerTldrawLibraryVersion(
30
31
  "@tldraw/store",
31
- "4.3.0-canary.bf87ebaf143a",
32
+ "4.3.0-canary.c08047039e53",
32
33
  "esm"
33
34
  );
34
35
  export {
35
36
  AtomMap,
37
+ AtomSet,
36
38
  IncrementalSetConstructor,
37
39
  MigrationFailureReason,
38
40
  RecordType,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\nexport { AtomMap } from './lib/AtomMap'\nexport type { BaseRecord, IdOf, RecordId, UnknownRecord } from './lib/BaseRecord'\nexport { devFreeze } from './lib/devFreeze'\nexport { type QueryExpression, type QueryValueMatcher } from './lib/executeQuery'\nexport { IncrementalSetConstructor } from './lib/IncrementalSetConstructor'\nexport {\n\tcreateMigrationIds,\n\tcreateMigrationSequence,\n\tcreateRecordMigrationSequence,\n\tMigrationFailureReason,\n\tparseMigrationId,\n\ttype LegacyBaseMigrationsInfo,\n\ttype LegacyMigration,\n\ttype LegacyMigrations,\n\ttype Migration,\n\ttype MigrationId,\n\ttype MigrationResult,\n\ttype MigrationSequence,\n\ttype StandaloneDependsOn,\n\ttype SynchronousRecordStorage,\n\ttype SynchronousStorage,\n} from './lib/migrate'\nexport {\n\tcreateEmptyRecordsDiff,\n\tisRecordsDiffEmpty,\n\treverseRecordsDiff,\n\tsquashRecordDiffs,\n\tsquashRecordDiffsMutable,\n\ttype RecordsDiff,\n} from './lib/RecordsDiff'\nexport { assertIdType, createRecordType, RecordType, type RecordScope } from './lib/RecordType'\nexport {\n\tcreateComputedCache,\n\tStore,\n\ttype ChangeSource,\n\ttype CollectionDiff,\n\ttype ComputedCache,\n\ttype CreateComputedCacheOpts,\n\ttype HistoryEntry,\n\ttype RecordFromId,\n\ttype SerializedStore,\n\ttype StoreError,\n\ttype StoreListener,\n\ttype StoreListenerFilters,\n\ttype StoreObject,\n\ttype StoreObjectRecordType,\n\ttype StoreRecord,\n\ttype StoreSnapshot,\n\ttype StoreValidator,\n\ttype StoreValidators,\n} from './lib/Store'\nexport { StoreQueries, type RSIndex, type RSIndexDiff, type RSIndexMap } from './lib/StoreQueries'\nexport { StoreSchema, type StoreValidationFailure } from './lib/StoreSchema'\nexport type {\n\tSerializedSchema,\n\tSerializedSchemaV1,\n\tSerializedSchemaV2,\n\tStoreSchemaOptions,\n} from './lib/StoreSchema'\nexport {\n\tStoreSideEffects,\n\ttype StoreAfterChangeHandler,\n\ttype StoreAfterCreateHandler,\n\ttype StoreAfterDeleteHandler,\n\ttype StoreBeforeChangeHandler,\n\ttype StoreBeforeCreateHandler,\n\ttype StoreBeforeDeleteHandler,\n\ttype StoreOperationCompleteHandler,\n} from './lib/StoreSideEffects'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
5
- "mappings": "AAAA,SAAS,oCAAoC;AAC7C,SAAS,eAAe;AAExB,SAAS,iBAAiB;AAE1B,SAAS,iCAAiC;AAC1C;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWM;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AACP,SAAS,cAAc,kBAAkB,kBAAoC;AAC7E;AAAA,EACC;AAAA,EACA;AAAA,OAiBM;AACP,SAAS,oBAAqE;AAC9E,SAAS,mBAAgD;AAOzD;AAAA,EACC;AAAA,OAQM;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
4
+ "sourcesContent": ["import { registerTldrawLibraryVersion } from '@tldraw/utils'\nexport { AtomMap } from './lib/AtomMap'\nexport { AtomSet } from './lib/AtomSet'\nexport type { BaseRecord, IdOf, RecordId, UnknownRecord } from './lib/BaseRecord'\nexport { devFreeze } from './lib/devFreeze'\nexport { type QueryExpression, type QueryValueMatcher } from './lib/executeQuery'\nexport { IncrementalSetConstructor } from './lib/IncrementalSetConstructor'\nexport {\n\tcreateMigrationIds,\n\tcreateMigrationSequence,\n\tcreateRecordMigrationSequence,\n\tMigrationFailureReason,\n\tparseMigrationId,\n\ttype LegacyBaseMigrationsInfo,\n\ttype LegacyMigration,\n\ttype LegacyMigrations,\n\ttype Migration,\n\ttype MigrationId,\n\ttype MigrationResult,\n\ttype MigrationSequence,\n\ttype StandaloneDependsOn,\n\ttype SynchronousRecordStorage,\n\ttype SynchronousStorage,\n} from './lib/migrate'\nexport {\n\tcreateEmptyRecordsDiff,\n\tisRecordsDiffEmpty,\n\treverseRecordsDiff,\n\tsquashRecordDiffs,\n\tsquashRecordDiffsMutable,\n\ttype RecordsDiff,\n} from './lib/RecordsDiff'\nexport { assertIdType, createRecordType, RecordType, type RecordScope } from './lib/RecordType'\nexport {\n\tcreateComputedCache,\n\tStore,\n\ttype ChangeSource,\n\ttype CollectionDiff,\n\ttype ComputedCache,\n\ttype CreateComputedCacheOpts,\n\ttype HistoryEntry,\n\ttype RecordFromId,\n\ttype SerializedStore,\n\ttype StoreError,\n\ttype StoreListener,\n\ttype StoreListenerFilters,\n\ttype StoreObject,\n\ttype StoreObjectRecordType,\n\ttype StoreRecord,\n\ttype StoreSnapshot,\n\ttype StoreValidator,\n\ttype StoreValidators,\n} from './lib/Store'\nexport { StoreQueries, type RSIndex, type RSIndexDiff, type RSIndexMap } from './lib/StoreQueries'\nexport { StoreSchema, type StoreValidationFailure } from './lib/StoreSchema'\nexport type {\n\tSerializedSchema,\n\tSerializedSchemaV1,\n\tSerializedSchemaV2,\n\tStoreSchemaOptions,\n} from './lib/StoreSchema'\nexport {\n\tStoreSideEffects,\n\ttype StoreAfterChangeHandler,\n\ttype StoreAfterCreateHandler,\n\ttype StoreAfterDeleteHandler,\n\ttype StoreBeforeChangeHandler,\n\ttype StoreBeforeCreateHandler,\n\ttype StoreBeforeDeleteHandler,\n\ttype StoreOperationCompleteHandler,\n} from './lib/StoreSideEffects'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
5
+ "mappings": "AAAA,SAAS,oCAAoC;AAC7C,SAAS,eAAe;AACxB,SAAS,eAAe;AAExB,SAAS,iBAAiB;AAE1B,SAAS,iCAAiC;AAC1C;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWM;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AACP,SAAS,cAAc,kBAAkB,kBAAoC;AAC7E;AAAA,EACC;AAAA,EACA;AAAA,OAiBM;AACP,SAAS,oBAAqE;AAC9E,SAAS,mBAAgD;AAOzD;AAAA,EACC;AAAA,OAQM;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,48 @@
1
+ import { AtomMap } from "./AtomMap.mjs";
2
+ class AtomSet {
3
+ constructor(name, keys) {
4
+ this.name = name;
5
+ const entries = keys ? Array.from(keys, (k) => [k, k]) : void 0;
6
+ this.map = new AtomMap(name, entries);
7
+ }
8
+ map;
9
+ add(value) {
10
+ this.map.set(value, value);
11
+ return this;
12
+ }
13
+ clear() {
14
+ this.map.clear();
15
+ }
16
+ delete(value) {
17
+ return this.map.delete(value);
18
+ }
19
+ forEach(callbackfn, thisArg) {
20
+ for (const value of this) {
21
+ callbackfn.call(thisArg, value, value, this);
22
+ }
23
+ }
24
+ has(value) {
25
+ return this.map.has(value);
26
+ }
27
+ // eslint-disable-next-line no-restricted-syntax
28
+ get size() {
29
+ return this.map.size;
30
+ }
31
+ entries() {
32
+ return this.map.entries();
33
+ }
34
+ keys() {
35
+ return this.map.keys();
36
+ }
37
+ values() {
38
+ return this.map.keys();
39
+ }
40
+ [Symbol.iterator]() {
41
+ return this.map.keys();
42
+ }
43
+ [Symbol.toStringTag] = "AtomSet";
44
+ }
45
+ export {
46
+ AtomSet
47
+ };
48
+ //# sourceMappingURL=AtomSet.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/AtomSet.ts"],
4
+ "sourcesContent": ["import { AtomMap } from './AtomMap'\n\n/**\n * A drop-in replacement for Set that stores values in atoms and can be used in reactive contexts.\n * @public\n */\nexport class AtomSet<T> {\n\tprivate readonly map: AtomMap<T, T>\n\tconstructor(\n\t\tprivate readonly name: string,\n\t\tkeys?: Iterable<T>\n\t) {\n\t\tconst entries = keys ? Array.from(keys, (k) => [k, k] as const) : undefined\n\t\tthis.map = new AtomMap(name, entries)\n\t}\n\n\tadd(value: T): this {\n\t\tthis.map.set(value, value)\n\t\treturn this\n\t}\n\tclear(): void {\n\t\tthis.map.clear()\n\t}\n\tdelete(value: T): boolean {\n\t\treturn this.map.delete(value)\n\t}\n\tforEach(callbackfn: (value: T, value2: T, set: AtomSet<T>) => void, thisArg?: any): void {\n\t\tfor (const value of this) {\n\t\t\tcallbackfn.call(thisArg, value, value, this)\n\t\t}\n\t}\n\thas(value: T): boolean {\n\t\treturn this.map.has(value)\n\t}\n\t// eslint-disable-next-line no-restricted-syntax\n\tget size(): number {\n\t\treturn this.map.size\n\t}\n\tentries(): Generator<[T, T], undefined, unknown> {\n\t\treturn this.map.entries()\n\t}\n\tkeys(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\tvalues(): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.iterator](): Generator<T, undefined, unknown> {\n\t\treturn this.map.keys()\n\t}\n\t[Symbol.toStringTag]: string = 'AtomSet'\n}\n"],
5
+ "mappings": "AAAA,SAAS,eAAe;AAMjB,MAAM,QAAW;AAAA,EAEvB,YACkB,MACjB,MACC;AAFgB;AAGjB,UAAM,UAAU,OAAO,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAU,IAAI;AAClE,SAAK,MAAM,IAAI,QAAQ,MAAM,OAAO;AAAA,EACrC;AAAA,EAPiB;AAAA,EASjB,IAAI,OAAgB;AACnB,SAAK,IAAI,IAAI,OAAO,KAAK;AACzB,WAAO;AAAA,EACR;AAAA,EACA,QAAc;AACb,SAAK,IAAI,MAAM;AAAA,EAChB;AAAA,EACA,OAAO,OAAmB;AACzB,WAAO,KAAK,IAAI,OAAO,KAAK;AAAA,EAC7B;AAAA,EACA,QAAQ,YAA4D,SAAqB;AACxF,eAAW,SAAS,MAAM;AACzB,iBAAW,KAAK,SAAS,OAAO,OAAO,IAAI;AAAA,IAC5C;AAAA,EACD;AAAA,EACA,IAAI,OAAmB;AACtB,WAAO,KAAK,IAAI,IAAI,KAAK;AAAA,EAC1B;AAAA;AAAA,EAEA,IAAI,OAAe;AAClB,WAAO,KAAK,IAAI;AAAA,EACjB;AAAA,EACA,UAAiD;AAChD,WAAO,KAAK,IAAI,QAAQ;AAAA,EACzB;AAAA,EACA,OAAyC;AACxC,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,SAA2C;AAC1C,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,CAAC,OAAO,QAAQ,IAAsC;AACrD,WAAO,KAAK,IAAI,KAAK;AAAA,EACtB;AAAA,EACA,CAAC,OAAO,WAAW,IAAY;AAChC;",
6
+ "names": []
7
+ }
@@ -124,16 +124,9 @@ function SetRef(ref) {
124
124
  ref.value = true;
125
125
  }
126
126
  }
127
- function arrCopy(arr, offset) {
128
- offset = offset || 0;
129
- const len = Math.max(0, arr.length - offset);
130
- const newArr = new Array(len);
131
- for (let ii = 0; ii < len; ii++) {
132
- newArr[ii] = arr[ii + offset];
133
- }
134
- return newArr;
127
+ function arrCopy(arr, offset = 0) {
128
+ return arr.slice(offset);
135
129
  }
136
- const is = Object.is;
137
130
  class OwnerID {
138
131
  }
139
132
  class ImmutableMap {
@@ -358,7 +351,7 @@ class ArrayMapNode {
358
351
  get(_shift, _keyHash, key, notSetValue) {
359
352
  const entries = this.entries;
360
353
  for (let ii = 0, len = entries.length; ii < len; ii++) {
361
- if (is(key, entries[ii][0])) {
354
+ if (Object.is(key, entries[ii][0])) {
362
355
  return entries[ii][1];
363
356
  }
364
357
  }
@@ -370,7 +363,7 @@ class ArrayMapNode {
370
363
  let idx = 0;
371
364
  const len = entries.length;
372
365
  for (; idx < len; idx++) {
373
- if (is(key, entries[idx][0])) {
366
+ if (Object.is(key, entries[idx][0])) {
374
367
  break;
375
368
  }
376
369
  }
@@ -379,7 +372,7 @@ class ArrayMapNode {
379
372
  return this;
380
373
  }
381
374
  SetRef(didAlter);
382
- (removed || !exists) && SetRef(didChangeSize);
375
+ if (removed || !exists) SetRef(didChangeSize);
383
376
  if (removed && entries.length === 1) {
384
377
  return;
385
378
  }
@@ -390,7 +383,11 @@ class ArrayMapNode {
390
383
  const newEntries = isEditable ? entries : arrCopy(entries);
391
384
  if (exists) {
392
385
  if (removed) {
393
- idx === len - 1 ? newEntries.pop() : newEntries[idx] = newEntries.pop();
386
+ if (idx === len - 1) {
387
+ newEntries.pop();
388
+ } else {
389
+ newEntries[idx] = newEntries.pop();
390
+ }
394
391
  } else {
395
392
  newEntries[idx] = [key, value];
396
393
  }
@@ -531,7 +528,7 @@ class HashCollisionNode {
531
528
  get(shift, keyHash, key, notSetValue) {
532
529
  const entries = this.entries;
533
530
  for (let ii = 0, len = entries.length; ii < len; ii++) {
534
- if (is(key, entries[ii][0])) {
531
+ if (Object.is(key, entries[ii][0])) {
535
532
  return entries[ii][1];
536
533
  }
537
534
  }
@@ -554,7 +551,7 @@ class HashCollisionNode {
554
551
  let idx = 0;
555
552
  const len = entries.length;
556
553
  for (; idx < len; idx++) {
557
- if (is(key, entries[idx][0])) {
554
+ if (Object.is(key, entries[idx][0])) {
558
555
  break;
559
556
  }
560
557
  }
@@ -563,7 +560,7 @@ class HashCollisionNode {
563
560
  return this;
564
561
  }
565
562
  SetRef(didAlter);
566
- (removed || !exists) && SetRef(didChangeSize);
563
+ if (removed || !exists) SetRef(didChangeSize);
567
564
  if (removed && len === 2) {
568
565
  return new ValueNode(ownerID, this.keyHash, entries[idx ^ 1]);
569
566
  }
@@ -571,7 +568,11 @@ class HashCollisionNode {
571
568
  const newEntries = isEditable ? entries : arrCopy(entries);
572
569
  if (exists) {
573
570
  if (removed) {
574
- idx === len - 1 ? newEntries.pop() : newEntries[idx] = newEntries.pop();
571
+ if (idx === len - 1) {
572
+ newEntries.pop();
573
+ } else {
574
+ newEntries[idx] = newEntries.pop();
575
+ }
575
576
  } else {
576
577
  newEntries[idx] = [key, value];
577
578
  }
@@ -592,11 +593,11 @@ class ValueNode {
592
593
  this.entry = entry;
593
594
  }
594
595
  get(shift, keyHash, key, notSetValue) {
595
- return is(key, this.entry[0]) ? this.entry[1] : notSetValue;
596
+ return Object.is(key, this.entry[0]) ? this.entry[1] : notSetValue;
596
597
  }
597
598
  update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
598
599
  const removed = value === NOT_SET;
599
- const keyMatch = is(key, this.entry[0]);
600
+ const keyMatch = Object.is(key, this.entry[0]);
600
601
  if (keyMatch ? value === this.entry[1] : removed) {
601
602
  return this;
602
603
  }
@@ -675,10 +676,11 @@ const ITERATE_VALUES = 1;
675
676
  const ITERATE_ENTRIES = 2;
676
677
  function iteratorValue(type, k, v, iteratorResult) {
677
678
  const value = type === ITERATE_KEYS ? k : type === ITERATE_VALUES ? v : [k, v];
678
- iteratorResult ? iteratorResult.value = value : iteratorResult = {
679
- value,
680
- done: false
681
- };
679
+ if (iteratorResult) {
680
+ iteratorResult.value = value;
681
+ } else {
682
+ iteratorResult = { value, done: false };
683
+ }
682
684
  return iteratorResult;
683
685
  }
684
686
  function iteratorDone() {