@tanstack/db 0.0.14 → 0.0.16

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 (197) 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 +31 -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 +92 -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 +81 -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 +5 -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 +30 -12
  51. package/dist/esm/query/builder/functions.d.ts +38 -0
  52. package/dist/esm/query/builder/functions.js +107 -0
  53. package/dist/esm/query/builder/functions.js.map +1 -0
  54. package/dist/esm/query/builder/index.d.ts +324 -0
  55. package/dist/esm/query/builder/index.js +499 -0
  56. package/dist/esm/query/builder/index.js.map +1 -0
  57. package/dist/esm/query/builder/ref-proxy.d.ts +28 -0
  58. package/dist/esm/query/builder/ref-proxy.js +92 -0
  59. package/dist/esm/query/builder/ref-proxy.js.map +1 -0
  60. package/dist/esm/query/builder/types.d.ts +81 -0
  61. package/dist/esm/query/compiler/evaluators.d.ts +11 -0
  62. package/dist/esm/query/compiler/evaluators.js +261 -0
  63. package/dist/esm/query/compiler/evaluators.js.map +1 -0
  64. package/dist/esm/query/compiler/group-by.d.ts +7 -0
  65. package/dist/esm/query/compiler/group-by.js +271 -0
  66. package/dist/esm/query/compiler/group-by.js.map +1 -0
  67. package/dist/esm/query/compiler/index.d.ts +15 -0
  68. package/dist/esm/query/compiler/index.js +181 -0
  69. package/dist/esm/query/compiler/index.js.map +1 -0
  70. package/dist/esm/query/compiler/joins.d.ts +11 -0
  71. package/dist/esm/query/compiler/joins.js +116 -0
  72. package/dist/esm/query/compiler/joins.js.map +1 -0
  73. package/dist/esm/query/compiler/order-by.d.ts +9 -0
  74. package/dist/esm/query/compiler/order-by.js +89 -0
  75. package/dist/esm/query/compiler/order-by.js.map +1 -0
  76. package/dist/esm/query/compiler/select.d.ts +15 -0
  77. package/dist/esm/query/compiler/select.js +57 -0
  78. package/dist/esm/query/compiler/select.js.map +1 -0
  79. package/dist/esm/query/index.d.ts +5 -5
  80. package/dist/esm/query/ir.d.ts +81 -0
  81. package/dist/esm/query/ir.js +57 -0
  82. package/dist/esm/query/ir.js.map +1 -0
  83. package/dist/esm/query/live-query-collection.d.ts +124 -0
  84. package/dist/esm/query/live-query-collection.js +224 -0
  85. package/dist/esm/query/live-query-collection.js.map +1 -0
  86. package/dist/esm/transactions.d.ts +10 -1
  87. package/dist/esm/transactions.js +20 -13
  88. package/dist/esm/transactions.js.map +1 -1
  89. package/dist/esm/types.d.ts +13 -0
  90. package/package.json +3 -4
  91. package/src/collection.ts +152 -129
  92. package/src/index.ts +0 -1
  93. package/src/query/builder/functions.ts +267 -0
  94. package/src/query/builder/index.ts +648 -0
  95. package/src/query/builder/ref-proxy.ts +156 -0
  96. package/src/query/builder/types.ts +282 -0
  97. package/src/query/compiler/evaluators.ts +315 -0
  98. package/src/query/compiler/group-by.ts +428 -0
  99. package/src/query/compiler/index.ts +276 -0
  100. package/src/query/compiler/joins.ts +228 -0
  101. package/src/query/compiler/order-by.ts +139 -0
  102. package/src/query/compiler/select.ts +173 -0
  103. package/src/query/index.ts +54 -5
  104. package/src/query/ir.ts +128 -0
  105. package/src/query/live-query-collection.ts +512 -0
  106. package/src/transactions.ts +27 -16
  107. package/src/types.ts +15 -0
  108. package/dist/cjs/query/compiled-query.cjs +0 -160
  109. package/dist/cjs/query/compiled-query.cjs.map +0 -1
  110. package/dist/cjs/query/compiled-query.d.cts +0 -20
  111. package/dist/cjs/query/evaluators.cjs +0 -161
  112. package/dist/cjs/query/evaluators.cjs.map +0 -1
  113. package/dist/cjs/query/evaluators.d.cts +0 -14
  114. package/dist/cjs/query/extractors.cjs +0 -122
  115. package/dist/cjs/query/extractors.cjs.map +0 -1
  116. package/dist/cjs/query/extractors.d.cts +0 -22
  117. package/dist/cjs/query/functions.cjs +0 -152
  118. package/dist/cjs/query/functions.cjs.map +0 -1
  119. package/dist/cjs/query/functions.d.cts +0 -21
  120. package/dist/cjs/query/group-by.cjs +0 -88
  121. package/dist/cjs/query/group-by.cjs.map +0 -1
  122. package/dist/cjs/query/group-by.d.cts +0 -40
  123. package/dist/cjs/query/joins.cjs +0 -141
  124. package/dist/cjs/query/joins.cjs.map +0 -1
  125. package/dist/cjs/query/joins.d.cts +0 -14
  126. package/dist/cjs/query/order-by.cjs +0 -185
  127. package/dist/cjs/query/order-by.cjs.map +0 -1
  128. package/dist/cjs/query/order-by.d.cts +0 -3
  129. package/dist/cjs/query/pipeline-compiler.cjs +0 -89
  130. package/dist/cjs/query/pipeline-compiler.cjs.map +0 -1
  131. package/dist/cjs/query/pipeline-compiler.d.cts +0 -10
  132. package/dist/cjs/query/query-builder.cjs +0 -307
  133. package/dist/cjs/query/query-builder.cjs.map +0 -1
  134. package/dist/cjs/query/query-builder.d.cts +0 -225
  135. package/dist/cjs/query/schema.d.cts +0 -100
  136. package/dist/cjs/query/select.cjs +0 -130
  137. package/dist/cjs/query/select.cjs.map +0 -1
  138. package/dist/cjs/query/select.d.cts +0 -3
  139. package/dist/cjs/query/types.d.cts +0 -189
  140. package/dist/cjs/query/utils.cjs +0 -154
  141. package/dist/cjs/query/utils.cjs.map +0 -1
  142. package/dist/cjs/query/utils.d.cts +0 -37
  143. package/dist/cjs/utils.cjs +0 -17
  144. package/dist/cjs/utils.cjs.map +0 -1
  145. package/dist/cjs/utils.d.cts +0 -3
  146. package/dist/esm/query/compiled-query.d.ts +0 -20
  147. package/dist/esm/query/compiled-query.js +0 -160
  148. package/dist/esm/query/compiled-query.js.map +0 -1
  149. package/dist/esm/query/evaluators.d.ts +0 -14
  150. package/dist/esm/query/evaluators.js +0 -161
  151. package/dist/esm/query/evaluators.js.map +0 -1
  152. package/dist/esm/query/extractors.d.ts +0 -22
  153. package/dist/esm/query/extractors.js +0 -122
  154. package/dist/esm/query/extractors.js.map +0 -1
  155. package/dist/esm/query/functions.d.ts +0 -21
  156. package/dist/esm/query/functions.js +0 -152
  157. package/dist/esm/query/functions.js.map +0 -1
  158. package/dist/esm/query/group-by.d.ts +0 -40
  159. package/dist/esm/query/group-by.js +0 -88
  160. package/dist/esm/query/group-by.js.map +0 -1
  161. package/dist/esm/query/joins.d.ts +0 -14
  162. package/dist/esm/query/joins.js +0 -141
  163. package/dist/esm/query/joins.js.map +0 -1
  164. package/dist/esm/query/order-by.d.ts +0 -3
  165. package/dist/esm/query/order-by.js +0 -185
  166. package/dist/esm/query/order-by.js.map +0 -1
  167. package/dist/esm/query/pipeline-compiler.d.ts +0 -10
  168. package/dist/esm/query/pipeline-compiler.js +0 -89
  169. package/dist/esm/query/pipeline-compiler.js.map +0 -1
  170. package/dist/esm/query/query-builder.d.ts +0 -225
  171. package/dist/esm/query/query-builder.js +0 -307
  172. package/dist/esm/query/query-builder.js.map +0 -1
  173. package/dist/esm/query/schema.d.ts +0 -100
  174. package/dist/esm/query/select.d.ts +0 -3
  175. package/dist/esm/query/select.js +0 -130
  176. package/dist/esm/query/select.js.map +0 -1
  177. package/dist/esm/query/types.d.ts +0 -189
  178. package/dist/esm/query/utils.d.ts +0 -37
  179. package/dist/esm/query/utils.js +0 -154
  180. package/dist/esm/query/utils.js.map +0 -1
  181. package/dist/esm/utils.d.ts +0 -3
  182. package/dist/esm/utils.js +0 -17
  183. package/dist/esm/utils.js.map +0 -1
  184. package/src/query/compiled-query.ts +0 -234
  185. package/src/query/evaluators.ts +0 -250
  186. package/src/query/extractors.ts +0 -214
  187. package/src/query/functions.ts +0 -297
  188. package/src/query/group-by.ts +0 -139
  189. package/src/query/joins.ts +0 -260
  190. package/src/query/order-by.ts +0 -264
  191. package/src/query/pipeline-compiler.ts +0 -149
  192. package/src/query/query-builder.ts +0 -902
  193. package/src/query/schema.ts +0 -268
  194. package/src/query/select.ts +0 -208
  195. package/src/query/types.ts +0 -418
  196. package/src/query/utils.ts +0 -245
  197. package/src/utils.ts +0 -15
