@tanstack/db 0.0.23 → 0.0.25

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 (71) hide show
  1. package/dist/cjs/collection.cjs +60 -19
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +27 -6
  4. package/dist/cjs/local-only.cjs +2 -1
  5. package/dist/cjs/local-only.cjs.map +1 -1
  6. package/dist/cjs/local-storage.cjs +2 -1
  7. package/dist/cjs/local-storage.cjs.map +1 -1
  8. package/dist/cjs/proxy.cjs +105 -11
  9. package/dist/cjs/proxy.cjs.map +1 -1
  10. package/dist/cjs/proxy.d.cts +8 -0
  11. package/dist/cjs/query/builder/index.cjs +72 -0
  12. package/dist/cjs/query/builder/index.cjs.map +1 -1
  13. package/dist/cjs/query/builder/index.d.cts +64 -0
  14. package/dist/cjs/query/compiler/index.cjs +44 -8
  15. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  16. package/dist/cjs/query/compiler/index.d.cts +4 -7
  17. package/dist/cjs/query/compiler/joins.cjs +14 -6
  18. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  19. package/dist/cjs/query/compiler/joins.d.cts +4 -8
  20. package/dist/cjs/query/compiler/types.d.cts +10 -0
  21. package/dist/cjs/query/live-query-collection.cjs +2 -1
  22. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  23. package/dist/cjs/query/optimizer.cjs +283 -0
  24. package/dist/cjs/query/optimizer.cjs.map +1 -0
  25. package/dist/cjs/query/optimizer.d.cts +42 -0
  26. package/dist/cjs/types.d.cts +1 -0
  27. package/dist/cjs/utils.cjs +42 -0
  28. package/dist/cjs/utils.cjs.map +1 -0
  29. package/dist/cjs/utils.d.cts +18 -0
  30. package/dist/esm/collection.d.ts +27 -6
  31. package/dist/esm/collection.js +60 -19
  32. package/dist/esm/collection.js.map +1 -1
  33. package/dist/esm/local-only.js +2 -1
  34. package/dist/esm/local-only.js.map +1 -1
  35. package/dist/esm/local-storage.js +2 -1
  36. package/dist/esm/local-storage.js.map +1 -1
  37. package/dist/esm/proxy.d.ts +8 -0
  38. package/dist/esm/proxy.js +105 -11
  39. package/dist/esm/proxy.js.map +1 -1
  40. package/dist/esm/query/builder/index.d.ts +64 -0
  41. package/dist/esm/query/builder/index.js +72 -0
  42. package/dist/esm/query/builder/index.js.map +1 -1
  43. package/dist/esm/query/compiler/index.d.ts +4 -7
  44. package/dist/esm/query/compiler/index.js +44 -8
  45. package/dist/esm/query/compiler/index.js.map +1 -1
  46. package/dist/esm/query/compiler/joins.d.ts +4 -8
  47. package/dist/esm/query/compiler/joins.js +14 -6
  48. package/dist/esm/query/compiler/joins.js.map +1 -1
  49. package/dist/esm/query/compiler/types.d.ts +10 -0
  50. package/dist/esm/query/live-query-collection.js +2 -1
  51. package/dist/esm/query/live-query-collection.js.map +1 -1
  52. package/dist/esm/query/optimizer.d.ts +42 -0
  53. package/dist/esm/query/optimizer.js +283 -0
  54. package/dist/esm/query/optimizer.js.map +1 -0
  55. package/dist/esm/types.d.ts +1 -0
  56. package/dist/esm/utils.d.ts +18 -0
  57. package/dist/esm/utils.js +42 -0
  58. package/dist/esm/utils.js.map +1 -0
  59. package/package.json +1 -1
  60. package/src/collection.ts +75 -26
  61. package/src/local-only.ts +4 -1
  62. package/src/local-storage.ts +4 -1
  63. package/src/proxy.ts +152 -24
  64. package/src/query/builder/index.ts +104 -0
  65. package/src/query/compiler/index.ts +85 -18
  66. package/src/query/compiler/joins.ts +21 -13
  67. package/src/query/compiler/types.ts +12 -0
  68. package/src/query/live-query-collection.ts +3 -1
  69. package/src/query/optimizer.ts +738 -0
  70. package/src/types.ts +1 -0
  71. package/src/utils.ts +86 -0
