@tanstack/db 0.4.8 → 0.4.10

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 (134) 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 +59 -17
  14. package/dist/cjs/errors.cjs.map +1 -1
  15. package/dist/cjs/errors.d.cts +44 -8
  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 +9 -4
  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/builder/types.d.cts +1 -1
  24. package/dist/cjs/query/compiler/index.cjs +46 -19
  25. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  26. package/dist/cjs/query/compiler/index.d.cts +35 -9
  27. package/dist/cjs/query/compiler/joins.cjs +91 -66
  28. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  29. package/dist/cjs/query/compiler/joins.d.cts +6 -3
  30. package/dist/cjs/query/compiler/order-by.cjs +20 -4
  31. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  32. package/dist/cjs/query/compiler/order-by.d.cts +3 -1
  33. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  34. package/dist/cjs/query/compiler/types.d.cts +4 -0
  35. package/dist/cjs/query/index.d.cts +1 -0
  36. package/dist/cjs/query/live/collection-config-builder.cjs +306 -46
  37. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  38. package/dist/cjs/query/live/collection-config-builder.d.cts +97 -9
  39. package/dist/cjs/query/live/collection-registry.cjs +16 -0
  40. package/dist/cjs/query/live/collection-registry.cjs.map +1 -0
  41. package/dist/cjs/query/live/collection-registry.d.cts +26 -0
  42. package/dist/cjs/query/live/collection-subscriber.cjs +86 -58
  43. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  44. package/dist/cjs/query/live/collection-subscriber.d.cts +5 -7
  45. package/dist/cjs/query/live-query-collection.cjs +11 -5
  46. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  47. package/dist/cjs/query/live-query-collection.d.cts +12 -5
  48. package/dist/cjs/query/optimizer.cjs +44 -7
  49. package/dist/cjs/query/optimizer.cjs.map +1 -1
  50. package/dist/cjs/query/optimizer.d.cts +4 -4
  51. package/dist/cjs/scheduler.cjs +137 -0
  52. package/dist/cjs/scheduler.cjs.map +1 -0
  53. package/dist/cjs/scheduler.d.cts +56 -0
  54. package/dist/cjs/transactions.cjs +7 -1
  55. package/dist/cjs/transactions.cjs.map +1 -1
  56. package/dist/cjs/types.d.cts +82 -11
  57. package/dist/esm/collection/events.d.ts +18 -7
  58. package/dist/esm/collection/events.js +9 -51
  59. package/dist/esm/collection/events.js.map +1 -1
  60. package/dist/esm/collection/index.d.ts +13 -14
  61. package/dist/esm/collection/index.js +9 -12
  62. package/dist/esm/collection/index.js.map +1 -1
  63. package/dist/esm/collection/subscription.d.ts +16 -3
  64. package/dist/esm/collection/subscription.js +62 -6
  65. package/dist/esm/collection/subscription.js.map +1 -1
  66. package/dist/esm/collection/sync.d.ts +18 -4
  67. package/dist/esm/collection/sync.js +59 -7
  68. package/dist/esm/collection/sync.js.map +1 -1
  69. package/dist/esm/errors.d.ts +44 -8
  70. package/dist/esm/errors.js +60 -18
  71. package/dist/esm/errors.js.map +1 -1
  72. package/dist/esm/event-emitter.d.ts +45 -0
  73. package/dist/esm/event-emitter.js +94 -0
  74. package/dist/esm/event-emitter.js.map +1 -0
  75. package/dist/esm/index.js +10 -5
  76. package/dist/esm/local-only.d.ts +2 -5
  77. package/dist/esm/local-only.js.map +1 -1
  78. package/dist/esm/query/builder/types.d.ts +1 -1
  79. package/dist/esm/query/compiler/index.d.ts +35 -9
  80. package/dist/esm/query/compiler/index.js +46 -19
  81. package/dist/esm/query/compiler/index.js.map +1 -1
  82. package/dist/esm/query/compiler/joins.d.ts +6 -3
  83. package/dist/esm/query/compiler/joins.js +93 -68
  84. package/dist/esm/query/compiler/joins.js.map +1 -1
  85. package/dist/esm/query/compiler/order-by.d.ts +3 -1
  86. package/dist/esm/query/compiler/order-by.js +20 -4
  87. package/dist/esm/query/compiler/order-by.js.map +1 -1
  88. package/dist/esm/query/compiler/select.js.map +1 -1
  89. package/dist/esm/query/compiler/types.d.ts +4 -0
  90. package/dist/esm/query/index.d.ts +1 -0
  91. package/dist/esm/query/live/collection-config-builder.d.ts +97 -9
  92. package/dist/esm/query/live/collection-config-builder.js +306 -46
  93. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  94. package/dist/esm/query/live/collection-registry.d.ts +26 -0
  95. package/dist/esm/query/live/collection-registry.js +16 -0
  96. package/dist/esm/query/live/collection-registry.js.map +1 -0
  97. package/dist/esm/query/live/collection-subscriber.d.ts +5 -7
  98. package/dist/esm/query/live/collection-subscriber.js +86 -58
  99. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  100. package/dist/esm/query/live-query-collection.d.ts +12 -5
  101. package/dist/esm/query/live-query-collection.js +11 -5
  102. package/dist/esm/query/live-query-collection.js.map +1 -1
  103. package/dist/esm/query/optimizer.d.ts +4 -4
  104. package/dist/esm/query/optimizer.js +44 -7
  105. package/dist/esm/query/optimizer.js.map +1 -1
  106. package/dist/esm/scheduler.d.ts +56 -0
  107. package/dist/esm/scheduler.js +137 -0
  108. package/dist/esm/scheduler.js.map +1 -0
  109. package/dist/esm/transactions.js +7 -1
  110. package/dist/esm/transactions.js.map +1 -1
  111. package/dist/esm/types.d.ts +82 -11
  112. package/package.json +2 -2
  113. package/src/collection/events.ts +25 -74
  114. package/src/collection/index.ts +15 -19
  115. package/src/collection/subscription.ts +88 -6
  116. package/src/collection/sync.ts +81 -9
  117. package/src/errors.ts +91 -13
  118. package/src/event-emitter.ts +118 -0
  119. package/src/local-only.ts +5 -12
  120. package/src/query/builder/types.ts +1 -1
  121. package/src/query/compiler/index.ts +124 -33
  122. package/src/query/compiler/joins.ts +187 -128
  123. package/src/query/compiler/order-by.ts +30 -2
  124. package/src/query/compiler/select.ts +2 -3
  125. package/src/query/compiler/types.ts +5 -0
  126. package/src/query/index.ts +1 -0
  127. package/src/query/live/collection-config-builder.ts +501 -60
  128. package/src/query/live/collection-registry.ts +47 -0
  129. package/src/query/live/collection-subscriber.ts +137 -105
  130. package/src/query/live-query-collection.ts +47 -18
  131. package/src/query/optimizer.ts +85 -15
  132. package/src/scheduler.ts +198 -0
  133. package/src/transactions.ts +12 -1
  134. package/src/types.ts +93 -11
