@tanstack/db 0.4.9 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/cjs/collection/events.cjs +9 -51
  2. package/dist/cjs/collection/events.cjs.map +1 -1
  3. package/dist/cjs/collection/events.d.cts +18 -7
  4. package/dist/cjs/collection/index.cjs +9 -12
  5. package/dist/cjs/collection/index.cjs.map +1 -1
  6. package/dist/cjs/collection/index.d.cts +13 -14
  7. package/dist/cjs/collection/subscription.cjs +62 -6
  8. package/dist/cjs/collection/subscription.cjs.map +1 -1
  9. package/dist/cjs/collection/subscription.d.cts +16 -3
  10. package/dist/cjs/collection/sync.cjs +58 -6
  11. package/dist/cjs/collection/sync.cjs.map +1 -1
  12. package/dist/cjs/collection/sync.d.cts +18 -4
  13. package/dist/cjs/errors.cjs +8 -0
  14. package/dist/cjs/errors.cjs.map +1 -1
  15. package/dist/cjs/errors.d.cts +6 -0
  16. package/dist/cjs/event-emitter.cjs +94 -0
  17. package/dist/cjs/event-emitter.cjs.map +1 -0
  18. package/dist/cjs/event-emitter.d.cts +45 -0
  19. package/dist/cjs/index.cjs +1 -0
  20. package/dist/cjs/index.cjs.map +1 -1
  21. package/dist/cjs/local-only.cjs.map +1 -1
  22. package/dist/cjs/local-only.d.cts +2 -5
  23. package/dist/cjs/query/compiler/index.cjs +6 -2
  24. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  25. package/dist/cjs/query/compiler/index.d.cts +3 -2
  26. package/dist/cjs/query/compiler/joins.cjs +6 -3
  27. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  28. package/dist/cjs/query/compiler/joins.d.cts +2 -2
  29. package/dist/cjs/query/compiler/order-by.cjs +18 -4
  30. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  31. package/dist/cjs/query/compiler/order-by.d.cts +2 -1
  32. package/dist/cjs/query/compiler/types.d.cts +4 -0
  33. package/dist/cjs/query/index.d.cts +1 -0
  34. package/dist/cjs/query/live/collection-config-builder.cjs +43 -6
  35. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  36. package/dist/cjs/query/live/collection-config-builder.d.cts +27 -1
  37. package/dist/cjs/query/live/collection-subscriber.cjs +29 -0
  38. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  39. package/dist/cjs/query/live/collection-subscriber.d.cts +1 -0
  40. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  41. package/dist/cjs/query/live-query-collection.d.cts +2 -2
  42. package/dist/cjs/types.d.cts +82 -11
  43. package/dist/esm/collection/events.d.ts +18 -7
  44. package/dist/esm/collection/events.js +9 -51
  45. package/dist/esm/collection/events.js.map +1 -1
  46. package/dist/esm/collection/index.d.ts +13 -14
  47. package/dist/esm/collection/index.js +9 -12
  48. package/dist/esm/collection/index.js.map +1 -1
  49. package/dist/esm/collection/subscription.d.ts +16 -3
  50. package/dist/esm/collection/subscription.js +62 -6
  51. package/dist/esm/collection/subscription.js.map +1 -1
  52. package/dist/esm/collection/sync.d.ts +18 -4
  53. package/dist/esm/collection/sync.js +59 -7
  54. package/dist/esm/collection/sync.js.map +1 -1
  55. package/dist/esm/errors.d.ts +6 -0
  56. package/dist/esm/errors.js +8 -0
  57. package/dist/esm/errors.js.map +1 -1
  58. package/dist/esm/event-emitter.d.ts +45 -0
  59. package/dist/esm/event-emitter.js +94 -0
  60. package/dist/esm/event-emitter.js.map +1 -0
  61. package/dist/esm/index.js +2 -1
  62. package/dist/esm/local-only.d.ts +2 -5
  63. package/dist/esm/local-only.js.map +1 -1
  64. package/dist/esm/query/compiler/index.d.ts +3 -2
  65. package/dist/esm/query/compiler/index.js +6 -2
  66. package/dist/esm/query/compiler/index.js.map +1 -1
  67. package/dist/esm/query/compiler/joins.d.ts +2 -2
  68. package/dist/esm/query/compiler/joins.js +6 -3
  69. package/dist/esm/query/compiler/joins.js.map +1 -1
  70. package/dist/esm/query/compiler/order-by.d.ts +2 -1
  71. package/dist/esm/query/compiler/order-by.js +18 -4
  72. package/dist/esm/query/compiler/order-by.js.map +1 -1
  73. package/dist/esm/query/compiler/types.d.ts +4 -0
  74. package/dist/esm/query/index.d.ts +1 -0
  75. package/dist/esm/query/live/collection-config-builder.d.ts +27 -1
  76. package/dist/esm/query/live/collection-config-builder.js +44 -7
  77. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  78. package/dist/esm/query/live/collection-subscriber.d.ts +1 -0
  79. package/dist/esm/query/live/collection-subscriber.js +29 -0
  80. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  81. package/dist/esm/query/live-query-collection.d.ts +2 -2
  82. package/dist/esm/query/live-query-collection.js.map +1 -1
  83. package/dist/esm/types.d.ts +82 -11
  84. package/package.json +2 -2
  85. package/src/collection/events.ts +25 -74
  86. package/src/collection/index.ts +15 -19
  87. package/src/collection/subscription.ts +88 -6
  88. package/src/collection/sync.ts +81 -9
  89. package/src/errors.ts +12 -0
  90. package/src/event-emitter.ts +118 -0
  91. package/src/local-only.ts +5 -12
  92. package/src/query/compiler/index.ts +9 -1
  93. package/src/query/compiler/joins.ts +7 -1
  94. package/src/query/compiler/order-by.ts +23 -2
  95. package/src/query/compiler/types.ts +5 -0
  96. package/src/query/index.ts +1 -0
  97. package/src/query/live/collection-config-builder.ts +76 -7
  98. package/src/query/live/collection-subscriber.ts +50 -0
  99. package/src/query/live-query-collection.ts +8 -4
  100. package/src/types.ts +93 -11
