@tanstack/db 0.4.7 → 0.4.9

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