@tanstack/db 0.1.1 → 0.1.4

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 (110) hide show
  1. package/dist/cjs/collection.cjs +112 -6
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +12 -3
  4. package/dist/cjs/errors.cjs +6 -0
  5. package/dist/cjs/errors.cjs.map +1 -1
  6. package/dist/cjs/errors.d.cts +3 -0
  7. package/dist/cjs/index.cjs +1 -0
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/indexes/auto-index.cjs +30 -19
  10. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  11. package/dist/cjs/indexes/auto-index.d.cts +1 -0
  12. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  13. package/dist/cjs/indexes/base-index.d.cts +2 -1
  14. package/dist/cjs/indexes/btree-index.cjs +29 -3
  15. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  16. package/dist/cjs/indexes/btree-index.d.cts +7 -0
  17. package/dist/cjs/indexes/index-options.d.cts +1 -1
  18. package/dist/cjs/query/builder/index.cjs +9 -2
  19. package/dist/cjs/query/builder/index.cjs.map +1 -1
  20. package/dist/cjs/query/builder/index.d.cts +2 -2
  21. package/dist/cjs/query/builder/types.d.cts +27 -6
  22. package/dist/cjs/query/compiler/evaluators.cjs +2 -2
  23. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  24. package/dist/cjs/query/compiler/evaluators.d.cts +1 -1
  25. package/dist/cjs/query/compiler/group-by.cjs +3 -1
  26. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  27. package/dist/cjs/query/compiler/index.cjs +72 -6
  28. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  29. package/dist/cjs/query/compiler/index.d.cts +16 -2
  30. package/dist/cjs/query/compiler/joins.cjs +111 -12
  31. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  32. package/dist/cjs/query/compiler/joins.d.cts +9 -2
  33. package/dist/cjs/query/compiler/order-by.cjs +80 -23
  34. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  35. package/dist/cjs/query/compiler/order-by.d.cts +12 -2
  36. package/dist/cjs/query/ir.cjs.map +1 -1
  37. package/dist/cjs/query/ir.d.cts +2 -1
  38. package/dist/cjs/query/live-query-collection.cjs +196 -23
  39. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  40. package/dist/cjs/types.d.cts +1 -0
  41. package/dist/cjs/utils/btree.cjs +15 -0
  42. package/dist/cjs/utils/btree.cjs.map +1 -1
  43. package/dist/cjs/utils/btree.d.cts +8 -0
  44. package/dist/cjs/utils/comparison.cjs +29 -7
  45. package/dist/cjs/utils/comparison.cjs.map +1 -1
  46. package/dist/cjs/utils/comparison.d.cts +6 -2
  47. package/dist/esm/collection.d.ts +12 -3
  48. package/dist/esm/collection.js +113 -7
  49. package/dist/esm/collection.js.map +1 -1
  50. package/dist/esm/errors.d.ts +3 -0
  51. package/dist/esm/errors.js +6 -0
  52. package/dist/esm/errors.js.map +1 -1
  53. package/dist/esm/index.js +2 -1
  54. package/dist/esm/indexes/auto-index.d.ts +1 -0
  55. package/dist/esm/indexes/auto-index.js +31 -20
  56. package/dist/esm/indexes/auto-index.js.map +1 -1
  57. package/dist/esm/indexes/base-index.d.ts +2 -1
  58. package/dist/esm/indexes/base-index.js.map +1 -1
  59. package/dist/esm/indexes/btree-index.d.ts +7 -0
  60. package/dist/esm/indexes/btree-index.js +29 -3
  61. package/dist/esm/indexes/btree-index.js.map +1 -1
  62. package/dist/esm/indexes/index-options.d.ts +1 -1
  63. package/dist/esm/query/builder/index.d.ts +2 -2
  64. package/dist/esm/query/builder/index.js +9 -2
  65. package/dist/esm/query/builder/index.js.map +1 -1
  66. package/dist/esm/query/builder/types.d.ts +27 -6
  67. package/dist/esm/query/compiler/evaluators.d.ts +1 -1
  68. package/dist/esm/query/compiler/evaluators.js +2 -2
  69. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  70. package/dist/esm/query/compiler/group-by.js +3 -1
  71. package/dist/esm/query/compiler/group-by.js.map +1 -1
  72. package/dist/esm/query/compiler/index.d.ts +16 -2
  73. package/dist/esm/query/compiler/index.js +73 -7
  74. package/dist/esm/query/compiler/index.js.map +1 -1
  75. package/dist/esm/query/compiler/joins.d.ts +9 -2
  76. package/dist/esm/query/compiler/joins.js +114 -15
  77. package/dist/esm/query/compiler/joins.js.map +1 -1
  78. package/dist/esm/query/compiler/order-by.d.ts +12 -2
  79. package/dist/esm/query/compiler/order-by.js +81 -24
  80. package/dist/esm/query/compiler/order-by.js.map +1 -1
  81. package/dist/esm/query/ir.d.ts +2 -1
  82. package/dist/esm/query/ir.js.map +1 -1
  83. package/dist/esm/query/live-query-collection.js +196 -23
  84. package/dist/esm/query/live-query-collection.js.map +1 -1
  85. package/dist/esm/types.d.ts +1 -0
  86. package/dist/esm/utils/btree.d.ts +8 -0
  87. package/dist/esm/utils/btree.js +15 -0
  88. package/dist/esm/utils/btree.js.map +1 -1
  89. package/dist/esm/utils/comparison.d.ts +6 -2
  90. package/dist/esm/utils/comparison.js +30 -8
  91. package/dist/esm/utils/comparison.js.map +1 -1
  92. package/package.json +2 -2
  93. package/src/collection.ts +237 -11
  94. package/src/errors.ts +6 -0
  95. package/src/indexes/auto-index.ts +53 -31
  96. package/src/indexes/base-index.ts +6 -1
  97. package/src/indexes/btree-index.ts +32 -3
  98. package/src/indexes/index-options.ts +2 -2
  99. package/src/query/builder/index.ts +19 -2
  100. package/src/query/builder/types.ts +48 -15
  101. package/src/query/compiler/evaluators.ts +6 -3
  102. package/src/query/compiler/group-by.ts +3 -1
  103. package/src/query/compiler/index.ts +112 -5
  104. package/src/query/compiler/joins.ts +216 -20
  105. package/src/query/compiler/order-by.ts +117 -26
  106. package/src/query/ir.ts +2 -1
  107. package/src/query/live-query-collection.ts +352 -24
  108. package/src/types.ts +1 -0
  109. package/src/utils/btree.ts +17 -0
  110. package/src/utils/comparison.ts +40 -7
@@ -2,32 +2,60 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const dbIvm = require("@tanstack/db-ivm");
4
4
  const errors = require("../../errors.cjs");
