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