@@ -28,27 +28,58 @@ import type {
28
28
  NamespacedAndKeyedStream,
29
29
  ResultStream,
30
30
  } from "../../types.js"
31
- import type { QueryCache, QueryMapping } from "./types.js"
31
+ import type { QueryCache, QueryMapping, WindowOptions } from "./types.js"
32
+
33
+ export type { WindowOptions } from "./types.js"
32
34
 
33
35
  /**
34
- * Result of query compilation including both the pipeline and collection-specific WHERE clauses
36
+ * Result of query compilation including both the pipeline and source-specific WHERE clauses
35
37
  */
36
38
  export interface CompilationResult {
37
39
  /** The ID of the main collection */
38
40
  collectionId: string
39
- /** The compiled query pipeline */
41
+
42
+ /** The compiled query pipeline (D2 stream) */
40
43
  pipeline: ResultStream
41
- /** Map of collection aliases to their WHERE clauses for index optimization */
42
- collectionWhereClauses: Map<string, BasicExpression<boolean>>
44
+
45
+ /** Map of source aliases to their WHERE clauses for index optimization */
46
+ sourceWhereClauses: Map<string, BasicExpression<boolean>>
47
+
48
+ /**
49
+ * Maps each source alias to its collection ID. Enables per-alias subscriptions for self-joins.
50
+ * Example: `{ employee: 'employees-col-id', manager: 'employees-col-id' }`
51
+ */
52
+ aliasToCollectionId: Record<string, string>
53
+
54
+ /**
55
+ * Flattened mapping from outer alias to innermost alias for subqueries.
56
+ * Always provides one-hop lookups, never recursive chains.
57
+ *
58
+ * Example: `{ activeUser: 'user' }` when `.from({ activeUser: subquery })`
59
+ * where the subquery uses `.from({ user: collection })`.
60
+ *
61
+ * For deeply nested subqueries, the mapping goes directly to the innermost alias:
62
+ * `{ author: 'user' }` (not `{ author: 'activeUser' }`), so `aliasRemapping[alias]`
63
+ * always resolves in a single lookup.
64
+ *
65
+ * Used to resolve subscriptions during lazy loading when join aliases differ from
66
+ * the inner aliases where collection subscriptions were created.
67
+ */
68
+ aliasRemapping: Record<string, string>
43
69
  }
