@tldraw/store 4.1.0-canary.ccd6179e1cb2 → 4.1.0-canary.d716f21afebb

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
@@ -27,28 +27,289 @@ export declare function assertIdType<R extends UnknownRecord>(id: string | undef
27
27
  export declare class AtomMap<K, V> implements Map<K, V> {
28
28
  private readonly name;
29
29
  private atoms;
30
+ /**
31
+ * Creates a new AtomMap instance.
32
+ *
33
+ * name - A unique name for this map, used for atom identification
34
+ * entries - Optional initial entries to populate the map with
35
+ * @example
36
+ * ```ts
37
+ * // Create an empty map
38
+ * const map = new AtomMap('userMap')
39
+ *
40
+ * // Create a map with initial data
41
+ * const initialData: [string, number][] = [['a', 1], ['b', 2]]
42
+ * const mapWithData = new AtomMap('numbersMap', initialData)
43
+ * ```
44
+ */
30
45
  constructor(name: string, entries?: Iterable<readonly [K, V]>);
31
46
  /* Excluded from this release type: getAtom */
47
+ /**
48
+ * Gets the value associated with a key. Returns undefined if the key doesn't exist.
49
+ * This method is reactive and will cause reactive contexts to update when the value changes.
50
+ *
51
+ * @param key - The key to retrieve the value for
52
+ * @returns The value associated with the key, or undefined if not found
53
+ * @example
54
+ * ```ts
55
+ * const map = new AtomMap('myMap')
56
+ * map.set('name', 'Alice')
57
+ * console.log(map.get('name')) // 'Alice'
58
+ * console.log(map.get('missing')) // undefined
59
+ * ```
60
+ */
32
61
  get(key: K): undefined | V;
62
+ /**
63
+ * Gets the value associated with a key without creating reactive dependencies.
64
+ * This method will not cause reactive contexts to update when the value changes.
65
+ *
66
+ * @param key - The key to retrieve the value for
67
+ * @returns The value associated with the key, or undefined if not found
68
+ * @example
69
+ * ```ts
70
+ * const map = new AtomMap('myMap')
71
+ * map.set('count', 42)
72
+ * const value = map.__unsafe__getWithoutCapture('count') // No reactive subscription
73
+ * ```
74
+ */
33
75
  __unsafe__getWithoutCapture(key: K): undefined | V;
76
+ /**
77
+ * Checks whether a key exists in the map.
78
+ * This method is reactive and will cause reactive contexts to update when keys are added or removed.
79
+ *
80
+ * @param key - The key to check for
81
+ * @returns True if the key exists in the map, false otherwise
82
+ * @example
83
+ * ```ts
84
+ * const map = new AtomMap('myMap')
85
+ * console.log(map.has('name')) // false
86
+ * map.set('name', 'Alice')
87
+ * console.log(map.has('name')) // true
88
+ * ```
89
+ */
34
90
  has(key: K): boolean;
91
+ /**
92
+ * Checks whether a key exists in the map without creating reactive dependencies.
93
+ * This method will not cause reactive contexts to update when keys are added or removed.
94
+ *
95
+ * @param key - The key to check for
96
+ * @returns True if the key exists in the map, false otherwise
97
+ * @example
98
+ * ```ts
99
+ * const map = new AtomMap('myMap')
100
+ * map.set('active', true)
101
+ * const exists = map.__unsafe__hasWithoutCapture('active') // No reactive subscription
102
+ * ```
103
+ */
35
104
  __unsafe__hasWithoutCapture(key: K): boolean;
105
+ /**
106
+ * Sets a value for the given key. If the key already exists, its value is updated.
107
+ * If the key doesn't exist, a new entry is created.
108
+ *
109
+ * @param key - The key to set the value for
110
+ * @param value - The value to associate with the key
111
+ * @returns This AtomMap instance for method chaining
112
+ * @example
113
+ * ```ts
114
+ * const map = new AtomMap('myMap')
115
+ * map.set('name', 'Alice').set('age', 30)
116
+ * ```
117
+ */
36
118
  set(key: K, value: V): this;
119
+ /**
120
+ * Updates an existing value using an updater function.
121
+ *
122
+ * @param key - The key of the value to update
123
+ * @param updater - A function that receives the current value and returns the new value
124
+ * @throws Error if the key doesn't exist in the map
125
+ * @example
126
+ * ```ts
127
+ * const map = new AtomMap('myMap')
128
+ * map.set('count', 5)
129
+ * map.update('count', count => count + 1) // count is now 6
130
+ * ```
131
+ */
37
132
  update(key: K, updater: (value: V) => V): void;
133
+ /**
134
+ * Removes a key-value pair from the map.
135
+ *
136
+ * @param key - The key to remove
137
+ * @returns True if the key existed and was removed, false if it didn't exist
138
+ * @example
139
+ * ```ts
140
+ * const map = new AtomMap('myMap')
141
+ * map.set('temp', 'value')
142
+ * console.log(map.delete('temp')) // true
143
+ * console.log(map.delete('missing')) // false
144
+ * ```
145
+ */
38
146
  delete(key: K): boolean;
147
+ /**
148
+ * Removes multiple key-value pairs from the map in a single transaction.
149
+ *
150
+ * @param keys - An iterable of keys to remove
151
+ * @returns An array of [key, value] pairs that were actually deleted
152
+ * @example
153
+ * ```ts
154
+ * const map = new AtomMap('myMap')
155
+ * map.set('a', 1).set('b', 2).set('c', 3)
156
+ * const deleted = map.deleteMany(['a', 'c', 'missing'])
157
+ * console.log(deleted) // [['a', 1], ['c', 3]]
158
+ * ```
159
+ */
39
160
  deleteMany(keys: Iterable<K>): [K, V][];
161
+ /**
162
+ * Removes all key-value pairs from the map.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * const map = new AtomMap('myMap')
167
+ * map.set('a', 1).set('b', 2)
168
+ * map.clear()
169
+ * console.log(map.size) // 0
170
+ * ```
171
+ */
40
172
  clear(): void;
173
+ /**
174
+ * Returns an iterator that yields [key, value] pairs for each entry in the map.
175
+ * This method is reactive and will cause reactive contexts to update when entries change.
176
+ *
177
+ * @returns A generator that yields [key, value] tuples
178
+ * @example
179
+ * ```ts
180
+ * const map = new AtomMap('myMap')
181
+ * map.set('a', 1).set('b', 2)
182
+ * for (const [key, value] of map.entries()) {
183
+ * console.log(`${key}: ${value}`)
184
+ * }
185
+ * ```
186
+ */
41
187
  entries(): Generator<[K, V], undefined, unknown>;
188
+ /**
189
+ * Returns an iterator that yields all keys in the map.
190
+ * This method is reactive and will cause reactive contexts to update when keys change.
191
+ *
192
+ * @returns A generator that yields keys
193
+ * @example
194
+ * ```ts
195
+ * const map = new AtomMap('myMap')
196
+ * map.set('name', 'Alice').set('age', 30)
197
+ * for (const key of map.keys()) {
198
+ * console.log(key) // 'name', 'age'
199
+ * }
200
+ * ```
201
+ */
42
202
  keys(): Generator<K, undefined, unknown>;
203
+ /**
204
+ * Returns an iterator that yields all values in the map.
205
+ * This method is reactive and will cause reactive contexts to update when values change.
206
+ *
207
+ * @returns A generator that yields values
208
+ * @example
209
+ * ```ts
210
+ * const map = new AtomMap('myMap')
211
+ * map.set('name', 'Alice').set('age', 30)
212
+ * for (const value of map.values()) {
213
+ * console.log(value) // 'Alice', 30
214
+ * }
215
+ * ```
216
+ */
43
217
  values(): Generator<V, undefined, unknown>;
218
+ /**
219
+ * The number of key-value pairs in the map.
220
+ * This property is reactive and will cause reactive contexts to update when the size changes.
221
+ *
222
+ * @returns The number of entries in the map
223
+ * @example
224
+ * ```ts
225
+ * const map = new AtomMap('myMap')
226
+ * console.log(map.size) // 0
227
+ * map.set('a', 1)
228
+ * console.log(map.size) // 1
229
+ * ```
230
+ */
44
231
  get size(): number;
232
+ /**
233
+ * Executes a provided function once for each key-value pair in the map.
234
+ * This method is reactive and will cause reactive contexts to update when entries change.
235
+ *
236
+ * @param callbackfn - Function to execute for each entry
237
+ * - value - The value of the current entry
238
+ * - key - The key of the current entry
239
+ * - map - The AtomMap being traversed
240
+ * @param thisArg - Value to use as `this` when executing the callback
241
+ * @example
242
+ * ```ts
243
+ * const map = new AtomMap('myMap')
244
+ * map.set('a', 1).set('b', 2)
245
+ * map.forEach((value, key) => {
246
+ * console.log(`${key} = ${value}`)
247
+ * })
248
+ * ```
249
+ */
45
250
  forEach(callbackfn: (value: V, key: K, map: AtomMap<K, V>) => void, thisArg?: any): void;
251
+ /**
252
+ * Returns the default iterator for the map, which is the same as entries().
253
+ * This allows the map to be used in for...of loops and other iterable contexts.
254
+ *
255
+ * @returns The same iterator as entries()
256
+ * @example
257
+ * ```ts
258
+ * const map = new AtomMap('myMap')
259
+ * map.set('a', 1).set('b', 2)
260
+ *
261
+ * // These are equivalent:
262
+ * for (const [key, value] of map) {
263
+ * console.log(`${key}: ${value}`)
264
+ * }
265
+ *
266
+ * for (const [key, value] of map.entries()) {
267
+ * console.log(`${key}: ${value}`)
268
+ * }
269
+ * ```
270
+ */
46
271
  [Symbol.iterator](): Generator<[K, V], undefined, unknown>;
272
+ /**
273
+ * The string tag used by Object.prototype.toString for this class.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * const map = new AtomMap('myMap')
278
+ * console.log(Object.prototype.toString.call(map)) // '[object AtomMap]'
279
+ * ```
280
+ */
47
281
  [Symbol.toStringTag]: string;
48
282
  }
49
283
 
50
284
  /**
51
- * The base record that all records must extend.
285
+ * The base record interface that all records in the store must extend.
286
+ * This interface provides the fundamental structure required for all records: a unique ID and a type name.
287
+ * The type parameters ensure type safety and prevent mixing of different record types.
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * // Define a Book record that extends BaseRecord
292
+ * interface Book extends BaseRecord<'book', RecordId<Book>> {
293
+ * title: string
294
+ * author: string
295
+ * publishedYear: number
296
+ * }
297
+ *
298
+ * // Define an Author record
299
+ * interface Author extends BaseRecord<'author', RecordId<Author>> {
300
+ * name: string
301
+ * birthYear: number
302
+ * }
303
+ *
304
+ * // Usage with RecordType
305
+ * const Book = createRecordType<Book>('book', { scope: 'document' })
306
+ * const book = Book.create({
307
+ * title: '1984',
308
+ * author: 'George Orwell',
309
+ * publishedYear: 1949
310
+ * })
311
+ * // Results in: { id: 'book:abc123', typeName: 'book', title: '1984', ... }
312
+ * ```
52
313
  *
53
314
  * @public
54
315
  */
@@ -57,59 +318,135 @@ export declare interface BaseRecord<TypeName extends string, Id extends RecordId
57
318
  readonly typeName: TypeName;
58
319
  }
59
320
 
60
- /** @public */
321
+ /**
322
+ * The source of a change to the store.
323
+ * - `'user'` - Changes originating from local user actions
324
+ * - `'remote'` - Changes originating from remote synchronization
325
+ *
326
+ * @public
327
+ */
61
328
  export declare type ChangeSource = 'remote' | 'user';
62
329
 
63
330
  /**
64
331
  * A diff describing the changes to a collection.
65
332
  *
333
+ * @example
334
+ * ```ts
335
+ * const diff: CollectionDiff<string> = {
336
+ * added: new Set(['newItem']),
337
+ * removed: new Set(['oldItem'])
338
+ * }
339
+ * ```
340
+ *
66
341
  * @public
67
342
  */
68
343
  export declare interface CollectionDiff<T> {
344
+ /** Items that were added to the collection */
69
345
  added?: Set<T>;
346
+ /** Items that were removed from the collection */
70
347
  removed?: Set<T>;
71
348
  }
72
349
 
73
350
  /**
74
- * A record store is a collection of records of different types.
351
+ * A computed cache that stores derived data for records.
352
+ * The cache automatically updates when underlying records change and cleans up when records are deleted.
353
+ *
354
+ * @example
355
+ * ```ts
356
+ * const expensiveCache = store.createComputedCache(
357
+ * 'expensive',
358
+ * (book: Book) => performExpensiveCalculation(book)
359
+ * )
360
+ *
361
+ * const result = expensiveCache.get(bookId)
362
+ * ```
75
363
  *
76
364
  * @public
77
365
  */
78
366
  export declare interface ComputedCache<Data, R extends UnknownRecord> {
367
+ /**
368
+ * Get the cached data for a record by its ID.
369
+ *
370
+ * @param id - The ID of the record
371
+ * @returns The cached data or undefined if the record doesn't exist
372
+ */
79
373
  get(id: IdOf<R>): Data | undefined;
80
374
  }