@@ -0,0 +1,499 @@
1
+ import { CollectionImpl } from "../../collection.js";
2
+ import { CollectionRef, QueryRef } from "../ir.js";
3
+ import { createRefProxy, toExpression, isRefProxy } from "./ref-proxy.js";
4
+ class BaseQueryBuilder {
5
+ constructor(query = {}) {
6
+ this.query = {};
7
+ this.query = { ...query };
8
+ }
9
+ /**
10
+ * Creates a CollectionRef or QueryRef from a source object
11
+ * @param source - An object with a single key-value pair
12
+ * @param context - Context string for error messages (e.g., "from clause", "join clause")
13
+ * @returns A tuple of [alias, ref] where alias is the source key and ref is the created reference
14
+ */
15
+ _createRefForSource(source, context) {
16
+ if (Object.keys(source).length !== 1) {
17
+ throw new Error(`Only one source is allowed in the ${context}`);
18
+ }
19
+ const alias = Object.keys(source)[0];
20
+ const sourceValue = source[alias];
21
+ let ref;
22
+ if (sourceValue instanceof CollectionImpl) {
23
+ ref = new CollectionRef(sourceValue, alias);
24
+ } else if (sourceValue instanceof BaseQueryBuilder) {
25
+ const subQuery = sourceValue._getQuery();
26
+ if (!subQuery.from) {
27
+ throw new Error(
28
+ `A sub query passed to a ${context} must have a from clause itself`
29
+ );
30
+ }
31
+ ref = new QueryRef(subQuery, alias);
32
+ } else {
33
+ throw new Error(`Invalid source`);
34
+ }
35
+ return [alias, ref];
36
+ }
37
+ /**
38
+ * Specify the source table or subquery for the query
39
+ *
40
+ * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery
41
+ * @returns A QueryBuilder with the specified source
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * // Query from a collection
46
+ * query.from({ users: usersCollection })
47
+ *
48
+ * // Query from a subquery
49
+ * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)
50
+ * query.from({ activeUsers })
51
+ * ```
52
+ */
53
+ from(source) {
54
+ const [, from] = this._createRefForSource(source, `from clause`);
55
+ return new BaseQueryBuilder({
56
+ ...this.query,
57
+ from
58
+ });
59
+ }
60
+ /**
61
+ * Join another table or subquery to the current query
62
+ *
63
+ * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery
64
+ * @param onCallback - A function that receives table references and returns the join condition
65
+ * @param type - The type of join: 'inner', 'left', 'right', or 'full' (defaults to 'left')
66
+ * @returns A QueryBuilder with the joined table available
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * // Left join users with posts
71
+ * query
72
+ * .from({ users: usersCollection })
73
+ * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))
74
+ *
75
+ * // Inner join with explicit type
76
+ * query
77
+ * .from({ u: usersCollection })
78
+ * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId), 'inner')
79
+ * ```
80
+ *
81
+ * // Join with a subquery
82
+ * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)
83
+ * query
84
+ * .from({ activeUsers })
85
+ * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId))
86
+ */
87
+ join(source, onCallback, type = `left`) {
88
+ const [alias, from] = this._createRefForSource(source, `join clause`);
89
+ const currentAliases = this._getCurrentAliases();
90
+ const newAliases = [...currentAliases, alias];
91
+ const refProxy = createRefProxy(newAliases);
92
+ const onExpression = onCallback(refProxy);
93
+ let left;
94
+ let right;
95
+ if (onExpression.type === `func` && onExpression.name === `eq` && onExpression.args.length === 2) {
96
+ left = onExpression.args[0];
97
+ right = onExpression.args[1];
98
+ } else {
99
+ throw new Error(`Join condition must be an equality expression`);
100
+ }
101
+ const joinClause = {
102
+ from,
103
+ type,
104
+ left,
105
+ right
106
+ };
107
+ const existingJoins = this.query.join || [];
108
+ return new BaseQueryBuilder({
109
+ ...this.query,
110
+ join: [...existingJoins, joinClause]
111
+ });
112
+ }
113
+ /**
114
+ * Filter rows based on a condition
115
+ *
116
+ * @param callback - A function that receives table references and returns an expression
117
+ * @returns A QueryBuilder with the where condition applied
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * // Simple condition
122
+ * query
123
+ * .from({ users: usersCollection })
124
+ * .where(({users}) => gt(users.age, 18))
125
+ *
126
+ * // Multiple conditions
127
+ * query
128
+ * .from({ users: usersCollection })
129
+ * .where(({users}) => and(
130
+ * gt(users.age, 18),
131
+ * eq(users.active, true)
132
+ * ))
133
+ *
134
+ * // Multiple where calls are ANDed together
135
+ * query
136
+ * .from({ users: usersCollection })
137
+ * .where(({users}) => gt(users.age, 18))
138
+ * .where(({users}) => eq(users.active, true))
139
+ * ```
140
+ */
141
+ where(callback) {
142
+ const aliases = this._getCurrentAliases();
143
+ const refProxy = createRefProxy(aliases);
144
+ const expression = callback(refProxy);
145
+ const existingWhere = this.query.where || [];
146
+ return new BaseQueryBuilder({
147
+ ...this.query,
148
+ where: [...existingWhere, expression]
149
+ });
150
+ }
151
+ /**
152
+ * Filter grouped rows based on aggregate conditions
153
+ *
154
+ * @param callback - A function that receives table references and returns an expression
155
+ * @returns A QueryBuilder with the having condition applied
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * // Filter groups by count
160
+ * query
161
+ * .from({ posts: postsCollection })
162
+ * .groupBy(({posts}) => posts.userId)
163
+ * .having(({posts}) => gt(count(posts.id), 5))
164
+ *
165
+ * // Filter by average
166
+ * query
167
+ * .from({ orders: ordersCollection })
168
+ * .groupBy(({orders}) => orders.customerId)
169
+ * .having(({orders}) => gt(avg(orders.total), 100))
170
+ *
171
+ * // Multiple having calls are ANDed together
172
+ * query
173
+ * .from({ orders: ordersCollection })
174
+ * .groupBy(({orders}) => orders.customerId)
175
+ * .having(({orders}) => gt(count(orders.id), 5))
176
+ * .having(({orders}) => gt(avg(orders.total), 100))
177
+ * ```
178
+ */
179
+ having(callback) {
180
+ const aliases = this._getCurrentAliases();
181
+ const refProxy = createRefProxy(aliases);
182
+ const expression = callback(refProxy);
183
+ const existingHaving = this.query.having || [];
184
+ return new BaseQueryBuilder({
185
+ ...this.query,
186
+ having: [...existingHaving, expression]
187
+ });
188
+ }
189
+ /**
190
+ * Select specific columns or computed values from the query
191
+ *
192
+ * @param callback - A function that receives table references and returns an object with selected fields or expressions
193
+ * @returns A QueryBuilder that returns only the selected fields
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * // Select specific columns
198
+ * query
199
+ * .from({ users: usersCollection })
200
+ * .select(({users}) => ({
201
+ * name: users.name,
202
+ * email: users.email
203
+ * }))
204
+ *
205
+ * // Select with computed values
206
+ * query
207
+ * .from({ users: usersCollection })
208
+ * .select(({users}) => ({
209
+ * fullName: concat(users.firstName, ' ', users.lastName),
210
+ * ageInMonths: mul(users.age, 12)
211
+ * }))
212
+ *
213
+ * // Select with aggregates (requires GROUP BY)
214
+ * query
215
+ * .from({ posts: postsCollection })
216
+ * .groupBy(({posts}) => posts.userId)
217
+ * .select(({posts, count}) => ({
218
+ * userId: posts.userId,
219
+ * postCount: count(posts.id)
220
+ * }))
221
+ * ```
222
+ */
223
+ select(callback) {
224
+ const aliases = this._getCurrentAliases();
225
+ const refProxy = createRefProxy(aliases);
226
+ const selectObject = callback(refProxy);
227
+ const spreadSentinels = refProxy.__spreadSentinels;
228
+ const select = {};
229
+ for (const spreadAlias of spreadSentinels) {
230
+ const sentinelKey = `__SPREAD_SENTINEL__${spreadAlias}`;
231
+ select[sentinelKey] = toExpression(spreadAlias);
232
+ }
233
+ for (const [key, value] of Object.entries(selectObject)) {
234
+ if (isRefProxy(value)) {
235
+ select[key] = toExpression(value);
236
+ } else if (typeof value === `object` && `type` in value && (value.type === `agg` || value.type === `func`)) {
237
+ select[key] = value;
238
+ } else {
239
+ select[key] = toExpression(value);
240
+ }
241
+ }
242
+ return new BaseQueryBuilder({
243
+ ...this.query,
244
+ select,
245
+ fnSelect: void 0
246
+ // remove the fnSelect clause if it exists
247
+ });
248
+ }
249
+ /**
250
+ * Sort the query results by one or more columns
251
+ *
252
+ * @param callback - A function that receives table references and returns the field to sort by
253
+ * @param direction - Sort direction: 'asc' for ascending, 'desc' for descending (defaults to 'asc')
254
+ * @returns A QueryBuilder with the ordering applied
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * // Sort by a single column
259
+ * query
260
+ * .from({ users: usersCollection })
261
+ * .orderBy(({users}) => users.name)
262
+ *
263
+ * // Sort descending
264
+ * query
265
+ * .from({ users: usersCollection })
266
+ * .orderBy(({users}) => users.createdAt, 'desc')
267
+ *
268
+ * // Multiple sorts (chain orderBy calls)
269
+ * query
270
+ * .from({ users: usersCollection })
271
+ * .orderBy(({users}) => users.lastName)
272
+ * .orderBy(({users}) => users.firstName)
273
+ * ```
274
+ */
275
+ orderBy(callback, direction = `asc`) {
276
+ const aliases = this._getCurrentAliases();
277
+ const refProxy = createRefProxy(aliases);
278
+ const result = callback(refProxy);
279
+ const orderByClause = {
280
+ expression: toExpression(result),
281
+ direction
282
+ };
283
+ const existingOrderBy = this.query.orderBy || [];
284
+ return new BaseQueryBuilder({
285
+ ...this.query,
286
+ orderBy: [...existingOrderBy, orderByClause]
287
+ });
288
+ }
289
+ /**
290
+ * Group rows by one or more columns for aggregation
291
+ *
292
+ * @param callback - A function that receives table references and returns the field(s) to group by
293
+ * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING)
294
+ *
295
+ * @example
296
+ * ```ts
297
+ * // Group by a single column
298
+ * query
299
+ * .from({ posts: postsCollection })
300
+ * .groupBy(({posts}) => posts.userId)
301
+ * .select(({posts, count}) => ({
302
+ * userId: posts.userId,
303
+ * postCount: count()
304
+ * }))
305
+ *
306
+ * // Group by multiple columns
307
+ * query
308
+ * .from({ sales: salesCollection })
309
+ * .groupBy(({sales}) => [sales.region, sales.category])
310
+ * .select(({sales, sum}) => ({
311
+ * region: sales.region,
312
+ * category: sales.category,
313
+ * totalSales: sum(sales.amount)
314
+ * }))
315
+ * ```
316
+ */
317
+ groupBy(callback) {
318
+ const aliases = this._getCurrentAliases();
319
+ const refProxy = createRefProxy(aliases);
320
+ const result = callback(refProxy);
321
+ const newExpressions = Array.isArray(result) ? result.map((r) => toExpression(r)) : [toExpression(result)];
322
+ return new BaseQueryBuilder({
323
+ ...this.query,
324
+ groupBy: newExpressions
325
+ });
326
+ }
327
+ /**
328
+ * Limit the number of rows returned by the query
329
+ * `orderBy` is required for `limit`
330
+ *
331
+ * @param count - Maximum number of rows to return
332
+ * @returns A QueryBuilder with the limit applied
333
+ *
334
+ * @example
335
+ * ```ts
336
+ * // Get top 5 posts by likes
337
+ * query
338
+ * .from({ posts: postsCollection })
339
+ * .orderBy(({posts}) => posts.likes, 'desc')
340
+ * .limit(5)
341
+ * ```
342
+ */
343
+ limit(count) {
344
+ return new BaseQueryBuilder({
345
+ ...this.query,
346
+ limit: count
347
+ });
348
+ }
349
+ /**
350
+ * Skip a number of rows before returning results
351
+ * `orderBy` is required for `offset`
352
+ *
353
+ * @param count - Number of rows to skip
354
+ * @returns A QueryBuilder with the offset applied
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * // Get second page of results
359
+ * query
360
+ * .from({ posts: postsCollection })
361
+ * .orderBy(({posts}) => posts.createdAt, 'desc')
362
+ * .offset(page * pageSize)
363
+ * .limit(pageSize)
364
+ * ```
365
+ */
366
+ offset(count) {
367
+ return new BaseQueryBuilder({
368
+ ...this.query,
369
+ offset: count
370
+ });
371
+ }
372
+ // Helper methods
373
+ _getCurrentAliases() {
374
+ const aliases = [];
375
+ if (this.query.from) {
376
+ aliases.push(this.query.from.alias);
377
+ }
378
+ if (this.query.join) {
379
+ for (const join of this.query.join) {
380
+ aliases.push(join.from.alias);
381
+ }
382
+ }
383
+ return aliases;
384
+ }
385
+ /**
386
+ * Functional variants of the query builder
387
+ * These are imperative function that are called for ery row.
388
+ * Warning: that these cannot be optimized by the query compiler, and may prevent
389
+ * some type of optimizations being possible.
390
+ * @example
391
+ * ```ts
392
+ * q.fn.select((row) => ({
393
+ * name: row.user.name.toUpperCase(),
394
+ * age: row.user.age + 1,
395
+ * }))
396
+ * ```
397
+ */
398
+ get fn() {
399
+ const builder = this;
400
+ return {
401
+ /**
402
+ * Select fields using a function that operates on each row
403
+ * Warning: This cannot be optimized by the query compiler
404
+ *
405
+ * @param callback - A function that receives a row and returns the selected value
406
+ * @returns A QueryBuilder with functional selection applied
407
+ *
408
+ * @example
409
+ * ```ts
410
+ * // Functional select (not optimized)
411
+ * query
412
+ * .from({ users: usersCollection })
413
+ * .fn.select(row => ({
414
+ * name: row.users.name.toUpperCase(),
415
+ * age: row.users.age + 1,
416
+ * }))
417
+ * ```
418
+ */
419
+ select(callback) {
420
+ return new BaseQueryBuilder({
421
+ ...builder.query,
422
+ select: void 0,
423
+ // remove the select clause if it exists
424
+ fnSelect: callback
425
+ });
426
+ },
427
+ /**
428
+ * Filter rows using a function that operates on each row
429
+ * Warning: This cannot be optimized by the query compiler
430
+ *
431
+ * @param callback - A function that receives a row and returns a boolean
432
+ * @returns A QueryBuilder with functional filtering applied
433
+ *
434
+ * @example
435
+ * ```ts
436
+ * // Functional where (not optimized)
437
+ * query
438
+ * .from({ users: usersCollection })
439
+ * .fn.where(row => row.users.name.startsWith('A'))
440
+ * ```
441
+ */
442
+ where(callback) {
443
+ return new BaseQueryBuilder({
444
+ ...builder.query,
445
+ fnWhere: [
446
+ ...builder.query.fnWhere || [],
447
+ callback
448
+ ]
449
+ });
450
+ },
451
+ /**
452
+ * Filter grouped rows using a function that operates on each aggregated row
453
+ * Warning: This cannot be optimized by the query compiler
454
+ *
455
+ * @param callback - A function that receives an aggregated row and returns a boolean
456
+ * @returns A QueryBuilder with functional having filter applied
457
+ *
458
+ * @example
459
+ * ```ts
460
+ * // Functional having (not optimized)
461
+ * query
462
+ * .from({ posts: postsCollection })
463
+ * .groupBy(({posts}) => posts.userId)
464
+ * .fn.having(row => row.count > 5)
465
+ * ```
466
+ */
467
+ having(callback) {
468
+ return new BaseQueryBuilder({
469
+ ...builder.query,
470
+ fnHaving: [
471
+ ...builder.query.fnHaving || [],
472
+ callback
473
+ ]
474
+ });
475
+ }
476
+ };
477
+ }
478
+ _getQuery() {
479
+ if (!this.query.from) {
480
+ throw new Error(`Query must have a from clause`);
481
+ }
482
+ return this.query;
483
+ }
484
+ }
485
+ function buildQuery(fn) {
486
+ const result = fn(new BaseQueryBuilder());
487
+ return getQueryIR(result);
488
+ }
489
+ function getQueryIR(builder) {
490
+ return builder._getQuery();
491
+ }
492
+ const Query = BaseQueryBuilder;
493
+ export {
494
+ BaseQueryBuilder,
495
+ Query,
496
+ buildQuery,
497
+ getQueryIR
498
+ };
499
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../../src/query/builder/index.ts"],"sourcesContent":["import { CollectionImpl } from \"../../collection.js\"\nimport { CollectionRef, QueryRef } from \"../ir.js\"\nimport { createRefProxy, isRefProxy, toExpression } from \"./ref-proxy.js\"\nimport type { NamespacedRow } from \"../../types.js\"\nimport type {\n Aggregate,\n BasicExpression,\n JoinClause,\n OrderBy,\n OrderByClause,\n OrderByDirection,\n QueryIR,\n} from \"../ir.js\"\nimport type {\n Context,\n GroupByCallback,\n JoinOnCallback,\n MergeContext,\n MergeContextWithJoinType,\n OrderByCallback,\n RefProxyForContext,\n ResultTypeFromSelect,\n SchemaFromSource,\n SelectObject,\n Source,\n WhereCallback,\n WithResult,\n} from \"./types.js\"\n\nexport class BaseQueryBuilder<TContext extends Context = Context> {\n private readonly query: Partial<QueryIR> = {}\n\n constructor(query: Partial<QueryIR> = {}) {\n this.query = { ...query }\n }\n\n /**\n * Creates a CollectionRef or QueryRef from a source object\n * @param source - An object with a single key-value pair\n * @param context - Context string for error messages (e.g., \"from clause\", \"join clause\")\n * @returns A tuple of [alias, ref] where alias is the source key and ref is the created reference\n */\n private _createRefForSource<TSource extends Source>(\n source: TSource,\n context: string\n ): [string, CollectionRef | QueryRef] {\n if (Object.keys(source).length !== 1) {\n throw new Error(`Only one source is allowed in the ${context}`)\n }\n\n const alias = Object.keys(source)[0]!\n const sourceValue = source[alias]\n\n let ref: CollectionRef | QueryRef\n\n if (sourceValue instanceof CollectionImpl) {\n ref = new CollectionRef(sourceValue, alias)\n } else if (sourceValue instanceof BaseQueryBuilder) {\n const subQuery = sourceValue._getQuery()\n if (!(subQuery as Partial<QueryIR>).from) {\n throw new Error(\n `A sub query passed to a ${context} must have a from clause itself`\n )\n }\n ref = new QueryRef(subQuery, alias)\n } else {\n throw new Error(`Invalid source`)\n }\n\n return [alias, ref]\n }\n\n /**\n * Specify the source table or subquery for the query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @returns A QueryBuilder with the specified source\n *\n * @example\n * ```ts\n * // Query from a collection\n * query.from({ users: usersCollection })\n *\n * // Query from a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query.from({ activeUsers })\n * ```\n */\n from<TSource extends Source>(\n source: TSource\n ): QueryBuilder<{\n baseSchema: SchemaFromSource<TSource>\n schema: SchemaFromSource<TSource>\n fromSourceName: keyof TSource & string\n hasJoins: false\n }> {\n const [, from] = this._createRefForSource(source, `from clause`)\n\n return new BaseQueryBuilder({\n ...this.query,\n from,\n }) as any\n }\n\n /**\n * Join another table or subquery to the current query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @param type - The type of join: 'inner', 'left', 'right', or 'full' (defaults to 'left')\n * @returns A QueryBuilder with the joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n *\n * // Inner join with explicit type\n * query\n * .from({ u: usersCollection })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId), 'inner')\n * ```\n *\n * // Join with a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query\n * .from({ activeUsers })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId))\n */\n join<\n TSource extends Source,\n TJoinType extends `inner` | `left` | `right` | `full` = `left`,\n >(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >,\n type: TJoinType = `left` as TJoinType\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, TJoinType>\n > {\n const [alias, from] = this._createRefForSource(source, `join clause`)\n\n // Create a temporary context for the callback\n const currentAliases = this._getCurrentAliases()\n const newAliases = [...currentAliases, alias]\n const refProxy = createRefProxy(newAliases) as RefProxyForContext<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n\n // Get the join condition expression\n const onExpression = onCallback(refProxy)\n\n // Extract left and right from the expression\n // For now, we'll assume it's an eq function with two arguments\n let left: BasicExpression\n let right: BasicExpression\n\n if (\n onExpression.type === `func` &&\n onExpression.name === `eq` &&\n onExpression.args.length === 2\n ) {\n left = onExpression.args[0]!\n right = onExpression.args[1]!\n } else {\n throw new Error(`Join condition must be an equality expression`)\n }\n\n const joinClause: JoinClause = {\n from,\n type,\n left,\n right,\n }\n\n const existingJoins = this.query.join || []\n\n return new BaseQueryBuilder({\n ...this.query,\n join: [...existingJoins, joinClause],\n }) as any\n }\n\n /**\n * Filter rows based on a condition\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the where condition applied\n *\n * @example\n * ```ts\n * // Simple condition\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n *\n * // Multiple conditions\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => and(\n * gt(users.age, 18),\n * eq(users.active, true)\n * ))\n *\n * // Multiple where calls are ANDed together\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n * .where(({users}) => eq(users.active, true))\n * ```\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingWhere = this.query.where || []\n\n return new BaseQueryBuilder({\n ...this.query,\n where: [...existingWhere, expression],\n }) as any\n }\n\n /**\n * Filter grouped rows based on aggregate conditions\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the having condition applied\n *\n * @example\n * ```ts\n * // Filter groups by count\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .having(({posts}) => gt(count(posts.id), 5))\n *\n * // Filter by average\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(avg(orders.total), 100))\n *\n * // Multiple having calls are ANDed together\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(count(orders.id), 5))\n * .having(({orders}) => gt(avg(orders.total), 100))\n * ```\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingHaving = this.query.having || []\n\n return new BaseQueryBuilder({\n ...this.query,\n having: [...existingHaving, expression],\n }) as any\n }\n\n /**\n * Select specific columns or computed values from the query\n *\n * @param callback - A function that receives table references and returns an object with selected fields or expressions\n * @returns A QueryBuilder that returns only the selected fields\n *\n * @example\n * ```ts\n * // Select specific columns\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * name: users.name,\n * email: users.email\n * }))\n *\n * // Select with computed values\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * fullName: concat(users.firstName, ' ', users.lastName),\n * ageInMonths: mul(users.age, 12)\n * }))\n *\n * // Select with aggregates (requires GROUP BY)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count(posts.id)\n * }))\n * ```\n */\n select<TSelectObject extends SelectObject>(\n callback: (refs: RefProxyForContext<TContext>) => TSelectObject\n ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const selectObject = callback(refProxy)\n\n // Check if any tables were spread during the callback\n const spreadSentinels = (refProxy as any).__spreadSentinels as Set<string>\n\n // Convert the select object to use expressions, including spread sentinels\n const select: Record<string, BasicExpression | Aggregate> = {}\n\n // First, add spread sentinels for any tables that were spread\n for (const spreadAlias of spreadSentinels) {\n const sentinelKey = `__SPREAD_SENTINEL__${spreadAlias}`\n select[sentinelKey] = toExpression(spreadAlias) // Use alias as a simple reference\n }\n\n // Then add the explicit select fields\n for (const [key, value] of Object.entries(selectObject)) {\n if (isRefProxy(value)) {\n select[key] = toExpression(value)\n } else if (\n typeof value === `object` &&\n `type` in value &&\n (value.type === `agg` || value.type === `func`)\n ) {\n select[key] = value as BasicExpression | Aggregate\n } else {\n select[key] = toExpression(value)\n }\n }\n\n return new BaseQueryBuilder({\n ...this.query,\n select,\n fnSelect: undefined, // remove the fnSelect clause if it exists\n }) as any\n }\n\n /**\n * Sort the query results by one or more columns\n *\n * @param callback - A function that receives table references and returns the field to sort by\n * @param direction - Sort direction: 'asc' for ascending, 'desc' for descending (defaults to 'asc')\n * @returns A QueryBuilder with the ordering applied\n *\n * @example\n * ```ts\n * // Sort by a single column\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.name)\n *\n * // Sort descending\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.createdAt, 'desc')\n *\n * // Multiple sorts (chain orderBy calls)\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.lastName)\n * .orderBy(({users}) => users.firstName)\n * ```\n */\n orderBy(\n callback: OrderByCallback<TContext>,\n direction: OrderByDirection = `asc`\n ): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n // Create the new OrderBy structure with expression and direction\n const orderByClause: OrderByClause = {\n expression: toExpression(result),\n direction,\n }\n\n const existingOrderBy: OrderBy = this.query.orderBy || []\n\n return new BaseQueryBuilder({\n ...this.query,\n orderBy: [...existingOrderBy, orderByClause],\n }) as any\n }\n\n /**\n * Group rows by one or more columns for aggregation\n *\n * @param callback - A function that receives table references and returns the field(s) to group by\n * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING)\n *\n * @example\n * ```ts\n * // Group by a single column\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count()\n * }))\n *\n * // Group by multiple columns\n * query\n * .from({ sales: salesCollection })\n * .groupBy(({sales}) => [sales.region, sales.category])\n * .select(({sales, sum}) => ({\n * region: sales.region,\n * category: sales.category,\n * totalSales: sum(sales.amount)\n * }))\n * ```\n */\n groupBy(callback: GroupByCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n const newExpressions = Array.isArray(result)\n ? result.map((r) => toExpression(r))\n : [toExpression(result)]\n\n // Replace existing groupBy expressions instead of extending them\n return new BaseQueryBuilder({\n ...this.query,\n groupBy: newExpressions,\n }) as any\n }\n\n /**\n * Limit the number of rows returned by the query\n * `orderBy` is required for `limit`\n *\n * @param count - Maximum number of rows to return\n * @returns A QueryBuilder with the limit applied\n *\n * @example\n * ```ts\n * // Get top 5 posts by likes\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.likes, 'desc')\n * .limit(5)\n * ```\n */\n limit(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n limit: count,\n }) as any\n }\n\n /**\n * Skip a number of rows before returning results\n * `orderBy` is required for `offset`\n *\n * @param count - Number of rows to skip\n * @returns A QueryBuilder with the offset applied\n *\n * @example\n * ```ts\n * // Get second page of results\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.createdAt, 'desc')\n * .offset(page * pageSize)\n * .limit(pageSize)\n * ```\n */\n offset(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n offset: count,\n }) as any\n }\n\n // Helper methods\n private _getCurrentAliases(): Array<string> {\n const aliases: Array<string> = []\n\n // Add the from alias\n if (this.query.from) {\n aliases.push(this.query.from.alias)\n }\n\n // Add join aliases\n if (this.query.join) {\n for (const join of this.query.join) {\n aliases.push(join.from.alias)\n }\n }\n\n return aliases\n }\n\n /**\n * Functional variants of the query builder\n * These are imperative function that are called for ery row.\n * Warning: that these cannot be optimized by the query compiler, and may prevent\n * some type of optimizations being possible.\n * @example\n * ```ts\n * q.fn.select((row) => ({\n * name: row.user.name.toUpperCase(),\n * age: row.user.age + 1,\n * }))\n * ```\n */\n get fn() {\n const builder = this\n return {\n /**\n * Select fields using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns the selected value\n * @returns A QueryBuilder with functional selection applied\n *\n * @example\n * ```ts\n * // Functional select (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.select(row => ({\n * name: row.users.name.toUpperCase(),\n * age: row.users.age + 1,\n * }))\n * ```\n */\n select<TFuncSelectResult>(\n callback: (row: TContext[`schema`]) => TFuncSelectResult\n ): QueryBuilder<WithResult<TContext, TFuncSelectResult>> {\n return new BaseQueryBuilder({\n ...builder.query,\n select: undefined, // remove the select clause if it exists\n fnSelect: callback,\n })\n },\n /**\n * Filter rows using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns a boolean\n * @returns A QueryBuilder with functional filtering applied\n *\n * @example\n * ```ts\n * // Functional where (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.where(row => row.users.name.startsWith('A'))\n * ```\n */\n where(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnWhere: [\n ...(builder.query.fnWhere || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n /**\n * Filter grouped rows using a function that operates on each aggregated row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives an aggregated row and returns a boolean\n * @returns A QueryBuilder with functional having filter applied\n *\n * @example\n * ```ts\n * // Functional having (not optimized)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .fn.having(row => row.count > 5)\n * ```\n */\n having(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnHaving: [\n ...(builder.query.fnHaving || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n }\n }\n\n _getQuery(): QueryIR {\n if (!this.query.from) {\n throw new Error(`Query must have a from clause`)\n }\n return this.query as QueryIR\n }\n}\n\n// Internal function to build a query from a callback\n// used by liveQueryCollectionOptions.query\nexport function buildQuery<TContext extends Context>(\n fn: (builder: InitialQueryBuilder) => QueryBuilder<TContext>\n): QueryIR {\n const result = fn(new BaseQueryBuilder())\n return getQueryIR(result)\n}\n\n// Internal function to get the QueryIR from a builder\nexport function getQueryIR(\n builder: BaseQueryBuilder | QueryBuilder<any> | InitialQueryBuilder\n): QueryIR {\n return (builder as unknown as BaseQueryBuilder)._getQuery()\n}\n\n// Type-only exports for the query builder\nexport type InitialQueryBuilder = Pick<BaseQueryBuilder<Context>, `from`>\n\nexport type InitialQueryBuilderConstructor = new () => InitialQueryBuilder\n\nexport type QueryBuilder<TContext extends Context> = Omit<\n BaseQueryBuilder<TContext>,\n `from` | `_getQuery`\n>\n\n// Main query builder class alias with the constructor type modified to hide all\n// but the from method on the initial instance\nexport const Query: InitialQueryBuilderConstructor = BaseQueryBuilder\n\n// Helper type to extract context from a QueryBuilder\nexport type ExtractContext<T> =\n T extends BaseQueryBuilder<infer TContext>\n ? TContext\n : T extends QueryBuilder<infer TContext>\n ? TContext\n : never\n\n// Export the types from types.ts for convenience\nexport type { Context, Source, GetResult } from \"./types.js\"\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { BasicExpression } from '../ir.js';
2
+ export interface RefProxy<T = any> {
3
+ /** @internal */
4
+ readonly __refProxy: true;
5
+ /** @internal */
6
+ readonly __path: Array<string>;
7
+ /** @internal */
8
+ readonly __type: T;
9
+ }
10
+ /**
11
+ * Creates a proxy object that records property access paths
12
+ * Used in callbacks like where, select, etc. to create type-safe references
13
+ */
14
+ export declare function createRefProxy<T extends Record<string, any>>(aliases: Array<string>): RefProxy<T> & T;
15
+ /**
16
+ * Converts a value to an Expression
17
+ * If it's a RefProxy, creates a Ref, otherwise creates a Value
18
+ */
19
+ export declare function toExpression<T = any>(value: T): BasicExpression<T>;
20
+ export declare function toExpression(value: RefProxy<any>): BasicExpression<any>;
21
+ /**
22
+ * Type guard to check if a value is a RefProxy
23
+ */
24
+ export declare function isRefProxy(value: any): value is RefProxy;
25
+ /**
26
+ * Helper to create a Value expression from a literal
27
+ */
28
+ export declare function val<T>(value: T): BasicExpression<T>;
@@ -0,0 +1,92 @@
1
+ import { PropRef, Value } from "../ir.js";
2
+ function createRefProxy(aliases) {
3
+ const cache = /* @__PURE__ */ new Map();
4
+ const spreadSentinels = /* @__PURE__ */ new Set();
5
+ function createProxy(path) {
6
+ const pathKey = path.join(`.`);
7
+ if (cache.has(pathKey)) {
8
+ return cache.get(pathKey);
9
+ }
10
+ const proxy = new Proxy({}, {
11
+ get(target, prop, receiver) {
12
+ if (prop === `__refProxy`) return true;
13
+ if (prop === `__path`) return path;
14
+ if (prop === `__type`) return void 0;
15
+ if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver);
16
+ const newPath = [...path, String(prop)];
17
+ return createProxy(newPath);
18
+ },
19
+ has(target, prop) {
20
+ if (prop === `__refProxy` || prop === `__path` || prop === `__type`)
21
+ return true;
22
+ return Reflect.has(target, prop);
23
+ },
24
+ ownKeys(target) {
25
+ if (path.length === 1) {
26
+ const aliasName = path[0];
27
+ spreadSentinels.add(aliasName);
28
+ }
29
+ return Reflect.ownKeys(target);
30
+ },
31
+ getOwnPropertyDescriptor(target, prop) {
32
+ if (prop === `__refProxy` || prop === `__path` || prop === `__type`) {
33
+ return { enumerable: false, configurable: true };
34
+ }
35
+ return Reflect.getOwnPropertyDescriptor(target, prop);
36
+ }
37
+ });
38
+ cache.set(pathKey, proxy);
39
+ return proxy;
40
+ }
41
+ const rootProxy = new Proxy({}, {
42
+ get(target, prop, receiver) {
43
+ if (prop === `__refProxy`) return true;
44
+ if (prop === `__path`) return [];
45
+ if (prop === `__type`) return void 0;
46
+ if (prop === `__spreadSentinels`) return spreadSentinels;
47
+ if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver);
48
+ const propStr = String(prop);
49
+ if (aliases.includes(propStr)) {
50
+ return createProxy([propStr]);
51
+ }
52
+ return void 0;
53
+ },
54
+ has(target, prop) {
55
+ if (prop === `__refProxy` || prop === `__path` || prop === `__type` || prop === `__spreadSentinels`)
56
+ return true;
57
+ if (typeof prop === `string` && aliases.includes(prop)) return true;
58
+ return Reflect.has(target, prop);
59
+ },
60
+ ownKeys(_target) {
61
+ return [...aliases, `__refProxy`, `__path`, `__type`, `__spreadSentinels`];
62
+ },
63
+ getOwnPropertyDescriptor(target, prop) {
64
+ if (prop === `__refProxy` || prop === `__path` || prop === `__type` || prop === `__spreadSentinels`) {
65
+ return { enumerable: false, configurable: true };
66
+ }
67
+ if (typeof prop === `string` && aliases.includes(prop)) {
68
+ return { enumerable: true, configurable: true };
69
+ }
70
+ return void 0;
71
+ }
72
+ });
73
+ return rootProxy;
74
+ }
75
+ function toExpression(value) {
76
+ if (isRefProxy(value)) {
77
+ return new PropRef(value.__path);
78
+ }
79
+ if (value && typeof value === `object` && `type` in value && (value.type === `func` || value.type === `ref` || value.type === `val` || value.type === `agg`)) {
80
+ return value;
81
+ }
82
+ return new Value(value);
83
+ }
84
+ function isRefProxy(value) {
85
+ return value && typeof value === `object` && value.__refProxy === true;
86
+ }
87
+ export {
88
+ createRefProxy,
89
+ isRefProxy,
90
+ toExpression
91
+ };
92
+ //# sourceMappingURL=ref-proxy.js.map