@tldraw/store 4.1.0-canary.9f9255bd7a83 → 4.1.0-canary.a6e63b3bbde6

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.
Files changed (72) hide show
  1. package/dist-cjs/index.d.ts +1884 -153
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/AtomMap.js +241 -1
  4. package/dist-cjs/lib/AtomMap.js.map +2 -2
  5. package/dist-cjs/lib/BaseRecord.js.map +2 -2
  6. package/dist-cjs/lib/ImmutableMap.js +141 -0
  7. package/dist-cjs/lib/ImmutableMap.js.map +2 -2
  8. package/dist-cjs/lib/IncrementalSetConstructor.js +45 -5
  9. package/dist-cjs/lib/IncrementalSetConstructor.js.map +2 -2
  10. package/dist-cjs/lib/RecordType.js +116 -21
  11. package/dist-cjs/lib/RecordType.js.map +2 -2
  12. package/dist-cjs/lib/RecordsDiff.js.map +2 -2
  13. package/dist-cjs/lib/Store.js +233 -39
  14. package/dist-cjs/lib/Store.js.map +2 -2
  15. package/dist-cjs/lib/StoreQueries.js +135 -22
  16. package/dist-cjs/lib/StoreQueries.js.map +2 -2
  17. package/dist-cjs/lib/StoreSchema.js +207 -2
  18. package/dist-cjs/lib/StoreSchema.js.map +2 -2
  19. package/dist-cjs/lib/StoreSideEffects.js +102 -10
  20. package/dist-cjs/lib/StoreSideEffects.js.map +2 -2
  21. package/dist-cjs/lib/executeQuery.js.map +2 -2
  22. package/dist-cjs/lib/migrate.js.map +2 -2
  23. package/dist-cjs/lib/setUtils.js.map +2 -2
  24. package/dist-esm/index.d.mts +1884 -153
  25. package/dist-esm/index.mjs +1 -1
  26. package/dist-esm/lib/AtomMap.mjs +241 -1
  27. package/dist-esm/lib/AtomMap.mjs.map +2 -2
  28. package/dist-esm/lib/BaseRecord.mjs.map +2 -2
  29. package/dist-esm/lib/ImmutableMap.mjs +141 -0
  30. package/dist-esm/lib/ImmutableMap.mjs.map +2 -2
  31. package/dist-esm/lib/IncrementalSetConstructor.mjs +45 -5
  32. package/dist-esm/lib/IncrementalSetConstructor.mjs.map +2 -2
  33. package/dist-esm/lib/RecordType.mjs +116 -21
  34. package/dist-esm/lib/RecordType.mjs.map +2 -2
  35. package/dist-esm/lib/RecordsDiff.mjs.map +2 -2
  36. package/dist-esm/lib/Store.mjs +233 -39
  37. package/dist-esm/lib/Store.mjs.map +2 -2
  38. package/dist-esm/lib/StoreQueries.mjs +135 -22
  39. package/dist-esm/lib/StoreQueries.mjs.map +2 -2
  40. package/dist-esm/lib/StoreSchema.mjs +207 -2
  41. package/dist-esm/lib/StoreSchema.mjs.map +2 -2
  42. package/dist-esm/lib/StoreSideEffects.mjs +102 -10
  43. package/dist-esm/lib/StoreSideEffects.mjs.map +2 -2
  44. package/dist-esm/lib/executeQuery.mjs.map +2 -2
  45. package/dist-esm/lib/migrate.mjs.map +2 -2
  46. package/dist-esm/lib/setUtils.mjs.map +2 -2
  47. package/package.json +3 -3
  48. package/src/lib/AtomMap.ts +241 -1
  49. package/src/lib/BaseRecord.test.ts +44 -0
  50. package/src/lib/BaseRecord.ts +118 -4
  51. package/src/lib/ImmutableMap.test.ts +103 -0
  52. package/src/lib/ImmutableMap.ts +212 -0
  53. package/src/lib/IncrementalSetConstructor.test.ts +111 -0
  54. package/src/lib/IncrementalSetConstructor.ts +63 -6
  55. package/src/lib/RecordType.ts +149 -25
  56. package/src/lib/RecordsDiff.test.ts +144 -0
  57. package/src/lib/RecordsDiff.ts +145 -10
  58. package/src/lib/Store.test.ts +827 -0
  59. package/src/lib/Store.ts +533 -67
  60. package/src/lib/StoreQueries.test.ts +627 -0
  61. package/src/lib/StoreQueries.ts +194 -27
  62. package/src/lib/StoreSchema.test.ts +226 -0
  63. package/src/lib/StoreSchema.ts +386 -8
  64. package/src/lib/StoreSideEffects.test.ts +239 -19
  65. package/src/lib/StoreSideEffects.ts +266 -19
  66. package/src/lib/devFreeze.test.ts +137 -0
  67. package/src/lib/executeQuery.test.ts +481 -0
  68. package/src/lib/executeQuery.ts +80 -2
  69. package/src/lib/migrate.test.ts +400 -0
  70. package/src/lib/migrate.ts +187 -14
  71. package/src/lib/setUtils.test.ts +105 -0
  72. package/src/lib/setUtils.ts +44 -4