@@ -1,4 +1,5 @@
1
1
  import { distinct, filter, map } from "@electric-sql/d2mini"
2
+ import { optimizeQuery } from "../optimizer.js"
2
3
  import { compileExpression } from "./evaluators.js"
3
4
  import { processJoins } from "./joins.js"
4
5
  import { processGroupBy } from "./group-by.js"
@@ -10,30 +11,35 @@ import type {
10
11
  NamespacedAndKeyedStream,
11
12
  ResultStream,
12
13
  } from "../../types.js"
13
-
14
- /**
15
- * Cache for compiled subqueries to avoid duplicate compilation
16
- */
17
- type QueryCache = WeakMap<QueryIR, ResultStream>
14
+ import type { QueryCache, QueryMapping } from "./types.js"
18
15
 
19
16
  /**
20
17
  * Compiles a query2 IR into a D2 pipeline
21
- * @param query The query IR to compile
18
+ * @param rawQuery The query IR to compile
22
19
  * @param inputs Mapping of collection names to input streams
23
20
  * @param cache Optional cache for compiled subqueries (used internally for recursion)
21
+ * @param queryMapping Optional mapping from optimized queries to original queries
24
22
  * @returns A stream builder representing the compiled query
25
23
  */
26
24
  export function compileQuery(
27
- query: QueryIR,
25
+ rawQuery: QueryIR,
28
26
  inputs: Record<string, KeyedStream>,
29
- cache: QueryCache = new WeakMap()
27
+ cache: QueryCache = new WeakMap(),
28
+ queryMapping: QueryMapping = new WeakMap()
30
29
  ): ResultStream {
31
- // Check if this query has already been compiled
32
- const cachedResult = cache.get(query)
30
+ // Check if the original raw query has already been compiled
31
+ const cachedResult = cache.get(rawQuery)
33
32
  if (cachedResult) {
34
33
  return cachedResult
35
34
  }
36
35
 
36
+ // Optimize the query before compilation
37
+ const query = optimizeQuery(rawQuery)
38
+
39
+ // Create mapping from optimized query to original for caching
40
+ queryMapping.set(query, rawQuery)
41
+ mapNestedQueries(query, rawQuery, queryMapping)
42
+
37
43
  // Create a copy of the inputs map to avoid modifying the original
38
44
  const allInputs = { ...inputs }
39
45
 
@@ -44,7 +50,8 @@ export function compileQuery(
44
50
  const { alias: mainTableAlias, input: mainInput } = processFrom(
45
51
  query.from,
46
52
  allInputs,
47
- cache
53
+ cache,
54
+ queryMapping
48
55
  )
49
56
  tables[mainTableAlias] = mainInput
50
57
 
@@ -68,7 +75,8 @@ export function compileQuery(
68
75
  tables,
69
76
  mainTableAlias,
70
77
  allInputs,
71
- cache
78
+ cache,
79
+ queryMapping
72
80
  )
73
81
  }
74
82
 
@@ -218,8 +226,8 @@ export function compileQuery(
218
226
  )
219
227
 
220
228
  const result = resultPipeline
221
- // Cache the result before returning
222
- cache.set(query, result)
229
+ // Cache the result before returning (use original query as key)
230
+ cache.set(rawQuery, result)
223
231
  return result
224
232
  } else if (query.limit !== undefined || query.offset !== undefined) {
225
233
  // If there's a limit or offset without orderBy, throw an error
@@ -241,8 +249,8 @@ export function compileQuery(
241
249
  )
242
250
 
243
251
  const result = resultPipeline
244
- // Cache the result before returning
245
- cache.set(query, result)
252
+ // Cache the result before returning (use original query as key)
253
+ cache.set(rawQuery, result)
246
254
  return result
247
255
  }
