@tanstack/db 0.0.14 → 0.0.15

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 (198) hide show
  1. package/dist/cjs/collection.cjs +117 -104
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +18 -21
  4. package/dist/cjs/index.cjs +35 -13
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/index.d.cts +0 -1
  7. package/dist/cjs/query/builder/functions.cjs +107 -0
  8. package/dist/cjs/query/builder/functions.cjs.map +1 -0
  9. package/dist/cjs/query/builder/functions.d.cts +38 -0
  10. package/dist/cjs/query/builder/index.cjs +499 -0
  11. package/dist/cjs/query/builder/index.cjs.map +1 -0
  12. package/dist/cjs/query/builder/index.d.cts +324 -0
  13. package/dist/cjs/query/builder/ref-proxy.cjs +96 -0
  14. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -0
  15. package/dist/cjs/query/builder/ref-proxy.d.cts +28 -0
  16. package/dist/cjs/query/builder/types.d.cts +80 -0
  17. package/dist/cjs/query/compiler/evaluators.cjs +261 -0
  18. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -0
  19. package/dist/cjs/query/compiler/evaluators.d.cts +11 -0
  20. package/dist/cjs/query/compiler/group-by.cjs +271 -0
  21. package/dist/cjs/query/compiler/group-by.cjs.map +1 -0
  22. package/dist/cjs/query/compiler/group-by.d.cts +7 -0
  23. package/dist/cjs/query/compiler/index.cjs +181 -0
  24. package/dist/cjs/query/compiler/index.cjs.map +1 -0
  25. package/dist/cjs/query/compiler/index.d.cts +15 -0
  26. package/dist/cjs/query/compiler/joins.cjs +116 -0
  27. package/dist/cjs/query/compiler/joins.cjs.map +1 -0
  28. package/dist/cjs/query/compiler/joins.d.cts +11 -0
  29. package/dist/cjs/query/compiler/order-by.cjs +89 -0
  30. package/dist/cjs/query/compiler/order-by.cjs.map +1 -0
  31. package/dist/cjs/query/compiler/order-by.d.cts +9 -0
  32. package/dist/cjs/query/compiler/select.cjs +57 -0
  33. package/dist/cjs/query/compiler/select.cjs.map +1 -0
  34. package/dist/cjs/query/compiler/select.d.cts +15 -0
  35. package/dist/cjs/query/index.d.cts +6 -5
  36. package/dist/cjs/query/ir.cjs +57 -0
  37. package/dist/cjs/query/ir.cjs.map +1 -0
  38. package/dist/cjs/query/ir.d.cts +81 -0
  39. package/dist/cjs/query/live-query-collection.cjs +224 -0
  40. package/dist/cjs/query/live-query-collection.cjs.map +1 -0
  41. package/dist/cjs/query/live-query-collection.d.cts +124 -0
  42. package/dist/cjs/transactions.cjs +20 -13
  43. package/dist/cjs/transactions.cjs.map +1 -1
  44. package/dist/cjs/transactions.d.cts +10 -1
  45. package/dist/cjs/types.d.cts +13 -0
  46. package/dist/esm/collection.d.ts +18 -21
  47. package/dist/esm/collection.js +118 -105
  48. package/dist/esm/collection.js.map +1 -1
  49. package/dist/esm/index.d.ts +0 -1
  50. package/dist/esm/index.js +34 -12
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/query/builder/functions.d.ts +38 -0
  53. package/dist/esm/query/builder/functions.js +107 -0
  54. package/dist/esm/query/builder/functions.js.map +1 -0
  55. package/dist/esm/query/builder/index.d.ts +324 -0
  56. package/dist/esm/query/builder/index.js +499 -0
  57. package/dist/esm/query/builder/index.js.map +1 -0
  58. package/dist/esm/query/builder/ref-proxy.d.ts +28 -0
  59. package/dist/esm/query/builder/ref-proxy.js +96 -0
  60. package/dist/esm/query/builder/ref-proxy.js.map +1 -0
  61. package/dist/esm/query/builder/types.d.ts +80 -0
  62. package/dist/esm/query/compiler/evaluators.d.ts +11 -0
  63. package/dist/esm/query/compiler/evaluators.js +261 -0
  64. package/dist/esm/query/compiler/evaluators.js.map +1 -0
  65. package/dist/esm/query/compiler/group-by.d.ts +7 -0
  66. package/dist/esm/query/compiler/group-by.js +271 -0
  67. package/dist/esm/query/compiler/group-by.js.map +1 -0
  68. package/dist/esm/query/compiler/index.d.ts +15 -0
  69. package/dist/esm/query/compiler/index.js +181 -0
  70. package/dist/esm/query/compiler/index.js.map +1 -0
  71. package/dist/esm/query/compiler/joins.d.ts +11 -0
  72. package/dist/esm/query/compiler/joins.js +116 -0
  73. package/dist/esm/query/compiler/joins.js.map +1 -0
  74. package/dist/esm/query/compiler/order-by.d.ts +9 -0
  75. package/dist/esm/query/compiler/order-by.js +89 -0
  76. package/dist/esm/query/compiler/order-by.js.map +1 -0
  77. package/dist/esm/query/compiler/select.d.ts +15 -0
  78. package/dist/esm/query/compiler/select.js +57 -0
  79. package/dist/esm/query/compiler/select.js.map +1 -0
  80. package/dist/esm/query/index.d.ts +6 -5
  81. package/dist/esm/query/ir.d.ts +81 -0
  82. package/dist/esm/query/ir.js +57 -0
  83. package/dist/esm/query/ir.js.map +1 -0
  84. package/dist/esm/query/live-query-collection.d.ts +124 -0
  85. package/dist/esm/query/live-query-collection.js +224 -0
  86. package/dist/esm/query/live-query-collection.js.map +1 -0
  87. package/dist/esm/transactions.d.ts +10 -1
  88. package/dist/esm/transactions.js +20 -13
  89. package/dist/esm/transactions.js.map +1 -1
  90. package/dist/esm/types.d.ts +13 -0
  91. package/package.json +3 -4
  92. package/src/collection.ts +152 -129
  93. package/src/index.ts +0 -1
  94. package/src/query/builder/functions.ts +267 -0
  95. package/src/query/builder/index.ts +648 -0
  96. package/src/query/builder/ref-proxy.ts +156 -0
  97. package/src/query/builder/types.ts +278 -0
  98. package/src/query/compiler/evaluators.ts +315 -0
  99. package/src/query/compiler/group-by.ts +428 -0
  100. package/src/query/compiler/index.ts +276 -0
  101. package/src/query/compiler/joins.ts +228 -0
  102. package/src/query/compiler/order-by.ts +139 -0
  103. package/src/query/compiler/select.ts +173 -0
  104. package/src/query/index.ts +64 -5
  105. package/src/query/ir.ts +128 -0
  106. package/src/query/live-query-collection.ts +509 -0
  107. package/src/transactions.ts +27 -16
  108. package/src/types.ts +15 -0
  109. package/dist/cjs/query/compiled-query.cjs +0 -160
  110. package/dist/cjs/query/compiled-query.cjs.map +0 -1
  111. package/dist/cjs/query/compiled-query.d.cts +0 -20
  112. package/dist/cjs/query/evaluators.cjs +0 -161
  113. package/dist/cjs/query/evaluators.cjs.map +0 -1
  114. package/dist/cjs/query/evaluators.d.cts +0 -14
  115. package/dist/cjs/query/extractors.cjs +0 -122
  116. package/dist/cjs/query/extractors.cjs.map +0 -1
  117. package/dist/cjs/query/extractors.d.cts +0 -22
  118. package/dist/cjs/query/functions.cjs +0 -152
  119. package/dist/cjs/query/functions.cjs.map +0 -1
  120. package/dist/cjs/query/functions.d.cts +0 -21
  121. package/dist/cjs/query/group-by.cjs +0 -88
  122. package/dist/cjs/query/group-by.cjs.map +0 -1
  123. package/dist/cjs/query/group-by.d.cts +0 -40
  124. package/dist/cjs/query/joins.cjs +0 -141
  125. package/dist/cjs/query/joins.cjs.map +0 -1
  126. package/dist/cjs/query/joins.d.cts +0 -14
  127. package/dist/cjs/query/order-by.cjs +0 -185
  128. package/dist/cjs/query/order-by.cjs.map +0 -1
  129. package/dist/cjs/query/order-by.d.cts +0 -3
  130. package/dist/cjs/query/pipeline-compiler.cjs +0 -89
  131. package/dist/cjs/query/pipeline-compiler.cjs.map +0 -1
  132. package/dist/cjs/query/pipeline-compiler.d.cts +0 -10
  133. package/dist/cjs/query/query-builder.cjs +0 -307
  134. package/dist/cjs/query/query-builder.cjs.map +0 -1
  135. package/dist/cjs/query/query-builder.d.cts +0 -225
  136. package/dist/cjs/query/schema.d.cts +0 -100
  137. package/dist/cjs/query/select.cjs +0 -130
  138. package/dist/cjs/query/select.cjs.map +0 -1
  139. package/dist/cjs/query/select.d.cts +0 -3
  140. package/dist/cjs/query/types.d.cts +0 -189
  141. package/dist/cjs/query/utils.cjs +0 -154
  142. package/dist/cjs/query/utils.cjs.map +0 -1
  143. package/dist/cjs/query/utils.d.cts +0 -37
  144. package/dist/cjs/utils.cjs +0 -17
  145. package/dist/cjs/utils.cjs.map +0 -1
  146. package/dist/cjs/utils.d.cts +0 -3
  147. package/dist/esm/query/compiled-query.d.ts +0 -20
  148. package/dist/esm/query/compiled-query.js +0 -160
  149. package/dist/esm/query/compiled-query.js.map +0 -1
  150. package/dist/esm/query/evaluators.d.ts +0 -14
  151. package/dist/esm/query/evaluators.js +0 -161
  152. package/dist/esm/query/evaluators.js.map +0 -1
  153. package/dist/esm/query/extractors.d.ts +0 -22
  154. package/dist/esm/query/extractors.js +0 -122
  155. package/dist/esm/query/extractors.js.map +0 -1
  156. package/dist/esm/query/functions.d.ts +0 -21
  157. package/dist/esm/query/functions.js +0 -152
  158. package/dist/esm/query/functions.js.map +0 -1
  159. package/dist/esm/query/group-by.d.ts +0 -40
  160. package/dist/esm/query/group-by.js +0 -88
  161. package/dist/esm/query/group-by.js.map +0 -1
  162. package/dist/esm/query/joins.d.ts +0 -14
  163. package/dist/esm/query/joins.js +0 -141
  164. package/dist/esm/query/joins.js.map +0 -1
  165. package/dist/esm/query/order-by.d.ts +0 -3
  166. package/dist/esm/query/order-by.js +0 -185
  167. package/dist/esm/query/order-by.js.map +0 -1
  168. package/dist/esm/query/pipeline-compiler.d.ts +0 -10
  169. package/dist/esm/query/pipeline-compiler.js +0 -89
  170. package/dist/esm/query/pipeline-compiler.js.map +0 -1
  171. package/dist/esm/query/query-builder.d.ts +0 -225
  172. package/dist/esm/query/query-builder.js +0 -307
  173. package/dist/esm/query/query-builder.js.map +0 -1
  174. package/dist/esm/query/schema.d.ts +0 -100
  175. package/dist/esm/query/select.d.ts +0 -3
  176. package/dist/esm/query/select.js +0 -130
  177. package/dist/esm/query/select.js.map +0 -1
  178. package/dist/esm/query/types.d.ts +0 -189
  179. package/dist/esm/query/utils.d.ts +0 -37
  180. package/dist/esm/query/utils.js +0 -154
  181. package/dist/esm/query/utils.js.map +0 -1
  182. package/dist/esm/utils.d.ts +0 -3
  183. package/dist/esm/utils.js +0 -17
  184. package/dist/esm/utils.js.map +0 -1
  185. package/src/query/compiled-query.ts +0 -234
  186. package/src/query/evaluators.ts +0 -250
  187. package/src/query/extractors.ts +0 -214
  188. package/src/query/functions.ts +0 -297
  189. package/src/query/group-by.ts +0 -139
  190. package/src/query/joins.ts +0 -260
  191. package/src/query/order-by.ts +0 -264
  192. package/src/query/pipeline-compiler.ts +0 -149
  193. package/src/query/query-builder.ts +0 -902
  194. package/src/query/schema.ts +0 -268
  195. package/src/query/select.ts +0 -208
  196. package/src/query/types.ts +0 -418
  197. package/src/query/utils.ts +0 -245
  198. package/src/utils.ts +0 -15