package/src/lib/Store.ts CHANGED
@@ -20,105 +20,328 @@ import { SerializedSchema, StoreSchema } from './StoreSchema'
20
20
  import { StoreSideEffects } from './StoreSideEffects'
21
21
  import { devFreeze } from './devFreeze'
22
22
 
23
- /** @public */
23
+ /**
24
+ * Extracts the record type from a record ID type.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * type BookId = RecordId<Book>
29
+ * type BookType = RecordFromId<BookId> // Book
30
+ * ```
31
+ *
32
+ * @public
33
+ */
24
34
  export type RecordFromId<K extends RecordId<UnknownRecord>> =
25
35
  K extends RecordId<infer R> ? R : never
26
36
 
27
37
  /**
28
38
  * A diff describing the changes to a collection.
29
39
  *
40
+ * @example
41
+ * ```ts
42
+ * const diff: CollectionDiff<string> = {
43
+ * added: new Set(['newItem']),
44
+ * removed: new Set(['oldItem'])
45
+ * }
46
+ * ```
47
+ *
30
48
  * @public
31
49
  */
32
50
  export interface CollectionDiff<T> {
51
+ /** Items that were added to the collection */
33
52
  added?: Set<T>
53
+ /** Items that were removed from the collection */
34
54
  removed?: Set<T>
35
55
  }
36
56
 
37
- /** @public */
57
+ /**
58
+ * The source of a change to the store.
59
+ * - `'user'` - Changes originating from local user actions
60
+ * - `'remote'` - Changes originating from remote synchronization
61
+ *
62
+ * @public
63
+ */
38
64
  export type ChangeSource = 'user' | 'remote'
39
65
 
40
- /** @public */
66
+ /**
67
+ * Filters for store listeners to control which changes trigger the listener.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * const filters: StoreListenerFilters = {
72
+ * source: 'user', // Only listen to user changes
73
+ * scope: 'document' // Only listen to document-scoped records
74
+ * }
75
+ * ```
76
+ *
77
+ * @public
78
+ */
41
79
  export interface StoreListenerFilters {
80
+ /** Filter by the source of changes */
42
81
  source: ChangeSource | 'all'
82
+ /** Filter by the scope of records */
43
83
  scope: RecordScope | 'all'
44
84
  }
45
85
 
46
86
  /**
47
87
  * An entry containing changes that originated either by user actions or remote changes.
88
+ * History entries are used to track and replay changes to the store.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const entry: HistoryEntry<Book> = {
93
+ * changes: {
94
+ * added: { 'book:123': bookRecord },
95
+ * updated: {},
96
+ * removed: {}
97
+ * },
98
+ * source: 'user'
99
+ * }
100
+ * ```
48
101
  *
49
102
  * @public
50
103
  */
51
104
  export interface HistoryEntry<R extends UnknownRecord = UnknownRecord> {
105
+ /** The changes that occurred in this history entry */
52
106
  changes: RecordsDiff<R>
107
+ /** The source of these changes */
53
108
  source: ChangeSource
54
109
  }
55
110
 
56
111
  /**
57
112
  * A function that will be called when the history changes.
58
113
  *
114
+ * @example
115
+ * ```ts
116
+ * const listener: StoreListener<Book> = (entry) => {
117
+ * console.log('Changes:', entry.changes)
118
+ * console.log('Source:', entry.source)
119
+ * }
120
+ *
121
+ * store.listen(listener)
122
+ * ```
123
+ *
124
+ * @param entry - The history entry containing the changes
125
+ *
59
126
  * @public
60
127
  */
61
128
  export type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void
62
129
 
63
130
  /**
64
- * A record store is a collection of records of different types.
131
+ * A computed cache that stores derived data for records.
132
+ * The cache automatically updates when underlying records change and cleans up when records are deleted.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * const expensiveCache = store.createComputedCache(
137
+ * 'expensive',
138
+ * (book: Book) => performExpensiveCalculation(book)
139
+ * )
140
+ *
141
+ * const result = expensiveCache.get(bookId)
142
+ * ```
65
143
  *
66
144
  * @public
67
145
  */