248
256
 
@@ -252,7 +260,8 @@ export function compileQuery(
252
260
  function processFrom(
253
261
  from: CollectionRef | QueryRef,
254
262
  allInputs: Record<string, KeyedStream>,
255
- cache: QueryCache
263
+ cache: QueryCache,
264
+ queryMapping: QueryMapping
256
265
  ): { alias: string; input: KeyedStream } {
257
266
  switch (from.type) {
258
267
  case `collectionRef`: {
@@ -265,8 +274,16 @@ function processFrom(
265
274
  return { alias: from.alias, input }
266
275
  }
267
276
  case `queryRef`: {
277
+ // Find the original query for caching purposes
278
+ const originalQuery = queryMapping.get(from.query) || from.query
279
+
268
280
  // Recursively compile the sub-query with cache
269
- const subQueryInput = compileQuery(from.query, allInputs, cache)
281
+ const subQueryInput = compileQuery(
282
+ originalQuery,
283
+ allInputs,
284
+ cache,
285
+ queryMapping
286
+ )
270
287
 
271
288
  // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)
272
289
  // We need to extract just the value for use in parent queries
@@ -283,3 +300,53 @@ function processFrom(
283
300
  throw new Error(`Unsupported FROM type: ${(from as any).type}`)
284
301
  }
285
302
  }
303
+
304
+ /**
305
+ * Recursively maps optimized subqueries to their original queries for proper caching.
306
+ * This ensures that when we encounter the same QueryRef object in different contexts,
307
+ * we can find the original query to check the cache.
308
+ */
309
+ function mapNestedQueries(
310
+ optimizedQuery: QueryIR,
311
+ originalQuery: QueryIR,
312
+ queryMapping: QueryMapping
313
+ ): void {
314
+ // Map the FROM clause if it's a QueryRef
315
+ if (
316
+ optimizedQuery.from.type === `queryRef` &&
317
+ originalQuery.from.type === `queryRef`
318
+ ) {
319
+ queryMapping.set(optimizedQuery.from.query, originalQuery.from.query)
320
+ // Recursively map nested queries
321
+ mapNestedQueries(
322
+ optimizedQuery.from.query,
323
+ originalQuery.from.query,
324
+ queryMapping
325
+ )
326
+ }
327
+
328
+ // Map JOIN clauses if they exist
329
+ if (optimizedQuery.join && originalQuery.join) {
330
+ for (
331
+ let i = 0;
332
+ i < optimizedQuery.join.length && i < originalQuery.join.length;
333
+ i++
334
+ ) {
335
+ const optimizedJoin = optimizedQuery.join[i]!
336
+ const originalJoin = originalQuery.join[i]!
337
+
338
+ if (
339
+ optimizedJoin.from.type === `queryRef` &&
340
+ originalJoin.from.type === `queryRef`
341
+ ) {
342
+ queryMapping.set(optimizedJoin.from.query, originalJoin.from.query)
343
+ // Recursively map nested queries in joins
344
+ mapNestedQueries(
345
+ optimizedJoin.from.query,
346
+ originalJoin.from.query,
347
+ queryMapping
348
+ )
349
+ }
350
+ }
351
+ }
352
+ }
@@ -7,18 +7,13 @@ import {
7
7
  import { compileExpression } from "./evaluators.js"
8
8
  import { compileQuery } from "./index.js"
9
9
  import type { IStreamBuilder, JoinType } from "@electric-sql/d2mini"
10
- import type { CollectionRef, JoinClause, QueryIR, QueryRef } from "../ir.js"
10
+ import type { CollectionRef, JoinClause, QueryRef } from "../ir.js"
11
11
  import type {
12
12
  KeyedStream,
13
13
  NamespacedAndKeyedStream,
14
14
  NamespacedRow,
15
- ResultStream,
16
15
  } from "../../types.js"
17
-
18
- /**
19
- * Cache for compiled subqueries to avoid duplicate compilation
20
- */
21
- type QueryCache = WeakMap<QueryIR, ResultStream>
16
+ import type { QueryCache, QueryMapping } from "./types.js"
22
17
 
23
18
  /**
24
19
  * Processes all join clauses in a query
@@ -29,7 +24,8 @@ export function processJoins(
29
24
  tables: Record<string, KeyedStream>,
30
25
  mainTableAlias: string,
31
26
  allInputs: Record<string, KeyedStream>,
32
- cache: QueryCache
27
+ cache: QueryCache,
28
+ queryMapping: QueryMapping
33
29
  ): NamespacedAndKeyedStream {
34
30
  let resultPipeline = pipeline
35
31
 
@@ -40,7 +36,8 @@ export function processJoins(
40
36
  tables,
41
37
  mainTableAlias,
42
38
  allInputs,
43
- cache
39
+ cache,
40
+ queryMapping
44
41
  )
45
42
  }
46
43
 
@@ -56,13 +53,15 @@ function processJoin(
56
53
  tables: Record<string, KeyedStream>,
57
54
  mainTableAlias: string,
58
55
  allInputs: Record<string, KeyedStream>,
59
- cache: QueryCache
56
+ cache: QueryCache,
57
+ queryMapping: QueryMapping
60
58
  ): NamespacedAndKeyedStream {
61
59
  // Get the joined table alias and input stream
62
60
  const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(
63
61
  joinClause.from,
64
62
  allInputs,
65
- cache
63
+ cache,
64
+ queryMapping
66
65
  )
67
66
 
68
67
  // Add the joined table to the tables map
@@ -128,7 +127,8 @@ function processJoin(
128
127
  function processJoinSource(
129
128
  from: CollectionRef | QueryRef,
130
129
  allInputs: Record<string, KeyedStream>,
131
- cache: QueryCache
130
+ cache: QueryCache,
131
+ queryMapping: QueryMapping
132
132
  ): { alias: string; input: KeyedStream } {
133
133
  switch (from.type) {
134
134
  case `collectionRef`: {
@@ -141,8 +141,16 @@ function processJoinSource(
141
141
  return { alias: from.alias, input }
142
142
  }
143
143
  case `queryRef`: {
144
+ // Find the original query for caching purposes
145
+ const originalQuery = queryMapping.get(from.query) || from.query
146
+
144
147
  // Recursively compile the sub-query with cache
145
- const subQueryInput = compileQuery(from.query, allInputs, cache)
148
+ const subQueryInput = compileQuery(
149
+ originalQuery,
150
+ allInputs,
151
+ cache,
152
+ queryMapping
153
+ )
146
154
 
147
155
  // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)
148
156
  // We need to extract just the value for use in parent queries
@@ -0,0 +1,12 @@
1
+ import type { QueryIR } from "../ir.js"
2
+ import type { ResultStream } from "../../types.js"
3
+
4
+ /**
5
+ * Cache for compiled subqueries to avoid duplicate compilation
6
+ */
7
+ export type QueryCache = WeakMap<QueryIR, ResultStream>
8
+
9
+ /**
10
+ * Mapping from optimized queries back to their original queries for caching
11
+ */
12
+ export type QueryMapping = WeakMap<QueryIR, QueryIR>
@@ -203,7 +203,7 @@ export function liveQueryCollectionOptions<
203
203
  // Create the sync configuration
204
204
  const sync: SyncConfig<TResult> = {
205
205
  rowUpdateMode: `full`,
206
- sync: ({ begin, write, commit, collection: theCollection }) => {
206
+ sync: ({ begin, write, commit, markReady, collection: theCollection }) => {
207
207
  const { graph, inputs, pipeline } = maybeCompileBasePipeline()
208
208
  let messagesCount = 0
209
209
  pipeline.pipe(
@@ -295,6 +295,8 @@ export function liveQueryCollectionOptions<
295
295
  begin()
296
296
  commit()
297
297
  }
298
+ // Mark the collection as ready after the first successful run
299
+ markReady()
298
300
  }
299
301
  }
300
302