@@ -0,0 +1,228 @@
1
+ import {
2
+ consolidate,
3
+ filter,
4
+ join as joinOperator,
5
+ map,
6
+ } from "@electric-sql/d2mini"
7
+ import { compileExpression } from "./evaluators.js"
8
+ import { compileQuery } from "./index.js"
9
+ import type { IStreamBuilder, JoinType } from "@electric-sql/d2mini"
10
+ import type { CollectionRef, JoinClause, QueryIR, QueryRef } from "../ir.js"
11
+ import type {
12
+ KeyedStream,
13
+ NamespacedAndKeyedStream,
14
+ NamespacedRow,
15
+ ResultStream,
16
+ } from "../../types.js"
17
+
18
+ /**
19
+ * Cache for compiled subqueries to avoid duplicate compilation
20
+ */
21
+ type QueryCache = WeakMap<QueryIR, ResultStream>
22
+
23
+ /**
24
+ * Processes all join clauses in a query
25
+ */
26
+ export function processJoins(
27
+ pipeline: NamespacedAndKeyedStream,
28
+ joinClauses: Array<JoinClause>,
29
+ tables: Record<string, KeyedStream>,
30
+ mainTableAlias: string,
31
+ allInputs: Record<string, KeyedStream>,
32
+ cache: QueryCache
33
+ ): NamespacedAndKeyedStream {
34
+ let resultPipeline = pipeline
35
+
36
+ for (const joinClause of joinClauses) {
37
+ resultPipeline = processJoin(
38
+ resultPipeline,
39
+ joinClause,
40
+ tables,
41
+ mainTableAlias,
42
+ allInputs,
43
+ cache
44
+ )
45
+ }
46
+
47
+ return resultPipeline
48
+ }
49
+
50
+ /**
51
+ * Processes a single join clause
52
+ */
53
+ function processJoin(
54
+ pipeline: NamespacedAndKeyedStream,
55
+ joinClause: JoinClause,
56
+ tables: Record<string, KeyedStream>,
57
+ mainTableAlias: string,
58
+ allInputs: Record<string, KeyedStream>,
59
+ cache: QueryCache
60
+ ): NamespacedAndKeyedStream {
61
+ // Get the joined table alias and input stream
62
+ const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(
63
+ joinClause.from,
64
+ allInputs,
65
+ cache
66
+ )
67
+
68
+ // Add the joined table to the tables map
69
+ tables[joinedTableAlias] = joinedInput
70
+
71
+ // Convert join type to D2 join type
72
+ const joinType: JoinType =
73
+ joinClause.type === `cross`
74
+ ? `inner`
75
+ : joinClause.type === `outer`
76
+ ? `full`
77
+ : (joinClause.type as JoinType)
78
+
79
+ // Pre-compile the join expressions
80
+ const compiledLeftExpr = compileExpression(joinClause.left)
81
+ const compiledRightExpr = compileExpression(joinClause.right)
82
+
83
+ // Prepare the main pipeline for joining
84
+ const mainPipeline = pipeline.pipe(
85
+ map(([currentKey, namespacedRow]) => {
86
+ // Extract the join key from the left side of the join condition
87
+ const leftKey = compiledLeftExpr(namespacedRow)
88
+
89
+ // Return [joinKey, [originalKey, namespacedRow]]
90
+ return [leftKey, [currentKey, namespacedRow]] as [
91
+ unknown,
92
+ [string, typeof namespacedRow],
93
+ ]
94
+ })
95
+ )
96
+
97
+ // Prepare the joined pipeline
98
+ const joinedPipeline = joinedInput.pipe(
99
+ map(([currentKey, row]) => {
100
+ // Wrap the row in a namespaced structure
101
+ const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }
102
+
103
+ // Extract the join key from the right side of the join condition
104
+ const rightKey = compiledRightExpr(namespacedRow)
105
+
106
+ // Return [joinKey, [originalKey, namespacedRow]]
107
+ return [rightKey, [currentKey, namespacedRow]] as [
108
+ unknown,
109
+ [string, typeof namespacedRow],
110
+ ]
111
+ })
112
+ )
113
+
114
+ // Apply the join operation
115
+ if (![`inner`, `left`, `right`, `full`].includes(joinType)) {
116
+ throw new Error(`Unsupported join type: ${joinClause.type}`)
117
+ }
118
+ return mainPipeline.pipe(
119
+ joinOperator(joinedPipeline, joinType),
120
+ consolidate(),
121
+ processJoinResults(joinClause.type)
122
+ )
123
+ }
124
+
125
+ /**
126
+ * Processes the join source (collection or sub-query)
127
+ */
128
+ function processJoinSource(
129
+ from: CollectionRef | QueryRef,
130
+ allInputs: Record<string, KeyedStream>,
131
+ cache: QueryCache
132
+ ): { alias: string; input: KeyedStream } {
133
+ switch (from.type) {
134
+ case `collectionRef`: {
135
+ const input = allInputs[from.collection.id]
136
+ if (!input) {
137
+ throw new Error(
138
+ `Input for collection "${from.collection.id}" not found in inputs map`
139
+ )
140
+ }
141
+ return { alias: from.alias, input }
142
+ }
143
+ case `queryRef`: {
144
+ // Recursively compile the sub-query with cache
145
+ const subQueryInput = compileQuery(from.query, allInputs, cache)
146
+
147
+ // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)
148
+ // We need to extract just the value for use in parent queries
149
+ const extractedInput = subQueryInput.pipe(
150
+ map((data: any) => {
151
+ const [key, [value, _orderByIndex]] = data
152
+ return [key, value] as [unknown, any]
153
+ })
154
+ )
155
+
156
+ return { alias: from.alias, input: extractedInput as KeyedStream }
157
+ }
158
+ default:
159
+ throw new Error(`Unsupported join source type: ${(from as any).type}`)
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Processes the results of a join operation
165
+ */
166
+ function processJoinResults(joinType: string) {
167
+ return function (
168
+ pipeline: IStreamBuilder<
169
+ [
170
+ key: string,
171
+ [
172
+ [string, NamespacedRow] | undefined,
173
+ [string, NamespacedRow] | undefined,
174
+ ],
175
+ ]
176
+ >
177
+ ): NamespacedAndKeyedStream {
178
+ return pipeline.pipe(
179
+ // Process the join result and handle nulls
180
+ filter((result) => {
181
+ const [_key, [main, joined]] = result
182
+ const mainNamespacedRow = main?.[1]
183
+ const joinedNamespacedRow = joined?.[1]
184
+
185
+ // Handle different join types
186
+ if (joinType === `inner`) {
187
+ return !!(mainNamespacedRow && joinedNamespacedRow)
188
+ }
189
+
190
+ if (joinType === `left`) {
191
+ return !!mainNamespacedRow
192
+ }
193
+
194
+ if (joinType === `right`) {
195
+ return !!joinedNamespacedRow
196
+ }
197
+
198
+ // For full joins, always include
199
+ return true
200
+ }),
201
+ map((result) => {
202
+ const [_key, [main, joined]] = result
203
+ const mainKey = main?.[0]
204
+ const mainNamespacedRow = main?.[1]
205
+ const joinedKey = joined?.[0]
206
+ const joinedNamespacedRow = joined?.[1]
207
+
208
+ // Merge the namespaced rows
209
+ const mergedNamespacedRow: NamespacedRow = {}
210
+
211
+ // Add main row data if it exists
212
+ if (mainNamespacedRow) {
213
+ Object.assign(mergedNamespacedRow, mainNamespacedRow)
214
+ }
215
+
216
+ // Add joined row data if it exists
217
+ if (joinedNamespacedRow) {
218
+ Object.assign(mergedNamespacedRow, joinedNamespacedRow)
219
+ }
220
+
221
+ // We create a composite key that combines the main and joined keys
222
+ const resultKey = `[${mainKey},${joinedKey}]`
223
+
224
+ return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]
225
+ })
226
+ )
227
+ }
228
+ }
@@ -0,0 +1,139 @@
1
+ import { orderByWithFractionalIndex } from "@electric-sql/d2mini"
2
+ import { compileExpression } from "./evaluators.js"
3
+ import type { OrderByClause } from "../ir.js"
4
+ import type { NamespacedAndKeyedStream, NamespacedRow } from "../../types.js"
5
+ import type { IStreamBuilder, KeyValue } from "@electric-sql/d2mini"
6
+
7
+ /**
8
+ * Processes the ORDER BY clause
9
+ * Works with the new structure that has both namespaced row data and __select_results
10
+ * Always uses fractional indexing and adds the index as __ordering_index to the result
11
+ */
12
+ export function processOrderBy(
13
+ pipeline: NamespacedAndKeyedStream,
14
+ orderByClause: Array<OrderByClause>,
15
+ limit?: number,
16
+ offset?: number
17
+ ): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {
18
+ // Pre-compile all order by expressions
19
+ const compiledOrderBy = orderByClause.map((clause) => ({
20
+ compiledExpression: compileExpression(clause.expression),
21
+ direction: clause.direction,
22
+ }))
23
+
24
+ // Create a value extractor function for the orderBy operator
25
+ const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {
26
+ // For ORDER BY expressions, we need to provide access to both:
27
+ // 1. The original namespaced row data (for direct table column references)
28
+ // 2. The __select_results (for SELECT alias references)
29
+
30
+ // Create a merged context for expression evaluation
31
+ const orderByContext = { ...row }
32
+
33
+ // If there are select results, merge them at the top level for alias access
34
+ if (row.__select_results) {
35
+ // Add select results as top-level properties for alias access
36
+ Object.assign(orderByContext, row.__select_results)
37
+ }
38
+
39
+ if (orderByClause.length > 1) {
40
+ // For multiple orderBy columns, create a composite key
41
+ return compiledOrderBy.map((compiled) =>
42
+ compiled.compiledExpression(orderByContext)
43
+ )
44
+ } else if (orderByClause.length === 1) {
45
+ // For a single orderBy column, use the value directly
46
+ const compiled = compiledOrderBy[0]!
47
+ return compiled.compiledExpression(orderByContext)
48
+ }
49
+
50
+ // Default case - no ordering
51
+ return null
52
+ }
53
+
54
+ // Create comparator functions
55
+ const ascComparator = (a: any, b: any): number => {
56
+ // Handle null/undefined
57
+ if (a == null && b == null) return 0
58
+ if (a == null) return -1
59
+ if (b == null) return 1
60
+
61
+ // if a and b are both strings, compare them based on locale
62
+ if (typeof a === `string` && typeof b === `string`) {
63
+ return a.localeCompare(b)
64
+ }
65
+
66
+ // if a and b are both arrays, compare them element by element
67
+ if (Array.isArray(a) && Array.isArray(b)) {
68
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
69
+ const result = ascComparator(a[i], b[i])
70
+ if (result !== 0) {
71
+ return result
72
+ }
73
+ }
74
+ // All elements are equal up to the minimum length
75
+ return a.length - b.length
76
+ }
77
+
78
+ // If both are dates, compare them
79
+ if (a instanceof Date && b instanceof Date) {
80
+ return a.getTime() - b.getTime()
81
+ }
82
+
83
+ // If at least one of the values is an object, convert to strings
84
+ const bothObjects = typeof a === `object` && typeof b === `object`
85
+ const notNull = a !== null && b !== null
86
+ if (bothObjects && notNull) {
87
+ return a.toString().localeCompare(b.toString())
88
+ }
89
+
90
+ if (a < b) return -1
91
+ if (a > b) return 1
92
+ return 0
93
+ }
94
+
95
+ const descComparator = (a: unknown, b: unknown): number => {
96
+ return ascComparator(b, a)
97
+ }
98
+
99
+ // Create a multi-property comparator that respects the order and direction of each property
100
+ const makeComparator = () => {
101
+ return (a: unknown, b: unknown) => {
102
+ // If we're comparing arrays (multiple properties), compare each property in order
103
+ if (orderByClause.length > 1) {
104
+ const arrayA = a as Array<unknown>
105
+ const arrayB = b as Array<unknown>
106
+ for (let i = 0; i < orderByClause.length; i++) {
107
+ const direction = orderByClause[i]!.direction
108
+ const compareFn =
109
+ direction === `desc` ? descComparator : ascComparator
110
+ const result = compareFn(arrayA[i], arrayB[i])
111
+ if (result !== 0) {
112
+ return result
113
+ }
114
+ }
115
+ return arrayA.length - arrayB.length
116
+ }
117
+
118
+ // Single property comparison
119
+ if (orderByClause.length === 1) {
120
+ const direction = orderByClause[0]!.direction
121
+ return direction === `desc` ? descComparator(a, b) : ascComparator(a, b)
122
+ }
123
+
124
+ return ascComparator(a, b)
125
+ }
126
+ }
127
+
128
+ const comparator = makeComparator()
129
+
130
+ // Use fractional indexing and return the tuple [value, index]
131
+ return pipeline.pipe(
132
+ orderByWithFractionalIndex(valueExtractor, {
133
+ limit,
134
+ offset,
135
+ comparator,
136
+ })
137
+ // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
138
+ )
139
+ }
@@ -0,0 +1,173 @@
1
+ import { map } from "@electric-sql/d2mini"
2
+ import { compileExpression } from "./evaluators.js"
3
+ import type { Aggregate, BasicExpression, Select } from "../ir.js"
4
+ import type {
5
+ KeyedStream,
6
+ NamespacedAndKeyedStream,
7
+ NamespacedRow,
8
+ } from "../../types.js"
9
+
10
+ /**
11
+ * Processes the SELECT clause and places results in __select_results
12
+ * while preserving the original namespaced row for ORDER BY access
13
+ */
14
+ export function processSelectToResults(
15
+ pipeline: NamespacedAndKeyedStream,
16
+ select: Select,
17
+ _allInputs: Record<string, KeyedStream>
18
+ ): NamespacedAndKeyedStream {
19
+ // Pre-compile all select expressions
20
+ const compiledSelect: Array<{
21
+ alias: string
22
+ compiledExpression: (row: NamespacedRow) => any
23
+ }> = []
24
+ const spreadAliases: Array<string> = []
25
+
26
+ for (const [alias, expression] of Object.entries(select)) {
27
+ if (alias.startsWith(`__SPREAD_SENTINEL__`)) {
28
+ // Extract the table alias from the sentinel key
29
+ const tableAlias = alias.replace(`__SPREAD_SENTINEL__`, ``)
30
+ spreadAliases.push(tableAlias)
31
+ } else {
32
+ if (isAggregateExpression(expression)) {
33
+ // For aggregates, we'll store the expression info for GROUP BY processing
34
+ // but still compile a placeholder that will be replaced later
35
+ compiledSelect.push({
36
+ alias,
37
+ compiledExpression: () => null, // Placeholder - will be handled by GROUP BY
38
+ })
39
+ } else {
40
+ compiledSelect.push({
41
+ alias,
42
+ compiledExpression: compileExpression(expression as BasicExpression),
43
+ })
44
+ }
45
+ }
46
+ }
47
+
48
+ return pipeline.pipe(
49
+ map(([key, namespacedRow]) => {
50
+ const selectResults: Record<string, any> = {}
51
+
52
+ // First pass: spread table data for any spread sentinels
53
+ for (const tableAlias of spreadAliases) {
54
+ const tableData = namespacedRow[tableAlias]
55
+ if (tableData && typeof tableData === `object`) {
56
+ // Spread the table data into the result, but don't overwrite explicit fields
57
+ for (const [fieldName, fieldValue] of Object.entries(tableData)) {
58
+ if (!(fieldName in selectResults)) {
59
+ selectResults[fieldName] = fieldValue
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ // Second pass: evaluate all compiled select expressions (non-aggregates)
66
+ for (const { alias, compiledExpression } of compiledSelect) {
67
+ selectResults[alias] = compiledExpression(namespacedRow)
68
+ }
69
+
70
+ // Return the namespaced row with __select_results added
71
+ return [
72
+ key,
73
+ {
74
+ ...namespacedRow,
75
+ __select_results: selectResults,
76
+ },
77
+ ] as [
78
+ string,
79
+ typeof namespacedRow & { __select_results: typeof selectResults },
80
+ ]
81
+ })
82
+ )
83
+ }
84
+
85
+ /**
86
+ * Processes the SELECT clause (legacy function - kept for compatibility)
87
+ */
88
+ export function processSelect(
89
+ pipeline: NamespacedAndKeyedStream,
90
+ select: Select,
91
+ _allInputs: Record<string, KeyedStream>
92
+ ): KeyedStream {
93
+ // Pre-compile all select expressions
94
+ const compiledSelect: Array<{
95
+ alias: string
96
+ compiledExpression: (row: NamespacedRow) => any
97
+ }> = []
98
+ const spreadAliases: Array<string> = []
99
+
100
+ for (const [alias, expression] of Object.entries(select)) {
101
+ if (alias.startsWith(`__SPREAD_SENTINEL__`)) {
102
+ // Extract the table alias from the sentinel key
103
+ const tableAlias = alias.replace(`__SPREAD_SENTINEL__`, ``)
104
+ spreadAliases.push(tableAlias)
105
+ } else {
106
+ if (isAggregateExpression(expression)) {
107
+ // Aggregates should be handled by GROUP BY processing, not here
108
+ throw new Error(
109
+ `Aggregate expressions in SELECT clause should be handled by GROUP BY processing`
110
+ )
111
+ }
112
+ compiledSelect.push({
113
+ alias,
114
+ compiledExpression: compileExpression(expression as BasicExpression),
115
+ })
116
+ }
117
+ }
118
+
119
+ return pipeline.pipe(
120
+ map(([key, namespacedRow]) => {
121
+ const result: Record<string, any> = {}
122
+
123
+ // First pass: spread table data for any spread sentinels
124
+ for (const tableAlias of spreadAliases) {
125
+ const tableData = namespacedRow[tableAlias]
126
+ if (tableData && typeof tableData === `object`) {
127
+ // Spread the table data into the result, but don't overwrite explicit fields
128
+ for (const [fieldName, fieldValue] of Object.entries(tableData)) {
129
+ if (!(fieldName in result)) {
130
+ result[fieldName] = fieldValue
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ // Second pass: evaluate all compiled select expressions
137
+ for (const { alias, compiledExpression } of compiledSelect) {
138
+ result[alias] = compiledExpression(namespacedRow)
139
+ }
140
+
141
+ return [key, result] as [string, typeof result]
142
+ })
143
+ )
144
+ }
145
+
146
+ /**
147
+ * Helper function to check if an expression is an aggregate
148
+ */
149
+ function isAggregateExpression(
150
+ expr: BasicExpression | Aggregate
151
+ ): expr is Aggregate {
152
+ return expr.type === `agg`
153
+ }
154
+
155
+ /**
156
+ * Processes a single argument in a function context
157
+ */
158
+ export function processArgument(
159
+ arg: BasicExpression | Aggregate,
160
+ namespacedRow: NamespacedRow
161
+ ): any {
162
+ if (isAggregateExpression(arg)) {
163
+ throw new Error(
164
+ `Aggregate expressions are not supported in this context. Use GROUP BY clause for aggregates.`
165
+ )
166
+ }
167
+
168
+ // Pre-compile the expression and evaluate immediately
169
+ const compiledExpression = compileExpression(arg)
170
+ const value = compiledExpression(namespacedRow)
171
+
172
+ return value
173
+ }
@@ -1,5 +1,64 @@
1
- export * from "./query-builder.js"
2
- export * from "./compiled-query.js"
3
- export * from "./pipeline-compiler.js"
4
- export * from "./schema.js"
5
- export * from "./types.js"
1
+ // Main exports for the new query builder system
2
+
3
+ // Query builder exports
4
+ export {
5
+ BaseQueryBuilder,
6
+ Query,
7
+ type InitialQueryBuilder,
8
+ type QueryBuilder,
9
+ type Context,
10
+ type Source,
11
+ type GetResult,
12
+ } from "./builder/index.js"
13
+
14
+ // Expression functions exports
15
+ export {
16
+ // Operators
17
+ eq,
18
+ gt,
19
+ gte,
20
+ lt,
21
+ lte,
22
+ and,
23
+ or,
24
+ not,
25
+ inArray,
26
+ like,
27
+ ilike,
28
+ // Functions
29
+ upper,
30
+ lower,
31
+ length,
32
+ concat,
33
+ coalesce,
34
+ add,
35
+ // Aggregates
36
+ count,
37
+ avg,
38
+ sum,
39
+ min,
40
+ max,
41
+ } from "./builder/functions.js"
42
+
43
+ // Ref proxy utilities
44
+ export { val, toExpression, isRefProxy } from "./builder/ref-proxy.js"
45
+
46
+ // IR types (for advanced usage)
47
+ export type {
48
+ QueryIR,
49
+ BasicExpression as Expression,
50
+ Aggregate,
51
+ CollectionRef,
52
+ QueryRef,
53
+ JoinClause,
54
+ } from "./ir.js"
55
+
56
+ // Compiler
57
+ export { compileQuery } from "./compiler/index.js"
58
+
59
+ // Live query collection utilities
60
+ export {
61
+ createLiveQueryCollection,
62
+ liveQueryCollectionOptions,
63
+ type LiveQueryCollectionConfig,
64
+ } from "./live-query-collection.js"