5
+ const indexOptimization = require("../../utils/index-optimization.cjs");
6
+ const autoIndex = require("../../indexes/auto-index.cjs");
5
7
  const evaluators = require("./evaluators.cjs");
6
8
  const index = require("./index.cjs");
7
- function processJoins(pipeline, joinClauses, tables, mainTableAlias, allInputs, cache, queryMapping) {
9
+ function processJoins(pipeline, joinClauses, tables, mainTableId, mainTableAlias, allInputs, cache, queryMapping, collections, callbacks, lazyCollections, optimizableOrderByCollections, rawQuery) {
8
10
  let resultPipeline = pipeline;
9
11
  for (const joinClause of joinClauses) {
10
12
  resultPipeline = processJoin(
11
13
  resultPipeline,
12
14
  joinClause,
13
15
  tables,
16
+ mainTableId,
14
17
  mainTableAlias,
15
18
  allInputs,
16
19
  cache,
17
- queryMapping
20
+ queryMapping,
21
+ collections,
22
+ callbacks,
23
+ lazyCollections,
24
+ optimizableOrderByCollections,
25
+ rawQuery
18
26
  );
19
27
  }
20
28
  return resultPipeline;
21
29
  }
22
- function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, cache, queryMapping) {
23
- const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(
30
+ function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias, allInputs, cache, queryMapping, collections, callbacks, lazyCollections, optimizableOrderByCollections, rawQuery) {
31
+ const {
32
+ alias: joinedTableAlias,
33
+ input: joinedInput,
34
+ collectionId: joinedCollectionId
35
+ } = processJoinSource(
24
36
  joinClause.from,
25
37
  allInputs,
38
+ collections,
39
+ callbacks,
40
+ lazyCollections,
41
+ optimizableOrderByCollections,
26
42
  cache,
27
43
  queryMapping
28
44
  );
29
45
  tables[joinedTableAlias] = joinedInput;
30
- const joinType = joinClause.type === `cross` ? `inner` : joinClause.type === `outer` ? `full` : joinClause.type;
46
+ const mainCollection = collections[mainTableId];
47
+ const joinedCollection = collections[joinedCollectionId];
48
+ if (!mainCollection) {
49
+ throw new errors.JoinCollectionNotFoundError(mainTableId);
50
+ }
51
+ if (!joinedCollection) {
52
+ throw new errors.JoinCollectionNotFoundError(joinedCollectionId);
53
+ }
54
+ const { activeCollection, lazyCollection } = getActiveAndLazyCollections(
55
+ joinClause.type,
56
+ mainCollection,
57
+ joinedCollection
58
+ );
31
59
  const { mainExpr, joinedExpr } = analyzeJoinExpressions(
32
60
  joinClause.left,
33
61
  joinClause.right,
@@ -36,24 +64,72 @@ function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, ca
36
64
  );
37
65
  const compiledMainExpr = evaluators.compileExpression(mainExpr);
38
66
  const compiledJoinedExpr = evaluators.compileExpression(joinedExpr);
39
- const mainPipeline = pipeline.pipe(
67
+ let mainPipeline = pipeline.pipe(
40
68
  dbIvm.map(([currentKey, namespacedRow]) => {
41
69
  const mainKey = compiledMainExpr(namespacedRow);
42
70
  return [mainKey, [currentKey, namespacedRow]];
43
71
  })
44
72
  );
45
- const joinedPipeline = joinedInput.pipe(
73
+ let joinedPipeline = joinedInput.pipe(
46
74
  dbIvm.map(([currentKey, row]) => {
47
75
  const namespacedRow = { [joinedTableAlias]: row };
48
76
  const joinedKey = compiledJoinedExpr(namespacedRow);
49
77
  return [joinedKey, [currentKey, namespacedRow]];
50
78
  })
51
79
  );
52
- if (![`inner`, `left`, `right`, `full`].includes(joinType)) {
80
+ if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {
53
81
  throw new errors.UnsupportedJoinTypeError(joinClause.type);
54
82
  }
83
+ if (activeCollection) {
84
+ lazyCollections.add(lazyCollection.id);
85
+ const activePipeline = activeCollection === `main` ? mainPipeline : joinedPipeline;
86
+ let index$1;
87
+ const lazyCollectionJoinExpr = activeCollection === `main` ? joinedExpr : mainExpr;
88
+ const activeColl = activeCollection === `main` ? collections[mainTableId] : lazyCollection;
89
+ const followRefResult = index.followRef(
90
+ rawQuery,
91
+ lazyCollectionJoinExpr,
92
+ activeColl
93
+ );
94
+ const followRefCollection = followRefResult.collection;
95
+ const fieldName = followRefResult.path[0];
96
+ if (fieldName) {
97
+ autoIndex.ensureIndexForField(fieldName, followRefResult.path, followRefCollection);
98
+ }
99
+ let deoptimized = false;
100
+ const activePipelineWithLoading = activePipeline.pipe(
101
+ dbIvm.tap(([joinKey, _]) => {
102
+ if (deoptimized) {
103
+ return;
104
+ }
105
+ index$1 ?? (index$1 = indexOptimization.findIndexForField(
106
+ followRefCollection.indexes,
107
+ followRefResult.path
108
+ ));
109
+ const collectionCallbacks = callbacks[lazyCollection.id];
110
+ if (!collectionCallbacks) {
111
+ throw new Error(
112
+ `Internal error: callbacks for collection are missing in join pipeline. Make sure the live query collection sets them before running the pipeline.`
113
+ );
114
+ }
115
+ const { loadKeys, loadInitialState } = collectionCallbacks;
116
+ if (index$1 && index$1.supports(`eq`)) {
117
+ const matchingKeys = index$1.lookup(`eq`, joinKey);
118
+ loadKeys(matchingKeys);
119
+ } else {
120
+ deoptimized = true;
121
+ loadInitialState();
122
+ }
123
+ })
124
+ );
125
+ if (activeCollection === `main`) {
126
+ mainPipeline = activePipelineWithLoading;
127
+ } else {
128
+ joinedPipeline = activePipelineWithLoading;
129
+ }
130
+ }
55
131
  return mainPipeline.pipe(
56
- dbIvm.join(joinedPipeline, joinType),
132
+ dbIvm.join(joinedPipeline, joinClause.type),
57
133
  dbIvm.consolidate(),
58
134
  processJoinResults(joinClause.type)
59
135
  );
@@ -101,20 +177,24 @@ function getTableAliasFromExpression(expr) {
101
177
  return null;
102
178
  }
103
179
  }
104
- function processJoinSource(from, allInputs, cache, queryMapping) {
180
+ function processJoinSource(from, allInputs, collections, callbacks, lazyCollections, optimizableOrderByCollections, cache, queryMapping) {
105
181
  switch (from.type) {
106
182
  case `collectionRef`: {
107
183
  const input = allInputs[from.collection.id];
108
184
  if (!input) {
109
185
  throw new errors.CollectionInputNotFoundError(from.collection.id);
110
186
  }
111
- return { alias: from.alias, input };
187
+ return { alias: from.alias, input, collectionId: from.collection.id };
112
188
  }
113
189
  case `queryRef`: {
114
190
  const originalQuery = queryMapping.get(from.query) || from.query;
115
191
  const subQueryResult = index.compileQuery(
116
192
  originalQuery,
117
193
  allInputs,
194
+ collections,
195
+ callbacks,
196
+ lazyCollections,
197
+ optimizableOrderByCollections,
118
198
  cache,
119
199
  queryMapping
120
200
  );
@@ -125,7 +205,11 @@ function processJoinSource(from, allInputs, cache, queryMapping) {
125
205
  return [key, value];
126
206
  })
127
207
  );
128
- return { alias: from.alias, input: extractedInput };
208
+ return {
209
+ alias: from.alias,
210
+ input: extractedInput,
211
+ collectionId: subQueryResult.collectionId
212
+ };
129
213
  }
130
214
  default:
131
215
  throw new errors.UnsupportedJoinSourceTypeError(from.type);
@@ -169,5 +253,20 @@ function processJoinResults(joinType) {
169
253
  );
170
254
  };
171
255
  }
256
+ function getActiveAndLazyCollections(joinType, leftCollection, rightCollection) {
257
+ if (leftCollection.id === rightCollection.id) {
258
+ return { activeCollection: void 0, lazyCollection: void 0 };
259
+ }
260
+ switch (joinType) {
261
+ case `left`:
262
+ return { activeCollection: `main`, lazyCollection: rightCollection };
263
+ case `right`:
264
+ return { activeCollection: `joined`, lazyCollection: leftCollection };
265
+ case `inner`:
266
+ return leftCollection.size < rightCollection.size ? { activeCollection: `main`, lazyCollection: rightCollection } : { activeCollection: `joined`, lazyCollection: leftCollection };
267
+ default:
268
+ return { activeCollection: void 0, lazyCollection: void 0 };
269
+ }
270
+ }
172
271
  exports.processJoins = processJoins;
173
272
  //# sourceMappingURL=joins.cjs.map
@@ -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 \"@tanstack/db-ivm\"\nimport {\n CollectionInputNotFoundError,\n InvalidJoinConditionSameTableError,\n InvalidJoinConditionTableMismatchError,\n InvalidJoinConditionWrongTablesError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from \"../../errors.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery } from \"./index.js\"\nimport type { IStreamBuilder, JoinType } from \"@tanstack/db-ivm\"\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n QueryRef,\n} 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 // Analyze which table each expression refers to and swap if necessary\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n mainTableAlias,\n joinedTableAlias\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n const mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main table expression\n const mainKey = compiledMainExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [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 joined table expression\n const joinedKey = compiledJoinedExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [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 UnsupportedJoinTypeError(joinClause.type)\n }\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which table\n * and returns them in the correct order (main table expression first, joined table expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n mainTableAlias: string,\n joinedTableAlias: string\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n const leftTableAlias = getTableAliasFromExpression(left)\n const rightTableAlias = getTableAliasFromExpression(right)\n\n // If left expression refers to main table and right refers to joined table, keep as is\n if (\n leftTableAlias === mainTableAlias &&\n rightTableAlias === joinedTableAlias\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined table and right refers to main table, swap them\n if (\n leftTableAlias === joinedTableAlias &&\n rightTableAlias === mainTableAlias\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftTableAlias === rightTableAlias) {\n throw new InvalidJoinConditionSameTableError(leftTableAlias || `unknown`)\n }\n\n // If one expression doesn't refer to either table, this is an invalid join\n if (!leftTableAlias || !rightTableAlias) {\n throw new InvalidJoinConditionTableMismatchError(\n mainTableAlias,\n joinedTableAlias\n )\n }\n\n // If expressions refer to tables not involved in this join, this is an invalid join\n throw new InvalidJoinConditionWrongTablesError(\n leftTableAlias,\n rightTableAlias,\n mainTableAlias,\n joinedTableAlias\n )\n}\n\n/**\n * Extracts the table alias from a join expression\n */\nfunction getTableAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the table alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same table\n const tableAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getTableAliasFromExpression(arg)\n if (alias) {\n tableAliases.add(alias)\n }\n }\n // If all arguments refer to the same table, return that table alias\n return tableAliases.size === 1 ? Array.from(tableAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any table\n return null\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 CollectionInputNotFoundError(from.collection.id)\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 subQueryResult = compileQuery(\n originalQuery,\n allInputs,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\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 UnsupportedJoinSourceTypeError((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","UnsupportedJoinTypeError","joinOperator","consolidate","InvalidJoinConditionSameTableError","InvalidJoinConditionTableMismatchError","InvalidJoinConditionWrongTablesError","CollectionInputNotFoundError","compileQuery","UnsupportedJoinSourceTypeError","filter"],"mappings":";;;;;;AAiCO,SAAS,aACd,UACA,aACA,QACA,gBACA,WACA,OACA,cAC0B;AAC1B,MAAI,iBAAiB;AAErB,aAAW,cAAc,aAAa;AACpC,qBAAiB;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AACT;AAKA,SAAS,YACP,UACA,YACA,QACA,gBACA,WACA,OACA,cAC0B;AAE1B,QAAM,EAAE,OAAO,kBAAkB,OAAO,gBAAgB;AAAA,IACtD,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAIF,SAAO,gBAAgB,IAAI;AAG3B,QAAM,WACJ,WAAW,SAAS,UAChB,UACA,WAAW,SAAS,UAClB,SACC,WAAW;AAGpB,QAAM,EAAE,UAAU,WAAA,IAAe;AAAA,IAC/B,WAAW;AAAA,IACX,WAAW;AAAA,IACX;AAAA,IACA;AAAA,EAAA;AAIF,QAAM,mBAAmBA,WAAAA,kBAAkB,QAAQ;AACnD,QAAM,qBAAqBA,WAAAA,kBAAkB,UAAU;AAGvD,QAAM,eAAe,SAAS;AAAA,IAC5BC,MAAAA,IAAI,CAAC,CAAC,YAAY,aAAa,MAAM;AAEnC,YAAM,UAAU,iBAAiB,aAAa;AAG9C,aAAO,CAAC,SAAS,CAAC,YAAY,aAAa,CAAC;AAAA,IAI9C,CAAC;AAAA,EAAA;AAIH,QAAM,iBAAiB,YAAY;AAAA,IACjCA,MAAAA,IAAI,CAAC,CAAC,YAAY,GAAG,MAAM;AAEzB,YAAM,gBAA+B,EAAE,CAAC,gBAAgB,GAAG,IAAA;AAG3D,YAAM,YAAY,mBAAmB,aAAa;AAGlD,aAAO,CAAC,WAAW,CAAC,YAAY,aAAa,CAAC;AAAA,IAIhD,CAAC;AAAA,EAAA;AAIH,MAAI,CAAC,CAAC,SAAS,QAAQ,SAAS,MAAM,EAAE,SAAS,QAAQ,GAAG;AAC1D,UAAM,IAAIC,OAAAA,yBAAyB,WAAW,IAAI;AAAA,EACpD;AACA,SAAO,aAAa;AAAA,IAClBC,MAAAA,KAAa,gBAAgB,QAAQ;AAAA,IACrCC,kBAAA;AAAA,IACA,mBAAmB,WAAW,IAAI;AAAA,EAAA;AAEtC;AAMA,SAAS,uBACP,MACA,OACA,gBACA,kBAC4D;AAC5D,QAAM,iBAAiB,4BAA4B,IAAI;AACvD,QAAM,kBAAkB,4BAA4B,KAAK;AAGzD,MACE,mBAAmB,kBACnB,oBAAoB,kBACpB;AACA,WAAO,EAAE,UAAU,MAAM,YAAY,MAAA;AAAA,EACvC;AAGA,MACE,mBAAmB,oBACnB,oBAAoB,gBACpB;AACA,WAAO,EAAE,UAAU,OAAO,YAAY,KAAA;AAAA,EACxC;AAGA,MAAI,mBAAmB,iBAAiB;AACtC,UAAM,IAAIC,OAAAA,mCAAmC,kBAAkB,SAAS;AAAA,EAC1E;AAGA,MAAI,CAAC,kBAAkB,CAAC,iBAAiB;AACvC,UAAM,IAAIC,OAAAA;AAAAA,MACR;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAGA,QAAM,IAAIC,OAAAA;AAAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAKA,SAAS,4BAA4B,MAAsC;AACzE,UAAQ,KAAK,MAAA;AAAA,IACX,KAAK;AAEH,aAAO,KAAK,KAAK,CAAC,KAAK;AAAA,IACzB,KAAK,QAAQ;AAEX,YAAM,mCAAmB,IAAA;AACzB,iBAAW,OAAO,KAAK,MAAM;AAC3B,cAAM,QAAQ,4BAA4B,GAAG;AAC7C,YAAI,OAAO;AACT,uBAAa,IAAI,KAAK;AAAA,QACxB;AAAA,MACF;AAEA,aAAO,aAAa,SAAS,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC,IAAK;AAAA,IAClE;AAAA,IACA;AAEE,aAAO;AAAA,EAAA;AAEb;AAKA,SAAS,kBACP,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,IAAIC,OAAAA,6BAA6B,KAAK,WAAW,EAAE;AAAA,MAC3D;AACA,aAAO,EAAE,OAAO,KAAK,OAAO,MAAA;AAAA,IAC9B;AAAA,IACA,KAAK,YAAY;AAEf,YAAM,gBAAgB,aAAa,IAAI,KAAK,KAAK,KAAK,KAAK;AAG3D,YAAM,iBAAiBC,MAAAA;AAAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAIF,YAAM,gBAAgB,eAAe;AAIrC,YAAM,iBAAiB,cAAc;AAAA,QACnCR,MAAAA,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,IAAIS,OAAAA,+BAAgC,KAAa,IAAI;AAAA,EAAA;AAEjE;AAKA,SAAS,mBAAmB,UAAkB;AAC5C,SAAO,SACL,UAS0B;AAC1B,WAAO,SAAS;AAAA;AAAA,MAEdC,MAAAA,OAAO,CAAC,WAAW;AACjB,cAAM,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI;AAC/B,cAAM,oBAAoB,6BAAO;AACjC,cAAM,sBAAsB,iCAAS;AAGrC,YAAI,aAAa,SAAS;AACxB,iBAAO,CAAC,EAAE,qBAAqB;AAAA,QACjC;AAEA,YAAI,aAAa,QAAQ;AACvB,iBAAO,CAAC,CAAC;AAAA,QACX;AAEA,YAAI,aAAa,SAAS;AACxB,iBAAO,CAAC,CAAC;AAAA,QACX;AAGA,eAAO;AAAA,MACT,CAAC;AAAA,MACDV,MAAAA,IAAI,CAAC,WAAW;AACd,cAAM,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI;AAC/B,cAAM,UAAU,6BAAO;AACvB,cAAM,oBAAoB,6BAAO;AACjC,cAAM,YAAY,iCAAS;AAC3B,cAAM,sBAAsB,iCAAS;AAGrC,cAAM,sBAAqC,CAAA;AAG3C,YAAI,mBAAmB;AACrB,iBAAO,OAAO,qBAAqB,iBAAiB;AAAA,QACtD;AAGA,YAAI,qBAAqB;AACvB,iBAAO,OAAO,qBAAqB,mBAAmB;AAAA,QACxD;AAGA,cAAM,YAAY,IAAI,OAAO,IAAI,SAAS;AAE1C,eAAO,CAAC,WAAW,mBAAmB;AAAA,MACxC,CAAC;AAAA,IAAA;AAAA,EAEL;AACF;;"}
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 tap,\n} from \"@tanstack/db-ivm\"\nimport {\n CollectionInputNotFoundError,\n InvalidJoinConditionSameTableError,\n InvalidJoinConditionTableMismatchError,\n InvalidJoinConditionWrongTablesError,\n JoinCollectionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from \"../../errors.js\"\nimport { findIndexForField } from \"../../utils/index-optimization.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery, followRef } from \"./index.js\"\nimport type { OrderByOptimizationInfo } from \"./order-by.js\"\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n PropRef,\n QueryIR,\n QueryRef,\n} from \"../ir.js\"\nimport type { IStreamBuilder, JoinType } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\nimport type { BaseIndex } from \"../../indexes/base-index.js\"\n\nexport type LoadKeysFn = (key: Set<string | number>) => void\nexport type LazyCollectionCallbacks = {\n loadKeys: LoadKeysFn\n loadInitialState: () => void\n}\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 mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableId,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n rawQuery\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 mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const {\n alias: joinedTableAlias,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n const mainCollection = collections[mainTableId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainTableId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeCollection, lazyCollection } = getActiveAndLazyCollections(\n joinClause.type,\n mainCollection,\n joinedCollection\n )\n\n // Analyze which table each expression refers to and swap if necessary\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n mainTableAlias,\n joinedTableAlias\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n let mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main table expression\n const mainKey = compiledMainExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n let 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 joined table expression\n const joinedKey = compiledJoinedExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [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(joinClause.type)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n\n if (activeCollection) {\n // This join can be optimized by having the active collection\n // dynamically load keys into the lazy collection\n // based on the value of the joinKey and by looking up\n // matching rows in the index of the lazy collection\n\n // Mark the lazy collection as lazy\n // this Set is passed by the liveQueryCollection to the compiler\n // such that the liveQueryCollection can check it after compilation\n // to know which collections are lazy collections\n lazyCollections.add(lazyCollection.id)\n\n const activePipeline =\n activeCollection === `main` ? mainPipeline : joinedPipeline\n\n let index: BaseIndex<string | number> | undefined\n\n const lazyCollectionJoinExpr =\n activeCollection === `main`\n ? (joinedExpr as PropRef)\n : (mainExpr as PropRef)\n\n const activeColl =\n activeCollection === `main` ? collections[mainTableId]! : lazyCollection\n\n const followRefResult = followRef(\n rawQuery,\n lazyCollectionJoinExpr,\n activeColl\n )!\n const followRefCollection = followRefResult.collection\n\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(fieldName, followRefResult.path, followRefCollection)\n }\n\n let deoptimized = false\n\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap(([joinKey, _]) => {\n if (deoptimized) {\n return\n }\n\n // Find the index for the path we join on\n // we need to find the index inside the map operator\n // because the indexes are only available after the initial sync\n // so we can't fetch it during compilation\n index ??= findIndexForField(\n followRefCollection.indexes,\n followRefResult.path\n )\n\n // The `callbacks` object is passed by the liveQueryCollection to the compiler.\n // It contains a function to lazy load keys for each lazy collection\n // as well as a function to switch back to a regular collection\n // (useful when there's no index for available for lazily loading the collection)\n const collectionCallbacks = callbacks[lazyCollection.id]\n if (!collectionCallbacks) {\n throw new Error(\n `Internal error: callbacks for collection are missing in join pipeline. Make sure the live query collection sets them before running the pipeline.`\n )\n }\n\n const { loadKeys, loadInitialState } = collectionCallbacks\n\n if (index && index.supports(`eq`)) {\n // Use the index to fetch the PKs of the rows in the lazy collection\n // that match this row from the active collection based on the value of the joinKey\n const matchingKeys = index.lookup(`eq`, joinKey)\n // Inform the lazy collection that those keys need to be loaded\n loadKeys(matchingKeys)\n } else {\n // We can't optimize the join because there is no index for the join key\n // on the lazy collection, so we load the initial state\n deoptimized = true\n loadInitialState()\n }\n })\n )\n\n if (activeCollection === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which table\n * and returns them in the correct order (main table expression first, joined table expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n mainTableAlias: string,\n joinedTableAlias: string\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n const leftTableAlias = getTableAliasFromExpression(left)\n const rightTableAlias = getTableAliasFromExpression(right)\n\n // If left expression refers to main table and right refers to joined table, keep as is\n if (\n leftTableAlias === mainTableAlias &&\n rightTableAlias === joinedTableAlias\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined table and right refers to main table, swap them\n if (\n leftTableAlias === joinedTableAlias &&\n rightTableAlias === mainTableAlias\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftTableAlias === rightTableAlias) {\n throw new InvalidJoinConditionSameTableError(leftTableAlias || `unknown`)\n }\n\n // If one expression doesn't refer to either table, this is an invalid join\n if (!leftTableAlias || !rightTableAlias) {\n throw new InvalidJoinConditionTableMismatchError(\n mainTableAlias,\n joinedTableAlias\n )\n }\n\n // If expressions refer to tables not involved in this join, this is an invalid join\n throw new InvalidJoinConditionWrongTablesError(\n leftTableAlias,\n rightTableAlias,\n mainTableAlias,\n joinedTableAlias\n )\n}\n\n/**\n * Extracts the table alias from a join expression\n */\nfunction getTableAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the table alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same table\n const tableAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getTableAliasFromExpression(arg)\n if (alias) {\n tableAliases.add(alias)\n }\n }\n // If all arguments refer to the same table, return that table alias\n return tableAliases.size === 1 ? Array.from(tableAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any table\n return null\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 collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new CollectionInputNotFoundError(from.collection.id)\n }\n return { alias: from.alias, input, collectionId: from.collection.id }\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 subQueryResult = compileQuery(\n originalQuery,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\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 {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((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\n/**\n * Returns the active and lazy collections for a join clause.\n * The active collection is the one that we need to fully iterate over\n * and it can be the main table (i.e. left collection) or the joined table (i.e. right collection).\n * The lazy collection is the one that we should join-in lazily based on matches in the active collection.\n * @param joinClause - The join clause to analyze\n * @param leftCollection - The left collection\n * @param rightCollection - The right collection\n * @returns The active and lazy collections. They are undefined if we need to loop over both collections (i.e. both are active)\n */\nfunction getActiveAndLazyCollections(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection\n):\n | { activeCollection: `main` | `joined`; lazyCollection: Collection }\n | { activeCollection: undefined; lazyCollection: undefined } {\n if (leftCollection.id === rightCollection.id) {\n // We can't apply this optimization if there's only one collection\n // because `liveQueryCollection` will detect that the collection is lazy\n // and treat it lazily (because the collection is shared)\n // and thus it will not load any keys because both sides of the join\n // will be handled lazily\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n\n switch (joinType) {\n case `left`:\n return { activeCollection: `main`, lazyCollection: rightCollection }\n case `right`:\n return { activeCollection: `joined`, lazyCollection: leftCollection }\n case `inner`:\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeCollection: `main`, lazyCollection: rightCollection }\n : { activeCollection: `joined`, lazyCollection: leftCollection }\n default:\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n}\n"],"names":["JoinCollectionNotFoundError","compileExpression","map","UnsupportedJoinTypeError","index","followRef","ensureIndexForField","tap","findIndexForField","joinOperator","consolidate","InvalidJoinConditionSameTableError","InvalidJoinConditionTableMismatchError","InvalidJoinConditionWrongTablesError","CollectionInputNotFoundError","compileQuery","UnsupportedJoinSourceTypeError","filter"],"mappings":"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"}
@@ -1,7 +1,14 @@
1
- import { JoinClause } from '../ir.js';
1
+ import { OrderByOptimizationInfo } from './order-by.js';
2
+ import { JoinClause, QueryIR } from '../ir.js';
3
+ import { Collection } from '../../collection.js';
2
4
  import { KeyedStream, NamespacedAndKeyedStream } from '../../types.js';