@@ -24,6 +24,12 @@ export class CollectionSubscriber<
24
24
  // Keep track of the biggest value we've sent so far (needed for orderBy optimization)
25
25
  private biggest: any = undefined
26
26
 
27
+ // Track deferred promises for subscription loading states
28
+ private subscriptionLoadingPromises = new Map<
29
+ CollectionSubscription,
30
+ { resolve: () => void }
31
+ >()
32
+
27
33
  constructor(
28
34
  private alias: string,
29
35
  private collectionId: string,
@@ -66,7 +72,51 @@ export class CollectionSubscriber<
66
72
  includeInitialState
67
73
  )
68
74
  }
75
+
76
+ // Subscribe to subscription status changes to propagate loading state
77
+ const statusUnsubscribe = subscription.on(`status:change`, (event) => {
78
+ // TODO: For now we are setting this loading state whenever the subscription
79
+ // status changes to 'loadingSubset'. But we have discussed it only happening
80
+ // when the the live query has it's offset/limit changed, and that triggers the
81
+ // subscription to request a snapshot. This will require more work to implement,
82
+ // and builds on https://github.com/TanStack/db/pull/663 which this PR
83
+ // does not yet depend on.
84
+ if (event.status === `loadingSubset`) {
85
+ // Guard against duplicate transitions
86
+ if (!this.subscriptionLoadingPromises.has(subscription)) {
87
+ let resolve: () => void
88
+ const promise = new Promise<void>((res) => {
89
+ resolve = res
90
+ })
91
+
92
+ this.subscriptionLoadingPromises.set(subscription, {
93
+ resolve: resolve!,
94
+ })
95
+ this.collectionConfigBuilder.liveQueryCollection!._sync.trackLoadPromise(
96
+ promise
97
+ )
98
+ }
99
+ } else {
100
+ // status is 'ready'
101
+ const deferred = this.subscriptionLoadingPromises.get(subscription)
102
+ if (deferred) {
103
+ // Clear the map entry FIRST (before resolving)
104
+ this.subscriptionLoadingPromises.delete(subscription)
105
+ deferred.resolve()
106
+ }
107
+ }
108
+ })
109
+
69
110
  const unsubscribe = () => {
111
+ // If subscription has a pending promise, resolve it before unsubscribing
112
+ const deferred = this.subscriptionLoadingPromises.get(subscription)
113
+ if (deferred) {
114
+ // Clear the map entry FIRST (before resolving)
115
+ this.subscriptionLoadingPromises.delete(subscription)
116
+ deferred.resolve()
117
+ }
118
+
119
+ statusUnsubscribe()
70
120
  subscription.unsubscribe()
71
121
  }