44
70
 
45
71
  /**
46
- * Compiles a query2 IR into a D2 pipeline
72
+ * Compiles a query IR into a D2 pipeline
47
73
  * @param rawQuery The query IR to compile
48
- * @param inputs Mapping of collection names to input streams
74
+ * @param inputs Mapping of source aliases to input streams (e.g., `{ employee: input1, manager: input2 }`)
75
+ * @param collections Mapping of collection IDs to Collection instances
76
+ * @param subscriptions Mapping of source aliases to CollectionSubscription instances
77
+ * @param callbacks Mapping of source aliases to lazy loading callbacks
78
+ * @param lazySources Set of source aliases that should load data lazily
79
+ * @param optimizableOrderByCollections Map of collection IDs to order-by optimization info
49
80
  * @param cache Optional cache for compiled subqueries (used internally for recursion)
50
81
  * @param queryMapping Optional mapping from optimized queries to original queries
51
- * @returns A CompilationResult with the pipeline and collection WHERE clauses
82
+ * @returns A CompilationResult with the pipeline, source WHERE clauses, and alias metadata
52
83
  */
53
84
  export function compileQuery(
54
85
  rawQuery: QueryIR,
@@ -56,8 +87,9 @@ export function compileQuery(
56
87
  collections: Record<string, Collection<any, any, any, any, any>>,
57
88
  subscriptions: Record<string, CollectionSubscription>,
58
89
  callbacks: Record<string, LazyCollectionCallbacks>,
59
- lazyCollections: Set<string>,
90
+ lazySources: Set<string>,
60
91
  optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
92
+ setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
61
93
  cache: QueryCache = new WeakMap(),
62
94
  queryMapping: QueryMapping = new WeakMap()
63
95
  ): CompilationResult {
@@ -68,8 +100,7 @@ export function compileQuery(
68
100
  }
69
101
 
70
102
  // Optimize the query before compilation
71
- const { optimizedQuery: query, collectionWhereClauses } =
72
- optimizeQuery(rawQuery)
103
+ const { optimizedQuery: query, sourceWhereClauses } = optimizeQuery(rawQuery)
73
104
 
74
105
  // Create mapping from optimized query to original for caching
75
106
  queryMapping.set(query, rawQuery)
@@ -78,12 +109,24 @@ export function compileQuery(
78
109
  // Create a copy of the inputs map to avoid modifying the original
79
110
  const allInputs = { ...inputs }
80
111
 
81
- // Create a map of table aliases to inputs
82
- const tables: Record<string, KeyedStream> = {}
112
+ // Track alias to collection id relationships discovered during compilation.
113
+ // This includes all user-declared aliases plus inner aliases from subqueries.
114
+ const aliasToCollectionId: Record<string, string> = {}
115
+
116
+ // Track alias remapping for subqueries (outer alias → inner alias)
117
+ // e.g., when .join({ activeUser: subquery }) where subquery uses .from({ user: collection })
118
+ // we store: aliasRemapping['activeUser'] = 'user'
119
+ const aliasRemapping: Record<string, string> = {}
120
+
121
+ // Create a map of source aliases to input streams.
122
+ // Inputs MUST be keyed by alias (e.g., `{ employee: input1, manager: input2 }`),
123
+ // not by collection ID. This enables per-alias subscriptions where different aliases
124
+ // of the same collection (e.g., self-joins) maintain independent filtered streams.
125
+ const sources: Record<string, KeyedStream> = {}
83
126
 
84
- // Process the FROM clause to get the main table
127
+ // Process the FROM clause to get the main source
85
128
  const {
86
- alias: mainTableAlias,
129
+ alias: mainSource,
87
130
  input: mainInput,
88
131
  collectionId: mainCollectionId,
89
132
  } = processFrom(
@@ -92,18 +135,21 @@ export function compileQuery(
92
135
  collections,
93
136
  subscriptions,
94
137
  callbacks,
95
- lazyCollections,
138
+ lazySources,
96
139
  optimizableOrderByCollections,
140
+ setWindowFn,
97
141
  cache,
98
- queryMapping
142
+ queryMapping,
143
+ aliasToCollectionId,
144
+ aliasRemapping
99
145
  )
100
- tables[mainTableAlias] = mainInput
146
+ sources[mainSource] = mainInput
101
147
 
102
- // Prepare the initial pipeline with the main table wrapped in its alias
148
+ // Prepare the initial pipeline with the main source wrapped in its alias
103
149
  let pipeline: NamespacedAndKeyedStream = mainInput.pipe(
104
150
  map(([key, row]) => {
105
151
  // Initialize the record with a nested structure
106
- const ret = [key, { [mainTableAlias]: row }] as [
152
+ const ret = [key, { [mainSource]: row }] as [
107
153
  string,
108
154
  Record<string, typeof row>,
109
155
  ]
@@ -116,19 +162,22 @@ export function compileQuery(
116
162
  pipeline = processJoins(
117
163
  pipeline,
118
164
  query.join,
119
- tables,
165
+ sources,
120
166
  mainCollectionId,
121
- mainTableAlias,
167
+ mainSource,
122
168
  allInputs,
123
169
  cache,
124
170
  queryMapping,
125
171
  collections,
126
172
  subscriptions,
127
173
  callbacks,
128
- lazyCollections,
174
+ lazySources,
129
175
  optimizableOrderByCollections,
176
+ setWindowFn,
130
177
  rawQuery,
131
- compileQuery
178
+ compileQuery,
179
+ aliasToCollectionId,
180
+ aliasRemapping
132
181
  )
133
182
  }
134
183
 
@@ -185,7 +234,7 @@ export function compileQuery(
185
234
  map(([key, namespacedRow]) => {
186
235
  const selectResults =
187
236
  !query.join && !query.groupBy
188
- ? namespacedRow[mainTableAlias]
237
+ ? namespacedRow[mainSource]
189
238
  : namespacedRow
190
239
 
191
240
  return [
@@ -267,6 +316,7 @@ export function compileQuery(
267
316
  query.select || {},
268
317
  collections[mainCollectionId]!,
269
318
  optimizableOrderByCollections,
319
+ setWindowFn,
270
320
  query.limit,
271
321
  query.offset
272
322
  )
@@ -286,7 +336,9 @@ export function compileQuery(
286
336
  const compilationResult = {
287
337
  collectionId: mainCollectionId,
288
338
  pipeline: result,
289
- collectionWhereClauses,
339
+ sourceWhereClauses,
340
+ aliasToCollectionId,
341
+ aliasRemapping,
290
342
  }
291
343
  cache.set(rawQuery, compilationResult)
292
344
 
@@ -314,7 +366,9 @@ export function compileQuery(
314
366
  const compilationResult = {
315
367
  collectionId: mainCollectionId,
316
368
  pipeline: result,
317
- collectionWhereClauses,
369
+ sourceWhereClauses,
370
+ aliasToCollectionId,
371
+ aliasRemapping,
318
372
  }
319
373
  cache.set(rawQuery, compilationResult)
320
374
 
@@ -322,7 +376,8 @@ export function compileQuery(
322
376
  }
323
377
 
324
378
  /**
325
- * Processes the FROM clause to extract the main table alias and input stream
379
+ * Processes the FROM clause, handling direct collection references and subqueries.
380
+ * Populates `aliasToCollectionId` and `aliasRemapping` for per-alias subscription tracking.
326
381
  */
327
382
  function processFrom(
328
383
  from: CollectionRef | QueryRef,
@@ -330,17 +385,25 @@ function processFrom(
330
385
  collections: Record<string, Collection>,
331
386
  subscriptions: Record<string, CollectionSubscription>,
332
387
  callbacks: Record<string, LazyCollectionCallbacks>,
333
- lazyCollections: Set<string>,
388
+ lazySources: Set<string>,
334
389
  optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
390
+ setWindowFn: (windowFn: (options: WindowOptions) => void) => void,
335
391
  cache: QueryCache,
336
- queryMapping: QueryMapping
392
+ queryMapping: QueryMapping,
393
+ aliasToCollectionId: Record<string, string>,
394
+ aliasRemapping: Record<string, string>
337
395
  ): { alias: string; input: KeyedStream; collectionId: string } {
338
396
  switch (from.type) {
339
397
  case `collectionRef`: {
340
- const input = allInputs[from.collection.id]
398
+ const input = allInputs[from.alias]
341
399
  if (!input) {
342
- throw new CollectionInputNotFoundError(from.collection.id)
400
+ throw new CollectionInputNotFoundError(
401
+ from.alias,
402
+ from.collection.id,
403
+ Object.keys(allInputs)
404
+ )
343
405
  }
406
+ aliasToCollectionId[from.alias] = from.collection.id
344
407
  return { alias: from.alias, input, collectionId: from.collection.id }
345
408
  }
346
409
  case `queryRef`: {
@@ -354,12 +417,40 @@ function processFrom(
354
417
  collections,
355
418
  subscriptions,
356
419
  callbacks,
357
- lazyCollections,
420
+ lazySources,
358
421
  optimizableOrderByCollections,
422
+ setWindowFn,
359
423
  cache,
360
424
  queryMapping
361
425
  )
362
426
 
427
+ // Pull up alias mappings from subquery to parent scope.
428
+ // This includes both the innermost alias-to-collection mappings AND
429
+ // any existing remappings from nested subquery levels.
430
+ Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId)
431
+ Object.assign(aliasRemapping, subQueryResult.aliasRemapping)
432
+
433
+ // Create a FLATTENED remapping from outer alias to innermost alias.
434
+ // For nested subqueries, this ensures one-hop lookups (not recursive chains).
435
+ //
436
+ // Example with 3-level nesting:
437
+ // Inner: .from({ user: usersCollection })
438
+ // Middle: .from({ activeUser: innerSubquery }) → creates: activeUser → user
439
+ // Outer: .from({ author: middleSubquery }) → creates: author → user (not author → activeUser)
440
+ //
441
+ // The key insight: We search through the PULLED-UP aliasToCollectionId (which contains
442
+ // the innermost 'user' alias), so we always map directly to the deepest level.
443
+ // This means aliasRemapping[alias] is always a single lookup, never recursive.
444
+ // Needed for subscription resolution during lazy loading.
445
+ const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(
446
+ (alias) =>
447
+ subQueryResult.aliasToCollectionId[alias] ===
448
+ subQueryResult.collectionId
449
+ )
450
+ if (innerAlias && innerAlias !== from.alias) {
451
+ aliasRemapping[from.alias] = innerAlias
452
+ }
453
+
363
454
  // Extract the pipeline from the compilation result
364
455
  const subQueryInput = subQueryResult.pipeline
365
456