68
146
  export interface ComputedCache<Data, R extends UnknownRecord> {
147
+ /**
148
+ * Get the cached data for a record by its ID.
149
+ *
150
+ * @param id - The ID of the record
151
+ * @returns The cached data or undefined if the record doesn't exist
152
+ */
69
153
  get(id: IdOf<R>): Data | undefined
70
154
  }
71
155
 
72
- /** @public */
156
+ /**
157
+ * Options for creating a computed cache.
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * const options: CreateComputedCacheOpts<string[], Book> = {
162
+ * areRecordsEqual: (a, b) => a.title === b.title,
163
+ * areResultsEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b)
164
+ * }
165
+ * ```
166
+ *
167
+ * @public
168
+ */
73
169
  export interface CreateComputedCacheOpts<Data, R extends UnknownRecord> {
170
+ /** Custom equality function for comparing records */
74
171
  areRecordsEqual?(a: R, b: R): boolean
172
+ /** Custom equality function for comparing results */
75
173
  areResultsEqual?(a: Data, b: Data): boolean
76
174
  }
77
175
 
78
176
  /**
79
177
  * A serialized snapshot of the record store's values.
178
+ * This is a plain JavaScript object that can be saved to storage or transmitted over the network.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * const serialized: SerializedStore<Book> = {
183
+ * 'book:123': { id: 'book:123', typeName: 'book', title: 'The Lathe of Heaven' },
184
+ * 'book:456': { id: 'book:456', typeName: 'book', title: 'The Left Hand of Darkness' }
185
+ * }
186
+ * ```
80
187
  *
81
188
  * @public
82
189
  */
83
190
  export type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>
84
191
 
85
- /** @public */
192
+ /**
193
+ * A snapshot of the store including both data and schema information.
194
+ * This enables proper migration when loading data from different schema versions.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * const snapshot = store.getStoreSnapshot()
199
+ * // Later...
200
+ * store.loadStoreSnapshot(snapshot)
201
+ * ```
202
+ *
203
+ * @public
204
+ */
86
205
  export interface StoreSnapshot<R extends UnknownRecord> {
206
+ /** The serialized store data */
87
207
  store: SerializedStore<R>
208
+ /** The serialized schema information */
88
209
  schema: SerializedSchema
89
210
  }
90
211
 
91
- /** @public */
212
+ /**
213
+ * A validator for store records that ensures data integrity.
214
+ * Validators are called when records are created or updated.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * const bookValidator: StoreValidator<Book> = {
219
+ * validate(record: unknown): Book {
220
+ * // Validate and return the record
221
+ * if (typeof record !== 'object' || !record.title) {
222
+ * throw new Error('Invalid book')
223
+ * }
224
+ * return record as Book
225
+ * }
226
+ * }
227
+ * ```
228
+ *
229
+ * @public
230
+ */
92
231
  export interface StoreValidator<R extends UnknownRecord> {
232
+ /**
233
+ * Validate a record.
234
+ *
235
+ * @param record - The record to validate
236
+ * @returns The validated record
237
+ * @throws When validation fails
238
+ */
93
239
  validate(record: unknown): R
240
+ /**
241
+ * Validate a record using a known good version for reference.
242
+ *
243
+ * @param knownGoodVersion - A known valid version of the record
244
+ * @param record - The record to validate
245
+ * @returns The validated record
246
+ */
94
247
  validateUsingKnownGoodVersion?(knownGoodVersion: R, record: unknown): R
95
248
  }
96
249
 
97
- /** @public */
250
+ /**
251
+ * A map of validators for each record type in the store.
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * const validators: StoreValidators<Book | Author> = {
256
+ * book: bookValidator,
257
+ * author: authorValidator
258
+ * }
259
+ * ```
260
+ *
261
+ * @public
262
+ */
98
263
  export type StoreValidators<R extends UnknownRecord> = {
99
264
  [K in R['typeName']]: StoreValidator<Extract<R, { typeName: K }>>
100
265
  }
101
266
 