72
122
  // currentSyncState is always defined when subscribe() is called
@@ -20,16 +20,20 @@ import type { Context, GetResult } from "./builder/types.js"
20
20
  type CollectionConfigForContext<
21
21
  TContext extends Context,
22
22
  TResult extends object,
23
+ TUtils extends UtilsRecord = {},
23
24
  > = TContext extends SingleResult
24
- ? CollectionConfigSingleRowOption<TResult> & SingleResult
25
- : CollectionConfigSingleRowOption<TResult> & NonSingleResult
25
+ ? CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &
26
+ SingleResult
27
+ : CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &
28
+ NonSingleResult
26
29
 
27
30
  type CollectionForContext<
28
31
  TContext extends Context,
29
32
  TResult extends object,
33
+ TUtils extends UtilsRecord = {},
30
34
  > = TContext extends SingleResult
31
- ? Collection<TResult> & SingleResult
32
- : Collection<TResult> & NonSingleResult
35
+ ? Collection<TResult, string | number, TUtils> & SingleResult
36
+ : Collection<TResult, string | number, TUtils> & NonSingleResult
33
37
 
34
38
  /**
35
39
  * Creates live query collection options for use with createCollection
package/src/types.ts CHANGED
@@ -3,6 +3,7 @@ import type { Collection } from "./collection/index.js"
3
3
  import type { StandardSchemaV1 } from "@standard-schema/spec"
4
4
  import type { Transaction } from "./transactions"
5
5
  import type { BasicExpression, OrderBy } from "./query/ir.js"
6
+ import type { EventEmitter } from "./event-emitter.js"
6
7
 
7
8
  /**
8
9
  * Helper type to extract the output type from a standard schema
@@ -150,17 +151,83 @@ export type Row<TExtensions = never> = Record<string, Value<TExtensions>>
150
151
 
151
152
  export type OperationType = `insert` | `update` | `delete`
152
153
 
153
- export type OnLoadMoreOptions = {
154
+ /**
155
+ * Subscription status values
156
+ */
157
+ export type SubscriptionStatus = `ready` | `loadingSubset`
158
+
159
+ /**
160
+ * Event emitted when subscription status changes
161
+ */
162
+ export interface SubscriptionStatusChangeEvent {
163
+ type: `status:change`
164
+ subscription: Subscription
165
+ previousStatus: SubscriptionStatus
166
+ status: SubscriptionStatus
167
+ }
168
+
169
+ /**
170
+ * Event emitted when subscription status changes to a specific status
171
+ */
172
+ export interface SubscriptionStatusEvent<T extends SubscriptionStatus> {
173
+ type: `status:${T}`
174
+ subscription: Subscription
175
+ previousStatus: SubscriptionStatus
176
+ status: T
177
+ }
178
+
179
+ /**
180
+ * Event emitted when subscription is unsubscribed
181
+ */
182
+ export interface SubscriptionUnsubscribedEvent {
183
+ type: `unsubscribed`
184
+ subscription: Subscription
185
+ }
186
+
187
+ /**
188
+ * All subscription events
189
+ */
190
+ export type SubscriptionEvents = {
191
+ "status:change": SubscriptionStatusChangeEvent
192
+ "status:ready": SubscriptionStatusEvent<`ready`>
193
+ "status:loadingSubset": SubscriptionStatusEvent<`loadingSubset`>
194
+ unsubscribed: SubscriptionUnsubscribedEvent
195
+ }
196
+
197
+ /**
198
+ * Public interface for a collection subscription
199
+ * Used by sync implementations to track subscription lifecycle
200
+ */
201
+ export interface Subscription extends EventEmitter<SubscriptionEvents> {
202
+ /** Current status of the subscription */
203
+ readonly status: SubscriptionStatus
204
+ }
205
+
206
+ export type LoadSubsetOptions = {
207
+ /** The where expression to filter the data */
154
208
  where?: BasicExpression<boolean>
209
+ /** The order by clause to sort the data */
155
210
  orderBy?: OrderBy
211
+ /** The limit of the data to load */
156
212
  limit?: number
213
+ /**
214
+ * The subscription that triggered the load.
215
+ * Advanced sync implementations can use this for:
216
+ * - LRU caching keyed by subscription
217
+ * - Reference counting to track active subscriptions
218
+ * - Subscribing to subscription events (e.g., finalization/unsubscribe)
219
+ * @optional Available when called from CollectionSubscription, may be undefined for direct calls
220
+ */
221
+ subscription?: Subscription
157
222
  }