3
5
  import { QueryCache, QueryMapping } from './types.js';
6
+ export type LoadKeysFn = (key: Set<string | number>) => void;
7
+ export type LazyCollectionCallbacks = {
8
+ loadKeys: LoadKeysFn;
9
+ loadInitialState: () => void;
10
+ };
4
11
  /**
5
12
  * Processes all join clauses in a query
6
13
  */
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;
14
+ export declare function processJoins(pipeline: NamespacedAndKeyedStream, joinClauses: Array<JoinClause>, tables: Record<string, KeyedStream>, mainTableId: string, mainTableAlias: string, allInputs: Record<string, KeyedStream>, cache: QueryCache, queryMapping: QueryMapping, collections: Record<string, Collection>, callbacks: Record<string, LazyCollectionCallbacks>, lazyCollections: Set<string>, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, rawQuery: QueryIR): NamespacedAndKeyedStream;
@@ -2,11 +2,15 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const dbIvm = require("@tanstack/db-ivm");
4
4
  const comparison = require("../../utils/comparison.cjs");
5
+ const ir = require("../ir.cjs");
6
+ const autoIndex = require("../../indexes/auto-index.cjs");
7
+ const indexOptimization = require("../../utils/index-optimization.cjs");
5
8
  const evaluators = require("./evaluators.cjs");