81
375
 
82
376
  /**
83
- * Free version of {@link Store.createComputedCache}.
377
+ * Create a computed cache that works with any StoreObject (store or object containing a store).
378
+ * This is a standalone version of Store.createComputedCache that can work with multiple store instances.
84
379
  *
85
380
  * @example
86
381
  * ```ts
87
- * const myCache = createComputedCache('myCache', (editor: Editor, shape: TLShape) => {
88
- * return editor.getSomethingExpensive(shape)
89
- * })
382
+ * const expensiveCache = createComputedCache(
383
+ * 'expensiveData',
384
+ * (context: { store: Store<Book> }, book: Book) => {
385
+ * return performExpensiveCalculation(book)
386
+ * }
387
+ * )
90
388
  *
91
- * myCache.get(editor, shape.id)
389
+ * // Use with different store instances
390
+ * const result1 = expensiveCache.get(storeObject1, bookId)
391
+ * const result2 = expensiveCache.get(storeObject2, bookId)
92
392
  * ```
93
393
  *
394
+ * @param name - A unique name for the cache (used for debugging)
395
+ * @param derive - Function that derives a value from the context and record
396
+ * @param opts - Optional configuration for equality checks
397
+ * @returns A cache that can be used with multiple store instances
94
398
  * @public
95
399
  */
96
400
  export declare function createComputedCache<Context extends StoreObject<any>, Result, Record extends StoreObjectRecordType<Context> = StoreObjectRecordType<Context>>(name: string, derive: (context: Context, record: Record) => Result | undefined, opts?: CreateComputedCacheOpts<Result, Record>): {
97
401
  get(context: Context, id: IdOf<Record>): Result | undefined;
98
402
  };
99
403
 
100
- /** @public */
404
+ /**
405
+ * Options for creating a computed cache.
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * const options: CreateComputedCacheOpts<string[], Book> = {
410
+ * areRecordsEqual: (a, b) => a.title === b.title,
411
+ * areResultsEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b)
412
+ * }
413
+ * ```
414
+ *
415
+ * @public
416
+ */
101
417
  export declare interface CreateComputedCacheOpts<Data, R extends UnknownRecord> {
418
+ /** Custom equality function for comparing records */
102
419
  areRecordsEqual?(a: R, b: R): boolean;
420
+ /** Custom equality function for comparing results */
103
421
  areResultsEqual?(a: Data, b: Data): boolean;
104
422
  }
105
423
 
106
424
  /* Excluded from this release type: createEmptyRecordsDiff */
107
425
 
108
426
  /**
109
- * Creates a named set of migration ids given a named set of version numbers and a sequence id.
427
+ * Creates a named set of migration IDs from version numbers and a sequence ID.
428
+ *
429
+ * This utility function helps generate properly formatted migration IDs that follow
430
+ * the required `sequenceId/version` pattern. It takes a sequence ID and a record
431
+ * of named versions, returning migration IDs that can be used in migration definitions.
110
432
  *
111
433
  * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.
112
- * @public
434
+ * @param sequenceId - The sequence identifier (e.g., 'com.myapp.book')
435
+ * @param versions - Record mapping version names to numbers
436
+ * @returns Record mapping version names to properly formatted migration IDs
437
+ * @example
438
+ * ```ts
439
+ * const migrationIds = createMigrationIds('com.myapp.book', {
440
+ * addGenre: 1,
441
+ * addPublisher: 2,
442
+ * removeOldField: 3
443
+ * })
444
+ * // Result: {
445
+ * // addGenre: 'com.myapp.book/1',
446
+ * // addPublisher: 'com.myapp.book/2',
447
+ * // removeOldField: 'com.myapp.book/3'
448
+ * // }
449
+ * ```
113
450
  * @public
114
451
  */
115
452
  export declare function createMigrationIds<const ID extends string, const Versions extends Record<string, number>>(sequenceId: ID, versions: Versions): {
@@ -117,8 +454,32 @@ export declare function createMigrationIds<const ID extends string, const Versio
117
454
  };
118
455
 
119
456
  /**
120
- * Creates a migration sequence.
457
+ * Creates a migration sequence that defines how to transform data as your schema evolves.
458
+ *
459
+ * A migration sequence contains a series of migrations that are applied in order to transform
460
+ * data from older versions to newer versions. Each migration is identified by a unique ID
461
+ * and can operate at either the record level (transforming individual records) or store level
462
+ * (transforming the entire store structure).
463
+ *
121
464
  * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.
465
+ * @param options - Configuration for the migration sequence
466
+ * - sequenceId - Unique identifier for this migration sequence (e.g., 'com.myapp.book')
467
+ * - sequence - Array of migrations or dependency declarations to include in the sequence
468
+ * - retroactive - Whether migrations should apply to snapshots created before this sequence was added (defaults to true)
469
+ * @returns A validated migration sequence that can be included in a store schema
470
+ * @example
471
+ * ```ts
472
+ * const bookMigrations = createMigrationSequence({
473
+ * sequenceId: 'com.myapp.book',
474
+ * sequence: [
475
+ * {
476
+ * id: 'com.myapp.book/1',
477
+ * scope: 'record',
478
+ * up: (record) => ({ ...record, newField: 'default' })
479
+ * }
480
+ * ]
481
+ * })
482
+ * ```
122
483
  * @public
123
484
  */