158
223
 
224
+ export type LoadSubsetFn = (options: LoadSubsetOptions) => true | Promise<void>
225
+
159
226
  export type CleanupFn = () => void
160
227
 
161
228
  export type SyncConfigRes = {
162
229
  cleanup?: CleanupFn
163
- onLoadMore?: (options: OnLoadMoreOptions) => void | Promise<void>
230
+ loadSubset?: LoadSubsetFn
164
231
  }
165
232
  export interface SyncConfig<
166
233
  T extends object = Record<string, unknown>,
@@ -242,7 +309,7 @@ export interface InsertConfig {
242
309
  export type UpdateMutationFnParams<
243
310
  T extends object = Record<string, unknown>,
244
311
  TKey extends string | number = string | number,
245
- TUtils extends UtilsRecord = Record<string, Fn>,
312
+ TUtils extends UtilsRecord = UtilsRecord,
246
313
  > = {
247
314
  transaction: TransactionWithMutations<T, `update`>
248
315
  collection: Collection<T, TKey, TUtils>
@@ -251,7 +318,7 @@ export type UpdateMutationFnParams<
251
318
  export type InsertMutationFnParams<
252
319
  T extends object = Record<string, unknown>,
253
320
  TKey extends string | number = string | number,
254
- TUtils extends UtilsRecord = Record<string, Fn>,
321
+ TUtils extends UtilsRecord = UtilsRecord,
255
322
  > = {
256
323
  transaction: TransactionWithMutations<T, `insert`>
257
324
  collection: Collection<T, TKey, TUtils>
@@ -259,7 +326,7 @@ export type InsertMutationFnParams<
259
326
  export type DeleteMutationFnParams<
260
327
  T extends object = Record<string, unknown>,
261
328
  TKey extends string | number = string | number,
262
- TUtils extends UtilsRecord = Record<string, Fn>,
329
+ TUtils extends UtilsRecord = UtilsRecord,
263
330
  > = {
264
331
  transaction: TransactionWithMutations<T, `delete`>
265
332
  collection: Collection<T, TKey, TUtils>
@@ -268,21 +335,21 @@ export type DeleteMutationFnParams<
268
335
  export type InsertMutationFn<
269
336
  T extends object = Record<string, unknown>,
270
337
  TKey extends string | number = string | number,
271
- TUtils extends UtilsRecord = Record<string, Fn>,
338
+ TUtils extends UtilsRecord = UtilsRecord,
272
339
  TReturn = any,
273
340
  > = (params: InsertMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
274
341
 
275
342
  export type UpdateMutationFn<
276
343
  T extends object = Record<string, unknown>,
277
344
  TKey extends string | number = string | number,
278
- TUtils extends UtilsRecord = Record<string, Fn>,
345
+ TUtils extends UtilsRecord = UtilsRecord,
279
346
  TReturn = any,
280
347
  > = (params: UpdateMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
281
348
 
282
349
  export type DeleteMutationFn<
283
350
  T extends object = Record<string, unknown>,
284
351
  TKey extends string | number = string | number,
285
- TUtils extends UtilsRecord = Record<string, Fn>,
352
+ TUtils extends UtilsRecord = UtilsRecord,
286
353
  TReturn = any,
287
354
  > = (params: DeleteMutationFnParams<T, TKey, TUtils>) => Promise<TReturn>
288
355
 
@@ -313,6 +380,8 @@ export type CollectionStatus =
313
380
  /** Collection has been cleaned up and resources freed */
314
381
  | `cleaned-up`
315
382
 
383
+ export type SyncMode = `eager` | `on-demand`
384
+
316
385
  export interface BaseCollectionConfig<
317
386
  T extends object = Record<string, unknown>,
318
387
  TKey extends string | number = string | number,
@@ -321,7 +390,7 @@ export interface BaseCollectionConfig<
321
390
  // then it would conflict with the overloads of createCollection which
322
391
  // requires either T to be provided or a schema to be provided but not both!
323
392
  TSchema extends StandardSchemaV1 = never,
324
- TUtils extends UtilsRecord = Record<string, Fn>,
393
+ TUtils extends UtilsRecord = UtilsRecord,
325
394
  TReturn = any,
326
395
  > {
327
396
  // If an id isn't passed in, a UUID will be
@@ -374,6 +443,15 @@ export interface BaseCollectionConfig<
374
443
  * compare: (x, y) => x.createdAt.getTime() - y.createdAt.getTime()
375
444
  */
376
445
  compare?: (x: T, y: T) => number
446
+ /**
447
+ * The mode of sync to use for the collection.
448
+ * @default `eager`
449
+ * @description
450
+ * - `eager`: syncs all data immediately on preload
451
+ * - `on-demand`: syncs data in incremental snapshots when the collection is queried
452
+ * The exact implementation of the sync mode is up to the sync implementation.
453
+ */
454
+ syncMode?: SyncMode
377
455
  /**
378
456
  * Optional asynchronous handler function called before an insert operation
379
457
  * @param params Object containing transaction and collection information
@@ -503,13 +581,16 @@ export interface BaseCollectionConfig<
503
581
  * }
504
582
  */
505
583
  onDelete?: DeleteMutationFn<T, TKey, TUtils, TReturn>
584
+
585
+ utils?: TUtils
506
586
  }
507
587
 
508
588
  export interface CollectionConfig<
509
589
  T extends object = Record<string, unknown>,
510
590
  TKey extends string | number = string | number,
511
591
  TSchema extends StandardSchemaV1 = never,
512
- > extends BaseCollectionConfig<T, TKey, TSchema> {
592
+ TUtils extends UtilsRecord = UtilsRecord,
593
+ > extends BaseCollectionConfig<T, TKey, TSchema, TUtils> {
513
594
  sync: SyncConfig<T, TKey>
514
595
  }
515
596
 
@@ -533,7 +614,8 @@ export type CollectionConfigSingleRowOption<
533
614
  T extends object = Record<string, unknown>,
534
615
  TKey extends string | number = string | number,
535
616
  TSchema extends StandardSchemaV1 = never,
536
- > = CollectionConfig<T, TKey, TSchema> & MaybeSingleResult
617
+ TUtils extends UtilsRecord = {},
618
+ > = CollectionConfig<T, TKey, TSchema, TUtils> & MaybeSingleResult
537
619
 
538
620
  export type ChangesPayload<T extends object = Record<string, unknown>> = Array<
539
621
  ChangeMessage<T>