6
- function processOrderBy(pipeline, orderByClause, limit, offset) {
9
+ const index = require("./index.cjs");
10
+ function processOrderBy(rawQuery, pipeline, orderByClause, collection, optimizableOrderByCollections, limit, offset) {
7
11
  const compiledOrderBy = orderByClause.map((clause) => ({
8
12
  compiledExpression: evaluators.compileExpression(clause.expression),
9
- direction: clause.direction
13
+ compareOptions: clause.compareOptions
10
14
  }));
11
15
  const valueExtractor = (row) => {
12
16
  const orderByContext = { ...row };
@@ -23,34 +27,87 @@ function processOrderBy(pipeline, orderByClause, limit, offset) {
23
27
  }
24
28
  return null;
25
29
  };
26
- const makeComparator = () => {
27
- return (a, b) => {
28
- if (orderByClause.length > 1) {
29
- const arrayA = a;
30
- const arrayB = b;
31
- for (let i = 0; i < orderByClause.length; i++) {
32
- const direction = orderByClause[i].direction;
33
- const compareFn = direction === `desc` ? comparison.descComparator : comparison.ascComparator;
34
- const result = compareFn(arrayA[i], arrayB[i]);
35
- if (result !== 0) {
36
- return result;
37
- }
30
+ const compare = (a, b) => {
31
+ if (orderByClause.length > 1) {
32
+ const arrayA = a;
33
+ const arrayB = b;
34
+ for (let i = 0; i < orderByClause.length; i++) {
35
+ const clause = orderByClause[i];
36
+ const compareFn = comparison.makeComparator(clause.compareOptions);
37
+ const result = compareFn(arrayA[i], arrayB[i]);
38
+ if (result !== 0) {
39
+ return result;
38
40
  }
39
- return arrayA.length - arrayB.length;
40
- }
41
- if (orderByClause.length === 1) {
42
- const direction = orderByClause[0].direction;
43
- return direction === `desc` ? comparison.descComparator(a, b) : comparison.ascComparator(a, b);
44
41
  }
45
- return comparison.ascComparator(a, b);
46
- };
42
+ return arrayA.length - arrayB.length;
43
+ }
44
+ if (orderByClause.length === 1) {
45
+ const clause = orderByClause[0];
46
+ const compareFn = comparison.makeComparator(clause.compareOptions);
47
+ return compareFn(a, b);
48
+ }
49
+ return comparison.defaultComparator(a, b);
47
50
  };
48
- const comparator = makeComparator();
51
+ let setSizeCallback;
52
+ if (limit && orderByClause.length === 1) {
53
+ const clause = orderByClause[0];
54
+ const orderByExpression = clause.expression;
55
+ if (orderByExpression.type === `ref`) {
56
+ const followRefResult = index.followRef(
57
+ rawQuery,
58
+ orderByExpression,
59
+ collection
60
+ );
61
+ const followRefCollection = followRefResult.collection;
62
+ const fieldName = followRefResult.path[0];
63
+ if (fieldName) {
64
+ autoIndex.ensureIndexForField(
65
+ fieldName,
66
+ followRefResult.path,
67
+ followRefCollection,
68
+ compare
69
+ );
70
+ }
71
+ const valueExtractorForRawRow = evaluators.compileExpression(
72
+ new ir.PropRef(followRefResult.path),
73
+ true
74
+ );
75
+ const comparator = (a, b) => {
76
+ const extractedA = a ? valueExtractorForRawRow(a) : a;
77
+ const extractedB = b ? valueExtractorForRawRow(b) : b;
78
+ return compare(extractedA, extractedB);
79
+ };
80
+ const index$1 = indexOptimization.findIndexForField(
81
+ followRefCollection.indexes,
82
+ followRefResult.path
83
+ );
84
+ if (index$1 && index$1.supports(`gt`)) {
85
+ const orderByOptimizationInfo = {
86
+ offset: offset ?? 0,
87
+ limit,
88
+ comparator,
89
+ valueExtractorForRawRow,
90
+ index: index$1
91
+ };
92
+ optimizableOrderByCollections[followRefCollection.id] = orderByOptimizationInfo;
93
+ setSizeCallback = (getSize) => {
94
+ optimizableOrderByCollections[followRefCollection.id] = {
95
+ ...optimizableOrderByCollections[followRefCollection.id],
96
+ dataNeeded: () => {
97
+ const size = getSize();
98
+ return Math.max(0, limit - size);
99
+ }
100
+ };
101
+ };
102
+ }
103
+ }
104
+ }
49
105
  return pipeline.pipe(
50
106
  dbIvm.orderByWithFractionalIndex(valueExtractor, {
51
107
  limit,
52
108
  offset,
53
- comparator
109
+ comparator: compare,
110
+ setSizeCallback
54
111
  })
55
112
  // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
56
113
  );
@@ -1 +1 @@
1
- {"version":3,"file":"order-by.cjs","sources":["../../../../src/query/compiler/order-by.ts"],"sourcesContent":["import { orderByWithFractionalIndex } from \"@tanstack/db-ivm\"\nimport { ascComparator, descComparator } from \"../../utils/comparison.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { OrderByClause } from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\nimport type { IStreamBuilder, KeyValue } from \"@tanstack/db-ivm\"\n\n/**\n * Processes the ORDER BY clause\n * Works with the new structure that has both namespaced row data and __select_results\n * Always uses fractional indexing and adds the index as __ordering_index to the result\n */\nexport function processOrderBy(\n pipeline: NamespacedAndKeyedStream,\n orderByClause: Array<OrderByClause>,\n limit?: number,\n offset?: number\n): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {\n // Pre-compile all order by expressions\n const compiledOrderBy = orderByClause.map((clause) => ({\n compiledExpression: compileExpression(clause.expression),\n direction: clause.direction,\n }))\n\n // Create a value extractor function for the orderBy operator\n const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {\n // For ORDER BY expressions, we need to provide access to both:\n // 1. The original namespaced row data (for direct table column references)\n // 2. The __select_results (for SELECT alias references)\n\n // Create a merged context for expression evaluation\n const orderByContext = { ...row }\n\n // If there are select results, merge them at the top level for alias access\n if (row.__select_results) {\n // Add select results as top-level properties for alias access\n Object.assign(orderByContext, row.__select_results)\n }\n\n if (orderByClause.length > 1) {\n // For multiple orderBy columns, create a composite key\n return compiledOrderBy.map((compiled) =>\n compiled.compiledExpression(orderByContext)\n )\n } else if (orderByClause.length === 1) {\n // For a single orderBy column, use the value directly\n const compiled = compiledOrderBy[0]!\n return compiled.compiledExpression(orderByContext)\n }\n\n // Default case - no ordering\n return null\n }\n\n // Create a multi-property comparator that respects the order and direction of each property\n const makeComparator = () => {\n return (a: unknown, b: unknown) => {\n // If we're comparing arrays (multiple properties), compare each property in order\n if (orderByClause.length > 1) {\n const arrayA = a as Array<unknown>\n const arrayB = b as Array<unknown>\n for (let i = 0; i < orderByClause.length; i++) {\n const direction = orderByClause[i]!.direction\n const compareFn =\n direction === `desc` ? descComparator : ascComparator\n const result = compareFn(arrayA[i], arrayB[i])\n if (result !== 0) {\n return result\n }\n }\n return arrayA.length - arrayB.length\n }\n\n // Single property comparison\n if (orderByClause.length === 1) {\n const direction = orderByClause[0]!.direction\n return direction === `desc` ? descComparator(a, b) : ascComparator(a, b)\n }\n\n return ascComparator(a, b)\n }\n }\n\n const comparator = makeComparator()\n\n // Use fractional indexing and return the tuple [value, index]\n return pipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit,\n offset,\n comparator,\n })\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n"],"names":["compileExpression","descComparator","ascComparator","orderByWithFractionalIndex"],"mappings":";;;;;AAYO,SAAS,eACd,UACA,eACA,OACA,QAC4D;AAE5D,QAAM,kBAAkB,cAAc,IAAI,CAAC,YAAY;AAAA,IACrD,oBAAoBA,WAAAA,kBAAkB,OAAO,UAAU;AAAA,IACvD,WAAW,OAAO;AAAA,EAAA,EAClB;AAGF,QAAM,iBAAiB,CAAC,QAAoD;AAM1E,UAAM,iBAAiB,EAAE,GAAG,IAAA;AAG5B,QAAI,IAAI,kBAAkB;AAExB,aAAO,OAAO,gBAAgB,IAAI,gBAAgB;AAAA,IACpD;AAEA,QAAI,cAAc,SAAS,GAAG;AAE5B,aAAO,gBAAgB;AAAA,QAAI,CAAC,aAC1B,SAAS,mBAAmB,cAAc;AAAA,MAAA;AAAA,IAE9C,WAAW,cAAc,WAAW,GAAG;AAErC,YAAM,WAAW,gBAAgB,CAAC;AAClC,aAAO,SAAS,mBAAmB,cAAc;AAAA,IACnD;AAGA,WAAO;AAAA,EACT;AAGA,QAAM,iBAAiB,MAAM;AAC3B,WAAO,CAAC,GAAY,MAAe;AAEjC,UAAI,cAAc,SAAS,GAAG;AAC5B,cAAM,SAAS;AACf,cAAM,SAAS;AACf,iBAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,gBAAM,YAAY,cAAc,CAAC,EAAG;AACpC,gBAAM,YACJ,cAAc,SAASC,WAAAA,iBAAiBC,WAAAA;AAC1C,gBAAM,SAAS,UAAU,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAC7C,cAAI,WAAW,GAAG;AAChB,mBAAO;AAAA,UACT;AAAA,QACF;AACA,eAAO,OAAO,SAAS,OAAO;AAAA,MAChC;AAGA,UAAI,cAAc,WAAW,GAAG;AAC9B,cAAM,YAAY,cAAc,CAAC,EAAG;AACpC,eAAO,cAAc,SAASD,WAAAA,eAAe,GAAG,CAAC,IAAIC,WAAAA,cAAc,GAAG,CAAC;AAAA,MACzE;AAEA,aAAOA,WAAAA,cAAc,GAAG,CAAC;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,aAAa,eAAA;AAGnB,SAAO,SAAS;AAAA,IACdC,MAAAA,2BAA2B,gBAAgB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA;AAAA,EAAA;AAGL;;"}
1
+ {"version":3,"file":"order-by.cjs","sources":["../../../../src/query/compiler/order-by.ts"],"sourcesContent":["import { orderByWithFractionalIndex } from \"@tanstack/db-ivm\"\nimport { defaultComparator, makeComparator } from \"../../utils/comparison.js\"\nimport { PropRef } from \"../ir.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { findIndexForField } from \"../../utils/index-optimization.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { followRef } from \"./index.js\"\nimport type { CompiledSingleRowExpression } from \"./evaluators.js\"\nimport type { OrderByClause, QueryIR } from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\nimport type { IStreamBuilder, KeyValue } from \"@tanstack/db-ivm\"\nimport type { BaseIndex } from \"../../indexes/base-index.js\"\nimport type { Collection } from \"../../collection.js\"\n\nexport type OrderByOptimizationInfo = {\n offset: number\n limit: number\n comparator: (\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined\n ) => number\n valueExtractorForRawRow: (row: Record<string, unknown>) => any\n index: BaseIndex<string | number>\n dataNeeded?: () => number\n}\n\n/**\n * Processes the ORDER BY clause\n * Works with the new structure that has both namespaced row data and __select_results\n * Always uses fractional indexing and adds the index as __ordering_index to the result\n */\nexport function processOrderBy(\n rawQuery: QueryIR,\n pipeline: NamespacedAndKeyedStream,\n orderByClause: Array<OrderByClause>,\n collection: Collection,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n limit?: number,\n offset?: number\n): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {\n // Pre-compile all order by expressions\n const compiledOrderBy = orderByClause.map((clause) => ({\n compiledExpression: compileExpression(clause.expression),\n compareOptions: clause.compareOptions,\n }))\n\n // Create a value extractor function for the orderBy operator\n const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {\n // For ORDER BY expressions, we need to provide access to both:\n // 1. The original namespaced row data (for direct table column references)\n // 2. The __select_results (for SELECT alias references)\n\n // Create a merged context for expression evaluation\n const orderByContext = { ...row }\n\n // If there are select results, merge them at the top level for alias access\n if (row.__select_results) {\n // Add select results as top-level properties for alias access\n Object.assign(orderByContext, row.__select_results)\n }\n\n if (orderByClause.length > 1) {\n // For multiple orderBy columns, create a composite key\n return compiledOrderBy.map((compiled) =>\n compiled.compiledExpression(orderByContext)\n )\n } else if (orderByClause.length === 1) {\n // For a single orderBy column, use the value directly\n const compiled = compiledOrderBy[0]!\n return compiled.compiledExpression(orderByContext)\n }\n\n // Default case - no ordering\n return null\n }\n\n // Create a multi-property comparator that respects the order and direction of each property\n const compare = (a: unknown, b: unknown) => {\n // If we're comparing arrays (multiple properties), compare each property in order\n if (orderByClause.length > 1) {\n const arrayA = a as Array<unknown>\n const arrayB = b as Array<unknown>\n for (let i = 0; i < orderByClause.length; i++) {\n const clause = orderByClause[i]!\n const compareFn = makeComparator(clause.compareOptions)\n const result = compareFn(arrayA[i], arrayB[i])\n if (result !== 0) {\n return result\n }\n }\n return arrayA.length - arrayB.length\n }\n\n // Single property comparison\n if (orderByClause.length === 1) {\n const clause = orderByClause[0]!\n const compareFn = makeComparator(clause.compareOptions)\n return compareFn(a, b)\n }\n\n return defaultComparator(a, b)\n }\n\n let setSizeCallback: ((getSize: () => number) => void) | undefined\n\n // Optimize the orderBy operator to lazily load elements\n // by using the range index of the collection.\n // Only for orderBy clause on a single column for now (no composite ordering)\n if (limit && orderByClause.length === 1) {\n const clause = orderByClause[0]!\n const orderByExpression = clause.expression\n\n if (orderByExpression.type === `ref`) {\n const followRefResult = followRef(\n rawQuery,\n orderByExpression,\n collection\n )!\n\n const followRefCollection = followRefResult.collection\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(\n fieldName,\n followRefResult.path,\n followRefCollection,\n compare\n )\n }\n\n const valueExtractorForRawRow = compileExpression(\n new PropRef(followRefResult.path),\n true\n ) as CompiledSingleRowExpression\n\n const comparator = (\n a: Record<string, unknown> | null | undefined,\n b: Record<string, unknown> | null | undefined\n ) => {\n const extractedA = a ? valueExtractorForRawRow(a) : a\n const extractedB = b ? valueExtractorForRawRow(b) : b\n return compare(extractedA, extractedB)\n }\n\n const index: BaseIndex<string | number> | undefined = findIndexForField(\n followRefCollection.indexes,\n followRefResult.path\n )\n\n if (index && index.supports(`gt`)) {\n // We found an index that we can use to lazily load ordered data\n const orderByOptimizationInfo = {\n offset: offset ?? 0,\n limit,\n comparator,\n valueExtractorForRawRow,\n index,\n }\n\n optimizableOrderByCollections[followRefCollection.id] =\n orderByOptimizationInfo\n\n setSizeCallback = (getSize: () => number) => {\n optimizableOrderByCollections[followRefCollection.id] = {\n ...optimizableOrderByCollections[followRefCollection.id]!,\n dataNeeded: () => {\n const size = getSize()\n return Math.max(0, limit - size)\n },\n }\n }\n }\n }\n }\n\n // Use fractional indexing and return the tuple [value, index]\n return pipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit,\n offset,\n comparator: compare,\n setSizeCallback,\n })\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n"],"names":["compileExpression","makeComparator","defaultComparator","followRef","ensureIndexForField","PropRef","index","findIndexForField","orderByWithFractionalIndex"],"mappings":"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"}
@@ -1,9 +1,19 @@
1
- import { OrderByClause } from '../ir.js';
1
+ import { OrderByClause, QueryIR } from '../ir.js';
2
2
  import { NamespacedAndKeyedStream, NamespacedRow } from '../../types.js';
3
3
  import { IStreamBuilder, KeyValue } from '@tanstack/db-ivm';
4
+ import { BaseIndex } from '../../indexes/base-index.js';
5
+ import { Collection } from '../../collection.js';
6
+ export type OrderByOptimizationInfo = {
7
+ offset: number;
8
+ limit: number;
9
+ comparator: (a: Record<string, unknown> | null | undefined, b: Record<string, unknown> | null | undefined) => number;
10
+ valueExtractorForRawRow: (row: Record<string, unknown>) => any;
11
+ index: BaseIndex<string | number>;
12
+ dataNeeded?: () => number;
13
+ };
4
14
  /**
5
15
  * Processes the ORDER BY clause
6
16
  * Works with the new structure that has both namespaced row data and __select_results
7
17
  * Always uses fractional indexing and adds the index as __ordering_index to the result
8
18
  */
9
- export declare function processOrderBy(pipeline: NamespacedAndKeyedStream, orderByClause: Array<OrderByClause>, limit?: number, offset?: number): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>>;
19
+ export declare function processOrderBy(rawQuery: QueryIR, pipeline: NamespacedAndKeyedStream, orderByClause: Array<OrderByClause>, collection: Collection, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, limit?: number, offset?: number): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>>;
@@ -1 +1 @@
1
- {"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CollectionImpl } from \"../collection\"\nimport type { NamespacedRow } from \"../types\"\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n distinct?: true\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where = BasicExpression<boolean>\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n direction: OrderByDirection\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class PropRef<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string> // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n"],"names":[],"mappings":";;AA6DA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACA,UAAA;AAHO,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACA,UAAA;AAHO,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,gBAAyB,eAAkB;AAAA,EAEtD,YACS,MACP;AACA,UAAA;AAFO,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACA,UAAA;AAFO,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;;;;;;;"}
1
+ {"version":3,"file":"ir.cjs","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CompareOptions } from \"./builder/types\"\nimport type { CollectionImpl } from \"../collection\"\nimport type { NamespacedRow } from \"../types\"\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n distinct?: true\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where = BasicExpression<boolean>\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n compareOptions: CompareOptions\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class PropRef<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string> // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n"],"names":[],"mappings":";;AA8DA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACA,UAAA;AAHO,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACA,UAAA;AAHO,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,gBAAyB,eAAkB;AAAA,EAEtD,YACS,MACP;AACA,UAAA;AAFO,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACA,UAAA;AAFO,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;;;;;;;"}
@@ -1,3 +1,4 @@
1
+ import { CompareOptions } from './builder/types.cjs';
1
2
  import { CollectionImpl } from '../collection.cjs';
2
3
  import { NamespacedRow } from '../types.cjs';
3
4
  export interface QueryIR {
@@ -32,7 +33,7 @@ export type Having = Where;
32
33
  export type OrderBy = Array<OrderByClause>;
33
34
  export type OrderByClause = {
34
35
  expression: BasicExpression;
35
- direction: OrderByDirection;
36
+ compareOptions: CompareOptions;
36
37
  };
37
38
  export type OrderByDirection = `asc` | `desc`;
38
39
  export type Limit = number;