102
- /** @public */
267
+ /**
268
+ * Information about an error that occurred in the store.
269
+ *
270
+ * @example
271
+ * ```ts
272
+ * const error: StoreError = {
273
+ * error: new Error('Validation failed'),
274
+ * phase: 'updateRecord',
275
+ * recordBefore: oldRecord,
276
+ * recordAfter: newRecord,
277
+ * isExistingValidationIssue: false
278
+ * }
279
+ * ```
280
+ *
281
+ * @public
282
+ */
103
283
  export interface StoreError {
284
+ /** The error that occurred */
104
285
  error: Error
286
+ /** The phase during which the error occurred */
105
287
  phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'
288
+ /** The record state before the operation (if applicable) */
106
289
  recordBefore?: unknown
290
+ /** The record state after the operation */
107
291
  recordAfter: unknown
292
+ /** Whether this is an existing validation issue */
108
293
  isExistingValidationIssue: boolean
109
294
  }
110
295
 
111
- /** @internal */
296
+ /**
297
+ * Extract the record type from a Store type.
298
+ * Used internally for type inference.
299
+ *
300
+ * @internal
301
+ */
112
302
  export type StoreRecord<S extends Store<any>> = S extends Store<infer R> ? R : never
113
303
 
114
304
  /**
115
- * A store of records.
305
+ * A reactive store that manages collections of typed records.
306
+ *
307
+ * The Store is the central container for your application's data, providing:
308
+ * - Reactive state management with automatic updates
309
+ * - Type-safe record operations
310
+ * - History tracking and change notifications
311
+ * - Schema validation and migrations
312
+ * - Side effects and business logic hooks
313
+ * - Efficient querying and indexing
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * // Create a store with schema
318
+ * const schema = StoreSchema.create({
319
+ * book: Book,
320
+ * author: Author
321
+ * })
322
+ *
323
+ * const store = new Store({
324
+ * schema,
325
+ * props: {}
326
+ * })
327
+ *
328
+ * // Add records
329
+ * const book = Book.create({ title: 'The Lathe of Heaven', author: 'Le Guin' })
330
+ * store.put([book])
331
+ *
332
+ * // Listen to changes
333
+ * store.listen((entry) => {
334
+ * console.log('Changes:', entry.changes)
335
+ * })
336
+ * ```
116
337
  *
117
338
  * @public
118
339
  */
119
340
  export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
120
341
  /**
121
- * The random id of the store.
342
+ * The unique identifier of the store instance.
343
+ *
344
+ * @public
122
345
  */
123
346
  public readonly id: string
124
347
  /**
@@ -140,7 +363,19 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
140
363
  })
141
364
 
142
365
  /**
143
- * A StoreQueries instance for this store.
366
+ * Reactive queries and indexes for efficiently accessing store data.
367
+ * Provides methods for filtering, indexing, and subscribing to subsets of records.
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * // Create an index by a property
372
+ * const booksByAuthor = store.query.index('book', 'author')
373
+ *
374
+ * // Get records matching criteria
375
+ * const inStockBooks = store.query.records('book', () => ({
376
+ * inStock: { eq: true }
377
+ * }))
378
+ * ```
144
379
  *
145
380
  * @public
146
381
  * @readonly
@@ -178,23 +413,65 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
178
413
  /* noop */
179
414
  }
180
415
 
416
+ /**
417
+ * The schema that defines the structure and validation rules for records in this store.
418
+ *
419
+ * @public
420
+ */
181
421
  readonly schema: StoreSchema<R, Props>
182
422
 
423
+ /**
424
+ * Custom properties associated with this store instance.
425
+ *
426
+ * @public
427
+ */
183
428
  readonly props: Props
184
429
 
430
+ /**
431
+ * A mapping of record scopes to the set of record type names that belong to each scope.
432
+ * Used to filter records by their persistence and synchronization behavior.
433
+ *
434
+ * @public
435
+ */
185
436
  public readonly scopedTypes: { readonly [K in RecordScope]: ReadonlySet<R['typeName']> }
186
437
 
438
+ /**
439
+ * Side effects manager that handles lifecycle events for record operations.
440
+ * Allows registration of callbacks for create, update, delete, and validation events.
441
+ *
442
+ * @example
443
+ * ```ts
444
+ * store.sideEffects.registerAfterCreateHandler('book', (book) => {
445
+ * console.log('Book created:', book.title)
446
+ * })
447
+ * ```
448
+ *
449
+ * @public
450
+ */
187
451
  public readonly sideEffects = new StoreSideEffects<R>(this)
188
452
 