124
485
  export declare function createMigrationSequence({ sequence, sequenceId, retroactive, }: {
@@ -130,15 +491,29 @@ export declare function createMigrationSequence({ sequence, sequenceId, retroact
130
491
  /* Excluded from this release type: createRecordMigrationSequence */
131
492
 
132
493
  /**
133
- * Create a record type.
494
+ * Creates a new RecordType with the specified configuration.
134
495
  *
135
- * @example
496
+ * This factory function creates a RecordType that can be used to create, validate, and manage
497
+ * records of a specific type within a store. The resulting RecordType can be extended with
498
+ * default properties using the withDefaultProperties method.
136
499
  *
500
+ * @example
137
501
  * ```ts
138
- * const Book = createRecordType<Book>('book')
502
+ * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {
503
+ * title: string
504
+ * author: string
505
+ * inStock: boolean
506
+ * }
507
+ *
508
+ * const Book = createRecordType<BookRecord>('book', {
509
+ * scope: 'document',
510
+ * validator: bookValidator
511
+ * })
139
512
  * ```
140
513
  *
141
- * @param typeName - The name of the type to create.
514
+ * @param typeName - The unique type name for this record type
515
+ * @param config - Configuration object containing validator, scope, and ephemeral keys
516
+ * @returns A new RecordType instance for creating and managing records
142
517
  * @public
143
518
  */
144
519
  export declare function createRecordType<R extends UnknownRecord>(typeName: R['typeName'], config: {
@@ -167,22 +542,85 @@ export declare function devFreeze<T>(object: T): T;
167
542
 
168
543
  /**
169
544
  * An entry containing changes that originated either by user actions or remote changes.
545
+ * History entries are used to track and replay changes to the store.
546
+ *
547
+ * @example
548
+ * ```ts
549
+ * const entry: HistoryEntry<Book> = {
550
+ * changes: {
551
+ * added: { 'book:123': bookRecord },
552
+ * updated: {},
553
+ * removed: {}
554
+ * },
555
+ * source: 'user'
556
+ * }
557
+ * ```
170
558
  *
171
559
  * @public
172
560
  */
173
561
  export declare interface HistoryEntry<R extends UnknownRecord = UnknownRecord> {
562
+ /** The changes that occurred in this history entry */
174
563
  changes: RecordsDiff<R>;
564
+ /** The source of these changes */
175
565
  source: ChangeSource;
176
566
  }
177
567
 
178
- /** @public */
568
+ /**
569
+ * Utility type that extracts the ID type from a record type.
570
+ * This is useful when you need to work with record IDs without having the full record type.
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * interface Book extends BaseRecord<'book', RecordId<Book>> {
575
+ * title: string
576
+ * author: string
577
+ * }
578
+ *
579
+ * // Extract the ID type from the Book record
580
+ * type BookId = IdOf<Book> // RecordId<Book>
581
+ *
582
+ * function findBook(id: IdOf<Book>): Book | undefined {
583
+ * return store.get(id)
584
+ * }
585
+ * ```
586
+ *
587
+ * @public
588
+ */
179
589
  export declare type IdOf<R extends UnknownRecord> = R['id'];
180
590
 
181
591
  /* Excluded from this release type: IncrementalSetConstructor */
182
592
 
183
- /* Excluded from this release type: isRecordsDiffEmpty */
593
+ /**
594
+ * Checks whether a RecordsDiff contains any changes. A diff is considered empty
595
+ * if it has no added, updated, or removed records.
596
+ *
597
+ * @param diff - The diff to check
598
+ * @returns True if the diff contains no changes, false otherwise
599
+ * @example
600
+ * ```ts
601
+ * const emptyDiff = createEmptyRecordsDiff<Book>()
602
+ * console.log(isRecordsDiffEmpty(emptyDiff)) // true
603
+ *
604
+ * const nonEmptyDiff: RecordsDiff<Book> = {
605
+ * added: { 'book:1': someBook },
606
+ * updated: {},
607
+ * removed: {}
608
+ * }
609
+ * console.log(isRecordsDiffEmpty(nonEmptyDiff)) // false
610
+ * ```
611
+ *
612
+ * @public
613
+ */
614
+ export declare function isRecordsDiffEmpty<T extends UnknownRecord>(diff: RecordsDiff<T>): boolean;
184
615
 
185
- /** @public */
616
+ /**
617
+ * Base interface for legacy migration information.
618
+ *
619
+ * Contains the basic structure used by the legacy migration system, including version
620
+ * range information and the migration functions indexed by version number. This is
621
+ * maintained for backward compatibility with older migration definitions.
622
+ * @public
623
+ */
186
624
  export declare interface LegacyBaseMigrationsInfo {
187
625
  firstVersion: number;
188
626
  currentVersion: number;
@@ -191,19 +629,45 @@ export declare interface LegacyBaseMigrationsInfo {
191
629
  };
192
630
  }
193
631
 
194
- /** @public */
632
+ /**
633
+ * Legacy migration interface for backward compatibility.
634
+ *
635
+ * This interface represents the old migration format that included both `up` and `down`
636
+ * transformation functions. While still supported, new code should use the `Migration`
637
+ * type which provides more flexibility and better integration with the current system.
638
+ * @public
639
+ */
195
640
  export declare interface LegacyMigration<Before = any, After = any> {
196
641
  up: (oldState: Before) => After;
197
642
  down: (newState: After) => Before;
198
643
  }
199
644
 
200
- /** @public */
645
+ /**
646
+ * Legacy migration configuration with support for sub-type migrations.
647
+ *
648
+ * This interface extends the base legacy migration info to support migrations that
649
+ * vary based on a sub-type key within records. This allows different migration paths
650
+ * for different variants of the same record type, which was useful in older migration
651
+ * systems but is now handled more elegantly by the current Migration system.
652
+ * @public
653
+ */
201
654
  export declare interface LegacyMigrations extends LegacyBaseMigrationsInfo {
202
655
  subTypeKey?: string;
203
656
  subTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>;
204
657
  }
205
658
 
206
- /** @public */
659
+ /**
660
+ * Defines a single migration that transforms data from one schema version to another.
661
+ *
662
+ * A migration can operate at two different scopes:
663
+ * - `record`: Transforms individual records, with optional filtering to target specific records
664
+ * - `store`: Transforms the entire serialized store structure
665
+ *
666
+ * Each migration has a unique ID and can declare dependencies on other migrations that must
667
+ * be applied first. The `up` function performs the forward transformation, while the optional
668
+ * `down` function can reverse the migration if needed.
669
+ * @public
670
+ */
207
671
  export declare type Migration = {
208
672
  readonly dependsOn?: readonly MigrationId[] | undefined;
209
673
  readonly id: MigrationId;
@@ -218,7 +682,15 @@ export declare type Migration = {
218
682
  readonly up: (oldState: UnknownRecord) => UnknownRecord | void;
219
683
  });
220
684
 
221
- /** @public */
685
+ /**
686
+ * Enumeration of possible reasons why a migration might fail.
687
+ *
688
+ * These reasons help identify what went wrong during migration processing,
689
+ * allowing applications to handle different failure scenarios appropriately.
690
+ * Common failures include incompatible data formats, unknown record types,
691
+ * and version mismatches between the data and available migrations.
692
+ * @public
693
+ */
222
694
  export declare enum MigrationFailureReason {
223
695
  IncompatibleSubtype = "incompatible-subtype",
224
696
  UnknownType = "unknown-type",
@@ -228,10 +700,24 @@ export declare enum MigrationFailureReason {
228
700
  UnrecognizedSubtype = "unrecognized-subtype"
229
701
  }
230
702
 
231
- /** @public */
703
+ /**
704
+ * Unique identifier for a migration in the format `sequenceId/version`.
705
+ *
706
+ * Migration IDs follow a specific pattern where the sequence ID identifies the migration
707
+ * sequence and the version number indicates the order within that sequence. For example:
708
+ * 'com.myapp.book/1', 'com.myapp.book/2', etc.
709
+ * @public
710
+ */
232
711
  export declare type MigrationId = `${string}/${number}`;
233
712
 
234
- /** @public */
713
+ /**
714
+ * Result type returned by migration operations.
715
+ *
716
+ * Migration operations can either succeed and return the transformed value,
717
+ * or fail with a specific reason. This discriminated union type allows for
718
+ * safe handling of both success and error cases when applying migrations.
719
+ * @public
720
+ */
235
721
  export declare type MigrationResult<T> = {
236
722
  reason: MigrationFailureReason;
237
723
  type: 'error';
@@ -240,7 +726,15 @@ export declare type MigrationResult<T> = {
240
726
  value: T;
241
727
  };
242
728
 
243
- /** @public */
729
+ /**
730
+ * A complete sequence of migrations that can be applied to transform data.
731
+ *
732
+ * A migration sequence represents a series of ordered migrations that belong together,
733
+ * typically for a specific part of your schema. The sequence includes metadata about
734
+ * whether it should be applied retroactively to existing data and contains the actual
735
+ * migration definitions in execution order.
736
+ * @public
737
+ */
244
738
  export declare interface MigrationSequence {
245
739
  sequenceId: string;
246
740
  /**
@@ -258,12 +752,47 @@ export declare interface MigrationSequence {
258
752
 
259
753
  /* Excluded from this release type: parseMigrationId */
260
754
 
261
- /** @public */
755
+ /**
756
+ * Query expression for filtering records by their property values. Maps record property names
757
+ * to matching criteria.
758
+ *
759
+ * @example
760
+ * ```ts
761
+ * // Query for books published after 2020 that are in stock
762
+ * const bookQuery: QueryExpression<Book> = {
763
+ * publishedYear: { gt: 2020 },
764
+ * inStock: { eq: true }
765
+ * }
766
+ *
767
+ * // Query for books not by a specific author
768
+ * const notByAuthor: QueryExpression<Book> = {
769
+ * authorId: { neq: 'author:tolkien' }
770
+ * }
771
+ * ```
772
+ *
773
+ * @public
774
+ */
262
775
  export declare type QueryExpression<R extends object> = {
263
776
  [k in keyof R & string]?: QueryValueMatcher<R[k]>;
264
777
  };
265
778
 
266
- /** @public */
779
+ /**
780
+ * Defines matching criteria for query values. Supports equality, inequality, and greater-than comparisons.
781
+ *
782
+ * @example
783
+ * ```ts
784
+ * // Exact match
785
+ * const exactMatch: QueryValueMatcher<string> = { eq: 'Science Fiction' }
786
+ *
787
+ * // Not equal to
788
+ * const notMatch: QueryValueMatcher<string> = { neq: 'Romance' }
789
+ *
790
+ * // Greater than (numeric values only)
791
+ * const greaterThan: QueryValueMatcher<number> = { gt: 2020 }
792
+ * ```
793
+ *
794
+ * @public
795
+ */
267
796
  export declare type QueryValueMatcher<T> = {
268
797
  eq: T;
269
798
  } | {
@@ -272,10 +801,40 @@ export declare type QueryValueMatcher<T> = {
272
801
  neq: T;
273
802
  };
274
803
 
275
- /** @public */
804
+ /**
805
+ * Extracts the record type from a record ID type.
806
+ *
807
+ * @example
808
+ * ```ts
809
+ * type BookId = RecordId<Book>
810
+ * type BookType = RecordFromId<BookId> // Book
811
+ * ```
812
+ *
813
+ * @public
814
+ */
276
815
  export declare type RecordFromId<K extends RecordId<UnknownRecord>> = K extends RecordId<infer R> ? R : never;
277
816
 
278
- /** @public */
817
+ /**
818
+ * A branded string type that represents a unique identifier for a record.
819
+ * The brand ensures type safety by preventing mixing of IDs between different record types.
820
+ *
821
+ * @example
822
+ * ```ts
823
+ * // Define a Book record
824
+ * interface Book extends BaseRecord<'book', RecordId<Book>> {
825
+ * title: string
826
+ * author: string
827
+ * }
828
+ *
829
+ * const bookId: RecordId<Book> = 'book:abc123' as RecordId<Book>
830
+ * const authorId: RecordId<Author> = 'author:xyz789' as RecordId<Author>
831
+ *
832
+ * // TypeScript prevents mixing different record ID types
833
+ * // bookId = authorId // Type error!
834
+ * ```
835
+ *
836
+ * @public
837
+ */
279
838
  export declare type RecordId<R extends UnknownRecord> = string & {
280
839
  __type__: R;
281
840
  };
@@ -292,13 +851,36 @@ export declare type RecordId<R extends UnknownRecord> = string & {
292
851
  export declare type RecordScope = 'document' | 'presence' | 'session';
293
852
 
294
853
  /**
295
- * A diff describing the changes to a record.
854
+ * A diff describing the changes to records, containing collections of records that were added,
855
+ * updated, or removed. This is the fundamental data structure used throughout the store system
856
+ * to track and communicate changes.
857
+ *
858
+ * @example
859
+ * ```ts
860
+ * const diff: RecordsDiff<Book> = {
861
+ * added: {
862
+ * 'book:1': { id: 'book:1', typeName: 'book', title: 'New Book' }
863
+ * },
864
+ * updated: {
865
+ * 'book:2': [
866
+ * { id: 'book:2', typeName: 'book', title: 'Old Title' }, // from
867
+ * { id: 'book:2', typeName: 'book', title: 'New Title' } // to
868
+ * ]
869
+ * },
870
+ * removed: {
871
+ * 'book:3': { id: 'book:3', typeName: 'book', title: 'Deleted Book' }
872
+ * }
873
+ * }
874
+ * ```
296
875
  *
297
876
  * @public
298
877
  */
299
878
  export declare interface RecordsDiff<R extends UnknownRecord> {
879
+ /** Records that were created, keyed by their ID */
300
880
  added: Record<IdOf<R>, R>;
881
+ /** Records that were modified, keyed by their ID. Each entry contains [from, to] tuple */
301
882
  updated: Record<IdOf<R>, [from: R, to: R]>;
883
+ /** Records that were deleted, keyed by their ID */
302
884
  removed: Record<IdOf<R>, R>;
303
885
  }
304
886
 
@@ -316,13 +898,45 @@ export declare class RecordType<R extends UnknownRecord, RequiredProperties exte
316
898
  * @readonly
317
899
  */
318
900
  readonly typeName: R['typeName'];
901
+ /**
902
+ * Factory function that creates default properties for new records.
903
+ * @public
904
+ */
319
905
  readonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>;
906
+ /**
907
+ * Validator function used to validate records of this type.
908
+ * @public
909
+ */
320
910
  readonly validator: StoreValidator<R>;
911
+ /**
912
+ * Optional configuration specifying which record properties are ephemeral.
913
+ * Ephemeral properties are not included in snapshots or synchronization.
914
+ * @public
915
+ */
321
916
  readonly ephemeralKeys?: {
322
917
  readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
323
918
  };
919
+ /**
920
+ * Set of property names that are marked as ephemeral for efficient lookup.
921
+ * @public
922
+ */
324
923
  readonly ephemeralKeySet: ReadonlySet<string>;
924
+ /**
925
+ * The scope that determines how records of this type are persisted and synchronized.
926
+ * @public
927
+ */
325
928
  readonly scope: RecordScope;
929
+ /**
930
+ * Creates a new RecordType instance.
931
+ *
932
+ * typeName - The unique type name for records created by this RecordType
933
+ * config - Configuration object for the RecordType
934
+ * - createDefaultProperties - Function that returns default properties for new records
935
+ * - validator - Optional validator function for record validation
936
+ * - scope - Optional scope determining persistence behavior (defaults to 'document')
937
+ * - ephemeralKeys - Optional mapping of property names to ephemeral status
938
+ * @public
939
+ */
326
940
  constructor(
327
941
  /**
328
942
  * The unique type associated with this record.
@@ -339,17 +953,40 @@ export declare class RecordType<R extends UnknownRecord, RequiredProperties exte
339
953
  readonly validator?: StoreValidator<R>;
340
954
  });
341
955
  /**
342
- * Create a new record of this type.
956
+ * Creates a new record of this type with the given properties.
957
+ *
958
+ * Properties are merged with default properties from the RecordType configuration.
959
+ * If no id is provided, a unique id will be generated automatically.
343
960
  *
344
- * @param properties - The properties of the record.
345
- * @returns The new record.
961
+ * @example
962
+ * ```ts
963
+ * const book = Book.create({
964
+ * title: 'The Great Gatsby',
965
+ * author: 'F. Scott Fitzgerald'
966
+ * })
967
+ * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }
968
+ * ```
969
+ *
970
+ * @param properties - The properties for the new record, including both required and optional fields
971
+ * @returns The newly created record with generated id and typeName
972
+ * @public
346
973
  */
347
974
  create(properties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>): R;
348
975
  /**
349
- * Clone a record of this type.
976
+ * Creates a deep copy of an existing record with a new unique id.
977
+ *
978
+ * This method performs a deep clone of all properties while generating a fresh id,
979
+ * making it useful for duplicating records without id conflicts.
980
+ *
981
+ * @example
982
+ * ```ts
983
+ * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })
984
+ * const duplicatedBook = Book.clone(originalBook)
985
+ * // duplicatedBook has same properties but different id
986
+ * ```
350
987
  *
351
- * @param record - The record to clone.
352
- * @returns The cloned record.
988
+ * @param record - The record to clone
989
+ * @returns A new record with the same properties but a different id
353
990
  * @public
354
991
  */
355
992
  clone(record: R): R;
@@ -367,36 +1004,58 @@ export declare class RecordType<R extends UnknownRecord, RequiredProperties exte
367
1004
  */
368
1005
  createId(customUniquePart?: string): IdOf<R>;
369
1006
  /**
370
- * Takes an id like `user:123` and returns the part after the colon `123`
1007
+ * Extracts the unique identifier part from a full record id.
371
1008
  *
372
- * @param id - The id
373
- * @returns
1009
+ * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.
1010
+ *
1011
+ * @example
1012
+ * ```ts
1013
+ * const bookId = Book.createId() // 'book:abc123'
1014
+ * const uniquePart = Book.parseId(bookId) // 'abc123'
1015
+ * ```
1016
+ *
1017
+ * @param id - The full record id to parse
1018
+ * @returns The unique identifier portion after the colon
1019
+ * @throws Error if the id is not valid for this record type
1020
+ * @public
374
1021
  */
375
1022
  parseId(id: IdOf<R>): string;
376
1023
  /**
377
- * Check whether a record is an instance of this record type.
1024
+ * Type guard that checks whether a record belongs to this RecordType.
378
1025
  *
379
- * @example
1026
+ * This method performs a runtime check by comparing the record's typeName
1027
+ * against this RecordType's typeName.
380
1028
  *
1029
+ * @example
381
1030
  * ```ts
382
- * const result = recordType.isInstance(someRecord)
1031
+ * if (Book.isInstance(someRecord)) {
1032
+ * // someRecord is now typed as a book record
1033
+ * console.log(someRecord.title)
1034
+ * }
383
1035
  * ```
384
1036
  *
385
- * @param record - The record to check.
386
- * @returns Whether the record is an instance of this record type.
1037
+ * @param record - The record to check, may be undefined
1038
+ * @returns True if the record is an instance of this record type
1039
+ * @public
387
1040
  */
388
1041
  isInstance(record?: UnknownRecord): record is R;
389
1042
  /**
390
- * Check whether an id is an id of this type.
1043
+ * Type guard that checks whether an id string belongs to this RecordType.
391
1044
  *
392
- * @example
1045
+ * Validates that the id starts with this RecordType's typeName followed by a colon.
1046
+ * This is more efficient than parsing the full id when you only need to verify the type.
393
1047
  *
1048
+ * @example
394
1049
  * ```ts
395
- * const result = recordType.isIn('someId')
1050
+ * if (Book.isId(someId)) {
1051
+ * // someId is now typed as IdOf<BookRecord>
1052
+ * const book = store.get(someId)
1053
+ * }
396
1054
  * ```
397
1055
  *
398
- * @param id - The id to check.
399
- * @returns Whether the id is an id of this type.
1056
+ * @param id - The id string to check, may be undefined
1057
+ * @returns True if the id belongs to this record type
1058
+ * @public
400
1059
  */
401
1060
  isId(id?: string): id is IdOf<R>;
402
1061
  /**
@@ -415,28 +1074,156 @@ export declare class RecordType<R extends UnknownRecord, RequiredProperties exte
415
1074
  */
416
1075
  withDefaultProperties<DefaultProps extends Omit<Partial<R>, 'id' | 'typeName'>>(createDefaultProperties: () => DefaultProps): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>;
417
1076
  /**
418
- * Check that the passed in record passes the validations for this type. Returns its input
419
- * correctly typed if it does, but throws an error otherwise.
1077
+ * Validates a record against this RecordType's validator and returns it with proper typing.
1078
+ *
1079
+ * This method runs the configured validator function and throws an error if validation fails.
1080
+ * If a previous version of the record is provided, it may use optimized validation.
1081
+ *
1082
+ * @example
1083
+ * ```ts
1084
+ * try {
1085
+ * const validBook = Book.validate(untrustedData)
1086
+ * // validBook is now properly typed and validated
1087
+ * } catch (error) {
1088
+ * console.log('Validation failed:', error.message)
1089
+ * }
1090
+ * ```
1091
+ *
1092
+ * @param record - The unknown record data to validate
1093
+ * @param recordBefore - Optional previous version for optimized validation
1094
+ * @returns The validated and properly typed record
1095
+ * @throws Error if validation fails
1096
+ * @public
420
1097
  */
421
1098
  validate(record: unknown, recordBefore?: R): R;
422
1099
  }
423
1100
 
424
- /** @public */
1101
+ /**
1102
+ * Creates the inverse of a RecordsDiff, effectively reversing all changes.
1103
+ * Added records become removed, removed records become added, and updated records
1104
+ * have their from/to values swapped. This is useful for implementing undo operations.
1105
+ *
1106
+ * @param diff - The diff to reverse
1107
+ * @returns A new RecordsDiff that represents the inverse of the input diff
1108
+ * @example
1109
+ * ```ts
1110
+ * const originalDiff: RecordsDiff<Book> = {
1111
+ * added: { 'book:1': newBook },
1112
+ * updated: { 'book:2': [oldBook, updatedBook] },
1113
+ * removed: { 'book:3': deletedBook }
1114
+ * }
1115
+ *
1116
+ * const reversedDiff = reverseRecordsDiff(originalDiff)
1117
+ * // Result: {
1118
+ * // added: { 'book:3': deletedBook },
1119
+ * // updated: { 'book:2': [updatedBook, oldBook] },
1120
+ * // removed: { 'book:1': newBook }
1121
+ * // }
1122
+ * ```
1123
+ *
1124
+ * @public
1125
+ */
425
1126
  export declare function reverseRecordsDiff(diff: RecordsDiff<any>): RecordsDiff<any>;
426
1127
 
427
- /** @public */
1128
+ /**
1129
+ * A reactive computed index that provides efficient lookups of records by property values.
1130
+ * Returns a computed value containing an RSIndexMap with diffs for change tracking.
1131
+ *
1132
+ * @example
1133
+ * ```ts
1134
+ * // Create an index on book authors
1135
+ * const authorIndex: RSIndex<Book, 'authorId'> = store.query.index('book', 'authorId')
1136
+ *
1137
+ * // Get all books by a specific author
1138
+ * const leguinBooks = authorIndex.get().get('author:leguin')
1139
+ * ```
1140
+ *
1141
+ * @public
1142
+ */
428
1143
  export declare type RSIndex<R extends UnknownRecord, Property extends string & keyof R = string & keyof R> = Computed<RSIndexMap<R, Property>, RSIndexDiff<R, Property>>;
429
1144
 
430
- /** @public */
1145
+ /**
1146
+ * A type representing the diff of changes to a reactive store index.
1147
+ * Maps property values to the collection differences for record IDs that have that property value.
1148
+ *
1149
+ * @example
1150
+ * ```ts
1151
+ * // For an index on book titles, the diff might look like:
1152
+ * const titleIndexDiff: RSIndexDiff<Book, 'title'> = new Map([
1153
+ * ['The Lathe of Heaven', { added: new Set(['book:1']), removed: new Set() }],
1154
+ * ['Animal Farm', { added: new Set(), removed: new Set(['book:2']) }]
1155
+ * ])
1156
+ * ```
1157
+ *
1158
+ * @public
1159
+ */
431
1160
  export declare type RSIndexDiff<R extends UnknownRecord, Property extends string & keyof R = string & keyof R> = Map<R[Property], CollectionDiff<IdOf<R>>>;
432
1161
 
433
- /** @public */
1162
+ /**
1163
+ * A type representing a reactive store index as a map from property values to sets of record IDs.
1164
+ * This is used to efficiently look up records by a specific property value.
1165
+ *
1166
+ * @example
1167
+ * ```ts
1168
+ * // Index mapping book titles to the IDs of books with that title
1169
+ * const titleIndex: RSIndexMap<Book, 'title'> = new Map([
1170
+ * ['The Lathe of Heaven', new Set(['book:1'])],
1171
+ * ['Animal Farm', new Set(['book:2', 'book:3'])]
1172
+ * ])
1173
+ * ```
1174
+ *
1175
+ * @public
1176
+ */
434
1177
  export declare type RSIndexMap<R extends UnknownRecord, Property extends string & keyof R = string & keyof R> = Map<R[Property], Set<IdOf<R>>>;
435
1178
 
436
- /** @public */
1179
+ /**
1180
+ * Union type representing all supported serialized schema formats.
1181
+ *
1182
+ * This type allows the store to handle both legacy (V1) and current (V2)
1183
+ * schema formats during deserialization and migration.
1184
+ *
1185
+ * @example
1186
+ * ```ts
1187
+ * function handleSchema(schema: SerializedSchema) {
1188
+ * if (schema.schemaVersion === 1) {
1189
+ * // Handle V1 format
1190
+ * console.log('Store version:', schema.storeVersion)
1191
+ * } else {
1192
+ * // Handle V2 format
1193
+ * console.log('Sequences:', schema.sequences)
1194
+ * }
1195
+ * }
1196
+ * ```
1197
+ *
1198
+ * @public
1199
+ */
437
1200
  export declare type SerializedSchema = SerializedSchemaV1 | SerializedSchemaV2;
438
1201
 
439
- /** @public */
1202
+ /**
1203
+ * Version 1 format for serialized store schema information.
1204
+ *
1205
+ * This is the legacy format used before schema version 2. Version 1 schemas
1206
+ * separate store-level versioning from record-level versioning, and support
1207
+ * subtypes for complex record types like shapes.
1208
+ *
1209
+ * @example
1210
+ * ```ts
1211
+ * const schemaV1: SerializedSchemaV1 = {
1212
+ * schemaVersion: 1,
1213
+ * storeVersion: 2,
1214
+ * recordVersions: {
1215
+ * book: { version: 3 },
1216
+ * shape: {
1217
+ * version: 2,
1218
+ * subTypeVersions: { rectangle: 1, circle: 2 },
1219
+ * subTypeKey: 'type'
1220
+ * }
1221
+ * }
1222
+ * }
1223
+ * ```
1224
+ *
1225
+ * @public
1226
+ */
440
1227
  export declare interface SerializedSchemaV1 {
441
1228
  /** Schema version is the version for this type you're looking at right now */
442
1229
  schemaVersion: 1;
@@ -455,7 +1242,28 @@ export declare interface SerializedSchemaV1 {
455
1242
  }>;
456
1243
  }
457
1244
 
458
- /** @public */
1245
+ /**
1246
+ * Version 2 format for serialized store schema information.
1247
+ *
1248
+ * This is the current format that uses a unified sequence-based approach
1249
+ * for tracking versions across all migration sequences. Each sequence ID
1250
+ * maps to the latest version number for that sequence.
1251
+ *
1252
+ * @example
1253
+ * ```ts
1254
+ * const schemaV2: SerializedSchemaV2 = {
1255
+ * schemaVersion: 2,
1256
+ * sequences: {
1257
+ * 'com.tldraw.store': 3,
1258
+ * 'com.tldraw.book': 2,
1259
+ * 'com.tldraw.shape': 4,
1260
+ * 'com.tldraw.shape.rectangle': 1
1261
+ * }
1262
+ * }
1263
+ * ```
1264
+ *
1265
+ * @public
1266
+ */
459
1267
  export declare interface SerializedSchemaV2 {
460
1268
  schemaVersion: 2;
461
1269
  sequences: {
@@ -465,17 +1273,53 @@ export declare interface SerializedSchemaV2 {
465
1273
 
466
1274
  /**
467
1275
  * A serialized snapshot of the record store's values.
1276
+ * This is a plain JavaScript object that can be saved to storage or transmitted over the network.
1277
+ *
1278
+ * @example
1279
+ * ```ts
1280
+ * const serialized: SerializedStore<Book> = {
1281
+ * 'book:123': { id: 'book:123', typeName: 'book', title: 'The Lathe of Heaven' },
1282
+ * 'book:456': { id: 'book:456', typeName: 'book', title: 'The Left Hand of Darkness' }
1283
+ * }
1284
+ * ```
468
1285
  *
469
1286
  * @public
470
1287
  */
471
1288
  export declare type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>;
472
1289
 
473
1290
  /**
474
- * Squash a collection of diffs into a single diff.
1291
+ * Combines multiple RecordsDiff objects into a single consolidated diff.
1292
+ * This function intelligently merges changes, handling cases where the same record
1293
+ * is modified multiple times across different diffs. For example, if a record is
1294
+ * added in one diff and then updated in another, the result will show it as added
1295
+ * with the final state.
1296
+ *
1297
+ * @param diffs - An array of diffs to combine into a single diff
1298
+ * @param options - Configuration options for the squashing operation
1299
+ * - mutateFirstDiff - If true, modifies the first diff in place instead of creating a new one
1300
+ * @returns A single diff that represents the cumulative effect of all input diffs
1301
+ * @example
1302
+ * ```ts
1303
+ * const diff1: RecordsDiff<Book> = {
1304
+ * added: { 'book:1': { id: 'book:1', title: 'New Book' } },
1305
+ * updated: {},
1306
+ * removed: {}
1307
+ * }
1308
+ *
1309
+ * const diff2: RecordsDiff<Book> = {
1310
+ * added: {},
1311
+ * updated: { 'book:1': [{ id: 'book:1', title: 'New Book' }, { id: 'book:1', title: 'Updated Title' }] },
1312
+ * removed: {}
1313
+ * }
1314
+ *
1315
+ * const squashed = squashRecordDiffs([diff1, diff2])
1316
+ * // Result: {
1317
+ * // added: { 'book:1': { id: 'book:1', title: 'Updated Title' } },
1318
+ * // updated: {},
1319
+ * // removed: {}
1320
+ * // }
1321
+ * ```
475
1322
  *
476
- * @param diffs - An array of diffs to squash.
477
- * @param options - An optional object with a `mutateFirstDiff` property. If `mutateFirstDiff` is true, the first diff in the array will be mutated in-place.
478
- * @returns A single diff that represents the squashed diffs.
479
1323
  * @public
480
1324
  */
481
1325
  export declare function squashRecordDiffs<T extends UnknownRecord>(diffs: RecordsDiff<T>[], options?: {
@@ -484,19 +1328,59 @@ export declare function squashRecordDiffs<T extends UnknownRecord>(diffs: Record
484
1328
 
485
1329
  /* Excluded from this release type: squashRecordDiffsMutable */
486
1330
 
487
- /** @public */
1331
+ /**
1332
+ * Declares dependencies for migrations without being a migration itself.
1333
+ *
1334
+ * This interface allows you to specify that future migrations in a sequence depend on
1335
+ * migrations from other sequences, without defining an actual migration transformation.
1336
+ * It's used to establish cross-sequence dependencies in the migration graph.
1337
+ * @public
1338
+ */
488
1339
  export declare interface StandaloneDependsOn {
489
1340
  readonly dependsOn: readonly MigrationId[];
490
1341
  }
491
1342
 
492
1343
  /**
493
- * A store of records.
1344
+ * A reactive store that manages collections of typed records.
1345
+ *
1346
+ * The Store is the central container for your application's data, providing:
1347
+ * - Reactive state management with automatic updates
1348
+ * - Type-safe record operations
1349
+ * - History tracking and change notifications
1350
+ * - Schema validation and migrations
1351
+ * - Side effects and business logic hooks
1352
+ * - Efficient querying and indexing
1353
+ *
1354
+ * @example
1355
+ * ```ts
1356
+ * // Create a store with schema
1357
+ * const schema = StoreSchema.create({
1358
+ * book: Book,
1359
+ * author: Author
1360
+ * })
1361
+ *
1362
+ * const store = new Store({
1363
+ * schema,
1364
+ * props: {}
1365
+ * })
1366
+ *
1367
+ * // Add records
1368
+ * const book = Book.create({ title: 'The Lathe of Heaven', author: 'Le Guin' })
1369
+ * store.put([book])
1370
+ *
1371
+ * // Listen to changes
1372
+ * store.listen((entry) => {
1373
+ * console.log('Changes:', entry.changes)
1374
+ * })
1375
+ * ```
494
1376
  *
495
1377
  * @public
496
1378
  */
497
1379
  export declare class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
498
1380
  /**
499
- * The random id of the store.
1381
+ * The unique identifier of the store instance.
1382
+ *
1383
+ * @public
500
1384
  */
501
1385
  readonly id: string;
502
1386
  /* Excluded from this release type: records */
@@ -508,7 +1392,19 @@ export declare class Store<R extends UnknownRecord = UnknownRecord, Props = unkn
508
1392
  */
509
1393
  readonly history: Atom<number, RecordsDiff<R>>;
510
1394
  /**
511
- * A StoreQueries instance for this store.
1395
+ * Reactive queries and indexes for efficiently accessing store data.
1396
+ * Provides methods for filtering, indexing, and subscribing to subsets of records.
1397
+ *
1398
+ * @example
1399
+ * ```ts
1400
+ * // Create an index by a property
1401
+ * const booksByAuthor = store.query.index('book', 'author')
1402
+ *
1403
+ * // Get records matching criteria
1404
+ * const inStockBooks = store.query.records('book', () => ({
1405
+ * inStock: { eq: true }
1406
+ * }))
1407
+ * ```
512
1408
  *
513
1409
  * @public
514
1410
  * @readonly
@@ -518,22 +1414,64 @@ export declare class Store<R extends UnknownRecord = UnknownRecord, Props = unkn
518
1414
  /* Excluded from this release type: historyAccumulator */
519
1415
  /* Excluded from this release type: historyReactor */
520
1416
  /* Excluded from this release type: cancelHistoryReactor */
1417
+ /**
1418
+ * The schema that defines the structure and validation rules for records in this store.
1419
+ *
1420
+ * @public
1421
+ */
521
1422
  readonly schema: StoreSchema<R, Props>;
1423
+ /**
1424
+ * Custom properties associated with this store instance.
1425
+ *
1426
+ * @public
1427
+ */
522
1428
  readonly props: Props;
1429
+ /**
1430
+ * A mapping of record scopes to the set of record type names that belong to each scope.
1431
+ * Used to filter records by their persistence and synchronization behavior.
1432
+ *
1433
+ * @public
1434
+ */
523
1435
  readonly scopedTypes: {
524
1436
  readonly [K in RecordScope]: ReadonlySet<R['typeName']>;
525
1437
  };
1438
+ /**
1439
+ * Side effects manager that handles lifecycle events for record operations.
1440
+ * Allows registration of callbacks for create, update, delete, and validation events.
1441
+ *
1442
+ * @example
1443
+ * ```ts
1444
+ * store.sideEffects.registerAfterCreateHandler('book', (book) => {
1445
+ * console.log('Book created:', book.title)
1446
+ * })
1447
+ * ```
1448
+ *
1449
+ * @public
1450
+ */
526
1451
  readonly sideEffects: StoreSideEffects<R>;
1452
+ /**
1453
+ * Creates a new Store instance.
1454
+ *
1455
+ * @example
1456
+ * ```ts
1457
+ * const store = new Store({
1458
+ * schema: StoreSchema.create({ book: Book }),
1459
+ * props: { appName: 'MyLibrary' },
1460
+ * initialData: savedData
1461
+ * })
1462
+ * ```
1463
+ *
1464
+ * @param config - Configuration object for the store
1465
+ */
527
1466
  constructor(config: {
528
- /**
529
- * A map of validators for each record type. A record's validator will be called when the record
530
- * is created or updated. It should throw an error if the record is invalid.
531
- */
1467
+ /** Custom properties for the store instance */
1468
+ props: Props;
1469
+ /** Optional unique identifier for the store */
1470
+ id?: string;
1471
+ /** The schema defining record types, validation, and migrations */
532
1472
  schema: StoreSchema<R, Props>;
533
- /** The store's initial data. */
1473
+ /** The store's initial data to populate on creation */
534
1474
  initialData?: SerializedStore<R>;
535
- id?: string;
536
- props: Props;
537
1475
  });
538
1476
  _flushHistory(): void;
539
1477
  dispose(): void;
@@ -556,119 +1494,238 @@ export declare class Store<R extends UnknownRecord = UnknownRecord, Props = unkn
556
1494
  private updateHistory;
557
1495
  validate(phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord'): void;
558
1496
  /**
559
- * Add some records to the store. It's an error if they already exist.
1497
+ * Add or update records in the store. If a record with the same ID already exists, it will be updated.
1498
+ * Otherwise, a new record will be created.
1499
+ *
1500
+ * @example
1501
+ * ```ts
1502
+ * // Add new records
1503
+ * const book = Book.create({ title: 'Lathe Of Heaven', author: 'Le Guin' })
1504
+ * store.put([book])
560
1505
  *
561
- * @param records - The records to add.
562
- * @param phaseOverride - The phase override.
1506
+ * // Update existing record
1507
+ * store.put([{ ...book, title: 'The Lathe of Heaven' }])
1508
+ * ```
1509
+ *
1510
+ * @param records - The records to add or update
1511
+ * @param phaseOverride - Override the validation phase (used internally)
563
1512
  * @public
564
1513
  */
565
1514
  put(records: R[], phaseOverride?: 'initialize'): void;
566
1515
  /**
567
- * Remove some records from the store via their ids.
1516
+ * Remove records from the store by their IDs.
1517
+ *
1518
+ * @example
1519
+ * ```ts
1520
+ * // Remove a single record
1521
+ * store.remove([book.id])
1522
+ *
1523
+ * // Remove multiple records
1524
+ * store.remove([book1.id, book2.id, book3.id])
1525
+ * ```
568
1526
  *
569
- * @param ids - The ids of the records to remove.
1527
+ * @param ids - The IDs of the records to remove
570
1528
  * @public
571
1529
  */
572
1530
  remove(ids: IdOf<R>[]): void;
573
1531
  /**
574
- * Get the value of a store record by its id.
1532
+ * Get a record by its ID. This creates a reactive subscription to the record.
1533
+ *
1534
+ * @example
1535
+ * ```ts
1536
+ * const book = store.get(bookId)
1537
+ * if (book) {
1538
+ * console.log(book.title)
1539
+ * }
1540
+ * ```
575
1541
  *
576
- * @param id - The id of the record to get.
1542
+ * @param id - The ID of the record to get
1543
+ * @returns The record if it exists, undefined otherwise
577
1544
  * @public
578
1545
  */
579
1546
  get<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined;
580
1547
  /**
581
- * Get the value of a store record by its id without updating its epoch.
1548
+ * Get a record by its ID without creating a reactive subscription.
1549
+ * Use this when you need to access a record but don't want reactive updates.
1550
+ *
1551
+ * @example
1552
+ * ```ts
1553
+ * // Won't trigger reactive updates when this record changes
1554
+ * const book = store.unsafeGetWithoutCapture(bookId)
1555
+ * ```
582
1556
  *
583
- * @param id - The id of the record to get.
1557
+ * @param id - The ID of the record to get
1558
+ * @returns The record if it exists, undefined otherwise
584
1559
  * @public
585
1560
  */
586
1561
  unsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined;
587
1562
  /**
588
- * Creates a JSON payload from the record store.
1563
+ * Serialize the store's records to a plain JavaScript object.
1564
+ * Only includes records matching the specified scope.
1565
+ *
1566
+ * @example
1567
+ * ```ts
1568
+ * // Serialize only document records (default)
1569
+ * const documentData = store.serialize('document')
1570
+ *
1571
+ * // Serialize all records
1572
+ * const allData = store.serialize('all')
1573
+ * ```
589
1574
  *
590
- * @param scope - The scope of records to serialize. Defaults to 'document'.
591
- * @returns The record store snapshot as a JSON payload.
1575
+ * @param scope - The scope of records to serialize. Defaults to 'document'
1576
+ * @returns The serialized store data
1577
+ * @public
592
1578
  */
593
1579
  serialize(scope?: 'all' | RecordScope): SerializedStore<R>;
594
1580
  /**
595
1581
  * Get a serialized snapshot of the store and its schema.
1582
+ * This includes both the data and schema information needed for proper migration.
596
1583
  *
1584
+ * @example
597
1585
  * ```ts
598
1586
  * const snapshot = store.getStoreSnapshot()
599
- * store.loadStoreSnapshot(snapshot)
600
- * ```
1587
+ * localStorage.setItem('myApp', JSON.stringify(snapshot))
601
1588
  *
602
- * @param scope - The scope of records to serialize. Defaults to 'document'.
1589
+ * // Later...
1590
+ * const saved = JSON.parse(localStorage.getItem('myApp'))
1591
+ * store.loadStoreSnapshot(saved)
1592
+ * ```
603
1593
  *
1594
+ * @param scope - The scope of records to serialize. Defaults to 'document'
1595
+ * @returns A snapshot containing both store data and schema information
604
1596
  * @public
605
1597
  */
606
1598
  getStoreSnapshot(scope?: 'all' | RecordScope): StoreSnapshot<R>;
607
1599
  /**
608
- * Migrate a serialized snapshot of the store and its schema.
1600
+ * Migrate a serialized snapshot to the current schema version.
1601
+ * This applies any necessary migrations to bring old data up to date.
609
1602
  *
1603
+ * @example
610
1604
  * ```ts
611
- * const snapshot = store.getStoreSnapshot()
612
- * store.migrateSnapshot(snapshot)
1605
+ * const oldSnapshot = JSON.parse(localStorage.getItem('myApp'))
1606
+ * const migratedSnapshot = store.migrateSnapshot(oldSnapshot)
613
1607
  * ```
614
1608
  *
615
- * @param snapshot - The snapshot to load.
1609
+ * @param snapshot - The snapshot to migrate
1610
+ * @returns The migrated snapshot with current schema version
1611
+ * @throws Error if migration fails
616
1612
  * @public
617
1613
  */
618
1614
  migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R>;
619
1615
  /**
620
- * Load a serialized snapshot.
1616
+ * Load a serialized snapshot into the store, replacing all current data.
1617
+ * The snapshot will be automatically migrated to the current schema version if needed.
621
1618
  *
1619
+ * @example
622
1620
  * ```ts
623
- * const snapshot = store.getStoreSnapshot()
1621
+ * const snapshot = JSON.parse(localStorage.getItem('myApp'))
624
1622
  * store.loadStoreSnapshot(snapshot)
625
1623
  * ```
626
1624
  *
627
- * @param snapshot - The snapshot to load.
1625
+ * @param snapshot - The snapshot to load
1626
+ * @throws Error if migration fails or snapshot is invalid
628
1627
  * @public
629
1628
  */
630
1629
  loadStoreSnapshot(snapshot: StoreSnapshot<R>): void;
631
1630
  /**
632
- * Get an array of all values in the store.
1631
+ * Get an array of all records in the store.
1632
+ *
1633
+ * @example
1634
+ * ```ts
1635
+ * const allRecords = store.allRecords()
1636
+ * const books = allRecords.filter(r => r.typeName === 'book')
1637
+ * ```
633
1638
  *
634
- * @returns An array of all values in the store.
1639
+ * @returns An array containing all records in the store
635
1640
  * @public
636
1641
  */
637
1642
  allRecords(): R[];
638
1643
  /**
639
- * Removes all records from the store.
1644
+ * Remove all records from the store.
1645
+ *
1646
+ * @example
1647
+ * ```ts
1648
+ * store.clear()
1649
+ * console.log(store.allRecords().length) // 0
1650
+ * ```
640
1651
  *
641
1652
  * @public
642
1653
  */
643
1654
  clear(): void;
644
1655
  /**
645
- * Update a record. To update multiple records at once, use the `update` method of the
646
- * `TypedStore` class.
1656
+ * Update a single record using an updater function. To update multiple records at once,
1657
+ * use the `update` method of the `TypedStore` class.
1658
+ *
1659
+ * @example
1660
+ * ```ts
1661
+ * store.update(book.id, (book) => ({
1662
+ * ...book,
1663
+ * title: 'Updated Title'
1664
+ * }))
1665
+ * ```
647
1666
  *
648
- * @param id - The id of the record to update.
649
- * @param updater - A function that updates the record.
1667
+ * @param id - The ID of the record to update
1668
+ * @param updater - A function that receives the current record and returns the updated record
1669
+ * @public
650
1670
  */
651
1671
  update<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>): void;
652
1672
  /**
653
- * Get whether the record store has a id.
1673
+ * Check whether a record with the given ID exists in the store.
1674
+ *
1675
+ * @example
1676
+ * ```ts
1677
+ * if (store.has(bookId)) {
1678
+ * console.log('Book exists!')
1679
+ * }
1680
+ * ```
654
1681
  *
655
- * @param id - The id of the record to check.
1682
+ * @param id - The ID of the record to check
1683
+ * @returns True if the record exists, false otherwise
656
1684
  * @public
657
1685
  */
658
1686
  has<K extends IdOf<R>>(id: K): boolean;
659
1687
  /**
660
- * Add a new listener to the store.
1688
+ * Add a listener that will be called when the store changes.
1689
+ * Returns a function to remove the listener.
1690
+ *
1691
+ * @example
1692
+ * ```ts
1693
+ * const removeListener = store.listen((entry) => {
1694
+ * console.log('Changes:', entry.changes)
1695
+ * console.log('Source:', entry.source)
1696
+ * })
1697
+ *
1698
+ * // Listen only to user changes to document records
1699
+ * const removeDocumentListener = store.listen(
1700
+ * (entry) => console.log('Document changed:', entry),
1701
+ * { source: 'user', scope: 'document' }
1702
+ * )
1703
+ *
1704
+ * // Later, remove the listener
1705
+ * removeListener()
1706
+ * ```
661
1707
  *
662
- * @param onHistory - The listener to call when the store updates.
663
- * @param filters - Filters to apply to the listener.
664
- * @returns A function to remove the listener.
1708
+ * @param onHistory - The listener function to call when changes occur
1709
+ * @param filters - Optional filters to control when the listener is called
1710
+ * @returns A function that removes the listener when called
1711
+ * @public
665
1712
  */
666
1713
  listen(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>): () => void;
667
1714
  private isMergingRemoteChanges;
668
1715
  /**
669
- * Merge changes from a remote source
1716
+ * Merge changes from a remote source. Changes made within the provided function
1717
+ * will be marked with source 'remote' instead of 'user'.
1718
+ *
1719
+ * @example
1720
+ * ```ts
1721
+ * // Changes from sync/collaboration
1722
+ * store.mergeRemoteChanges(() => {
1723
+ * store.put(remoteRecords)
1724
+ * store.remove(deletedIds)
1725
+ * })
1726
+ * ```
670
1727
  *
671
- * @param fn - A function that merges the external changes.
1728
+ * @param fn - A function that applies the remote changes
672
1729
  * @public
673
1730
  */
674
1731
  mergeRemoteChanges(fn: () => void): void;
@@ -711,85 +1768,337 @@ export declare class Store<R extends UnknownRecord = UnknownRecord, Props = unkn
711
1768
  /* Excluded from this release type: addHistoryInterceptor */
712
1769
  }
713
1770
 
714
- /** @public */
1771
+ /**
1772
+ * Handler function called after a record has been successfully updated in the store.
1773
+ * Use this for side effects that should happen after record changes, such as
1774
+ * updating related records or maintaining consistency constraints.
1775
+ *
1776
+ * @param prev - The previous version of the record
1777
+ * @param next - The new version of the record that was stored
1778
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1779
+ *
1780
+ * @example
1781
+ * ```ts
1782
+ * const handler: StoreAfterChangeHandler<ShapeRecord> = (prev, next, source) => {
1783
+ * // Update connected arrows when a shape moves
1784
+ * if (prev.x !== next.x || prev.y !== next.y) {
1785
+ * updateConnectedArrows(next.id)
1786
+ * }
1787
+ * }
1788
+ * ```
1789
+ *
1790
+ * @public
1791
+ */
715
1792
  export declare type StoreAfterChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => void;
716
1793
 
717
- /** @public */
1794
+ /**
1795
+ * Handler function called after a record has been successfully created in the store.
1796
+ * Use this for side effects that should happen after record creation, such as updating
1797
+ * related records or triggering notifications.
1798
+ *
1799
+ * @param record - The record that was created
1800
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1801
+ *
1802
+ * @example
1803
+ * ```ts
1804
+ * const handler: StoreAfterCreateHandler<BookRecord> = (book, source) => {
1805
+ * if (source === 'user') {
1806
+ * console.log(`New book added: ${book.title}`)
1807
+ * updateAuthorBookCount(book.authorId)
1808
+ * }
1809
+ * }
1810
+ * ```
1811
+ *
1812
+ * @public
1813
+ */
718
1814
  export declare type StoreAfterCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void;
719
1815
 
720
- /** @public */
1816
+ /**
1817
+ * Handler function called after a record has been successfully deleted from the store.
1818
+ * Use this for cleanup operations and maintaining referential integrity.
1819
+ *
1820
+ * @param record - The record that was deleted
1821
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1822
+ *
1823
+ * @example
1824
+ * ```ts
1825
+ * const handler: StoreAfterDeleteHandler<ShapeRecord> = (shape, source) => {
1826
+ * // Clean up arrows that were connected to this shape
1827
+ * const connectedArrows = findArrowsConnectedTo(shape.id)
1828
+ * store.remove(connectedArrows.map(arrow => arrow.id))
1829
+ * }
1830
+ * ```
1831
+ *
1832
+ * @public
1833
+ */
721
1834
  export declare type StoreAfterDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void;
722
1835
 
723
- /** @public */
1836
+ /**
1837
+ * Handler function called before a record is updated in the store.
1838
+ * The handler receives the current and new versions of the record and can return
1839
+ * a modified version or the original to prevent the change.
1840
+ *
1841
+ * @param prev - The current version of the record in the store
1842
+ * @param next - The proposed new version of the record
1843
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1844
+ * @returns The record version to actually store (may be modified or the original to block change)
1845
+ *
1846
+ * @example
1847
+ * ```ts
1848
+ * const handler: StoreBeforeChangeHandler<ShapeRecord> = (prev, next, source) => {
1849
+ * // Prevent shapes from being moved outside the canvas bounds
1850
+ * if (next.x < 0 || next.y < 0) {
1851
+ * return prev // Block the change
1852
+ * }
1853
+ * return next
1854
+ * }
1855
+ * ```
1856
+ *
1857
+ * @public
1858
+ */
724
1859
  export declare type StoreBeforeChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => R;
725
1860
 
726
- /** @public */
1861
+ /**
1862
+ * Handler function called before a record is created in the store.
1863
+ * The handler receives the record to be created and can return a modified version.
1864
+ * Use this to validate, transform, or modify records before they are added to the store.
1865
+ *
1866
+ * @param record - The record about to be created
1867
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1868
+ * @returns The record to actually create (may be modified)
1869
+ *
1870
+ * @example
1871
+ * ```ts
1872
+ * const handler: StoreBeforeCreateHandler<MyRecord> = (record, source) => {
1873
+ * // Ensure all user-created records have a timestamp
1874
+ * if (source === 'user' && !record.createdAt) {
1875
+ * return { ...record, createdAt: Date.now() }
1876
+ * }
1877
+ * return record
1878
+ * }
1879
+ * ```
1880
+ *
1881
+ * @public
1882
+ */
727
1883
  export declare type StoreBeforeCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => R;
728
1884
 
729
- /** @public */
1885
+ /**
1886
+ * Handler function called before a record is deleted from the store.
1887
+ * The handler can return `false` to prevent the deletion from occurring.
1888
+ *
1889
+ * @param record - The record about to be deleted
1890
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1891
+ * @returns `false` to prevent deletion, `void` or any other value to allow it
1892
+ *
1893
+ * @example
1894
+ * ```ts
1895
+ * const handler: StoreBeforeDeleteHandler<BookRecord> = (book, source) => {
1896
+ * // Prevent deletion of books that are currently checked out
1897
+ * if (book.isCheckedOut) {
1898
+ * console.warn('Cannot delete checked out book')
1899
+ * return false
1900
+ * }
1901
+ * // Allow deletion for other books
1902
+ * }
1903
+ * ```
1904
+ *
1905
+ * @public
1906
+ */
730
1907
  export declare type StoreBeforeDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => false | void;
731
1908
 
732
- /** @public */
1909
+ /**
1910
+ * Information about an error that occurred in the store.
1911
+ *
1912
+ * @example
1913
+ * ```ts
1914
+ * const error: StoreError = {
1915
+ * error: new Error('Validation failed'),
1916
+ * phase: 'updateRecord',
1917
+ * recordBefore: oldRecord,
1918
+ * recordAfter: newRecord,
1919
+ * isExistingValidationIssue: false
1920
+ * }
1921
+ * ```
1922
+ *
1923
+ * @public
1924
+ */
733
1925
  export declare interface StoreError {
1926
+ /** The error that occurred */
734
1927
  error: Error;
1928
+ /** The phase during which the error occurred */
735
1929
  phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord';
1930
+ /** The record state before the operation (if applicable) */
736
1931
  recordBefore?: unknown;
1932
+ /** The record state after the operation */
737
1933
  recordAfter: unknown;
1934
+ /** Whether this is an existing validation issue */
738
1935
  isExistingValidationIssue: boolean;
739
1936
  }
740
1937
 
741
1938
  /**
742
1939
  * A function that will be called when the history changes.
743
1940
  *
1941
+ * @example
1942
+ * ```ts
1943
+ * const listener: StoreListener<Book> = (entry) => {
1944
+ * console.log('Changes:', entry.changes)
1945
+ * console.log('Source:', entry.source)
1946
+ * }
1947
+ *
1948
+ * store.listen(listener)
1949
+ * ```
1950
+ *
1951
+ * @param entry - The history entry containing the changes
1952
+ *
744
1953
  * @public
745
1954
  */
746
1955
  export declare type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void;
747
1956
 
748
- /** @public */
1957
+ /**
1958
+ * Filters for store listeners to control which changes trigger the listener.
1959
+ *
1960
+ * @example
1961
+ * ```ts
1962
+ * const filters: StoreListenerFilters = {
1963
+ * source: 'user', // Only listen to user changes
1964
+ * scope: 'document' // Only listen to document-scoped records
1965
+ * }
1966
+ * ```
1967
+ *
1968
+ * @public
1969
+ */
749
1970
  export declare interface StoreListenerFilters {
1971
+ /** Filter by the source of changes */
750
1972
  source: 'all' | ChangeSource;
1973
+ /** Filter by the scope of records */
751
1974
  scope: 'all' | RecordScope;
752
1975
  }
753
1976
 
754
- /** @public */
1977
+ /**
1978
+ * A store or an object containing a store.
1979
+ * This type is used for APIs that can accept either a store directly or an object with a store property.
1980
+ *
1981
+ * @example
1982
+ * ```ts
1983
+ * function useStore(storeOrObject: StoreObject<MyRecord>) {
1984
+ * const store = storeOrObject instanceof Store ? storeOrObject : storeOrObject.store
1985
+ * return store
1986
+ * }
1987
+ * ```
1988
+ *
1989
+ * @public
1990
+ */
755
1991
  export declare type StoreObject<R extends UnknownRecord> = {
756
1992
  store: Store<R>;
757
1993
  } | Store<R>;
758
1994
 
759
- /** @public */
1995
+ /**
1996
+ * Extract the record type from a StoreObject.
1997
+ *
1998
+ * @example
1999
+ * ```ts
2000
+ * type MyStoreObject = { store: Store<Book | Author> }
2001
+ * type Records = StoreObjectRecordType<MyStoreObject> // Book | Author
2002
+ * ```
2003
+ *
2004
+ * @public
2005
+ */
760
2006
  export declare type StoreObjectRecordType<Context extends StoreObject<any>> = Context extends Store<infer R> ? R : Context extends {
761
2007
  store: Store<infer R>;
762
2008
  } ? R : never;
763
2009
 
764
- /** @public */
2010
+ /**
2011
+ * Handler function called when a store operation (atomic transaction) completes.
2012
+ * This is useful for performing actions after a batch of changes has been applied,
2013
+ * such as triggering saves or sending notifications.
2014
+ *
2015
+ * @param source - Whether the operation originated from 'user' interaction or 'remote' synchronization
2016
+ *
2017
+ * @example
2018
+ * ```ts
2019
+ * const handler: StoreOperationCompleteHandler = (source) => {
2020
+ * if (source === 'user') {
2021
+ * // Auto-save after user operations complete
2022
+ * saveStoreSnapshot()
2023
+ * }
2024
+ * }
2025
+ * ```
2026
+ *
2027
+ * @public
2028
+ */
765
2029
  export declare type StoreOperationCompleteHandler = (source: 'remote' | 'user') => void;
766
2030
 
767
2031
  /**
768
- * A class that provides a 'namespace' for the various kinds of indexes one may wish to derive from
769
- * the record store.
2032
+ * A class that provides reactive querying capabilities for a record store.
2033
+ * Offers methods to create indexes, filter records, and perform efficient lookups with automatic cache management.
2034
+ * All queries are reactive and will automatically update when the underlying store data changes.
2035
+ *
2036
+ * @example
2037
+ * ```ts
2038
+ * // Create a store with books
2039
+ * const store = new Store({ schema: StoreSchema.create({ book: Book, author: Author }) })
2040
+ *
2041
+ * // Get reactive queries for books
2042
+ * const booksByAuthor = store.query.index('book', 'authorId')
2043
+ * const inStockBooks = store.query.records('book', () => ({ inStock: { eq: true } }))
2044
+ * ```
2045
+ *
770
2046
  * @public
771
2047
  */
772
2048
  export declare class StoreQueries<R extends UnknownRecord> {
773
2049
  private readonly recordMap;
774
2050
  private readonly history;
775
- constructor(recordMap: AtomMap<IdOf<R>, R>, history: Atom<number, RecordsDiff<R>>);
2051
+ /* Excluded from this release type: __constructor */
776
2052
  /* Excluded from this release type: indexCache */
777
2053
  /* Excluded from this release type: historyCache */
778
2054
  /**
779
- * Create a derivation that contains the history for a given type
2055
+ * Creates a reactive computed that tracks the change history for records of a specific type.
2056
+ * The returned computed provides incremental diffs showing what records of the given type
2057
+ * have been added, updated, or removed.
2058
+ *
2059
+ * @param typeName - The type name to filter the history by
2060
+ * @returns A computed value containing the current epoch and diffs of changes for the specified type
2061
+ *
2062
+ * @example
2063
+ * ```ts
2064
+ * // Track changes to book records only
2065
+ * const bookHistory = store.query.filterHistory('book')
2066
+ *
2067
+ * // React to book changes
2068
+ * react('book-changes', () => {
2069
+ * const currentEpoch = bookHistory.get()
2070
+ * console.log('Books updated at epoch:', currentEpoch)
2071
+ * })
2072
+ * ```
780
2073
  *
781
- * @param typeName - The name of the type to filter by.
782
- * @returns A derivation that returns the ids of all records of the given type.
783
2074
  * @public
784
2075
  */
785
2076
  filterHistory<TypeName extends R['typeName']>(typeName: TypeName): Computed<number, RecordsDiff<Extract<R, {
786
2077
  typeName: TypeName;
787
2078
  }>>>;
788
2079
  /**
789
- * Create a derivation that returns an index on a property for the given type.
2080
+ * Creates a reactive index that maps property values to sets of record IDs for efficient lookups.
2081
+ * The index automatically updates when records are added, updated, or removed, and results are cached
2082
+ * for performance.
2083
+ *
2084
+ * @param typeName - The type name of records to index
2085
+ * @param property - The property name to index by
2086
+ * @returns A reactive computed containing the index map with change diffs
2087
+ *
2088
+ * @example
2089
+ * ```ts
2090
+ * // Create an index of books by author ID
2091
+ * const booksByAuthor = store.query.index('book', 'authorId')
2092
+ *
2093
+ * // Get all books by a specific author
2094
+ * const authorBooks = booksByAuthor.get().get('author:leguin')
2095
+ * console.log(authorBooks) // Set<RecordId<Book>>
2096
+ *
2097
+ * // Index by title for quick title lookups
2098
+ * const booksByTitle = store.query.index('book', 'title')
2099
+ * const booksLatheOfHeaven = booksByTitle.get().get('The Lathe of Heaven')
2100
+ * ```
790
2101
  *
791
- * @param typeName - The name of the type.
792
- * @param property - The name of the property.
793
2102
  * @public
794
2103
  */
795
2104
  index<TypeName extends R['typeName'], Property extends string & keyof Extract<R, {
@@ -799,13 +2108,26 @@ export declare class StoreQueries<R extends UnknownRecord> {
799
2108
  }>, Property>;
800
2109
  /* Excluded from this release type: __uncached_createIndex */
801
2110
  /**
802
- * Create a derivation that will return a signle record matching the given query.
2111
+ * Creates a reactive query that returns the first record matching the given query criteria.
2112
+ * Returns undefined if no matching record is found. The query automatically updates
2113
+ * when records change.
2114
+ *
2115
+ * @param typeName - The type name of records to query
2116
+ * @param queryCreator - Function that returns the query expression object to match against
2117
+ * @param name - Optional name for the query computation (used for debugging)
2118
+ * @returns A computed value containing the first matching record or undefined
2119
+ *
2120
+ * @example
2121
+ * ```ts
2122
+ * // Find the first book with a specific title
2123
+ * const bookLatheOfHeaven = store.query.record('book', () => ({ title: { eq: 'The Lathe of Heaven' } }))
2124
+ * console.log(bookLatheOfHeaven.get()?.title) // 'The Lathe of Heaven' or undefined
803
2125
  *
804
- * It will return undefined if there is no matching record
2126
+ * // Find any book in stock
2127
+ * const anyInStockBook = store.query.record('book', () => ({ inStock: { eq: true } }))
2128
+ * ```
805
2129
  *
806
- * @param typeName - The name of the type?
807
- * @param queryCreator - A function that returns the query expression.
808
- * @param name - (optional) The name of the query.
2130
+ * @public
809
2131
  */
810
2132
  record<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, {
811
2133
  typeName: TypeName;
@@ -813,11 +2135,28 @@ export declare class StoreQueries<R extends UnknownRecord> {
813
2135
  typeName: TypeName;
814
2136
  }> | undefined>;
815
2137
  /**
816
- * Create a derivation that will return an array of records matching the given query
2138
+ * Creates a reactive query that returns an array of all records matching the given query criteria.
2139
+ * The array automatically updates when records are added, updated, or removed.
2140
+ *
2141
+ * @param typeName - The type name of records to query
2142
+ * @param queryCreator - Function that returns the query expression object to match against
2143
+ * @param name - Optional name for the query computation (used for debugging)
2144
+ * @returns A computed value containing an array of all matching records
2145
+ *
2146
+ * @example
2147
+ * ```ts
2148
+ * // Get all books in stock
2149
+ * const inStockBooks = store.query.records('book', () => ({ inStock: { eq: true } }))
2150
+ * console.log(inStockBooks.get()) // Book[]
2151
+ *
2152
+ * // Get all books by a specific author
2153
+ * const leguinBooks = store.query.records('book', () => ({ authorId: { eq: 'author:leguin' } }))
2154
+ *
2155
+ * // Get all books (no filter)
2156
+ * const allBooks = store.query.records('book')
2157
+ * ```
817
2158
  *
818
- * @param typeName - The name of the type?
819
- * @param queryCreator - A function that returns the query expression.
820
- * @param name - (optinal) The name of the query.
2159
+ * @public
821
2160
  */
822
2161
  records<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, {
823
2162
  typeName: TypeName;
@@ -825,11 +2164,29 @@ export declare class StoreQueries<R extends UnknownRecord> {
825
2164
  typeName: TypeName;
826
2165
  }>>>;
827
2166
  /**
828
- * Create a derivation that will return the ids of all records of the given type.
2167
+ * Creates a reactive query that returns a set of record IDs matching the given query criteria.
2168
+ * This is more efficient than `records()` when you only need the IDs and not the full record objects.
2169
+ * The set automatically updates with collection diffs when records change.
2170
+ *
2171
+ * @param typeName - The type name of records to query
2172
+ * @param queryCreator - Function that returns the query expression object to match against
2173
+ * @param name - Optional name for the query computation (used for debugging)
2174
+ * @returns A computed value containing a set of matching record IDs with collection diffs
2175
+ *
2176
+ * @example
2177
+ * ```ts
2178
+ * // Get IDs of all books in stock
2179
+ * const inStockBookIds = store.query.ids('book', () => ({ inStock: { eq: true } }))
2180
+ * console.log(inStockBookIds.get()) // Set<RecordId<Book>>
2181
+ *
2182
+ * // Get all book IDs (no filter)
2183
+ * const allBookIds = store.query.ids('book')
2184
+ *
2185
+ * // Use with other queries for efficient lookups
2186
+ * const authorBookIds = store.query.ids('book', () => ({ authorId: { eq: 'author:leguin' } }))
2187
+ * ```
829
2188
  *
830
- * @param typeName - The name of the type.
831
- * @param queryCreator - A function that returns the query expression.
832
- * @param name - (optinal) The name of the query.
2189
+ * @public
833
2190
  */
834
2191
  ids<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, {
835
2192
  typeName: TypeName;
@@ -838,6 +2195,27 @@ export declare class StoreQueries<R extends UnknownRecord> {
838
2195
  }>>>, CollectionDiff<IdOf<Extract<R, {
839
2196
  typeName: TypeName;
840
2197
  }>>>>;
2198
+ /**
2199
+ * Executes a one-time query against the current store state and returns matching records.
2200
+ * This is a non-reactive query that returns results immediately without creating a computed value.
2201
+ * Use this when you need a snapshot of data at a specific point in time.
2202
+ *
2203
+ * @param typeName - The type name of records to query
2204
+ * @param query - The query expression object to match against
2205
+ * @returns An array of records that match the query at the current moment
2206
+ *
2207
+ * @example
2208
+ * ```ts
2209
+ * // Get current in-stock books (non-reactive)
2210
+ * const currentInStockBooks = store.query.exec('book', { inStock: { eq: true } })
2211
+ * console.log(currentInStockBooks) // Book[]
2212
+ *
2213
+ * // Unlike records(), this won't update when the data changes
2214
+ * const staticBookList = store.query.exec('book', { authorId: { eq: 'author:leguin' } })
2215
+ * ```
2216
+ *
2217
+ * @public
2218
+ */
841
2219
  exec<TypeName extends R['typeName']>(typeName: TypeName, query: QueryExpression<Extract<R, {
842
2220
  typeName: TypeName;
843
2221
  }>>): Array<Extract<R, {
@@ -847,12 +2225,72 @@ export declare class StoreQueries<R extends UnknownRecord> {
847
2225
 
848
2226
  /* Excluded from this release type: StoreRecord */
849
2227
 
850
- /** @public */
2228
+ /**
2229
+ * Manages the schema definition, validation, and migration system for a Store.
2230
+ *
2231
+ * StoreSchema coordinates record types, handles data migrations between schema
2232
+ * versions, validates records, and provides the foundational structure for
2233
+ * reactive stores. It acts as the central authority for data consistency
2234
+ * and evolution within the store system.
2235
+ *
2236
+ * @example
2237
+ * ```ts
2238
+ * // Define record types
2239
+ * const Book = createRecordType<Book>('book', { scope: 'document' })
2240
+ * const Author = createRecordType<Author>('author', { scope: 'document' })
2241
+ *
2242
+ * // Create schema with migrations
2243
+ * const schema = StoreSchema.create(
2244
+ * { book: Book, author: Author },
2245
+ * {
2246
+ * migrations: [bookMigrations, authorMigrations],
2247
+ * onValidationFailure: (failure) => {
2248
+ * console.warn('Validation failed, using default:', failure.error)
2249
+ * return failure.record // or return a corrected version
2250
+ * }
2251
+ * }
2252
+ * )
2253
+ *
2254
+ * // Use with store
2255
+ * const store = new Store({ schema })
2256
+ * ```
2257
+ *
2258
+ * @public
2259
+ */
851
2260
  export declare class StoreSchema<R extends UnknownRecord, P = unknown> {
852
2261
  readonly types: {
853
2262
  [Record in R as Record['typeName']]: RecordType<R, any>;
854
2263
  };
855
2264
  private readonly options;
2265
+ /**
2266
+ * Creates a new StoreSchema with the given record types and options.
2267
+ *
2268
+ * This static factory method is the recommended way to create a StoreSchema.
2269
+ * It ensures type safety while providing a clean API for schema definition.
2270
+ *
2271
+ * @param types - Object mapping type names to their RecordType definitions
2272
+ * @param options - Optional configuration for migrations, validation, and integrity checking
2273
+ * @returns A new StoreSchema instance
2274
+ *
2275
+ * @example
2276
+ * ```ts
2277
+ * const Book = createRecordType<Book>('book', { scope: 'document' })
2278
+ * const Author = createRecordType<Author>('author', { scope: 'document' })
2279
+ *
2280
+ * const schema = StoreSchema.create(
2281
+ * {
2282
+ * book: Book,
2283
+ * author: Author
2284
+ * },
2285
+ * {
2286
+ * migrations: [bookMigrations],
2287
+ * onValidationFailure: (failure) => failure.record
2288
+ * }
2289
+ * )
2290
+ * ```
2291
+ *
2292
+ * @public
2293
+ */
856
2294
  static create<R extends UnknownRecord, P = unknown>(types: {
857
2295
  [TypeName in R['typeName']]: {
858
2296
  createId: any;
@@ -862,19 +2300,186 @@ export declare class StoreSchema<R extends UnknownRecord, P = unknown> {
862
2300
  readonly sortedMigrations: readonly Migration[];
863
2301
  private readonly migrationCache;
864
2302
  private constructor();
2303
+ /**
2304
+ * Validates a record using its corresponding RecordType validator.
2305
+ *
2306
+ * This method ensures that records conform to their type definitions before
2307
+ * being stored. If validation fails and an onValidationFailure handler is
2308
+ * provided, it will be called to potentially recover from the error.
2309
+ *
2310
+ * @param store - The store instance where validation is occurring
2311
+ * @param record - The record to validate
2312
+ * @param phase - The lifecycle phase where validation is happening
2313
+ * @param recordBefore - The previous version of the record (for updates)
2314
+ * @returns The validated record, potentially modified by validation failure handler
2315
+ *
2316
+ * @example
2317
+ * ```ts
2318
+ * try {
2319
+ * const validatedBook = schema.validateRecord(
2320
+ * store,
2321
+ * { id: 'book:1', typeName: 'book', title: '', author: 'Jane Doe' },
2322
+ * 'createRecord',
2323
+ * null
2324
+ * )
2325
+ * } catch (error) {
2326
+ * console.error('Record validation failed:', error)
2327
+ * }
2328
+ * ```
2329
+ *
2330
+ * @public
2331
+ */
865
2332
  validateRecord(store: Store<R>, record: R, phase: 'createRecord' | 'initialize' | 'tests' | 'updateRecord', recordBefore: null | R): R;
2333
+ /**
2334
+ * Gets all migrations that need to be applied to upgrade from a persisted schema
2335
+ * to the current schema version.
2336
+ *
2337
+ * This method compares the persisted schema with the current schema and determines
2338
+ * which migrations need to be applied to bring the data up to date. It handles
2339
+ * both regular migrations and retroactive migrations, and caches results for
2340
+ * performance.
2341
+ *
2342
+ * @param persistedSchema - The schema version that was previously persisted
2343
+ * @returns A Result containing the list of migrations to apply, or an error message
2344
+ *
2345
+ * @example
2346
+ * ```ts
2347
+ * const persistedSchema = {
2348
+ * schemaVersion: 2,
2349
+ * sequences: { 'com.tldraw.book': 1, 'com.tldraw.author': 0 }
2350
+ * }
2351
+ *
2352
+ * const migrationsResult = schema.getMigrationsSince(persistedSchema)
2353
+ * if (migrationsResult.ok) {
2354
+ * console.log('Migrations to apply:', migrationsResult.value.length)
2355
+ * // Apply each migration to bring data up to date
2356
+ * }
2357
+ * ```
2358
+ *
2359
+ * @public
2360
+ */
866
2361
  getMigrationsSince(persistedSchema: SerializedSchema): Result<Migration[], string>;
2362
+ /**
2363
+ * Migrates a single persisted record to match the current schema version.
2364
+ *
2365
+ * This method applies the necessary migrations to transform a record from an
2366
+ * older (or newer) schema version to the current version. It supports both
2367
+ * forward ('up') and backward ('down') migrations.
2368
+ *
2369
+ * @param record - The record to migrate
2370
+ * @param persistedSchema - The schema version the record was persisted with
2371
+ * @param direction - Direction to migrate ('up' for newer, 'down' for older)
2372
+ * @returns A MigrationResult containing the migrated record or an error
2373
+ *
2374
+ * @example
2375
+ * ```ts
2376
+ * const oldRecord = { id: 'book:1', typeName: 'book', title: 'Old Title', publishDate: '2020-01-01' }
2377
+ * const oldSchema = { schemaVersion: 2, sequences: { 'com.tldraw.book': 1 } }
2378
+ *
2379
+ * const result = schema.migratePersistedRecord(oldRecord, oldSchema, 'up')
2380
+ * if (result.type === 'success') {
2381
+ * console.log('Migrated record:', result.value)
2382
+ * // Record now has publishedYear instead of publishDate
2383
+ * } else {
2384
+ * console.error('Migration failed:', result.reason)
2385
+ * }
2386
+ * ```
2387
+ *
2388
+ * @public
2389
+ */
867
2390
  migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'down' | 'up'): MigrationResult<R>;
2391
+ /**
2392
+ * Migrates an entire store snapshot to match the current schema version.
2393
+ *
2394
+ * This method applies all necessary migrations to bring a persisted store
2395
+ * snapshot up to the current schema version. It handles both record-level
2396
+ * and store-level migrations, and can optionally mutate the input store
2397
+ * for performance.
2398
+ *
2399
+ * @param snapshot - The store snapshot containing data and schema information
2400
+ * @param opts - Options controlling migration behavior
2401
+ * - mutateInputStore - Whether to modify the input store directly (default: false)
2402
+ * @returns A MigrationResult containing the migrated store or an error
2403
+ *
2404
+ * @example
2405
+ * ```ts
2406
+ * const snapshot = {
2407
+ * schema: { schemaVersion: 2, sequences: { 'com.tldraw.book': 1 } },
2408
+ * store: {
2409
+ * 'book:1': { id: 'book:1', typeName: 'book', title: 'Old Book', publishDate: '2020-01-01' }
2410
+ * }
2411
+ * }
2412
+ *
2413
+ * const result = schema.migrateStoreSnapshot(snapshot)
2414
+ * if (result.type === 'success') {
2415
+ * console.log('Migrated store:', result.value)
2416
+ * // All records are now at current schema version
2417
+ * }
2418
+ * ```
2419
+ *
2420
+ * @public
2421
+ */
868
2422
  migrateStoreSnapshot(snapshot: StoreSnapshot<R>, opts?: {
869
2423
  mutateInputStore?: boolean;
870
2424
  }): MigrationResult<SerializedStore<R>>;
871
2425
  /* Excluded from this release type: createIntegrityChecker */
2426
+ /**
2427
+ * Serializes the current schema to a SerializedSchemaV2 format.
2428
+ *
2429
+ * This method creates a serialized representation of the current schema,
2430
+ * capturing the latest version number for each migration sequence.
2431
+ * The result can be persisted and later used to determine what migrations
2432
+ * need to be applied when loading data.
2433
+ *
2434
+ * @returns A SerializedSchemaV2 object representing the current schema state
2435
+ *
2436
+ * @example
2437
+ * ```ts
2438
+ * const serialized = schema.serialize()
2439
+ * console.log(serialized)
2440
+ * // {
2441
+ * // schemaVersion: 2,
2442
+ * // sequences: {
2443
+ * // 'com.tldraw.book': 3,
2444
+ * // 'com.tldraw.author': 2
2445
+ * // }
2446
+ * // }
2447
+ *
2448
+ * // Store this with your data for future migrations
2449
+ * localStorage.setItem('schema', JSON.stringify(serialized))
2450
+ * ```
2451
+ *
2452
+ * @public
2453
+ */
872
2454
  serialize(): SerializedSchemaV2;
873
2455
  /* Excluded from this release type: serializeEarliestVersion */
874
2456
  /* Excluded from this release type: getType */
875
2457
  }
876
2458
 
877
- /** @public */
2459
+ /**
2460
+ * Configuration options for creating a StoreSchema.
2461
+ *
2462
+ * These options control migration behavior, validation error handling,
2463
+ * and integrity checking for the store schema.
2464
+ *
2465
+ * @example
2466
+ * ```ts
2467
+ * const options: StoreSchemaOptions<MyRecord, MyProps> = {
2468
+ * migrations: [bookMigrations, authorMigrations],
2469
+ * onValidationFailure: (failure) => {
2470
+ * // Log the error and return a corrected record
2471
+ * console.error('Validation failed:', failure.error)
2472
+ * return sanitizeRecord(failure.record)
2473
+ * },
2474
+ * createIntegrityChecker: (store) => {
2475
+ * // Set up integrity checking logic
2476
+ * return setupIntegrityChecks(store)
2477
+ * }
2478
+ * }
2479
+ * ```
2480
+ *
2481
+ * @public
2482
+ */
878
2483
  export declare interface StoreSchemaOptions<R extends UnknownRecord, P> {
879
2484
  migrations?: MigrationSequence[];
880
2485
  /** @public */
@@ -884,14 +2489,36 @@ export declare interface StoreSchemaOptions<R extends UnknownRecord, P> {
884
2489
 
885
2490
  /**
886
2491
  * The side effect manager (aka a "correct state enforcer") is responsible
887
- * for making sure that the editor's state is always correct. This includes
2492
+ * for making sure that the store's state is always correct and consistent. This includes
888
2493
  * things like: deleting a shape if its parent is deleted; unbinding
889
- * arrows when their binding target is deleted; etc.
2494
+ * arrows when their binding target is deleted; maintaining referential integrity; etc.
2495
+ *
2496
+ * Side effects are organized into lifecycle hooks that run before and after
2497
+ * record operations (create, change, delete), allowing you to validate data,
2498
+ * transform records, and maintain business rules.
2499
+ *
2500
+ * @example
2501
+ * ```ts
2502
+ * const sideEffects = new StoreSideEffects(store)
2503
+ *
2504
+ * // Ensure arrows are deleted when their target shape is deleted
2505
+ * sideEffects.registerAfterDeleteHandler('shape', (shape) => {
2506
+ * const arrows = store.query.records('arrow', () => ({
2507
+ * toId: { eq: shape.id }
2508
+ * })).get()
2509
+ * store.remove(arrows.map(arrow => arrow.id))
2510
+ * })
2511
+ * ```
890
2512
  *
891
2513
  * @public
892
2514
  */
893
2515
  export declare class StoreSideEffects<R extends UnknownRecord> {
894
2516
  private readonly store;
2517
+ /**
2518
+ * Creates a new side effects manager for the given store.
2519
+ *
2520
+ * store - The store instance to manage side effects for
2521
+ */
895
2522
  constructor(store: Store<R>);
896
2523
  private _beforeCreateHandlers;
897
2524
  private _afterCreateHandlers;
@@ -1103,13 +2730,52 @@ export declare class StoreSideEffects<R extends UnknownRecord> {
1103
2730
  registerOperationCompleteHandler(handler: StoreOperationCompleteHandler): () => void;
1104
2731
  }
1105
2732
 
1106
- /** @public */
2733
+ /**
2734
+ * A snapshot of the store including both data and schema information.
2735
+ * This enables proper migration when loading data from different schema versions.
2736
+ *
2737
+ * @example
2738
+ * ```ts
2739
+ * const snapshot = store.getStoreSnapshot()
2740
+ * // Later...
2741
+ * store.loadStoreSnapshot(snapshot)
2742
+ * ```
2743
+ *
2744
+ * @public
2745
+ */
1107
2746
  export declare interface StoreSnapshot<R extends UnknownRecord> {
2747
+ /** The serialized store data */
1108
2748
  store: SerializedStore<R>;
2749
+ /** The serialized schema information */
1109
2750
  schema: SerializedSchema;
1110
2751
  }
1111
2752
 
1112
- /** @public */
2753
+ /**
2754
+ * Information about a record validation failure that occurred in the store.
2755
+ *
2756
+ * This interface provides context about validation errors, including the failed
2757
+ * record, the store state, and the operation phase where the failure occurred.
2758
+ * It's used by validation failure handlers to implement recovery strategies.
2759
+ *
2760
+ * @example
2761
+ * ```ts
2762
+ * const schema = StoreSchema.create(
2763
+ * { book: Book },
2764
+ * {
2765
+ * onValidationFailure: (failure: StoreValidationFailure<Book>) => {
2766
+ * console.error(`Validation failed during ${failure.phase}:`, failure.error)
2767
+ * console.log('Failed record:', failure.record)
2768
+ * console.log('Previous record:', failure.recordBefore)
2769
+ *
2770
+ * // Return a corrected version of the record
2771
+ * return { ...failure.record, title: failure.record.title || 'Untitled' }
2772
+ * }
2773
+ * }
2774
+ * )
2775
+ * ```
2776
+ *
2777
+ * @public
2778
+ */
1113
2779
  export declare interface StoreValidationFailure<R extends UnknownRecord> {
1114
2780
  error: unknown;
1115
2781
  store: Store<R>;
@@ -1118,20 +2784,85 @@ export declare interface StoreValidationFailure<R extends UnknownRecord> {
1118
2784
  recordBefore: null | R;
1119
2785
  }
1120
2786
 
1121
- /** @public */
2787
+ /**
2788
+ * A validator for store records that ensures data integrity.
2789
+ * Validators are called when records are created or updated.
2790
+ *
2791
+ * @example
2792
+ * ```ts
2793
+ * const bookValidator: StoreValidator<Book> = {
2794
+ * validate(record: unknown): Book {
2795
+ * // Validate and return the record
2796
+ * if (typeof record !== 'object' || !record.title) {
2797
+ * throw new Error('Invalid book')
2798
+ * }
2799
+ * return record as Book
2800
+ * }
2801
+ * }
2802
+ * ```
2803
+ *
2804
+ * @public
2805
+ */
1122
2806
  export declare interface StoreValidator<R extends UnknownRecord> {
2807
+ /**
2808
+ * Validate a record.
2809
+ *
2810
+ * @param record - The record to validate
2811
+ * @returns The validated record
2812
+ * @throws When validation fails
2813
+ */
1123
2814
  validate(record: unknown): R;
2815
+ /**
2816
+ * Validate a record using a known good version for reference.
2817
+ *
2818
+ * @param knownGoodVersion - A known valid version of the record
2819
+ * @param record - The record to validate
2820
+ * @returns The validated record
2821
+ */
1124
2822
  validateUsingKnownGoodVersion?(knownGoodVersion: R, record: unknown): R;
1125
2823
  }
1126
2824
 
1127
- /** @public */
2825
+ /**
2826
+ * A map of validators for each record type in the store.
2827
+ *
2828
+ * @example
2829
+ * ```ts
2830
+ * const validators: StoreValidators<Book | Author> = {
2831
+ * book: bookValidator,
2832
+ * author: authorValidator
2833
+ * }
2834
+ * ```
2835
+ *
2836
+ * @public
2837
+ */
1128
2838
  export declare type StoreValidators<R extends UnknownRecord> = {
1129
2839
  [K in R['typeName']]: StoreValidator<Extract<R, {
1130
2840
  typeName: K;
1131
2841
  }>>;
1132
2842
  };
1133
2843
 
1134
- /** @public */
2844
+ /**
2845
+ * A generic type representing any record that extends BaseRecord.
2846
+ * This is useful for type constraints when you need to work with records of unknown types,
2847
+ * but still want to ensure they follow the BaseRecord structure.
2848
+ *
2849
+ * @example
2850
+ * ```ts
2851
+ * // Function that works with any type of record
2852
+ * function logRecord(record: UnknownRecord): void {
2853
+ * console.log(`Record ${record.id} of type ${record.typeName}`)
2854
+ * }
2855
+ *
2856
+ * // Can be used with any record type
2857
+ * const book: Book = { id: 'book:123' as RecordId<Book>, typeName: 'book', title: '1984' }
2858
+ * const author: Author = { id: 'author:456' as RecordId<Author>, typeName: 'author', name: 'Orwell' }
2859
+ *
2860
+ * logRecord(book) // "Record book:123 of type book"
2861
+ * logRecord(author) // "Record author:456 of type author"
2862
+ * ```
2863
+ *
2864
+ * @public
2865
+ */
1135
2866
  export declare type UnknownRecord = BaseRecord<string, RecordId<UnknownRecord>>;
1136
2867
 
1137
2868
  export { }