@ibodr/store 0.0.0

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.
@@ -0,0 +1,3327 @@
1
+ import { Atom, UNINITIALIZED, Signal, Computed } from '@ibodr/state';
2
+ import { Expand, Result } from '@ibodr/utils';
3
+
4
+ /**
5
+ * A drop-in replacement for Map that stores values in atoms and can be used in reactive contexts.
6
+ * @public
7
+ */
8
+ declare class AtomMap<K, V> implements Map<K, V> {
9
+ private readonly name;
10
+ private atoms;
11
+ /**
12
+ * Creates a new AtomMap instance.
13
+ *
14
+ * name - A unique name for this map, used for atom identification
15
+ * entries - Optional initial entries to populate the map with
16
+ * @example
17
+ * ```ts
18
+ * // Create an empty map
19
+ * const map = new AtomMap('userMap')
20
+ *
21
+ * // Create a map with initial data
22
+ * const initialData: [string, number][] = [['a', 1], ['b', 2]]
23
+ * const mapWithData = new AtomMap('numbersMap', initialData)
24
+ * ```
25
+ */
26
+ constructor(name: string, entries?: Iterable<readonly [K, V]>);
27
+ /**
28
+ * Retrieves the underlying atom for a given key.
29
+ *
30
+ * @param key - The key to retrieve the atom for
31
+ * @returns The atom containing the value, or undefined if the key doesn't exist
32
+ * @internal
33
+ */
34
+ getAtom(key: K): Atom<V | UNINITIALIZED> | undefined;
35
+ /**
36
+ * Gets the value associated with a key. Returns undefined if the key doesn't exist.
37
+ * This method is reactive and will cause reactive contexts to update when the value changes.
38
+ *
39
+ * @param key - The key to retrieve the value for
40
+ * @returns The value associated with the key, or undefined if not found
41
+ * @example
42
+ * ```ts
43
+ * const map = new AtomMap('myMap')
44
+ * map.set('name', 'Alice')
45
+ * console.log(map.get('name')) // 'Alice'
46
+ * console.log(map.get('missing')) // undefined
47
+ * ```
48
+ */
49
+ get(key: K): V | undefined;
50
+ /**
51
+ * Gets the value associated with a key without creating reactive dependencies.
52
+ * This method will not cause reactive contexts to update when the value changes.
53
+ *
54
+ * @param key - The key to retrieve the value for
55
+ * @returns The value associated with the key, or undefined if not found
56
+ * @example
57
+ * ```ts
58
+ * const map = new AtomMap('myMap')
59
+ * map.set('count', 42)
60
+ * const value = map.__unsafe__getWithoutCapture('count') // No reactive subscription
61
+ * ```
62
+ */
63
+ __unsafe__getWithoutCapture(key: K): V | undefined;
64
+ /**
65
+ * Checks whether a key exists in the map.
66
+ * This method is reactive and will cause reactive contexts to update when keys are added or removed.
67
+ *
68
+ * @param key - The key to check for
69
+ * @returns True if the key exists in the map, false otherwise
70
+ * @example
71
+ * ```ts
72
+ * const map = new AtomMap('myMap')
73
+ * console.log(map.has('name')) // false
74
+ * map.set('name', 'Alice')
75
+ * console.log(map.has('name')) // true
76
+ * ```
77
+ */
78
+ has(key: K): boolean;
79
+ /**
80
+ * Checks whether a key exists in the map without creating reactive dependencies.
81
+ * This method will not cause reactive contexts to update when keys are added or removed.
82
+ *
83
+ * @param key - The key to check for
84
+ * @returns True if the key exists in the map, false otherwise
85
+ * @example
86
+ * ```ts
87
+ * const map = new AtomMap('myMap')
88
+ * map.set('active', true)
89
+ * const exists = map.__unsafe__hasWithoutCapture('active') // No reactive subscription
90
+ * ```
91
+ */
92
+ __unsafe__hasWithoutCapture(key: K): boolean;
93
+ /**
94
+ * Sets a value for the given key. If the key already exists, its value is updated.
95
+ * If the key doesn't exist, a new entry is created.
96
+ *
97
+ * @param key - The key to set the value for
98
+ * @param value - The value to associate with the key
99
+ * @returns This AtomMap instance for method chaining
100
+ * @example
101
+ * ```ts
102
+ * const map = new AtomMap('myMap')
103
+ * map.set('name', 'Alice').set('age', 30)
104
+ * ```
105
+ */
106
+ set(key: K, value: V): this;
107
+ /**
108
+ * Updates an existing value using an updater function.
109
+ *
110
+ * @param key - The key of the value to update
111
+ * @param updater - A function that receives the current value and returns the new value
112
+ * @throws Error if the key doesn't exist in the map
113
+ * @example
114
+ * ```ts
115
+ * const map = new AtomMap('myMap')
116
+ * map.set('count', 5)
117
+ * map.update('count', count => count + 1) // count is now 6
118
+ * ```
119
+ */
120
+ update(key: K, updater: (value: V) => V): void;
121
+ /**
122
+ * Removes a key-value pair from the map.
123
+ *
124
+ * @param key - The key to remove
125
+ * @returns True if the key existed and was removed, false if it didn't exist
126
+ * @example
127
+ * ```ts
128
+ * const map = new AtomMap('myMap')
129
+ * map.set('temp', 'value')
130
+ * console.log(map.delete('temp')) // true
131
+ * console.log(map.delete('missing')) // false
132
+ * ```
133
+ */
134
+ delete(key: K): boolean;
135
+ /**
136
+ * Removes multiple key-value pairs from the map in a single transaction.
137
+ *
138
+ * @param keys - An iterable of keys to remove
139
+ * @returns An array of [key, value] pairs that were actually deleted
140
+ * @example
141
+ * ```ts
142
+ * const map = new AtomMap('myMap')
143
+ * map.set('a', 1).set('b', 2).set('c', 3)
144
+ * const deleted = map.deleteMany(['a', 'c', 'missing'])
145
+ * console.log(deleted) // [['a', 1], ['c', 3]]
146
+ * ```
147
+ */
148
+ deleteMany(keys: Iterable<K>): [K, V][];
149
+ /**
150
+ * Removes all key-value pairs from the map.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * const map = new AtomMap('myMap')
155
+ * map.set('a', 1).set('b', 2)
156
+ * map.clear()
157
+ * console.log(map.size) // 0
158
+ * ```
159
+ */
160
+ clear(): void;
161
+ /**
162
+ * Returns an iterator that yields [key, value] pairs for each entry in the map.
163
+ * This method is reactive and will cause reactive contexts to update when entries change.
164
+ *
165
+ * @returns A generator that yields [key, value] tuples
166
+ * @example
167
+ * ```ts
168
+ * const map = new AtomMap('myMap')
169
+ * map.set('a', 1).set('b', 2)
170
+ * for (const [key, value] of map.entries()) {
171
+ * console.log(`${key}: ${value}`)
172
+ * }
173
+ * ```
174
+ */
175
+ entries(): Generator<[K, V], undefined, unknown>;
176
+ /**
177
+ * Returns an iterator that yields all keys in the map.
178
+ * This method is reactive and will cause reactive contexts to update when keys change.
179
+ *
180
+ * @returns A generator that yields keys
181
+ * @example
182
+ * ```ts
183
+ * const map = new AtomMap('myMap')
184
+ * map.set('name', 'Alice').set('age', 30)
185
+ * for (const key of map.keys()) {
186
+ * console.log(key) // 'name', 'age'
187
+ * }
188
+ * ```
189
+ */
190
+ keys(): Generator<K, undefined, unknown>;
191
+ /**
192
+ * Returns an iterator that yields all values in the map.
193
+ * This method is reactive and will cause reactive contexts to update when values change.
194
+ *
195
+ * @returns A generator that yields values
196
+ * @example
197
+ * ```ts
198
+ * const map = new AtomMap('myMap')
199
+ * map.set('name', 'Alice').set('age', 30)
200
+ * for (const value of map.values()) {
201
+ * console.log(value) // 'Alice', 30
202
+ * }
203
+ * ```
204
+ */
205
+ values(): Generator<V, undefined, unknown>;
206
+ /**
207
+ * The number of key-value pairs in the map.
208
+ * This property is reactive and will cause reactive contexts to update when the size changes.
209
+ *
210
+ * @returns The number of entries in the map
211
+ * @example
212
+ * ```ts
213
+ * const map = new AtomMap('myMap')
214
+ * console.log(map.size) // 0
215
+ * map.set('a', 1)
216
+ * console.log(map.size) // 1
217
+ * ```
218
+ */
219
+ get size(): number;
220
+ /**
221
+ * Executes a provided function once for each key-value pair in the map.
222
+ * This method is reactive and will cause reactive contexts to update when entries change.
223
+ *
224
+ * @param callbackfn - Function to execute for each entry
225
+ * - value - The value of the current entry
226
+ * - key - The key of the current entry
227
+ * - map - The AtomMap being traversed
228
+ * @param thisArg - Value to use as `this` when executing the callback
229
+ * @example
230
+ * ```ts
231
+ * const map = new AtomMap('myMap')
232
+ * map.set('a', 1).set('b', 2)
233
+ * map.forEach((value, key) => {
234
+ * console.log(`${key} = ${value}`)
235
+ * })
236
+ * ```
237
+ */
238
+ forEach(callbackfn: (value: V, key: K, map: AtomMap<K, V>) => void, thisArg?: any): void;
239
+ /**
240
+ * Returns the default iterator for the map, which is the same as entries().
241
+ * This allows the map to be used in for...of loops and other iterable contexts.
242
+ *
243
+ * @returns The same iterator as entries()
244
+ * @example
245
+ * ```ts
246
+ * const map = new AtomMap('myMap')
247
+ * map.set('a', 1).set('b', 2)
248
+ *
249
+ * // These are equivalent:
250
+ * for (const [key, value] of map) {
251
+ * console.log(`${key}: ${value}`)
252
+ * }
253
+ *
254
+ * for (const [key, value] of map.entries()) {
255
+ * console.log(`${key}: ${value}`)
256
+ * }
257
+ * ```
258
+ */
259
+ [Symbol.iterator](): Generator<[K, V], undefined, unknown>;
260
+ /**
261
+ * The string tag used by Object.prototype.toString for this class.
262
+ *
263
+ * @example
264
+ * ```ts
265
+ * const map = new AtomMap('myMap')
266
+ * console.log(Object.prototype.toString.call(map)) // '[object AtomMap]'
267
+ * ```
268
+ */
269
+ [Symbol.toStringTag]: string;
270
+ }
271
+
272
+ /**
273
+ * A drop-in replacement for Set that stores values in atoms and can be used in reactive contexts.
274
+ * @public
275
+ */
276
+ declare class AtomSet<T> {
277
+ private readonly name;
278
+ private readonly map;
279
+ constructor(name: string, keys?: Iterable<T>);
280
+ add(value: T): this;
281
+ clear(): void;
282
+ delete(value: T): boolean;
283
+ forEach(callbackfn: (value: T, value2: T, set: AtomSet<T>) => void, thisArg?: any): void;
284
+ has(value: T): boolean;
285
+ get size(): number;
286
+ entries(): Generator<[T, T], undefined, unknown>;
287
+ keys(): Generator<T, undefined, unknown>;
288
+ values(): Generator<T, undefined, unknown>;
289
+ [Symbol.iterator](): Generator<T, undefined, unknown>;
290
+ [Symbol.toStringTag]: string;
291
+ }
292
+
293
+ /**
294
+ * A branded string type that represents a unique identifier for a record.
295
+ * The brand ensures type safety by preventing mixing of IDs between different record types.
296
+ *
297
+ * @example
298
+ * ```ts
299
+ * // Define a Book record
300
+ * interface Book extends BaseRecord<'book', RecordId<Book>> {
301
+ * title: string
302
+ * author: string
303
+ * }
304
+ *
305
+ * const bookId: RecordId<Book> = 'book:abc123' as RecordId<Book>
306
+ * const authorId: RecordId<Author> = 'author:xyz789' as RecordId<Author>
307
+ *
308
+ * // TypeScript prevents mixing different record ID types
309
+ * // bookId = authorId // Type error!
310
+ * ```
311
+ *
312
+ * @public
313
+ */
314
+ type RecordId<R extends UnknownRecord> = string & {
315
+ __type__: R;
316
+ };
317
+ /**
318
+ * Utility type that extracts the ID type from a record type.
319
+ * This is useful when you need to work with record IDs without having the full record type.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * interface Book extends BaseRecord<'book', RecordId<Book>> {
324
+ * title: string
325
+ * author: string
326
+ * }
327
+ *
328
+ * // Extract the ID type from the Book record
329
+ * type BookId = IdOf<Book> // RecordId<Book>
330
+ *
331
+ * function findBook(id: IdOf<Book>): Book | undefined {
332
+ * return store.get(id)
333
+ * }
334
+ * ```
335
+ *
336
+ * @public
337
+ */
338
+ type IdOf<R extends UnknownRecord> = R['id'];
339
+ /**
340
+ * The base record interface that all records in the store must extend.
341
+ * This interface provides the fundamental structure required for all records: a unique ID and a type name.
342
+ * The type parameters ensure type safety and prevent mixing of different record types.
343
+ *
344
+ * @example
345
+ * ```ts
346
+ * // Define a Book record that extends BaseRecord
347
+ * interface Book extends BaseRecord<'book', RecordId<Book>> {
348
+ * title: string
349
+ * author: string
350
+ * publishedYear: number
351
+ * }
352
+ *
353
+ * // Define an Author record
354
+ * interface Author extends BaseRecord<'author', RecordId<Author>> {
355
+ * name: string
356
+ * birthYear: number
357
+ * }
358
+ *
359
+ * // Usage with RecordType
360
+ * const Book = createRecordType<Book>('book', { scope: 'document' })
361
+ * const book = Book.create({
362
+ * title: '1984',
363
+ * author: 'George Orwell',
364
+ * publishedYear: 1949
365
+ * })
366
+ * // Results in: { id: 'book:abc123', typeName: 'book', title: '1984', ... }
367
+ * ```
368
+ *
369
+ * @public
370
+ */
371
+ interface BaseRecord<TypeName extends string, Id extends RecordId<UnknownRecord>> {
372
+ readonly id: Id;
373
+ readonly typeName: TypeName;
374
+ }
375
+ /**
376
+ * A generic type representing any record that extends BaseRecord.
377
+ * This is useful for type constraints when you need to work with records of unknown types,
378
+ * but still want to ensure they follow the BaseRecord structure.
379
+ *
380
+ * @example
381
+ * ```ts
382
+ * // Function that works with any type of record
383
+ * function logRecord(record: UnknownRecord): void {
384
+ * console.log(`Record ${record.id} of type ${record.typeName}`)
385
+ * }
386
+ *
387
+ * // Can be used with any record type
388
+ * const book: Book = { id: 'book:123' as RecordId<Book>, typeName: 'book', title: '1984' }
389
+ * const author: Author = { id: 'author:456' as RecordId<Author>, typeName: 'author', name: 'Orwell' }
390
+ *
391
+ * logRecord(book) // "Record book:123 of type book"
392
+ * logRecord(author) // "Record author:456 of type author"
393
+ * ```
394
+ *
395
+ * @public
396
+ */
397
+ type UnknownRecord = BaseRecord<string, RecordId<UnknownRecord>>;
398
+
399
+ /**
400
+ * Freeze an object when in development mode. Copied from
401
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
402
+ *
403
+ * @example
404
+ *
405
+ * ```ts
406
+ * const frozen = devFreeze({ a: 1 })
407
+ * ```
408
+ *
409
+ * @param object - The object to freeze.
410
+ * @returns The frozen object when in development mode, or else the object when in other modes.
411
+ * @public
412
+ */
413
+ declare function devFreeze<T>(object: T): T;
414
+
415
+ /**
416
+ * A diff describing the changes to records, containing collections of records that were added,
417
+ * updated, or removed. This is the fundamental data structure used throughout the store system
418
+ * to track and communicate changes.
419
+ *
420
+ * @example
421
+ * ```ts
422
+ * const diff: RecordsDiff<Book> = {
423
+ * added: {
424
+ * 'book:1': { id: 'book:1', typeName: 'book', title: 'New Book' }
425
+ * },
426
+ * updated: {
427
+ * 'book:2': [
428
+ * { id: 'book:2', typeName: 'book', title: 'Old Title' }, // from
429
+ * { id: 'book:2', typeName: 'book', title: 'New Title' } // to
430
+ * ]
431
+ * },
432
+ * removed: {
433
+ * 'book:3': { id: 'book:3', typeName: 'book', title: 'Deleted Book' }
434
+ * }
435
+ * }
436
+ * ```
437
+ *
438
+ * @public
439
+ */
440
+ interface RecordsDiff<R extends UnknownRecord> {
441
+ /** Records that were created, keyed by their ID */
442
+ added: Record<IdOf<R>, R>;
443
+ /** Records that were modified, keyed by their ID. Each entry contains [from, to] tuple */
444
+ updated: Record<IdOf<R>, [from: R, to: R]>;
445
+ /** Records that were deleted, keyed by their ID */
446
+ removed: Record<IdOf<R>, R>;
447
+ }
448
+ /**
449
+ * Creates an empty RecordsDiff with no added, updated, or removed records.
450
+ * This is useful as a starting point when building diffs programmatically.
451
+ *
452
+ * @returns An empty RecordsDiff with all collections initialized to empty objects
453
+ * @example
454
+ * ```ts
455
+ * const emptyDiff = createEmptyRecordsDiff<Book>()
456
+ * // Result: { added: {}, updated: {}, removed: {} }
457
+ * ```
458
+ *
459
+ * @internal
460
+ */
461
+ declare function createEmptyRecordsDiff<R extends UnknownRecord>(): RecordsDiff<R>;
462
+ /**
463
+ * Creates the inverse of a RecordsDiff, effectively reversing all changes.
464
+ * Added records become removed, removed records become added, and updated records
465
+ * have their from/to values swapped. This is useful for implementing undo operations.
466
+ *
467
+ * @param diff - The diff to reverse
468
+ * @returns A new RecordsDiff that represents the inverse of the input diff
469
+ * @example
470
+ * ```ts
471
+ * const originalDiff: RecordsDiff<Book> = {
472
+ * added: { 'book:1': newBook },
473
+ * updated: { 'book:2': [oldBook, updatedBook] },
474
+ * removed: { 'book:3': deletedBook }
475
+ * }
476
+ *
477
+ * const reversedDiff = reverseRecordsDiff(originalDiff)
478
+ * // Result: {
479
+ * // added: { 'book:3': deletedBook },
480
+ * // updated: { 'book:2': [updatedBook, oldBook] },
481
+ * // removed: { 'book:1': newBook }
482
+ * // }
483
+ * ```
484
+ *
485
+ * @public
486
+ */
487
+ declare function reverseRecordsDiff(diff: RecordsDiff<any>): RecordsDiff<any>;
488
+ /**
489
+ * Checks whether a RecordsDiff contains any changes. A diff is considered empty
490
+ * if it has no added, updated, or removed records.
491
+ *
492
+ * @param diff - The diff to check
493
+ * @returns True if the diff contains no changes, false otherwise
494
+ * @example
495
+ * ```ts
496
+ * const emptyDiff = createEmptyRecordsDiff<Book>()
497
+ * console.log(isRecordsDiffEmpty(emptyDiff)) // true
498
+ *
499
+ * const nonEmptyDiff: RecordsDiff<Book> = {
500
+ * added: { 'book:1': someBook },
501
+ * updated: {},
502
+ * removed: {}
503
+ * }
504
+ * console.log(isRecordsDiffEmpty(nonEmptyDiff)) // false
505
+ * ```
506
+ *
507
+ * @public
508
+ */
509
+ declare function isRecordsDiffEmpty<T extends UnknownRecord>(diff: RecordsDiff<T>): boolean;
510
+ /**
511
+ * Combines multiple RecordsDiff objects into a single consolidated diff.
512
+ * This function intelligently merges changes, handling cases where the same record
513
+ * is modified multiple times across different diffs. For example, if a record is
514
+ * added in one diff and then updated in another, the result will show it as added
515
+ * with the final state.
516
+ *
517
+ * @param diffs - An array of diffs to combine into a single diff
518
+ * @param options - Configuration options for the squashing operation
519
+ * - mutateFirstDiff - If true, modifies the first diff in place instead of creating a new one
520
+ * @returns A single diff that represents the cumulative effect of all input diffs
521
+ * @example
522
+ * ```ts
523
+ * const diff1: RecordsDiff<Book> = {
524
+ * added: { 'book:1': { id: 'book:1', title: 'New Book' } },
525
+ * updated: {},
526
+ * removed: {}
527
+ * }
528
+ *
529
+ * const diff2: RecordsDiff<Book> = {
530
+ * added: {},
531
+ * updated: { 'book:1': [{ id: 'book:1', title: 'New Book' }, { id: 'book:1', title: 'Updated Title' }] },
532
+ * removed: {}
533
+ * }
534
+ *
535
+ * const squashed = squashRecordDiffs([diff1, diff2])
536
+ * // Result: {
537
+ * // added: { 'book:1': { id: 'book:1', title: 'Updated Title' } },
538
+ * // updated: {},
539
+ * // removed: {}
540
+ * // }
541
+ * ```
542
+ *
543
+ * @public
544
+ */
545
+ declare function squashRecordDiffs<T extends UnknownRecord>(diffs: RecordsDiff<T>[], options?: {
546
+ mutateFirstDiff?: boolean;
547
+ }): RecordsDiff<T>;
548
+ /**
549
+ * Applies an array of diffs to a target diff by mutating the target in-place.
550
+ * This is the core implementation used by squashRecordDiffs. It handles complex
551
+ * scenarios where records move between added/updated/removed states across multiple diffs.
552
+ *
553
+ * The function processes each diff sequentially, applying the following logic:
554
+ * - Added records: If the record was previously removed, convert to an update; otherwise add it
555
+ * - Updated records: Chain updates together, preserving the original 'from' state
556
+ * - Removed records: If the record was added in this sequence, cancel both operations
557
+ *
558
+ * @param target - The diff to modify in-place (will be mutated)
559
+ * @param diffs - Array of diffs to apply to the target
560
+ * @example
561
+ * ```ts
562
+ * const targetDiff: RecordsDiff<Book> = {
563
+ * added: {},
564
+ * updated: {},
565
+ * removed: { 'book:1': oldBook }
566
+ * }
567
+ *
568
+ * const newDiffs = [{
569
+ * added: { 'book:1': newBook },
570
+ * updated: {},
571
+ * removed: {}
572
+ * }]
573
+ *
574
+ * squashRecordDiffsMutable(targetDiff, newDiffs)
575
+ * // targetDiff is now: {
576
+ * // added: {},
577
+ * // updated: { 'book:1': [oldBook, newBook] },
578
+ * // removed: {}
579
+ * // }
580
+ * ```
581
+ *
582
+ * @internal
583
+ */
584
+ declare function squashRecordDiffsMutable<T extends UnknownRecord>(target: RecordsDiff<T>, diffs: RecordsDiff<T>[]): void;
585
+
586
+ /**
587
+ * Defines the scope of the record
588
+ *
589
+ * session: The record belongs to a single instance of the store. It should not be synced, and any persistence logic should 'de-instance-ize' the record before persisting it, and apply the reverse when rehydrating.
590
+ * document: The record is persisted and synced. It is available to all store instances.
591
+ * presence: The record belongs to a single instance of the store. It may be synced to other instances, but other instances should not make changes to it. It should not be persisted.
592
+ *
593
+ * @public
594
+ * */
595
+ type RecordScope = 'session' | 'document' | 'presence';
596
+ /**
597
+ * A record type is a type that can be stored in a record store. It is created with
598
+ * `createRecordType`.
599
+ *
600
+ * @public
601
+ */
602
+ declare class RecordType<R extends UnknownRecord, RequiredProperties extends keyof Omit<R, 'id' | 'typeName'>> {
603
+ /**
604
+ * The unique type associated with this record.
605
+ *
606
+ * @public
607
+ * @readonly
608
+ */
609
+ readonly typeName: R['typeName'];
610
+ /**
611
+ * Factory function that creates default properties for new records.
612
+ * @public
613
+ */
614
+ readonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>;
615
+ /**
616
+ * Validator function used to validate records of this type.
617
+ * @public
618
+ */
619
+ readonly validator: StoreValidator<R>;
620
+ /**
621
+ * Optional configuration specifying which record properties are ephemeral.
622
+ * Ephemeral properties are not included in snapshots or synchronization.
623
+ * @public
624
+ */
625
+ readonly ephemeralKeys?: {
626
+ readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
627
+ };
628
+ /**
629
+ * Set of property names that are marked as ephemeral for efficient lookup.
630
+ * @public
631
+ */
632
+ readonly ephemeralKeySet: ReadonlySet<string>;
633
+ /**
634
+ * The scope that determines how records of this type are persisted and synchronized.
635
+ * @public
636
+ */
637
+ readonly scope: RecordScope;
638
+ /**
639
+ * Creates a new RecordType instance.
640
+ *
641
+ * typeName - The unique type name for records created by this RecordType
642
+ * config - Configuration object for the RecordType
643
+ * - createDefaultProperties - Function that returns default properties for new records
644
+ * - validator - Optional validator function for record validation
645
+ * - scope - Optional scope determining persistence behavior (defaults to 'document')
646
+ * - ephemeralKeys - Optional mapping of property names to ephemeral status
647
+ * @public
648
+ */
649
+ constructor(
650
+ /**
651
+ * The unique type associated with this record.
652
+ *
653
+ * @public
654
+ * @readonly
655
+ */
656
+ typeName: R['typeName'], config: {
657
+ readonly createDefaultProperties: () => Exclude<Omit<R, 'id' | 'typeName'>, RequiredProperties>;
658
+ readonly validator?: StoreValidator<R>;
659
+ readonly scope?: RecordScope;
660
+ readonly ephemeralKeys?: {
661
+ readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
662
+ };
663
+ });
664
+ /**
665
+ * Creates a new record of this type with the given properties.
666
+ *
667
+ * Properties are merged with default properties from the RecordType configuration.
668
+ * If no id is provided, a unique id will be generated automatically.
669
+ *
670
+ * @example
671
+ * ```ts
672
+ * const book = Book.create({
673
+ * title: 'The Great Gatsby',
674
+ * author: 'F. Scott Fitzgerald'
675
+ * })
676
+ * // Result: { id: 'book:abc123', typeName: 'book', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', inStock: true }
677
+ * ```
678
+ *
679
+ * @param properties - The properties for the new record, including both required and optional fields
680
+ * @returns The newly created record with generated id and typeName
681
+ * @public
682
+ */
683
+ create(properties: Expand<Pick<R, RequiredProperties> & Omit<Partial<R>, RequiredProperties>>): R;
684
+ /**
685
+ * Creates a deep copy of an existing record with a new unique id.
686
+ *
687
+ * This method performs a deep clone of all properties while generating a fresh id,
688
+ * making it useful for duplicating records without id conflicts.
689
+ *
690
+ * @example
691
+ * ```ts
692
+ * const originalBook = Book.create({ title: '1984', author: 'George Orwell' })
693
+ * const duplicatedBook = Book.clone(originalBook)
694
+ * // duplicatedBook has same properties but different id
695
+ * ```
696
+ *
697
+ * @param record - The record to clone
698
+ * @returns A new record with the same properties but a different id
699
+ * @public
700
+ */
701
+ clone(record: R): R;
702
+ /**
703
+ * Create a new ID for this record type.
704
+ *
705
+ * @example
706
+ *
707
+ * ```ts
708
+ * const id = recordType.createId()
709
+ * ```
710
+ *
711
+ * @returns The new ID.
712
+ * @public
713
+ */
714
+ createId(customUniquePart?: string): IdOf<R>;
715
+ /**
716
+ * Extracts the unique identifier part from a full record id.
717
+ *
718
+ * Record ids have the format `typeName:uniquePart`. This method returns just the unique part.
719
+ *
720
+ * @example
721
+ * ```ts
722
+ * const bookId = Book.createId() // 'book:abc123'
723
+ * const uniquePart = Book.parseId(bookId) // 'abc123'
724
+ * ```
725
+ *
726
+ * @param id - The full record id to parse
727
+ * @returns The unique identifier portion after the colon
728
+ * @throws Error if the id is not valid for this record type
729
+ * @public
730
+ */
731
+ parseId(id: IdOf<R>): string;
732
+ /**
733
+ * Type guard that checks whether a record belongs to this RecordType.
734
+ *
735
+ * This method performs a runtime check by comparing the record's typeName
736
+ * against this RecordType's typeName.
737
+ *
738
+ * @example
739
+ * ```ts
740
+ * if (Book.isInstance(someRecord)) {
741
+ * // someRecord is now typed as a book record
742
+ * console.log(someRecord.title)
743
+ * }
744
+ * ```
745
+ *
746
+ * @param record - The record to check, may be undefined
747
+ * @returns True if the record is an instance of this record type
748
+ * @public
749
+ */
750
+ isInstance(record?: UnknownRecord): record is R;
751
+ /**
752
+ * Type guard that checks whether an id string belongs to this RecordType.
753
+ *
754
+ * Validates that the id starts with this RecordType's typeName followed by a colon.
755
+ * This is more efficient than parsing the full id when you only need to verify the type.
756
+ *
757
+ * @example
758
+ * ```ts
759
+ * if (Book.isId(someId)) {
760
+ * // someId is now typed as IdOf<BookRecord>
761
+ * const book = store.get(someId)
762
+ * }
763
+ * ```
764
+ *
765
+ * @param id - The id string to check, may be undefined
766
+ * @returns True if the id belongs to this record type
767
+ * @public
768
+ */
769
+ isId(id?: string): id is IdOf<R>;
770
+ /**
771
+ * Create a new RecordType that has the same type name as this RecordType and includes the given
772
+ * default properties.
773
+ *
774
+ * @example
775
+ *
776
+ * ```ts
777
+ * const authorType = createRecordType('author', () => ({ living: true }))
778
+ * const deadAuthorType = authorType.withDefaultProperties({ living: false })
779
+ * ```
780
+ *
781
+ * @param createDefaultProperties - A function that returns the default properties of the new RecordType.
782
+ * @returns The new RecordType.
783
+ */
784
+ withDefaultProperties<DefaultProps extends Omit<Partial<R>, 'typeName' | 'id'>>(createDefaultProperties: () => DefaultProps): RecordType<R, Exclude<RequiredProperties, keyof DefaultProps>>;
785
+ /**
786
+ * Validates a record against this RecordType's validator and returns it with proper typing.
787
+ *
788
+ * This method runs the configured validator function and throws an error if validation fails.
789
+ * If a previous version of the record is provided, it may use optimized validation.
790
+ *
791
+ * @example
792
+ * ```ts
793
+ * try {
794
+ * const validBook = Book.validate(untrustedData)
795
+ * // validBook is now properly typed and validated
796
+ * } catch (error) {
797
+ * console.log('Validation failed:', error.message)
798
+ * }
799
+ * ```
800
+ *
801
+ * @param record - The unknown record data to validate
802
+ * @param recordBefore - Optional previous version for optimized validation
803
+ * @returns The validated and properly typed record
804
+ * @throws Error if validation fails
805
+ * @public
806
+ */
807
+ validate(record: unknown, recordBefore?: R): R;
808
+ }
809
+ /**
810
+ * Creates a new RecordType with the specified configuration.
811
+ *
812
+ * This factory function creates a RecordType that can be used to create, validate, and manage
813
+ * records of a specific type within a store. The resulting RecordType can be extended with
814
+ * default properties using the withDefaultProperties method.
815
+ *
816
+ * @example
817
+ * ```ts
818
+ * interface BookRecord extends BaseRecord<'book', RecordId<BookRecord>> {
819
+ * title: string
820
+ * author: string
821
+ * inStock: boolean
822
+ * }
823
+ *
824
+ * const Book = createRecordType<BookRecord>('book', {
825
+ * scope: 'document',
826
+ * validator: bookValidator
827
+ * })
828
+ * ```
829
+ *
830
+ * @param typeName - The unique type name for this record type
831
+ * @param config - Configuration object containing validator, scope, and ephemeral keys
832
+ * @returns A new RecordType instance for creating and managing records
833
+ * @public
834
+ */
835
+ declare function createRecordType<R extends UnknownRecord>(typeName: R['typeName'], config: {
836
+ validator?: StoreValidator<R>;
837
+ scope: RecordScope;
838
+ ephemeralKeys?: {
839
+ readonly [K in Exclude<keyof R, 'id' | 'typeName'>]: boolean;
840
+ };
841
+ }): RecordType<R, keyof Omit<R, 'id' | 'typeName'>>;
842
+ /**
843
+ * Assert whether an id correspond to a record type.
844
+ *
845
+ * @example
846
+ *
847
+ * ```ts
848
+ * assertIdType(myId, "shape")
849
+ * ```
850
+ *
851
+ * @param id - The id to check.
852
+ * @param type - The type of the record.
853
+ * @public
854
+ */
855
+ declare function assertIdType<R extends UnknownRecord>(id: string | undefined, type: RecordType<R, any>): asserts id is IdOf<R>;
856
+
857
+ /**
858
+ * Creates a migration sequence that defines how to transform data as your schema evolves.
859
+ *
860
+ * A migration sequence contains a series of migrations that are applied in order to transform
861
+ * data from older versions to newer versions. Each migration is identified by a unique ID
862
+ * and can operate at either the record level (transforming individual records) or store level
863
+ * (transforming the entire store structure).
864
+ *
865
+ * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.
866
+ * @param options - Configuration for the migration sequence
867
+ * - sequenceId - Unique identifier for this migration sequence (e.g., 'com.myapp.book')
868
+ * - sequence - Array of migrations or dependency declarations to include in the sequence
869
+ * - retroactive - Whether migrations should apply to snapshots created before this sequence was added (defaults to true)
870
+ * @returns A validated migration sequence that can be included in a store schema
871
+ * @example
872
+ * ```ts
873
+ * const bookMigrations = createMigrationSequence({
874
+ * sequenceId: 'com.myapp.book',
875
+ * sequence: [
876
+ * {
877
+ * id: 'com.myapp.book/1',
878
+ * scope: 'record',
879
+ * up: (record) => ({ ...record, newField: 'default' })
880
+ * }
881
+ * ]
882
+ * })
883
+ * ```
884
+ * @public
885
+ */
886
+ declare function createMigrationSequence({ sequence, sequenceId, retroactive, }: {
887
+ sequenceId: string;
888
+ retroactive?: boolean;
889
+ sequence: Array<Migration | StandaloneDependsOn>;
890
+ }): MigrationSequence;
891
+ /**
892
+ * Creates a named set of migration IDs from version numbers and a sequence ID.
893
+ *
894
+ * This utility function helps generate properly formatted migration IDs that follow
895
+ * the required `sequenceId/version` pattern. It takes a sequence ID and a record
896
+ * of named versions, returning migration IDs that can be used in migration definitions.
897
+ *
898
+ * See the [migration guide](https://tldraw.dev/docs/persistence#Migrations) for more info on how to use this API.
899
+ * @param sequenceId - The sequence identifier (e.g., 'com.myapp.book')
900
+ * @param versions - Record mapping version names to numbers
901
+ * @returns Record mapping version names to properly formatted migration IDs
902
+ * @example
903
+ * ```ts
904
+ * const migrationIds = createMigrationIds('com.myapp.book', {
905
+ * addGenre: 1,
906
+ * addPublisher: 2,
907
+ * removeOldField: 3
908
+ * })
909
+ * // Result: {
910
+ * // addGenre: 'com.myapp.book/1',
911
+ * // addPublisher: 'com.myapp.book/2',
912
+ * // removeOldField: 'com.myapp.book/3'
913
+ * // }
914
+ * ```
915
+ * @public
916
+ */
917
+ declare function createMigrationIds<const ID extends string, const Versions extends Record<string, number>>(sequenceId: ID, versions: Versions): {
918
+ [K in keyof Versions]: `${ID}/${Versions[K]}`;
919
+ };
920
+ /**
921
+ * Creates a migration sequence specifically for record-level migrations.
922
+ *
923
+ * This is a convenience function that creates a migration sequence where all migrations
924
+ * operate at the record scope and are automatically filtered to apply only to records
925
+ * of a specific type. Each migration in the sequence will be enhanced with the record
926
+ * scope and appropriate filtering logic.
927
+ * @param opts - Configuration for the record migration sequence
928
+ * - recordType - The record type name these migrations should apply to
929
+ * - filter - Optional additional filter function to determine which records to migrate
930
+ * - retroactive - Whether migrations should apply to snapshots created before this sequence was added
931
+ * - sequenceId - Unique identifier for this migration sequence
932
+ * - sequence - Array of record migration definitions (scope will be added automatically)
933
+ * @returns A migration sequence configured for record-level operations
934
+ * @internal
935
+ */
936
+ declare function createRecordMigrationSequence(opts: {
937
+ recordType: string;
938
+ filter?(record: UnknownRecord): boolean;
939
+ retroactive?: boolean;
940
+ sequenceId: string;
941
+ sequence: Omit<Extract<Migration, {
942
+ scope: 'record';
943
+ }>, 'scope'>[];
944
+ }): MigrationSequence;
945
+ /**
946
+ * Legacy migration interface for backward compatibility.
947
+ *
948
+ * This interface represents the old migration format that included both `up` and `down`
949
+ * transformation functions. While still supported, new code should use the `Migration`
950
+ * type which provides more flexibility and better integration with the current system.
951
+ * @public
952
+ */
953
+ interface LegacyMigration<Before = any, After = any> {
954
+ up: (oldState: Before) => After;
955
+ down: (newState: After) => Before;
956
+ }
957
+ /**
958
+ * Unique identifier for a migration in the format `sequenceId/version`.
959
+ *
960
+ * Migration IDs follow a specific pattern where the sequence ID identifies the migration
961
+ * sequence and the version number indicates the order within that sequence. For example:
962
+ * 'com.myapp.book/1', 'com.myapp.book/2', etc.
963
+ * @public
964
+ */
965
+ type MigrationId = `${string}/${number}`;
966
+ /**
967
+ * Declares dependencies for migrations without being a migration itself.
968
+ *
969
+ * This interface allows you to specify that future migrations in a sequence depend on
970
+ * migrations from other sequences, without defining an actual migration transformation.
971
+ * It's used to establish cross-sequence dependencies in the migration graph.
972
+ * @public
973
+ */
974
+ interface StandaloneDependsOn {
975
+ readonly dependsOn: readonly MigrationId[];
976
+ }
977
+ /**
978
+ * Defines a single migration that transforms data from one schema version to another.
979
+ *
980
+ * A migration can operate at two different scopes:
981
+ * - `record`: Transforms individual records, with optional filtering to target specific records
982
+ * - `store`: Transforms the entire serialized store structure
983
+ *
984
+ * Each migration has a unique ID and can declare dependencies on other migrations that must
985
+ * be applied first. The `up` function performs the forward transformation, while the optional
986
+ * `down` function can reverse the migration if needed.
987
+ * @public
988
+ */
989
+ type Migration = {
990
+ readonly id: MigrationId;
991
+ readonly dependsOn?: readonly MigrationId[] | undefined;
992
+ } & ({
993
+ readonly scope: 'record';
994
+ readonly filter?: (record: UnknownRecord) => boolean;
995
+ readonly up: (oldState: UnknownRecord) => void | UnknownRecord;
996
+ readonly down?: (newState: UnknownRecord) => void | UnknownRecord;
997
+ } | {
998
+ readonly scope: 'store';
999
+ readonly up: (oldState: SerializedStore<UnknownRecord>) => void | SerializedStore<UnknownRecord>;
1000
+ readonly down?: (newState: SerializedStore<UnknownRecord>) => void | SerializedStore<UnknownRecord>;
1001
+ } | {
1002
+ readonly scope: 'storage';
1003
+ readonly up: (storage: SynchronousRecordStorage<UnknownRecord>) => void;
1004
+ readonly down?: never;
1005
+ });
1006
+ /**
1007
+ * Abstraction over the store that can be used to perform migrations.
1008
+ * @public
1009
+ */
1010
+ interface SynchronousRecordStorage<R extends UnknownRecord> {
1011
+ get(id: string): R | undefined;
1012
+ set(id: string, record: R): void;
1013
+ delete(id: string): void;
1014
+ keys(): Iterable<string>;
1015
+ values(): Iterable<R>;
1016
+ entries(): Iterable<[string, R]>;
1017
+ }
1018
+ /**
1019
+ * Abstraction over the storage that can be used to perform migrations.
1020
+ * @public
1021
+ */
1022
+ interface SynchronousStorage<R extends UnknownRecord> extends SynchronousRecordStorage<R> {
1023
+ getSchema(): SerializedSchema;
1024
+ setSchema(schema: SerializedSchema): void;
1025
+ }
1026
+ /**
1027
+ * Base interface for legacy migration information.
1028
+ *
1029
+ * Contains the basic structure used by the legacy migration system, including version
1030
+ * range information and the migration functions indexed by version number. This is
1031
+ * maintained for backward compatibility with older migration definitions.
1032
+ * @public
1033
+ */
1034
+ interface LegacyBaseMigrationsInfo {
1035
+ firstVersion: number;
1036
+ currentVersion: number;
1037
+ migrators: {
1038
+ [version: number]: LegacyMigration;
1039
+ };
1040
+ }
1041
+ /**
1042
+ * Legacy migration configuration with support for sub-type migrations.
1043
+ *
1044
+ * This interface extends the base legacy migration info to support migrations that
1045
+ * vary based on a sub-type key within records. This allows different migration paths
1046
+ * for different variants of the same record type, which was useful in older migration
1047
+ * systems but is now handled more elegantly by the current Migration system.
1048
+ * @public
1049
+ */
1050
+ interface LegacyMigrations extends LegacyBaseMigrationsInfo {
1051
+ subTypeKey?: string;
1052
+ subTypeMigrations?: Record<string, LegacyBaseMigrationsInfo>;
1053
+ }
1054
+ /**
1055
+ * A complete sequence of migrations that can be applied to transform data.
1056
+ *
1057
+ * A migration sequence represents a series of ordered migrations that belong together,
1058
+ * typically for a specific part of your schema. The sequence includes metadata about
1059
+ * whether it should be applied retroactively to existing data and contains the actual
1060
+ * migration definitions in execution order.
1061
+ * @public
1062
+ */
1063
+ interface MigrationSequence {
1064
+ sequenceId: string;
1065
+ /**
1066
+ * retroactive should be true if the migrations should be applied to snapshots that were created before
1067
+ * this migration sequence was added to the schema.
1068
+ *
1069
+ * In general:
1070
+ *
1071
+ * - retroactive should be true when app developers create their own new migration sequences.
1072
+ * - retroactive should be false when library developers ship a migration sequence. When you install a library for the first time, any migrations that were added in the library before that point should generally _not_ be applied to your existing data.
1073
+ */
1074
+ retroactive: boolean;
1075
+ sequence: Migration[];
1076
+ }
1077
+ /**
1078
+ * Parses a migration ID to extract the sequence ID and version number.
1079
+ *
1080
+ * Migration IDs follow the format `sequenceId/version`, and this function splits
1081
+ * them into their component parts. This is used internally for sorting migrations
1082
+ * and understanding their relationships.
1083
+ * @param id - The migration ID to parse
1084
+ * @returns Object containing the sequence ID and numeric version
1085
+ * @example
1086
+ * ```ts
1087
+ * const { sequenceId, version } = parseMigrationId('com.myapp.book/5')
1088
+ * // sequenceId: 'com.myapp.book', version: 5
1089
+ * ```
1090
+ * @internal
1091
+ */
1092
+ declare function parseMigrationId(id: MigrationId): {
1093
+ sequenceId: string;
1094
+ version: number;
1095
+ };
1096
+ /**
1097
+ * Result type returned by migration operations.
1098
+ *
1099
+ * Migration operations can either succeed and return the transformed value,
1100
+ * or fail with a specific reason. This discriminated union type allows for
1101
+ * safe handling of both success and error cases when applying migrations.
1102
+ * @public
1103
+ */
1104
+ type MigrationResult<T> = {
1105
+ type: 'success';
1106
+ value: T;
1107
+ } | {
1108
+ type: 'error';
1109
+ reason: MigrationFailureReason;
1110
+ };
1111
+ /** @public */
1112
+ declare const MigrationFailureReason: {
1113
+ readonly IncompatibleSubtype: "incompatible-subtype";
1114
+ readonly UnknownType: "unknown-type";
1115
+ readonly TargetVersionTooNew: "target-version-too-new";
1116
+ readonly TargetVersionTooOld: "target-version-too-old";
1117
+ readonly MigrationError: "migration-error";
1118
+ readonly UnrecognizedSubtype: "unrecognized-subtype";
1119
+ };
1120
+ /** @public */
1121
+ type MigrationFailureReason = (typeof MigrationFailureReason)[keyof typeof MigrationFailureReason];
1122
+ /** @public */
1123
+ declare namespace MigrationFailureReason {
1124
+ type IncompatibleSubtype = typeof MigrationFailureReason.IncompatibleSubtype;
1125
+ type UnknownType = typeof MigrationFailureReason.UnknownType;
1126
+ type TargetVersionTooNew = typeof MigrationFailureReason.TargetVersionTooNew;
1127
+ type TargetVersionTooOld = typeof MigrationFailureReason.TargetVersionTooOld;
1128
+ type MigrationError = typeof MigrationFailureReason.MigrationError;
1129
+ type UnrecognizedSubtype = typeof MigrationFailureReason.UnrecognizedSubtype;
1130
+ }
1131
+
1132
+ /**
1133
+ * Version 1 format for serialized store schema information.
1134
+ *
1135
+ * This is the legacy format used before schema version 2. Version 1 schemas
1136
+ * separate store-level versioning from record-level versioning, and support
1137
+ * subtypes for complex record types like shapes.
1138
+ *
1139
+ * @example
1140
+ * ```ts
1141
+ * const schemaV1: SerializedSchemaV1 = {
1142
+ * schemaVersion: 1,
1143
+ * storeVersion: 2,
1144
+ * recordVersions: {
1145
+ * book: { version: 3 },
1146
+ * shape: {
1147
+ * version: 2,
1148
+ * subTypeVersions: { rectangle: 1, circle: 2 },
1149
+ * subTypeKey: 'type'
1150
+ * }
1151
+ * }
1152
+ * }
1153
+ * ```
1154
+ *
1155
+ * @public
1156
+ */
1157
+ interface SerializedSchemaV1 {
1158
+ /** Schema version is the version for this type you're looking at right now */
1159
+ schemaVersion: 1;
1160
+ /**
1161
+ * Store version is the version for the structure of the store. e.g. higher level structure like
1162
+ * removing or renaming a record type.
1163
+ */
1164
+ storeVersion: number;
1165
+ /** Record versions are the versions for each record type. e.g. adding a new field to a record */
1166
+ recordVersions: Record<string, {
1167
+ version: number;
1168
+ } | {
1169
+ version: number;
1170
+ subTypeVersions: Record<string, number>;
1171
+ subTypeKey: string;
1172
+ }>;
1173
+ }
1174
+ /**
1175
+ * Version 2 format for serialized store schema information.
1176
+ *
1177
+ * This is the current format that uses a unified sequence-based approach
1178
+ * for tracking versions across all migration sequences. Each sequence ID
1179
+ * maps to the latest version number for that sequence.
1180
+ *
1181
+ * @example
1182
+ * ```ts
1183
+ * const schemaV2: SerializedSchemaV2 = {
1184
+ * schemaVersion: 2,
1185
+ * sequences: {
1186
+ * 'com.draw.store': 3,
1187
+ * 'com.draw.book': 2,
1188
+ * 'com.draw.shape': 4,
1189
+ * 'com.draw.shape.rectangle': 1
1190
+ * }
1191
+ * }
1192
+ * ```
1193
+ *
1194
+ * @public
1195
+ */
1196
+ interface SerializedSchemaV2 {
1197
+ schemaVersion: 2;
1198
+ sequences: {
1199
+ [sequenceId: string]: number;
1200
+ };
1201
+ }
1202
+ /**
1203
+ * Union type representing all supported serialized schema formats.
1204
+ *
1205
+ * This type allows the store to handle both legacy (V1) and current (V2)
1206
+ * schema formats during deserialization and migration.
1207
+ *
1208
+ * @example
1209
+ * ```ts
1210
+ * function handleSchema(schema: SerializedSchema) {
1211
+ * if (schema.schemaVersion === 1) {
1212
+ * // Handle V1 format
1213
+ * console.log('Store version:', schema.storeVersion)
1214
+ * } else {
1215
+ * // Handle V2 format
1216
+ * console.log('Sequences:', schema.sequences)
1217
+ * }
1218
+ * }
1219
+ * ```
1220
+ *
1221
+ * @public
1222
+ */
1223
+ type SerializedSchema = SerializedSchemaV1 | SerializedSchemaV2;
1224
+ /**
1225
+ * Information about a record validation failure that occurred in the store.
1226
+ *
1227
+ * This interface provides context about validation errors, including the failed
1228
+ * record, the store state, and the operation phase where the failure occurred.
1229
+ * It's used by validation failure handlers to implement recovery strategies.
1230
+ *
1231
+ * @example
1232
+ * ```ts
1233
+ * const schema = StoreSchema.create(
1234
+ * { book: Book },
1235
+ * {
1236
+ * onValidationFailure: (failure: StoreValidationFailure<Book>) => {
1237
+ * console.error(`Validation failed during ${failure.phase}:`, failure.error)
1238
+ * console.log('Failed record:', failure.record)
1239
+ * console.log('Previous record:', failure.recordBefore)
1240
+ *
1241
+ * // Return a corrected version of the record
1242
+ * return { ...failure.record, title: failure.record.title || 'Untitled' }
1243
+ * }
1244
+ * }
1245
+ * )
1246
+ * ```
1247
+ *
1248
+ * @public
1249
+ */
1250
+ interface StoreValidationFailure<R extends UnknownRecord> {
1251
+ error: unknown;
1252
+ store: Store<R>;
1253
+ record: R;
1254
+ phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests';
1255
+ recordBefore: R | null;
1256
+ }
1257
+ /**
1258
+ * Configuration options for creating a StoreSchema.
1259
+ *
1260
+ * These options control migration behavior, validation error handling,
1261
+ * and integrity checking for the store schema.
1262
+ *
1263
+ * @example
1264
+ * ```ts
1265
+ * const options: StoreSchemaOptions<MyRecord, MyProps> = {
1266
+ * migrations: [bookMigrations, authorMigrations],
1267
+ * onValidationFailure: (failure) => {
1268
+ * // Log the error and return a corrected record
1269
+ * console.error('Validation failed:', failure.error)
1270
+ * return sanitizeRecord(failure.record)
1271
+ * },
1272
+ * createIntegrityChecker: (store) => {
1273
+ * // Set up integrity checking logic
1274
+ * return setupIntegrityChecks(store)
1275
+ * }
1276
+ * }
1277
+ * ```
1278
+ *
1279
+ * @public
1280
+ */
1281
+ interface StoreSchemaOptions<R extends UnknownRecord, P> {
1282
+ migrations?: MigrationSequence[];
1283
+ /** @public */
1284
+ onValidationFailure?(data: StoreValidationFailure<R>): R;
1285
+ /** @internal */
1286
+ createIntegrityChecker?(store: Store<R, P>): void;
1287
+ }
1288
+ /**
1289
+ * Manages the schema definition, validation, and migration system for a Store.
1290
+ *
1291
+ * StoreSchema coordinates record types, handles data migrations between schema
1292
+ * versions, validates records, and provides the foundational structure for
1293
+ * reactive stores. It acts as the central authority for data consistency
1294
+ * and evolution within the store system.
1295
+ *
1296
+ * @example
1297
+ * ```ts
1298
+ * // Define record types
1299
+ * const Book = createRecordType<Book>('book', { scope: 'document' })
1300
+ * const Author = createRecordType<Author>('author', { scope: 'document' })
1301
+ *
1302
+ * // Create schema with migrations
1303
+ * const schema = StoreSchema.create(
1304
+ * { book: Book, author: Author },
1305
+ * {
1306
+ * migrations: [bookMigrations, authorMigrations],
1307
+ * onValidationFailure: (failure) => {
1308
+ * console.warn('Validation failed, using default:', failure.error)
1309
+ * return failure.record // or return a corrected version
1310
+ * }
1311
+ * }
1312
+ * )
1313
+ *
1314
+ * // Use with store
1315
+ * const store = new Store({ schema })
1316
+ * ```
1317
+ *
1318
+ * @public
1319
+ */
1320
+ declare class StoreSchema<R extends UnknownRecord, P = unknown> {
1321
+ readonly types: {
1322
+ [Record in R as Record['typeName']]: RecordType<R, any>;
1323
+ };
1324
+ private readonly options;
1325
+ /**
1326
+ * Creates a new StoreSchema with the given record types and options.
1327
+ *
1328
+ * This static factory method is the recommended way to create a StoreSchema.
1329
+ * It ensures type safety while providing a clean API for schema definition.
1330
+ *
1331
+ * @param types - Object mapping type names to their RecordType definitions
1332
+ * @param options - Optional configuration for migrations, validation, and integrity checking
1333
+ * @returns A new StoreSchema instance
1334
+ *
1335
+ * @example
1336
+ * ```ts
1337
+ * const Book = createRecordType<Book>('book', { scope: 'document' })
1338
+ * const Author = createRecordType<Author>('author', { scope: 'document' })
1339
+ *
1340
+ * const schema = StoreSchema.create(
1341
+ * {
1342
+ * book: Book,
1343
+ * author: Author
1344
+ * },
1345
+ * {
1346
+ * migrations: [bookMigrations],
1347
+ * onValidationFailure: (failure) => failure.record
1348
+ * }
1349
+ * )
1350
+ * ```
1351
+ *
1352
+ * @public
1353
+ */
1354
+ static create<R extends UnknownRecord, P = unknown>(types: {
1355
+ [TypeName in R['typeName']]: {
1356
+ createId: any;
1357
+ };
1358
+ }, options?: StoreSchemaOptions<R, P>): StoreSchema<R, P>;
1359
+ readonly migrations: Record<string, MigrationSequence>;
1360
+ readonly sortedMigrations: readonly Migration[];
1361
+ private readonly migrationCache;
1362
+ private constructor();
1363
+ /**
1364
+ * Validates a record using its corresponding RecordType validator.
1365
+ *
1366
+ * This method ensures that records conform to their type definitions before
1367
+ * being stored. If validation fails and an onValidationFailure handler is
1368
+ * provided, it will be called to potentially recover from the error.
1369
+ *
1370
+ * @param store - The store instance where validation is occurring
1371
+ * @param record - The record to validate
1372
+ * @param phase - The lifecycle phase where validation is happening
1373
+ * @param recordBefore - The previous version of the record (for updates)
1374
+ * @returns The validated record, potentially modified by validation failure handler
1375
+ *
1376
+ * @example
1377
+ * ```ts
1378
+ * try {
1379
+ * const validatedBook = schema.validateRecord(
1380
+ * store,
1381
+ * { id: 'book:1', typeName: 'book', title: '', author: 'Jane Doe' },
1382
+ * 'createRecord',
1383
+ * null
1384
+ * )
1385
+ * } catch (error) {
1386
+ * console.error('Record validation failed:', error)
1387
+ * }
1388
+ * ```
1389
+ *
1390
+ * @public
1391
+ */
1392
+ validateRecord(store: Store<R>, record: R, phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests', recordBefore: R | null): R;
1393
+ /**
1394
+ * Gets all migrations that need to be applied to upgrade from a persisted schema
1395
+ * to the current schema version.
1396
+ *
1397
+ * This method compares the persisted schema with the current schema and determines
1398
+ * which migrations need to be applied to bring the data up to date. It handles
1399
+ * both regular migrations and retroactive migrations, and caches results for
1400
+ * performance.
1401
+ *
1402
+ * @param persistedSchema - The schema version that was previously persisted
1403
+ * @returns A Result containing the list of migrations to apply, or an error message
1404
+ *
1405
+ * @example
1406
+ * ```ts
1407
+ * const persistedSchema = {
1408
+ * schemaVersion: 2,
1409
+ * sequences: { 'com.draw.book': 1, 'com.draw.author': 0 }
1410
+ * }
1411
+ *
1412
+ * const migrationsResult = schema.getMigrationsSince(persistedSchema)
1413
+ * if (migrationsResult.ok) {
1414
+ * console.log('Migrations to apply:', migrationsResult.value.length)
1415
+ * // Apply each migration to bring data up to date
1416
+ * }
1417
+ * ```
1418
+ *
1419
+ * @public
1420
+ */
1421
+ getMigrationsSince(persistedSchema: SerializedSchema): Result<Migration[], string>;
1422
+ /**
1423
+ * Migrates a single persisted record to match the current schema version.
1424
+ *
1425
+ * This method applies the necessary migrations to transform a record from an
1426
+ * older (or newer) schema version to the current version. It supports both
1427
+ * forward ('up') and backward ('down') migrations.
1428
+ *
1429
+ * @param record - The record to migrate
1430
+ * @param persistedSchema - The schema version the record was persisted with
1431
+ * @param direction - Direction to migrate ('up' for newer, 'down' for older)
1432
+ * @returns A MigrationResult containing the migrated record or an error
1433
+ *
1434
+ * @example
1435
+ * ```ts
1436
+ * const oldRecord = { id: 'book:1', typeName: 'book', title: 'Old Title', publishDate: '2020-01-01' }
1437
+ * const oldSchema = { schemaVersion: 2, sequences: { 'com.draw.book': 1 } }
1438
+ *
1439
+ * const result = schema.migratePersistedRecord(oldRecord, oldSchema, 'up')
1440
+ * if (result.type === 'success') {
1441
+ * console.log('Migrated record:', result.value)
1442
+ * // Record now has publishedYear instead of publishDate
1443
+ * } else {
1444
+ * console.error('Migration failed:', result.reason)
1445
+ * }
1446
+ * ```
1447
+ *
1448
+ * @public
1449
+ */
1450
+ migratePersistedRecord(record: R, persistedSchema: SerializedSchema, direction?: 'up' | 'down'): MigrationResult<R>;
1451
+ migrateStorage(storage: SynchronousStorage<R>): void;
1452
+ /**
1453
+ * Migrates an entire store snapshot to match the current schema version.
1454
+ *
1455
+ * This method applies all necessary migrations to bring a persisted store
1456
+ * snapshot up to the current schema version. It handles both record-level
1457
+ * and store-level migrations, and can optionally mutate the input store
1458
+ * for performance.
1459
+ *
1460
+ * @param snapshot - The store snapshot containing data and schema information
1461
+ * @param opts - Options controlling migration behavior
1462
+ * - mutateInputStore - Whether to modify the input store directly (default: false)
1463
+ * @returns A MigrationResult containing the migrated store or an error
1464
+ *
1465
+ * @example
1466
+ * ```ts
1467
+ * const snapshot = {
1468
+ * schema: { schemaVersion: 2, sequences: { 'com.draw.book': 1 } },
1469
+ * store: {
1470
+ * 'book:1': { id: 'book:1', typeName: 'book', title: 'Old Book', publishDate: '2020-01-01' }
1471
+ * }
1472
+ * }
1473
+ *
1474
+ * const result = schema.migrateStoreSnapshot(snapshot)
1475
+ * if (result.type === 'success') {
1476
+ * console.log('Migrated store:', result.value)
1477
+ * // All records are now at current schema version
1478
+ * }
1479
+ * ```
1480
+ *
1481
+ * @public
1482
+ */
1483
+ migrateStoreSnapshot(snapshot: StoreSnapshot<R>, opts?: {
1484
+ mutateInputStore?: boolean;
1485
+ }): MigrationResult<SerializedStore<R>>;
1486
+ /**
1487
+ * Creates an integrity checker function for the given store.
1488
+ *
1489
+ * This method calls the createIntegrityChecker option if provided, allowing
1490
+ * custom integrity checking logic to be set up for the store. The integrity
1491
+ * checker is used to validate store consistency and catch data corruption.
1492
+ *
1493
+ * @param store - The store instance to create an integrity checker for
1494
+ * @returns An integrity checker function, or undefined if none is configured
1495
+ *
1496
+ * @internal
1497
+ */
1498
+ createIntegrityChecker(store: Store<R, P>): (() => void) | undefined;
1499
+ /**
1500
+ * Serializes the current schema to a SerializedSchemaV2 format.
1501
+ *
1502
+ * This method creates a serialized representation of the current schema,
1503
+ * capturing the latest version number for each migration sequence.
1504
+ * The result can be persisted and later used to determine what migrations
1505
+ * need to be applied when loading data.
1506
+ *
1507
+ * @returns A SerializedSchemaV2 object representing the current schema state
1508
+ *
1509
+ * @example
1510
+ * ```ts
1511
+ * const serialized = schema.serialize()
1512
+ * console.log(serialized)
1513
+ * // {
1514
+ * // schemaVersion: 2,
1515
+ * // sequences: {
1516
+ * // 'com.draw.book': 3,
1517
+ * // 'com.draw.author': 2
1518
+ * // }
1519
+ * // }
1520
+ *
1521
+ * // Store this with your data for future migrations
1522
+ * localStorage.setItem('schema', JSON.stringify(serialized))
1523
+ * ```
1524
+ *
1525
+ * @public
1526
+ */
1527
+ serialize(): SerializedSchemaV2;
1528
+ /**
1529
+ * Serializes a schema representing the earliest possible version.
1530
+ *
1531
+ * This method creates a serialized schema where all migration sequences
1532
+ * are set to version 0, representing the state before any migrations
1533
+ * have been applied. This is used in specific legacy scenarios.
1534
+ *
1535
+ * @returns A SerializedSchema with all sequences set to version 0
1536
+ *
1537
+ * @deprecated This is only here for legacy reasons, don't use it unless you have david's blessing!
1538
+ * @internal
1539
+ */
1540
+ serializeEarliestVersion(): SerializedSchema;
1541
+ /**
1542
+ * Gets the RecordType definition for a given type name.
1543
+ *
1544
+ * This method retrieves the RecordType associated with the specified
1545
+ * type name, which contains the record's validation, creation, and
1546
+ * other behavioral logic.
1547
+ *
1548
+ * @param typeName - The name of the record type to retrieve
1549
+ * @returns The RecordType definition for the specified type
1550
+ *
1551
+ * @throws Will throw an error if the record type does not exist
1552
+ *
1553
+ * @internal
1554
+ */
1555
+ getType(typeName: string): RecordType<R, any>;
1556
+ }
1557
+
1558
+ /**
1559
+ * Handler function called before a record is created in the store.
1560
+ * The handler receives the record to be created and can return a modified version.
1561
+ * Use this to validate, transform, or modify records before they are added to the store.
1562
+ *
1563
+ * @param record - The record about to be created
1564
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1565
+ * @returns The record to actually create (may be modified)
1566
+ *
1567
+ * @example
1568
+ * ```ts
1569
+ * const handler: StoreBeforeCreateHandler<MyRecord> = (record, source) => {
1570
+ * // Ensure all user-created records have a timestamp
1571
+ * if (source === 'user' && !record.createdAt) {
1572
+ * return { ...record, createdAt: Date.now() }
1573
+ * }
1574
+ * return record
1575
+ * }
1576
+ * ```
1577
+ *
1578
+ * @public
1579
+ */
1580
+ type StoreBeforeCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => R;
1581
+ /**
1582
+ * Handler function called after a record has been successfully created in the store.
1583
+ * Use this for side effects that should happen after record creation, such as updating
1584
+ * related records or triggering notifications.
1585
+ *
1586
+ * @param record - The record that was created
1587
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1588
+ *
1589
+ * @example
1590
+ * ```ts
1591
+ * const handler: StoreAfterCreateHandler<BookRecord> = (book, source) => {
1592
+ * if (source === 'user') {
1593
+ * console.log(`New book added: ${book.title}`)
1594
+ * updateAuthorBookCount(book.authorId)
1595
+ * }
1596
+ * }
1597
+ * ```
1598
+ *
1599
+ * @public
1600
+ */
1601
+ type StoreAfterCreateHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void;
1602
+ /**
1603
+ * Handler function called before a record is updated in the store.
1604
+ * The handler receives the current and new versions of the record and can return
1605
+ * a modified version or the original to prevent the change.
1606
+ *
1607
+ * @param prev - The current version of the record in the store
1608
+ * @param next - The proposed new version of the record
1609
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1610
+ * @returns The record version to actually store (may be modified or the original to block change)
1611
+ *
1612
+ * @example
1613
+ * ```ts
1614
+ * const handler: StoreBeforeChangeHandler<ShapeRecord> = (prev, next, source) => {
1615
+ * // Prevent shapes from being moved outside the canvas bounds
1616
+ * if (next.x < 0 || next.y < 0) {
1617
+ * return prev // Block the change
1618
+ * }
1619
+ * return next
1620
+ * }
1621
+ * ```
1622
+ *
1623
+ * @public
1624
+ */
1625
+ type StoreBeforeChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => R;
1626
+ /**
1627
+ * Handler function called after a record has been successfully updated in the store.
1628
+ * Use this for side effects that should happen after record changes, such as
1629
+ * updating related records or maintaining consistency constraints.
1630
+ *
1631
+ * @param prev - The previous version of the record
1632
+ * @param next - The new version of the record that was stored
1633
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1634
+ *
1635
+ * @example
1636
+ * ```ts
1637
+ * const handler: StoreAfterChangeHandler<ShapeRecord> = (prev, next, source) => {
1638
+ * // Update connected arrows when a shape moves
1639
+ * if (prev.x !== next.x || prev.y !== next.y) {
1640
+ * updateConnectedArrows(next.id)
1641
+ * }
1642
+ * }
1643
+ * ```
1644
+ *
1645
+ * @public
1646
+ */
1647
+ type StoreAfterChangeHandler<R extends UnknownRecord> = (prev: R, next: R, source: 'remote' | 'user') => void;
1648
+ /**
1649
+ * Handler function called before a record is deleted from the store.
1650
+ * The handler can return `false` to prevent the deletion from occurring.
1651
+ *
1652
+ * @param record - The record about to be deleted
1653
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1654
+ * @returns `false` to prevent deletion, `void` or any other value to allow it
1655
+ *
1656
+ * @example
1657
+ * ```ts
1658
+ * const handler: StoreBeforeDeleteHandler<BookRecord> = (book, source) => {
1659
+ * // Prevent deletion of books that are currently checked out
1660
+ * if (book.isCheckedOut) {
1661
+ * console.warn('Cannot delete checked out book')
1662
+ * return false
1663
+ * }
1664
+ * // Allow deletion for other books
1665
+ * }
1666
+ * ```
1667
+ *
1668
+ * @public
1669
+ */
1670
+ type StoreBeforeDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void | false;
1671
+ /**
1672
+ * Handler function called after a record has been successfully deleted from the store.
1673
+ * Use this for cleanup operations and maintaining referential integrity.
1674
+ *
1675
+ * @param record - The record that was deleted
1676
+ * @param source - Whether the change originated from 'user' interaction or 'remote' synchronization
1677
+ *
1678
+ * @example
1679
+ * ```ts
1680
+ * const handler: StoreAfterDeleteHandler<ShapeRecord> = (shape, source) => {
1681
+ * // Clean up arrows that were connected to this shape
1682
+ * const connectedArrows = findArrowsConnectedTo(shape.id)
1683
+ * store.remove(connectedArrows.map(arrow => arrow.id))
1684
+ * }
1685
+ * ```
1686
+ *
1687
+ * @public
1688
+ */
1689
+ type StoreAfterDeleteHandler<R extends UnknownRecord> = (record: R, source: 'remote' | 'user') => void;
1690
+ /**
1691
+ * Handler function called when a store operation (atomic transaction) completes.
1692
+ * This is useful for performing actions after a batch of changes has been applied,
1693
+ * such as triggering saves or sending notifications.
1694
+ *
1695
+ * @param source - Whether the operation originated from 'user' interaction or 'remote' synchronization
1696
+ *
1697
+ * @example
1698
+ * ```ts
1699
+ * const handler: StoreOperationCompleteHandler = (source) => {
1700
+ * if (source === 'user') {
1701
+ * // Auto-save after user operations complete
1702
+ * saveStoreSnapshot()
1703
+ * }
1704
+ * }
1705
+ * ```
1706
+ *
1707
+ * @public
1708
+ */
1709
+ type StoreOperationCompleteHandler = (source: 'remote' | 'user') => void;
1710
+ /**
1711
+ * The side effect manager (aka a "correct state enforcer") is responsible
1712
+ * for making sure that the store's state is always correct and consistent. This includes
1713
+ * things like: deleting a shape if its parent is deleted; unbinding
1714
+ * arrows when their binding target is deleted; maintaining referential integrity; etc.
1715
+ *
1716
+ * Side effects are organized into lifecycle hooks that run before and after
1717
+ * record operations (create, change, delete), allowing you to validate data,
1718
+ * transform records, and maintain business rules.
1719
+ *
1720
+ * @example
1721
+ * ```ts
1722
+ * const sideEffects = new StoreSideEffects(store)
1723
+ *
1724
+ * // Ensure arrows are deleted when their target shape is deleted
1725
+ * sideEffects.registerAfterDeleteHandler('shape', (shape) => {
1726
+ * const arrows = store.query.records('arrow', () => ({
1727
+ * toId: { eq: shape.id }
1728
+ * })).get()
1729
+ * store.remove(arrows.map(arrow => arrow.id))
1730
+ * })
1731
+ * ```
1732
+ *
1733
+ * @public
1734
+ */
1735
+ declare class StoreSideEffects<R extends UnknownRecord> {
1736
+ private readonly store;
1737
+ /**
1738
+ * Creates a new side effects manager for the given store.
1739
+ *
1740
+ * store - The store instance to manage side effects for
1741
+ */
1742
+ constructor(store: Store<R>);
1743
+ private _beforeCreateHandlers;
1744
+ private _afterCreateHandlers;
1745
+ private _beforeChangeHandlers;
1746
+ private _afterChangeHandlers;
1747
+ private _beforeDeleteHandlers;
1748
+ private _afterDeleteHandlers;
1749
+ private _operationCompleteHandlers;
1750
+ private _isEnabled;
1751
+ /**
1752
+ * Checks whether side effects are currently enabled.
1753
+ * When disabled, all side effect handlers are bypassed.
1754
+ *
1755
+ * @returns `true` if side effects are enabled, `false` otherwise
1756
+ * @internal
1757
+ */
1758
+ isEnabled(): boolean;
1759
+ /**
1760
+ * Enables or disables side effects processing.
1761
+ * When disabled, no side effect handlers will be called.
1762
+ *
1763
+ * @param enabled - Whether to enable or disable side effects
1764
+ * @internal
1765
+ */
1766
+ setIsEnabled(enabled: boolean): void;
1767
+ /**
1768
+ * Processes all registered 'before create' handlers for a record.
1769
+ * Handlers are called in registration order and can transform the record.
1770
+ *
1771
+ * @param record - The record about to be created
1772
+ * @param source - Whether the change originated from 'user' or 'remote'
1773
+ * @returns The potentially modified record to actually create
1774
+ * @internal
1775
+ */
1776
+ handleBeforeCreate(record: R, source: 'remote' | 'user'): R;
1777
+ /**
1778
+ * Processes all registered 'after create' handlers for a record.
1779
+ * Handlers are called in registration order after the record is created.
1780
+ *
1781
+ * @param record - The record that was created
1782
+ * @param source - Whether the change originated from 'user' or 'remote'
1783
+ * @internal
1784
+ */
1785
+ handleAfterCreate(record: R, source: 'remote' | 'user'): void;
1786
+ /**
1787
+ * Processes all registered 'before change' handlers for a record.
1788
+ * Handlers are called in registration order and can modify or block the change.
1789
+ *
1790
+ * @param prev - The current version of the record
1791
+ * @param next - The proposed new version of the record
1792
+ * @param source - Whether the change originated from 'user' or 'remote'
1793
+ * @returns The potentially modified record to actually store
1794
+ * @internal
1795
+ */
1796
+ handleBeforeChange(prev: R, next: R, source: 'remote' | 'user'): R;
1797
+ /**
1798
+ * Processes all registered 'after change' handlers for a record.
1799
+ * Handlers are called in registration order after the record is updated.
1800
+ *
1801
+ * @param prev - The previous version of the record
1802
+ * @param next - The new version of the record that was stored
1803
+ * @param source - Whether the change originated from 'user' or 'remote'
1804
+ * @internal
1805
+ */
1806
+ handleAfterChange(prev: R, next: R, source: 'remote' | 'user'): void;
1807
+ /**
1808
+ * Processes all registered 'before delete' handlers for a record.
1809
+ * If any handler returns `false`, the deletion is prevented.
1810
+ *
1811
+ * @param record - The record about to be deleted
1812
+ * @param source - Whether the change originated from 'user' or 'remote'
1813
+ * @returns `true` to allow deletion, `false` to prevent it
1814
+ * @internal
1815
+ */
1816
+ handleBeforeDelete(record: R, source: 'remote' | 'user'): boolean;
1817
+ /**
1818
+ * Processes all registered 'after delete' handlers for a record.
1819
+ * Handlers are called in registration order after the record is deleted.
1820
+ *
1821
+ * @param record - The record that was deleted
1822
+ * @param source - Whether the change originated from 'user' or 'remote'
1823
+ * @internal
1824
+ */
1825
+ handleAfterDelete(record: R, source: 'remote' | 'user'): void;
1826
+ /**
1827
+ * Processes all registered operation complete handlers.
1828
+ * Called after an atomic store operation finishes.
1829
+ *
1830
+ * @param source - Whether the operation originated from 'user' or 'remote'
1831
+ * @internal
1832
+ */
1833
+ handleOperationComplete(source: 'remote' | 'user'): void;
1834
+ /**
1835
+ * Internal helper for registering multiple side effect handlers at once and keeping them organized.
1836
+ * This provides a convenient way to register handlers for multiple record types and lifecycle events
1837
+ * in a single call, returning a single cleanup function.
1838
+ *
1839
+ * @param handlersByType - An object mapping record type names to their respective handlers
1840
+ * @returns A function that removes all registered handlers when called
1841
+ *
1842
+ * @example
1843
+ * ```ts
1844
+ * const cleanup = sideEffects.register({
1845
+ * shape: {
1846
+ * afterDelete: (shape) => console.log('Shape deleted:', shape.id),
1847
+ * beforeChange: (prev, next) => ({ ...next, lastModified: Date.now() })
1848
+ * },
1849
+ * arrow: {
1850
+ * afterCreate: (arrow) => updateConnectedShapes(arrow)
1851
+ * }
1852
+ * })
1853
+ *
1854
+ * // Later, remove all handlers
1855
+ * cleanup()
1856
+ * ```
1857
+ *
1858
+ * @internal
1859
+ */
1860
+ register(handlersByType: {
1861
+ [T in R as T['typeName']]?: {
1862
+ beforeCreate?: StoreBeforeCreateHandler<T>;
1863
+ afterCreate?: StoreAfterCreateHandler<T>;
1864
+ beforeChange?: StoreBeforeChangeHandler<T>;
1865
+ afterChange?: StoreAfterChangeHandler<T>;
1866
+ beforeDelete?: StoreBeforeDeleteHandler<T>;
1867
+ afterDelete?: StoreAfterDeleteHandler<T>;
1868
+ };
1869
+ }): () => void;
1870
+ /**
1871
+ * Register a handler to be called before a record of a certain type is created. Return a
1872
+ * modified record from the handler to change the record that will be created.
1873
+ *
1874
+ * Use this handle only to modify the creation of the record itself. If you want to trigger a
1875
+ * side-effect on a different record (for example, moving one shape when another is created),
1876
+ * use {@link StoreSideEffects.registerAfterCreateHandler} instead.
1877
+ *
1878
+ * @example
1879
+ * ```ts
1880
+ * editor.sideEffects.registerBeforeCreateHandler('shape', (shape, source) => {
1881
+ * // only modify shapes created by the user
1882
+ * if (source !== 'user') return shape
1883
+ *
1884
+ * //by default, arrow shapes have no label. Let's make sure they always have a label.
1885
+ * if (shape.type === 'arrow') {
1886
+ * return {...shape, props: {...shape.props, text: 'an arrow'}}
1887
+ * }
1888
+ *
1889
+ * // other shapes get returned unmodified
1890
+ * return shape
1891
+ * })
1892
+ * ```
1893
+ *
1894
+ * @param typeName - The type of record to listen for
1895
+ * @param handler - The handler to call
1896
+ *
1897
+ * @returns A callback that removes the handler.
1898
+ */
1899
+ registerBeforeCreateHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeCreateHandler<R & {
1900
+ typeName: T;
1901
+ }>): () => void;
1902
+ /**
1903
+ * Register a handler to be called after a record is created. This is useful for side-effects
1904
+ * that would update _other_ records. If you want to modify the record being created use
1905
+ * {@link StoreSideEffects.registerBeforeCreateHandler} instead.
1906
+ *
1907
+ * @example
1908
+ * ```ts
1909
+ * editor.sideEffects.registerAfterCreateHandler('page', (page, source) => {
1910
+ * // Automatically create a shape when a page is created
1911
+ * editor.createShape({
1912
+ * id: createShapeId(),
1913
+ * type: 'text',
1914
+ * props: { richText: toRichText(page.name) },
1915
+ * })
1916
+ * })
1917
+ * ```
1918
+ *
1919
+ * @param typeName - The type of record to listen for
1920
+ * @param handler - The handler to call
1921
+ *
1922
+ * @returns A callback that removes the handler.
1923
+ */
1924
+ registerAfterCreateHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterCreateHandler<R & {
1925
+ typeName: T;
1926
+ }>): () => void;
1927
+ /**
1928
+ * Register a handler to be called before a record is changed. The handler is given the old and
1929
+ * new record - you can return a modified record to apply a different update, or the old record
1930
+ * to block the update entirely.
1931
+ *
1932
+ * Use this handler only for intercepting updates to the record itself. If you want to update
1933
+ * other records in response to a change, use
1934
+ * {@link StoreSideEffects.registerAfterChangeHandler} instead.
1935
+ *
1936
+ * @example
1937
+ * ```ts
1938
+ * editor.sideEffects.registerBeforeChangeHandler('shape', (prev, next, source) => {
1939
+ * if (next.isLocked && !prev.isLocked) {
1940
+ * // prevent shapes from ever being locked:
1941
+ * return prev
1942
+ * }
1943
+ * // other types of change are allowed
1944
+ * return next
1945
+ * })
1946
+ * ```
1947
+ *
1948
+ * @param typeName - The type of record to listen for
1949
+ * @param handler - The handler to call
1950
+ *
1951
+ * @returns A callback that removes the handler.
1952
+ */
1953
+ registerBeforeChangeHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeChangeHandler<R & {
1954
+ typeName: T;
1955
+ }>): () => void;
1956
+ /**
1957
+ * Register a handler to be called after a record is changed. This is useful for side-effects
1958
+ * that would update _other_ records - if you want to modify the record being changed, use
1959
+ * {@link StoreSideEffects.registerBeforeChangeHandler} instead.
1960
+ *
1961
+ * @example
1962
+ * ```ts
1963
+ * editor.sideEffects.registerAfterChangeHandler('shape', (prev, next, source) => {
1964
+ * if (next.props.color === 'red') {
1965
+ * // there can only be one red shape at a time:
1966
+ * const otherRedShapes = editor.getCurrentPageShapes().filter(s => s.props.color === 'red' && s.id !== next.id)
1967
+ * editor.updateShapes(otherRedShapes.map(s => ({...s, props: {...s.props, color: 'blue'}})))
1968
+ * }
1969
+ * })
1970
+ * ```
1971
+ *
1972
+ * @param typeName - The type of record to listen for
1973
+ * @param handler - The handler to call
1974
+ *
1975
+ * @returns A callback that removes the handler.
1976
+ */
1977
+ registerAfterChangeHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterChangeHandler<R & {
1978
+ typeName: T;
1979
+ }>): () => void;
1980
+ /**
1981
+ * Register a handler to be called before a record is deleted. The handler can return `false` to
1982
+ * prevent the deletion.
1983
+ *
1984
+ * Use this handler only for intercepting deletions of the record itself. If you want to do
1985
+ * something to other records in response to a deletion, use
1986
+ * {@link StoreSideEffects.registerAfterDeleteHandler} instead.
1987
+ *
1988
+ * @example
1989
+ * ```ts
1990
+ * editor.sideEffects.registerBeforeDeleteHandler('shape', (shape, source) => {
1991
+ * if (shape.props.color === 'red') {
1992
+ * // prevent red shapes from being deleted
1993
+ * return false
1994
+ * }
1995
+ * })
1996
+ * ```
1997
+ *
1998
+ * @param typeName - The type of record to listen for
1999
+ * @param handler - The handler to call
2000
+ *
2001
+ * @returns A callback that removes the handler.
2002
+ */
2003
+ registerBeforeDeleteHandler<T extends R['typeName']>(typeName: T, handler: StoreBeforeDeleteHandler<R & {
2004
+ typeName: T;
2005
+ }>): () => void;
2006
+ /**
2007
+ * Register a handler to be called after a record is deleted. This is useful for side-effects
2008
+ * that would update _other_ records - if you want to block the deletion of the record itself,
2009
+ * use {@link StoreSideEffects.registerBeforeDeleteHandler} instead.
2010
+ *
2011
+ * @example
2012
+ * ```ts
2013
+ * editor.sideEffects.registerAfterDeleteHandler('shape', (shape, source) => {
2014
+ * // if the last shape in a frame is deleted, delete the frame too:
2015
+ * const parentFrame = editor.getShape(shape.parentId)
2016
+ * if (!parentFrame || parentFrame.type !== 'frame') return
2017
+ *
2018
+ * const siblings = editor.getSortedChildIdsForParent(parentFrame)
2019
+ * if (siblings.length === 0) {
2020
+ * editor.deleteShape(parentFrame.id)
2021
+ * }
2022
+ * })
2023
+ * ```
2024
+ *
2025
+ * @param typeName - The type of record to listen for
2026
+ * @param handler - The handler to call
2027
+ *
2028
+ * @returns A callback that removes the handler.
2029
+ */
2030
+ registerAfterDeleteHandler<T extends R['typeName']>(typeName: T, handler: StoreAfterDeleteHandler<R & {
2031
+ typeName: T;
2032
+ }>): () => void;
2033
+ /**
2034
+ * Register a handler to be called when a store completes an atomic operation.
2035
+ *
2036
+ * @example
2037
+ * ```ts
2038
+ * let count = 0
2039
+ *
2040
+ * editor.sideEffects.registerOperationCompleteHandler(() => count++)
2041
+ *
2042
+ * editor.selectAll()
2043
+ * expect(count).toBe(1)
2044
+ *
2045
+ * editor.store.atomic(() => {
2046
+ * editor.selectNone()
2047
+ * editor.selectAll()
2048
+ * })
2049
+ *
2050
+ * expect(count).toBe(2)
2051
+ * ```
2052
+ *
2053
+ * @param handler - The handler to call
2054
+ *
2055
+ * @returns A callback that removes the handler.
2056
+ *
2057
+ * @public
2058
+ */
2059
+ registerOperationCompleteHandler(handler: StoreOperationCompleteHandler): () => void;
2060
+ }
2061
+
2062
+ /**
2063
+ * Extracts the record type from a record ID type.
2064
+ *
2065
+ * @example
2066
+ * ```ts
2067
+ * type BookId = RecordId<Book>
2068
+ * type BookType = RecordFromId<BookId> // Book
2069
+ * ```
2070
+ *
2071
+ * @public
2072
+ */
2073
+ type RecordFromId<K extends RecordId<UnknownRecord>> = K extends RecordId<infer R> ? R : never;
2074
+ /**
2075
+ * A diff describing the changes to a collection.
2076
+ *
2077
+ * @example
2078
+ * ```ts
2079
+ * const diff: CollectionDiff<string> = {
2080
+ * added: new Set(['newItem']),
2081
+ * removed: new Set(['oldItem'])
2082
+ * }
2083
+ * ```
2084
+ *
2085
+ * @public
2086
+ */
2087
+ interface CollectionDiff<T> {
2088
+ /** Items that were added to the collection */
2089
+ added?: Set<T>;
2090
+ /** Items that were removed from the collection */
2091
+ removed?: Set<T>;
2092
+ }
2093
+ /**
2094
+ * The source of a change to the store.
2095
+ * - `'user'` - Changes originating from local user actions
2096
+ * - `'remote'` - Changes originating from remote synchronization
2097
+ *
2098
+ * @public
2099
+ */
2100
+ type ChangeSource = 'user' | 'remote';
2101
+ /**
2102
+ * Filters for store listeners to control which changes trigger the listener.
2103
+ *
2104
+ * @example
2105
+ * ```ts
2106
+ * const filters: StoreListenerFilters = {
2107
+ * source: 'user', // Only listen to user changes
2108
+ * scope: 'document' // Only listen to document-scoped records
2109
+ * }
2110
+ * ```
2111
+ *
2112
+ * @public
2113
+ */
2114
+ interface StoreListenerFilters {
2115
+ /** Filter by the source of changes */
2116
+ source: ChangeSource | 'all';
2117
+ /** Filter by the scope of records */
2118
+ scope: RecordScope | 'all';
2119
+ }
2120
+ /**
2121
+ * An entry containing changes that originated either by user actions or remote changes.
2122
+ * History entries are used to track and replay changes to the store.
2123
+ *
2124
+ * @example
2125
+ * ```ts
2126
+ * const entry: HistoryEntry<Book> = {
2127
+ * changes: {
2128
+ * added: { 'book:123': bookRecord },
2129
+ * updated: {},
2130
+ * removed: {}
2131
+ * },
2132
+ * source: 'user'
2133
+ * }
2134
+ * ```
2135
+ *
2136
+ * @public
2137
+ */
2138
+ interface HistoryEntry<R extends UnknownRecord = UnknownRecord> {
2139
+ /** The changes that occurred in this history entry */
2140
+ changes: RecordsDiff<R>;
2141
+ /** The source of these changes */
2142
+ source: ChangeSource;
2143
+ }
2144
+ /**
2145
+ * A function that will be called when the history changes.
2146
+ *
2147
+ * @example
2148
+ * ```ts
2149
+ * const listener: StoreListener<Book> = (entry) => {
2150
+ * console.log('Changes:', entry.changes)
2151
+ * console.log('Source:', entry.source)
2152
+ * }
2153
+ *
2154
+ * store.listen(listener)
2155
+ * ```
2156
+ *
2157
+ * @param entry - The history entry containing the changes
2158
+ *
2159
+ * @public
2160
+ */
2161
+ type StoreListener<R extends UnknownRecord> = (entry: HistoryEntry<R>) => void;
2162
+ /**
2163
+ * A computed cache that stores derived data for records.
2164
+ * The cache automatically updates when underlying records change and cleans up when records are deleted.
2165
+ *
2166
+ * @example
2167
+ * ```ts
2168
+ * const expensiveCache = store.createComputedCache(
2169
+ * 'expensive',
2170
+ * (book: Book) => performExpensiveCalculation(book)
2171
+ * )
2172
+ *
2173
+ * const result = expensiveCache.get(bookId)
2174
+ * ```
2175
+ *
2176
+ * @public
2177
+ */
2178
+ interface ComputedCache<Data, R extends UnknownRecord> {
2179
+ /**
2180
+ * Get the cached data for a record by its ID.
2181
+ *
2182
+ * @param id - The ID of the record
2183
+ * @returns The cached data or undefined if the record doesn't exist
2184
+ */
2185
+ get(id: IdOf<R>): Data | undefined;
2186
+ }
2187
+ /**
2188
+ * Options for creating a computed cache.
2189
+ *
2190
+ * @example
2191
+ * ```ts
2192
+ * const options: CreateComputedCacheOpts<string[], Book> = {
2193
+ * areRecordsEqual: (a, b) => a.title === b.title,
2194
+ * areResultsEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b)
2195
+ * }
2196
+ * ```
2197
+ *
2198
+ * @public
2199
+ */
2200
+ interface CreateComputedCacheOpts<Data, R extends UnknownRecord> {
2201
+ /** Custom equality function for comparing records */
2202
+ areRecordsEqual?(a: R, b: R): boolean;
2203
+ /** Custom equality function for comparing results */
2204
+ areResultsEqual?(a: Data, b: Data): boolean;
2205
+ }
2206
+ /**
2207
+ * A serialized snapshot of the record store's values.
2208
+ * This is a plain JavaScript object that can be saved to storage or transmitted over the network.
2209
+ *
2210
+ * @example
2211
+ * ```ts
2212
+ * const serialized: SerializedStore<Book> = {
2213
+ * 'book:123': { id: 'book:123', typeName: 'book', title: 'The Lathe of Heaven' },
2214
+ * 'book:456': { id: 'book:456', typeName: 'book', title: 'The Left Hand of Darkness' }
2215
+ * }
2216
+ * ```
2217
+ *
2218
+ * @public
2219
+ */
2220
+ type SerializedStore<R extends UnknownRecord> = Record<IdOf<R>, R>;
2221
+ /**
2222
+ * A snapshot of the store including both data and schema information.
2223
+ * This enables proper migration when loading data from different schema versions.
2224
+ *
2225
+ * @example
2226
+ * ```ts
2227
+ * const snapshot = store.getStoreSnapshot()
2228
+ * // Later...
2229
+ * store.loadStoreSnapshot(snapshot)
2230
+ * ```
2231
+ *
2232
+ * @public
2233
+ */
2234
+ interface StoreSnapshot<R extends UnknownRecord> {
2235
+ /** The serialized store data */
2236
+ store: SerializedStore<R>;
2237
+ /** The serialized schema information */
2238
+ schema: SerializedSchema;
2239
+ }
2240
+ /**
2241
+ * A validator for store records that ensures data integrity.
2242
+ * Validators are called when records are created or updated.
2243
+ *
2244
+ * @example
2245
+ * ```ts
2246
+ * const bookValidator: StoreValidator<Book> = {
2247
+ * validate(record: unknown): Book {
2248
+ * // Validate and return the record
2249
+ * if (typeof record !== 'object' || !record.title) {
2250
+ * throw new Error('Invalid book')
2251
+ * }
2252
+ * return record as Book
2253
+ * }
2254
+ * }
2255
+ * ```
2256
+ *
2257
+ * @public
2258
+ */
2259
+ interface StoreValidator<R extends UnknownRecord> {
2260
+ /**
2261
+ * Validate a record.
2262
+ *
2263
+ * @param record - The record to validate
2264
+ * @returns The validated record
2265
+ * @throws When validation fails
2266
+ */
2267
+ validate(record: unknown): R;
2268
+ /**
2269
+ * Validate a record using a known good version for reference.
2270
+ *
2271
+ * @param knownGoodVersion - A known valid version of the record
2272
+ * @param record - The record to validate
2273
+ * @returns The validated record
2274
+ */
2275
+ validateUsingKnownGoodVersion?(knownGoodVersion: R, record: unknown): R;
2276
+ }
2277
+ /**
2278
+ * A map of validators for each record type in the store.
2279
+ *
2280
+ * @example
2281
+ * ```ts
2282
+ * const validators: StoreValidators<Book | Author> = {
2283
+ * book: bookValidator,
2284
+ * author: authorValidator
2285
+ * }
2286
+ * ```
2287
+ *
2288
+ * @public
2289
+ */
2290
+ type StoreValidators<R extends UnknownRecord> = {
2291
+ [K in R['typeName']]: StoreValidator<Extract<R, {
2292
+ typeName: K;
2293
+ }>>;
2294
+ };
2295
+ /**
2296
+ * Information about an error that occurred in the store.
2297
+ *
2298
+ * @example
2299
+ * ```ts
2300
+ * const error: StoreError = {
2301
+ * error: new Error('Validation failed'),
2302
+ * phase: 'updateRecord',
2303
+ * recordBefore: oldRecord,
2304
+ * recordAfter: newRecord,
2305
+ * isExistingValidationIssue: false
2306
+ * }
2307
+ * ```
2308
+ *
2309
+ * @public
2310
+ */
2311
+ interface StoreError {
2312
+ /** The error that occurred */
2313
+ error: Error;
2314
+ /** The phase during which the error occurred */
2315
+ phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests';
2316
+ /** The record state before the operation (if applicable) */
2317
+ recordBefore?: unknown;
2318
+ /** The record state after the operation */
2319
+ recordAfter: unknown;
2320
+ /** Whether this is an existing validation issue */
2321
+ isExistingValidationIssue: boolean;
2322
+ }
2323
+ /**
2324
+ * Extract the record type from a Store type.
2325
+ * Used internally for type inference.
2326
+ *
2327
+ * @internal
2328
+ */
2329
+ type StoreRecord<S extends Store<any>> = S extends Store<infer R> ? R : never;
2330
+ /**
2331
+ * A reactive store that manages collections of typed records.
2332
+ *
2333
+ * The Store is the central container for your application's data, providing:
2334
+ * - Reactive state management with automatic updates
2335
+ * - Type-safe record operations
2336
+ * - History tracking and change notifications
2337
+ * - Schema validation and migrations
2338
+ * - Side effects and business logic hooks
2339
+ * - Efficient querying and indexing
2340
+ *
2341
+ * @example
2342
+ * ```ts
2343
+ * // Create a store with schema
2344
+ * const schema = StoreSchema.create({
2345
+ * book: Book,
2346
+ * author: Author
2347
+ * })
2348
+ *
2349
+ * const store = new Store({
2350
+ * schema,
2351
+ * props: {}
2352
+ * })
2353
+ *
2354
+ * // Add records
2355
+ * const book = Book.create({ title: 'The Lathe of Heaven', author: 'Le Guin' })
2356
+ * store.put([book])
2357
+ *
2358
+ * // Listen to changes
2359
+ * store.listen((entry) => {
2360
+ * console.log('Changes:', entry.changes)
2361
+ * })
2362
+ * ```
2363
+ *
2364
+ * @public
2365
+ */
2366
+ declare class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
2367
+ /**
2368
+ * The unique identifier of the store instance.
2369
+ *
2370
+ * @public
2371
+ */
2372
+ readonly id: string;
2373
+ /**
2374
+ * An AtomMap containing the stores records.
2375
+ *
2376
+ * @internal
2377
+ * @readonly
2378
+ */
2379
+ private readonly records;
2380
+ /**
2381
+ * An atom containing the store's history.
2382
+ *
2383
+ * @public
2384
+ * @readonly
2385
+ */
2386
+ readonly history: Atom<number, RecordsDiff<R>>;
2387
+ /**
2388
+ * Reactive queries and indexes for efficiently accessing store data.
2389
+ * Provides methods for filtering, indexing, and subscribing to subsets of records.
2390
+ *
2391
+ * @example
2392
+ * ```ts
2393
+ * // Create an index by a property
2394
+ * const booksByAuthor = store.query.index('book', 'author')
2395
+ *
2396
+ * // Get records matching criteria
2397
+ * const inStockBooks = store.query.records('book', () => ({
2398
+ * inStock: { eq: true }
2399
+ * }))
2400
+ * ```
2401
+ *
2402
+ * @public
2403
+ * @readonly
2404
+ */
2405
+ readonly query: StoreQueries<R>;
2406
+ /**
2407
+ * A set containing listeners that have been added to this store.
2408
+ *
2409
+ * @internal
2410
+ */
2411
+ private listeners;
2412
+ /**
2413
+ * An array of history entries that have not yet been flushed.
2414
+ *
2415
+ * @internal
2416
+ */
2417
+ private historyAccumulator;
2418
+ /**
2419
+ * A reactor that responds to changes to the history by squashing the accumulated history and
2420
+ * notifying listeners of the changes.
2421
+ *
2422
+ * @internal
2423
+ */
2424
+ private historyReactor;
2425
+ /**
2426
+ * Function to dispose of any in-flight timeouts.
2427
+ *
2428
+ * @internal
2429
+ */
2430
+ private cancelHistoryReactor;
2431
+ /**
2432
+ * The schema that defines the structure and validation rules for records in this store.
2433
+ *
2434
+ * @public
2435
+ */
2436
+ readonly schema: StoreSchema<R, Props>;
2437
+ /**
2438
+ * Custom properties associated with this store instance.
2439
+ *
2440
+ * @public
2441
+ */
2442
+ readonly props: Props;
2443
+ /**
2444
+ * A mapping of record scopes to the set of record type names that belong to each scope.
2445
+ * Used to filter records by their persistence and synchronization behavior.
2446
+ *
2447
+ * @public
2448
+ */
2449
+ readonly scopedTypes: {
2450
+ readonly [K in RecordScope]: ReadonlySet<R['typeName']>;
2451
+ };
2452
+ /**
2453
+ * Side effects manager that handles lifecycle events for record operations.
2454
+ * Allows registration of callbacks for create, update, delete, and validation events.
2455
+ *
2456
+ * @example
2457
+ * ```ts
2458
+ * store.sideEffects.registerAfterCreateHandler('book', (book) => {
2459
+ * console.log('Book created:', book.title)
2460
+ * })
2461
+ * ```
2462
+ *
2463
+ * @public
2464
+ */
2465
+ readonly sideEffects: StoreSideEffects<R>;
2466
+ /**
2467
+ * Creates a new Store instance.
2468
+ *
2469
+ * @example
2470
+ * ```ts
2471
+ * const store = new Store({
2472
+ * schema: StoreSchema.create({ book: Book }),
2473
+ * props: { appName: 'MyLibrary' },
2474
+ * initialData: savedData
2475
+ * })
2476
+ * ```
2477
+ *
2478
+ * @param config - Configuration object for the store
2479
+ */
2480
+ constructor(config: {
2481
+ /** Optional unique identifier for the store */
2482
+ id?: string;
2483
+ /** The store's initial data to populate on creation */
2484
+ initialData?: SerializedStore<R>;
2485
+ /** The schema defining record types, validation, and migrations */
2486
+ schema: StoreSchema<R, Props>;
2487
+ /** Custom properties for the store instance */
2488
+ props: Props;
2489
+ });
2490
+ _flushHistory(): void;
2491
+ dispose(): void;
2492
+ /**
2493
+ * Filters out non-document changes from a diff. Returns null if there are no changes left.
2494
+ * @param change - the records diff
2495
+ * @param scope - the records scope
2496
+ * @returns
2497
+ */
2498
+ filterChangesByScope(change: RecordsDiff<R>, scope: RecordScope): {
2499
+ added: { [K in IdOf<R>]: R; };
2500
+ updated: { [K_1 in IdOf<R>]: [from: R, to: R]; };
2501
+ removed: { [K in IdOf<R>]: R; };
2502
+ } | null;
2503
+ /**
2504
+ * Update the history with a diff of changes.
2505
+ *
2506
+ * @param changes - The changes to add to the history.
2507
+ */
2508
+ private updateHistory;
2509
+ validate(phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests'): void;
2510
+ /**
2511
+ * Add or update records in the store. If a record with the same ID already exists, it will be updated.
2512
+ * Otherwise, a new record will be created.
2513
+ *
2514
+ * @example
2515
+ * ```ts
2516
+ * // Add new records
2517
+ * const book = Book.create({ title: 'Lathe Of Heaven', author: 'Le Guin' })
2518
+ * store.put([book])
2519
+ *
2520
+ * // Update existing record
2521
+ * store.put([{ ...book, title: 'The Lathe of Heaven' }])
2522
+ * ```
2523
+ *
2524
+ * @param records - The records to add or update
2525
+ * @param phaseOverride - Override the validation phase (used internally)
2526
+ * @public
2527
+ */
2528
+ put(records: R[], phaseOverride?: 'initialize'): void;
2529
+ /**
2530
+ * Remove records from the store by their IDs.
2531
+ *
2532
+ * @example
2533
+ * ```ts
2534
+ * // Remove a single record
2535
+ * store.remove([book.id])
2536
+ *
2537
+ * // Remove multiple records
2538
+ * store.remove([book1.id, book2.id, book3.id])
2539
+ * ```
2540
+ *
2541
+ * @param ids - The IDs of the records to remove
2542
+ * @public
2543
+ */
2544
+ remove(ids: IdOf<R>[]): void;
2545
+ /**
2546
+ * Get a record by its ID. This creates a reactive subscription to the record.
2547
+ *
2548
+ * @example
2549
+ * ```ts
2550
+ * const book = store.get(bookId)
2551
+ * if (book) {
2552
+ * console.log(book.title)
2553
+ * }
2554
+ * ```
2555
+ *
2556
+ * @param id - The ID of the record to get
2557
+ * @returns The record if it exists, undefined otherwise
2558
+ * @public
2559
+ */
2560
+ get<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined;
2561
+ /**
2562
+ * Get a record by its ID without creating a reactive subscription.
2563
+ * Use this when you need to access a record but don't want reactive updates.
2564
+ *
2565
+ * @example
2566
+ * ```ts
2567
+ * // Won't trigger reactive updates when this record changes
2568
+ * const book = store.unsafeGetWithoutCapture(bookId)
2569
+ * ```
2570
+ *
2571
+ * @param id - The ID of the record to get
2572
+ * @returns The record if it exists, undefined otherwise
2573
+ * @public
2574
+ */
2575
+ unsafeGetWithoutCapture<K extends IdOf<R>>(id: K): RecordFromId<K> | undefined;
2576
+ /**
2577
+ * Serialize the store's records to a plain JavaScript object.
2578
+ * Only includes records matching the specified scope.
2579
+ *
2580
+ * @example
2581
+ * ```ts
2582
+ * // Serialize only document records (default)
2583
+ * const documentData = store.serialize('document')
2584
+ *
2585
+ * // Serialize all records
2586
+ * const allData = store.serialize('all')
2587
+ * ```
2588
+ *
2589
+ * @param scope - The scope of records to serialize. Defaults to 'document'
2590
+ * @returns The serialized store data
2591
+ * @public
2592
+ */
2593
+ serialize(scope?: RecordScope | 'all'): SerializedStore<R>;
2594
+ /**
2595
+ * Get a serialized snapshot of the store and its schema.
2596
+ * This includes both the data and schema information needed for proper migration.
2597
+ *
2598
+ * @example
2599
+ * ```ts
2600
+ * const snapshot = store.getStoreSnapshot()
2601
+ * localStorage.setItem('myApp', JSON.stringify(snapshot))
2602
+ *
2603
+ * // Later...
2604
+ * const saved = JSON.parse(localStorage.getItem('myApp'))
2605
+ * store.loadStoreSnapshot(saved)
2606
+ * ```
2607
+ *
2608
+ * @param scope - The scope of records to serialize. Defaults to 'document'
2609
+ * @returns A snapshot containing both store data and schema information
2610
+ * @public
2611
+ */
2612
+ getStoreSnapshot(scope?: RecordScope | 'all'): StoreSnapshot<R>;
2613
+ /**
2614
+ * Migrate a serialized snapshot to the current schema version.
2615
+ * This applies any necessary migrations to bring old data up to date.
2616
+ *
2617
+ * @example
2618
+ * ```ts
2619
+ * const oldSnapshot = JSON.parse(localStorage.getItem('myApp'))
2620
+ * const migratedSnapshot = store.migrateSnapshot(oldSnapshot)
2621
+ * ```
2622
+ *
2623
+ * @param snapshot - The snapshot to migrate
2624
+ * @returns The migrated snapshot with current schema version
2625
+ * @throws Error if migration fails
2626
+ * @public
2627
+ */
2628
+ migrateSnapshot(snapshot: StoreSnapshot<R>): StoreSnapshot<R>;
2629
+ /**
2630
+ * Load a serialized snapshot into the store, replacing all current data.
2631
+ * The snapshot will be automatically migrated to the current schema version if needed.
2632
+ *
2633
+ * @example
2634
+ * ```ts
2635
+ * const snapshot = JSON.parse(localStorage.getItem('myApp'))
2636
+ * store.loadStoreSnapshot(snapshot)
2637
+ * ```
2638
+ *
2639
+ * @param snapshot - The snapshot to load
2640
+ * @throws Error if migration fails or snapshot is invalid
2641
+ * @public
2642
+ */
2643
+ loadStoreSnapshot(snapshot: StoreSnapshot<R>): void;
2644
+ /**
2645
+ * Get an array of all records in the store.
2646
+ *
2647
+ * @example
2648
+ * ```ts
2649
+ * const allRecords = store.allRecords()
2650
+ * const books = allRecords.filter(r => r.typeName === 'book')
2651
+ * ```
2652
+ *
2653
+ * @returns An array containing all records in the store
2654
+ * @public
2655
+ */
2656
+ allRecords(): R[];
2657
+ /**
2658
+ * Remove all records from the store.
2659
+ *
2660
+ * @example
2661
+ * ```ts
2662
+ * store.clear()
2663
+ * console.log(store.allRecords().length) // 0
2664
+ * ```
2665
+ *
2666
+ * @public
2667
+ */
2668
+ clear(): void;
2669
+ /**
2670
+ * Update a single record using an updater function. To update multiple records at once,
2671
+ * use the `update` method of the `TypedStore` class.
2672
+ *
2673
+ * @example
2674
+ * ```ts
2675
+ * store.update(book.id, (book) => ({
2676
+ * ...book,
2677
+ * title: 'Updated Title'
2678
+ * }))
2679
+ * ```
2680
+ *
2681
+ * @param id - The ID of the record to update
2682
+ * @param updater - A function that receives the current record and returns the updated record
2683
+ * @public
2684
+ */
2685
+ update<K extends IdOf<R>>(id: K, updater: (record: RecordFromId<K>) => RecordFromId<K>): void;
2686
+ /**
2687
+ * Check whether a record with the given ID exists in the store.
2688
+ *
2689
+ * @example
2690
+ * ```ts
2691
+ * if (store.has(bookId)) {
2692
+ * console.log('Book exists!')
2693
+ * }
2694
+ * ```
2695
+ *
2696
+ * @param id - The ID of the record to check
2697
+ * @returns True if the record exists, false otherwise
2698
+ * @public
2699
+ */
2700
+ has<K extends IdOf<R>>(id: K): boolean;
2701
+ /**
2702
+ * Add a listener that will be called when the store changes.
2703
+ * Returns a function to remove the listener.
2704
+ *
2705
+ * @example
2706
+ * ```ts
2707
+ * const removeListener = store.listen((entry) => {
2708
+ * console.log('Changes:', entry.changes)
2709
+ * console.log('Source:', entry.source)
2710
+ * })
2711
+ *
2712
+ * // Listen only to user changes to document records
2713
+ * const removeDocumentListener = store.listen(
2714
+ * (entry) => console.log('Document changed:', entry),
2715
+ * { source: 'user', scope: 'document' }
2716
+ * )
2717
+ *
2718
+ * // Later, remove the listener
2719
+ * removeListener()
2720
+ * ```
2721
+ *
2722
+ * @param onHistory - The listener function to call when changes occur
2723
+ * @param filters - Optional filters to control when the listener is called
2724
+ * @returns A function that removes the listener when called
2725
+ * @public
2726
+ */
2727
+ listen(onHistory: StoreListener<R>, filters?: Partial<StoreListenerFilters>): () => void;
2728
+ private isMergingRemoteChanges;
2729
+ /**
2730
+ * Merge changes from a remote source. Changes made within the provided function
2731
+ * will be marked with source 'remote' instead of 'user'.
2732
+ *
2733
+ * @example
2734
+ * ```ts
2735
+ * // Changes from sync/collaboration
2736
+ * store.mergeRemoteChanges(() => {
2737
+ * store.put(remoteRecords)
2738
+ * store.remove(deletedIds)
2739
+ * })
2740
+ * ```
2741
+ *
2742
+ * @param fn - A function that applies the remote changes
2743
+ * @public
2744
+ */
2745
+ mergeRemoteChanges(fn: () => void): void;
2746
+ /**
2747
+ * Run `fn` and return a {@link RecordsDiff} of the changes that occurred as a result.
2748
+ */
2749
+ extractingChanges(fn: () => void): RecordsDiff<R>;
2750
+ applyDiff(diff: RecordsDiff<R>, { runCallbacks, ignoreEphemeralKeys, }?: {
2751
+ runCallbacks?: boolean;
2752
+ ignoreEphemeralKeys?: boolean;
2753
+ }): void;
2754
+ /**
2755
+ * Create a cache based on values in the store. Pass in a function that takes and ID and a
2756
+ * signal for the underlying record. Return a signal (usually a computed) for the cached value.
2757
+ * For simple derivations, use {@link Store.createComputedCache}. This function is useful if you
2758
+ * need more precise control over intermediate values.
2759
+ */
2760
+ createCache<Result, Record extends R = R>(create: (id: IdOf<Record>, recordSignal: Signal<R>) => Signal<Result>): {
2761
+ get: (id: IdOf<Record>) => Result | undefined;
2762
+ };
2763
+ /**
2764
+ * Create a computed cache.
2765
+ *
2766
+ * @param name - The name of the derivation cache.
2767
+ * @param derive - A function used to derive the value of the cache.
2768
+ * @param opts - Options for the computed cache.
2769
+ * @public
2770
+ */
2771
+ createComputedCache<Result, Record extends R = R>(name: string, derive: (record: Record) => Result | undefined, opts?: CreateComputedCacheOpts<Result, Record>): ComputedCache<Result, Record>;
2772
+ private _integrityChecker?;
2773
+ /** @internal */
2774
+ ensureStoreIsUsable(): void;
2775
+ private _isPossiblyCorrupted;
2776
+ /** @internal */
2777
+ markAsPossiblyCorrupted(): void;
2778
+ /** @internal */
2779
+ isPossiblyCorrupted(): boolean;
2780
+ private pendingAfterEvents;
2781
+ private addDiffForAfterEvent;
2782
+ private flushAtomicCallbacks;
2783
+ private _isInAtomicOp;
2784
+ /** @internal */
2785
+ atomic<T>(fn: () => T, runCallbacks?: boolean, isMergingRemoteChanges?: boolean): T;
2786
+ /** @internal */
2787
+ addHistoryInterceptor(fn: (entry: HistoryEntry<R>, source: ChangeSource) => void): () => void;
2788
+ }
2789
+ /**
2790
+ * A store or an object containing a store.
2791
+ * This type is used for APIs that can accept either a store directly or an object with a store property.
2792
+ *
2793
+ * @example
2794
+ * ```ts
2795
+ * function useStore(storeOrObject: StoreObject<MyRecord>) {
2796
+ * const store = storeOrObject instanceof Store ? storeOrObject : storeOrObject.store
2797
+ * return store
2798
+ * }
2799
+ * ```
2800
+ *
2801
+ * @public
2802
+ */
2803
+ type StoreObject<R extends UnknownRecord> = Store<R> | {
2804
+ store: Store<R>;
2805
+ };
2806
+ /**
2807
+ * Extract the record type from a StoreObject.
2808
+ *
2809
+ * @example
2810
+ * ```ts
2811
+ * type MyStoreObject = { store: Store<Book | Author> }
2812
+ * type Records = StoreObjectRecordType<MyStoreObject> // Book | Author
2813
+ * ```
2814
+ *
2815
+ * @public
2816
+ */
2817
+ type StoreObjectRecordType<Context extends StoreObject<any>> = Context extends Store<infer R> ? R : Context extends {
2818
+ store: Store<infer R>;
2819
+ } ? R : never;
2820
+ /**
2821
+ * Create a computed cache that works with any StoreObject (store or object containing a store).
2822
+ * This is a standalone version of Store.createComputedCache that can work with multiple store instances.
2823
+ *
2824
+ * @example
2825
+ * ```ts
2826
+ * const expensiveCache = createComputedCache(
2827
+ * 'expensiveData',
2828
+ * (context: { store: Store<Book> }, book: Book) => {
2829
+ * return performExpensiveCalculation(book)
2830
+ * }
2831
+ * )
2832
+ *
2833
+ * // Use with different store instances
2834
+ * const result1 = expensiveCache.get(storeObject1, bookId)
2835
+ * const result2 = expensiveCache.get(storeObject2, bookId)
2836
+ * ```
2837
+ *
2838
+ * @param name - A unique name for the cache (used for debugging)
2839
+ * @param derive - Function that derives a value from the context and record
2840
+ * @param opts - Optional configuration for equality checks
2841
+ * @returns A cache that can be used with multiple store instances
2842
+ * @public
2843
+ */
2844
+ 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>): {
2845
+ get(context: Context, id: IdOf<Record>): Result | undefined;
2846
+ };
2847
+
2848
+ /**
2849
+ * A type representing the diff of changes to a reactive store index.
2850
+ * Maps property values to the collection differences for record IDs that have that property value.
2851
+ *
2852
+ * @example
2853
+ * ```ts
2854
+ * // For an index on book titles, the diff might look like:
2855
+ * const titleIndexDiff: RSIndexDiff<Book, 'title'> = new Map([
2856
+ * ['The Lathe of Heaven', { added: new Set(['book:1']), removed: new Set() }],
2857
+ * ['Animal Farm', { added: new Set(), removed: new Set(['book:2']) }]
2858
+ * ])
2859
+ * ```
2860
+ *
2861
+ * @public
2862
+ */
2863
+ type RSIndexDiff<R extends UnknownRecord> = Map<any, CollectionDiff<IdOf<R>>>;
2864
+ /**
2865
+ * A type representing a reactive store index as a map from property values to sets of record IDs.
2866
+ * This is used to efficiently look up records by a specific property value.
2867
+ *
2868
+ * @example
2869
+ * ```ts
2870
+ * // Index mapping book titles to the IDs of books with that title
2871
+ * const titleIndex: RSIndexMap<Book, 'title'> = new Map([
2872
+ * ['The Lathe of Heaven', new Set(['book:1'])],
2873
+ * ['Animal Farm', new Set(['book:2', 'book:3'])]
2874
+ * ])
2875
+ * ```
2876
+ *
2877
+ * @public
2878
+ */
2879
+ type RSIndexMap<R extends UnknownRecord> = Map<any, Set<IdOf<R>>>;
2880
+ /**
2881
+ * A reactive computed index that provides efficient lookups of records by property values.
2882
+ * Returns a computed value containing an RSIndexMap with diffs for change tracking.
2883
+ *
2884
+ * @example
2885
+ * ```ts
2886
+ * // Create an index on book authors
2887
+ * const authorIndex: RSIndex<Book, 'authorId'> = store.query.index('book', 'authorId')
2888
+ *
2889
+ * // Get all books by a specific author
2890
+ * const leguinBooks = authorIndex.get().get('author:leguin')
2891
+ * ```
2892
+ *
2893
+ * @public
2894
+ */
2895
+ type RSIndex<R extends UnknownRecord> = Computed<RSIndexMap<R>, RSIndexDiff<R>>;
2896
+ /**
2897
+ * A class that provides reactive querying capabilities for a record store.
2898
+ * Offers methods to create indexes, filter records, and perform efficient lookups with automatic cache management.
2899
+ * All queries are reactive and will automatically update when the underlying store data changes.
2900
+ *
2901
+ * @example
2902
+ * ```ts
2903
+ * // Create a store with books
2904
+ * const store = new Store({ schema: StoreSchema.create({ book: Book, author: Author }) })
2905
+ *
2906
+ * // Get reactive queries for books
2907
+ * const booksByAuthor = store.query.index('book', 'authorId')
2908
+ * const inStockBooks = store.query.records('book', () => ({ inStock: { eq: true } }))
2909
+ * ```
2910
+ *
2911
+ * @public
2912
+ */
2913
+ declare class StoreQueries<R extends UnknownRecord> {
2914
+ private readonly recordMap;
2915
+ private readonly history;
2916
+ /**
2917
+ * Creates a new StoreQueries instance.
2918
+ *
2919
+ * recordMap - The atom map containing all records in the store
2920
+ * history - The atom tracking the store's change history with diffs
2921
+ *
2922
+ * @internal
2923
+ */
2924
+ constructor(recordMap: AtomMap<IdOf<R>, R>, history: Atom<number, RecordsDiff<R>>);
2925
+ /**
2926
+ * A cache of derivations (indexes).
2927
+ *
2928
+ * @internal
2929
+ */
2930
+ private indexCache;
2931
+ /**
2932
+ * A cache of derivations (filtered histories).
2933
+ *
2934
+ * @internal
2935
+ */
2936
+ private historyCache;
2937
+ /**
2938
+ * @internal
2939
+ */
2940
+ getAllIdsForType<TypeName extends R['typeName']>(typeName: TypeName): Set<IdOf<Extract<R, {
2941
+ typeName: TypeName;
2942
+ }>>>;
2943
+ /**
2944
+ * @internal
2945
+ */
2946
+ getRecordById<TypeName extends R['typeName']>(typeName: TypeName, id: IdOf<Extract<R, {
2947
+ typeName: TypeName;
2948
+ }>>): Extract<R, {
2949
+ typeName: TypeName;
2950
+ }> | undefined;
2951
+ /**
2952
+ * Helper to extract nested property value using pre-split path parts.
2953
+ * @internal
2954
+ */
2955
+ private getNestedValue;
2956
+ /**
2957
+ * Creates a reactive computed that tracks the change history for records of a specific type.
2958
+ * The returned computed provides incremental diffs showing what records of the given type
2959
+ * have been added, updated, or removed.
2960
+ *
2961
+ * @param typeName - The type name to filter the history by
2962
+ * @returns A computed value containing the current epoch and diffs of changes for the specified type
2963
+ *
2964
+ * @example
2965
+ * ```ts
2966
+ * // Track changes to book records only
2967
+ * const bookHistory = store.query.filterHistory('book')
2968
+ *
2969
+ * // React to book changes
2970
+ * react('book-changes', () => {
2971
+ * const currentEpoch = bookHistory.get()
2972
+ * console.log('Books updated at epoch:', currentEpoch)
2973
+ * })
2974
+ * ```
2975
+ *
2976
+ * @public
2977
+ */
2978
+ filterHistory<TypeName extends R['typeName']>(typeName: TypeName): Computed<number, RecordsDiff<Extract<R, {
2979
+ typeName: TypeName;
2980
+ }>>>;
2981
+ /**
2982
+ * Creates a reactive index that maps property values to sets of record IDs for efficient lookups.
2983
+ * The index automatically updates when records are added, updated, or removed, and results are cached
2984
+ * for performance.
2985
+ *
2986
+ * Supports nested property paths using backslash separator (e.g., 'metadata\\sessionId').
2987
+ *
2988
+ * @param typeName - The type name of records to index
2989
+ * @param path - The property name or backslash-delimited path to index by
2990
+ * @returns A reactive computed containing the index map with change diffs
2991
+ *
2992
+ * @example
2993
+ * ```ts
2994
+ * // Create an index of books by author ID
2995
+ * const booksByAuthor = store.query.index('book', 'authorId')
2996
+ *
2997
+ * // Get all books by a specific author
2998
+ * const authorBooks = booksByAuthor.get().get('author:leguin')
2999
+ * console.log(authorBooks) // Set<RecordId<Book>>
3000
+ *
3001
+ * // Index by nested property using backslash separator
3002
+ * const booksBySession = store.query.index('book', 'metadata\\sessionId')
3003
+ * const sessionBooks = booksBySession.get().get('session:alpha')
3004
+ * ```
3005
+ *
3006
+ * @public
3007
+ */
3008
+ index<TypeName extends R['typeName']>(typeName: TypeName, path: string): RSIndex<Extract<R, {
3009
+ typeName: TypeName;
3010
+ }>>;
3011
+ /**
3012
+ * Creates a new index without checking the cache. This method performs the actual work
3013
+ * of building the reactive index computation that tracks property values to record ID sets.
3014
+ *
3015
+ * Supports nested property paths using backslash separator.
3016
+ *
3017
+ * @param typeName - The type name of records to index
3018
+ * @param path - The property name or backslash-delimited path to index by
3019
+ * @returns A reactive computed containing the index map with change diffs
3020
+ *
3021
+ * @internal
3022
+ */
3023
+ __uncached_createIndex<TypeName extends R['typeName']>(typeName: TypeName, path: string): RSIndex<Extract<R, {
3024
+ typeName: TypeName;
3025
+ }>>;
3026
+ /**
3027
+ * Creates a reactive query that returns the first record matching the given query criteria.
3028
+ * Returns undefined if no matching record is found. The query automatically updates
3029
+ * when records change.
3030
+ *
3031
+ * @param typeName - The type name of records to query
3032
+ * @param queryCreator - Function that returns the query expression object to match against
3033
+ * @param name - Optional name for the query computation (used for debugging)
3034
+ * @returns A computed value containing the first matching record or undefined
3035
+ *
3036
+ * @example
3037
+ * ```ts
3038
+ * // Find the first book with a specific title
3039
+ * const bookLatheOfHeaven = store.query.record('book', () => ({ title: { eq: 'The Lathe of Heaven' } }))
3040
+ * console.log(bookLatheOfHeaven.get()?.title) // 'The Lathe of Heaven' or undefined
3041
+ *
3042
+ * // Find any book in stock
3043
+ * const anyInStockBook = store.query.record('book', () => ({ inStock: { eq: true } }))
3044
+ * ```
3045
+ *
3046
+ * @public
3047
+ */
3048
+ record<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, {
3049
+ typeName: TypeName;
3050
+ }>>, name?: string): Computed<Extract<R, {
3051
+ typeName: TypeName;
3052
+ }> | undefined>;
3053
+ /**
3054
+ * Creates a reactive query that returns an array of all records matching the given query criteria.
3055
+ * The array automatically updates when records are added, updated, or removed.
3056
+ *
3057
+ * @param typeName - The type name of records to query
3058
+ * @param queryCreator - Function that returns the query expression object to match against
3059
+ * @param name - Optional name for the query computation (used for debugging)
3060
+ * @returns A computed value containing an array of all matching records
3061
+ *
3062
+ * @example
3063
+ * ```ts
3064
+ * // Get all books in stock
3065
+ * const inStockBooks = store.query.records('book', () => ({ inStock: { eq: true } }))
3066
+ * console.log(inStockBooks.get()) // Book[]
3067
+ *
3068
+ * // Get all books by a specific author
3069
+ * const leguinBooks = store.query.records('book', () => ({ authorId: { eq: 'author:leguin' } }))
3070
+ *
3071
+ * // Get all books (no filter)
3072
+ * const allBooks = store.query.records('book')
3073
+ * ```
3074
+ *
3075
+ * @public
3076
+ */
3077
+ records<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, {
3078
+ typeName: TypeName;
3079
+ }>>, name?: string): Computed<Array<Extract<R, {
3080
+ typeName: TypeName;
3081
+ }>>>;
3082
+ /**
3083
+ * Creates a reactive query that returns a set of record IDs matching the given query criteria.
3084
+ * This is more efficient than `records()` when you only need the IDs and not the full record objects.
3085
+ * The set automatically updates with collection diffs when records change.
3086
+ *
3087
+ * @param typeName - The type name of records to query
3088
+ * @param queryCreator - Function that returns the query expression object to match against
3089
+ * @param name - Optional name for the query computation (used for debugging)
3090
+ * @returns A computed value containing a set of matching record IDs with collection diffs
3091
+ *
3092
+ * @example
3093
+ * ```ts
3094
+ * // Get IDs of all books in stock
3095
+ * const inStockBookIds = store.query.ids('book', () => ({ inStock: { eq: true } }))
3096
+ * console.log(inStockBookIds.get()) // Set<RecordId<Book>>
3097
+ *
3098
+ * // Get all book IDs (no filter)
3099
+ * const allBookIds = store.query.ids('book')
3100
+ *
3101
+ * // Use with other queries for efficient lookups
3102
+ * const authorBookIds = store.query.ids('book', () => ({ authorId: { eq: 'author:leguin' } }))
3103
+ * ```
3104
+ *
3105
+ * @public
3106
+ */
3107
+ ids<TypeName extends R['typeName']>(typeName: TypeName, queryCreator?: () => QueryExpression<Extract<R, {
3108
+ typeName: TypeName;
3109
+ }>>, name?: string): Computed<Set<IdOf<Extract<R, {
3110
+ typeName: TypeName;
3111
+ }>>>, CollectionDiff<IdOf<Extract<R, {
3112
+ typeName: TypeName;
3113
+ }>>>>;
3114
+ /**
3115
+ * Executes a one-time query against the current store state and returns matching records.
3116
+ * This is a non-reactive query that returns results immediately without creating a computed value.
3117
+ * Use this when you need a snapshot of data at a specific point in time.
3118
+ *
3119
+ * @param typeName - The type name of records to query
3120
+ * @param query - The query expression object to match against
3121
+ * @returns An array of records that match the query at the current moment
3122
+ *
3123
+ * @example
3124
+ * ```ts
3125
+ * // Get current in-stock books (non-reactive)
3126
+ * const currentInStockBooks = store.query.exec('book', { inStock: { eq: true } })
3127
+ * console.log(currentInStockBooks) // Book[]
3128
+ *
3129
+ * // Unlike records(), this won't update when the data changes
3130
+ * const staticBookList = store.query.exec('book', { authorId: { eq: 'author:leguin' } })
3131
+ * ```
3132
+ *
3133
+ * @public
3134
+ */
3135
+ exec<TypeName extends R['typeName']>(typeName: TypeName, query: QueryExpression<Extract<R, {
3136
+ typeName: TypeName;
3137
+ }>>): Array<Extract<R, {
3138
+ typeName: TypeName;
3139
+ }>>;
3140
+ }
3141
+
3142
+ /**
3143
+ * Defines matching criteria for query values. Supports equality, inequality, and greater-than comparisons.
3144
+ *
3145
+ * @example
3146
+ * ```ts
3147
+ * // Exact match
3148
+ * const exactMatch: QueryValueMatcher<string> = { eq: 'Science Fiction' }
3149
+ *
3150
+ * // Not equal to
3151
+ * const notMatch: QueryValueMatcher<string> = { neq: 'Romance' }
3152
+ *
3153
+ * // Greater than (numeric values only)
3154
+ * const greaterThan: QueryValueMatcher<number> = { gt: 2020 }
3155
+ * ```
3156
+ *
3157
+ * @public
3158
+ */
3159
+ type QueryValueMatcher<T> = {
3160
+ eq: T;
3161
+ } | {
3162
+ neq: T;
3163
+ } | {
3164
+ gt: number;
3165
+ };
3166
+ /**
3167
+ * Query expression for filtering records by their property values. Maps record property names
3168
+ * to matching criteria.
3169
+ *
3170
+ * @example
3171
+ * ```ts
3172
+ * // Query for books published after 2020 that are in stock
3173
+ * const bookQuery: QueryExpression<Book> = {
3174
+ * publishedYear: { gt: 2020 },
3175
+ * inStock: { eq: true }
3176
+ * }
3177
+ *
3178
+ * // Query for books not by a specific author
3179
+ * const notByAuthor: QueryExpression<Book> = {
3180
+ * authorId: { neq: 'author:tolkien' }
3181
+ * }
3182
+ *
3183
+ * // Query with nested properties
3184
+ * const nestedQuery: QueryExpression<Book> = {
3185
+ * metadata: { sessionId: { eq: 'session:alpha' } }
3186
+ * }
3187
+ * ```
3188
+ *
3189
+ * @public
3190
+ */
3191
+ /** @public */
3192
+ type QueryExpression<R extends object> = {
3193
+ [k in keyof R & string]?: R[k] extends string | number | boolean | null | undefined ? QueryValueMatcher<R[k]> : R[k] extends object ? QueryExpression<R[k]> : QueryValueMatcher<R[k]>;
3194
+ };
3195
+
3196
+ /**
3197
+ * A utility class for incrementally building a set while tracking changes. This class allows
3198
+ * you to add and remove items from a set while maintaining a diff of what was added and
3199
+ * removed from the original set. It's optimized for cases where you need to track changes
3200
+ * to a set over time and get both the final result and the change delta.
3201
+ *
3202
+ * @example
3203
+ * ```ts
3204
+ * const originalSet = new Set(['a', 'b', 'c'])
3205
+ * const constructor = new IncrementalSetConstructor(originalSet)
3206
+ *
3207
+ * constructor.add('d') // Add new item
3208
+ * constructor.remove('b') // Remove existing item
3209
+ * constructor.add('a') // Re-add removed item (no-op since already present)
3210
+ *
3211
+ * const result = constructor.get()
3212
+ * // result.value contains Set(['a', 'c', 'd'])
3213
+ * // result.diff contains { added: Set(['d']), removed: Set(['b']) }
3214
+ * ```
3215
+ *
3216
+ * @internal
3217
+ */
3218
+ declare class IncrementalSetConstructor<T> {
3219
+ /**
3220
+ * The previous value of the set.
3221
+ *
3222
+ * @internal
3223
+ * @readonly
3224
+ */
3225
+ private readonly previousValue;
3226
+ /**
3227
+ * The next value of the set.
3228
+ *
3229
+ * @internal
3230
+ */
3231
+ private nextValue?;
3232
+ /**
3233
+ * The diff of the set.
3234
+ *
3235
+ * @internal
3236
+ */
3237
+ private diff?;
3238
+ constructor(
3239
+ /**
3240
+ * The previous value of the set.
3241
+ *
3242
+ * @internal
3243
+ * @readonly
3244
+ */
3245
+ previousValue: Set<T>);
3246
+ /**
3247
+ * Gets the result of the incremental set construction if any changes were made.
3248
+ * Returns undefined if no additions or removals occurred.
3249
+ *
3250
+ * @returns An object containing the final set value and the diff of changes,
3251
+ * or undefined if no changes were made
3252
+ *
3253
+ * @example
3254
+ * ```ts
3255
+ * const constructor = new IncrementalSetConstructor(new Set(['a', 'b']))
3256
+ * constructor.add('c')
3257
+ *
3258
+ * const result = constructor.get()
3259
+ * // result = {
3260
+ * // value: Set(['a', 'b', 'c']),
3261
+ * // diff: { added: Set(['c']) }
3262
+ * // }
3263
+ * ```
3264
+ *
3265
+ * @public
3266
+ */
3267
+ get(): {
3268
+ value: Set<T>;
3269
+ diff: CollectionDiff<T>;
3270
+ } | undefined;
3271
+ /**
3272
+ * Add an item to the set.
3273
+ *
3274
+ * @param item - The item to add.
3275
+ * @param wasAlreadyPresent - Whether the item was already present in the set.
3276
+ * @internal
3277
+ */
3278
+ private _add;
3279
+ /**
3280
+ * Adds an item to the set. If the item was already present in the original set
3281
+ * and was previously removed during this construction, it will be restored.
3282
+ * If the item is already present and wasn't removed, this is a no-op.
3283
+ *
3284
+ * @param item - The item to add to the set
3285
+ *
3286
+ * @example
3287
+ * ```ts
3288
+ * const constructor = new IncrementalSetConstructor(new Set(['a', 'b']))
3289
+ * constructor.add('c') // Adds new item
3290
+ * constructor.add('a') // No-op, already present
3291
+ * constructor.remove('b')
3292
+ * constructor.add('b') // Restores previously removed item
3293
+ * ```
3294
+ *
3295
+ * @public
3296
+ */
3297
+ add(item: T): void;
3298
+ /**
3299
+ * Remove an item from the set.
3300
+ *
3301
+ * @param item - The item to remove.
3302
+ * @param wasAlreadyPresent - Whether the item was already present in the set.
3303
+ * @internal
3304
+ */
3305
+ private _remove;
3306
+ /**
3307
+ * Removes an item from the set. If the item wasn't present in the original set
3308
+ * and was added during this construction, it will be removed from the added diff.
3309
+ * If the item is not present at all, this is a no-op.
3310
+ *
3311
+ * @param item - The item to remove from the set
3312
+ *
3313
+ * @example
3314
+ * ```ts
3315
+ * const constructor = new IncrementalSetConstructor(new Set(['a', 'b']))
3316
+ * constructor.remove('a') // Removes existing item
3317
+ * constructor.remove('c') // No-op, not present
3318
+ * constructor.add('d')
3319
+ * constructor.remove('d') // Removes recently added item
3320
+ * ```
3321
+ *
3322
+ * @public
3323
+ */
3324
+ remove(item: T): void;
3325
+ }
3326
+
3327
+ export { AtomMap, AtomSet, type BaseRecord, type ChangeSource, type CollectionDiff, type ComputedCache, type CreateComputedCacheOpts, type HistoryEntry, type IdOf, IncrementalSetConstructor, type LegacyBaseMigrationsInfo, type LegacyMigration, type LegacyMigrations, type Migration, MigrationFailureReason, type MigrationId, type MigrationResult, type MigrationSequence, type QueryExpression, type QueryValueMatcher, type RSIndex, type RSIndexDiff, type RSIndexMap, type RecordFromId, type RecordId, type RecordScope, RecordType, type RecordsDiff, type SerializedSchema, type SerializedSchemaV1, type SerializedSchemaV2, type SerializedStore, type StandaloneDependsOn, Store, type StoreAfterChangeHandler, type StoreAfterCreateHandler, type StoreAfterDeleteHandler, type StoreBeforeChangeHandler, type StoreBeforeCreateHandler, type StoreBeforeDeleteHandler, type StoreError, type StoreListener, type StoreListenerFilters, type StoreObject, type StoreObjectRecordType, type StoreOperationCompleteHandler, StoreQueries, type StoreRecord, StoreSchema, type StoreSchemaOptions, StoreSideEffects, type StoreSnapshot, type StoreValidationFailure, type StoreValidator, type StoreValidators, type SynchronousRecordStorage, type SynchronousStorage, type UnknownRecord, assertIdType, createComputedCache, createEmptyRecordsDiff, createMigrationIds, createMigrationSequence, createRecordMigrationSequence, createRecordType, devFreeze, isRecordsDiffEmpty, parseMigrationId, reverseRecordsDiff, squashRecordDiffs, squashRecordDiffsMutable };