453
+ /**
454
+ * Creates a new Store instance.
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * const store = new Store({
459
+ * schema: StoreSchema.create({ book: Book }),
460
+ * props: { appName: 'MyLibrary' },
461
+ * initialData: savedData
462
+ * })
463
+ * ```
464
+ *
465
+ * @param config - Configuration object for the store
466
+ */
189
467
  constructor(config: {
468
+ /** Optional unique identifier for the store */
190
469
  id?: string
191
- /** The store's initial data. */
470
+ /** The store's initial data to populate on creation */
192
471
  initialData?: SerializedStore<R>
193
- /**
194
- * A map of validators for each record type. A record's validator will be called when the record
195
- * is created or updated. It should throw an error if the record is invalid.
196
- */
472
+ /** The schema defining record types, validation, and migrations */
197
473
  schema: StoreSchema<R, Props>
474
+ /** Custom properties for the store instance */
198
475
  props: Props
199
476
  }) {
200
477
  const { initialData, schema, id } = config
@@ -327,10 +604,21 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
327
604
  }
328
605
 
329
606
  /**
330
- * Add some records to the store. It's an error if they already exist.
607
+ * Add or update records in the store. If a record with the same ID already exists, it will be updated.
608
+ * Otherwise, a new record will be created.
331
609
  *
332
- * @param records - The records to add.
333
- * @param phaseOverride - The phase override.
610
+ * @example
611
+ * ```ts
612
+ * // Add new records
613
+ * const book = Book.create({ title: 'Lathe Of Heaven', author: 'Le Guin' })
614
+ * store.put([book])
615
+ *
616
+ * // Update existing record
617
+ * store.put([{ ...book, title: 'The Lathe of Heaven' }])
618
+ * ```
619
+ *
620
+ * @param records - The records to add or update
621
+ * @param phaseOverride - Override the validation phase (used internally)
334
622
  * @public
335
623
  */
336
624
  put(records: R[], phaseOverride?: 'initialize'): void {
@@ -411,9 +699,18 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
411
699
  }
412
700
 
413
701
  /**
414
- * Remove some records from the store via their ids.
702
+ * Remove records from the store by their IDs.
703
+ *
704
+ * @example
705
+ * ```ts
706
+ * // Remove a single record
707
+ * store.remove([book.id])
708
+ *
709
+ * // Remove multiple records
710
+ * store.remove([book1.id, book2.id, book3.id])
711
+ * ```
415
712
  *
416
- * @param ids - The ids of the records to remove.
713
+ * @param ids - The IDs of the records to remove
417
714
  * @public
418
715
  */
419
716
  remove(ids: IdOf<R>[]): void {
@@ -447,9 +744,18 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
447
744
  }
448
745
 
449
746
  /**
450
- * Get the value of a store record by its id.
747
+ * Get a record by its ID. This creates a reactive subscription to the record.
451
748
  *
452
- * @param id - The id of the record to get.
749
+ * @example
750
+ * ```ts
751
+ * const book = store.get(bookId)
752
+ * if (book) {
753
+ * console.log(book.title)
754
+ * }
755
+ * ```
756
+ *
757
+ * @param id - The ID of the record to get
758
+ * @returns The record if it exists, undefined otherwise
453
759
  * @public
454
760
  */
455
761
  get<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {
@@ -457,9 +763,17 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
457
763
  }
458
764
 
459
765
  /**
460
- * Get the value of a store record by its id without updating its epoch.
766
+ * Get a record by its ID without creating a reactive subscription.
767
+ * Use this when you need to access a record but don't want reactive updates.
461
768
  *
462
- * @param id - The id of the record to get.
769
+ * @example
770
+ * ```ts
771
+ * // Won't trigger reactive updates when this record changes
772
+ * const book = store.unsafeGetWithoutCapture(bookId)
773
+ * ```
774
+ *
775
+ * @param id - The ID of the record to get
776
+ * @returns The record if it exists, undefined otherwise
463
777
  * @public
464
778
  */
465
779
  unsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined {
@@ -467,10 +781,21 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
467
781
  }
468
782
 
469
783
  /**
470
- * Creates a JSON payload from the record store.
784
+ * Serialize the store's records to a plain JavaScript object.
785
+ * Only includes records matching the specified scope.
471
786
  *
472
- * @param scope - The scope of records to serialize. Defaults to 'document'.
473
- * @returns The record store snapshot as a JSON payload.
787
+ * @example
788
+ * ```ts
789
+ * // Serialize only document records (default)
790
+ * const documentData = store.serialize('document')
791
+ *
792
+ * // Serialize all records
793
+ * const allData = store.serialize('all')
794
+ * ```
795
+ *
796
+ * @param scope - The scope of records to serialize. Defaults to 'document'
797
+ * @returns The serialized store data
798
+ * @public
474
799
  */
