@tldraw/store 4.2.0-next.873779de89c7 → 4.2.0-next.a0e143fb0bf2

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.
@@ -31,10 +31,7 @@ import { CollectionDiff } from './Store'
31
31
  *
32
32
  * @public
33
33
  */
34
- export type RSIndexDiff<
35
- R extends UnknownRecord,
36
- Property extends string & keyof R = string & keyof R,
37
- > = Map<R[Property], CollectionDiff<IdOf<R>>>
34
+ export type RSIndexDiff<R extends UnknownRecord> = Map<any, CollectionDiff<IdOf<R>>>
38
35
 
39
36
  /**
40
37
  * A type representing a reactive store index as a map from property values to sets of record IDs.
@@ -51,10 +48,7 @@ export type RSIndexDiff<
51
48
  *
52
49
  * @public
53
50
  */
54
- export type RSIndexMap<
55
- R extends UnknownRecord,
56
- Property extends string & keyof R = string & keyof R,
57
- > = Map<R[Property], Set<IdOf<R>>>
51
+ export type RSIndexMap<R extends UnknownRecord> = Map<any, Set<IdOf<R>>>
58
52
 
59
53
  /**
60
54
  * A reactive computed index that provides efficient lookups of records by property values.
@@ -71,10 +65,7 @@ export type RSIndexMap<
71
65
  *
72
66
  * @public
73
67
  */
74
- export type RSIndex<
75
- R extends UnknownRecord,
76
- Property extends string & keyof R = string & keyof R,
77
- > = Computed<RSIndexMap<R, Property>, RSIndexDiff<R, Property>>
68
+ export type RSIndex<R extends UnknownRecord> = Computed<RSIndexMap<R>, RSIndexDiff<R>>
78
69
 
79
70
  /**
80
71
  * A class that provides reactive querying capabilities for a record store.
@@ -121,6 +112,49 @@ export class StoreQueries<R extends UnknownRecord> {
121
112
  */
122
113
  private historyCache = new Map<string, Computed<number, RecordsDiff<R>>>()
123
114
 
115
+ /**
116
+ * @internal
117
+ */
118
+ public getAllIdsForType<TypeName extends R['typeName']>(
119
+ typeName: TypeName
120
+ ): Set<IdOf<Extract<R, { typeName: TypeName }>>> {
121
+ type S = Extract<R, { typeName: TypeName }>
122
+ const ids = new Set<IdOf<S>>()
123
+ for (const record of this.recordMap.values()) {
124
+ if (record.typeName === typeName) {
125
+ ids.add(record.id as IdOf<S>)
126
+ }
127
+ }
128
+ return ids
129
+ }
130
+
131
+ /**
132
+ * @internal
133
+ */
134
+ public getRecordById<TypeName extends R['typeName']>(
135
+ typeName: TypeName,
136
+ id: IdOf<Extract<R, { typeName: TypeName }>>
137
+ ): Extract<R, { typeName: TypeName }> | undefined {
138
+ const record = this.recordMap.get(id as IdOf<R>)
139
+ if (record && record.typeName === typeName) {
140
+ return record as Extract<R, { typeName: TypeName }>
141
+ }
142
+ return undefined
143
+ }
144
+
145
+ /**
146
+ * Helper to extract nested property value using pre-split path parts.
147
+ * @internal
148
+ */
149
+ private getNestedValue(obj: any, pathParts: string[]): any {
150
+ let current = obj
151
+ for (const part of pathParts) {
152
+ if (current == null || typeof current !== 'object') return undefined
153
+ current = current[part]
154
+ }
155
+ return current
156
+ }
157
+
124
158
  /**
125
159
  * Creates a reactive computed that tracks the change history for records of a specific type.
126
160
  * The returned computed provides incremental diffs showing what records of the given type
@@ -237,8 +271,10 @@ export class StoreQueries<R extends UnknownRecord> {
237
271
  * The index automatically updates when records are added, updated, or removed, and results are cached
238
272
  * for performance.
239
273
  *
274
+ * Supports nested property paths using backslash separator (e.g., 'metadata\\sessionId').
275
+ *
240
276
  * @param typeName - The type name of records to index
241
- * @param property - The property name to index by
277
+ * @param path - The property name or backslash-delimited path to index by
242
278
  * @returns A reactive computed containing the index map with change diffs
243
279
  *
244
280
  * @example
@@ -250,24 +286,24 @@ export class StoreQueries<R extends UnknownRecord> {
250
286
  * const authorBooks = booksByAuthor.get().get('author:leguin')
251
287
  * console.log(authorBooks) // Set<RecordId<Book>>
252
288
  *
253
- * // Index by title for quick title lookups
254
- * const booksByTitle = store.query.index('book', 'title')
255
- * const booksLatheOfHeaven = booksByTitle.get().get('The Lathe of Heaven')
289
+ * // Index by nested property using backslash separator
290
+ * const booksBySession = store.query.index('book', 'metadata\\sessionId')
291
+ * const sessionBooks = booksBySession.get().get('session:alpha')
256
292
  * ```
257
293
  *
258
294
  * @public
259
295
  */
