@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,22 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const d2mini = require("@electric-sql/d2mini");
4
+ const optimizer = require("../optimizer.cjs");
4
5
  const evaluators = require("./evaluators.cjs");
5
6
  const joins = require("./joins.cjs");
6
7
  const groupBy = require("./group-by.cjs");
7
8
  const orderBy = require("./order-by.cjs");
8
9
  const select = require("./select.cjs");
9
- function compileQuery(query, inputs, cache = /* @__PURE__ */ new WeakMap()) {
10
- const cachedResult = cache.get(query);
10
+ function compileQuery(rawQuery, inputs, cache = /* @__PURE__ */ new WeakMap(), queryMapping = /* @__PURE__ */ new WeakMap()) {
11
+ const cachedResult = cache.get(rawQuery);
11
12
  if (cachedResult) {
12
13
  return cachedResult;
13
14
  }
15
+ const query = optimizer.optimizeQuery(rawQuery);
16
+ queryMapping.set(query, rawQuery);
17
+ mapNestedQueries(query, rawQuery, queryMapping);
14
18
  const allInputs = { ...inputs };
15
19
  const tables = {};
16
20
  const { alias: mainTableAlias, input: mainInput } = processFrom(
17
21
  query.from,
18
22
  allInputs,
19
- cache
23
+ cache,
24
+ queryMapping
20
25
  );
21
26
  tables[mainTableAlias] = mainInput;
22
27
  let pipeline = mainInput.pipe(
@@ -32,7 +37,8 @@ function compileQuery(query, inputs, cache = /* @__PURE__ */ new WeakMap()) {
32
37
  tables,
33
38
  mainTableAlias,
34
39
  allInputs,
35
- cache
40
+ cache,
41
+ queryMapping
36
42
  );
37
43
  }
38
44
  if (query.where && query.where.length > 0) {
@@ -141,7 +147,7 @@ function compileQuery(query, inputs, cache = /* @__PURE__ */ new WeakMap()) {
141
147
  })
142
148
  );
143
149
  const result2 = resultPipeline2;
144
- cache.set(query, result2);
150
+ cache.set(rawQuery, result2);
145
151
  return result2;
146
152
  } else if (query.limit !== void 0 || query.offset !== void 0) {
147
153
  throw new Error(
@@ -155,10 +161,10 @@ function compileQuery(query, inputs, cache = /* @__PURE__ */ new WeakMap()) {
155
161
  })
156
162
  );
157
163
  const result = resultPipeline;
158
- cache.set(query, result);
164
+ cache.set(rawQuery, result);
159
165
  return result;
160
166
  }