475
800
  serialize(scope: RecordScope | 'all' = 'document'): SerializedStore<R> {
476
801
  const result = {} as SerializedStore<R>
@@ -484,14 +809,20 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
484
809
 
485
810
  /**
486
811
  * Get a serialized snapshot of the store and its schema.
812
+ * This includes both the data and schema information needed for proper migration.
487
813
  *
814
+ * @example
488
815
  * ```ts
489
816
  * const snapshot = store.getStoreSnapshot()
490
- * store.loadStoreSnapshot(snapshot)
491
- * ```
817
+ * localStorage.setItem('myApp', JSON.stringify(snapshot))
492
818
  *
493
- * @param scope - The scope of records to serialize. Defaults to 'document'.
819
+ * // Later...
820
+ * const saved = JSON.parse(localStorage.getItem('myApp'))
821
+ * store.loadStoreSnapshot(saved)
822
+ * ```
494
823
  *
824
+ * @param scope - The scope of records to serialize. Defaults to 'document'
825
+ * @returns A snapshot containing both store data and schema information
495
826
  * @public
496
827
  */
497
828
  getStoreSnapshot(scope: RecordScope | 'all' = 'document'): StoreSnapshot<R> {
@@ -502,14 +833,18 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
502
833
  }
503
834
 
504
835
  /**
505
- * Migrate a serialized snapshot of the store and its schema.
836
+ * Migrate a serialized snapshot to the current schema version.
837
+ * This applies any necessary migrations to bring old data up to date.
506
838
  *
839
+ * @example
507
840
  * ```ts
508
- * const snapshot = store.getStoreSnapshot()
509
- * store.migrateSnapshot(snapshot)
841
+ * const oldSnapshot = JSON.parse(localStorage.getItem('myApp'))
842
+ * const migratedSnapshot = store.migrateSnapshot(oldSnapshot)
510
843
  * ```
511
844
  *
512
- * @param snapshot - The snapshot to load.
845
+ * @param snapshot - The snapshot to migrate
846
+ * @returns The migrated snapshot with current schema version
847
+ * @throws Error if migration fails
513
848
  * @public
514
849
  */
515
850
  migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R> {
@@ -526,14 +861,17 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
526
861
  }
527
862
 
528
863
  /**
529
- * Load a serialized snapshot.
864
+ * Load a serialized snapshot into the store, replacing all current data.
865
+ * The snapshot will be automatically migrated to the current schema version if needed.
530
866
  *
867
+ * @example
531
868
  * ```ts
532
- * const snapshot = store.getStoreSnapshot()
869
+ * const snapshot = JSON.parse(localStorage.getItem('myApp'))
533
870
  * store.loadStoreSnapshot(snapshot)
534
871
  * ```
535
872
  *
536
- * @param snapshot - The snapshot to load.
873
+ * @param snapshot - The snapshot to load
874
+ * @throws Error if migration fails or snapshot is invalid
537
875
  * @public
538
876
  */
539
877
  loadStoreSnapshot(snapshot: StoreSnapshot<R>): void {
@@ -557,9 +895,15 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
557
895
  }
558
896
 
559
897
  /**
560
- * Get an array of all values in the store.
898
+ * Get an array of all records in the store.
899
+ *
900
+ * @example
901
+ * ```ts
902
+ * const allRecords = store.allRecords()
903
+ * const books = allRecords.filter(r => r.typeName === 'book')
904
+ * ```
561
905
  *
562
- * @returns An array of all values in the store.
906
+ * @returns An array containing all records in the store
563
907
  * @public
564
908
  */
565
909
  allRecords(): R[] {
@@ -567,7 +911,13 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
567
911
  }
568
912
 
569
913
  /**
570
- * Removes all records from the store.
914
+ * Remove all records from the store.
915
+ *
916
+ * @example
917
+ * ```ts
918
+ * store.clear()
919
+ * console.log(store.allRecords().length) // 0
920
+ * ```
571
921
  *
572
922
  * @public
573
923
  */
@@ -576,11 +926,20 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
576
926
  }
577
927
 
578
928
  /**
579
- * Update a record. To update multiple records at once, use the `update` method of the
580
- * `TypedStore` class.
929
+ * Update a single record using an updater function. To update multiple records at once,
930
+ * use the `update` method of the `TypedStore` class.
581
931
  *
582
- * @param id - The id of the record to update.
583
- * @param updater - A function that updates the record.
932
+ * @example
933
+ * ```ts
934
+ * store.update(book.id, (book) => ({
935
+ * ...book,
936
+ * title: 'Updated Title'
937
+ * }))
938
+ * ```
939
+ *
940
+ * @param id - The ID of the record to update
941
+ * @param updater - A function that receives the current record and returns the updated record
942
+ * @public
584
943
  */
