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