@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.
- package/dist/cjs/collection/events.cjs +9 -51
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +18 -7
- package/dist/cjs/collection/index.cjs +9 -12
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +13 -14
- package/dist/cjs/collection/subscription.cjs +62 -6
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +16 -3
- package/dist/cjs/collection/sync.cjs +58 -6
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/collection/sync.d.cts +18 -4
- package/dist/cjs/errors.cjs +59 -17
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +44 -8
- package/dist/cjs/event-emitter.cjs +94 -0
- package/dist/cjs/event-emitter.cjs.map +1 -0
- package/dist/cjs/event-emitter.d.cts +45 -0
- package/dist/cjs/index.cjs +9 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-only.d.cts +2 -5
- package/dist/cjs/query/builder/types.d.cts +1 -1
- package/dist/cjs/query/compiler/index.cjs +46 -19
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +35 -9
- package/dist/cjs/query/compiler/joins.cjs +91 -66
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +6 -3
- package/dist/cjs/query/compiler/order-by.cjs +20 -4
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +3 -1
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/compiler/types.d.cts +4 -0
- package/dist/cjs/query/index.d.cts +1 -0
- package/dist/cjs/query/live/collection-config-builder.cjs +306 -46
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +97 -9
- package/dist/cjs/query/live/collection-registry.cjs +16 -0
- package/dist/cjs/query/live/collection-registry.cjs.map +1 -0
- package/dist/cjs/query/live/collection-registry.d.cts +26 -0
- package/dist/cjs/query/live/collection-subscriber.cjs +86 -58
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +5 -7
- package/dist/cjs/query/live-query-collection.cjs +11 -5
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +12 -5
- package/dist/cjs/query/optimizer.cjs +44 -7
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/optimizer.d.cts +4 -4
- package/dist/cjs/scheduler.cjs +137 -0
- package/dist/cjs/scheduler.cjs.map +1 -0
- package/dist/cjs/scheduler.d.cts +56 -0
- package/dist/cjs/transactions.cjs +7 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +82 -11
- package/dist/esm/collection/events.d.ts +18 -7
- package/dist/esm/collection/events.js +9 -51
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +13 -14
- package/dist/esm/collection/index.js +9 -12
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +16 -3
- package/dist/esm/collection/subscription.js +62 -6
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.d.ts +18 -4
- package/dist/esm/collection/sync.js +59 -7
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +44 -8
- package/dist/esm/errors.js +60 -18
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/event-emitter.d.ts +45 -0
- package/dist/esm/event-emitter.js +94 -0
- package/dist/esm/event-emitter.js.map +1 -0
- package/dist/esm/index.js +10 -5
- package/dist/esm/local-only.d.ts +2 -5
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +1 -1
- package/dist/esm/query/compiler/index.d.ts +35 -9
- package/dist/esm/query/compiler/index.js +46 -19
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +6 -3
- package/dist/esm/query/compiler/joins.js +93 -68
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +3 -1
- package/dist/esm/query/compiler/order-by.js +20 -4
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/compiler/types.d.ts +4 -0
- package/dist/esm/query/index.d.ts +1 -0
- package/dist/esm/query/live/collection-config-builder.d.ts +97 -9
- package/dist/esm/query/live/collection-config-builder.js +306 -46
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-registry.d.ts +26 -0
- package/dist/esm/query/live/collection-registry.js +16 -0
- package/dist/esm/query/live/collection-registry.js.map +1 -0
- package/dist/esm/query/live/collection-subscriber.d.ts +5 -7
- package/dist/esm/query/live/collection-subscriber.js +86 -58
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +12 -5
- package/dist/esm/query/live-query-collection.js +11 -5
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.d.ts +4 -4
- package/dist/esm/query/optimizer.js +44 -7
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/scheduler.d.ts +56 -0
- package/dist/esm/scheduler.js +137 -0
- package/dist/esm/scheduler.js.map +1 -0
- package/dist/esm/transactions.js +7 -1
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +82 -11
- package/package.json +2 -2
- package/src/collection/events.ts +25 -74
- package/src/collection/index.ts +15 -19
- package/src/collection/subscription.ts +88 -6
- package/src/collection/sync.ts +81 -9
- package/src/errors.ts +91 -13
- package/src/event-emitter.ts +118 -0
- package/src/local-only.ts +5 -12
- package/src/query/builder/types.ts +1 -1
- package/src/query/compiler/index.ts +124 -33
- package/src/query/compiler/joins.ts +187 -128
- package/src/query/compiler/order-by.ts +30 -2
- package/src/query/compiler/select.ts +2 -3
- package/src/query/compiler/types.ts +5 -0
- package/src/query/index.ts +1 -0
- package/src/query/live/collection-config-builder.ts +501 -60
- package/src/query/live/collection-registry.ts +47 -0
- package/src/query/live/collection-subscriber.ts +137 -105
- package/src/query/live-query-collection.ts +47 -18
- package/src/query/optimizer.ts +85 -15
- package/src/scheduler.ts +198 -0
- package/src/transactions.ts +12 -1
- 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
|
|
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
|
-
|
|
41
|
+
|
|
42
|
+
/** The compiled query pipeline (D2 stream) */
|
|
40
43
|
pipeline: ResultStream
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
72
|
+
* Compiles a query IR into a D2 pipeline
|
|
47
73
|
* @param rawQuery The query IR to compile
|
|
48
|
-
* @param inputs Mapping of
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
//
|
|
82
|
-
|
|
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
|
|
127
|
+
// Process the FROM clause to get the main source
|
|
85
128
|
const {
|
|
86
|
-
alias:
|
|
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
|
-
|
|
138
|
+
lazySources,
|
|
96
139
|
optimizableOrderByCollections,
|
|
140
|
+
setWindowFn,
|
|
97
141
|
cache,
|
|
98
|
-
queryMapping
|
|
142
|
+
queryMapping,
|
|
143
|
+
aliasToCollectionId,
|
|
144
|
+
aliasRemapping
|
|
99
145
|
)
|
|
100
|
-
|
|
146
|
+
sources[mainSource] = mainInput
|
|
101
147
|
|
|
102
|
-
// Prepare the initial pipeline with the main
|
|
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, { [
|
|
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
|
-
|
|
165
|
+
sources,
|
|
120
166
|
mainCollectionId,
|
|
121
|
-
|
|
167
|
+
mainSource,
|
|
122
168
|
allInputs,
|
|
123
169
|
cache,
|
|
124
170
|
queryMapping,
|
|
125
171
|
collections,
|
|
126
172
|
subscriptions,
|
|
127
173
|
callbacks,
|
|
128
|
-
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
398
|
+
const input = allInputs[from.alias]
|
|
341
399
|
if (!input) {
|
|
342
|
-
throw new CollectionInputNotFoundError(
|
|
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
|
-
|
|
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
|
|