260
- public index<
261
- TypeName extends R['typeName'],
262
- Property extends string & keyof Extract<R, { typeName: TypeName }>,
263
- >(typeName: TypeName, property: Property): RSIndex<Extract<R, { typeName: TypeName }>, Property> {
264
- const cacheKey = typeName + ':' + property
296
+ public index<TypeName extends R['typeName']>(
297
+ typeName: TypeName,
298
+ path: string
299
+ ): RSIndex<Extract<R, { typeName: TypeName }>> {
300
+ const cacheKey = typeName + ':' + path
265
301
 
266
302
  if (this.indexCache.has(cacheKey)) {
267
303
  return this.indexCache.get(cacheKey) as any
268
304
  }
269
305
 
270
- const index = this.__uncached_createIndex(typeName, property)
306
+ const index = this.__uncached_createIndex(typeName, path)
271
307
 
272
308
  this.indexCache.set(cacheKey, index as any)
273
309
 
@@ -278,40 +314,51 @@ export class StoreQueries<R extends UnknownRecord> {
278
314
  * Creates a new index without checking the cache. This method performs the actual work
279
315
  * of building the reactive index computation that tracks property values to record ID sets.
280
316
  *
317
+ * Supports nested property paths using backslash separator.
318
+ *
281
319
  * @param typeName - The type name of records to index
282
- * @param property - The property name to index by
320
+ * @param path - The property name or backslash-delimited path to index by
283
321
  * @returns A reactive computed containing the index map with change diffs
284
322
  *
285
323
  * @internal
286
324
  */
287
- __uncached_createIndex<
288
- TypeName extends R['typeName'],
289
- Property extends string & keyof Extract<R, { typeName: TypeName }>,
290
- >(typeName: TypeName, property: Property): RSIndex<Extract<R, { typeName: TypeName }>, Property> {
325
+ __uncached_createIndex<TypeName extends R['typeName']>(
326
+ typeName: TypeName,
327
+ path: string
328
+ ): RSIndex<Extract<R, { typeName: TypeName }>> {
291
329
  type S = Extract<R, { typeName: TypeName }>
292
330
 
293
331
  const typeHistory = this.filterHistory(typeName)
294
332
 
333
+ // Create closure for efficient property value extraction
334
+ const pathParts = path.split('\\')
335
+ const getPropertyValue =
336
+ pathParts.length > 1
337
+ ? (obj: S) => this.getNestedValue(obj, pathParts)
338
+ : (obj: S) => obj[path as keyof S]
339
+
295
340
  const fromScratch = () => {
296
341
  // deref typeHistory early so that the first time the incremental version runs
297
342
  // it gets a diff to work with instead of having to bail to this from-scratch version
298
343
  typeHistory.get()
299
- const res = new Map<S[Property], Set<IdOf<S>>>()
344
+ const res = new Map<any, Set<IdOf<S>>>()
300
345
  for (const record of this.recordMap.values()) {
301
346
  if (record.typeName === typeName) {
302
- const value = (record as S)[property]
303
- if (!res.has(value)) {
304
- res.set(value, new Set())
347
+ const value = getPropertyValue(record as S)
348
+ if (value !== undefined) {
349
+ if (!res.has(value)) {
350
+ res.set(value, new Set())
351
+ }
352
+ res.get(value)!.add(record.id)
305
353
  }
306
- res.get(value)!.add(record.id)
307
354
  }
308
355
  }
309
356
 
310
357
  return res
311
358
  }
312
359
 
313
- return computed<RSIndexMap<S, Property>, RSIndexDiff<S, Property>>(
314
- 'index:' + typeName + ':' + property,
360
+ return computed<RSIndexMap<S>, RSIndexDiff<S>>(
361
+ 'index:' + typeName + ':' + path,
315
362
  (prevValue, lastComputedEpoch) => {
316
363
  if (isUninitialized(prevValue)) return fromScratch()
317
364
 
@@ -322,7 +369,7 @@ export class StoreQueries<R extends UnknownRecord> {
322
369
 
323
370
  const setConstructors = new Map<any, IncrementalSetConstructor<IdOf<S>>>()
324
371
 
325
- const add = (value: S[Property], id: IdOf<S>) => {
372
+ const add = (value: any, id: IdOf<S>) => {
326
373
  let setConstructor = setConstructors.get(value)
327
374
  if (!setConstructor)
328
375
  setConstructor = new IncrementalSetConstructor<IdOf<S>>(
@@ -332,7 +379,7 @@ export class StoreQueries<R extends UnknownRecord> {
332
379
  setConstructors.set(value, setConstructor)
333
380
  }
334
381
 
335
- const remove = (value: S[Property], id: IdOf<S>) => {
382
+ const remove = (value: any, id: IdOf<S>) => {
336
383
  let set = setConstructors.get(value)
337
384
  if (!set) set = new IncrementalSetConstructor<IdOf<S>>(prevValue.get(value) ?? new Set())
338
385
  set.remove(id)
@@ -342,30 +389,38 @@ export class StoreQueries<R extends UnknownRecord> {
342
389
  for (const changes of history) {
343
390
  for (const record of objectMapValues(changes.added)) {
344
391
  if (record.typeName === typeName) {
345
- const value = (record as S)[property]
346
- add(value, record.id)
392
+ const value = getPropertyValue(record as S)
393
+ if (value !== undefined) {
394
+ add(value, record.id)
395
+ }
347
396
  }
348
397
  }
349
398
  for (const [from, to] of objectMapValues(changes.updated)) {
350
399
  if (to.typeName === typeName) {
351
- const prev = (from as S)[property]
352
- const next = (to as S)[property]
400
+ const prev = getPropertyValue(from as S)
401
+ const next = getPropertyValue(to as S)
353
402
  if (prev !== next) {
354
- remove(prev, to.id)
355
- add(next, to.id)
403
+ if (prev !== undefined) {
404
+ remove(prev, to.id)
405
+ }
406
+ if (next !== undefined) {
407
+ add(next, to.id)
408
+ }
356
409
  }
357
410
  }
358
411
  }
359
412
  for (const record of objectMapValues(changes.removed)) {
360
413
  if (record.typeName === typeName) {
361
- const value = (record as S)[property]
362
- remove(value, record.id)
414
+ const value = getPropertyValue(record as S)
415
+ if (value !== undefined) {
416
+ remove(value, record.id)
417
+ }
363
418
  }
364
419
  }
365
420
  }
366
421
 
367
- let nextValue: undefined | RSIndexMap<S, Property> = undefined
368
- let nextDiff: undefined | RSIndexDiff<S, Property> = undefined
422
+ let nextValue: undefined | RSIndexMap<S> = undefined
423
+ let nextDiff: undefined | RSIndexDiff<S> = undefined
369
424
 
370
425
  for (const [value, setConstructor] of setConstructors) {
371
426
  const result = setConstructor.get()
@@ -513,11 +568,7 @@ export class StoreQueries<R extends UnknownRecord> {
513
568
  typeHistory.get()
514
569
  const query: QueryExpression<S> = queryCreator()
515
570
  if (Object.keys(query).length === 0) {
516
- const ids = new Set<IdOf<S>>()
517
- for (const record of this.recordMap.values()) {
518
- if (record.typeName === typeName) ids.add(record.id)
519
- }
520
- return ids
571
+ return this.getAllIdsForType(typeName)
521
572
  }
522
573
 
523
574
  return executeQuery(this, typeName, query)