@tldraw/store 5.6.0-canary.e9e8ec003426 → 5.6.0-canary.ff4411b6d84d

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.
package/dist-cjs/index.js CHANGED
@@ -56,7 +56,7 @@ var import_StoreSchema = require("./lib/StoreSchema");
56
56
  var import_StoreSideEffects = require("./lib/StoreSideEffects");
57
57
  (0, import_utils.registerTldrawLibraryVersion)(
58
58
  "@tldraw/store",
59
- "5.6.0-canary.e9e8ec003426",
59
+ "5.6.0-canary.ff4411b6d84d",
60
60
  "cjs"
61
61
  );
62
62
  //# sourceMappingURL=index.js.map
@@ -180,8 +180,9 @@ class Store {
180
180
  }
181
181
  isFlushingHistory = false;
182
182
  _flushHistory() {
183
- if (this.historyAccumulator.hasChanges()) {
184
- const entries = this.historyAccumulator.flush();
183
+ const version = this.history.__unsafe__getWithoutCapture();
184
+ const entries = this.historyAccumulator.flush(version);
185
+ if (entries.length > 0) {
185
186
  const errors = [];
186
187
  const wasFlushingHistory = this.isFlushingHistory;
187
188
  this.isFlushingHistory = true;
@@ -241,14 +242,18 @@ class Store {
241
242
  * @param changes - The changes to add to the history.
242
243
  */
243
244
  updateHistory(changes) {
244
- this.historyAccumulator.add({
245
- changes,
246
- source: this.isMergingRemoteChanges ? "remote" : "user"
247
- });
245
+ const version = this.history.__unsafe__getWithoutCapture() + 1;
246
+ this.history.set(version, changes);
247
+ this.historyAccumulator.add(
248
+ {
249
+ changes,
250
+ source: this.isMergingRemoteChanges ? "remote" : "user"
251
+ },
252
+ version
253
+ );
248
254
  if (this.listeners.size === 0) {
249
255
  this.historyAccumulator.clear();
250
256
  }
251
- this.history.set(this.history.__unsafe__getWithoutCapture() + 1, changes);
252
257
  }
253
258
  validate(phase) {
254
259
  this.allRecords().forEach((record) => this.schema.validateRecord(this, record, phase, null));
@@ -661,11 +666,20 @@ class Store {
661
666
  * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result.
662
667
  */
663
668
  extractingChanges(fn) {
664
- const changes = [];
665
- const dispose = this.historyAccumulator.addInterceptor((entry) => changes.push(entry.changes));
669
+ const collected = [];
670
+ const dispose = this.historyAccumulator.addInterceptor((entry, version) => {
671
+ while (collected.length && collected[collected.length - 1].version >= version) {
672
+ collected.pop();
673
+ }
674
+ collected.push({ version, changes: entry.changes });
675
+ });
666
676
  try {
667
677
  (0, import_state.transact)(fn);
668
- return (0, import_RecordsDiff.squashRecordDiffs)(changes);
678
+ const current = this.history.__unsafe__getWithoutCapture();
679
+ while (collected.length && collected[collected.length - 1].version > current) {
680
+ collected.pop();
681
+ }
682
+ return (0, import_RecordsDiff.squashRecordDiffs)(collected.map((c) => c.changes));
669
683
  } finally {
670
684
  dispose();
671
685
  }
@@ -874,10 +888,14 @@ function squashHistoryEntries(entries) {
874
888
  }
875
889
  class HistoryAccumulator {
876
890
  _history = [];
891
+ // The `history` atom value each entry was recorded at. A transaction that aborts rolls that atom
892
+ // back but cannot unwind this array, so an entry stamped at or past the atom's current value
893
+ // belongs to a rolled-back change-set and must not reach listeners.
894
+ _versions = [];
877
895
  _interceptors = /* @__PURE__ */ new Set();
878
896
  /**
879
- * Add an interceptor that will be called for each history entry.
880
- * Returns a function to remove the interceptor.
897
+ * Add an interceptor that will be called for each history entry, with the version it was
898
+ * recorded at (see `add`). Returns a function to remove the interceptor.
881
899
  */
882
900
  addInterceptor(fn) {
883
901
  this._interceptors.add(fn);
@@ -886,22 +904,25 @@ class HistoryAccumulator {
886
904
  };
887
905
  }
888
906
  /**
889
- * Add a history entry to the accumulator.
907
+ * Add a history entry recorded at `version` (the `history` atom value it sets).
890
908
  * Calls all registered interceptors with the entry.
891
909
  */
892
- add(entry) {
910
+ add(entry, version) {
911
+ this.discardFrom(version);
893
912
  this._history.push(entry);
913
+ this._versions.push(version);
894
914
  for (const interceptor of this._interceptors) {
895
- interceptor(entry);
915
+ interceptor(entry, version);
896
916
  }
897
917
  }
898
918
  /**
899
919
  * Flush all accumulated history entries, squashing adjacent entries from the same source.
900
920
  * Clears the internal history buffer.
901
921
  */
902
- flush() {
922
+ flush(currentVersion) {
923
+ this.discardFrom(currentVersion + 1);
903
924
  const history = squashHistoryEntries(this._history);
904
- this._history = [];
925
+ this.clear();
905
926
  return history;
906
927
  }
907
928
  /**
@@ -909,12 +930,15 @@ class HistoryAccumulator {
909
930
  */
910
931
  clear() {
911
932
  this._history = [];
912
- }
913
- /**
914
- * Check if there are any accumulated history entries.
915
- */
916
- hasChanges() {
917
- return this._history.length > 0;
933
+ this._versions = [];
934
+ }
935
+ // Drops the entries recorded at or after `version`. Entries are in ascending version order: the
936
+ // atom increments by one per entry, and a rollback is followed by exactly this discard.
937
+ discardFrom(version) {
938
+ while (this._versions.length && this._versions[this._versions.length - 1] >= version) {
939
+ this._versions.pop();
940
+ this._history.pop();
941
+ }
918
942
  }
919
943
  }
920
944
  function createComputedCache(name, derive, opts) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/Store.ts"],
4
- "sourcesContent": ["import { Atom, Reactor, Signal, atom, computed, reactor, transact } from '@tldraw/state'\nimport {\n\tWeakCache,\n\tassert,\n\tfilterEntries,\n\tgetOwnProperty,\n\tisEqual,\n\tobjectMapEntries,\n\tobjectMapKeys,\n\tobjectMapValues,\n\tthrottleToNextFrame,\n\tuniqueId,\n} from '@tldraw/utils'\nimport { AtomMap } from './AtomMap'\nimport { IdOf, RecordId, UnknownRecord } from './BaseRecord'\nimport { devFreeze } from './devFreeze'\nimport { hasAnyKey, isRecordsDiffEmpty, RecordsDiff, squashRecordDiffs } from './RecordsDiff'\nimport { RecordScope } from './RecordType'\nimport { StoreQueries } from './StoreQueries'\nimport { SerializedSchema, StoreSchema } from './StoreSchema'\nimport { StoreSideEffects } from './StoreSideEffects'\n\n/**\n * Extracts the record type from a record ID type.\n *\n * @example\n * ```ts\n * type BookId = RecordId<Book>\n * type BookType = RecordFromId<BookId> // Book\n * ```\n *\n * @public\n */\nexport type RecordFromId<K extends RecordId<UnknownRecord>> =\n\tK extends RecordId<infer R> ? R : never\n\n/**\n * A diff describing the changes to a collection.\n *\n * @example\n * ```ts\n * const diff: CollectionDiff<string> = {\n * added: new Set(['newItem']),\n * removed: new Set(['oldItem'])\n * }\n * ```\n *\n * @public\n */\nexport interface CollectionDiff<T> {\n\t/** Items that were added to the collection */\n\tadded?: Set<T>\n\t/** Items that were removed from the collection */\n\tremoved?: Set<T>\n}\n\n/**\n * The source of a change to the store.\n * - `'user'` - Changes originating from local user actions\n * - `'remote'` - Changes originating from remote synchronization\n *\n * @public\n */\nexport type ChangeSource = 'user' | 'remote'\n\n/**\n * Filters for store listeners to control which changes trigger the listener.\n *\n * @example\n * ```ts\n * const filters: StoreListenerFilters = {\n * source: 'user', // Only listen to user changes\n * scope: 'document' // Only listen to document-scoped records\n * }\n * ```\n *\n * @public\n */\nexport interface StoreListenerFilters {\n\t/** Filter by the source of changes */\n\tsource: ChangeSource | 'all'\n\t/** Filter by the scope of records */\n\tscope: RecordScope | 'all'\n}\n\n/**\n * An entry containing changes that originated either by user actions or remote changes.\n * History entries are used to track and replay changes to the store.\n *\n * @example\n * ```ts\n * const entry: HistoryEntry<Book> = {\n * changes: {\n * added: { 'book:123': bookRecord },\n * updated: {},\n * removed: {}\n * },\n * source: 'user'\n * }\n * ```\n *\n * @public\n */\nexport interface HistoryEntry<R extends UnknownRecord = UnknownRecord> {\n\t/** The changes that occurred in this history entry */\n\tchanges: RecordsDiff<R>\n\t/** The source of these changes */\n\tsource: ChangeSource\n}\n\n/**\n * A function that will be called when the history changes.\n *\n * @example\n * ```ts\n * const listener: StoreListener<Book> = (entry) => {\n * console.log('Changes:', entry.changes)\n * console.log('Source:', entry.source)\n * }\n *\n * store.listen(listener)\n * ```\n *\n * @param entry - The history entry containing the changes\n *\n * @public\n */\nexport type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void\n\n/**\n * A computed cache that stores derived data for records.\n * The cache automatically updates when underlying records change and cleans up when records are deleted.\n *\n * @example\n * ```ts\n * const expensiveCache = store.createComputedCache(\n * 'expensive',\n * (book: Book) => performExpensiveCalculation(book)\n * )\n *\n * const result = expensiveCache.get(bookId)\n * ```\n *\n * @public\n */\nexport interface ComputedCache<Data, R extends UnknownRecord> {\n\t/**\n\t * Get the cached data for a record by its ID.\n\t *\n\t * @param id - The ID of the record\n\t * @returns The cached data or undefined if the record doesn't exist\n\t */\n\tget(id: IdOf<R>): Data | undefined\n}\n\n/**\n * Options for creating a computed cache.\n *\n * @example\n * ```ts\n * const options: CreateComputedCacheOpts<string[], Book> = {\n * areRecordsEqual: (a, b) => a.title === b.title,\n * areResultsEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b)\n * }\n * ```\n *\n * @public\n */\nexport interface CreateComputedCacheOpts<Data, R extends UnknownRecord> {\n\t/** Custom equality function for comparing records */\n\tareRecordsEqual?(a: R, b: R): boolean\n\t/** Custom equality function for comparing results */\n\tareResultsEqual?(a: Data, b: Data): boolean\n}\n\n/**\n * A serialized snapshot of the record store's values.\n * This is a plain JavaScript object that can be saved to storage or transmitted over the network.\n *\n * @example\n * ```ts\n * const serialized: SerializedStore<Book> = {\n * 'book:123': { id: 'book:123', typeName: 'book', title: 'The Lathe of Heaven' },\n * 'book:456': { id: 'book:456', typeName: 'book', title: 'The Left Hand of Darkness' }\n * }\n * ```\n *\n * @public\n */\nexport type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>\n\n/**\n * A snapshot of the store including both data and schema information.\n * This enables proper migration when loading data from different schema versions.\n *\n * @example\n * ```ts\n * const snapshot = store.getStoreSnapshot()\n * // Later...\n * store.loadStoreSnapshot(snapshot)\n * ```\n *\n * @public\n */\nexport interface StoreSnapshot<R extends UnknownRecord> {\n\t/** The serialized store data */\n\tstore: SerializedStore<R>\n\t/** The serialized schema information */\n\tschema: SerializedSchema\n}\n\n/**\n * A validator for store records that ensures data integrity.\n * Validators are called when records are created or updated.\n *\n * @example\n * ```ts\n * const bookValidator: StoreValidator<Book> = {\n * validate(record: unknown): Book {\n * // Validate and return the record\n * if (typeof record !== 'object' || !record.title) {\n * throw new Error('Invalid book')\n * }\n * return record as Book\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface StoreValidator<R extends UnknownRecord> {\n\t/**\n\t * Validate a record.\n\t *\n\t * @param record - The record to validate\n\t * @returns The validated record\n\t * @throws When validation fails\n\t */\n\tvalidate(record: unknown): R\n\t/**\n\t * Validate a record using a known good version for reference.\n\t *\n\t * @param knownGoodVersion - A known valid version of the record\n\t * @param record - The record to validate\n\t * @returns The validated record\n\t */\n\tvalidateUsingKnownGoodVersion?(knownGoodVersion: R, record: unknown): R\n}\n\n/**\n * A map of validators for each record type in the store.\n *\n * @example\n * ```ts\n * const validators: StoreValidators<Book | Author> = {\n * book: bookValidator,\n * author: authorValidator\n * }\n * ```\n *\n * @public\n */\nexport type StoreValidators<R extends UnknownRecord> = {\n\t[K in R['typeName']]: StoreValidator<Extract<R, { typeName: K }>>\n}\n\n/**\n * Information about an error that occurred in the store.\n *\n * @example\n * ```ts\n * const error: StoreError = {\n * error: new Error('Validation failed'),\n * phase: 'updateRecord',\n * recordBefore: oldRecord,\n * recordAfter: newRecord,\n * isExistingValidationIssue: false\n * }\n * ```\n *\n * @public\n */\nexport interface StoreError {\n\t/** The error that occurred */\n\terror: Error\n\t/** The phase during which the error occurred */\n\tphase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'\n\t/** The record state before the operation (if applicable) */\n\trecordBefore?: unknown\n\t/** The record state after the operation */\n\trecordAfter: unknown\n\t/** Whether this is an existing validation issue */\n\tisExistingValidationIssue: boolean\n}\n\n/**\n * Extract the record type from a Store type.\n * Used internally for type inference.\n *\n * @internal\n */\nexport type StoreRecord<S extends Store<any>> = S extends Store<infer R> ? R : never\n\n/**\n * A reactive store that manages collections of typed records.\n *\n * The Store is the central container for your application's data, providing:\n * - Reactive state management with automatic updates\n * - Type-safe record operations\n * - History tracking and change notifications\n * - Schema validation and migrations\n * - Side effects and business logic hooks\n * - Efficient querying and indexing\n *\n * @example\n * ```ts\n * // Create a store with schema\n * const schema = StoreSchema.create({\n * book: Book,\n * author: Author\n * })\n *\n * const store = new Store({\n * schema,\n * props: {}\n * })\n *\n * // Add records\n * const book = Book.create({ title: 'The Lathe of Heaven', author: 'Le Guin' })\n * store.put([book])\n *\n * // Listen to changes\n * store.listen((entry) => {\n * console.log('Changes:', entry.changes)\n * })\n * ```\n *\n * @public\n */\nexport class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {\n\t/**\n\t * The unique identifier of the store instance.\n\t *\n\t * @public\n\t */\n\tpublic readonly id: string\n\t/**\n\t * An AtomMap containing the stores records.\n\t *\n\t * @internal\n\t * @readonly\n\t */\n\tprivate readonly records: AtomMap<IdOf<R>, R>\n\n\t/**\n\t * An atom containing the store's history.\n\t *\n\t * @public\n\t * @readonly\n\t */\n\treadonly history: Atom<number, RecordsDiff<R>> = atom('history', 0, {\n\t\thistoryLength: 1000,\n\t})\n\n\t/**\n\t * Reactive queries and indexes for efficiently accessing store data.\n\t * Provides methods for filtering, indexing, and subscribing to subsets of records.\n\t *\n\t * @example\n\t * ```ts\n\t * // Create an index by a property\n\t * const booksByAuthor = store.query.index('book', 'author')\n\t *\n\t * // Get records matching criteria\n\t * const inStockBooks = store.query.records('book', () => ({\n\t * inStock: { eq: true }\n\t * }))\n\t * ```\n\t *\n\t * @public\n\t * @readonly\n\t */\n\treadonly query: StoreQueries<R>\n\n\t/**\n\t * A set containing listeners that have been added to this store.\n\t *\n\t * @internal\n\t */\n\tprivate listeners = new Set<{ onHistory: StoreListener<R>; filters: StoreListenerFilters }>()\n\n\t/**\n\t * An array of history entries that have not yet been flushed.\n\t *\n\t * @internal\n\t */\n\tprivate historyAccumulator = new HistoryAccumulator<R>()\n\n\t/**\n\t * A reactor that responds to changes to the history by squashing the accumulated history and\n\t * notifying listeners of the changes.\n\t *\n\t * @internal\n\t */\n\tprivate historyReactor: Reactor\n\n\t/**\n\t * Cancels the history flush scheduled for the next frame, if any.\n\t *\n\t * @internal\n\t */\n\tprivate cancelHistoryReactor: null | (() => void) = null\n\n\t/**\n\t * The schema that defines the structure and validation rules for records in this store.\n\t *\n\t * @public\n\t */\n\treadonly schema: StoreSchema<R, Props>\n\n\t/**\n\t * Custom properties associated with this store instance.\n\t *\n\t * @public\n\t */\n\treadonly props: Props\n\n\t/**\n\t * A mapping of record scopes to the set of record type names that belong to each scope.\n\t * Used to filter records by their persistence and synchronization behavior.\n\t *\n\t * @public\n\t */\n\tpublic readonly scopedTypes: { readonly [K in RecordScope]: ReadonlySet<R['typeName']> }\n\n\t/**\n\t * Side effects manager that handles lifecycle events for record operations.\n\t * Allows registration of callbacks for create, update, delete, and validation events.\n\t *\n\t * @example\n\t * ```ts\n\t * store.sideEffects.registerAfterCreateHandler('book', (book) => {\n\t * console.log('Book created:', book.title)\n\t * })\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic readonly sideEffects = new StoreSideEffects<R>(this)\n\n\t/**\n\t * Creates a new Store instance.\n\t *\n\t * @example\n\t * ```ts\n\t * const store = new Store({\n\t * schema: StoreSchema.create({ book: Book }),\n\t * props: { appName: 'MyLibrary' },\n\t * initialData: savedData\n\t * })\n\t * ```\n\t *\n\t * @param config - Configuration object for the store\n\t */\n\tconstructor(config: {\n\t\t/** Optional unique identifier for the store */\n\t\tid?: string\n\t\t/** The store's initial data to populate on creation */\n\t\tinitialData?: SerializedStore<R>\n\t\t/** The schema defining record types, validation, and migrations */\n\t\tschema: StoreSchema<R, Props>\n\t\t/** Custom properties for the store instance */\n\t\tprops: Props\n\t}) {\n\t\tconst { initialData, schema, id } = config\n\n\t\tthis.id = id ?? uniqueId()\n\t\tthis.schema = schema\n\t\tthis.props = config.props\n\n\t\tif (initialData) {\n\t\t\tthis.records = new AtomMap(\n\t\t\t\t'store',\n\t\t\t\tobjectMapEntries(initialData).map(([id, record]) => [\n\t\t\t\t\tid,\n\t\t\t\t\tdevFreeze(this.schema.validateRecord(this, record, 'initialize', null)),\n\t\t\t\t])\n\t\t\t)\n\t\t} else {\n\t\t\tthis.records = new AtomMap('store')\n\t\t}\n\n\t\tthis.query = new StoreQueries<R>(this.records, this.history)\n\n\t\tthis.historyReactor = reactor(\n\t\t\t'Store.historyReactor',\n\t\t\t() => {\n\t\t\t\t// deref to make sure we're subscribed regardless of whether we need to propagate\n\t\t\t\tthis.history.get()\n\t\t\t\t// If we have accumulated history, flush it and update listeners\n\t\t\t\tthis._flushHistory()\n\t\t\t},\n\t\t\t{ scheduleEffect: (cb) => (this.cancelHistoryReactor = throttleToNextFrame(cb)) }\n\t\t)\n\t\tconst scopedTypes: { [K in RecordScope]: Set<R['typeName']> } = {\n\t\t\tdocument: new Set(),\n\t\t\tsession: new Set(),\n\t\t\tpresence: new Set(),\n\t\t}\n\t\tfor (const type of objectMapValues(this.schema.types)) {\n\t\t\tscopedTypes[type.scope].add(type.typeName)\n\t\t}\n\t\tthis.scopedTypes = scopedTypes\n\t}\n\n\tprivate isFlushingHistory = false\n\n\tpublic _flushHistory() {\n\t\t// If we have accumulated history, flush it and update listeners\n\t\tif (this.historyAccumulator.hasChanges()) {\n\t\t\tconst entries = this.historyAccumulator.flush()\n\t\t\tconst errors: unknown[] = []\n\t\t\tconst wasFlushingHistory = this.isFlushingHistory\n\t\t\tthis.isFlushingHistory = true\n\t\t\tfor (const { changes, source } of entries) {\n\t\t\t\t// Filtered diffs are computed at most once per scope per entry, and shared by every\n\t\t\t\t// listener watching that scope.\n\t\t\t\tconst scopedChanges = new Map<RecordScope, RecordsDiff<R> | null>()\n\t\t\t\tfor (const { onHistory, filters } of this.listeners) {\n\t\t\t\t\tif (filters.source !== 'all' && filters.source !== source) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tlet listenerChanges = changes\n\t\t\t\t\tif (filters.scope !== 'all') {\n\t\t\t\t\t\tif (!scopedChanges.has(filters.scope)) {\n\t\t\t\t\t\t\tscopedChanges.set(filters.scope, this.filterChangesByScope(changes, filters.scope))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst filtered = scopedChanges.get(filters.scope)\n\t\t\t\t\t\tif (!filtered) continue\n\t\t\t\t\t\tlistenerChanges = filtered\n\t\t\t\t\t}\n\t\t\t\t\t// The entries are already dequeued, so a listener that throws must not stop the others\n\t\t\t\t\t// (e.g. a sync client) from receiving them: deliver to all, then rethrow the first error.\n\t\t\t\t\ttry {\n\t\t\t\t\t\tonHistory({ changes: listenerChanges, source })\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\terrors.push(error)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.isFlushingHistory = wasFlushingHistory\n\t\t\tif (errors.length > 0) throw errors[0]\n\t\t}\n\t}\n\n\tdispose() {\n\t\t// Deliver what is still pending first: a change-set made in the same frame as the dispose\n\t\t// would otherwise never reach the listeners (e.g. a sync client) still attached to the store.\n\t\ttry {\n\t\t\tthis._flushHistory()\n\t\t} finally {\n\t\t\tthis.cancelHistoryReactor?.()\n\t\t}\n\t}\n\n\t/**\n\t * Filters out non-document changes from a diff. Returns null if there are no changes left.\n\t * @param change - the records diff\n\t * @param scope - the records scope\n\t * @returns\n\t */\n\tfilterChangesByScope(change: RecordsDiff<R>, scope: RecordScope) {\n\t\tconst result = {\n\t\t\tadded: filterEntries(change.added, (_, r) => this.scopedTypes[scope].has(r.typeName)),\n\t\t\tupdated: filterEntries(change.updated, (_, r) => this.scopedTypes[scope].has(r[1].typeName)),\n\t\t\tremoved: filterEntries(change.removed, (_, r) => this.scopedTypes[scope].has(r.typeName)),\n\t\t}\n\t\tif (isRecordsDiffEmpty(result)) {\n\t\t\treturn null\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Update the history with a diff of changes.\n\t *\n\t * @param changes - The changes to add to the history.\n\t */\n\tprivate updateHistory(changes: RecordsDiff<R>): void {\n\t\tthis.historyAccumulator.add({\n\t\t\tchanges,\n\t\t\tsource: this.isMergingRemoteChanges ? 'remote' : 'user',\n\t\t})\n\t\tif (this.listeners.size === 0) {\n\t\t\tthis.historyAccumulator.clear()\n\t\t}\n\t\t// Don't capture: a reaction that writes would otherwise re-run on every store change (S11).\n\t\tthis.history.set(this.history.__unsafe__getWithoutCapture() + 1, changes)\n\t}\n\n\tvalidate(phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests') {\n\t\tthis.allRecords().forEach((record) => this.schema.validateRecord(this, record, phase, null))\n\t}\n\n\t/**\n\t * Add or update records in the store. If a record with the same ID already exists, it will be updated.\n\t * Otherwise, a new record will be created.\n\t *\n\t * @example\n\t * ```ts\n\t * // Add new records\n\t * const book = Book.create({ title: 'Lathe Of Heaven', author: 'Le Guin' })\n\t * store.put([book])\n\t *\n\t * // Update existing record\n\t * store.put([{ ...book, title: 'The Lathe of Heaven' }])\n\t * ```\n\t *\n\t * @param records - The records to add or update\n\t * @param phaseOverride - Override the validation phase (used internally)\n\t * @public\n\t */\n\tput(records: R[], phaseOverride?: 'initialize'): void {\n\t\tthis.atomic(() => {\n\t\t\tconst updates: Record<IdOf<UnknownRecord>, [from: R, to: R]> = {}\n\t\t\tconst additions: Record<IdOf<UnknownRecord>, R> = {}\n\n\t\t\t// Iterate through all records, creating, updating or removing as needed\n\t\t\tlet record: R\n\n\t\t\tconst source = this.isMergingRemoteChanges ? 'remote' : 'user'\n\n\t\t\tfor (let i = 0, n = records.length; i < n; i++) {\n\t\t\t\trecord = records[i]\n\n\t\t\t\tconst initialValue = this.records.__unsafe__getWithoutCapture(record.id)\n\t\t\t\t// If we already have an atom for this record, update its value.\n\t\t\t\tif (initialValue) {\n\t\t\t\t\t// If we have a beforeUpdate callback, run it against the initial and next records\n\t\t\t\t\trecord = this.sideEffects.handleBeforeChange(initialValue, record, source)\n\n\t\t\t\t\t// Validate the record\n\t\t\t\t\tconst validated = this.schema.validateRecord(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tphaseOverride ?? 'updateRecord',\n\t\t\t\t\t\tinitialValue\n\t\t\t\t\t)\n\n\t\t\t\t\tif (validated === initialValue) continue\n\n\t\t\t\t\trecord = devFreeze(validated)\n\t\t\t\t\tthis.records.set(record.id, record)\n\n\t\t\t\t\tif (additions[record.id]) {\n\t\t\t\t\t\t// the same record was created earlier in this call: fold the update into it\n\t\t\t\t\t\tadditions[record.id] = record\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// an earlier update to the same record in this call owns the `from`\n\t\t\t\t\t\tconst from = updates[record.id]?.[0] ?? initialValue\n\t\t\t\t\t\tif (from === record) {\n\t\t\t\t\t\t\t// back to where it started within this call: nothing to record\n\t\t\t\t\t\t\tdelete updates[record.id]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tupdates[record.id] = [from, record]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.addDiffForAfterEvent(initialValue, record)\n\t\t\t\t} else {\n\t\t\t\t\trecord = this.sideEffects.handleBeforeCreate(record, source)\n\n\t\t\t\t\t// If we don't have an atom, create one.\n\n\t\t\t\t\t// Validate the record\n\t\t\t\t\trecord = this.schema.validateRecord(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\trecord as R,\n\t\t\t\t\t\tphaseOverride ?? 'createRecord',\n\t\t\t\t\t\tnull\n\t\t\t\t\t)\n\n\t\t\t\t\t// freeze it\n\t\t\t\t\trecord = devFreeze(record)\n\n\t\t\t\t\t// Mark the change as a new addition.\n\t\t\t\t\tadditions[record.id] = record\n\t\t\t\t\tthis.addDiffForAfterEvent(null, record)\n\n\t\t\t\t\tthis.records.set(record.id, record)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Validation may have left every record identical to before, in which case there is no\n\t\t\t// change-set to record.\n\t\t\tif (!hasAnyKey(additions) && !hasAnyKey(updates)) return\n\t\t\tthis.updateHistory({\n\t\t\t\tadded: additions,\n\t\t\t\tupdated: updates,\n\t\t\t\tremoved: {} as Record<IdOf<R>, R>,\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Remove records from the store by their IDs.\n\t *\n\t * @example\n\t * ```ts\n\t * // Remove a single record\n\t * store.remove([book.id])\n\t *\n\t * // Remove multiple records\n\t * store.remove([book1.id, book2.id, book3.id])\n\t * ```\n\t *\n\t * @param ids - The IDs of the records to remove\n\t * @public\n\t */\n\tremove(ids: IdOf<R>[]): void {\n\t\tthis.atomic(() => {\n\t\t\tconst toDelete = new Set<IdOf<R>>(ids)\n\t\t\tconst source = this.isMergingRemoteChanges ? 'remote' : 'user'\n\n\t\t\tif (this.sideEffects.isEnabled()) {\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst record = this.records.__unsafe__getWithoutCapture(id)\n\t\t\t\t\tif (!record) continue\n\n\t\t\t\t\tif (this.sideEffects.handleBeforeDelete(record, source) === false) {\n\t\t\t\t\t\ttoDelete.delete(id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst actuallyDeleted = this.records.deleteMany(toDelete)\n\t\t\tif (actuallyDeleted.length === 0) return\n\n\t\t\tconst removed = {} as RecordsDiff<R>['removed']\n\t\t\tfor (const [id, record] of actuallyDeleted) {\n\t\t\t\tremoved[id] = record\n\t\t\t\tthis.addDiffForAfterEvent(record, null)\n\t\t\t}\n\n\t\t\t// Update the history with the removed records.\n\t\t\tthis.updateHistory({ added: {}, updated: {}, removed } as RecordsDiff<R>)\n\t\t})\n\t}\n\n\t/**\n\t * Get a record by its ID. This creates a reactive subscription to the record.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = store.get(bookId)\n\t * if (book) {\n\t * console.log(book.title)\n\t * }\n\t * ```\n\t *\n\t * @param id - The ID of the record to get\n\t * @returns The record if it exists, undefined otherwise\n\t * @public\n\t */\n\tget<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {\n\t\treturn this.records.get(id) as RecordFromId<K> | undefined\n\t}\n\n\t/**\n\t * Get a record by its ID without creating a reactive subscription.\n\t * Use this when you need to access a record but don't want reactive updates.\n\t *\n\t * @example\n\t * ```ts\n\t * // Won't trigger reactive updates when this record changes\n\t * const book = store.unsafeGetWithoutCapture(bookId)\n\t * ```\n\t *\n\t * @param id - The ID of the record to get\n\t * @returns The record if it exists, undefined otherwise\n\t * @public\n\t */\n\tunsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {\n\t\treturn this.records.__unsafe__getWithoutCapture(id) as RecordFromId<K> | undefined\n\t}\n\n\t/**\n\t * Serialize the store's records to a plain JavaScript object.\n\t * Only includes records matching the specified scope.\n\t *\n\t * @example\n\t * ```ts\n\t * // Serialize only document records (default)\n\t * const documentData = store.serialize('document')\n\t *\n\t * // Serialize all records\n\t * const allData = store.serialize('all')\n\t * ```\n\t *\n\t * @param scope - The scope of records to serialize. Defaults to 'document'\n\t * @returns The serialized store data\n\t * @public\n\t */\n\tserialize(scope: RecordScope | 'all' = 'document'): SerializedStore<R> {\n\t\tconst result = {} as SerializedStore<R>\n\t\tfor (const [id, record] of this.records) {\n\t\t\tif (scope === 'all' || this.scopedTypes[scope].has(record.typeName)) {\n\t\t\t\tresult[id as IdOf<R>] = record\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Get a serialized snapshot of the store and its schema.\n\t * This includes both the data and schema information needed for proper migration.\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = store.getStoreSnapshot()\n\t * localStorage.setItem('myApp', JSON.stringify(snapshot))\n\t *\n\t * // Later...\n\t * const saved = JSON.parse(localStorage.getItem('myApp'))\n\t * store.loadStoreSnapshot(saved)\n\t * ```\n\t *\n\t * @param scope - The scope of records to serialize. Defaults to 'document'\n\t * @returns A snapshot containing both store data and schema information\n\t * @public\n\t */\n\tgetStoreSnapshot(scope: RecordScope | 'all' = 'document'): StoreSnapshot<R> {\n\t\treturn {\n\t\t\tstore: this.serialize(scope),\n\t\t\tschema: this.schema.serialize(),\n\t\t}\n\t}\n\n\t/**\n\t * Migrate a serialized snapshot to the current schema version.\n\t * This applies any necessary migrations to bring old data up to date.\n\t *\n\t * @example\n\t * ```ts\n\t * const oldSnapshot = JSON.parse(localStorage.getItem('myApp'))\n\t * const migratedSnapshot = store.migrateSnapshot(oldSnapshot)\n\t * ```\n\t *\n\t * @param snapshot - The snapshot to migrate\n\t * @returns The migrated snapshot with current schema version\n\t * @throws Error if migration fails\n\t * @public\n\t */\n\tmigrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R> {\n\t\tconst migrationResult = this.schema.migrateStoreSnapshot(snapshot)\n\n\t\tif (migrationResult.type === 'error') {\n\t\t\tthrow new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)\n\t\t}\n\n\t\treturn {\n\t\t\tstore: migrationResult.value,\n\t\t\tschema: this.schema.serialize(),\n\t\t}\n\t}\n\n\t/**\n\t * Load a serialized snapshot into the store, replacing all current data.\n\t * The snapshot will be automatically migrated to the current schema version if needed.\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = JSON.parse(localStorage.getItem('myApp'))\n\t * store.loadStoreSnapshot(snapshot)\n\t * ```\n\t *\n\t * @param snapshot - The snapshot to load\n\t * @throws Error if migration fails or snapshot is invalid\n\t * @public\n\t */\n\tloadStoreSnapshot(snapshot: StoreSnapshot<R>): void {\n\t\tconst migrationResult = this.schema.migrateStoreSnapshot(snapshot)\n\n\t\tif (migrationResult.type === 'error') {\n\t\t\tthrow new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)\n\t\t}\n\n\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\ttry {\n\t\t\tthis.sideEffects.setIsEnabled(false)\n\t\t\tthis.atomic(() => {\n\t\t\t\tthis.clear()\n\t\t\t\tthis.put(Object.values(migrationResult.value))\n\t\t\t\tthis.ensureStoreIsUsable()\n\t\t\t}, false)\n\t\t} finally {\n\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t}\n\t}\n\n\t/**\n\t * Get an array of all records in the store.\n\t *\n\t * @example\n\t * ```ts\n\t * const allRecords = store.allRecords()\n\t * const books = allRecords.filter(r => r.typeName === 'book')\n\t * ```\n\t *\n\t * @returns An array containing all records in the store\n\t * @public\n\t */\n\tallRecords(): R[] {\n\t\treturn Array.from(this.records.values())\n\t}\n\n\t/**\n\t * Remove all records from the store.\n\t *\n\t * @example\n\t * ```ts\n\t * store.clear()\n\t * console.log(store.allRecords().length) // 0\n\t * ```\n\t *\n\t * @public\n\t */\n\tclear(): void {\n\t\tthis.remove(Array.from(this.records.keys()))\n\t}\n\n\t/**\n\t * Update a single record using an updater function. To update multiple records at once,\n\t * use the `update` method of the `TypedStore` class.\n\t *\n\t * @example\n\t * ```ts\n\t * store.update(book.id, (book) => ({\n\t * ...book,\n\t * title: 'Updated Title'\n\t * }))\n\t * ```\n\t *\n\t * @param id - The ID of the record to update\n\t * @param updater - A function that receives the current record and returns the updated record\n\t * @public\n\t */\n\tupdate<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>) {\n\t\tconst existing = this.unsafeGetWithoutCapture(id)\n\t\tif (!existing) {\n\t\t\tconsole.error(`Record ${id} not found. This is probably an error`)\n\t\t\treturn\n\t\t}\n\n\t\tthis.put([updater(existing) as any])\n\t}\n\n\t/**\n\t * Check whether a record with the given ID exists in the store.\n\t *\n\t * @example\n\t * ```ts\n\t * if (store.has(bookId)) {\n\t * console.log('Book exists!')\n\t * }\n\t * ```\n\t *\n\t * @param id - The ID of the record to check\n\t * @returns True if the record exists, false otherwise\n\t * @public\n\t */\n\thas<K extends IdOf<R>>(id: K): boolean {\n\t\treturn this.records.has(id)\n\t}\n\n\t/**\n\t * Add a listener that will be called when the store changes.\n\t * Returns a function to remove the listener.\n\t *\n\t * @example\n\t * ```ts\n\t * const removeListener = store.listen((entry) => {\n\t * console.log('Changes:', entry.changes)\n\t * console.log('Source:', entry.source)\n\t * })\n\t *\n\t * // Listen only to user changes to document records\n\t * const removeDocumentListener = store.listen(\n\t * (entry) => console.log('Document changed:', entry),\n\t * { source: 'user', scope: 'document' }\n\t * )\n\t *\n\t * // Later, remove the listener\n\t * removeListener()\n\t * ```\n\t *\n\t * @param onHistory - The listener function to call when changes occur\n\t * @param filters - Optional filters to control when the listener is called\n\t * @returns A function that removes the listener when called\n\t * @public\n\t */\n\tlisten(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>) {\n\t\t// flush history so that this listener's history starts from exactly now\n\t\tthis._flushHistory()\n\n\t\tconst listener = {\n\t\t\tonHistory,\n\t\t\tfilters: {\n\t\t\t\tsource: filters?.source ?? 'all',\n\t\t\t\tscope: filters?.scope ?? 'all',\n\t\t\t},\n\t\t}\n\n\t\tif (!this.historyReactor.scheduler.isActivelyListening) {\n\t\t\tthis.historyReactor.start()\n\t\t\tthis.historyReactor.scheduler.execute()\n\t\t}\n\n\t\tthis.listeners.add(listener)\n\n\t\treturn () => {\n\t\t\t// Flush so this listener's history ends at exactly now, but not from inside a flush:\n\t\t\t// the other listeners would then receive changes made during that flush before the\n\t\t\t// entry they are still being handed.\n\t\t\tif (!this.isFlushingHistory) {\n\t\t\t\ttry {\n\t\t\t\t\tthis._flushHistory()\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Removers run in teardown loops (Editor.dispose, TLSyncClient.close); throwing here\n\t\t\t\t\t// would skip the cleanups that follow.\n\t\t\t\t\tconsole.error(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.listeners.delete(listener)\n\n\t\t\tif (this.listeners.size === 0) {\n\t\t\t\tthis.historyReactor.stop()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate isMergingRemoteChanges = false\n\n\t/**\n\t * Merge changes from a remote source. Changes made within the provided function\n\t * will be marked with source 'remote' instead of 'user'.\n\t *\n\t * @example\n\t * ```ts\n\t * // Changes from sync/collaboration\n\t * store.mergeRemoteChanges(() => {\n\t * store.put(remoteRecords)\n\t * store.remove(deletedIds)\n\t * })\n\t * ```\n\t *\n\t * @param fn - A function that applies the remote changes\n\t * @public\n\t */\n\tmergeRemoteChanges(fn: () => void) {\n\t\tif (this.isMergingRemoteChanges) {\n\t\t\treturn fn()\n\t\t}\n\n\t\tif (this._isInAtomicOp) {\n\t\t\tthrow new Error('Cannot merge remote changes while in atomic operation')\n\t\t}\n\n\t\ttry {\n\t\t\tthis.atomic(fn, true, true)\n\t\t} finally {\n\t\t\tthis.ensureStoreIsUsable()\n\t\t}\n\t}\n\n\t/**\n\t * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result.\n\t */\n\textractingChanges(fn: () => void): RecordsDiff<R> {\n\t\tconst changes: Array<RecordsDiff<R>> = []\n\t\tconst dispose = this.historyAccumulator.addInterceptor((entry) => changes.push(entry.changes))\n\t\ttry {\n\t\t\ttransact(fn)\n\t\t\treturn squashRecordDiffs(changes)\n\t\t} finally {\n\t\t\tdispose()\n\t\t}\n\t}\n\n\tapplyDiff(\n\t\tdiff: RecordsDiff<R>,\n\t\t{\n\t\t\trunCallbacks = true,\n\t\t\tignoreEphemeralKeys = false,\n\t\t}: { runCallbacks?: boolean; ignoreEphemeralKeys?: boolean } = {}\n\t) {\n\t\tthis.atomic(() => {\n\t\t\tconst toPut = objectMapValues(diff.added)\n\n\t\t\tfor (const [_from, to] of objectMapValues(diff.updated)) {\n\t\t\t\tconst type = this.schema.getType(to.typeName)\n\t\t\t\tif (ignoreEphemeralKeys && type.ephemeralKeySet.size) {\n\t\t\t\t\tconst existing = this.unsafeGetWithoutCapture(to.id)\n\t\t\t\t\tif (!existing) {\n\t\t\t\t\t\ttoPut.push(to)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tlet changed: R | null = null\n\t\t\t\t\tfor (const [key, value] of Object.entries(to)) {\n\t\t\t\t\t\tif (type.ephemeralKeySet.has(key) || Object.is(value, getOwnProperty(existing, key))) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!changed) changed = { ...existing } as R\n\t\t\t\t\t\t;(changed as any)[key] = value\n\t\t\t\t\t}\n\t\t\t\t\t// a key the update removed (present before, absent in `to`) is a change too\n\t\t\t\t\tfor (const key of Object.keys(existing)) {\n\t\t\t\t\t\tif (type.ephemeralKeySet.has(key) || Object.hasOwn(to, key)) continue\n\t\t\t\t\t\tif (!changed) changed = { ...existing } as R\n\t\t\t\t\t\tdelete (changed as any)[key]\n\t\t\t\t\t}\n\t\t\t\t\tif (changed) toPut.push(changed)\n\t\t\t\t} else {\n\t\t\t\t\ttoPut.push(to)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst toRemove = objectMapKeys(diff.removed)\n\t\t\tif (toPut.length) {\n\t\t\t\tthis.put(toPut)\n\t\t\t}\n\t\t\tif (toRemove.length) {\n\t\t\t\tthis.remove(toRemove)\n\t\t\t}\n\t\t}, runCallbacks)\n\t}\n\n\t/**\n\t * Create a cache based on values in the store. Pass in a function that takes and ID and a\n\t * signal for the underlying record. Return a signal (usually a computed) for the cached value.\n\t * For simple derivations, use {@link Store.createComputedCache}. This function is useful if you\n\t * need more precise control over intermediate values.\n\t */\n\tcreateCache<Result, Record extends R = R>(\n\t\tcreate: (id: IdOf<Record>, recordSignal: Signal<R>) => Signal<Result>\n\t) {\n\t\tconst cache = new WeakCache<Atom<any>, Signal<Result>>()\n\t\treturn {\n\t\t\tget: (id: IdOf<Record>) => {\n\t\t\t\tconst atom = this.records.getAtom(id)\n\t\t\t\tif (!atom) return undefined\n\t\t\t\treturn cache.get(atom, () => create(id, atom as Signal<R>)).get()\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Create a computed cache.\n\t *\n\t * @param name - The name of the derivation cache.\n\t * @param derive - A function used to derive the value of the cache.\n\t * @param opts - Options for the computed cache.\n\t * @public\n\t */\n\tcreateComputedCache<Result, Record extends R = R>(\n\t\tname: string,\n\t\tderive: (record: Record) => Result | undefined,\n\t\topts?: CreateComputedCacheOpts<Result, Record>\n\t): ComputedCache<Result, Record> {\n\t\treturn this.createCache((id, record) => {\n\t\t\tconst recordSignal = opts?.areRecordsEqual\n\t\t\t\t? computed(`${name}:${id}:isEqual`, () => record.get(), { isEqual: opts.areRecordsEqual })\n\t\t\t\t: record\n\n\t\t\treturn computed<Result | undefined>(\n\t\t\t\tname + ':' + id,\n\t\t\t\t() => {\n\t\t\t\t\treturn derive(recordSignal.get() as Record)\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tisEqual: opts?.areResultsEqual,\n\t\t\t\t}\n\t\t\t)\n\t\t})\n\t}\n\n\tprivate _integrityChecker?: () => void | undefined\n\n\t/** @internal */\n\tensureStoreIsUsable() {\n\t\tthis.atomic(() => {\n\t\t\tthis._integrityChecker ??= this.schema.createIntegrityChecker(this)\n\t\t\tthis._integrityChecker?.()\n\t\t})\n\t}\n\n\tprivate _isPossiblyCorrupted = false\n\t/** @internal */\n\tmarkAsPossiblyCorrupted() {\n\t\tthis._isPossiblyCorrupted = true\n\t}\n\t/** @internal */\n\tisPossiblyCorrupted() {\n\t\treturn this._isPossiblyCorrupted\n\t}\n\n\tprivate pendingAfterEvents: Map<IdOf<R>, { before: R | null; after: R | null }> | null = null\n\tprivate addDiffForAfterEvent(before: R | null, after: R | null) {\n\t\tassert(this.pendingAfterEvents, 'must be in event operation')\n\t\tif (before === after) return\n\t\tif (before && after) assert(before.id === after.id)\n\t\tif (!before && !after) return\n\t\tconst id = (before || after)!.id\n\t\tconst existing = this.pendingAfterEvents.get(id)\n\t\tif (existing) {\n\t\t\texisting.after = after\n\t\t} else {\n\t\t\tthis.pendingAfterEvents.set(id, { before, after })\n\t\t}\n\t}\n\tprivate flushAtomicCallbacks(isMergingRemoteChanges: boolean) {\n\t\tlet updateDepth = 0\n\t\tlet source: ChangeSource = isMergingRemoteChanges ? 'remote' : 'user'\n\t\twhile (this.pendingAfterEvents) {\n\t\t\tconst events = this.pendingAfterEvents\n\t\t\tthis.pendingAfterEvents = null\n\n\t\t\tif (!this.sideEffects.isEnabled()) continue\n\n\t\t\tupdateDepth++\n\t\t\tif (updateDepth > 100) {\n\t\t\t\tthrow new Error('Maximum store update depth exceeded, bailing out')\n\t\t\t}\n\n\t\t\tfor (const { before, after } of events.values()) {\n\t\t\t\tif (before && after && before !== after && !isEqual(before, after)) {\n\t\t\t\t\tthis.sideEffects.handleAfterChange(before, after, source)\n\t\t\t\t} else if (before && !after) {\n\t\t\t\t\tthis.sideEffects.handleAfterDelete(before, source)\n\t\t\t\t} else if (!before && after) {\n\t\t\t\t\tthis.sideEffects.handleAfterCreate(after, source)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!this.pendingAfterEvents) {\n\t\t\t\tthis.sideEffects.handleOperationComplete(source)\n\t\t\t}\n\t\t\t// Whatever the after-handlers or the operation-complete handlers changed in response to\n\t\t\t// a remote operation is not itself remote: later rounds are attributed to 'user'.\n\t\t\tsource = 'user'\n\t\t}\n\t}\n\tprivate _isInAtomicOp = false\n\t/** @internal */\n\tatomic<T>(fn: () => T, runCallbacks = true, isMergingRemoteChanges = false): T {\n\t\treturn transact(() => {\n\t\t\tif (this._isInAtomicOp) {\n\t\t\t\tif (!this.pendingAfterEvents) this.pendingAfterEvents = new Map()\n\t\t\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\t\t\tassert(!isMergingRemoteChanges, 'cannot call mergeRemoteChanges while in atomic operation')\n\t\t\t\ttry {\n\t\t\t\t\t// if we are in an atomic context with side effects ON allow switching before* callbacks OFF.\n\t\t\t\t\t// but don't allow switching them ON if they had been marked OFF before.\n\t\t\t\t\tif (prevSideEffectsEnabled && !runCallbacks) {\n\t\t\t\t\t\tthis.sideEffects.setIsEnabled(false)\n\t\t\t\t\t}\n\t\t\t\t\treturn fn()\n\t\t\t\t} finally {\n\t\t\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pendingAfterEvents = new Map()\n\t\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\t\t// an operation may switch side effects off, but never on while they are disabled\n\t\t\tthis.sideEffects.setIsEnabled(runCallbacks && prevSideEffectsEnabled)\n\t\t\tthis._isInAtomicOp = true\n\n\t\t\tif (isMergingRemoteChanges) {\n\t\t\t\tthis.isMergingRemoteChanges = true\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst result = fn()\n\t\t\t\tthis.isMergingRemoteChanges = false\n\n\t\t\t\tthis.flushAtomicCallbacks(isMergingRemoteChanges)\n\n\t\t\t\treturn result\n\t\t\t} finally {\n\t\t\t\tthis.pendingAfterEvents = null\n\t\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t\t\tthis._isInAtomicOp = false\n\t\t\t\tthis.isMergingRemoteChanges = false\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @internal */\n\taddHistoryInterceptor(fn: (entry: HistoryEntry<R>, source: ChangeSource) => void) {\n\t\treturn this.historyAccumulator.addInterceptor((entry) =>\n\t\t\tfn(entry, this.isMergingRemoteChanges ? 'remote' : 'user')\n\t\t)\n\t}\n}\n\n/**\n * Collect and squash history entries by their adjacent sources.\n * Adjacent entries from the same source are combined into a single entry.\n *\n * For example: [user, user, remote, remote, user] becomes [user, remote, user]\n *\n * @example\n * ```ts\n * const entries = [\n * { source: 'user', changes: userChanges1 },\n * { source: 'user', changes: userChanges2 },\n * { source: 'remote', changes: remoteChanges }\n * ]\n *\n * const squashed = squashHistoryEntries(entries)\n * // Results in 2 entries: combined user changes + remote changes\n * ```\n *\n * @param entries - The array of history entries to squash\n * @returns An array of squashed history entries\n * @public\n */\nfunction squashHistoryEntries<T extends UnknownRecord>(\n\tentries: HistoryEntry<T>[]\n): HistoryEntry<T>[] {\n\tif (entries.length === 0) return []\n\n\tconst chunked: HistoryEntry<T>[][] = []\n\tlet chunk: HistoryEntry<T>[] = [entries[0]]\n\tlet entry: HistoryEntry<T>\n\n\tfor (let i = 1, n = entries.length; i < n; i++) {\n\t\tentry = entries[i]\n\t\tif (chunk[0].source !== entry.source) {\n\t\t\tchunked.push(chunk)\n\t\t\tchunk = []\n\t\t}\n\t\tchunk.push(entry)\n\t}\n\t// Push the last chunk\n\tchunked.push(chunk)\n\n\treturn devFreeze(\n\t\tchunked.map((chunk) => ({\n\t\t\tsource: chunk[0].source,\n\t\t\t// a single-entry chunk needs no squashing \u2014 skip the O(N) copy of its diff\n\t\t\tchanges:\n\t\t\t\tchunk.length === 1 ? chunk[0].changes : squashRecordDiffs(chunk.map((e) => e.changes)),\n\t\t}))\n\t)\n}\n\n/**\n * Internal class that accumulates history entries before they are flushed to listeners.\n * Handles batching and squashing of adjacent entries from the same source.\n *\n * @internal\n */\nclass HistoryAccumulator<T extends UnknownRecord> {\n\tprivate _history: HistoryEntry<T>[] = []\n\n\tprivate _interceptors: Set<(entry: HistoryEntry<T>) => void> = new Set()\n\n\t/**\n\t * Add an interceptor that will be called for each history entry.\n\t * Returns a function to remove the interceptor.\n\t */\n\taddInterceptor(fn: (entry: HistoryEntry<T>) => void) {\n\t\tthis._interceptors.add(fn)\n\t\treturn () => {\n\t\t\tthis._interceptors.delete(fn)\n\t\t}\n\t}\n\n\t/**\n\t * Add a history entry to the accumulator.\n\t * Calls all registered interceptors with the entry.\n\t */\n\tadd(entry: HistoryEntry<T>) {\n\t\tthis._history.push(entry)\n\t\tfor (const interceptor of this._interceptors) {\n\t\t\tinterceptor(entry)\n\t\t}\n\t}\n\n\t/**\n\t * Flush all accumulated history entries, squashing adjacent entries from the same source.\n\t * Clears the internal history buffer.\n\t */\n\tflush() {\n\t\tconst history = squashHistoryEntries(this._history)\n\t\tthis._history = []\n\t\treturn history\n\t}\n\n\t/**\n\t * Clear all accumulated history entries without flushing.\n\t */\n\tclear() {\n\t\tthis._history = []\n\t}\n\n\t/**\n\t * Check if there are any accumulated history entries.\n\t */\n\thasChanges() {\n\t\treturn this._history.length > 0\n\t}\n}\n\n/**\n * A store or an object containing a store.\n * This type is used for APIs that can accept either a store directly or an object with a store property.\n *\n * @example\n * ```ts\n * function useStore(storeOrObject: StoreObject<MyRecord>) {\n * const store = storeOrObject instanceof Store ? storeOrObject : storeOrObject.store\n * return store\n * }\n * ```\n *\n * @public\n */\nexport type StoreObject<R extends UnknownRecord> = Store<R> | { store: Store<R> }\n/**\n * Extract the record type from a StoreObject.\n *\n * @example\n * ```ts\n * type MyStoreObject = { store: Store<Book | Author> }\n * type Records = StoreObjectRecordType<MyStoreObject> // Book | Author\n * ```\n *\n * @public\n */\nexport type StoreObjectRecordType<Context extends StoreObject<any>> =\n\tContext extends Store<infer R> ? R : Context extends { store: Store<infer R> } ? R : never\n\n/**\n * Create a computed cache that works with any StoreObject (store or object containing a store).\n * This is a standalone version of Store.createComputedCache that can work with multiple store instances.\n *\n * @example\n * ```ts\n * const expensiveCache = createComputedCache(\n * 'expensiveData',\n * (context: { store: Store<Book> }, book: Book) => {\n * return performExpensiveCalculation(book)\n * }\n * )\n *\n * // Use with different store instances\n * const result1 = expensiveCache.get(storeObject1, bookId)\n * const result2 = expensiveCache.get(storeObject2, bookId)\n * ```\n *\n * @param name - A unique name for the cache (used for debugging)\n * @param derive - Function that derives a value from the context and record\n * @param opts - Optional configuration for equality checks\n * @returns A cache that can be used with multiple store instances\n * @public\n */\nexport function createComputedCache<\n\tContext extends StoreObject<any>,\n\tResult,\n\tRecord extends StoreObjectRecordType<Context> = StoreObjectRecordType<Context>,\n>(\n\tname: string,\n\tderive: (context: Context, record: Record) => Result | undefined,\n\topts?: CreateComputedCacheOpts<Result, Record>\n) {\n\tconst cache = new WeakCache<Context, ComputedCache<Result, Record>>()\n\treturn {\n\t\tget(context: Context, id: IdOf<Record>) {\n\t\t\tconst computedCache = cache.get(context, () => {\n\t\t\t\tconst store = (context instanceof Store ? context : context.store) as Store<Record>\n\t\t\t\treturn store.createComputedCache(name, (record) => derive(context, record), opts)\n\t\t\t})\n\t\t\treturn computedCache.get(id)\n\t\t},\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Atom, Reactor, Signal, atom, computed, reactor, transact } from '@tldraw/state'\nimport {\n\tWeakCache,\n\tassert,\n\tfilterEntries,\n\tgetOwnProperty,\n\tisEqual,\n\tobjectMapEntries,\n\tobjectMapKeys,\n\tobjectMapValues,\n\tthrottleToNextFrame,\n\tuniqueId,\n} from '@tldraw/utils'\nimport { AtomMap } from './AtomMap'\nimport { IdOf, RecordId, UnknownRecord } from './BaseRecord'\nimport { devFreeze } from './devFreeze'\nimport { hasAnyKey, isRecordsDiffEmpty, RecordsDiff, squashRecordDiffs } from './RecordsDiff'\nimport { RecordScope } from './RecordType'\nimport { StoreQueries } from './StoreQueries'\nimport { SerializedSchema, StoreSchema } from './StoreSchema'\nimport { StoreSideEffects } from './StoreSideEffects'\n\n/**\n * Extracts the record type from a record ID type.\n *\n * @example\n * ```ts\n * type BookId = RecordId<Book>\n * type BookType = RecordFromId<BookId> // Book\n * ```\n *\n * @public\n */\nexport type RecordFromId<K extends RecordId<UnknownRecord>> =\n\tK extends RecordId<infer R> ? R : never\n\n/**\n * A diff describing the changes to a collection.\n *\n * @example\n * ```ts\n * const diff: CollectionDiff<string> = {\n * added: new Set(['newItem']),\n * removed: new Set(['oldItem'])\n * }\n * ```\n *\n * @public\n */\nexport interface CollectionDiff<T> {\n\t/** Items that were added to the collection */\n\tadded?: Set<T>\n\t/** Items that were removed from the collection */\n\tremoved?: Set<T>\n}\n\n/**\n * The source of a change to the store.\n * - `'user'` - Changes originating from local user actions\n * - `'remote'` - Changes originating from remote synchronization\n *\n * @public\n */\nexport type ChangeSource = 'user' | 'remote'\n\n/**\n * Filters for store listeners to control which changes trigger the listener.\n *\n * @example\n * ```ts\n * const filters: StoreListenerFilters = {\n * source: 'user', // Only listen to user changes\n * scope: 'document' // Only listen to document-scoped records\n * }\n * ```\n *\n * @public\n */\nexport interface StoreListenerFilters {\n\t/** Filter by the source of changes */\n\tsource: ChangeSource | 'all'\n\t/** Filter by the scope of records */\n\tscope: RecordScope | 'all'\n}\n\n/**\n * An entry containing changes that originated either by user actions or remote changes.\n * History entries are used to track and replay changes to the store.\n *\n * @example\n * ```ts\n * const entry: HistoryEntry<Book> = {\n * changes: {\n * added: { 'book:123': bookRecord },\n * updated: {},\n * removed: {}\n * },\n * source: 'user'\n * }\n * ```\n *\n * @public\n */\nexport interface HistoryEntry<R extends UnknownRecord = UnknownRecord> {\n\t/** The changes that occurred in this history entry */\n\tchanges: RecordsDiff<R>\n\t/** The source of these changes */\n\tsource: ChangeSource\n}\n\n/**\n * A function that will be called when the history changes.\n *\n * @example\n * ```ts\n * const listener: StoreListener<Book> = (entry) => {\n * console.log('Changes:', entry.changes)\n * console.log('Source:', entry.source)\n * }\n *\n * store.listen(listener)\n * ```\n *\n * @param entry - The history entry containing the changes\n *\n * @public\n */\nexport type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void\n\n/**\n * A computed cache that stores derived data for records.\n * The cache automatically updates when underlying records change and cleans up when records are deleted.\n *\n * @example\n * ```ts\n * const expensiveCache = store.createComputedCache(\n * 'expensive',\n * (book: Book) => performExpensiveCalculation(book)\n * )\n *\n * const result = expensiveCache.get(bookId)\n * ```\n *\n * @public\n */\nexport interface ComputedCache<Data, R extends UnknownRecord> {\n\t/**\n\t * Get the cached data for a record by its ID.\n\t *\n\t * @param id - The ID of the record\n\t * @returns The cached data or undefined if the record doesn't exist\n\t */\n\tget(id: IdOf<R>): Data | undefined\n}\n\n/**\n * Options for creating a computed cache.\n *\n * @example\n * ```ts\n * const options: CreateComputedCacheOpts<string[], Book> = {\n * areRecordsEqual: (a, b) => a.title === b.title,\n * areResultsEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b)\n * }\n * ```\n *\n * @public\n */\nexport interface CreateComputedCacheOpts<Data, R extends UnknownRecord> {\n\t/** Custom equality function for comparing records */\n\tareRecordsEqual?(a: R, b: R): boolean\n\t/** Custom equality function for comparing results */\n\tareResultsEqual?(a: Data, b: Data): boolean\n}\n\n/**\n * A serialized snapshot of the record store's values.\n * This is a plain JavaScript object that can be saved to storage or transmitted over the network.\n *\n * @example\n * ```ts\n * const serialized: SerializedStore<Book> = {\n * 'book:123': { id: 'book:123', typeName: 'book', title: 'The Lathe of Heaven' },\n * 'book:456': { id: 'book:456', typeName: 'book', title: 'The Left Hand of Darkness' }\n * }\n * ```\n *\n * @public\n */\nexport type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>\n\n/**\n * A snapshot of the store including both data and schema information.\n * This enables proper migration when loading data from different schema versions.\n *\n * @example\n * ```ts\n * const snapshot = store.getStoreSnapshot()\n * // Later...\n * store.loadStoreSnapshot(snapshot)\n * ```\n *\n * @public\n */\nexport interface StoreSnapshot<R extends UnknownRecord> {\n\t/** The serialized store data */\n\tstore: SerializedStore<R>\n\t/** The serialized schema information */\n\tschema: SerializedSchema\n}\n\n/**\n * A validator for store records that ensures data integrity.\n * Validators are called when records are created or updated.\n *\n * @example\n * ```ts\n * const bookValidator: StoreValidator<Book> = {\n * validate(record: unknown): Book {\n * // Validate and return the record\n * if (typeof record !== 'object' || !record.title) {\n * throw new Error('Invalid book')\n * }\n * return record as Book\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface StoreValidator<R extends UnknownRecord> {\n\t/**\n\t * Validate a record.\n\t *\n\t * @param record - The record to validate\n\t * @returns The validated record\n\t * @throws When validation fails\n\t */\n\tvalidate(record: unknown): R\n\t/**\n\t * Validate a record using a known good version for reference.\n\t *\n\t * @param knownGoodVersion - A known valid version of the record\n\t * @param record - The record to validate\n\t * @returns The validated record\n\t */\n\tvalidateUsingKnownGoodVersion?(knownGoodVersion: R, record: unknown): R\n}\n\n/**\n * A map of validators for each record type in the store.\n *\n * @example\n * ```ts\n * const validators: StoreValidators<Book | Author> = {\n * book: bookValidator,\n * author: authorValidator\n * }\n * ```\n *\n * @public\n */\nexport type StoreValidators<R extends UnknownRecord> = {\n\t[K in R['typeName']]: StoreValidator<Extract<R, { typeName: K }>>\n}\n\n/**\n * Information about an error that occurred in the store.\n *\n * @example\n * ```ts\n * const error: StoreError = {\n * error: new Error('Validation failed'),\n * phase: 'updateRecord',\n * recordBefore: oldRecord,\n * recordAfter: newRecord,\n * isExistingValidationIssue: false\n * }\n * ```\n *\n * @public\n */\nexport interface StoreError {\n\t/** The error that occurred */\n\terror: Error\n\t/** The phase during which the error occurred */\n\tphase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'\n\t/** The record state before the operation (if applicable) */\n\trecordBefore?: unknown\n\t/** The record state after the operation */\n\trecordAfter: unknown\n\t/** Whether this is an existing validation issue */\n\tisExistingValidationIssue: boolean\n}\n\n/**\n * Extract the record type from a Store type.\n * Used internally for type inference.\n *\n * @internal\n */\nexport type StoreRecord<S extends Store<any>> = S extends Store<infer R> ? R : never\n\n/**\n * A reactive store that manages collections of typed records.\n *\n * The Store is the central container for your application's data, providing:\n * - Reactive state management with automatic updates\n * - Type-safe record operations\n * - History tracking and change notifications\n * - Schema validation and migrations\n * - Side effects and business logic hooks\n * - Efficient querying and indexing\n *\n * @example\n * ```ts\n * // Create a store with schema\n * const schema = StoreSchema.create({\n * book: Book,\n * author: Author\n * })\n *\n * const store = new Store({\n * schema,\n * props: {}\n * })\n *\n * // Add records\n * const book = Book.create({ title: 'The Lathe of Heaven', author: 'Le Guin' })\n * store.put([book])\n *\n * // Listen to changes\n * store.listen((entry) => {\n * console.log('Changes:', entry.changes)\n * })\n * ```\n *\n * @public\n */\nexport class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {\n\t/**\n\t * The unique identifier of the store instance.\n\t *\n\t * @public\n\t */\n\tpublic readonly id: string\n\t/**\n\t * An AtomMap containing the stores records.\n\t *\n\t * @internal\n\t * @readonly\n\t */\n\tprivate readonly records: AtomMap<IdOf<R>, R>\n\n\t/**\n\t * An atom containing the store's history.\n\t *\n\t * @public\n\t * @readonly\n\t */\n\treadonly history: Atom<number, RecordsDiff<R>> = atom('history', 0, {\n\t\thistoryLength: 1000,\n\t})\n\n\t/**\n\t * Reactive queries and indexes for efficiently accessing store data.\n\t * Provides methods for filtering, indexing, and subscribing to subsets of records.\n\t *\n\t * @example\n\t * ```ts\n\t * // Create an index by a property\n\t * const booksByAuthor = store.query.index('book', 'author')\n\t *\n\t * // Get records matching criteria\n\t * const inStockBooks = store.query.records('book', () => ({\n\t * inStock: { eq: true }\n\t * }))\n\t * ```\n\t *\n\t * @public\n\t * @readonly\n\t */\n\treadonly query: StoreQueries<R>\n\n\t/**\n\t * A set containing listeners that have been added to this store.\n\t *\n\t * @internal\n\t */\n\tprivate listeners = new Set<{ onHistory: StoreListener<R>; filters: StoreListenerFilters }>()\n\n\t/**\n\t * An array of history entries that have not yet been flushed.\n\t *\n\t * @internal\n\t */\n\tprivate historyAccumulator = new HistoryAccumulator<R>()\n\n\t/**\n\t * A reactor that responds to changes to the history by squashing the accumulated history and\n\t * notifying listeners of the changes.\n\t *\n\t * @internal\n\t */\n\tprivate historyReactor: Reactor\n\n\t/**\n\t * Cancels the history flush scheduled for the next frame, if any.\n\t *\n\t * @internal\n\t */\n\tprivate cancelHistoryReactor: null | (() => void) = null\n\n\t/**\n\t * The schema that defines the structure and validation rules for records in this store.\n\t *\n\t * @public\n\t */\n\treadonly schema: StoreSchema<R, Props>\n\n\t/**\n\t * Custom properties associated with this store instance.\n\t *\n\t * @public\n\t */\n\treadonly props: Props\n\n\t/**\n\t * A mapping of record scopes to the set of record type names that belong to each scope.\n\t * Used to filter records by their persistence and synchronization behavior.\n\t *\n\t * @public\n\t */\n\tpublic readonly scopedTypes: { readonly [K in RecordScope]: ReadonlySet<R['typeName']> }\n\n\t/**\n\t * Side effects manager that handles lifecycle events for record operations.\n\t * Allows registration of callbacks for create, update, delete, and validation events.\n\t *\n\t * @example\n\t * ```ts\n\t * store.sideEffects.registerAfterCreateHandler('book', (book) => {\n\t * console.log('Book created:', book.title)\n\t * })\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic readonly sideEffects = new StoreSideEffects<R>(this)\n\n\t/**\n\t * Creates a new Store instance.\n\t *\n\t * @example\n\t * ```ts\n\t * const store = new Store({\n\t * schema: StoreSchema.create({ book: Book }),\n\t * props: { appName: 'MyLibrary' },\n\t * initialData: savedData\n\t * })\n\t * ```\n\t *\n\t * @param config - Configuration object for the store\n\t */\n\tconstructor(config: {\n\t\t/** Optional unique identifier for the store */\n\t\tid?: string\n\t\t/** The store's initial data to populate on creation */\n\t\tinitialData?: SerializedStore<R>\n\t\t/** The schema defining record types, validation, and migrations */\n\t\tschema: StoreSchema<R, Props>\n\t\t/** Custom properties for the store instance */\n\t\tprops: Props\n\t}) {\n\t\tconst { initialData, schema, id } = config\n\n\t\tthis.id = id ?? uniqueId()\n\t\tthis.schema = schema\n\t\tthis.props = config.props\n\n\t\tif (initialData) {\n\t\t\tthis.records = new AtomMap(\n\t\t\t\t'store',\n\t\t\t\tobjectMapEntries(initialData).map(([id, record]) => [\n\t\t\t\t\tid,\n\t\t\t\t\tdevFreeze(this.schema.validateRecord(this, record, 'initialize', null)),\n\t\t\t\t])\n\t\t\t)\n\t\t} else {\n\t\t\tthis.records = new AtomMap('store')\n\t\t}\n\n\t\tthis.query = new StoreQueries<R>(this.records, this.history)\n\n\t\tthis.historyReactor = reactor(\n\t\t\t'Store.historyReactor',\n\t\t\t() => {\n\t\t\t\t// deref to make sure we're subscribed regardless of whether we need to propagate\n\t\t\t\tthis.history.get()\n\t\t\t\t// If we have accumulated history, flush it and update listeners\n\t\t\t\tthis._flushHistory()\n\t\t\t},\n\t\t\t{ scheduleEffect: (cb) => (this.cancelHistoryReactor = throttleToNextFrame(cb)) }\n\t\t)\n\t\tconst scopedTypes: { [K in RecordScope]: Set<R['typeName']> } = {\n\t\t\tdocument: new Set(),\n\t\t\tsession: new Set(),\n\t\t\tpresence: new Set(),\n\t\t}\n\t\tfor (const type of objectMapValues(this.schema.types)) {\n\t\t\tscopedTypes[type.scope].add(type.typeName)\n\t\t}\n\t\tthis.scopedTypes = scopedTypes\n\t}\n\n\tprivate isFlushingHistory = false\n\n\tpublic _flushHistory() {\n\t\t// If we have accumulated history, flush it and update listeners\n\t\tconst version = this.history.__unsafe__getWithoutCapture()\n\t\tconst entries = this.historyAccumulator.flush(version)\n\t\tif (entries.length > 0) {\n\t\t\tconst errors: unknown[] = []\n\t\t\tconst wasFlushingHistory = this.isFlushingHistory\n\t\t\tthis.isFlushingHistory = true\n\t\t\tfor (const { changes, source } of entries) {\n\t\t\t\t// Filtered diffs are computed at most once per scope per entry, and shared by every\n\t\t\t\t// listener watching that scope.\n\t\t\t\tconst scopedChanges = new Map<RecordScope, RecordsDiff<R> | null>()\n\t\t\t\tfor (const { onHistory, filters } of this.listeners) {\n\t\t\t\t\tif (filters.source !== 'all' && filters.source !== source) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tlet listenerChanges = changes\n\t\t\t\t\tif (filters.scope !== 'all') {\n\t\t\t\t\t\tif (!scopedChanges.has(filters.scope)) {\n\t\t\t\t\t\t\tscopedChanges.set(filters.scope, this.filterChangesByScope(changes, filters.scope))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst filtered = scopedChanges.get(filters.scope)\n\t\t\t\t\t\tif (!filtered) continue\n\t\t\t\t\t\tlistenerChanges = filtered\n\t\t\t\t\t}\n\t\t\t\t\t// The entries are already dequeued, so a listener that throws must not stop the others\n\t\t\t\t\t// (e.g. a sync client) from receiving them: deliver to all, then rethrow the first error.\n\t\t\t\t\ttry {\n\t\t\t\t\t\tonHistory({ changes: listenerChanges, source })\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\terrors.push(error)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.isFlushingHistory = wasFlushingHistory\n\t\t\tif (errors.length > 0) throw errors[0]\n\t\t}\n\t}\n\n\tdispose() {\n\t\t// Deliver what is still pending first: a change-set made in the same frame as the dispose\n\t\t// would otherwise never reach the listeners (e.g. a sync client) still attached to the store.\n\t\ttry {\n\t\t\tthis._flushHistory()\n\t\t} finally {\n\t\t\tthis.cancelHistoryReactor?.()\n\t\t}\n\t}\n\n\t/**\n\t * Filters out non-document changes from a diff. Returns null if there are no changes left.\n\t * @param change - the records diff\n\t * @param scope - the records scope\n\t * @returns\n\t */\n\tfilterChangesByScope(change: RecordsDiff<R>, scope: RecordScope) {\n\t\tconst result = {\n\t\t\tadded: filterEntries(change.added, (_, r) => this.scopedTypes[scope].has(r.typeName)),\n\t\t\tupdated: filterEntries(change.updated, (_, r) => this.scopedTypes[scope].has(r[1].typeName)),\n\t\t\tremoved: filterEntries(change.removed, (_, r) => this.scopedTypes[scope].has(r.typeName)),\n\t\t}\n\t\tif (isRecordsDiffEmpty(result)) {\n\t\t\treturn null\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Update the history with a diff of changes.\n\t *\n\t * @param changes - The changes to add to the history.\n\t */\n\tprivate updateHistory(changes: RecordsDiff<R>): void {\n\t\t// Don't capture: a reaction that writes to the store must not become a dependent of the\n\t\t// history atom, or every other store change would re-run it (and it could loop forever).\n\t\tconst version = this.history.__unsafe__getWithoutCapture() + 1\n\t\t// Set the atom before recording the entry: interceptors run inside `add`, and a flush they\n\t\t// trigger (e.g. via `listen`) would otherwise discard the entry as not yet committed.\n\t\tthis.history.set(version, changes)\n\t\tthis.historyAccumulator.add(\n\t\t\t{\n\t\t\t\tchanges,\n\t\t\t\tsource: this.isMergingRemoteChanges ? 'remote' : 'user',\n\t\t\t},\n\t\t\tversion\n\t\t)\n\t\tif (this.listeners.size === 0) {\n\t\t\tthis.historyAccumulator.clear()\n\t\t}\n\t}\n\n\tvalidate(phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests') {\n\t\tthis.allRecords().forEach((record) => this.schema.validateRecord(this, record, phase, null))\n\t}\n\n\t/**\n\t * Add or update records in the store. If a record with the same ID already exists, it will be updated.\n\t * Otherwise, a new record will be created.\n\t *\n\t * @example\n\t * ```ts\n\t * // Add new records\n\t * const book = Book.create({ title: 'Lathe Of Heaven', author: 'Le Guin' })\n\t * store.put([book])\n\t *\n\t * // Update existing record\n\t * store.put([{ ...book, title: 'The Lathe of Heaven' }])\n\t * ```\n\t *\n\t * @param records - The records to add or update\n\t * @param phaseOverride - Override the validation phase (used internally)\n\t * @public\n\t */\n\tput(records: R[], phaseOverride?: 'initialize'): void {\n\t\tthis.atomic(() => {\n\t\t\tconst updates: Record<IdOf<UnknownRecord>, [from: R, to: R]> = {}\n\t\t\tconst additions: Record<IdOf<UnknownRecord>, R> = {}\n\n\t\t\t// Iterate through all records, creating, updating or removing as needed\n\t\t\tlet record: R\n\n\t\t\tconst source = this.isMergingRemoteChanges ? 'remote' : 'user'\n\n\t\t\tfor (let i = 0, n = records.length; i < n; i++) {\n\t\t\t\trecord = records[i]\n\n\t\t\t\tconst initialValue = this.records.__unsafe__getWithoutCapture(record.id)\n\t\t\t\t// If we already have an atom for this record, update its value.\n\t\t\t\tif (initialValue) {\n\t\t\t\t\t// If we have a beforeUpdate callback, run it against the initial and next records\n\t\t\t\t\trecord = this.sideEffects.handleBeforeChange(initialValue, record, source)\n\n\t\t\t\t\t// Validate the record\n\t\t\t\t\tconst validated = this.schema.validateRecord(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tphaseOverride ?? 'updateRecord',\n\t\t\t\t\t\tinitialValue\n\t\t\t\t\t)\n\n\t\t\t\t\tif (validated === initialValue) continue\n\n\t\t\t\t\trecord = devFreeze(validated)\n\t\t\t\t\tthis.records.set(record.id, record)\n\n\t\t\t\t\tif (additions[record.id]) {\n\t\t\t\t\t\t// the same record was created earlier in this call: fold the update into it\n\t\t\t\t\t\tadditions[record.id] = record\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// an earlier update to the same record in this call owns the `from`\n\t\t\t\t\t\tconst from = updates[record.id]?.[0] ?? initialValue\n\t\t\t\t\t\tif (from === record) {\n\t\t\t\t\t\t\t// back to where it started within this call: nothing to record\n\t\t\t\t\t\t\tdelete updates[record.id]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tupdates[record.id] = [from, record]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.addDiffForAfterEvent(initialValue, record)\n\t\t\t\t} else {\n\t\t\t\t\trecord = this.sideEffects.handleBeforeCreate(record, source)\n\n\t\t\t\t\t// If we don't have an atom, create one.\n\n\t\t\t\t\t// Validate the record\n\t\t\t\t\trecord = this.schema.validateRecord(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\trecord as R,\n\t\t\t\t\t\tphaseOverride ?? 'createRecord',\n\t\t\t\t\t\tnull\n\t\t\t\t\t)\n\n\t\t\t\t\t// freeze it\n\t\t\t\t\trecord = devFreeze(record)\n\n\t\t\t\t\t// Mark the change as a new addition.\n\t\t\t\t\tadditions[record.id] = record\n\t\t\t\t\tthis.addDiffForAfterEvent(null, record)\n\n\t\t\t\t\tthis.records.set(record.id, record)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Validation may have left every record identical to before, in which case there is no\n\t\t\t// change-set to record.\n\t\t\tif (!hasAnyKey(additions) && !hasAnyKey(updates)) return\n\t\t\tthis.updateHistory({\n\t\t\t\tadded: additions,\n\t\t\t\tupdated: updates,\n\t\t\t\tremoved: {} as Record<IdOf<R>, R>,\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Remove records from the store by their IDs.\n\t *\n\t * @example\n\t * ```ts\n\t * // Remove a single record\n\t * store.remove([book.id])\n\t *\n\t * // Remove multiple records\n\t * store.remove([book1.id, book2.id, book3.id])\n\t * ```\n\t *\n\t * @param ids - The IDs of the records to remove\n\t * @public\n\t */\n\tremove(ids: IdOf<R>[]): void {\n\t\tthis.atomic(() => {\n\t\t\tconst toDelete = new Set<IdOf<R>>(ids)\n\t\t\tconst source = this.isMergingRemoteChanges ? 'remote' : 'user'\n\n\t\t\tif (this.sideEffects.isEnabled()) {\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst record = this.records.__unsafe__getWithoutCapture(id)\n\t\t\t\t\tif (!record) continue\n\n\t\t\t\t\tif (this.sideEffects.handleBeforeDelete(record, source) === false) {\n\t\t\t\t\t\ttoDelete.delete(id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst actuallyDeleted = this.records.deleteMany(toDelete)\n\t\t\tif (actuallyDeleted.length === 0) return\n\n\t\t\tconst removed = {} as RecordsDiff<R>['removed']\n\t\t\tfor (const [id, record] of actuallyDeleted) {\n\t\t\t\tremoved[id] = record\n\t\t\t\tthis.addDiffForAfterEvent(record, null)\n\t\t\t}\n\n\t\t\t// Update the history with the removed records.\n\t\t\tthis.updateHistory({ added: {}, updated: {}, removed } as RecordsDiff<R>)\n\t\t})\n\t}\n\n\t/**\n\t * Get a record by its ID. This creates a reactive subscription to the record.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = store.get(bookId)\n\t * if (book) {\n\t * console.log(book.title)\n\t * }\n\t * ```\n\t *\n\t * @param id - The ID of the record to get\n\t * @returns The record if it exists, undefined otherwise\n\t * @public\n\t */\n\tget<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {\n\t\treturn this.records.get(id) as RecordFromId<K> | undefined\n\t}\n\n\t/**\n\t * Get a record by its ID without creating a reactive subscription.\n\t * Use this when you need to access a record but don't want reactive updates.\n\t *\n\t * @example\n\t * ```ts\n\t * // Won't trigger reactive updates when this record changes\n\t * const book = store.unsafeGetWithoutCapture(bookId)\n\t * ```\n\t *\n\t * @param id - The ID of the record to get\n\t * @returns The record if it exists, undefined otherwise\n\t * @public\n\t */\n\tunsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {\n\t\treturn this.records.__unsafe__getWithoutCapture(id) as RecordFromId<K> | undefined\n\t}\n\n\t/**\n\t * Serialize the store's records to a plain JavaScript object.\n\t * Only includes records matching the specified scope.\n\t *\n\t * @example\n\t * ```ts\n\t * // Serialize only document records (default)\n\t * const documentData = store.serialize('document')\n\t *\n\t * // Serialize all records\n\t * const allData = store.serialize('all')\n\t * ```\n\t *\n\t * @param scope - The scope of records to serialize. Defaults to 'document'\n\t * @returns The serialized store data\n\t * @public\n\t */\n\tserialize(scope: RecordScope | 'all' = 'document'): SerializedStore<R> {\n\t\tconst result = {} as SerializedStore<R>\n\t\tfor (const [id, record] of this.records) {\n\t\t\tif (scope === 'all' || this.scopedTypes[scope].has(record.typeName)) {\n\t\t\t\tresult[id as IdOf<R>] = record\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Get a serialized snapshot of the store and its schema.\n\t * This includes both the data and schema information needed for proper migration.\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = store.getStoreSnapshot()\n\t * localStorage.setItem('myApp', JSON.stringify(snapshot))\n\t *\n\t * // Later...\n\t * const saved = JSON.parse(localStorage.getItem('myApp'))\n\t * store.loadStoreSnapshot(saved)\n\t * ```\n\t *\n\t * @param scope - The scope of records to serialize. Defaults to 'document'\n\t * @returns A snapshot containing both store data and schema information\n\t * @public\n\t */\n\tgetStoreSnapshot(scope: RecordScope | 'all' = 'document'): StoreSnapshot<R> {\n\t\treturn {\n\t\t\tstore: this.serialize(scope),\n\t\t\tschema: this.schema.serialize(),\n\t\t}\n\t}\n\n\t/**\n\t * Migrate a serialized snapshot to the current schema version.\n\t * This applies any necessary migrations to bring old data up to date.\n\t *\n\t * @example\n\t * ```ts\n\t * const oldSnapshot = JSON.parse(localStorage.getItem('myApp'))\n\t * const migratedSnapshot = store.migrateSnapshot(oldSnapshot)\n\t * ```\n\t *\n\t * @param snapshot - The snapshot to migrate\n\t * @returns The migrated snapshot with current schema version\n\t * @throws Error if migration fails\n\t * @public\n\t */\n\tmigrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R> {\n\t\tconst migrationResult = this.schema.migrateStoreSnapshot(snapshot)\n\n\t\tif (migrationResult.type === 'error') {\n\t\t\tthrow new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)\n\t\t}\n\n\t\treturn {\n\t\t\tstore: migrationResult.value,\n\t\t\tschema: this.schema.serialize(),\n\t\t}\n\t}\n\n\t/**\n\t * Load a serialized snapshot into the store, replacing all current data.\n\t * The snapshot will be automatically migrated to the current schema version if needed.\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = JSON.parse(localStorage.getItem('myApp'))\n\t * store.loadStoreSnapshot(snapshot)\n\t * ```\n\t *\n\t * @param snapshot - The snapshot to load\n\t * @throws Error if migration fails or snapshot is invalid\n\t * @public\n\t */\n\tloadStoreSnapshot(snapshot: StoreSnapshot<R>): void {\n\t\tconst migrationResult = this.schema.migrateStoreSnapshot(snapshot)\n\n\t\tif (migrationResult.type === 'error') {\n\t\t\tthrow new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)\n\t\t}\n\n\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\ttry {\n\t\t\tthis.sideEffects.setIsEnabled(false)\n\t\t\tthis.atomic(() => {\n\t\t\t\tthis.clear()\n\t\t\t\tthis.put(Object.values(migrationResult.value))\n\t\t\t\tthis.ensureStoreIsUsable()\n\t\t\t}, false)\n\t\t} finally {\n\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t}\n\t}\n\n\t/**\n\t * Get an array of all records in the store.\n\t *\n\t * @example\n\t * ```ts\n\t * const allRecords = store.allRecords()\n\t * const books = allRecords.filter(r => r.typeName === 'book')\n\t * ```\n\t *\n\t * @returns An array containing all records in the store\n\t * @public\n\t */\n\tallRecords(): R[] {\n\t\treturn Array.from(this.records.values())\n\t}\n\n\t/**\n\t * Remove all records from the store.\n\t *\n\t * @example\n\t * ```ts\n\t * store.clear()\n\t * console.log(store.allRecords().length) // 0\n\t * ```\n\t *\n\t * @public\n\t */\n\tclear(): void {\n\t\tthis.remove(Array.from(this.records.keys()))\n\t}\n\n\t/**\n\t * Update a single record using an updater function. To update multiple records at once,\n\t * use the `update` method of the `TypedStore` class.\n\t *\n\t * @example\n\t * ```ts\n\t * store.update(book.id, (book) => ({\n\t * ...book,\n\t * title: 'Updated Title'\n\t * }))\n\t * ```\n\t *\n\t * @param id - The ID of the record to update\n\t * @param updater - A function that receives the current record and returns the updated record\n\t * @public\n\t */\n\tupdate<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>) {\n\t\tconst existing = this.unsafeGetWithoutCapture(id)\n\t\tif (!existing) {\n\t\t\tconsole.error(`Record ${id} not found. This is probably an error`)\n\t\t\treturn\n\t\t}\n\n\t\tthis.put([updater(existing) as any])\n\t}\n\n\t/**\n\t * Check whether a record with the given ID exists in the store.\n\t *\n\t * @example\n\t * ```ts\n\t * if (store.has(bookId)) {\n\t * console.log('Book exists!')\n\t * }\n\t * ```\n\t *\n\t * @param id - The ID of the record to check\n\t * @returns True if the record exists, false otherwise\n\t * @public\n\t */\n\thas<K extends IdOf<R>>(id: K): boolean {\n\t\treturn this.records.has(id)\n\t}\n\n\t/**\n\t * Add a listener that will be called when the store changes.\n\t * Returns a function to remove the listener.\n\t *\n\t * @example\n\t * ```ts\n\t * const removeListener = store.listen((entry) => {\n\t * console.log('Changes:', entry.changes)\n\t * console.log('Source:', entry.source)\n\t * })\n\t *\n\t * // Listen only to user changes to document records\n\t * const removeDocumentListener = store.listen(\n\t * (entry) => console.log('Document changed:', entry),\n\t * { source: 'user', scope: 'document' }\n\t * )\n\t *\n\t * // Later, remove the listener\n\t * removeListener()\n\t * ```\n\t *\n\t * @param onHistory - The listener function to call when changes occur\n\t * @param filters - Optional filters to control when the listener is called\n\t * @returns A function that removes the listener when called\n\t * @public\n\t */\n\tlisten(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>) {\n\t\t// flush history so that this listener's history starts from exactly now\n\t\tthis._flushHistory()\n\n\t\tconst listener = {\n\t\t\tonHistory,\n\t\t\tfilters: {\n\t\t\t\tsource: filters?.source ?? 'all',\n\t\t\t\tscope: filters?.scope ?? 'all',\n\t\t\t},\n\t\t}\n\n\t\tif (!this.historyReactor.scheduler.isActivelyListening) {\n\t\t\tthis.historyReactor.start()\n\t\t\tthis.historyReactor.scheduler.execute()\n\t\t}\n\n\t\tthis.listeners.add(listener)\n\n\t\treturn () => {\n\t\t\t// Flush so this listener's history ends at exactly now, but not from inside a flush:\n\t\t\t// the other listeners would then receive changes made during that flush before the\n\t\t\t// entry they are still being handed.\n\t\t\tif (!this.isFlushingHistory) {\n\t\t\t\ttry {\n\t\t\t\t\tthis._flushHistory()\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Removers run in teardown loops (Editor.dispose, TLSyncClient.close); throwing here\n\t\t\t\t\t// would skip the cleanups that follow.\n\t\t\t\t\tconsole.error(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.listeners.delete(listener)\n\n\t\t\tif (this.listeners.size === 0) {\n\t\t\t\tthis.historyReactor.stop()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate isMergingRemoteChanges = false\n\n\t/**\n\t * Merge changes from a remote source. Changes made within the provided function\n\t * will be marked with source 'remote' instead of 'user'.\n\t *\n\t * @example\n\t * ```ts\n\t * // Changes from sync/collaboration\n\t * store.mergeRemoteChanges(() => {\n\t * store.put(remoteRecords)\n\t * store.remove(deletedIds)\n\t * })\n\t * ```\n\t *\n\t * @param fn - A function that applies the remote changes\n\t * @public\n\t */\n\tmergeRemoteChanges(fn: () => void) {\n\t\tif (this.isMergingRemoteChanges) {\n\t\t\treturn fn()\n\t\t}\n\n\t\tif (this._isInAtomicOp) {\n\t\t\tthrow new Error('Cannot merge remote changes while in atomic operation')\n\t\t}\n\n\t\ttry {\n\t\t\tthis.atomic(fn, true, true)\n\t\t} finally {\n\t\t\tthis.ensureStoreIsUsable()\n\t\t}\n\t}\n\n\t/**\n\t * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result.\n\t */\n\textractingChanges(fn: () => void): RecordsDiff<R> {\n\t\tconst collected: Array<{ version: number; changes: RecordsDiff<R> }> = []\n\t\tconst dispose = this.historyAccumulator.addInterceptor((entry, version) => {\n\t\t\t// a nested transaction that rolled back inside `fn` leaves entries stamped at or past the\n\t\t\t// next committed version \u2014 see HistoryAccumulator\n\t\t\twhile (collected.length && collected[collected.length - 1].version >= version) {\n\t\t\t\tcollected.pop()\n\t\t\t}\n\t\t\tcollected.push({ version, changes: entry.changes })\n\t\t})\n\t\ttry {\n\t\t\ttransact(fn)\n\t\t\tconst current = this.history.__unsafe__getWithoutCapture()\n\t\t\twhile (collected.length && collected[collected.length - 1].version > current) {\n\t\t\t\tcollected.pop()\n\t\t\t}\n\t\t\treturn squashRecordDiffs(collected.map((c) => c.changes))\n\t\t} finally {\n\t\t\tdispose()\n\t\t}\n\t}\n\n\tapplyDiff(\n\t\tdiff: RecordsDiff<R>,\n\t\t{\n\t\t\trunCallbacks = true,\n\t\t\tignoreEphemeralKeys = false,\n\t\t}: { runCallbacks?: boolean; ignoreEphemeralKeys?: boolean } = {}\n\t) {\n\t\tthis.atomic(() => {\n\t\t\tconst toPut = objectMapValues(diff.added)\n\n\t\t\tfor (const [_from, to] of objectMapValues(diff.updated)) {\n\t\t\t\tconst type = this.schema.getType(to.typeName)\n\t\t\t\tif (ignoreEphemeralKeys && type.ephemeralKeySet.size) {\n\t\t\t\t\tconst existing = this.unsafeGetWithoutCapture(to.id)\n\t\t\t\t\tif (!existing) {\n\t\t\t\t\t\ttoPut.push(to)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tlet changed: R | null = null\n\t\t\t\t\tfor (const [key, value] of Object.entries(to)) {\n\t\t\t\t\t\tif (type.ephemeralKeySet.has(key) || Object.is(value, getOwnProperty(existing, key))) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!changed) changed = { ...existing } as R\n\t\t\t\t\t\t;(changed as any)[key] = value\n\t\t\t\t\t}\n\t\t\t\t\t// a key the update removed (present before, absent in `to`) is a change too\n\t\t\t\t\tfor (const key of Object.keys(existing)) {\n\t\t\t\t\t\tif (type.ephemeralKeySet.has(key) || Object.hasOwn(to, key)) continue\n\t\t\t\t\t\tif (!changed) changed = { ...existing } as R\n\t\t\t\t\t\tdelete (changed as any)[key]\n\t\t\t\t\t}\n\t\t\t\t\tif (changed) toPut.push(changed)\n\t\t\t\t} else {\n\t\t\t\t\ttoPut.push(to)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst toRemove = objectMapKeys(diff.removed)\n\t\t\tif (toPut.length) {\n\t\t\t\tthis.put(toPut)\n\t\t\t}\n\t\t\tif (toRemove.length) {\n\t\t\t\tthis.remove(toRemove)\n\t\t\t}\n\t\t}, runCallbacks)\n\t}\n\n\t/**\n\t * Create a cache based on values in the store. Pass in a function that takes and ID and a\n\t * signal for the underlying record. Return a signal (usually a computed) for the cached value.\n\t * For simple derivations, use {@link Store.createComputedCache}. This function is useful if you\n\t * need more precise control over intermediate values.\n\t */\n\tcreateCache<Result, Record extends R = R>(\n\t\tcreate: (id: IdOf<Record>, recordSignal: Signal<R>) => Signal<Result>\n\t) {\n\t\tconst cache = new WeakCache<Atom<any>, Signal<Result>>()\n\t\treturn {\n\t\t\tget: (id: IdOf<Record>) => {\n\t\t\t\tconst atom = this.records.getAtom(id)\n\t\t\t\tif (!atom) return undefined\n\t\t\t\treturn cache.get(atom, () => create(id, atom as Signal<R>)).get()\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Create a computed cache.\n\t *\n\t * @param name - The name of the derivation cache.\n\t * @param derive - A function used to derive the value of the cache.\n\t * @param opts - Options for the computed cache.\n\t * @public\n\t */\n\tcreateComputedCache<Result, Record extends R = R>(\n\t\tname: string,\n\t\tderive: (record: Record) => Result | undefined,\n\t\topts?: CreateComputedCacheOpts<Result, Record>\n\t): ComputedCache<Result, Record> {\n\t\treturn this.createCache((id, record) => {\n\t\t\tconst recordSignal = opts?.areRecordsEqual\n\t\t\t\t? computed(`${name}:${id}:isEqual`, () => record.get(), { isEqual: opts.areRecordsEqual })\n\t\t\t\t: record\n\n\t\t\treturn computed<Result | undefined>(\n\t\t\t\tname + ':' + id,\n\t\t\t\t() => {\n\t\t\t\t\treturn derive(recordSignal.get() as Record)\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tisEqual: opts?.areResultsEqual,\n\t\t\t\t}\n\t\t\t)\n\t\t})\n\t}\n\n\tprivate _integrityChecker?: () => void | undefined\n\n\t/** @internal */\n\tensureStoreIsUsable() {\n\t\tthis.atomic(() => {\n\t\t\tthis._integrityChecker ??= this.schema.createIntegrityChecker(this)\n\t\t\tthis._integrityChecker?.()\n\t\t})\n\t}\n\n\tprivate _isPossiblyCorrupted = false\n\t/** @internal */\n\tmarkAsPossiblyCorrupted() {\n\t\tthis._isPossiblyCorrupted = true\n\t}\n\t/** @internal */\n\tisPossiblyCorrupted() {\n\t\treturn this._isPossiblyCorrupted\n\t}\n\n\tprivate pendingAfterEvents: Map<IdOf<R>, { before: R | null; after: R | null }> | null = null\n\tprivate addDiffForAfterEvent(before: R | null, after: R | null) {\n\t\tassert(this.pendingAfterEvents, 'must be in event operation')\n\t\tif (before === after) return\n\t\tif (before && after) assert(before.id === after.id)\n\t\tif (!before && !after) return\n\t\tconst id = (before || after)!.id\n\t\tconst existing = this.pendingAfterEvents.get(id)\n\t\tif (existing) {\n\t\t\texisting.after = after\n\t\t} else {\n\t\t\tthis.pendingAfterEvents.set(id, { before, after })\n\t\t}\n\t}\n\tprivate flushAtomicCallbacks(isMergingRemoteChanges: boolean) {\n\t\tlet updateDepth = 0\n\t\tlet source: ChangeSource = isMergingRemoteChanges ? 'remote' : 'user'\n\t\twhile (this.pendingAfterEvents) {\n\t\t\tconst events = this.pendingAfterEvents\n\t\t\tthis.pendingAfterEvents = null\n\n\t\t\tif (!this.sideEffects.isEnabled()) continue\n\n\t\t\tupdateDepth++\n\t\t\tif (updateDepth > 100) {\n\t\t\t\tthrow new Error('Maximum store update depth exceeded, bailing out')\n\t\t\t}\n\n\t\t\tfor (const { before, after } of events.values()) {\n\t\t\t\tif (before && after && before !== after && !isEqual(before, after)) {\n\t\t\t\t\tthis.sideEffects.handleAfterChange(before, after, source)\n\t\t\t\t} else if (before && !after) {\n\t\t\t\t\tthis.sideEffects.handleAfterDelete(before, source)\n\t\t\t\t} else if (!before && after) {\n\t\t\t\t\tthis.sideEffects.handleAfterCreate(after, source)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!this.pendingAfterEvents) {\n\t\t\t\tthis.sideEffects.handleOperationComplete(source)\n\t\t\t}\n\t\t\t// Whatever the after-handlers or the operation-complete handlers changed in response to\n\t\t\t// a remote operation is not itself remote: later rounds are attributed to 'user'.\n\t\t\tsource = 'user'\n\t\t}\n\t}\n\tprivate _isInAtomicOp = false\n\t/** @internal */\n\tatomic<T>(fn: () => T, runCallbacks = true, isMergingRemoteChanges = false): T {\n\t\treturn transact(() => {\n\t\t\tif (this._isInAtomicOp) {\n\t\t\t\tif (!this.pendingAfterEvents) this.pendingAfterEvents = new Map()\n\t\t\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\t\t\tassert(!isMergingRemoteChanges, 'cannot call mergeRemoteChanges while in atomic operation')\n\t\t\t\ttry {\n\t\t\t\t\t// if we are in an atomic context with side effects ON allow switching before* callbacks OFF.\n\t\t\t\t\t// but don't allow switching them ON if they had been marked OFF before.\n\t\t\t\t\tif (prevSideEffectsEnabled && !runCallbacks) {\n\t\t\t\t\t\tthis.sideEffects.setIsEnabled(false)\n\t\t\t\t\t}\n\t\t\t\t\treturn fn()\n\t\t\t\t} finally {\n\t\t\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pendingAfterEvents = new Map()\n\t\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\t\t// an operation may switch side effects off, but never on while they are disabled\n\t\t\tthis.sideEffects.setIsEnabled(runCallbacks && prevSideEffectsEnabled)\n\t\t\tthis._isInAtomicOp = true\n\n\t\t\tif (isMergingRemoteChanges) {\n\t\t\t\tthis.isMergingRemoteChanges = true\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst result = fn()\n\t\t\t\tthis.isMergingRemoteChanges = false\n\n\t\t\t\tthis.flushAtomicCallbacks(isMergingRemoteChanges)\n\n\t\t\t\treturn result\n\t\t\t} finally {\n\t\t\t\tthis.pendingAfterEvents = null\n\t\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t\t\tthis._isInAtomicOp = false\n\t\t\t\tthis.isMergingRemoteChanges = false\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @internal */\n\taddHistoryInterceptor(fn: (entry: HistoryEntry<R>, source: ChangeSource) => void) {\n\t\treturn this.historyAccumulator.addInterceptor((entry) =>\n\t\t\tfn(entry, this.isMergingRemoteChanges ? 'remote' : 'user')\n\t\t)\n\t}\n}\n\n/**\n * Collect and squash history entries by their adjacent sources.\n * Adjacent entries from the same source are combined into a single entry.\n *\n * For example: [user, user, remote, remote, user] becomes [user, remote, user]\n *\n * @example\n * ```ts\n * const entries = [\n * { source: 'user', changes: userChanges1 },\n * { source: 'user', changes: userChanges2 },\n * { source: 'remote', changes: remoteChanges }\n * ]\n *\n * const squashed = squashHistoryEntries(entries)\n * // Results in 2 entries: combined user changes + remote changes\n * ```\n *\n * @param entries - The array of history entries to squash\n * @returns An array of squashed history entries\n * @public\n */\nfunction squashHistoryEntries<T extends UnknownRecord>(\n\tentries: HistoryEntry<T>[]\n): HistoryEntry<T>[] {\n\tif (entries.length === 0) return []\n\n\tconst chunked: HistoryEntry<T>[][] = []\n\tlet chunk: HistoryEntry<T>[] = [entries[0]]\n\tlet entry: HistoryEntry<T>\n\n\tfor (let i = 1, n = entries.length; i < n; i++) {\n\t\tentry = entries[i]\n\t\tif (chunk[0].source !== entry.source) {\n\t\t\tchunked.push(chunk)\n\t\t\tchunk = []\n\t\t}\n\t\tchunk.push(entry)\n\t}\n\t// Push the last chunk\n\tchunked.push(chunk)\n\n\treturn devFreeze(\n\t\tchunked.map((chunk) => ({\n\t\t\tsource: chunk[0].source,\n\t\t\t// a single-entry chunk needs no squashing \u2014 skip the O(N) copy of its diff\n\t\t\tchanges:\n\t\t\t\tchunk.length === 1 ? chunk[0].changes : squashRecordDiffs(chunk.map((e) => e.changes)),\n\t\t}))\n\t)\n}\n\n/**\n * Internal class that accumulates history entries before they are flushed to listeners.\n * Handles batching and squashing of adjacent entries from the same source.\n *\n * @internal\n */\nclass HistoryAccumulator<T extends UnknownRecord> {\n\tprivate _history: HistoryEntry<T>[] = []\n\n\t// The `history` atom value each entry was recorded at. A transaction that aborts rolls that atom\n\t// back but cannot unwind this array, so an entry stamped at or past the atom's current value\n\t// belongs to a rolled-back change-set and must not reach listeners.\n\tprivate _versions: number[] = []\n\n\tprivate _interceptors: Set<(entry: HistoryEntry<T>, version: number) => void> = new Set()\n\n\t/**\n\t * Add an interceptor that will be called for each history entry, with the version it was\n\t * recorded at (see `add`). Returns a function to remove the interceptor.\n\t */\n\taddInterceptor(fn: (entry: HistoryEntry<T>, version: number) => void) {\n\t\tthis._interceptors.add(fn)\n\t\treturn () => {\n\t\t\tthis._interceptors.delete(fn)\n\t\t}\n\t}\n\n\t/**\n\t * Add a history entry recorded at `version` (the `history` atom value it sets).\n\t * Calls all registered interceptors with the entry.\n\t */\n\tadd(entry: HistoryEntry<T>, version: number) {\n\t\tthis.discardFrom(version)\n\t\tthis._history.push(entry)\n\t\tthis._versions.push(version)\n\t\tfor (const interceptor of this._interceptors) {\n\t\t\tinterceptor(entry, version)\n\t\t}\n\t}\n\n\t/**\n\t * Flush all accumulated history entries, squashing adjacent entries from the same source.\n\t * Clears the internal history buffer.\n\t */\n\tflush(currentVersion: number) {\n\t\tthis.discardFrom(currentVersion + 1)\n\t\tconst history = squashHistoryEntries(this._history)\n\t\tthis.clear()\n\t\treturn history\n\t}\n\n\t/**\n\t * Clear all accumulated history entries without flushing.\n\t */\n\tclear() {\n\t\tthis._history = []\n\t\tthis._versions = []\n\t}\n\n\t// Drops the entries recorded at or after `version`. Entries are in ascending version order: the\n\t// atom increments by one per entry, and a rollback is followed by exactly this discard.\n\tprivate discardFrom(version: number) {\n\t\twhile (this._versions.length && this._versions[this._versions.length - 1] >= version) {\n\t\t\tthis._versions.pop()\n\t\t\tthis._history.pop()\n\t\t}\n\t}\n}\n\n/**\n * A store or an object containing a store.\n * This type is used for APIs that can accept either a store directly or an object with a store property.\n *\n * @example\n * ```ts\n * function useStore(storeOrObject: StoreObject<MyRecord>) {\n * const store = storeOrObject instanceof Store ? storeOrObject : storeOrObject.store\n * return store\n * }\n * ```\n *\n * @public\n */\nexport type StoreObject<R extends UnknownRecord> = Store<R> | { store: Store<R> }\n/**\n * Extract the record type from a StoreObject.\n *\n * @example\n * ```ts\n * type MyStoreObject = { store: Store<Book | Author> }\n * type Records = StoreObjectRecordType<MyStoreObject> // Book | Author\n * ```\n *\n * @public\n */\nexport type StoreObjectRecordType<Context extends StoreObject<any>> =\n\tContext extends Store<infer R> ? R : Context extends { store: Store<infer R> } ? R : never\n\n/**\n * Create a computed cache that works with any StoreObject (store or object containing a store).\n * This is a standalone version of Store.createComputedCache that can work with multiple store instances.\n *\n * @example\n * ```ts\n * const expensiveCache = createComputedCache(\n * 'expensiveData',\n * (context: { store: Store<Book> }, book: Book) => {\n * return performExpensiveCalculation(book)\n * }\n * )\n *\n * // Use with different store instances\n * const result1 = expensiveCache.get(storeObject1, bookId)\n * const result2 = expensiveCache.get(storeObject2, bookId)\n * ```\n *\n * @param name - A unique name for the cache (used for debugging)\n * @param derive - Function that derives a value from the context and record\n * @param opts - Optional configuration for equality checks\n * @returns A cache that can be used with multiple store instances\n * @public\n */\nexport function createComputedCache<\n\tContext extends StoreObject<any>,\n\tResult,\n\tRecord extends StoreObjectRecordType<Context> = StoreObjectRecordType<Context>,\n>(\n\tname: string,\n\tderive: (context: Context, record: Record) => Result | undefined,\n\topts?: CreateComputedCacheOpts<Result, Record>\n) {\n\tconst cache = new WeakCache<Context, ComputedCache<Result, Record>>()\n\treturn {\n\t\tget(context: Context, id: IdOf<Record>) {\n\t\t\tconst computedCache = cache.get(context, () => {\n\t\t\t\tconst store = (context instanceof Store ? context : context.store) as Store<Record>\n\t\t\t\treturn store.createComputedCache(name, (record) => derive(context, record), opts)\n\t\t\t})\n\t\t\treturn computedCache.get(id)\n\t\t},\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["id", "atom", "prevSideEffectsEnabled", "chunk"]
7
7
  }
@@ -29,7 +29,7 @@ import {
29
29
  } from "./lib/StoreSideEffects.mjs";
30
30
  registerTldrawLibraryVersion(
31
31
  "@tldraw/store",
32
- "5.6.0-canary.e9e8ec003426",
32
+ "5.6.0-canary.ff4411b6d84d",
33
33
  "esm"
34
34
  );
35
35
  export {
@@ -167,8 +167,9 @@ class Store {
167
167
  }
168
168
  isFlushingHistory = false;
169
169
  _flushHistory() {
170
- if (this.historyAccumulator.hasChanges()) {
171
- const entries = this.historyAccumulator.flush();
170
+ const version = this.history.__unsafe__getWithoutCapture();
171
+ const entries = this.historyAccumulator.flush(version);
172
+ if (entries.length > 0) {
172
173
  const errors = [];
173
174
  const wasFlushingHistory = this.isFlushingHistory;
174
175
  this.isFlushingHistory = true;
@@ -228,14 +229,18 @@ class Store {
228
229
  * @param changes - The changes to add to the history.
229
230
  */
230
231
  updateHistory(changes) {
231
- this.historyAccumulator.add({
232
- changes,
233
- source: this.isMergingRemoteChanges ? "remote" : "user"
234
- });
232
+ const version = this.history.__unsafe__getWithoutCapture() + 1;
233
+ this.history.set(version, changes);
234
+ this.historyAccumulator.add(
235
+ {
236
+ changes,
237
+ source: this.isMergingRemoteChanges ? "remote" : "user"
238
+ },
239
+ version
240
+ );
235
241
  if (this.listeners.size === 0) {
236
242
  this.historyAccumulator.clear();
237
243
  }
238
- this.history.set(this.history.__unsafe__getWithoutCapture() + 1, changes);
239
244
  }
240
245
  validate(phase) {
241
246
  this.allRecords().forEach((record) => this.schema.validateRecord(this, record, phase, null));
@@ -648,11 +653,20 @@ class Store {
648
653
  * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result.
649
654
  */
650
655
  extractingChanges(fn) {
651
- const changes = [];
652
- const dispose = this.historyAccumulator.addInterceptor((entry) => changes.push(entry.changes));
656
+ const collected = [];
657
+ const dispose = this.historyAccumulator.addInterceptor((entry, version) => {
658
+ while (collected.length && collected[collected.length - 1].version >= version) {
659
+ collected.pop();
660
+ }
661
+ collected.push({ version, changes: entry.changes });
662
+ });
653
663
  try {
654
664
  transact(fn);
655
- return squashRecordDiffs(changes);
665
+ const current = this.history.__unsafe__getWithoutCapture();
666
+ while (collected.length && collected[collected.length - 1].version > current) {
667
+ collected.pop();
668
+ }
669
+ return squashRecordDiffs(collected.map((c) => c.changes));
656
670
  } finally {
657
671
  dispose();
658
672
  }
@@ -861,10 +875,14 @@ function squashHistoryEntries(entries) {
861
875
  }
862
876
  class HistoryAccumulator {
863
877
  _history = [];
878
+ // The `history` atom value each entry was recorded at. A transaction that aborts rolls that atom
879
+ // back but cannot unwind this array, so an entry stamped at or past the atom's current value
880
+ // belongs to a rolled-back change-set and must not reach listeners.
881
+ _versions = [];
864
882
  _interceptors = /* @__PURE__ */ new Set();
865
883
  /**
866
- * Add an interceptor that will be called for each history entry.
867
- * Returns a function to remove the interceptor.
884
+ * Add an interceptor that will be called for each history entry, with the version it was
885
+ * recorded at (see `add`). Returns a function to remove the interceptor.
868
886
  */
869
887
  addInterceptor(fn) {
870
888
  this._interceptors.add(fn);
@@ -873,22 +891,25 @@ class HistoryAccumulator {
873
891
  };
874
892
  }
875
893
  /**
876
- * Add a history entry to the accumulator.
894
+ * Add a history entry recorded at `version` (the `history` atom value it sets).
877
895
  * Calls all registered interceptors with the entry.
878
896
  */
879
- add(entry) {
897
+ add(entry, version) {
898
+ this.discardFrom(version);
880
899
  this._history.push(entry);
900
+ this._versions.push(version);
881
901
  for (const interceptor of this._interceptors) {
882
- interceptor(entry);
902
+ interceptor(entry, version);
883
903
  }
884
904
  }
885
905
  /**
886
906
  * Flush all accumulated history entries, squashing adjacent entries from the same source.
887
907
  * Clears the internal history buffer.
888
908
  */
889
- flush() {
909
+ flush(currentVersion) {
910
+ this.discardFrom(currentVersion + 1);
890
911
  const history = squashHistoryEntries(this._history);
891
- this._history = [];
912
+ this.clear();
892
913
  return history;
893
914
  }
894
915
  /**
@@ -896,12 +917,15 @@ class HistoryAccumulator {
896
917
  */
897
918
  clear() {
898
919
  this._history = [];
899
- }
900
- /**
901
- * Check if there are any accumulated history entries.
902
- */
903
- hasChanges() {
904
- return this._history.length > 0;
920
+ this._versions = [];
921
+ }
922
+ // Drops the entries recorded at or after `version`. Entries are in ascending version order: the
923
+ // atom increments by one per entry, and a rollback is followed by exactly this discard.
924
+ discardFrom(version) {
925
+ while (this._versions.length && this._versions[this._versions.length - 1] >= version) {
926
+ this._versions.pop();
927
+ this._history.pop();
928
+ }
905
929
  }
906
930
  }
907
931
  function createComputedCache(name, derive, opts) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/Store.ts"],
4
- "sourcesContent": ["import { Atom, Reactor, Signal, atom, computed, reactor, transact } from '@tldraw/state'\nimport {\n\tWeakCache,\n\tassert,\n\tfilterEntries,\n\tgetOwnProperty,\n\tisEqual,\n\tobjectMapEntries,\n\tobjectMapKeys,\n\tobjectMapValues,\n\tthrottleToNextFrame,\n\tuniqueId,\n} from '@tldraw/utils'\nimport { AtomMap } from './AtomMap'\nimport { IdOf, RecordId, UnknownRecord } from './BaseRecord'\nimport { devFreeze } from './devFreeze'\nimport { hasAnyKey, isRecordsDiffEmpty, RecordsDiff, squashRecordDiffs } from './RecordsDiff'\nimport { RecordScope } from './RecordType'\nimport { StoreQueries } from './StoreQueries'\nimport { SerializedSchema, StoreSchema } from './StoreSchema'\nimport { StoreSideEffects } from './StoreSideEffects'\n\n/**\n * Extracts the record type from a record ID type.\n *\n * @example\n * ```ts\n * type BookId = RecordId<Book>\n * type BookType = RecordFromId<BookId> // Book\n * ```\n *\n * @public\n */\nexport type RecordFromId<K extends RecordId<UnknownRecord>> =\n\tK extends RecordId<infer R> ? R : never\n\n/**\n * A diff describing the changes to a collection.\n *\n * @example\n * ```ts\n * const diff: CollectionDiff<string> = {\n * added: new Set(['newItem']),\n * removed: new Set(['oldItem'])\n * }\n * ```\n *\n * @public\n */\nexport interface CollectionDiff<T> {\n\t/** Items that were added to the collection */\n\tadded?: Set<T>\n\t/** Items that were removed from the collection */\n\tremoved?: Set<T>\n}\n\n/**\n * The source of a change to the store.\n * - `'user'` - Changes originating from local user actions\n * - `'remote'` - Changes originating from remote synchronization\n *\n * @public\n */\nexport type ChangeSource = 'user' | 'remote'\n\n/**\n * Filters for store listeners to control which changes trigger the listener.\n *\n * @example\n * ```ts\n * const filters: StoreListenerFilters = {\n * source: 'user', // Only listen to user changes\n * scope: 'document' // Only listen to document-scoped records\n * }\n * ```\n *\n * @public\n */\nexport interface StoreListenerFilters {\n\t/** Filter by the source of changes */\n\tsource: ChangeSource | 'all'\n\t/** Filter by the scope of records */\n\tscope: RecordScope | 'all'\n}\n\n/**\n * An entry containing changes that originated either by user actions or remote changes.\n * History entries are used to track and replay changes to the store.\n *\n * @example\n * ```ts\n * const entry: HistoryEntry<Book> = {\n * changes: {\n * added: { 'book:123': bookRecord },\n * updated: {},\n * removed: {}\n * },\n * source: 'user'\n * }\n * ```\n *\n * @public\n */\nexport interface HistoryEntry<R extends UnknownRecord = UnknownRecord> {\n\t/** The changes that occurred in this history entry */\n\tchanges: RecordsDiff<R>\n\t/** The source of these changes */\n\tsource: ChangeSource\n}\n\n/**\n * A function that will be called when the history changes.\n *\n * @example\n * ```ts\n * const listener: StoreListener<Book> = (entry) => {\n * console.log('Changes:', entry.changes)\n * console.log('Source:', entry.source)\n * }\n *\n * store.listen(listener)\n * ```\n *\n * @param entry - The history entry containing the changes\n *\n * @public\n */\nexport type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void\n\n/**\n * A computed cache that stores derived data for records.\n * The cache automatically updates when underlying records change and cleans up when records are deleted.\n *\n * @example\n * ```ts\n * const expensiveCache = store.createComputedCache(\n * 'expensive',\n * (book: Book) => performExpensiveCalculation(book)\n * )\n *\n * const result = expensiveCache.get(bookId)\n * ```\n *\n * @public\n */\nexport interface ComputedCache<Data, R extends UnknownRecord> {\n\t/**\n\t * Get the cached data for a record by its ID.\n\t *\n\t * @param id - The ID of the record\n\t * @returns The cached data or undefined if the record doesn't exist\n\t */\n\tget(id: IdOf<R>): Data | undefined\n}\n\n/**\n * Options for creating a computed cache.\n *\n * @example\n * ```ts\n * const options: CreateComputedCacheOpts<string[], Book> = {\n * areRecordsEqual: (a, b) => a.title === b.title,\n * areResultsEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b)\n * }\n * ```\n *\n * @public\n */\nexport interface CreateComputedCacheOpts<Data, R extends UnknownRecord> {\n\t/** Custom equality function for comparing records */\n\tareRecordsEqual?(a: R, b: R): boolean\n\t/** Custom equality function for comparing results */\n\tareResultsEqual?(a: Data, b: Data): boolean\n}\n\n/**\n * A serialized snapshot of the record store's values.\n * This is a plain JavaScript object that can be saved to storage or transmitted over the network.\n *\n * @example\n * ```ts\n * const serialized: SerializedStore<Book> = {\n * 'book:123': { id: 'book:123', typeName: 'book', title: 'The Lathe of Heaven' },\n * 'book:456': { id: 'book:456', typeName: 'book', title: 'The Left Hand of Darkness' }\n * }\n * ```\n *\n * @public\n */\nexport type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>\n\n/**\n * A snapshot of the store including both data and schema information.\n * This enables proper migration when loading data from different schema versions.\n *\n * @example\n * ```ts\n * const snapshot = store.getStoreSnapshot()\n * // Later...\n * store.loadStoreSnapshot(snapshot)\n * ```\n *\n * @public\n */\nexport interface StoreSnapshot<R extends UnknownRecord> {\n\t/** The serialized store data */\n\tstore: SerializedStore<R>\n\t/** The serialized schema information */\n\tschema: SerializedSchema\n}\n\n/**\n * A validator for store records that ensures data integrity.\n * Validators are called when records are created or updated.\n *\n * @example\n * ```ts\n * const bookValidator: StoreValidator<Book> = {\n * validate(record: unknown): Book {\n * // Validate and return the record\n * if (typeof record !== 'object' || !record.title) {\n * throw new Error('Invalid book')\n * }\n * return record as Book\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface StoreValidator<R extends UnknownRecord> {\n\t/**\n\t * Validate a record.\n\t *\n\t * @param record - The record to validate\n\t * @returns The validated record\n\t * @throws When validation fails\n\t */\n\tvalidate(record: unknown): R\n\t/**\n\t * Validate a record using a known good version for reference.\n\t *\n\t * @param knownGoodVersion - A known valid version of the record\n\t * @param record - The record to validate\n\t * @returns The validated record\n\t */\n\tvalidateUsingKnownGoodVersion?(knownGoodVersion: R, record: unknown): R\n}\n\n/**\n * A map of validators for each record type in the store.\n *\n * @example\n * ```ts\n * const validators: StoreValidators<Book | Author> = {\n * book: bookValidator,\n * author: authorValidator\n * }\n * ```\n *\n * @public\n */\nexport type StoreValidators<R extends UnknownRecord> = {\n\t[K in R['typeName']]: StoreValidator<Extract<R, { typeName: K }>>\n}\n\n/**\n * Information about an error that occurred in the store.\n *\n * @example\n * ```ts\n * const error: StoreError = {\n * error: new Error('Validation failed'),\n * phase: 'updateRecord',\n * recordBefore: oldRecord,\n * recordAfter: newRecord,\n * isExistingValidationIssue: false\n * }\n * ```\n *\n * @public\n */\nexport interface StoreError {\n\t/** The error that occurred */\n\terror: Error\n\t/** The phase during which the error occurred */\n\tphase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'\n\t/** The record state before the operation (if applicable) */\n\trecordBefore?: unknown\n\t/** The record state after the operation */\n\trecordAfter: unknown\n\t/** Whether this is an existing validation issue */\n\tisExistingValidationIssue: boolean\n}\n\n/**\n * Extract the record type from a Store type.\n * Used internally for type inference.\n *\n * @internal\n */\nexport type StoreRecord<S extends Store<any>> = S extends Store<infer R> ? R : never\n\n/**\n * A reactive store that manages collections of typed records.\n *\n * The Store is the central container for your application's data, providing:\n * - Reactive state management with automatic updates\n * - Type-safe record operations\n * - History tracking and change notifications\n * - Schema validation and migrations\n * - Side effects and business logic hooks\n * - Efficient querying and indexing\n *\n * @example\n * ```ts\n * // Create a store with schema\n * const schema = StoreSchema.create({\n * book: Book,\n * author: Author\n * })\n *\n * const store = new Store({\n * schema,\n * props: {}\n * })\n *\n * // Add records\n * const book = Book.create({ title: 'The Lathe of Heaven', author: 'Le Guin' })\n * store.put([book])\n *\n * // Listen to changes\n * store.listen((entry) => {\n * console.log('Changes:', entry.changes)\n * })\n * ```\n *\n * @public\n */\nexport class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {\n\t/**\n\t * The unique identifier of the store instance.\n\t *\n\t * @public\n\t */\n\tpublic readonly id: string\n\t/**\n\t * An AtomMap containing the stores records.\n\t *\n\t * @internal\n\t * @readonly\n\t */\n\tprivate readonly records: AtomMap<IdOf<R>, R>\n\n\t/**\n\t * An atom containing the store's history.\n\t *\n\t * @public\n\t * @readonly\n\t */\n\treadonly history: Atom<number, RecordsDiff<R>> = atom('history', 0, {\n\t\thistoryLength: 1000,\n\t})\n\n\t/**\n\t * Reactive queries and indexes for efficiently accessing store data.\n\t * Provides methods for filtering, indexing, and subscribing to subsets of records.\n\t *\n\t * @example\n\t * ```ts\n\t * // Create an index by a property\n\t * const booksByAuthor = store.query.index('book', 'author')\n\t *\n\t * // Get records matching criteria\n\t * const inStockBooks = store.query.records('book', () => ({\n\t * inStock: { eq: true }\n\t * }))\n\t * ```\n\t *\n\t * @public\n\t * @readonly\n\t */\n\treadonly query: StoreQueries<R>\n\n\t/**\n\t * A set containing listeners that have been added to this store.\n\t *\n\t * @internal\n\t */\n\tprivate listeners = new Set<{ onHistory: StoreListener<R>; filters: StoreListenerFilters }>()\n\n\t/**\n\t * An array of history entries that have not yet been flushed.\n\t *\n\t * @internal\n\t */\n\tprivate historyAccumulator = new HistoryAccumulator<R>()\n\n\t/**\n\t * A reactor that responds to changes to the history by squashing the accumulated history and\n\t * notifying listeners of the changes.\n\t *\n\t * @internal\n\t */\n\tprivate historyReactor: Reactor\n\n\t/**\n\t * Cancels the history flush scheduled for the next frame, if any.\n\t *\n\t * @internal\n\t */\n\tprivate cancelHistoryReactor: null | (() => void) = null\n\n\t/**\n\t * The schema that defines the structure and validation rules for records in this store.\n\t *\n\t * @public\n\t */\n\treadonly schema: StoreSchema<R, Props>\n\n\t/**\n\t * Custom properties associated with this store instance.\n\t *\n\t * @public\n\t */\n\treadonly props: Props\n\n\t/**\n\t * A mapping of record scopes to the set of record type names that belong to each scope.\n\t * Used to filter records by their persistence and synchronization behavior.\n\t *\n\t * @public\n\t */\n\tpublic readonly scopedTypes: { readonly [K in RecordScope]: ReadonlySet<R['typeName']> }\n\n\t/**\n\t * Side effects manager that handles lifecycle events for record operations.\n\t * Allows registration of callbacks for create, update, delete, and validation events.\n\t *\n\t * @example\n\t * ```ts\n\t * store.sideEffects.registerAfterCreateHandler('book', (book) => {\n\t * console.log('Book created:', book.title)\n\t * })\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic readonly sideEffects = new StoreSideEffects<R>(this)\n\n\t/**\n\t * Creates a new Store instance.\n\t *\n\t * @example\n\t * ```ts\n\t * const store = new Store({\n\t * schema: StoreSchema.create({ book: Book }),\n\t * props: { appName: 'MyLibrary' },\n\t * initialData: savedData\n\t * })\n\t * ```\n\t *\n\t * @param config - Configuration object for the store\n\t */\n\tconstructor(config: {\n\t\t/** Optional unique identifier for the store */\n\t\tid?: string\n\t\t/** The store's initial data to populate on creation */\n\t\tinitialData?: SerializedStore<R>\n\t\t/** The schema defining record types, validation, and migrations */\n\t\tschema: StoreSchema<R, Props>\n\t\t/** Custom properties for the store instance */\n\t\tprops: Props\n\t}) {\n\t\tconst { initialData, schema, id } = config\n\n\t\tthis.id = id ?? uniqueId()\n\t\tthis.schema = schema\n\t\tthis.props = config.props\n\n\t\tif (initialData) {\n\t\t\tthis.records = new AtomMap(\n\t\t\t\t'store',\n\t\t\t\tobjectMapEntries(initialData).map(([id, record]) => [\n\t\t\t\t\tid,\n\t\t\t\t\tdevFreeze(this.schema.validateRecord(this, record, 'initialize', null)),\n\t\t\t\t])\n\t\t\t)\n\t\t} else {\n\t\t\tthis.records = new AtomMap('store')\n\t\t}\n\n\t\tthis.query = new StoreQueries<R>(this.records, this.history)\n\n\t\tthis.historyReactor = reactor(\n\t\t\t'Store.historyReactor',\n\t\t\t() => {\n\t\t\t\t// deref to make sure we're subscribed regardless of whether we need to propagate\n\t\t\t\tthis.history.get()\n\t\t\t\t// If we have accumulated history, flush it and update listeners\n\t\t\t\tthis._flushHistory()\n\t\t\t},\n\t\t\t{ scheduleEffect: (cb) => (this.cancelHistoryReactor = throttleToNextFrame(cb)) }\n\t\t)\n\t\tconst scopedTypes: { [K in RecordScope]: Set<R['typeName']> } = {\n\t\t\tdocument: new Set(),\n\t\t\tsession: new Set(),\n\t\t\tpresence: new Set(),\n\t\t}\n\t\tfor (const type of objectMapValues(this.schema.types)) {\n\t\t\tscopedTypes[type.scope].add(type.typeName)\n\t\t}\n\t\tthis.scopedTypes = scopedTypes\n\t}\n\n\tprivate isFlushingHistory = false\n\n\tpublic _flushHistory() {\n\t\t// If we have accumulated history, flush it and update listeners\n\t\tif (this.historyAccumulator.hasChanges()) {\n\t\t\tconst entries = this.historyAccumulator.flush()\n\t\t\tconst errors: unknown[] = []\n\t\t\tconst wasFlushingHistory = this.isFlushingHistory\n\t\t\tthis.isFlushingHistory = true\n\t\t\tfor (const { changes, source } of entries) {\n\t\t\t\t// Filtered diffs are computed at most once per scope per entry, and shared by every\n\t\t\t\t// listener watching that scope.\n\t\t\t\tconst scopedChanges = new Map<RecordScope, RecordsDiff<R> | null>()\n\t\t\t\tfor (const { onHistory, filters } of this.listeners) {\n\t\t\t\t\tif (filters.source !== 'all' && filters.source !== source) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tlet listenerChanges = changes\n\t\t\t\t\tif (filters.scope !== 'all') {\n\t\t\t\t\t\tif (!scopedChanges.has(filters.scope)) {\n\t\t\t\t\t\t\tscopedChanges.set(filters.scope, this.filterChangesByScope(changes, filters.scope))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst filtered = scopedChanges.get(filters.scope)\n\t\t\t\t\t\tif (!filtered) continue\n\t\t\t\t\t\tlistenerChanges = filtered\n\t\t\t\t\t}\n\t\t\t\t\t// The entries are already dequeued, so a listener that throws must not stop the others\n\t\t\t\t\t// (e.g. a sync client) from receiving them: deliver to all, then rethrow the first error.\n\t\t\t\t\ttry {\n\t\t\t\t\t\tonHistory({ changes: listenerChanges, source })\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\terrors.push(error)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.isFlushingHistory = wasFlushingHistory\n\t\t\tif (errors.length > 0) throw errors[0]\n\t\t}\n\t}\n\n\tdispose() {\n\t\t// Deliver what is still pending first: a change-set made in the same frame as the dispose\n\t\t// would otherwise never reach the listeners (e.g. a sync client) still attached to the store.\n\t\ttry {\n\t\t\tthis._flushHistory()\n\t\t} finally {\n\t\t\tthis.cancelHistoryReactor?.()\n\t\t}\n\t}\n\n\t/**\n\t * Filters out non-document changes from a diff. Returns null if there are no changes left.\n\t * @param change - the records diff\n\t * @param scope - the records scope\n\t * @returns\n\t */\n\tfilterChangesByScope(change: RecordsDiff<R>, scope: RecordScope) {\n\t\tconst result = {\n\t\t\tadded: filterEntries(change.added, (_, r) => this.scopedTypes[scope].has(r.typeName)),\n\t\t\tupdated: filterEntries(change.updated, (_, r) => this.scopedTypes[scope].has(r[1].typeName)),\n\t\t\tremoved: filterEntries(change.removed, (_, r) => this.scopedTypes[scope].has(r.typeName)),\n\t\t}\n\t\tif (isRecordsDiffEmpty(result)) {\n\t\t\treturn null\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Update the history with a diff of changes.\n\t *\n\t * @param changes - The changes to add to the history.\n\t */\n\tprivate updateHistory(changes: RecordsDiff<R>): void {\n\t\tthis.historyAccumulator.add({\n\t\t\tchanges,\n\t\t\tsource: this.isMergingRemoteChanges ? 'remote' : 'user',\n\t\t})\n\t\tif (this.listeners.size === 0) {\n\t\t\tthis.historyAccumulator.clear()\n\t\t}\n\t\t// Don't capture: a reaction that writes would otherwise re-run on every store change (S11).\n\t\tthis.history.set(this.history.__unsafe__getWithoutCapture() + 1, changes)\n\t}\n\n\tvalidate(phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests') {\n\t\tthis.allRecords().forEach((record) => this.schema.validateRecord(this, record, phase, null))\n\t}\n\n\t/**\n\t * Add or update records in the store. If a record with the same ID already exists, it will be updated.\n\t * Otherwise, a new record will be created.\n\t *\n\t * @example\n\t * ```ts\n\t * // Add new records\n\t * const book = Book.create({ title: 'Lathe Of Heaven', author: 'Le Guin' })\n\t * store.put([book])\n\t *\n\t * // Update existing record\n\t * store.put([{ ...book, title: 'The Lathe of Heaven' }])\n\t * ```\n\t *\n\t * @param records - The records to add or update\n\t * @param phaseOverride - Override the validation phase (used internally)\n\t * @public\n\t */\n\tput(records: R[], phaseOverride?: 'initialize'): void {\n\t\tthis.atomic(() => {\n\t\t\tconst updates: Record<IdOf<UnknownRecord>, [from: R, to: R]> = {}\n\t\t\tconst additions: Record<IdOf<UnknownRecord>, R> = {}\n\n\t\t\t// Iterate through all records, creating, updating or removing as needed\n\t\t\tlet record: R\n\n\t\t\tconst source = this.isMergingRemoteChanges ? 'remote' : 'user'\n\n\t\t\tfor (let i = 0, n = records.length; i < n; i++) {\n\t\t\t\trecord = records[i]\n\n\t\t\t\tconst initialValue = this.records.__unsafe__getWithoutCapture(record.id)\n\t\t\t\t// If we already have an atom for this record, update its value.\n\t\t\t\tif (initialValue) {\n\t\t\t\t\t// If we have a beforeUpdate callback, run it against the initial and next records\n\t\t\t\t\trecord = this.sideEffects.handleBeforeChange(initialValue, record, source)\n\n\t\t\t\t\t// Validate the record\n\t\t\t\t\tconst validated = this.schema.validateRecord(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tphaseOverride ?? 'updateRecord',\n\t\t\t\t\t\tinitialValue\n\t\t\t\t\t)\n\n\t\t\t\t\tif (validated === initialValue) continue\n\n\t\t\t\t\trecord = devFreeze(validated)\n\t\t\t\t\tthis.records.set(record.id, record)\n\n\t\t\t\t\tif (additions[record.id]) {\n\t\t\t\t\t\t// the same record was created earlier in this call: fold the update into it\n\t\t\t\t\t\tadditions[record.id] = record\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// an earlier update to the same record in this call owns the `from`\n\t\t\t\t\t\tconst from = updates[record.id]?.[0] ?? initialValue\n\t\t\t\t\t\tif (from === record) {\n\t\t\t\t\t\t\t// back to where it started within this call: nothing to record\n\t\t\t\t\t\t\tdelete updates[record.id]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tupdates[record.id] = [from, record]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.addDiffForAfterEvent(initialValue, record)\n\t\t\t\t} else {\n\t\t\t\t\trecord = this.sideEffects.handleBeforeCreate(record, source)\n\n\t\t\t\t\t// If we don't have an atom, create one.\n\n\t\t\t\t\t// Validate the record\n\t\t\t\t\trecord = this.schema.validateRecord(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\trecord as R,\n\t\t\t\t\t\tphaseOverride ?? 'createRecord',\n\t\t\t\t\t\tnull\n\t\t\t\t\t)\n\n\t\t\t\t\t// freeze it\n\t\t\t\t\trecord = devFreeze(record)\n\n\t\t\t\t\t// Mark the change as a new addition.\n\t\t\t\t\tadditions[record.id] = record\n\t\t\t\t\tthis.addDiffForAfterEvent(null, record)\n\n\t\t\t\t\tthis.records.set(record.id, record)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Validation may have left every record identical to before, in which case there is no\n\t\t\t// change-set to record.\n\t\t\tif (!hasAnyKey(additions) && !hasAnyKey(updates)) return\n\t\t\tthis.updateHistory({\n\t\t\t\tadded: additions,\n\t\t\t\tupdated: updates,\n\t\t\t\tremoved: {} as Record<IdOf<R>, R>,\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Remove records from the store by their IDs.\n\t *\n\t * @example\n\t * ```ts\n\t * // Remove a single record\n\t * store.remove([book.id])\n\t *\n\t * // Remove multiple records\n\t * store.remove([book1.id, book2.id, book3.id])\n\t * ```\n\t *\n\t * @param ids - The IDs of the records to remove\n\t * @public\n\t */\n\tremove(ids: IdOf<R>[]): void {\n\t\tthis.atomic(() => {\n\t\t\tconst toDelete = new Set<IdOf<R>>(ids)\n\t\t\tconst source = this.isMergingRemoteChanges ? 'remote' : 'user'\n\n\t\t\tif (this.sideEffects.isEnabled()) {\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst record = this.records.__unsafe__getWithoutCapture(id)\n\t\t\t\t\tif (!record) continue\n\n\t\t\t\t\tif (this.sideEffects.handleBeforeDelete(record, source) === false) {\n\t\t\t\t\t\ttoDelete.delete(id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst actuallyDeleted = this.records.deleteMany(toDelete)\n\t\t\tif (actuallyDeleted.length === 0) return\n\n\t\t\tconst removed = {} as RecordsDiff<R>['removed']\n\t\t\tfor (const [id, record] of actuallyDeleted) {\n\t\t\t\tremoved[id] = record\n\t\t\t\tthis.addDiffForAfterEvent(record, null)\n\t\t\t}\n\n\t\t\t// Update the history with the removed records.\n\t\t\tthis.updateHistory({ added: {}, updated: {}, removed } as RecordsDiff<R>)\n\t\t})\n\t}\n\n\t/**\n\t * Get a record by its ID. This creates a reactive subscription to the record.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = store.get(bookId)\n\t * if (book) {\n\t * console.log(book.title)\n\t * }\n\t * ```\n\t *\n\t * @param id - The ID of the record to get\n\t * @returns The record if it exists, undefined otherwise\n\t * @public\n\t */\n\tget<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {\n\t\treturn this.records.get(id) as RecordFromId<K> | undefined\n\t}\n\n\t/**\n\t * Get a record by its ID without creating a reactive subscription.\n\t * Use this when you need to access a record but don't want reactive updates.\n\t *\n\t * @example\n\t * ```ts\n\t * // Won't trigger reactive updates when this record changes\n\t * const book = store.unsafeGetWithoutCapture(bookId)\n\t * ```\n\t *\n\t * @param id - The ID of the record to get\n\t * @returns The record if it exists, undefined otherwise\n\t * @public\n\t */\n\tunsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {\n\t\treturn this.records.__unsafe__getWithoutCapture(id) as RecordFromId<K> | undefined\n\t}\n\n\t/**\n\t * Serialize the store's records to a plain JavaScript object.\n\t * Only includes records matching the specified scope.\n\t *\n\t * @example\n\t * ```ts\n\t * // Serialize only document records (default)\n\t * const documentData = store.serialize('document')\n\t *\n\t * // Serialize all records\n\t * const allData = store.serialize('all')\n\t * ```\n\t *\n\t * @param scope - The scope of records to serialize. Defaults to 'document'\n\t * @returns The serialized store data\n\t * @public\n\t */\n\tserialize(scope: RecordScope | 'all' = 'document'): SerializedStore<R> {\n\t\tconst result = {} as SerializedStore<R>\n\t\tfor (const [id, record] of this.records) {\n\t\t\tif (scope === 'all' || this.scopedTypes[scope].has(record.typeName)) {\n\t\t\t\tresult[id as IdOf<R>] = record\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Get a serialized snapshot of the store and its schema.\n\t * This includes both the data and schema information needed for proper migration.\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = store.getStoreSnapshot()\n\t * localStorage.setItem('myApp', JSON.stringify(snapshot))\n\t *\n\t * // Later...\n\t * const saved = JSON.parse(localStorage.getItem('myApp'))\n\t * store.loadStoreSnapshot(saved)\n\t * ```\n\t *\n\t * @param scope - The scope of records to serialize. Defaults to 'document'\n\t * @returns A snapshot containing both store data and schema information\n\t * @public\n\t */\n\tgetStoreSnapshot(scope: RecordScope | 'all' = 'document'): StoreSnapshot<R> {\n\t\treturn {\n\t\t\tstore: this.serialize(scope),\n\t\t\tschema: this.schema.serialize(),\n\t\t}\n\t}\n\n\t/**\n\t * Migrate a serialized snapshot to the current schema version.\n\t * This applies any necessary migrations to bring old data up to date.\n\t *\n\t * @example\n\t * ```ts\n\t * const oldSnapshot = JSON.parse(localStorage.getItem('myApp'))\n\t * const migratedSnapshot = store.migrateSnapshot(oldSnapshot)\n\t * ```\n\t *\n\t * @param snapshot - The snapshot to migrate\n\t * @returns The migrated snapshot with current schema version\n\t * @throws Error if migration fails\n\t * @public\n\t */\n\tmigrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R> {\n\t\tconst migrationResult = this.schema.migrateStoreSnapshot(snapshot)\n\n\t\tif (migrationResult.type === 'error') {\n\t\t\tthrow new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)\n\t\t}\n\n\t\treturn {\n\t\t\tstore: migrationResult.value,\n\t\t\tschema: this.schema.serialize(),\n\t\t}\n\t}\n\n\t/**\n\t * Load a serialized snapshot into the store, replacing all current data.\n\t * The snapshot will be automatically migrated to the current schema version if needed.\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = JSON.parse(localStorage.getItem('myApp'))\n\t * store.loadStoreSnapshot(snapshot)\n\t * ```\n\t *\n\t * @param snapshot - The snapshot to load\n\t * @throws Error if migration fails or snapshot is invalid\n\t * @public\n\t */\n\tloadStoreSnapshot(snapshot: StoreSnapshot<R>): void {\n\t\tconst migrationResult = this.schema.migrateStoreSnapshot(snapshot)\n\n\t\tif (migrationResult.type === 'error') {\n\t\t\tthrow new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)\n\t\t}\n\n\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\ttry {\n\t\t\tthis.sideEffects.setIsEnabled(false)\n\t\t\tthis.atomic(() => {\n\t\t\t\tthis.clear()\n\t\t\t\tthis.put(Object.values(migrationResult.value))\n\t\t\t\tthis.ensureStoreIsUsable()\n\t\t\t}, false)\n\t\t} finally {\n\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t}\n\t}\n\n\t/**\n\t * Get an array of all records in the store.\n\t *\n\t * @example\n\t * ```ts\n\t * const allRecords = store.allRecords()\n\t * const books = allRecords.filter(r => r.typeName === 'book')\n\t * ```\n\t *\n\t * @returns An array containing all records in the store\n\t * @public\n\t */\n\tallRecords(): R[] {\n\t\treturn Array.from(this.records.values())\n\t}\n\n\t/**\n\t * Remove all records from the store.\n\t *\n\t * @example\n\t * ```ts\n\t * store.clear()\n\t * console.log(store.allRecords().length) // 0\n\t * ```\n\t *\n\t * @public\n\t */\n\tclear(): void {\n\t\tthis.remove(Array.from(this.records.keys()))\n\t}\n\n\t/**\n\t * Update a single record using an updater function. To update multiple records at once,\n\t * use the `update` method of the `TypedStore` class.\n\t *\n\t * @example\n\t * ```ts\n\t * store.update(book.id, (book) => ({\n\t * ...book,\n\t * title: 'Updated Title'\n\t * }))\n\t * ```\n\t *\n\t * @param id - The ID of the record to update\n\t * @param updater - A function that receives the current record and returns the updated record\n\t * @public\n\t */\n\tupdate<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>) {\n\t\tconst existing = this.unsafeGetWithoutCapture(id)\n\t\tif (!existing) {\n\t\t\tconsole.error(`Record ${id} not found. This is probably an error`)\n\t\t\treturn\n\t\t}\n\n\t\tthis.put([updater(existing) as any])\n\t}\n\n\t/**\n\t * Check whether a record with the given ID exists in the store.\n\t *\n\t * @example\n\t * ```ts\n\t * if (store.has(bookId)) {\n\t * console.log('Book exists!')\n\t * }\n\t * ```\n\t *\n\t * @param id - The ID of the record to check\n\t * @returns True if the record exists, false otherwise\n\t * @public\n\t */\n\thas<K extends IdOf<R>>(id: K): boolean {\n\t\treturn this.records.has(id)\n\t}\n\n\t/**\n\t * Add a listener that will be called when the store changes.\n\t * Returns a function to remove the listener.\n\t *\n\t * @example\n\t * ```ts\n\t * const removeListener = store.listen((entry) => {\n\t * console.log('Changes:', entry.changes)\n\t * console.log('Source:', entry.source)\n\t * })\n\t *\n\t * // Listen only to user changes to document records\n\t * const removeDocumentListener = store.listen(\n\t * (entry) => console.log('Document changed:', entry),\n\t * { source: 'user', scope: 'document' }\n\t * )\n\t *\n\t * // Later, remove the listener\n\t * removeListener()\n\t * ```\n\t *\n\t * @param onHistory - The listener function to call when changes occur\n\t * @param filters - Optional filters to control when the listener is called\n\t * @returns A function that removes the listener when called\n\t * @public\n\t */\n\tlisten(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>) {\n\t\t// flush history so that this listener's history starts from exactly now\n\t\tthis._flushHistory()\n\n\t\tconst listener = {\n\t\t\tonHistory,\n\t\t\tfilters: {\n\t\t\t\tsource: filters?.source ?? 'all',\n\t\t\t\tscope: filters?.scope ?? 'all',\n\t\t\t},\n\t\t}\n\n\t\tif (!this.historyReactor.scheduler.isActivelyListening) {\n\t\t\tthis.historyReactor.start()\n\t\t\tthis.historyReactor.scheduler.execute()\n\t\t}\n\n\t\tthis.listeners.add(listener)\n\n\t\treturn () => {\n\t\t\t// Flush so this listener's history ends at exactly now, but not from inside a flush:\n\t\t\t// the other listeners would then receive changes made during that flush before the\n\t\t\t// entry they are still being handed.\n\t\t\tif (!this.isFlushingHistory) {\n\t\t\t\ttry {\n\t\t\t\t\tthis._flushHistory()\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Removers run in teardown loops (Editor.dispose, TLSyncClient.close); throwing here\n\t\t\t\t\t// would skip the cleanups that follow.\n\t\t\t\t\tconsole.error(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.listeners.delete(listener)\n\n\t\t\tif (this.listeners.size === 0) {\n\t\t\t\tthis.historyReactor.stop()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate isMergingRemoteChanges = false\n\n\t/**\n\t * Merge changes from a remote source. Changes made within the provided function\n\t * will be marked with source 'remote' instead of 'user'.\n\t *\n\t * @example\n\t * ```ts\n\t * // Changes from sync/collaboration\n\t * store.mergeRemoteChanges(() => {\n\t * store.put(remoteRecords)\n\t * store.remove(deletedIds)\n\t * })\n\t * ```\n\t *\n\t * @param fn - A function that applies the remote changes\n\t * @public\n\t */\n\tmergeRemoteChanges(fn: () => void) {\n\t\tif (this.isMergingRemoteChanges) {\n\t\t\treturn fn()\n\t\t}\n\n\t\tif (this._isInAtomicOp) {\n\t\t\tthrow new Error('Cannot merge remote changes while in atomic operation')\n\t\t}\n\n\t\ttry {\n\t\t\tthis.atomic(fn, true, true)\n\t\t} finally {\n\t\t\tthis.ensureStoreIsUsable()\n\t\t}\n\t}\n\n\t/**\n\t * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result.\n\t */\n\textractingChanges(fn: () => void): RecordsDiff<R> {\n\t\tconst changes: Array<RecordsDiff<R>> = []\n\t\tconst dispose = this.historyAccumulator.addInterceptor((entry) => changes.push(entry.changes))\n\t\ttry {\n\t\t\ttransact(fn)\n\t\t\treturn squashRecordDiffs(changes)\n\t\t} finally {\n\t\t\tdispose()\n\t\t}\n\t}\n\n\tapplyDiff(\n\t\tdiff: RecordsDiff<R>,\n\t\t{\n\t\t\trunCallbacks = true,\n\t\t\tignoreEphemeralKeys = false,\n\t\t}: { runCallbacks?: boolean; ignoreEphemeralKeys?: boolean } = {}\n\t) {\n\t\tthis.atomic(() => {\n\t\t\tconst toPut = objectMapValues(diff.added)\n\n\t\t\tfor (const [_from, to] of objectMapValues(diff.updated)) {\n\t\t\t\tconst type = this.schema.getType(to.typeName)\n\t\t\t\tif (ignoreEphemeralKeys && type.ephemeralKeySet.size) {\n\t\t\t\t\tconst existing = this.unsafeGetWithoutCapture(to.id)\n\t\t\t\t\tif (!existing) {\n\t\t\t\t\t\ttoPut.push(to)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tlet changed: R | null = null\n\t\t\t\t\tfor (const [key, value] of Object.entries(to)) {\n\t\t\t\t\t\tif (type.ephemeralKeySet.has(key) || Object.is(value, getOwnProperty(existing, key))) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!changed) changed = { ...existing } as R\n\t\t\t\t\t\t;(changed as any)[key] = value\n\t\t\t\t\t}\n\t\t\t\t\t// a key the update removed (present before, absent in `to`) is a change too\n\t\t\t\t\tfor (const key of Object.keys(existing)) {\n\t\t\t\t\t\tif (type.ephemeralKeySet.has(key) || Object.hasOwn(to, key)) continue\n\t\t\t\t\t\tif (!changed) changed = { ...existing } as R\n\t\t\t\t\t\tdelete (changed as any)[key]\n\t\t\t\t\t}\n\t\t\t\t\tif (changed) toPut.push(changed)\n\t\t\t\t} else {\n\t\t\t\t\ttoPut.push(to)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst toRemove = objectMapKeys(diff.removed)\n\t\t\tif (toPut.length) {\n\t\t\t\tthis.put(toPut)\n\t\t\t}\n\t\t\tif (toRemove.length) {\n\t\t\t\tthis.remove(toRemove)\n\t\t\t}\n\t\t}, runCallbacks)\n\t}\n\n\t/**\n\t * Create a cache based on values in the store. Pass in a function that takes and ID and a\n\t * signal for the underlying record. Return a signal (usually a computed) for the cached value.\n\t * For simple derivations, use {@link Store.createComputedCache}. This function is useful if you\n\t * need more precise control over intermediate values.\n\t */\n\tcreateCache<Result, Record extends R = R>(\n\t\tcreate: (id: IdOf<Record>, recordSignal: Signal<R>) => Signal<Result>\n\t) {\n\t\tconst cache = new WeakCache<Atom<any>, Signal<Result>>()\n\t\treturn {\n\t\t\tget: (id: IdOf<Record>) => {\n\t\t\t\tconst atom = this.records.getAtom(id)\n\t\t\t\tif (!atom) return undefined\n\t\t\t\treturn cache.get(atom, () => create(id, atom as Signal<R>)).get()\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Create a computed cache.\n\t *\n\t * @param name - The name of the derivation cache.\n\t * @param derive - A function used to derive the value of the cache.\n\t * @param opts - Options for the computed cache.\n\t * @public\n\t */\n\tcreateComputedCache<Result, Record extends R = R>(\n\t\tname: string,\n\t\tderive: (record: Record) => Result | undefined,\n\t\topts?: CreateComputedCacheOpts<Result, Record>\n\t): ComputedCache<Result, Record> {\n\t\treturn this.createCache((id, record) => {\n\t\t\tconst recordSignal = opts?.areRecordsEqual\n\t\t\t\t? computed(`${name}:${id}:isEqual`, () => record.get(), { isEqual: opts.areRecordsEqual })\n\t\t\t\t: record\n\n\t\t\treturn computed<Result | undefined>(\n\t\t\t\tname + ':' + id,\n\t\t\t\t() => {\n\t\t\t\t\treturn derive(recordSignal.get() as Record)\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tisEqual: opts?.areResultsEqual,\n\t\t\t\t}\n\t\t\t)\n\t\t})\n\t}\n\n\tprivate _integrityChecker?: () => void | undefined\n\n\t/** @internal */\n\tensureStoreIsUsable() {\n\t\tthis.atomic(() => {\n\t\t\tthis._integrityChecker ??= this.schema.createIntegrityChecker(this)\n\t\t\tthis._integrityChecker?.()\n\t\t})\n\t}\n\n\tprivate _isPossiblyCorrupted = false\n\t/** @internal */\n\tmarkAsPossiblyCorrupted() {\n\t\tthis._isPossiblyCorrupted = true\n\t}\n\t/** @internal */\n\tisPossiblyCorrupted() {\n\t\treturn this._isPossiblyCorrupted\n\t}\n\n\tprivate pendingAfterEvents: Map<IdOf<R>, { before: R | null; after: R | null }> | null = null\n\tprivate addDiffForAfterEvent(before: R | null, after: R | null) {\n\t\tassert(this.pendingAfterEvents, 'must be in event operation')\n\t\tif (before === after) return\n\t\tif (before && after) assert(before.id === after.id)\n\t\tif (!before && !after) return\n\t\tconst id = (before || after)!.id\n\t\tconst existing = this.pendingAfterEvents.get(id)\n\t\tif (existing) {\n\t\t\texisting.after = after\n\t\t} else {\n\t\t\tthis.pendingAfterEvents.set(id, { before, after })\n\t\t}\n\t}\n\tprivate flushAtomicCallbacks(isMergingRemoteChanges: boolean) {\n\t\tlet updateDepth = 0\n\t\tlet source: ChangeSource = isMergingRemoteChanges ? 'remote' : 'user'\n\t\twhile (this.pendingAfterEvents) {\n\t\t\tconst events = this.pendingAfterEvents\n\t\t\tthis.pendingAfterEvents = null\n\n\t\t\tif (!this.sideEffects.isEnabled()) continue\n\n\t\t\tupdateDepth++\n\t\t\tif (updateDepth > 100) {\n\t\t\t\tthrow new Error('Maximum store update depth exceeded, bailing out')\n\t\t\t}\n\n\t\t\tfor (const { before, after } of events.values()) {\n\t\t\t\tif (before && after && before !== after && !isEqual(before, after)) {\n\t\t\t\t\tthis.sideEffects.handleAfterChange(before, after, source)\n\t\t\t\t} else if (before && !after) {\n\t\t\t\t\tthis.sideEffects.handleAfterDelete(before, source)\n\t\t\t\t} else if (!before && after) {\n\t\t\t\t\tthis.sideEffects.handleAfterCreate(after, source)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!this.pendingAfterEvents) {\n\t\t\t\tthis.sideEffects.handleOperationComplete(source)\n\t\t\t}\n\t\t\t// Whatever the after-handlers or the operation-complete handlers changed in response to\n\t\t\t// a remote operation is not itself remote: later rounds are attributed to 'user'.\n\t\t\tsource = 'user'\n\t\t}\n\t}\n\tprivate _isInAtomicOp = false\n\t/** @internal */\n\tatomic<T>(fn: () => T, runCallbacks = true, isMergingRemoteChanges = false): T {\n\t\treturn transact(() => {\n\t\t\tif (this._isInAtomicOp) {\n\t\t\t\tif (!this.pendingAfterEvents) this.pendingAfterEvents = new Map()\n\t\t\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\t\t\tassert(!isMergingRemoteChanges, 'cannot call mergeRemoteChanges while in atomic operation')\n\t\t\t\ttry {\n\t\t\t\t\t// if we are in an atomic context with side effects ON allow switching before* callbacks OFF.\n\t\t\t\t\t// but don't allow switching them ON if they had been marked OFF before.\n\t\t\t\t\tif (prevSideEffectsEnabled && !runCallbacks) {\n\t\t\t\t\t\tthis.sideEffects.setIsEnabled(false)\n\t\t\t\t\t}\n\t\t\t\t\treturn fn()\n\t\t\t\t} finally {\n\t\t\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pendingAfterEvents = new Map()\n\t\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\t\t// an operation may switch side effects off, but never on while they are disabled\n\t\t\tthis.sideEffects.setIsEnabled(runCallbacks && prevSideEffectsEnabled)\n\t\t\tthis._isInAtomicOp = true\n\n\t\t\tif (isMergingRemoteChanges) {\n\t\t\t\tthis.isMergingRemoteChanges = true\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst result = fn()\n\t\t\t\tthis.isMergingRemoteChanges = false\n\n\t\t\t\tthis.flushAtomicCallbacks(isMergingRemoteChanges)\n\n\t\t\t\treturn result\n\t\t\t} finally {\n\t\t\t\tthis.pendingAfterEvents = null\n\t\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t\t\tthis._isInAtomicOp = false\n\t\t\t\tthis.isMergingRemoteChanges = false\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @internal */\n\taddHistoryInterceptor(fn: (entry: HistoryEntry<R>, source: ChangeSource) => void) {\n\t\treturn this.historyAccumulator.addInterceptor((entry) =>\n\t\t\tfn(entry, this.isMergingRemoteChanges ? 'remote' : 'user')\n\t\t)\n\t}\n}\n\n/**\n * Collect and squash history entries by their adjacent sources.\n * Adjacent entries from the same source are combined into a single entry.\n *\n * For example: [user, user, remote, remote, user] becomes [user, remote, user]\n *\n * @example\n * ```ts\n * const entries = [\n * { source: 'user', changes: userChanges1 },\n * { source: 'user', changes: userChanges2 },\n * { source: 'remote', changes: remoteChanges }\n * ]\n *\n * const squashed = squashHistoryEntries(entries)\n * // Results in 2 entries: combined user changes + remote changes\n * ```\n *\n * @param entries - The array of history entries to squash\n * @returns An array of squashed history entries\n * @public\n */\nfunction squashHistoryEntries<T extends UnknownRecord>(\n\tentries: HistoryEntry<T>[]\n): HistoryEntry<T>[] {\n\tif (entries.length === 0) return []\n\n\tconst chunked: HistoryEntry<T>[][] = []\n\tlet chunk: HistoryEntry<T>[] = [entries[0]]\n\tlet entry: HistoryEntry<T>\n\n\tfor (let i = 1, n = entries.length; i < n; i++) {\n\t\tentry = entries[i]\n\t\tif (chunk[0].source !== entry.source) {\n\t\t\tchunked.push(chunk)\n\t\t\tchunk = []\n\t\t}\n\t\tchunk.push(entry)\n\t}\n\t// Push the last chunk\n\tchunked.push(chunk)\n\n\treturn devFreeze(\n\t\tchunked.map((chunk) => ({\n\t\t\tsource: chunk[0].source,\n\t\t\t// a single-entry chunk needs no squashing \u2014 skip the O(N) copy of its diff\n\t\t\tchanges:\n\t\t\t\tchunk.length === 1 ? chunk[0].changes : squashRecordDiffs(chunk.map((e) => e.changes)),\n\t\t}))\n\t)\n}\n\n/**\n * Internal class that accumulates history entries before they are flushed to listeners.\n * Handles batching and squashing of adjacent entries from the same source.\n *\n * @internal\n */\nclass HistoryAccumulator<T extends UnknownRecord> {\n\tprivate _history: HistoryEntry<T>[] = []\n\n\tprivate _interceptors: Set<(entry: HistoryEntry<T>) => void> = new Set()\n\n\t/**\n\t * Add an interceptor that will be called for each history entry.\n\t * Returns a function to remove the interceptor.\n\t */\n\taddInterceptor(fn: (entry: HistoryEntry<T>) => void) {\n\t\tthis._interceptors.add(fn)\n\t\treturn () => {\n\t\t\tthis._interceptors.delete(fn)\n\t\t}\n\t}\n\n\t/**\n\t * Add a history entry to the accumulator.\n\t * Calls all registered interceptors with the entry.\n\t */\n\tadd(entry: HistoryEntry<T>) {\n\t\tthis._history.push(entry)\n\t\tfor (const interceptor of this._interceptors) {\n\t\t\tinterceptor(entry)\n\t\t}\n\t}\n\n\t/**\n\t * Flush all accumulated history entries, squashing adjacent entries from the same source.\n\t * Clears the internal history buffer.\n\t */\n\tflush() {\n\t\tconst history = squashHistoryEntries(this._history)\n\t\tthis._history = []\n\t\treturn history\n\t}\n\n\t/**\n\t * Clear all accumulated history entries without flushing.\n\t */\n\tclear() {\n\t\tthis._history = []\n\t}\n\n\t/**\n\t * Check if there are any accumulated history entries.\n\t */\n\thasChanges() {\n\t\treturn this._history.length > 0\n\t}\n}\n\n/**\n * A store or an object containing a store.\n * This type is used for APIs that can accept either a store directly or an object with a store property.\n *\n * @example\n * ```ts\n * function useStore(storeOrObject: StoreObject<MyRecord>) {\n * const store = storeOrObject instanceof Store ? storeOrObject : storeOrObject.store\n * return store\n * }\n * ```\n *\n * @public\n */\nexport type StoreObject<R extends UnknownRecord> = Store<R> | { store: Store<R> }\n/**\n * Extract the record type from a StoreObject.\n *\n * @example\n * ```ts\n * type MyStoreObject = { store: Store<Book | Author> }\n * type Records = StoreObjectRecordType<MyStoreObject> // Book | Author\n * ```\n *\n * @public\n */\nexport type StoreObjectRecordType<Context extends StoreObject<any>> =\n\tContext extends Store<infer R> ? R : Context extends { store: Store<infer R> } ? R : never\n\n/**\n * Create a computed cache that works with any StoreObject (store or object containing a store).\n * This is a standalone version of Store.createComputedCache that can work with multiple store instances.\n *\n * @example\n * ```ts\n * const expensiveCache = createComputedCache(\n * 'expensiveData',\n * (context: { store: Store<Book> }, book: Book) => {\n * return performExpensiveCalculation(book)\n * }\n * )\n *\n * // Use with different store instances\n * const result1 = expensiveCache.get(storeObject1, bookId)\n * const result2 = expensiveCache.get(storeObject2, bookId)\n * ```\n *\n * @param name - A unique name for the cache (used for debugging)\n * @param derive - Function that derives a value from the context and record\n * @param opts - Optional configuration for equality checks\n * @returns A cache that can be used with multiple store instances\n * @public\n */\nexport function createComputedCache<\n\tContext extends StoreObject<any>,\n\tResult,\n\tRecord extends StoreObjectRecordType<Context> = StoreObjectRecordType<Context>,\n>(\n\tname: string,\n\tderive: (context: Context, record: Record) => Result | undefined,\n\topts?: CreateComputedCacheOpts<Result, Record>\n) {\n\tconst cache = new WeakCache<Context, ComputedCache<Result, Record>>()\n\treturn {\n\t\tget(context: Context, id: IdOf<Record>) {\n\t\t\tconst computedCache = cache.get(context, () => {\n\t\t\t\tconst store = (context instanceof Store ? context : context.store) as Store<Record>\n\t\t\t\treturn store.createComputedCache(name, (record) => derive(context, record), opts)\n\t\t\t})\n\t\t\treturn computedCache.get(id)\n\t\t},\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Atom, Reactor, Signal, atom, computed, reactor, transact } from '@tldraw/state'\nimport {\n\tWeakCache,\n\tassert,\n\tfilterEntries,\n\tgetOwnProperty,\n\tisEqual,\n\tobjectMapEntries,\n\tobjectMapKeys,\n\tobjectMapValues,\n\tthrottleToNextFrame,\n\tuniqueId,\n} from '@tldraw/utils'\nimport { AtomMap } from './AtomMap'\nimport { IdOf, RecordId, UnknownRecord } from './BaseRecord'\nimport { devFreeze } from './devFreeze'\nimport { hasAnyKey, isRecordsDiffEmpty, RecordsDiff, squashRecordDiffs } from './RecordsDiff'\nimport { RecordScope } from './RecordType'\nimport { StoreQueries } from './StoreQueries'\nimport { SerializedSchema, StoreSchema } from './StoreSchema'\nimport { StoreSideEffects } from './StoreSideEffects'\n\n/**\n * Extracts the record type from a record ID type.\n *\n * @example\n * ```ts\n * type BookId = RecordId<Book>\n * type BookType = RecordFromId<BookId> // Book\n * ```\n *\n * @public\n */\nexport type RecordFromId<K extends RecordId<UnknownRecord>> =\n\tK extends RecordId<infer R> ? R : never\n\n/**\n * A diff describing the changes to a collection.\n *\n * @example\n * ```ts\n * const diff: CollectionDiff<string> = {\n * added: new Set(['newItem']),\n * removed: new Set(['oldItem'])\n * }\n * ```\n *\n * @public\n */\nexport interface CollectionDiff<T> {\n\t/** Items that were added to the collection */\n\tadded?: Set<T>\n\t/** Items that were removed from the collection */\n\tremoved?: Set<T>\n}\n\n/**\n * The source of a change to the store.\n * - `'user'` - Changes originating from local user actions\n * - `'remote'` - Changes originating from remote synchronization\n *\n * @public\n */\nexport type ChangeSource = 'user' | 'remote'\n\n/**\n * Filters for store listeners to control which changes trigger the listener.\n *\n * @example\n * ```ts\n * const filters: StoreListenerFilters = {\n * source: 'user', // Only listen to user changes\n * scope: 'document' // Only listen to document-scoped records\n * }\n * ```\n *\n * @public\n */\nexport interface StoreListenerFilters {\n\t/** Filter by the source of changes */\n\tsource: ChangeSource | 'all'\n\t/** Filter by the scope of records */\n\tscope: RecordScope | 'all'\n}\n\n/**\n * An entry containing changes that originated either by user actions or remote changes.\n * History entries are used to track and replay changes to the store.\n *\n * @example\n * ```ts\n * const entry: HistoryEntry<Book> = {\n * changes: {\n * added: { 'book:123': bookRecord },\n * updated: {},\n * removed: {}\n * },\n * source: 'user'\n * }\n * ```\n *\n * @public\n */\nexport interface HistoryEntry<R extends UnknownRecord = UnknownRecord> {\n\t/** The changes that occurred in this history entry */\n\tchanges: RecordsDiff<R>\n\t/** The source of these changes */\n\tsource: ChangeSource\n}\n\n/**\n * A function that will be called when the history changes.\n *\n * @example\n * ```ts\n * const listener: StoreListener<Book> = (entry) => {\n * console.log('Changes:', entry.changes)\n * console.log('Source:', entry.source)\n * }\n *\n * store.listen(listener)\n * ```\n *\n * @param entry - The history entry containing the changes\n *\n * @public\n */\nexport type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void\n\n/**\n * A computed cache that stores derived data for records.\n * The cache automatically updates when underlying records change and cleans up when records are deleted.\n *\n * @example\n * ```ts\n * const expensiveCache = store.createComputedCache(\n * 'expensive',\n * (book: Book) => performExpensiveCalculation(book)\n * )\n *\n * const result = expensiveCache.get(bookId)\n * ```\n *\n * @public\n */\nexport interface ComputedCache<Data, R extends UnknownRecord> {\n\t/**\n\t * Get the cached data for a record by its ID.\n\t *\n\t * @param id - The ID of the record\n\t * @returns The cached data or undefined if the record doesn't exist\n\t */\n\tget(id: IdOf<R>): Data | undefined\n}\n\n/**\n * Options for creating a computed cache.\n *\n * @example\n * ```ts\n * const options: CreateComputedCacheOpts<string[], Book> = {\n * areRecordsEqual: (a, b) => a.title === b.title,\n * areResultsEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b)\n * }\n * ```\n *\n * @public\n */\nexport interface CreateComputedCacheOpts<Data, R extends UnknownRecord> {\n\t/** Custom equality function for comparing records */\n\tareRecordsEqual?(a: R, b: R): boolean\n\t/** Custom equality function for comparing results */\n\tareResultsEqual?(a: Data, b: Data): boolean\n}\n\n/**\n * A serialized snapshot of the record store's values.\n * This is a plain JavaScript object that can be saved to storage or transmitted over the network.\n *\n * @example\n * ```ts\n * const serialized: SerializedStore<Book> = {\n * 'book:123': { id: 'book:123', typeName: 'book', title: 'The Lathe of Heaven' },\n * 'book:456': { id: 'book:456', typeName: 'book', title: 'The Left Hand of Darkness' }\n * }\n * ```\n *\n * @public\n */\nexport type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>\n\n/**\n * A snapshot of the store including both data and schema information.\n * This enables proper migration when loading data from different schema versions.\n *\n * @example\n * ```ts\n * const snapshot = store.getStoreSnapshot()\n * // Later...\n * store.loadStoreSnapshot(snapshot)\n * ```\n *\n * @public\n */\nexport interface StoreSnapshot<R extends UnknownRecord> {\n\t/** The serialized store data */\n\tstore: SerializedStore<R>\n\t/** The serialized schema information */\n\tschema: SerializedSchema\n}\n\n/**\n * A validator for store records that ensures data integrity.\n * Validators are called when records are created or updated.\n *\n * @example\n * ```ts\n * const bookValidator: StoreValidator<Book> = {\n * validate(record: unknown): Book {\n * // Validate and return the record\n * if (typeof record !== 'object' || !record.title) {\n * throw new Error('Invalid book')\n * }\n * return record as Book\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface StoreValidator<R extends UnknownRecord> {\n\t/**\n\t * Validate a record.\n\t *\n\t * @param record - The record to validate\n\t * @returns The validated record\n\t * @throws When validation fails\n\t */\n\tvalidate(record: unknown): R\n\t/**\n\t * Validate a record using a known good version for reference.\n\t *\n\t * @param knownGoodVersion - A known valid version of the record\n\t * @param record - The record to validate\n\t * @returns The validated record\n\t */\n\tvalidateUsingKnownGoodVersion?(knownGoodVersion: R, record: unknown): R\n}\n\n/**\n * A map of validators for each record type in the store.\n *\n * @example\n * ```ts\n * const validators: StoreValidators<Book | Author> = {\n * book: bookValidator,\n * author: authorValidator\n * }\n * ```\n *\n * @public\n */\nexport type StoreValidators<R extends UnknownRecord> = {\n\t[K in R['typeName']]: StoreValidator<Extract<R, { typeName: K }>>\n}\n\n/**\n * Information about an error that occurred in the store.\n *\n * @example\n * ```ts\n * const error: StoreError = {\n * error: new Error('Validation failed'),\n * phase: 'updateRecord',\n * recordBefore: oldRecord,\n * recordAfter: newRecord,\n * isExistingValidationIssue: false\n * }\n * ```\n *\n * @public\n */\nexport interface StoreError {\n\t/** The error that occurred */\n\terror: Error\n\t/** The phase during which the error occurred */\n\tphase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'\n\t/** The record state before the operation (if applicable) */\n\trecordBefore?: unknown\n\t/** The record state after the operation */\n\trecordAfter: unknown\n\t/** Whether this is an existing validation issue */\n\tisExistingValidationIssue: boolean\n}\n\n/**\n * Extract the record type from a Store type.\n * Used internally for type inference.\n *\n * @internal\n */\nexport type StoreRecord<S extends Store<any>> = S extends Store<infer R> ? R : never\n\n/**\n * A reactive store that manages collections of typed records.\n *\n * The Store is the central container for your application's data, providing:\n * - Reactive state management with automatic updates\n * - Type-safe record operations\n * - History tracking and change notifications\n * - Schema validation and migrations\n * - Side effects and business logic hooks\n * - Efficient querying and indexing\n *\n * @example\n * ```ts\n * // Create a store with schema\n * const schema = StoreSchema.create({\n * book: Book,\n * author: Author\n * })\n *\n * const store = new Store({\n * schema,\n * props: {}\n * })\n *\n * // Add records\n * const book = Book.create({ title: 'The Lathe of Heaven', author: 'Le Guin' })\n * store.put([book])\n *\n * // Listen to changes\n * store.listen((entry) => {\n * console.log('Changes:', entry.changes)\n * })\n * ```\n *\n * @public\n */\nexport class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {\n\t/**\n\t * The unique identifier of the store instance.\n\t *\n\t * @public\n\t */\n\tpublic readonly id: string\n\t/**\n\t * An AtomMap containing the stores records.\n\t *\n\t * @internal\n\t * @readonly\n\t */\n\tprivate readonly records: AtomMap<IdOf<R>, R>\n\n\t/**\n\t * An atom containing the store's history.\n\t *\n\t * @public\n\t * @readonly\n\t */\n\treadonly history: Atom<number, RecordsDiff<R>> = atom('history', 0, {\n\t\thistoryLength: 1000,\n\t})\n\n\t/**\n\t * Reactive queries and indexes for efficiently accessing store data.\n\t * Provides methods for filtering, indexing, and subscribing to subsets of records.\n\t *\n\t * @example\n\t * ```ts\n\t * // Create an index by a property\n\t * const booksByAuthor = store.query.index('book', 'author')\n\t *\n\t * // Get records matching criteria\n\t * const inStockBooks = store.query.records('book', () => ({\n\t * inStock: { eq: true }\n\t * }))\n\t * ```\n\t *\n\t * @public\n\t * @readonly\n\t */\n\treadonly query: StoreQueries<R>\n\n\t/**\n\t * A set containing listeners that have been added to this store.\n\t *\n\t * @internal\n\t */\n\tprivate listeners = new Set<{ onHistory: StoreListener<R>; filters: StoreListenerFilters }>()\n\n\t/**\n\t * An array of history entries that have not yet been flushed.\n\t *\n\t * @internal\n\t */\n\tprivate historyAccumulator = new HistoryAccumulator<R>()\n\n\t/**\n\t * A reactor that responds to changes to the history by squashing the accumulated history and\n\t * notifying listeners of the changes.\n\t *\n\t * @internal\n\t */\n\tprivate historyReactor: Reactor\n\n\t/**\n\t * Cancels the history flush scheduled for the next frame, if any.\n\t *\n\t * @internal\n\t */\n\tprivate cancelHistoryReactor: null | (() => void) = null\n\n\t/**\n\t * The schema that defines the structure and validation rules for records in this store.\n\t *\n\t * @public\n\t */\n\treadonly schema: StoreSchema<R, Props>\n\n\t/**\n\t * Custom properties associated with this store instance.\n\t *\n\t * @public\n\t */\n\treadonly props: Props\n\n\t/**\n\t * A mapping of record scopes to the set of record type names that belong to each scope.\n\t * Used to filter records by their persistence and synchronization behavior.\n\t *\n\t * @public\n\t */\n\tpublic readonly scopedTypes: { readonly [K in RecordScope]: ReadonlySet<R['typeName']> }\n\n\t/**\n\t * Side effects manager that handles lifecycle events for record operations.\n\t * Allows registration of callbacks for create, update, delete, and validation events.\n\t *\n\t * @example\n\t * ```ts\n\t * store.sideEffects.registerAfterCreateHandler('book', (book) => {\n\t * console.log('Book created:', book.title)\n\t * })\n\t * ```\n\t *\n\t * @public\n\t */\n\tpublic readonly sideEffects = new StoreSideEffects<R>(this)\n\n\t/**\n\t * Creates a new Store instance.\n\t *\n\t * @example\n\t * ```ts\n\t * const store = new Store({\n\t * schema: StoreSchema.create({ book: Book }),\n\t * props: { appName: 'MyLibrary' },\n\t * initialData: savedData\n\t * })\n\t * ```\n\t *\n\t * @param config - Configuration object for the store\n\t */\n\tconstructor(config: {\n\t\t/** Optional unique identifier for the store */\n\t\tid?: string\n\t\t/** The store's initial data to populate on creation */\n\t\tinitialData?: SerializedStore<R>\n\t\t/** The schema defining record types, validation, and migrations */\n\t\tschema: StoreSchema<R, Props>\n\t\t/** Custom properties for the store instance */\n\t\tprops: Props\n\t}) {\n\t\tconst { initialData, schema, id } = config\n\n\t\tthis.id = id ?? uniqueId()\n\t\tthis.schema = schema\n\t\tthis.props = config.props\n\n\t\tif (initialData) {\n\t\t\tthis.records = new AtomMap(\n\t\t\t\t'store',\n\t\t\t\tobjectMapEntries(initialData).map(([id, record]) => [\n\t\t\t\t\tid,\n\t\t\t\t\tdevFreeze(this.schema.validateRecord(this, record, 'initialize', null)),\n\t\t\t\t])\n\t\t\t)\n\t\t} else {\n\t\t\tthis.records = new AtomMap('store')\n\t\t}\n\n\t\tthis.query = new StoreQueries<R>(this.records, this.history)\n\n\t\tthis.historyReactor = reactor(\n\t\t\t'Store.historyReactor',\n\t\t\t() => {\n\t\t\t\t// deref to make sure we're subscribed regardless of whether we need to propagate\n\t\t\t\tthis.history.get()\n\t\t\t\t// If we have accumulated history, flush it and update listeners\n\t\t\t\tthis._flushHistory()\n\t\t\t},\n\t\t\t{ scheduleEffect: (cb) => (this.cancelHistoryReactor = throttleToNextFrame(cb)) }\n\t\t)\n\t\tconst scopedTypes: { [K in RecordScope]: Set<R['typeName']> } = {\n\t\t\tdocument: new Set(),\n\t\t\tsession: new Set(),\n\t\t\tpresence: new Set(),\n\t\t}\n\t\tfor (const type of objectMapValues(this.schema.types)) {\n\t\t\tscopedTypes[type.scope].add(type.typeName)\n\t\t}\n\t\tthis.scopedTypes = scopedTypes\n\t}\n\n\tprivate isFlushingHistory = false\n\n\tpublic _flushHistory() {\n\t\t// If we have accumulated history, flush it and update listeners\n\t\tconst version = this.history.__unsafe__getWithoutCapture()\n\t\tconst entries = this.historyAccumulator.flush(version)\n\t\tif (entries.length > 0) {\n\t\t\tconst errors: unknown[] = []\n\t\t\tconst wasFlushingHistory = this.isFlushingHistory\n\t\t\tthis.isFlushingHistory = true\n\t\t\tfor (const { changes, source } of entries) {\n\t\t\t\t// Filtered diffs are computed at most once per scope per entry, and shared by every\n\t\t\t\t// listener watching that scope.\n\t\t\t\tconst scopedChanges = new Map<RecordScope, RecordsDiff<R> | null>()\n\t\t\t\tfor (const { onHistory, filters } of this.listeners) {\n\t\t\t\t\tif (filters.source !== 'all' && filters.source !== source) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tlet listenerChanges = changes\n\t\t\t\t\tif (filters.scope !== 'all') {\n\t\t\t\t\t\tif (!scopedChanges.has(filters.scope)) {\n\t\t\t\t\t\t\tscopedChanges.set(filters.scope, this.filterChangesByScope(changes, filters.scope))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst filtered = scopedChanges.get(filters.scope)\n\t\t\t\t\t\tif (!filtered) continue\n\t\t\t\t\t\tlistenerChanges = filtered\n\t\t\t\t\t}\n\t\t\t\t\t// The entries are already dequeued, so a listener that throws must not stop the others\n\t\t\t\t\t// (e.g. a sync client) from receiving them: deliver to all, then rethrow the first error.\n\t\t\t\t\ttry {\n\t\t\t\t\t\tonHistory({ changes: listenerChanges, source })\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\terrors.push(error)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.isFlushingHistory = wasFlushingHistory\n\t\t\tif (errors.length > 0) throw errors[0]\n\t\t}\n\t}\n\n\tdispose() {\n\t\t// Deliver what is still pending first: a change-set made in the same frame as the dispose\n\t\t// would otherwise never reach the listeners (e.g. a sync client) still attached to the store.\n\t\ttry {\n\t\t\tthis._flushHistory()\n\t\t} finally {\n\t\t\tthis.cancelHistoryReactor?.()\n\t\t}\n\t}\n\n\t/**\n\t * Filters out non-document changes from a diff. Returns null if there are no changes left.\n\t * @param change - the records diff\n\t * @param scope - the records scope\n\t * @returns\n\t */\n\tfilterChangesByScope(change: RecordsDiff<R>, scope: RecordScope) {\n\t\tconst result = {\n\t\t\tadded: filterEntries(change.added, (_, r) => this.scopedTypes[scope].has(r.typeName)),\n\t\t\tupdated: filterEntries(change.updated, (_, r) => this.scopedTypes[scope].has(r[1].typeName)),\n\t\t\tremoved: filterEntries(change.removed, (_, r) => this.scopedTypes[scope].has(r.typeName)),\n\t\t}\n\t\tif (isRecordsDiffEmpty(result)) {\n\t\t\treturn null\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Update the history with a diff of changes.\n\t *\n\t * @param changes - The changes to add to the history.\n\t */\n\tprivate updateHistory(changes: RecordsDiff<R>): void {\n\t\t// Don't capture: a reaction that writes to the store must not become a dependent of the\n\t\t// history atom, or every other store change would re-run it (and it could loop forever).\n\t\tconst version = this.history.__unsafe__getWithoutCapture() + 1\n\t\t// Set the atom before recording the entry: interceptors run inside `add`, and a flush they\n\t\t// trigger (e.g. via `listen`) would otherwise discard the entry as not yet committed.\n\t\tthis.history.set(version, changes)\n\t\tthis.historyAccumulator.add(\n\t\t\t{\n\t\t\t\tchanges,\n\t\t\t\tsource: this.isMergingRemoteChanges ? 'remote' : 'user',\n\t\t\t},\n\t\t\tversion\n\t\t)\n\t\tif (this.listeners.size === 0) {\n\t\t\tthis.historyAccumulator.clear()\n\t\t}\n\t}\n\n\tvalidate(phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests') {\n\t\tthis.allRecords().forEach((record) => this.schema.validateRecord(this, record, phase, null))\n\t}\n\n\t/**\n\t * Add or update records in the store. If a record with the same ID already exists, it will be updated.\n\t * Otherwise, a new record will be created.\n\t *\n\t * @example\n\t * ```ts\n\t * // Add new records\n\t * const book = Book.create({ title: 'Lathe Of Heaven', author: 'Le Guin' })\n\t * store.put([book])\n\t *\n\t * // Update existing record\n\t * store.put([{ ...book, title: 'The Lathe of Heaven' }])\n\t * ```\n\t *\n\t * @param records - The records to add or update\n\t * @param phaseOverride - Override the validation phase (used internally)\n\t * @public\n\t */\n\tput(records: R[], phaseOverride?: 'initialize'): void {\n\t\tthis.atomic(() => {\n\t\t\tconst updates: Record<IdOf<UnknownRecord>, [from: R, to: R]> = {}\n\t\t\tconst additions: Record<IdOf<UnknownRecord>, R> = {}\n\n\t\t\t// Iterate through all records, creating, updating or removing as needed\n\t\t\tlet record: R\n\n\t\t\tconst source = this.isMergingRemoteChanges ? 'remote' : 'user'\n\n\t\t\tfor (let i = 0, n = records.length; i < n; i++) {\n\t\t\t\trecord = records[i]\n\n\t\t\t\tconst initialValue = this.records.__unsafe__getWithoutCapture(record.id)\n\t\t\t\t// If we already have an atom for this record, update its value.\n\t\t\t\tif (initialValue) {\n\t\t\t\t\t// If we have a beforeUpdate callback, run it against the initial and next records\n\t\t\t\t\trecord = this.sideEffects.handleBeforeChange(initialValue, record, source)\n\n\t\t\t\t\t// Validate the record\n\t\t\t\t\tconst validated = this.schema.validateRecord(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tphaseOverride ?? 'updateRecord',\n\t\t\t\t\t\tinitialValue\n\t\t\t\t\t)\n\n\t\t\t\t\tif (validated === initialValue) continue\n\n\t\t\t\t\trecord = devFreeze(validated)\n\t\t\t\t\tthis.records.set(record.id, record)\n\n\t\t\t\t\tif (additions[record.id]) {\n\t\t\t\t\t\t// the same record was created earlier in this call: fold the update into it\n\t\t\t\t\t\tadditions[record.id] = record\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// an earlier update to the same record in this call owns the `from`\n\t\t\t\t\t\tconst from = updates[record.id]?.[0] ?? initialValue\n\t\t\t\t\t\tif (from === record) {\n\t\t\t\t\t\t\t// back to where it started within this call: nothing to record\n\t\t\t\t\t\t\tdelete updates[record.id]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tupdates[record.id] = [from, record]\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.addDiffForAfterEvent(initialValue, record)\n\t\t\t\t} else {\n\t\t\t\t\trecord = this.sideEffects.handleBeforeCreate(record, source)\n\n\t\t\t\t\t// If we don't have an atom, create one.\n\n\t\t\t\t\t// Validate the record\n\t\t\t\t\trecord = this.schema.validateRecord(\n\t\t\t\t\t\tthis,\n\t\t\t\t\t\trecord as R,\n\t\t\t\t\t\tphaseOverride ?? 'createRecord',\n\t\t\t\t\t\tnull\n\t\t\t\t\t)\n\n\t\t\t\t\t// freeze it\n\t\t\t\t\trecord = devFreeze(record)\n\n\t\t\t\t\t// Mark the change as a new addition.\n\t\t\t\t\tadditions[record.id] = record\n\t\t\t\t\tthis.addDiffForAfterEvent(null, record)\n\n\t\t\t\t\tthis.records.set(record.id, record)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Validation may have left every record identical to before, in which case there is no\n\t\t\t// change-set to record.\n\t\t\tif (!hasAnyKey(additions) && !hasAnyKey(updates)) return\n\t\t\tthis.updateHistory({\n\t\t\t\tadded: additions,\n\t\t\t\tupdated: updates,\n\t\t\t\tremoved: {} as Record<IdOf<R>, R>,\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * Remove records from the store by their IDs.\n\t *\n\t * @example\n\t * ```ts\n\t * // Remove a single record\n\t * store.remove([book.id])\n\t *\n\t * // Remove multiple records\n\t * store.remove([book1.id, book2.id, book3.id])\n\t * ```\n\t *\n\t * @param ids - The IDs of the records to remove\n\t * @public\n\t */\n\tremove(ids: IdOf<R>[]): void {\n\t\tthis.atomic(() => {\n\t\t\tconst toDelete = new Set<IdOf<R>>(ids)\n\t\t\tconst source = this.isMergingRemoteChanges ? 'remote' : 'user'\n\n\t\t\tif (this.sideEffects.isEnabled()) {\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst record = this.records.__unsafe__getWithoutCapture(id)\n\t\t\t\t\tif (!record) continue\n\n\t\t\t\t\tif (this.sideEffects.handleBeforeDelete(record, source) === false) {\n\t\t\t\t\t\ttoDelete.delete(id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst actuallyDeleted = this.records.deleteMany(toDelete)\n\t\t\tif (actuallyDeleted.length === 0) return\n\n\t\t\tconst removed = {} as RecordsDiff<R>['removed']\n\t\t\tfor (const [id, record] of actuallyDeleted) {\n\t\t\t\tremoved[id] = record\n\t\t\t\tthis.addDiffForAfterEvent(record, null)\n\t\t\t}\n\n\t\t\t// Update the history with the removed records.\n\t\t\tthis.updateHistory({ added: {}, updated: {}, removed } as RecordsDiff<R>)\n\t\t})\n\t}\n\n\t/**\n\t * Get a record by its ID. This creates a reactive subscription to the record.\n\t *\n\t * @example\n\t * ```ts\n\t * const book = store.get(bookId)\n\t * if (book) {\n\t * console.log(book.title)\n\t * }\n\t * ```\n\t *\n\t * @param id - The ID of the record to get\n\t * @returns The record if it exists, undefined otherwise\n\t * @public\n\t */\n\tget<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {\n\t\treturn this.records.get(id) as RecordFromId<K> | undefined\n\t}\n\n\t/**\n\t * Get a record by its ID without creating a reactive subscription.\n\t * Use this when you need to access a record but don't want reactive updates.\n\t *\n\t * @example\n\t * ```ts\n\t * // Won't trigger reactive updates when this record changes\n\t * const book = store.unsafeGetWithoutCapture(bookId)\n\t * ```\n\t *\n\t * @param id - The ID of the record to get\n\t * @returns The record if it exists, undefined otherwise\n\t * @public\n\t */\n\tunsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {\n\t\treturn this.records.__unsafe__getWithoutCapture(id) as RecordFromId<K> | undefined\n\t}\n\n\t/**\n\t * Serialize the store's records to a plain JavaScript object.\n\t * Only includes records matching the specified scope.\n\t *\n\t * @example\n\t * ```ts\n\t * // Serialize only document records (default)\n\t * const documentData = store.serialize('document')\n\t *\n\t * // Serialize all records\n\t * const allData = store.serialize('all')\n\t * ```\n\t *\n\t * @param scope - The scope of records to serialize. Defaults to 'document'\n\t * @returns The serialized store data\n\t * @public\n\t */\n\tserialize(scope: RecordScope | 'all' = 'document'): SerializedStore<R> {\n\t\tconst result = {} as SerializedStore<R>\n\t\tfor (const [id, record] of this.records) {\n\t\t\tif (scope === 'all' || this.scopedTypes[scope].has(record.typeName)) {\n\t\t\t\tresult[id as IdOf<R>] = record\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\t/**\n\t * Get a serialized snapshot of the store and its schema.\n\t * This includes both the data and schema information needed for proper migration.\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = store.getStoreSnapshot()\n\t * localStorage.setItem('myApp', JSON.stringify(snapshot))\n\t *\n\t * // Later...\n\t * const saved = JSON.parse(localStorage.getItem('myApp'))\n\t * store.loadStoreSnapshot(saved)\n\t * ```\n\t *\n\t * @param scope - The scope of records to serialize. Defaults to 'document'\n\t * @returns A snapshot containing both store data and schema information\n\t * @public\n\t */\n\tgetStoreSnapshot(scope: RecordScope | 'all' = 'document'): StoreSnapshot<R> {\n\t\treturn {\n\t\t\tstore: this.serialize(scope),\n\t\t\tschema: this.schema.serialize(),\n\t\t}\n\t}\n\n\t/**\n\t * Migrate a serialized snapshot to the current schema version.\n\t * This applies any necessary migrations to bring old data up to date.\n\t *\n\t * @example\n\t * ```ts\n\t * const oldSnapshot = JSON.parse(localStorage.getItem('myApp'))\n\t * const migratedSnapshot = store.migrateSnapshot(oldSnapshot)\n\t * ```\n\t *\n\t * @param snapshot - The snapshot to migrate\n\t * @returns The migrated snapshot with current schema version\n\t * @throws Error if migration fails\n\t * @public\n\t */\n\tmigrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R> {\n\t\tconst migrationResult = this.schema.migrateStoreSnapshot(snapshot)\n\n\t\tif (migrationResult.type === 'error') {\n\t\t\tthrow new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)\n\t\t}\n\n\t\treturn {\n\t\t\tstore: migrationResult.value,\n\t\t\tschema: this.schema.serialize(),\n\t\t}\n\t}\n\n\t/**\n\t * Load a serialized snapshot into the store, replacing all current data.\n\t * The snapshot will be automatically migrated to the current schema version if needed.\n\t *\n\t * @example\n\t * ```ts\n\t * const snapshot = JSON.parse(localStorage.getItem('myApp'))\n\t * store.loadStoreSnapshot(snapshot)\n\t * ```\n\t *\n\t * @param snapshot - The snapshot to load\n\t * @throws Error if migration fails or snapshot is invalid\n\t * @public\n\t */\n\tloadStoreSnapshot(snapshot: StoreSnapshot<R>): void {\n\t\tconst migrationResult = this.schema.migrateStoreSnapshot(snapshot)\n\n\t\tif (migrationResult.type === 'error') {\n\t\t\tthrow new Error(`Failed to migrate snapshot: ${migrationResult.reason}`)\n\t\t}\n\n\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\ttry {\n\t\t\tthis.sideEffects.setIsEnabled(false)\n\t\t\tthis.atomic(() => {\n\t\t\t\tthis.clear()\n\t\t\t\tthis.put(Object.values(migrationResult.value))\n\t\t\t\tthis.ensureStoreIsUsable()\n\t\t\t}, false)\n\t\t} finally {\n\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t}\n\t}\n\n\t/**\n\t * Get an array of all records in the store.\n\t *\n\t * @example\n\t * ```ts\n\t * const allRecords = store.allRecords()\n\t * const books = allRecords.filter(r => r.typeName === 'book')\n\t * ```\n\t *\n\t * @returns An array containing all records in the store\n\t * @public\n\t */\n\tallRecords(): R[] {\n\t\treturn Array.from(this.records.values())\n\t}\n\n\t/**\n\t * Remove all records from the store.\n\t *\n\t * @example\n\t * ```ts\n\t * store.clear()\n\t * console.log(store.allRecords().length) // 0\n\t * ```\n\t *\n\t * @public\n\t */\n\tclear(): void {\n\t\tthis.remove(Array.from(this.records.keys()))\n\t}\n\n\t/**\n\t * Update a single record using an updater function. To update multiple records at once,\n\t * use the `update` method of the `TypedStore` class.\n\t *\n\t * @example\n\t * ```ts\n\t * store.update(book.id, (book) => ({\n\t * ...book,\n\t * title: 'Updated Title'\n\t * }))\n\t * ```\n\t *\n\t * @param id - The ID of the record to update\n\t * @param updater - A function that receives the current record and returns the updated record\n\t * @public\n\t */\n\tupdate<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>) {\n\t\tconst existing = this.unsafeGetWithoutCapture(id)\n\t\tif (!existing) {\n\t\t\tconsole.error(`Record ${id} not found. This is probably an error`)\n\t\t\treturn\n\t\t}\n\n\t\tthis.put([updater(existing) as any])\n\t}\n\n\t/**\n\t * Check whether a record with the given ID exists in the store.\n\t *\n\t * @example\n\t * ```ts\n\t * if (store.has(bookId)) {\n\t * console.log('Book exists!')\n\t * }\n\t * ```\n\t *\n\t * @param id - The ID of the record to check\n\t * @returns True if the record exists, false otherwise\n\t * @public\n\t */\n\thas<K extends IdOf<R>>(id: K): boolean {\n\t\treturn this.records.has(id)\n\t}\n\n\t/**\n\t * Add a listener that will be called when the store changes.\n\t * Returns a function to remove the listener.\n\t *\n\t * @example\n\t * ```ts\n\t * const removeListener = store.listen((entry) => {\n\t * console.log('Changes:', entry.changes)\n\t * console.log('Source:', entry.source)\n\t * })\n\t *\n\t * // Listen only to user changes to document records\n\t * const removeDocumentListener = store.listen(\n\t * (entry) => console.log('Document changed:', entry),\n\t * { source: 'user', scope: 'document' }\n\t * )\n\t *\n\t * // Later, remove the listener\n\t * removeListener()\n\t * ```\n\t *\n\t * @param onHistory - The listener function to call when changes occur\n\t * @param filters - Optional filters to control when the listener is called\n\t * @returns A function that removes the listener when called\n\t * @public\n\t */\n\tlisten(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>) {\n\t\t// flush history so that this listener's history starts from exactly now\n\t\tthis._flushHistory()\n\n\t\tconst listener = {\n\t\t\tonHistory,\n\t\t\tfilters: {\n\t\t\t\tsource: filters?.source ?? 'all',\n\t\t\t\tscope: filters?.scope ?? 'all',\n\t\t\t},\n\t\t}\n\n\t\tif (!this.historyReactor.scheduler.isActivelyListening) {\n\t\t\tthis.historyReactor.start()\n\t\t\tthis.historyReactor.scheduler.execute()\n\t\t}\n\n\t\tthis.listeners.add(listener)\n\n\t\treturn () => {\n\t\t\t// Flush so this listener's history ends at exactly now, but not from inside a flush:\n\t\t\t// the other listeners would then receive changes made during that flush before the\n\t\t\t// entry they are still being handed.\n\t\t\tif (!this.isFlushingHistory) {\n\t\t\t\ttry {\n\t\t\t\t\tthis._flushHistory()\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Removers run in teardown loops (Editor.dispose, TLSyncClient.close); throwing here\n\t\t\t\t\t// would skip the cleanups that follow.\n\t\t\t\t\tconsole.error(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.listeners.delete(listener)\n\n\t\t\tif (this.listeners.size === 0) {\n\t\t\t\tthis.historyReactor.stop()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate isMergingRemoteChanges = false\n\n\t/**\n\t * Merge changes from a remote source. Changes made within the provided function\n\t * will be marked with source 'remote' instead of 'user'.\n\t *\n\t * @example\n\t * ```ts\n\t * // Changes from sync/collaboration\n\t * store.mergeRemoteChanges(() => {\n\t * store.put(remoteRecords)\n\t * store.remove(deletedIds)\n\t * })\n\t * ```\n\t *\n\t * @param fn - A function that applies the remote changes\n\t * @public\n\t */\n\tmergeRemoteChanges(fn: () => void) {\n\t\tif (this.isMergingRemoteChanges) {\n\t\t\treturn fn()\n\t\t}\n\n\t\tif (this._isInAtomicOp) {\n\t\t\tthrow new Error('Cannot merge remote changes while in atomic operation')\n\t\t}\n\n\t\ttry {\n\t\t\tthis.atomic(fn, true, true)\n\t\t} finally {\n\t\t\tthis.ensureStoreIsUsable()\n\t\t}\n\t}\n\n\t/**\n\t * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result.\n\t */\n\textractingChanges(fn: () => void): RecordsDiff<R> {\n\t\tconst collected: Array<{ version: number; changes: RecordsDiff<R> }> = []\n\t\tconst dispose = this.historyAccumulator.addInterceptor((entry, version) => {\n\t\t\t// a nested transaction that rolled back inside `fn` leaves entries stamped at or past the\n\t\t\t// next committed version \u2014 see HistoryAccumulator\n\t\t\twhile (collected.length && collected[collected.length - 1].version >= version) {\n\t\t\t\tcollected.pop()\n\t\t\t}\n\t\t\tcollected.push({ version, changes: entry.changes })\n\t\t})\n\t\ttry {\n\t\t\ttransact(fn)\n\t\t\tconst current = this.history.__unsafe__getWithoutCapture()\n\t\t\twhile (collected.length && collected[collected.length - 1].version > current) {\n\t\t\t\tcollected.pop()\n\t\t\t}\n\t\t\treturn squashRecordDiffs(collected.map((c) => c.changes))\n\t\t} finally {\n\t\t\tdispose()\n\t\t}\n\t}\n\n\tapplyDiff(\n\t\tdiff: RecordsDiff<R>,\n\t\t{\n\t\t\trunCallbacks = true,\n\t\t\tignoreEphemeralKeys = false,\n\t\t}: { runCallbacks?: boolean; ignoreEphemeralKeys?: boolean } = {}\n\t) {\n\t\tthis.atomic(() => {\n\t\t\tconst toPut = objectMapValues(diff.added)\n\n\t\t\tfor (const [_from, to] of objectMapValues(diff.updated)) {\n\t\t\t\tconst type = this.schema.getType(to.typeName)\n\t\t\t\tif (ignoreEphemeralKeys && type.ephemeralKeySet.size) {\n\t\t\t\t\tconst existing = this.unsafeGetWithoutCapture(to.id)\n\t\t\t\t\tif (!existing) {\n\t\t\t\t\t\ttoPut.push(to)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tlet changed: R | null = null\n\t\t\t\t\tfor (const [key, value] of Object.entries(to)) {\n\t\t\t\t\t\tif (type.ephemeralKeySet.has(key) || Object.is(value, getOwnProperty(existing, key))) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!changed) changed = { ...existing } as R\n\t\t\t\t\t\t;(changed as any)[key] = value\n\t\t\t\t\t}\n\t\t\t\t\t// a key the update removed (present before, absent in `to`) is a change too\n\t\t\t\t\tfor (const key of Object.keys(existing)) {\n\t\t\t\t\t\tif (type.ephemeralKeySet.has(key) || Object.hasOwn(to, key)) continue\n\t\t\t\t\t\tif (!changed) changed = { ...existing } as R\n\t\t\t\t\t\tdelete (changed as any)[key]\n\t\t\t\t\t}\n\t\t\t\t\tif (changed) toPut.push(changed)\n\t\t\t\t} else {\n\t\t\t\t\ttoPut.push(to)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst toRemove = objectMapKeys(diff.removed)\n\t\t\tif (toPut.length) {\n\t\t\t\tthis.put(toPut)\n\t\t\t}\n\t\t\tif (toRemove.length) {\n\t\t\t\tthis.remove(toRemove)\n\t\t\t}\n\t\t}, runCallbacks)\n\t}\n\n\t/**\n\t * Create a cache based on values in the store. Pass in a function that takes and ID and a\n\t * signal for the underlying record. Return a signal (usually a computed) for the cached value.\n\t * For simple derivations, use {@link Store.createComputedCache}. This function is useful if you\n\t * need more precise control over intermediate values.\n\t */\n\tcreateCache<Result, Record extends R = R>(\n\t\tcreate: (id: IdOf<Record>, recordSignal: Signal<R>) => Signal<Result>\n\t) {\n\t\tconst cache = new WeakCache<Atom<any>, Signal<Result>>()\n\t\treturn {\n\t\t\tget: (id: IdOf<Record>) => {\n\t\t\t\tconst atom = this.records.getAtom(id)\n\t\t\t\tif (!atom) return undefined\n\t\t\t\treturn cache.get(atom, () => create(id, atom as Signal<R>)).get()\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Create a computed cache.\n\t *\n\t * @param name - The name of the derivation cache.\n\t * @param derive - A function used to derive the value of the cache.\n\t * @param opts - Options for the computed cache.\n\t * @public\n\t */\n\tcreateComputedCache<Result, Record extends R = R>(\n\t\tname: string,\n\t\tderive: (record: Record) => Result | undefined,\n\t\topts?: CreateComputedCacheOpts<Result, Record>\n\t): ComputedCache<Result, Record> {\n\t\treturn this.createCache((id, record) => {\n\t\t\tconst recordSignal = opts?.areRecordsEqual\n\t\t\t\t? computed(`${name}:${id}:isEqual`, () => record.get(), { isEqual: opts.areRecordsEqual })\n\t\t\t\t: record\n\n\t\t\treturn computed<Result | undefined>(\n\t\t\t\tname + ':' + id,\n\t\t\t\t() => {\n\t\t\t\t\treturn derive(recordSignal.get() as Record)\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tisEqual: opts?.areResultsEqual,\n\t\t\t\t}\n\t\t\t)\n\t\t})\n\t}\n\n\tprivate _integrityChecker?: () => void | undefined\n\n\t/** @internal */\n\tensureStoreIsUsable() {\n\t\tthis.atomic(() => {\n\t\t\tthis._integrityChecker ??= this.schema.createIntegrityChecker(this)\n\t\t\tthis._integrityChecker?.()\n\t\t})\n\t}\n\n\tprivate _isPossiblyCorrupted = false\n\t/** @internal */\n\tmarkAsPossiblyCorrupted() {\n\t\tthis._isPossiblyCorrupted = true\n\t}\n\t/** @internal */\n\tisPossiblyCorrupted() {\n\t\treturn this._isPossiblyCorrupted\n\t}\n\n\tprivate pendingAfterEvents: Map<IdOf<R>, { before: R | null; after: R | null }> | null = null\n\tprivate addDiffForAfterEvent(before: R | null, after: R | null) {\n\t\tassert(this.pendingAfterEvents, 'must be in event operation')\n\t\tif (before === after) return\n\t\tif (before && after) assert(before.id === after.id)\n\t\tif (!before && !after) return\n\t\tconst id = (before || after)!.id\n\t\tconst existing = this.pendingAfterEvents.get(id)\n\t\tif (existing) {\n\t\t\texisting.after = after\n\t\t} else {\n\t\t\tthis.pendingAfterEvents.set(id, { before, after })\n\t\t}\n\t}\n\tprivate flushAtomicCallbacks(isMergingRemoteChanges: boolean) {\n\t\tlet updateDepth = 0\n\t\tlet source: ChangeSource = isMergingRemoteChanges ? 'remote' : 'user'\n\t\twhile (this.pendingAfterEvents) {\n\t\t\tconst events = this.pendingAfterEvents\n\t\t\tthis.pendingAfterEvents = null\n\n\t\t\tif (!this.sideEffects.isEnabled()) continue\n\n\t\t\tupdateDepth++\n\t\t\tif (updateDepth > 100) {\n\t\t\t\tthrow new Error('Maximum store update depth exceeded, bailing out')\n\t\t\t}\n\n\t\t\tfor (const { before, after } of events.values()) {\n\t\t\t\tif (before && after && before !== after && !isEqual(before, after)) {\n\t\t\t\t\tthis.sideEffects.handleAfterChange(before, after, source)\n\t\t\t\t} else if (before && !after) {\n\t\t\t\t\tthis.sideEffects.handleAfterDelete(before, source)\n\t\t\t\t} else if (!before && after) {\n\t\t\t\t\tthis.sideEffects.handleAfterCreate(after, source)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!this.pendingAfterEvents) {\n\t\t\t\tthis.sideEffects.handleOperationComplete(source)\n\t\t\t}\n\t\t\t// Whatever the after-handlers or the operation-complete handlers changed in response to\n\t\t\t// a remote operation is not itself remote: later rounds are attributed to 'user'.\n\t\t\tsource = 'user'\n\t\t}\n\t}\n\tprivate _isInAtomicOp = false\n\t/** @internal */\n\tatomic<T>(fn: () => T, runCallbacks = true, isMergingRemoteChanges = false): T {\n\t\treturn transact(() => {\n\t\t\tif (this._isInAtomicOp) {\n\t\t\t\tif (!this.pendingAfterEvents) this.pendingAfterEvents = new Map()\n\t\t\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\t\t\tassert(!isMergingRemoteChanges, 'cannot call mergeRemoteChanges while in atomic operation')\n\t\t\t\ttry {\n\t\t\t\t\t// if we are in an atomic context with side effects ON allow switching before* callbacks OFF.\n\t\t\t\t\t// but don't allow switching them ON if they had been marked OFF before.\n\t\t\t\t\tif (prevSideEffectsEnabled && !runCallbacks) {\n\t\t\t\t\t\tthis.sideEffects.setIsEnabled(false)\n\t\t\t\t\t}\n\t\t\t\t\treturn fn()\n\t\t\t\t} finally {\n\t\t\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pendingAfterEvents = new Map()\n\t\t\tconst prevSideEffectsEnabled = this.sideEffects.isEnabled()\n\t\t\t// an operation may switch side effects off, but never on while they are disabled\n\t\t\tthis.sideEffects.setIsEnabled(runCallbacks && prevSideEffectsEnabled)\n\t\t\tthis._isInAtomicOp = true\n\n\t\t\tif (isMergingRemoteChanges) {\n\t\t\t\tthis.isMergingRemoteChanges = true\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst result = fn()\n\t\t\t\tthis.isMergingRemoteChanges = false\n\n\t\t\t\tthis.flushAtomicCallbacks(isMergingRemoteChanges)\n\n\t\t\t\treturn result\n\t\t\t} finally {\n\t\t\t\tthis.pendingAfterEvents = null\n\t\t\t\tthis.sideEffects.setIsEnabled(prevSideEffectsEnabled)\n\t\t\t\tthis._isInAtomicOp = false\n\t\t\t\tthis.isMergingRemoteChanges = false\n\t\t\t}\n\t\t})\n\t}\n\n\t/** @internal */\n\taddHistoryInterceptor(fn: (entry: HistoryEntry<R>, source: ChangeSource) => void) {\n\t\treturn this.historyAccumulator.addInterceptor((entry) =>\n\t\t\tfn(entry, this.isMergingRemoteChanges ? 'remote' : 'user')\n\t\t)\n\t}\n}\n\n/**\n * Collect and squash history entries by their adjacent sources.\n * Adjacent entries from the same source are combined into a single entry.\n *\n * For example: [user, user, remote, remote, user] becomes [user, remote, user]\n *\n * @example\n * ```ts\n * const entries = [\n * { source: 'user', changes: userChanges1 },\n * { source: 'user', changes: userChanges2 },\n * { source: 'remote', changes: remoteChanges }\n * ]\n *\n * const squashed = squashHistoryEntries(entries)\n * // Results in 2 entries: combined user changes + remote changes\n * ```\n *\n * @param entries - The array of history entries to squash\n * @returns An array of squashed history entries\n * @public\n */\nfunction squashHistoryEntries<T extends UnknownRecord>(\n\tentries: HistoryEntry<T>[]\n): HistoryEntry<T>[] {\n\tif (entries.length === 0) return []\n\n\tconst chunked: HistoryEntry<T>[][] = []\n\tlet chunk: HistoryEntry<T>[] = [entries[0]]\n\tlet entry: HistoryEntry<T>\n\n\tfor (let i = 1, n = entries.length; i < n; i++) {\n\t\tentry = entries[i]\n\t\tif (chunk[0].source !== entry.source) {\n\t\t\tchunked.push(chunk)\n\t\t\tchunk = []\n\t\t}\n\t\tchunk.push(entry)\n\t}\n\t// Push the last chunk\n\tchunked.push(chunk)\n\n\treturn devFreeze(\n\t\tchunked.map((chunk) => ({\n\t\t\tsource: chunk[0].source,\n\t\t\t// a single-entry chunk needs no squashing \u2014 skip the O(N) copy of its diff\n\t\t\tchanges:\n\t\t\t\tchunk.length === 1 ? chunk[0].changes : squashRecordDiffs(chunk.map((e) => e.changes)),\n\t\t}))\n\t)\n}\n\n/**\n * Internal class that accumulates history entries before they are flushed to listeners.\n * Handles batching and squashing of adjacent entries from the same source.\n *\n * @internal\n */\nclass HistoryAccumulator<T extends UnknownRecord> {\n\tprivate _history: HistoryEntry<T>[] = []\n\n\t// The `history` atom value each entry was recorded at. A transaction that aborts rolls that atom\n\t// back but cannot unwind this array, so an entry stamped at or past the atom's current value\n\t// belongs to a rolled-back change-set and must not reach listeners.\n\tprivate _versions: number[] = []\n\n\tprivate _interceptors: Set<(entry: HistoryEntry<T>, version: number) => void> = new Set()\n\n\t/**\n\t * Add an interceptor that will be called for each history entry, with the version it was\n\t * recorded at (see `add`). Returns a function to remove the interceptor.\n\t */\n\taddInterceptor(fn: (entry: HistoryEntry<T>, version: number) => void) {\n\t\tthis._interceptors.add(fn)\n\t\treturn () => {\n\t\t\tthis._interceptors.delete(fn)\n\t\t}\n\t}\n\n\t/**\n\t * Add a history entry recorded at `version` (the `history` atom value it sets).\n\t * Calls all registered interceptors with the entry.\n\t */\n\tadd(entry: HistoryEntry<T>, version: number) {\n\t\tthis.discardFrom(version)\n\t\tthis._history.push(entry)\n\t\tthis._versions.push(version)\n\t\tfor (const interceptor of this._interceptors) {\n\t\t\tinterceptor(entry, version)\n\t\t}\n\t}\n\n\t/**\n\t * Flush all accumulated history entries, squashing adjacent entries from the same source.\n\t * Clears the internal history buffer.\n\t */\n\tflush(currentVersion: number) {\n\t\tthis.discardFrom(currentVersion + 1)\n\t\tconst history = squashHistoryEntries(this._history)\n\t\tthis.clear()\n\t\treturn history\n\t}\n\n\t/**\n\t * Clear all accumulated history entries without flushing.\n\t */\n\tclear() {\n\t\tthis._history = []\n\t\tthis._versions = []\n\t}\n\n\t// Drops the entries recorded at or after `version`. Entries are in ascending version order: the\n\t// atom increments by one per entry, and a rollback is followed by exactly this discard.\n\tprivate discardFrom(version: number) {\n\t\twhile (this._versions.length && this._versions[this._versions.length - 1] >= version) {\n\t\t\tthis._versions.pop()\n\t\t\tthis._history.pop()\n\t\t}\n\t}\n}\n\n/**\n * A store or an object containing a store.\n * This type is used for APIs that can accept either a store directly or an object with a store property.\n *\n * @example\n * ```ts\n * function useStore(storeOrObject: StoreObject<MyRecord>) {\n * const store = storeOrObject instanceof Store ? storeOrObject : storeOrObject.store\n * return store\n * }\n * ```\n *\n * @public\n */\nexport type StoreObject<R extends UnknownRecord> = Store<R> | { store: Store<R> }\n/**\n * Extract the record type from a StoreObject.\n *\n * @example\n * ```ts\n * type MyStoreObject = { store: Store<Book | Author> }\n * type Records = StoreObjectRecordType<MyStoreObject> // Book | Author\n * ```\n *\n * @public\n */\nexport type StoreObjectRecordType<Context extends StoreObject<any>> =\n\tContext extends Store<infer R> ? R : Context extends { store: Store<infer R> } ? R : never\n\n/**\n * Create a computed cache that works with any StoreObject (store or object containing a store).\n * This is a standalone version of Store.createComputedCache that can work with multiple store instances.\n *\n * @example\n * ```ts\n * const expensiveCache = createComputedCache(\n * 'expensiveData',\n * (context: { store: Store<Book> }, book: Book) => {\n * return performExpensiveCalculation(book)\n * }\n * )\n *\n * // Use with different store instances\n * const result1 = expensiveCache.get(storeObject1, bookId)\n * const result2 = expensiveCache.get(storeObject2, bookId)\n * ```\n *\n * @param name - A unique name for the cache (used for debugging)\n * @param derive - Function that derives a value from the context and record\n * @param opts - Optional configuration for equality checks\n * @returns A cache that can be used with multiple store instances\n * @public\n */\nexport function createComputedCache<\n\tContext extends StoreObject<any>,\n\tResult,\n\tRecord extends StoreObjectRecordType<Context> = StoreObjectRecordType<Context>,\n>(\n\tname: string,\n\tderive: (context: Context, record: Record) => Result | undefined,\n\topts?: CreateComputedCacheOpts<Result, Record>\n) {\n\tconst cache = new WeakCache<Context, ComputedCache<Result, Record>>()\n\treturn {\n\t\tget(context: Context, id: IdOf<Record>) {\n\t\t\tconst computedCache = cache.get(context, () => {\n\t\t\t\tconst store = (context instanceof Store ? context : context.store) as Store<Record>\n\t\t\t\treturn store.createComputedCache(name, (record) => derive(context, record), opts)\n\t\t\t})\n\t\t\treturn computedCache.get(id)\n\t\t},\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["id", "atom", "prevSideEffectsEnabled", "chunk"]
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/store",
3
3
  "description": "tldraw infinite canvas SDK (store).",
4
- "version": "5.6.0-canary.e9e8ec003426",
4
+ "version": "5.6.0-canary.ff4411b6d84d",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -39,8 +39,8 @@
39
39
  "DOCS.md"
40
40
  ],
41
41
  "dependencies": {
42
- "@tldraw/state": "5.6.0-canary.e9e8ec003426",
43
- "@tldraw/utils": "5.6.0-canary.e9e8ec003426"
42
+ "@tldraw/state": "5.6.0-canary.ff4411b6d84d",
43
+ "@tldraw/utils": "5.6.0-canary.ff4411b6d84d"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "react": "^18.2.0 || ^19.2.1"
package/src/lib/Store.ts CHANGED
@@ -517,8 +517,9 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
517
517
 
518
518
  public _flushHistory() {
519
519
  // If we have accumulated history, flush it and update listeners
520
- if (this.historyAccumulator.hasChanges()) {
521
- const entries = this.historyAccumulator.flush()
520
+ const version = this.history.__unsafe__getWithoutCapture()
521
+ const entries = this.historyAccumulator.flush(version)
522
+ if (entries.length > 0) {
522
523
  const errors: unknown[] = []
523
524
  const wasFlushingHistory = this.isFlushingHistory
524
525
  this.isFlushingHistory = true
@@ -587,15 +588,22 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
587
588
  * @param changes - The changes to add to the history.
588
589
  */
589
590
  private updateHistory(changes: RecordsDiff<R>): void {
590
- this.historyAccumulator.add({
591
- changes,
592
- source: this.isMergingRemoteChanges ? 'remote' : 'user',
593
- })
591
+ // Don't capture: a reaction that writes to the store must not become a dependent of the
592
+ // history atom, or every other store change would re-run it (and it could loop forever).
593
+ const version = this.history.__unsafe__getWithoutCapture() + 1
594
+ // Set the atom before recording the entry: interceptors run inside `add`, and a flush they
595
+ // trigger (e.g. via `listen`) would otherwise discard the entry as not yet committed.
596
+ this.history.set(version, changes)
597
+ this.historyAccumulator.add(
598
+ {
599
+ changes,
600
+ source: this.isMergingRemoteChanges ? 'remote' : 'user',
601
+ },
602
+ version
603
+ )
594
604
  if (this.listeners.size === 0) {
595
605
  this.historyAccumulator.clear()
596
606
  }
597
- // Don't capture: a reaction that writes would otherwise re-run on every store change (S11).
598
- this.history.set(this.history.__unsafe__getWithoutCapture() + 1, changes)
599
607
  }
600
608
 
601
609
  validate(phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests') {
@@ -1076,11 +1084,22 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
1076
1084
  * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result.
1077
1085
  */
1078
1086
  extractingChanges(fn: () => void): RecordsDiff<R> {
1079
- const changes: Array<RecordsDiff<R>> = []
1080
- const dispose = this.historyAccumulator.addInterceptor((entry) => changes.push(entry.changes))
1087
+ const collected: Array<{ version: number; changes: RecordsDiff<R> }> = []
1088
+ const dispose = this.historyAccumulator.addInterceptor((entry, version) => {
1089
+ // a nested transaction that rolled back inside `fn` leaves entries stamped at or past the
1090
+ // next committed version — see HistoryAccumulator
1091
+ while (collected.length && collected[collected.length - 1].version >= version) {
1092
+ collected.pop()
1093
+ }
1094
+ collected.push({ version, changes: entry.changes })
1095
+ })
1081
1096
  try {
1082
1097
  transact(fn)
1083
- return squashRecordDiffs(changes)
1098
+ const current = this.history.__unsafe__getWithoutCapture()
1099
+ while (collected.length && collected[collected.length - 1].version > current) {
1100
+ collected.pop()
1101
+ }
1102
+ return squashRecordDiffs(collected.map((c) => c.changes))
1084
1103
  } finally {
1085
1104
  dispose()
1086
1105
  }
@@ -1365,13 +1384,18 @@ function squashHistoryEntries<T extends UnknownRecord>(
1365
1384
  class HistoryAccumulator<T extends UnknownRecord> {
1366
1385
  private _history: HistoryEntry<T>[] = []
1367
1386
 
1368
- private _interceptors: Set<(entry: HistoryEntry<T>) => void> = new Set()
1387
+ // The `history` atom value each entry was recorded at. A transaction that aborts rolls that atom
1388
+ // back but cannot unwind this array, so an entry stamped at or past the atom's current value
1389
+ // belongs to a rolled-back change-set and must not reach listeners.
1390
+ private _versions: number[] = []
1391
+
1392
+ private _interceptors: Set<(entry: HistoryEntry<T>, version: number) => void> = new Set()
1369
1393
 
1370
1394
  /**
1371
- * Add an interceptor that will be called for each history entry.
1372
- * Returns a function to remove the interceptor.
1395
+ * Add an interceptor that will be called for each history entry, with the version it was
1396
+ * recorded at (see `add`). Returns a function to remove the interceptor.
1373
1397
  */
1374
- addInterceptor(fn: (entry: HistoryEntry<T>) => void) {
1398
+ addInterceptor(fn: (entry: HistoryEntry<T>, version: number) => void) {
1375
1399
  this._interceptors.add(fn)
1376
1400
  return () => {
1377
1401
  this._interceptors.delete(fn)
@@ -1379,13 +1403,15 @@ class HistoryAccumulator<T extends UnknownRecord> {
1379
1403
  }
1380
1404
 
1381
1405
  /**
1382
- * Add a history entry to the accumulator.
1406
+ * Add a history entry recorded at `version` (the `history` atom value it sets).
1383
1407
  * Calls all registered interceptors with the entry.
1384
1408
  */
1385
- add(entry: HistoryEntry<T>) {
1409
+ add(entry: HistoryEntry<T>, version: number) {
1410
+ this.discardFrom(version)
1386
1411
  this._history.push(entry)
1412
+ this._versions.push(version)
1387
1413
  for (const interceptor of this._interceptors) {
1388
- interceptor(entry)
1414
+ interceptor(entry, version)
1389
1415
  }
1390
1416
  }
1391
1417
 
@@ -1393,9 +1419,10 @@ class HistoryAccumulator<T extends UnknownRecord> {
1393
1419
  * Flush all accumulated history entries, squashing adjacent entries from the same source.
1394
1420
  * Clears the internal history buffer.
1395
1421
  */
1396
- flush() {
1422
+ flush(currentVersion: number) {
1423
+ this.discardFrom(currentVersion + 1)
1397
1424
  const history = squashHistoryEntries(this._history)
1398
- this._history = []
1425
+ this.clear()
1399
1426
  return history
1400
1427
  }
1401
1428
 
@@ -1404,13 +1431,16 @@ class HistoryAccumulator<T extends UnknownRecord> {
1404
1431
  */
1405
1432
  clear() {
1406
1433
  this._history = []
1434
+ this._versions = []
1407
1435
  }
1408
1436
 
1409
- /**
1410
- * Check if there are any accumulated history entries.
1411
- */
1412
- hasChanges() {
1413
- return this._history.length > 0
1437
+ // Drops the entries recorded at or after `version`. Entries are in ascending version order: the
1438
+ // atom increments by one per entry, and a rollback is followed by exactly this discard.
1439
+ private discardFrom(version: number) {
1440
+ while (this._versions.length && this._versions[this._versions.length - 1] >= version) {
1441
+ this._versions.pop()
1442
+ this._history.pop()
1443
+ }
1414
1444
  }
1415
1445
  }
1416
1446
 
@@ -1,4 +1,4 @@
1
- import { react, RESET_VALUE, transact } from '@tldraw/state'
1
+ import { react, RESET_VALUE, transact, transaction } from '@tldraw/state'
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest'
3
3
  import { BaseRecord, RecordId } from './BaseRecord'
4
4
  import { RecordsDiff, reverseRecordsDiff } from './RecordsDiff'
@@ -461,6 +461,93 @@ describe('applying diffs (H)', () => {
461
461
  })
462
462
  })
463
463
 
464
+ describe('listeners: rollback (H)', () => {
465
+ it('[H11] listeners never see change-sets from a transaction that rolled back', async () => {
466
+ const listener = vi.fn()
467
+ store.listen(listener)
468
+ const author = tolkein()
469
+
470
+ expect(() =>
471
+ store.atomic(() => {
472
+ store.put([author])
473
+ throw new Error('abort')
474
+ })
475
+ ).toThrow('abort')
476
+ expect(store.get(author.id)).toBeUndefined()
477
+
478
+ // a later, committed change flushes whatever the accumulator still holds
479
+ store.put([Author.create({ name: 'Ursula K. Le Guin' })])
480
+ await new Promise((resolve) => requestAnimationFrame(resolve))
481
+
482
+ const addedIds = listener.mock.calls.flatMap(([entry]) => Object.keys(entry.changes.added))
483
+ expect(addedIds).not.toContain(author.id)
484
+ expect(addedIds).toHaveLength(1)
485
+ })
486
+
487
+ it('[H11] a rolled-back nested transaction only discards its own change-sets', async () => {
488
+ const listener = vi.fn()
489
+ store.listen(listener)
490
+ const kept = tolkein()
491
+ const dropped = Author.create({ name: 'Dropped' })
492
+
493
+ transact(() => {
494
+ store.put([kept])
495
+ try {
496
+ transaction(() => {
497
+ store.put([dropped])
498
+ throw new Error('inner')
499
+ })
500
+ } catch {
501
+ // the outer transaction commits
502
+ }
503
+ })
504
+ await new Promise((resolve) => requestAnimationFrame(resolve))
505
+
506
+ expect(store.get(kept.id)).toBeDefined()
507
+ expect(store.get(dropped.id)).toBeUndefined()
508
+ const addedIds = listener.mock.calls.flatMap(([entry]) => Object.keys(entry.changes.added))
509
+ expect(addedIds).toEqual([kept.id])
510
+ })
511
+ })
512
+
513
+ describe('extracting changes across rolled-back nested transactions (H)', () => {
514
+ it('[H7] extractingChanges excludes changes from a nested transaction that rolled back', () => {
515
+ const kept = tolkein()
516
+ const dropped = Author.create({ name: 'Dropped' })
517
+ const changes = store.extractingChanges(() => {
518
+ store.put([kept])
519
+ try {
520
+ transaction(() => {
521
+ store.put([dropped])
522
+ throw new Error('inner')
523
+ })
524
+ } catch {
525
+ // swallowed: the outer work continues
526
+ }
527
+ })
528
+ expect(Object.keys(changes.added)).toEqual([kept.id])
529
+ expect(store.get(dropped.id)).toBeUndefined()
530
+ })
531
+ })
532
+
533
+ describe('listeners added from an interceptor (H)', () => {
534
+ it('[H8] a flush triggered from inside an interceptor still delivers the entry being recorded', () => {
535
+ const first = vi.fn()
536
+ store.listen(first)
537
+ let added = false
538
+ store.addHistoryInterceptor(() => {
539
+ if (added) return
540
+ added = true
541
+ // listen() flushes synchronously while the entry is still being recorded
542
+ store.listen(() => {})
543
+ })
544
+
545
+ store.put([tolkein()])
546
+ store._flushHistory()
547
+ expect(first).toHaveBeenCalledTimes(1)
548
+ })
549
+ })
550
+
464
551
  describe('applying diffs with ignoreEphemeralKeys (H)', () => {
465
552
  it('[H10] ignoreEphemeralKeys also applies the removal of a non-ephemeral key', () => {
466
553
  const visitId = Visit.createId('jane')