585
944
  update<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>) {
586
945
  const existing = this.unsafeGetWithoutCapture(id)
@@ -593,9 +952,17 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
593
952
  }
594
953
 
595
954
  /**
596
- * Get whether the record store has a id.
955
+ * Check whether a record with the given ID exists in the store.
956
+ *
957
+ * @example
958
+ * ```ts
959
+ * if (store.has(bookId)) {
960
+ * console.log('Book exists!')
961
+ * }
962
+ * ```
597
963
  *
598
- * @param id - The id of the record to check.
964
+ * @param id - The ID of the record to check
965
+ * @returns True if the record exists, false otherwise
599
966
  * @public
600
967
  */
601
968
  has<K extends IdOf<R>>(id: K): boolean {
@@ -603,11 +970,30 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
603
970
  }
604
971
 
605
972
  /**
606
- * Add a new listener to the store.
973
+ * Add a listener that will be called when the store changes.
974
+ * Returns a function to remove the listener.
975
+ *
976
+ * @example
977
+ * ```ts
978
+ * const removeListener = store.listen((entry) => {
979
+ * console.log('Changes:', entry.changes)
980
+ * console.log('Source:', entry.source)
981
+ * })
982
+ *
983
+ * // Listen only to user changes to document records
984
+ * const removeDocumentListener = store.listen(
985
+ * (entry) => console.log('Document changed:', entry),
986
+ * { source: 'user', scope: 'document' }
987
+ * )
988
+ *
989
+ * // Later, remove the listener
990
+ * removeListener()
991
+ * ```
607
992
  *
608
- * @param onHistory - The listener to call when the store updates.
609
- * @param filters - Filters to apply to the listener.
610
- * @returns A function to remove the listener.
993
+ * @param onHistory - The listener function to call when changes occur
994
+ * @param filters - Optional filters to control when the listener is called
995
+ * @returns A function that removes the listener when called
996
+ * @public
611
997
  */