161
- function processFrom(from, allInputs, cache) {
167
+ function processFrom(from, allInputs, cache, queryMapping) {
162
168
  switch (from.type) {
163
169
  case `collectionRef`: {
164
170
  const input = allInputs[from.collection.id];
@@ -170,7 +176,13 @@ function processFrom(from, allInputs, cache) {
170
176
  return { alias: from.alias, input };
171
177
  }
172
178
  case `queryRef`: {
173
- const subQueryInput = compileQuery(from.query, allInputs, cache);
179
+ const originalQuery = queryMapping.get(from.query) || from.query;
180
+ const subQueryInput = compileQuery(
181
+ originalQuery,
182
+ allInputs,
183
+ cache,
184
+ queryMapping
185
+ );
174
186
  const extractedInput = subQueryInput.pipe(
175
187
  d2mini.map((data) => {
176
188
  const [key, [value, _orderByIndex]] = data;
@@ -183,5 +195,29 @@ function processFrom(from, allInputs, cache) {
183
195
  throw new Error(`Unsupported FROM type: ${from.type}`);
184
196
  }
185
197
  }
198
+ function mapNestedQueries(optimizedQuery, originalQuery, queryMapping) {
199
+ if (optimizedQuery.from.type === `queryRef` && originalQuery.from.type === `queryRef`) {
200
+ queryMapping.set(optimizedQuery.from.query, originalQuery.from.query);
201
+ mapNestedQueries(
202
+ optimizedQuery.from.query,
203
+ originalQuery.from.query,
204
+ queryMapping
205
+ );
206
+ }
207
+ if (optimizedQuery.join && originalQuery.join) {
208
+ for (let i = 0; i < optimizedQuery.join.length && i < originalQuery.join.length; i++) {
209
+ const optimizedJoin = optimizedQuery.join[i];
210
+ const originalJoin = originalQuery.join[i];
211
+ if (optimizedJoin.from.type === `queryRef` && originalJoin.from.type === `queryRef`) {
212
+ queryMapping.set(optimizedJoin.from.query, originalJoin.from.query);
213
+ mapNestedQueries(
214
+ optimizedJoin.from.query,
215
+ originalJoin.from.query,
216
+ queryMapping
217
+ );
218
+ }
219
+ }
220
+ }
221
+ }
186
222
  exports.compileQuery = compileQuery;
187
223
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../../src/query/compiler/index.ts"],"sourcesContent":["import { distinct, filter, map } from \"@electric-sql/d2mini\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { processJoins } from \"./joins.js\"\nimport { processGroupBy } from \"./group-by.js\"\nimport { processOrderBy } from \"./order-by.js\"\nimport { processSelectToResults } from \"./select.js\"\nimport type { CollectionRef, QueryIR, QueryRef } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n ResultStream,\n} from \"../../types.js\"\n\n/**\n * Cache for compiled subqueries to avoid duplicate compilation\n */\ntype QueryCache = WeakMap<QueryIR, ResultStream>\n\n/**\n * Compiles a query2 IR into a D2 pipeline\n * @param query The query IR to compile\n * @param inputs Mapping of collection names to input streams\n * @param cache Optional cache for compiled subqueries (used internally for recursion)\n * @returns A stream builder representing the compiled query\n */\nexport function compileQuery(\n query: QueryIR,\n inputs: Record<string, KeyedStream>,\n cache: QueryCache = new WeakMap()\n): ResultStream {\n // Check if this query has already been compiled\n const cachedResult = cache.get(query)\n if (cachedResult) {\n return cachedResult\n }\n\n // Create a copy of the inputs map to avoid modifying the original\n const allInputs = { ...inputs }\n\n // Create a map of table aliases to inputs\n const tables: Record<string, KeyedStream> = {}\n\n // Process the FROM clause to get the main table\n const { alias: mainTableAlias, input: mainInput } = processFrom(\n query.from,\n allInputs,\n cache\n )\n tables[mainTableAlias] = mainInput\n\n // Prepare the initial pipeline with the main table wrapped in its alias\n let pipeline: NamespacedAndKeyedStream = mainInput.pipe(\n map(([key, row]) => {\n // Initialize the record with a nested structure\n const ret = [key, { [mainTableAlias]: row }] as [\n string,\n Record<string, typeof row>,\n ]\n return ret\n })\n )\n\n // Process JOIN clauses if they exist\n if (query.join && query.join.length > 0) {\n pipeline = processJoins(\n pipeline,\n query.join,\n tables,\n mainTableAlias,\n allInputs,\n cache\n )\n }\n\n // Process the WHERE clause if it exists\n if (query.where && query.where.length > 0) {\n // Compile all WHERE expressions\n const compiledWheres = query.where.map((where) => compileExpression(where))\n\n // Apply each WHERE condition as a filter (they are ANDed together)\n for (const compiledWhere of compiledWheres) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return compiledWhere(namespacedRow)\n })\n )\n }\n }\n\n // Process functional WHERE clauses if they exist\n if (query.fnWhere && query.fnWhere.length > 0) {\n for (const fnWhere of query.fnWhere) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnWhere(namespacedRow)\n })\n )\n }\n }\n\n if (query.distinct && !query.fnSelect && !query.select) {\n throw new Error(`DISTINCT requires a SELECT clause.`)\n }\n\n // Process the SELECT clause early - always create __select_results\n // This eliminates duplication and allows for DISTINCT implementation\n if (query.fnSelect) {\n // Handle functional select - apply the function to transform the row\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults = query.fnSelect!(namespacedRow)\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n } else if (query.select) {\n pipeline = processSelectToResults(pipeline, query.select, allInputs)\n } else {\n // If no SELECT clause, create __select_results with the main table data\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults =\n !query.join && !query.groupBy\n ? namespacedRow[mainTableAlias]\n : namespacedRow\n\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n }\n\n // Process the GROUP BY clause if it exists\n if (query.groupBy && query.groupBy.length > 0) {\n pipeline = processGroupBy(\n pipeline,\n query.groupBy,\n query.having,\n query.select,\n query.fnHaving\n )\n } else if (query.select) {\n // Check if SELECT contains aggregates but no GROUP BY (implicit single-group aggregation)\n const hasAggregates = Object.values(query.select).some(\n (expr) => expr.type === `agg`\n )\n if (hasAggregates) {\n // Handle implicit single-group aggregation\n pipeline = processGroupBy(\n pipeline,\n [], // Empty group by means single group\n query.having,\n query.select,\n query.fnHaving\n )\n }\n }\n\n // Process the HAVING clause if it exists (only applies after GROUP BY)\n if (query.having && (!query.groupBy || query.groupBy.length === 0)) {\n // Check if we have aggregates in SELECT that would trigger implicit grouping\n const hasAggregates = query.select\n ? Object.values(query.select).some((expr) => expr.type === `agg`)\n : false\n\n if (!hasAggregates) {\n throw new Error(`HAVING clause requires GROUP BY clause`)\n }\n }\n\n // Process functional HAVING clauses outside of GROUP BY (treat as additional WHERE filters)\n if (\n query.fnHaving &&\n query.fnHaving.length > 0 &&\n (!query.groupBy || query.groupBy.length === 0)\n ) {\n // If there's no GROUP BY but there are fnHaving clauses, apply them as filters\n for (const fnHaving of query.fnHaving) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n // Process the DISTINCT clause if it exists\n if (query.distinct) {\n pipeline = pipeline.pipe(distinct(([_key, row]) => row.__select_results))\n }\n\n // Process orderBy parameter if it exists\n if (query.orderBy && query.orderBy.length > 0) {\n const orderedPipeline = processOrderBy(\n pipeline,\n query.orderBy,\n query.limit,\n query.offset\n )\n\n // Final step: extract the __select_results and include orderBy index\n const resultPipeline = orderedPipeline.pipe(\n map(([key, [row, orderByIndex]]) => {\n // Extract the final results from __select_results and include orderBy index\n const finalResults = (row as any).__select_results\n return [key, [finalResults, orderByIndex]] as [unknown, [any, string]]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning\n cache.set(query, result)\n return result\n } else if (query.limit !== undefined || query.offset !== undefined) {\n // If there's a limit or offset without orderBy, throw an error\n throw new Error(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n\n // Final step: extract the __select_results and return tuple format (no orderBy)\n const resultPipeline: ResultStream = pipeline.pipe(\n map(([key, row]) => {\n // Extract the final results from __select_results and return [key, [results, undefined]]\n const finalResults = (row as any).__select_results\n return [key, [finalResults, undefined]] as [\n unknown,\n [any, string | undefined],\n ]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning\n cache.set(query, result)\n return result\n}\n\n/**\n * Processes the FROM clause to extract the main table alias and input stream\n */\nfunction processFrom(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache\n): { alias: string; input: KeyedStream } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new Error(\n `Input for collection \"${from.collection.id}\" not found in inputs map`\n )\n }\n return { alias: from.alias, input }\n }\n case `queryRef`: {\n // Recursively compile the sub-query with cache\n const subQueryInput = compileQuery(from.query, allInputs, cache)\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return { alias: from.alias, input: extractedInput }\n }\n default:\n throw new Error(`Unsupported FROM type: ${(from as any).type}`)\n }\n}\n"],"names":["map","processJoins","compileExpression","filter","processSelectToResults","processGroupBy","distinct","processOrderBy","resultPipeline","result"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../../../src/query/compiler/index.ts"],"sourcesContent":["import { distinct, filter, map } from \"@electric-sql/d2mini\"\nimport { optimizeQuery } from \"../optimizer.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { processJoins } from \"./joins.js\"\nimport { processGroupBy } from \"./group-by.js\"\nimport { processOrderBy } from \"./order-by.js\"\nimport { processSelectToResults } from \"./select.js\"\nimport type { CollectionRef, QueryIR, QueryRef } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n ResultStream,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\n\n/**\n * Compiles a query2 IR into a D2 pipeline\n * @param rawQuery The query IR to compile\n * @param inputs Mapping of collection names to input streams\n * @param cache Optional cache for compiled subqueries (used internally for recursion)\n * @param queryMapping Optional mapping from optimized queries to original queries\n * @returns A stream builder representing the compiled query\n */\nexport function compileQuery(\n rawQuery: QueryIR,\n inputs: Record<string, KeyedStream>,\n cache: QueryCache = new WeakMap(),\n queryMapping: QueryMapping = new WeakMap()\n): ResultStream {\n // Check if the original raw query has already been compiled\n const cachedResult = cache.get(rawQuery)\n if (cachedResult) {\n return cachedResult\n }\n\n // Optimize the query before compilation\n const query = optimizeQuery(rawQuery)\n\n // Create mapping from optimized query to original for caching\n queryMapping.set(query, rawQuery)\n mapNestedQueries(query, rawQuery, queryMapping)\n\n // Create a copy of the inputs map to avoid modifying the original\n const allInputs = { ...inputs }\n\n // Create a map of table aliases to inputs\n const tables: Record<string, KeyedStream> = {}\n\n // Process the FROM clause to get the main table\n const { alias: mainTableAlias, input: mainInput } = processFrom(\n query.from,\n allInputs,\n cache,\n queryMapping\n )\n tables[mainTableAlias] = mainInput\n\n // Prepare the initial pipeline with the main table wrapped in its alias\n let pipeline: NamespacedAndKeyedStream = mainInput.pipe(\n map(([key, row]) => {\n // Initialize the record with a nested structure\n const ret = [key, { [mainTableAlias]: row }] as [\n string,\n Record<string, typeof row>,\n ]\n return ret\n })\n )\n\n // Process JOIN clauses if they exist\n if (query.join && query.join.length > 0) {\n pipeline = processJoins(\n pipeline,\n query.join,\n tables,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping\n )\n }\n\n // Process the WHERE clause if it exists\n if (query.where && query.where.length > 0) {\n // Compile all WHERE expressions\n const compiledWheres = query.where.map((where) => compileExpression(where))\n\n // Apply each WHERE condition as a filter (they are ANDed together)\n for (const compiledWhere of compiledWheres) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return compiledWhere(namespacedRow)\n })\n )\n }\n }\n\n // Process functional WHERE clauses if they exist\n if (query.fnWhere && query.fnWhere.length > 0) {\n for (const fnWhere of query.fnWhere) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnWhere(namespacedRow)\n })\n )\n }\n }\n\n if (query.distinct && !query.fnSelect && !query.select) {\n throw new Error(`DISTINCT requires a SELECT clause.`)\n }\n\n // Process the SELECT clause early - always create __select_results\n // This eliminates duplication and allows for DISTINCT implementation\n if (query.fnSelect) {\n // Handle functional select - apply the function to transform the row\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults = query.fnSelect!(namespacedRow)\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n } else if (query.select) {\n pipeline = processSelectToResults(pipeline, query.select, allInputs)\n } else {\n // If no SELECT clause, create __select_results with the main table data\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults =\n !query.join && !query.groupBy\n ? namespacedRow[mainTableAlias]\n : namespacedRow\n\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n }\n\n // Process the GROUP BY clause if it exists\n if (query.groupBy && query.groupBy.length > 0) {\n pipeline = processGroupBy(\n pipeline,\n query.groupBy,\n query.having,\n query.select,\n query.fnHaving\n )\n } else if (query.select) {\n // Check if SELECT contains aggregates but no GROUP BY (implicit single-group aggregation)\n const hasAggregates = Object.values(query.select).some(\n (expr) => expr.type === `agg`\n )\n if (hasAggregates) {\n // Handle implicit single-group aggregation\n pipeline = processGroupBy(\n pipeline,\n [], // Empty group by means single group\n query.having,\n query.select,\n query.fnHaving\n )\n }\n }\n\n // Process the HAVING clause if it exists (only applies after GROUP BY)\n if (query.having && (!query.groupBy || query.groupBy.length === 0)) {\n // Check if we have aggregates in SELECT that would trigger implicit grouping\n const hasAggregates = query.select\n ? Object.values(query.select).some((expr) => expr.type === `agg`)\n : false\n\n if (!hasAggregates) {\n throw new Error(`HAVING clause requires GROUP BY clause`)\n }\n }\n\n // Process functional HAVING clauses outside of GROUP BY (treat as additional WHERE filters)\n if (\n query.fnHaving &&\n query.fnHaving.length > 0 &&\n (!query.groupBy || query.groupBy.length === 0)\n ) {\n // If there's no GROUP BY but there are fnHaving clauses, apply them as filters\n for (const fnHaving of query.fnHaving) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n // Process the DISTINCT clause if it exists\n if (query.distinct) {\n pipeline = pipeline.pipe(distinct(([_key, row]) => row.__select_results))\n }\n\n // Process orderBy parameter if it exists\n if (query.orderBy && query.orderBy.length > 0) {\n const orderedPipeline = processOrderBy(\n pipeline,\n query.orderBy,\n query.limit,\n query.offset\n )\n\n // Final step: extract the __select_results and include orderBy index\n const resultPipeline = orderedPipeline.pipe(\n map(([key, [row, orderByIndex]]) => {\n // Extract the final results from __select_results and include orderBy index\n const finalResults = (row as any).__select_results\n return [key, [finalResults, orderByIndex]] as [unknown, [any, string]]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning (use original query as key)\n cache.set(rawQuery, result)\n return result\n } else if (query.limit !== undefined || query.offset !== undefined) {\n // If there's a limit or offset without orderBy, throw an error\n throw new Error(\n `LIMIT and OFFSET require an ORDER BY clause to ensure deterministic results`\n )\n }\n\n // Final step: extract the __select_results and return tuple format (no orderBy)\n const resultPipeline: ResultStream = pipeline.pipe(\n map(([key, row]) => {\n // Extract the final results from __select_results and return [key, [results, undefined]]\n const finalResults = (row as any).__select_results\n return [key, [finalResults, undefined]] as [\n unknown,\n [any, string | undefined],\n ]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning (use original query as key)\n cache.set(rawQuery, result)\n return result\n}\n\n/**\n * Processes the FROM clause to extract the main table alias and input stream\n */\nfunction processFrom(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new Error(\n `Input for collection \"${from.collection.id}\" not found in inputs map`\n )\n }\n return { alias: from.alias, input }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryInput = compileQuery(\n originalQuery,\n allInputs,\n cache,\n queryMapping\n )\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return { alias: from.alias, input: extractedInput }\n }\n default:\n throw new Error(`Unsupported FROM type: ${(from as any).type}`)\n }\n}\n\n/**\n * Recursively maps optimized subqueries to their original queries for proper caching.\n * This ensures that when we encounter the same QueryRef object in different contexts,\n * we can find the original query to check the cache.\n */\nfunction mapNestedQueries(\n optimizedQuery: QueryIR,\n originalQuery: QueryIR,\n queryMapping: QueryMapping\n): void {\n // Map the FROM clause if it's a QueryRef\n if (\n optimizedQuery.from.type === `queryRef` &&\n originalQuery.from.type === `queryRef`\n ) {\n queryMapping.set(optimizedQuery.from.query, originalQuery.from.query)\n // Recursively map nested queries\n mapNestedQueries(\n optimizedQuery.from.query,\n originalQuery.from.query,\n queryMapping\n )\n }\n\n // Map JOIN clauses if they exist\n if (optimizedQuery.join && originalQuery.join) {\n for (\n let i = 0;\n i < optimizedQuery.join.length && i < originalQuery.join.length;\n i++\n ) {\n const optimizedJoin = optimizedQuery.join[i]!\n const originalJoin = originalQuery.join[i]!\n\n if (\n optimizedJoin.from.type === `queryRef` &&\n originalJoin.from.type === `queryRef`\n ) {\n queryMapping.set(optimizedJoin.from.query, originalJoin.from.query)\n // Recursively map nested queries in joins\n mapNestedQueries(\n optimizedJoin.from.query,\n originalJoin.from.query,\n queryMapping\n )\n }\n }\n }\n}\n"],"names":["optimizeQuery","map","processJoins","compileExpression","filter","processSelectToResults","processGroupBy","distinct","processOrderBy","resultPipeline","result"],"mappings":";;;;;;;;;AAuBO,SAAS,aACd,UACA,QACA,QAAoB,oBAAI,WACxB,eAA6B,oBAAI,WACnB;AAEd,QAAM,eAAe,MAAM,IAAI,QAAQ;AACvC,MAAI,cAAc;AAChB,WAAO;AAAA,EACT;AAGA,QAAM,QAAQA,UAAAA,cAAc,QAAQ;AAGpC,eAAa,IAAI,OAAO,QAAQ;AAChC,mBAAiB,OAAO,UAAU,YAAY;AAG9C,QAAM,YAAY,EAAE,GAAG,OAAA;AAGvB,QAAM,SAAsC,CAAA;AAG5C,QAAM,EAAE,OAAO,gBAAgB,OAAO,cAAc;AAAA,IAClD,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF,SAAO,cAAc,IAAI;AAGzB,MAAI,WAAqC,UAAU;AAAA,IACjDC,OAAAA,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM;AAElB,YAAM,MAAM,CAAC,KAAK,EAAE,CAAC,cAAc,GAAG,KAAK;AAI3C,aAAO;AAAA,IACT,CAAC;AAAA,EAAA;AAIH,MAAI,MAAM,QAAQ,MAAM,KAAK,SAAS,GAAG;AACvC,eAAWC,MAAAA;AAAAA,MACT;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAGA,MAAI,MAAM,SAAS,MAAM,MAAM,SAAS,GAAG;AAEzC,UAAM,iBAAiB,MAAM,MAAM,IAAI,CAAC,UAAUC,WAAAA,kBAAkB,KAAK,CAAC;AAG1E,eAAW,iBAAiB,gBAAgB;AAC1C,iBAAW,SAAS;AAAA,QAClBC,OAAAA,OAAO,CAAC,CAAC,MAAM,aAAa,MAAM;AAChC,iBAAO,cAAc,aAAa;AAAA,QACpC,CAAC;AAAA,MAAA;AAAA,IAEL;AAAA,EACF;AAGA,MAAI,MAAM,WAAW,MAAM,QAAQ,SAAS,GAAG;AAC7C,eAAW,WAAW,MAAM,SAAS;AACnC,iBAAW,SAAS;AAAA,QAClBA,OAAAA,OAAO,CAAC,CAAC,MAAM,aAAa,MAAM;AAChC,iBAAO,QAAQ,aAAa;AAAA,QAC9B,CAAC;AAAA,MAAA;AAAA,IAEL;AAAA,EACF;AAEA,MAAI,MAAM,YAAY,CAAC,MAAM,YAAY,CAAC,MAAM,QAAQ;AACtD,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AAIA,MAAI,MAAM,UAAU;AAElB,eAAW,SAAS;AAAA,MAClBH,OAAAA,IAAI,CAAC,CAAC,KAAK,aAAa,MAAM;AAC5B,cAAM,gBAAgB,MAAM,SAAU,aAAa;AACnD,eAAO;AAAA,UACL;AAAA,UACA;AAAA,YACE,GAAG;AAAA,YACH,kBAAkB;AAAA,UAAA;AAAA,QACpB;AAAA,MAEJ,CAAC;AAAA,IAAA;AAAA,EAEL,WAAW,MAAM,QAAQ;AACvB,eAAWI,OAAAA,uBAAuB,UAAU,MAAM,MAAiB;AAAA,EACrE,OAAO;AAEL,eAAW,SAAS;AAAA,MAClBJ,OAAAA,IAAI,CAAC,CAAC,KAAK,aAAa,MAAM;AAC5B,cAAM,gBACJ,CAAC,MAAM,QAAQ,CAAC,MAAM,UAClB,cAAc,cAAc,IAC5B;AAEN,eAAO;AAAA,UACL;AAAA,UACA;AAAA,YACE,GAAG;AAAA,YACH,kBAAkB;AAAA,UAAA;AAAA,QACpB;AAAA,MAEJ,CAAC;AAAA,IAAA;AAAA,EAEL;AAGA,MAAI,MAAM,WAAW,MAAM,QAAQ,SAAS,GAAG;AAC7C,eAAWK,QAAAA;AAAAA,MACT;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAAA,EAEV,WAAW,MAAM,QAAQ;AAEvB,UAAM,gBAAgB,OAAO,OAAO,MAAM,MAAM,EAAE;AAAA,MAChD,CAAC,SAAS,KAAK,SAAS;AAAA,IAAA;AAE1B,QAAI,eAAe;AAEjB,iBAAWA,QAAAA;AAAAA,QACT;AAAA,QACA,CAAA;AAAA;AAAA,QACA,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MAAA;AAAA,IAEV;AAAA,EACF;AAGA,MAAI,MAAM,WAAW,CAAC,MAAM,WAAW,MAAM,QAAQ,WAAW,IAAI;AAElE,UAAM,gBAAgB,MAAM,SACxB,OAAO,OAAO,MAAM,MAAM,EAAE,KAAK,CAAC,SAAS,KAAK,SAAS,KAAK,IAC9D;AAEJ,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AAAA,EACF;AAGA,MACE,MAAM,YACN,MAAM,SAAS,SAAS,MACvB,CAAC,MAAM,WAAW,MAAM,QAAQ,WAAW,IAC5C;AAEA,eAAW,YAAY,MAAM,UAAU;AACrC,iBAAW,SAAS;AAAA,QAClBF,OAAAA,OAAO,CAAC,CAAC,MAAM,aAAa,MAAM;AAChC,iBAAO,SAAS,aAAa;AAAA,QAC/B,CAAC;AAAA,MAAA;AAAA,IAEL;AAAA,EACF;AAGA,MAAI,MAAM,UAAU;AAClB,eAAW,SAAS,KAAKG,gBAAS,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,gBAAgB,CAAC;AAAA,EAC1E;AAGA,MAAI,MAAM,WAAW,MAAM,QAAQ,SAAS,GAAG;AAC7C,UAAM,kBAAkBC,QAAAA;AAAAA,MACtB;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAIR,UAAMC,kBAAiB,gBAAgB;AAAA,MACrCR,OAAAA,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,MAAM;AAElC,cAAM,eAAgB,IAAY;AAClC,eAAO,CAAC,KAAK,CAAC,cAAc,YAAY,CAAC;AAAA,MAC3C,CAAC;AAAA,IAAA;AAGH,UAAMS,UAASD;AAEf,UAAM,IAAI,UAAUC,OAAM;AAC1B,WAAOA;AAAAA,EACT,WAAW,MAAM,UAAU,UAAa,MAAM,WAAW,QAAW;AAElE,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAGA,QAAM,iBAA+B,SAAS;AAAA,IAC5CT,OAAAA,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM;AAElB,YAAM,eAAgB,IAAY;AAClC,aAAO,CAAC,KAAK,CAAC,cAAc,MAAS,CAAC;AAAA,IAIxC,CAAC;AAAA,EAAA;AAGH,QAAM,SAAS;AAEf,QAAM,IAAI,UAAU,MAAM;AAC1B,SAAO;AACT;AAKA,SAAS,YACP,MACA,WACA,OACA,cACuC;AACvC,UAAQ,KAAK,MAAA;AAAA,IACX,KAAK,iBAAiB;AACpB,YAAM,QAAQ,UAAU,KAAK,WAAW,EAAE;AAC1C,UAAI,CAAC,OAAO;AACV,cAAM,IAAI;AAAA,UACR,yBAAyB,KAAK,WAAW,EAAE;AAAA,QAAA;AAAA,MAE/C;AACA,aAAO,EAAE,OAAO,KAAK,OAAO,MAAA;AAAA,IAC9B;AAAA,IACA,KAAK,YAAY;AAEf,YAAM,gBAAgB,aAAa,IAAI,KAAK,KAAK,KAAK,KAAK;AAG3D,YAAM,gBAAgB;AAAA,QACpB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAKF,YAAM,iBAAiB,cAAc;AAAA,QACnCA,OAAAA,IAAI,CAAC,SAAc;AACjB,gBAAM,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,IAAI;AACtC,iBAAO,CAAC,KAAK,KAAK;AAAA,QACpB,CAAC;AAAA,MAAA;AAGH,aAAO,EAAE,OAAO,KAAK,OAAO,OAAO,eAAA;AAAA,IACrC;AAAA,IACA;AACE,YAAM,IAAI,MAAM,0BAA2B,KAAa,IAAI,EAAE;AAAA,EAAA;AAEpE;AAOA,SAAS,iBACP,gBACA,eACA,cACM;AAEN,MACE,eAAe,KAAK,SAAS,cAC7B,cAAc,KAAK,SAAS,YAC5B;AACA,iBAAa,IAAI,eAAe,KAAK,OAAO,cAAc,KAAK,KAAK;AAEpE;AAAA,MACE,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,MACnB;AAAA,IAAA;AAAA,EAEJ;AAGA,MAAI,eAAe,QAAQ,cAAc,MAAM;AAC7C,aACM,IAAI,GACR,IAAI,eAAe,KAAK,UAAU,IAAI,cAAc,KAAK,QACzD,KACA;AACA,YAAM,gBAAgB,eAAe,KAAK,CAAC;AAC3C,YAAM,eAAe,cAAc,KAAK,CAAC;AAEzC,UACE,cAAc,KAAK,SAAS,cAC5B,aAAa,KAAK,SAAS,YAC3B;AACA,qBAAa,IAAI,cAAc,KAAK,OAAO,aAAa,KAAK,KAAK;AAElE;AAAA,UACE,cAAc,KAAK;AAAA,UACnB,aAAa,KAAK;AAAA,UAClB;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AACF;;"}
@@ -1,15 +1,12 @@
1
1
  import { QueryIR } from '../ir.js';
2
2
  import { KeyedStream, ResultStream } from '../../types.js';
3
- /**
4
- * Cache for compiled subqueries to avoid duplicate compilation
5
- */
6
- type QueryCache = WeakMap<QueryIR, ResultStream>;
3
+ import { QueryCache, QueryMapping } from './types.js';
7
4
  /**
8
5
  * Compiles a query2 IR into a D2 pipeline
9
- * @param query The query IR to compile
6
+ * @param rawQuery The query IR to compile
10
7
  * @param inputs Mapping of collection names to input streams
11
8
  * @param cache Optional cache for compiled subqueries (used internally for recursion)
9
+ * @param queryMapping Optional mapping from optimized queries to original queries
12
10
  * @returns A stream builder representing the compiled query
13
11
  */
14
- export declare function compileQuery(query: QueryIR, inputs: Record<string, KeyedStream>, cache?: QueryCache): ResultStream;
15
- export {};
12
+ export declare function compileQuery(rawQuery: QueryIR, inputs: Record<string, KeyedStream>, cache?: QueryCache, queryMapping?: QueryMapping): ResultStream;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const d2mini = require("@electric-sql/d2mini");
4
4
  const evaluators = require("./evaluators.cjs");
5
5
  const index = require("./index.cjs");
6
- function processJoins(pipeline, joinClauses, tables, mainTableAlias, allInputs, cache) {
6
+ function processJoins(pipeline, joinClauses, tables, mainTableAlias, allInputs, cache, queryMapping) {
7
7
  let resultPipeline = pipeline;
8
8
  for (const joinClause of joinClauses) {
9
9
  resultPipeline = processJoin(
@@ -12,16 +12,18 @@ function processJoins(pipeline, joinClauses, tables, mainTableAlias, allInputs,
12
12
  tables,
13
13
  mainTableAlias,
14
14
  allInputs,
15
- cache
15
+ cache,
16
+ queryMapping
16
17
  );
17
18
  }
18
19
  return resultPipeline;
19
20
  }
20
- function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, cache) {
21
+ function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, cache, queryMapping) {
21
22
  const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(
22
23
  joinClause.from,
23
24
  allInputs,
24
- cache
25
+ cache,
26
+ queryMapping
25
27
  );
26
28
  tables[joinedTableAlias] = joinedInput;
27
29
  const joinType = joinClause.type === `cross` ? `inner` : joinClause.type === `outer` ? `full` : joinClause.type;
@@ -49,7 +51,7 @@ function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, ca
49
51
  processJoinResults(joinClause.type)
50
52
  );
51
53
  }
52
- function processJoinSource(from, allInputs, cache) {
54
+ function processJoinSource(from, allInputs, cache, queryMapping) {
53
55
  switch (from.type) {
54
56
  case `collectionRef`: {
55
57
  const input = allInputs[from.collection.id];
@@ -61,7 +63,13 @@ function processJoinSource(from, allInputs, cache) {
61
63
  return { alias: from.alias, input };
62
64
  }
63
65
  case `queryRef`: {
64
- const subQueryInput = index.compileQuery(from.query, allInputs, cache);
66
+ const originalQuery = queryMapping.get(from.query) || from.query;
67
+ const subQueryInput = index.compileQuery(
68
+ originalQuery,
69
+ allInputs,
70
+ cache,
71
+ queryMapping
72
+ );
65
73
  const extractedInput = subQueryInput.pipe(
66
74
  d2mini.map((data) => {
67
75
  const [key, [value, _orderByIndex]] = data;
@@ -1 +1 @@
1
- {"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n} from \"@electric-sql/d2mini\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery } from \"./index.js\"\nimport type { IStreamBuilder, JoinType } from \"@electric-sql/d2mini\"\nimport type { CollectionRef, JoinClause, QueryIR, QueryRef } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n ResultStream,\n} from \"../../types.js\"\n\n/**\n * Cache for compiled subqueries to avoid duplicate compilation\n */\ntype QueryCache = WeakMap<QueryIR, ResultStream>\n\n/**\n * Processes all join clauses in a query\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableAlias,\n allInputs,\n cache\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(\n joinClause.from,\n allInputs,\n cache\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n // Convert join type to D2 join type\n const joinType: JoinType =\n joinClause.type === `cross`\n ? `inner`\n : joinClause.type === `outer`\n ? `full`\n : (joinClause.type as JoinType)\n\n // Pre-compile the join expressions\n const compiledLeftExpr = compileExpression(joinClause.left)\n const compiledRightExpr = compileExpression(joinClause.right)\n\n // Prepare the main pipeline for joining\n const mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the left side of the join condition\n const leftKey = compiledLeftExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [leftKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n const joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the join key from the right side of the join condition\n const rightKey = compiledRightExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [rightKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinType)) {\n throw new Error(`Unsupported join type: ${joinClause.type}`)\n }\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache\n): { alias: string; input: KeyedStream } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new Error(\n `Input for collection \"${from.collection.id}\" not found in inputs map`\n )\n }\n return { alias: from.alias, input }\n }\n case `queryRef`: {\n // Recursively compile the sub-query with cache\n const subQueryInput = compileQuery(from.query, allInputs, cache)\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return { alias: from.alias, input: extractedInput as KeyedStream }\n }\n default:\n throw new Error(`Unsupported join source type: ${(from as any).type}`)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n"],"names":["compileExpression","map","joinOperator","consolidate","compileQuery","filter"],"mappings":"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"}
1
+ {"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n} from \"@electric-sql/d2mini\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery } from \"./index.js\"\nimport type { IStreamBuilder, JoinType } from \"@electric-sql/d2mini\"\nimport type { CollectionRef, JoinClause, QueryRef } from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\n\n/**\n * Processes all join clauses in a query\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(\n joinClause.from,\n allInputs,\n cache,\n queryMapping\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n // Convert join type to D2 join type\n const joinType: JoinType =\n joinClause.type === `cross`\n ? `inner`\n : joinClause.type === `outer`\n ? `full`\n : (joinClause.type as JoinType)\n\n // Pre-compile the join expressions\n const compiledLeftExpr = compileExpression(joinClause.left)\n const compiledRightExpr = compileExpression(joinClause.right)\n\n // Prepare the main pipeline for joining\n const mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the left side of the join condition\n const leftKey = compiledLeftExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [leftKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n const joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the join key from the right side of the join condition\n const rightKey = compiledRightExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [rightKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinType)) {\n throw new Error(`Unsupported join type: ${joinClause.type}`)\n }\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new Error(\n `Input for collection \"${from.collection.id}\" not found in inputs map`\n )\n }\n return { alias: from.alias, input }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryInput = compileQuery(\n originalQuery,\n allInputs,\n cache,\n queryMapping\n )\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return { alias: from.alias, input: extractedInput as KeyedStream }\n }\n default:\n throw new Error(`Unsupported join source type: ${(from as any).type}`)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n"],"names":["compileExpression","map","joinOperator","consolidate","compileQuery","filter"],"mappings":"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"}
@@ -1,11 +1,7 @@
1
- import { JoinClause, QueryIR } from '../ir.js';
2
- import { KeyedStream, NamespacedAndKeyedStream, ResultStream } from '../../types.js';
3
- /**
4
- * Cache for compiled subqueries to avoid duplicate compilation
5
- */
6
- type QueryCache = WeakMap<QueryIR, ResultStream>;
1
+ import { JoinClause } from '../ir.js';
2
+ import { KeyedStream, NamespacedAndKeyedStream } from '../../types.js';
3
+ import { QueryCache, QueryMapping } from './types.js';
7
4
  /**
8
5
  * Processes all join clauses in a query
9
6
  */
10
- export declare function processJoins(pipeline: NamespacedAndKeyedStream, joinClauses: Array<JoinClause>, tables: Record<string, KeyedStream>, mainTableAlias: string, allInputs: Record<string, KeyedStream>, cache: QueryCache): NamespacedAndKeyedStream;
11
- export {};
7
+ export declare function processJoins(pipeline: NamespacedAndKeyedStream, joinClauses: Array<JoinClause>, tables: Record<string, KeyedStream>, mainTableAlias: string, allInputs: Record<string, KeyedStream>, cache: QueryCache, queryMapping: QueryMapping): NamespacedAndKeyedStream;
@@ -0,0 +1,10 @@
1
+ import { QueryIR } from '../ir.js';
2
+ import { ResultStream } from '../../types.js';
3
+ /**
4
+ * Cache for compiled subqueries to avoid duplicate compilation
5
+ */
6
+ export type QueryCache = WeakMap<QueryIR, ResultStream>;
7
+ /**
8
+ * Mapping from optimized queries back to their original queries for caching
9
+ */
10
+ export type QueryMapping = WeakMap<QueryIR, QueryIR>;
@@ -59,7 +59,7 @@ function liveQueryCollectionOptions(config) {
59
59
  compileBasePipeline();
60
60
  const sync = {
61
61
  rowUpdateMode: `full`,
62
- sync: ({ begin, write, commit, collection: theCollection }) => {
62
+ sync: ({ begin, write, commit, markReady, collection: theCollection }) => {
63
63
  const { graph, inputs, pipeline } = maybeCompileBasePipeline();
64
64
  let messagesCount = 0;
65
65
  pipeline.pipe(
@@ -127,6 +127,7 @@ function liveQueryCollectionOptions(config) {
127
127
  begin();
128
128
  commit();
129
129
  }
130
+ markReady();
130
131
  }
131
132
  };
132
133
  const unsubscribeCallbacks = /* @__PURE__ */ new Set();
@@ -1 +1 @@
1
- {"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@electric-sql/d2mini\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery, getQueryIR } from \"./builder/index.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@electric-sql/d2mini\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function or instance\n const query =\n typeof config.query === `function`\n ? buildQuery<TContext>(config.query)\n : getQueryIR(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every(\n (collection) =>\n collection.status === `ready` || collection.status === `initialCommit`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n pipelineCache = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>\n )\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n\n graph.finalize()\n\n const maybeRunGraph = () => {\n // We only run the graph if all the collections are ready\n if (allCollectionsReady()) {\n graph.run()\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Set up data flow from input collections to the compiled query\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n\n // Subscribe to changes\n const unsubscribe = collection.subscribeChanges(\n (changes: Array<ChangeMessage>) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph()\n },\n { includeInitialState: true }\n )\n unsubscribeCallbacks.add(unsubscribe)\n })\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Array<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n}\n\n/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n"],"names":["buildQuery","getQueryIR","collection","D2","compileQuery","output","createCollection","MultiSet"],"mappings":"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"}
1
+ {"version":3,"file":"live-query-collection.cjs","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { D2, MultiSet, output } from \"@electric-sql/d2mini\"\nimport { createCollection } from \"../collection.js\"\nimport { compileQuery } from \"./compiler/index.js\"\nimport { buildQuery, getQueryIR } from \"./builder/index.js\"\nimport type { InitialQueryBuilder, QueryBuilder } from \"./builder/index.js\"\nimport type { Collection } from \"../collection.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n KeyedStream,\n ResultStream,\n SyncConfig,\n UtilsRecord,\n} from \"../types.js\"\nimport type { Context, GetResult } from \"./builder/types.js\"\nimport type { MultiSetArray, RootStreamBuilder } from \"@electric-sql/d2mini\"\n\n// Global counter for auto-generated collection IDs\nlet liveQueryCollectionCounter = 0\n\n/**\n * Configuration interface for live query collection options\n *\n * @example\n * ```typescript\n * const config: LiveQueryCollectionConfig<any, any> = {\n * // id is optional - will auto-generate \"live-query-1\", \"live-query-2\", etc.\n * query: (q) => q\n * .from({ comment: commentsCollection })\n * .join(\n * { user: usersCollection },\n * ({ comment, user }) => eq(comment.user_id, user.id)\n * )\n * .where(({ comment }) => eq(comment.active, true))\n * .select(({ comment, user }) => ({\n * id: comment.id,\n * content: comment.content,\n * authorName: user.name,\n * })),\n * // getKey is optional - defaults to using stream key\n * getKey: (item) => item.id,\n * }\n * ```\n */\nexport interface LiveQueryCollectionConfig<\n TContext extends Context,\n TResult extends object = GetResult<TContext> & object,\n> {\n /**\n * Unique identifier for the collection\n * If not provided, defaults to `live-query-${number}` with auto-incrementing number\n */\n id?: string\n\n /**\n * Query builder function that defines the live query\n */\n query:\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n | QueryBuilder<TContext>\n\n /**\n * Function to extract the key from result items\n * If not provided, defaults to using the key from the D2 stream\n */\n getKey?: (item: TResult) => string | number\n\n /**\n * Optional schema for validation\n */\n schema?: CollectionConfig<TResult>[`schema`]\n\n /**\n * Optional mutation handlers\n */\n onInsert?: CollectionConfig<TResult>[`onInsert`]\n onUpdate?: CollectionConfig<TResult>[`onUpdate`]\n onDelete?: CollectionConfig<TResult>[`onDelete`]\n\n /**\n * Start sync / the query immediately\n */\n startSync?: boolean\n\n /**\n * GC time for the collection\n */\n gcTime?: number\n}\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult>\n): CollectionConfig<TResult> {\n // Generate a unique ID if not provided\n const id = config.id || `live-query-${++liveQueryCollectionCounter}`\n\n // Build the query using the provided query builder function or instance\n const query =\n typeof config.query === `function`\n ? buildQuery<TContext>(config.query)\n : getQueryIR(config.query)\n\n // WeakMap to store the keys of the results so that we can retreve them in the\n // getKey function\n const resultKeys = new WeakMap<object, unknown>()\n\n // WeakMap to store the orderBy index for each result\n const orderByIndices = new WeakMap<object, string>()\n\n // Create compare function for ordering if the query has orderBy\n const compare =\n query.orderBy && query.orderBy.length > 0\n ? (val1: TResult, val2: TResult): number => {\n // Use the orderBy index stored in the WeakMap\n const index1 = orderByIndices.get(val1)\n const index2 = orderByIndices.get(val2)\n\n // Compare fractional indices lexicographically\n if (index1 && index2) {\n if (index1 < index2) {\n return -1\n } else if (index1 > index2) {\n return 1\n } else {\n return 0\n }\n }\n\n // Fallback to no ordering if indices are missing\n return 0\n }\n : undefined\n\n const collections = extractCollectionsFromQuery(query)\n\n const allCollectionsReady = () => {\n return Object.values(collections).every(\n (collection) =>\n collection.status === `ready` || collection.status === `initialCommit`\n )\n }\n\n let graphCache: D2 | undefined\n let inputsCache: Record<string, RootStreamBuilder<unknown>> | undefined\n let pipelineCache: ResultStream | undefined\n\n const compileBasePipeline = () => {\n graphCache = new D2()\n inputsCache = Object.fromEntries(\n Object.entries(collections).map(([key]) => [\n key,\n graphCache!.newInput<any>(),\n ])\n )\n pipelineCache = compileQuery(\n query,\n inputsCache as Record<string, KeyedStream>\n )\n }\n\n const maybeCompileBasePipeline = () => {\n if (!graphCache || !inputsCache || !pipelineCache) {\n compileBasePipeline()\n }\n return {\n graph: graphCache!,\n inputs: inputsCache!,\n pipeline: pipelineCache!,\n }\n }\n\n // Compile the base pipeline once initially\n // This is done to ensure that any errors are thrown immediately and synchronously\n compileBasePipeline()\n\n // Create the sync configuration\n const sync: SyncConfig<TResult> = {\n rowUpdateMode: `full`,\n sync: ({ begin, write, commit, markReady, collection: theCollection }) => {\n const { graph, inputs, pipeline } = maybeCompileBasePipeline()\n let messagesCount = 0\n pipeline.pipe(\n output((data) => {\n const messages = data.getInner()\n messagesCount += messages.length\n\n begin()\n messages\n .reduce((acc, [[key, tupleData], multiplicity]) => {\n // All queries now consistently return [value, orderByIndex] format\n // where orderByIndex is undefined for queries without ORDER BY\n const [value, orderByIndex] = tupleData as [\n TResult,\n string | undefined,\n ]\n\n const changes = acc.get(key) || {\n deletes: 0,\n inserts: 0,\n value,\n orderByIndex,\n }\n if (multiplicity < 0) {\n changes.deletes += Math.abs(multiplicity)\n } else if (multiplicity > 0) {\n changes.inserts += multiplicity\n changes.value = value\n changes.orderByIndex = orderByIndex\n }\n acc.set(key, changes)\n return acc\n }, new Map<unknown, { deletes: number; inserts: number; value: TResult; orderByIndex: string | undefined }>())\n .forEach((changes, rawKey) => {\n const { deletes, inserts, value, orderByIndex } = changes\n\n // Store the key of the result so that we can retrieve it in the\n // getKey function\n resultKeys.set(value, rawKey)\n\n // Store the orderBy index if it exists\n if (orderByIndex !== undefined) {\n orderByIndices.set(value, orderByIndex)\n }\n\n // Simple singular insert.\n if (inserts && deletes === 0) {\n write({\n value,\n type: `insert`,\n })\n } else if (\n // Insert & update(s) (updates are a delete & insert)\n inserts > deletes ||\n // Just update(s) but the item is already in the collection (so\n // was inserted previously).\n (inserts === deletes &&\n theCollection.has(rawKey as string | number))\n ) {\n write({\n value,\n type: `update`,\n })\n // Only delete is left as an option\n } else if (deletes > 0) {\n write({\n value,\n type: `delete`,\n })\n } else {\n throw new Error(\n `This should never happen ${JSON.stringify(changes)}`\n )\n }\n })\n commit()\n })\n )\n\n graph.finalize()\n\n const maybeRunGraph = () => {\n // We only run the graph if all the collections are ready\n if (allCollectionsReady()) {\n graph.run()\n // On the initial run, we may need to do an empty commit to ensure that\n // the collection is initialized\n if (messagesCount === 0) {\n begin()\n commit()\n }\n // Mark the collection as ready after the first successful run\n markReady()\n }\n }\n\n // Unsubscribe callbacks\n const unsubscribeCallbacks = new Set<() => void>()\n\n // Set up data flow from input collections to the compiled query\n Object.entries(collections).forEach(([collectionId, collection]) => {\n const input = inputs[collectionId]!\n\n // Subscribe to changes\n const unsubscribe = collection.subscribeChanges(\n (changes: Array<ChangeMessage>) => {\n sendChangesToInput(input, changes, collection.config.getKey)\n maybeRunGraph()\n },\n { includeInitialState: true }\n )\n unsubscribeCallbacks.add(unsubscribe)\n })\n\n // Initial run\n maybeRunGraph()\n\n // Return the unsubscribe function\n return () => {\n unsubscribeCallbacks.forEach((unsubscribe) => unsubscribe())\n }\n },\n }\n\n // Return collection configuration\n return {\n id,\n getKey:\n config.getKey || ((item) => resultKeys.get(item) as string | number),\n sync,\n compare,\n gcTime: config.gcTime || 5000, // 5 seconds by default for live queries\n schema: config.schema,\n onInsert: config.onInsert,\n onUpdate: config.onUpdate,\n onDelete: config.onDelete,\n startSync: config.startSync,\n }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n>(\n query: (q: InitialQueryBuilder) => QueryBuilder<TContext>\n): Collection<TResult, string | number, {}>\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils>\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = GetResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)\n): Collection<TResult, string | number, TUtils> {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder\n ) => QueryBuilder<TContext>,\n }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection(options)\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n const options = liveQueryCollectionOptions<TContext, TResult>(config)\n return bridgeToCreateCollection({\n ...options,\n utils: config.utils,\n })\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's ResolveType without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils?: TUtils }\n): Collection<TResult, string | number, TUtils> {\n // This is the only place we need a type assertion, hidden from user API\n return createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n TUtils\n >\n}\n\n/**\n * Helper function to send changes to a D2 input stream\n */\nfunction sendChangesToInput(\n input: RootStreamBuilder<unknown>,\n changes: Array<ChangeMessage>,\n getKey: (item: ChangeMessage[`value`]) => any\n) {\n const multiSetArray: MultiSetArray<unknown> = []\n for (const change of changes) {\n const key = getKey(change.value)\n if (change.type === `insert`) {\n multiSetArray.push([[key, change.value], 1])\n } else if (change.type === `update`) {\n multiSetArray.push([[key, change.previousValue], -1])\n multiSetArray.push([[key, change.value], 1])\n } else {\n // change.type === `delete`\n multiSetArray.push([[key, change.value], -1])\n }\n }\n input.sendData(new MultiSet(multiSetArray))\n}\n\n/**\n * Helper function to extract collections from a compiled query\n * Traverses the query IR to find all collection references\n * Maps collections by their ID (not alias) as expected by the compiler\n */\nfunction extractCollectionsFromQuery(\n query: any\n): Record<string, Collection<any, any, any>> {\n const collections: Record<string, any> = {}\n\n // Helper function to recursively extract collections from a query or source\n function extractFromSource(source: any) {\n if (source.type === `collectionRef`) {\n collections[source.collection.id] = source.collection\n } else if (source.type === `queryRef`) {\n // Recursively extract from subquery\n extractFromQuery(source.query)\n }\n }\n\n // Helper function to recursively extract collections from a query\n function extractFromQuery(q: any) {\n // Extract from FROM clause\n if (q.from) {\n extractFromSource(q.from)\n }\n\n // Extract from JOIN clauses\n if (q.join && Array.isArray(q.join)) {\n for (const joinClause of q.join) {\n if (joinClause.from) {\n extractFromSource(joinClause.from)\n }\n }\n }\n }\n\n // Start extraction from the root query\n extractFromQuery(query)\n\n return collections\n}\n"],"names":["buildQuery","getQueryIR","collection","D2","compileQuery","output","createCollection","MultiSet"],"mappings":"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"}