612
998
  listen(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>) {
613
999
  // flush history so that this listener's history starts from exactly now
@@ -640,9 +1026,19 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
640
1026
  private isMergingRemoteChanges = false
641
1027
 
642
1028
  /**
643
- * Merge changes from a remote source
1029
+ * Merge changes from a remote source. Changes made within the provided function
1030
+ * will be marked with source 'remote' instead of 'user'.
1031
+ *
1032
+ * @example
1033
+ * ```ts
1034
+ * // Changes from sync/collaboration
1035
+ * store.mergeRemoteChanges(() => {
1036
+ * store.put(remoteRecords)
1037
+ * store.remove(deletedIds)
1038
+ * })
1039
+ * ```
644
1040
  *
645
- * @param fn - A function that merges the external changes.
1041
+ * @param fn - A function that applies the remote changes
646
1042
  * @public
647
1043
  */
648
1044
  mergeRemoteChanges(fn: () => void) {
@@ -888,12 +1284,25 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
888
1284
  }
889
1285
 
890
1286
  /**
891
- * Collect all history entries by their adjacent sources.
892
- * For example, [user, user, remote, remote, user] would result in [user, remote, user],
893
- * with adjacent entries of the same source squashed into a single entry.
1287
+ * Collect and squash history entries by their adjacent sources.
1288
+ * Adjacent entries from the same source are combined into a single entry.
894
1289
  *
895
- * @param entries - The array of history entries.
896
- * @returns A map of history entries by their sources.
1290
+ * For example: [user, user, remote, remote, user] becomes [user, remote, user]
1291
+ *
1292
+ * @example
1293
+ * ```ts
1294
+ * const entries = [
1295
+ * { source: 'user', changes: userChanges1 },
1296
+ * { source: 'user', changes: userChanges2 },
1297
+ * { source: 'remote', changes: remoteChanges }
1298
+ * ]
1299
+ *
1300
+ * const squashed = squashHistoryEntries(entries)
1301
+ * // Results in 2 entries: combined user changes + remote changes
1302
+ * ```
1303
+ *
1304
+ * @param entries - The array of history entries to squash
1305
+ * @returns An array of squashed history entries
897
1306
  * @public
898
1307
  */
899
1308
  function squashHistoryEntries<T extends UnknownRecord>(
@@ -924,11 +1333,21 @@ function squashHistoryEntries<T extends UnknownRecord>(
924
1333
  )
925
1334
  }
926
1335
 
1336
+ /**
1337
+ * Internal class that accumulates history entries before they are flushed to listeners.
1338
+ * Handles batching and squashing of adjacent entries from the same source.
1339
+ *
1340
+ * @internal
1341
+ */
927
1342
  class HistoryAccumulator<T extends UnknownRecord> {
928
1343
  private _history: HistoryEntry<T>[] = []
929
1344
 
930
1345
  private _interceptors: Set<(entry: HistoryEntry<T>) => void> = new Set()
931
1346
 
1347
+ /**
1348
+ * Add an interceptor that will be called for each history entry.
1349
+ * Returns a function to remove the interceptor.
1350
+ */
932
1351
  addInterceptor(fn: (entry: HistoryEntry<T>) => void) {
933
1352
  this._interceptors.add(fn)
934
1353
  return () => {
@@ -936,6 +1355,10 @@ class HistoryAccumulator<T extends UnknownRecord> {
936
1355
  }
937
1356
  }
938
1357
 
1358
+ /**
1359
+ * Add a history entry to the accumulator.
1360
+ * Calls all registered interceptors with the entry.
1361
+ */
939
1362
  add(entry: HistoryEntry<T>) {
940
1363
  this._history.push(entry)
941
1364
  for (const interceptor of this._interceptors) {
@@ -943,39 +1366,82 @@ class HistoryAccumulator<T extends UnknownRecord> {
943
1366
  }
944
1367
  }
945
1368
 
1369
+ /**
1370
+ * Flush all accumulated history entries, squashing adjacent entries from the same source.
1371
+ * Clears the internal history buffer.
1372
+ */
946
1373
  flush() {
947
1374
  const history = squashHistoryEntries(this._history)
948
1375
  this._history = []
949
1376
  return history
950
1377
  }
951
1378
 
1379
+ /**
1380
+ * Clear all accumulated history entries without flushing.
1381
+ */
952
1382
  clear() {
953
1383
  this._history = []
954
1384
  }
955
1385
 
1386
+ /**
1387
+ * Check if there are any accumulated history entries.
1388
+ */
956
1389
  hasChanges() {
957
1390
  return this._history.length > 0
958
1391
  }
959
1392
  }
960
1393
 
961
- /** @public */
1394
+ /**
1395
+ * A store or an object containing a store.
1396
+ * This type is used for APIs that can accept either a store directly or an object with a store property.
1397
+ *
1398
+ * @example
1399
+ * ```ts
1400
+ * function useStore(storeOrObject: StoreObject<MyRecord>) {
1401
+ * const store = storeOrObject instanceof Store ? storeOrObject : storeOrObject.store
1402
+ * return store
1403
+ * }
1404
+ * ```
1405
+ *
1406
+ * @public
1407
+ */
962
1408
  export type StoreObject<R extends UnknownRecord> = Store<R> | { store: Store<R> }
963
- /** @public */
1409
+ /**
1410
+ * Extract the record type from a StoreObject.
1411
+ *
1412
+ * @example
1413
+ * ```ts
1414
+ * type MyStoreObject = { store: Store<Book | Author> }
1415
+ * type Records = StoreObjectRecordType<MyStoreObject> // Book | Author
1416
+ * ```
1417
+ *
1418
+ * @public
1419
+ */
964
1420
  export type StoreObjectRecordType<Context extends StoreObject<any>> =
965
1421
  Context extends Store<infer R> ? R : Context extends { store: Store<infer R> } ? R : never
966
1422
 
967
1423
  /**
968
- * Free version of {@link Store.createComputedCache}.
1424
+ * Create a computed cache that works with any StoreObject (store or object containing a store).
1425
+ * This is a standalone version of Store.createComputedCache that can work with multiple store instances.
969
1426
  *
970
1427
  * @example
971
1428
  * ```ts
972
- * const myCache = createComputedCache('myCache', (editor: Editor, shape: TLShape) => {
973
- * return editor.getSomethingExpensive(shape)
974
- * })
1429
+ * const expensiveCache = createComputedCache(
1430
+ * 'expensiveData',
1431
+ * (context: { store: Store<Book> }, book: Book) => {
1432
+ * return performExpensiveCalculation(book)
1433
+ * }
1434
+ * )
975
1435
  *
976
- * myCache.get(editor, shape.id)
1436
+ * // Use with different store instances
1437
+ * const result1 = expensiveCache.get(storeObject1, bookId)
1438
+ * const result2 = expensiveCache.get(storeObject2, bookId)
977
1439
  * ```
978
1440
  *
1441
+ * @param name - A unique name for the cache (used for debugging)
1442
+ * @param derive - Function that derives a value from the context and record
1443
+ * @param opts - Optional configuration for equality checks
1444
+ * @returns A cache that can be used with multiple store instances
979
1445
  * @public
980
1446
  */
981
1447
  export function createComputedCache<