@tanstack/db 0.0.1

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 (154) hide show
  1. package/README.md +37 -0
  2. package/dist/cjs/SortedMap.cjs +140 -0
  3. package/dist/cjs/SortedMap.cjs.map +1 -0
  4. package/dist/cjs/SortedMap.d.cts +91 -0
  5. package/dist/cjs/collection.cjs +597 -0
  6. package/dist/cjs/collection.cjs.map +1 -0
  7. package/dist/cjs/collection.d.cts +176 -0
  8. package/dist/cjs/deferred.cjs +25 -0
  9. package/dist/cjs/deferred.cjs.map +1 -0
  10. package/dist/cjs/deferred.d.cts +20 -0
  11. package/dist/cjs/errors.cjs +10 -0
  12. package/dist/cjs/errors.cjs.map +1 -0
  13. package/dist/cjs/errors.d.cts +3 -0
  14. package/dist/cjs/index.cjs +33 -0
  15. package/dist/cjs/index.cjs.map +1 -0
  16. package/dist/cjs/index.d.cts +9 -0
  17. package/dist/cjs/proxy.cjs +654 -0
  18. package/dist/cjs/proxy.cjs.map +1 -0
  19. package/dist/cjs/proxy.d.cts +59 -0
  20. package/dist/cjs/query/compiled-query.cjs +162 -0
  21. package/dist/cjs/query/compiled-query.cjs.map +1 -0
  22. package/dist/cjs/query/compiled-query.d.cts +22 -0
  23. package/dist/cjs/query/evaluators.cjs +146 -0
  24. package/dist/cjs/query/evaluators.cjs.map +1 -0
  25. package/dist/cjs/query/evaluators.d.cts +9 -0
  26. package/dist/cjs/query/extractors.cjs +122 -0
  27. package/dist/cjs/query/extractors.cjs.map +1 -0
  28. package/dist/cjs/query/extractors.d.cts +22 -0
  29. package/dist/cjs/query/functions.cjs +152 -0
  30. package/dist/cjs/query/functions.cjs.map +1 -0
  31. package/dist/cjs/query/functions.d.cts +21 -0
  32. package/dist/cjs/query/group-by.cjs +91 -0
  33. package/dist/cjs/query/group-by.cjs.map +1 -0
  34. package/dist/cjs/query/group-by.d.cts +40 -0
  35. package/dist/cjs/query/index.d.cts +5 -0
  36. package/dist/cjs/query/joins.cjs +155 -0
  37. package/dist/cjs/query/joins.cjs.map +1 -0
  38. package/dist/cjs/query/joins.d.cts +14 -0
  39. package/dist/cjs/query/key-by.cjs +43 -0
  40. package/dist/cjs/query/key-by.cjs.map +1 -0
  41. package/dist/cjs/query/key-by.d.cts +3 -0
  42. package/dist/cjs/query/order-by.cjs +229 -0
  43. package/dist/cjs/query/order-by.cjs.map +1 -0
  44. package/dist/cjs/query/order-by.d.cts +3 -0
  45. package/dist/cjs/query/pipeline-compiler.cjs +94 -0
  46. package/dist/cjs/query/pipeline-compiler.cjs.map +1 -0
  47. package/dist/cjs/query/pipeline-compiler.d.cts +9 -0
  48. package/dist/cjs/query/query-builder.cjs +314 -0
  49. package/dist/cjs/query/query-builder.cjs.map +1 -0
  50. package/dist/cjs/query/query-builder.d.cts +219 -0
  51. package/dist/cjs/query/schema.d.cts +98 -0
  52. package/dist/cjs/query/select.cjs +107 -0
  53. package/dist/cjs/query/select.cjs.map +1 -0
  54. package/dist/cjs/query/select.d.cts +3 -0
  55. package/dist/cjs/query/types.d.cts +188 -0
  56. package/dist/cjs/query/utils.cjs +154 -0
  57. package/dist/cjs/query/utils.cjs.map +1 -0
  58. package/dist/cjs/query/utils.d.cts +37 -0
  59. package/dist/cjs/transactions.cjs +137 -0
  60. package/dist/cjs/transactions.cjs.map +1 -0
  61. package/dist/cjs/transactions.d.cts +27 -0
  62. package/dist/cjs/types.d.cts +94 -0
  63. package/dist/cjs/utils.cjs +17 -0
  64. package/dist/cjs/utils.cjs.map +1 -0
  65. package/dist/cjs/utils.d.cts +3 -0
  66. package/dist/esm/SortedMap.d.ts +91 -0
  67. package/dist/esm/SortedMap.js +140 -0
  68. package/dist/esm/SortedMap.js.map +1 -0
  69. package/dist/esm/collection.d.ts +176 -0
  70. package/dist/esm/collection.js +597 -0
  71. package/dist/esm/collection.js.map +1 -0
  72. package/dist/esm/deferred.d.ts +20 -0
  73. package/dist/esm/deferred.js +25 -0
  74. package/dist/esm/deferred.js.map +1 -0
  75. package/dist/esm/errors.d.ts +3 -0
  76. package/dist/esm/errors.js +10 -0
  77. package/dist/esm/errors.js.map +1 -0
  78. package/dist/esm/index.d.ts +9 -0
  79. package/dist/esm/index.js +33 -0
  80. package/dist/esm/index.js.map +1 -0
  81. package/dist/esm/proxy.d.ts +59 -0
  82. package/dist/esm/proxy.js +654 -0
  83. package/dist/esm/proxy.js.map +1 -0
  84. package/dist/esm/query/compiled-query.d.ts +22 -0
  85. package/dist/esm/query/compiled-query.js +162 -0
  86. package/dist/esm/query/compiled-query.js.map +1 -0
  87. package/dist/esm/query/evaluators.d.ts +9 -0
  88. package/dist/esm/query/evaluators.js +146 -0
  89. package/dist/esm/query/evaluators.js.map +1 -0
  90. package/dist/esm/query/extractors.d.ts +22 -0
  91. package/dist/esm/query/extractors.js +122 -0
  92. package/dist/esm/query/extractors.js.map +1 -0
  93. package/dist/esm/query/functions.d.ts +21 -0
  94. package/dist/esm/query/functions.js +152 -0
  95. package/dist/esm/query/functions.js.map +1 -0
  96. package/dist/esm/query/group-by.d.ts +40 -0
  97. package/dist/esm/query/group-by.js +91 -0
  98. package/dist/esm/query/group-by.js.map +1 -0
  99. package/dist/esm/query/index.d.ts +5 -0
  100. package/dist/esm/query/joins.d.ts +14 -0
  101. package/dist/esm/query/joins.js +155 -0
  102. package/dist/esm/query/joins.js.map +1 -0
  103. package/dist/esm/query/key-by.d.ts +3 -0
  104. package/dist/esm/query/key-by.js +43 -0
  105. package/dist/esm/query/key-by.js.map +1 -0
  106. package/dist/esm/query/order-by.d.ts +3 -0
  107. package/dist/esm/query/order-by.js +229 -0
  108. package/dist/esm/query/order-by.js.map +1 -0
  109. package/dist/esm/query/pipeline-compiler.d.ts +9 -0
  110. package/dist/esm/query/pipeline-compiler.js +94 -0
  111. package/dist/esm/query/pipeline-compiler.js.map +1 -0
  112. package/dist/esm/query/query-builder.d.ts +219 -0
  113. package/dist/esm/query/query-builder.js +314 -0
  114. package/dist/esm/query/query-builder.js.map +1 -0
  115. package/dist/esm/query/schema.d.ts +98 -0
  116. package/dist/esm/query/select.d.ts +3 -0
  117. package/dist/esm/query/select.js +107 -0
  118. package/dist/esm/query/select.js.map +1 -0
  119. package/dist/esm/query/types.d.ts +188 -0
  120. package/dist/esm/query/utils.d.ts +37 -0
  121. package/dist/esm/query/utils.js +154 -0
  122. package/dist/esm/query/utils.js.map +1 -0
  123. package/dist/esm/transactions.d.ts +27 -0
  124. package/dist/esm/transactions.js +137 -0
  125. package/dist/esm/transactions.js.map +1 -0
  126. package/dist/esm/types.d.ts +94 -0
  127. package/dist/esm/utils.d.ts +3 -0
  128. package/dist/esm/utils.js +17 -0
  129. package/dist/esm/utils.js.map +1 -0
  130. package/package.json +57 -0
  131. package/src/SortedMap.ts +163 -0
  132. package/src/collection.ts +919 -0
  133. package/src/deferred.ts +47 -0
  134. package/src/errors.ts +6 -0
  135. package/src/index.ts +12 -0
  136. package/src/proxy.ts +1104 -0
  137. package/src/query/compiled-query.ts +193 -0
  138. package/src/query/evaluators.ts +222 -0
  139. package/src/query/extractors.ts +211 -0
  140. package/src/query/functions.ts +297 -0
  141. package/src/query/group-by.ts +137 -0
  142. package/src/query/index.ts +5 -0
  143. package/src/query/joins.ts +247 -0
  144. package/src/query/key-by.ts +61 -0
  145. package/src/query/order-by.ts +312 -0
  146. package/src/query/pipeline-compiler.ts +152 -0
  147. package/src/query/query-builder.ts +898 -0
  148. package/src/query/schema.ts +255 -0
  149. package/src/query/select.ts +173 -0
  150. package/src/query/types.ts +417 -0
  151. package/src/query/utils.ts +245 -0
  152. package/src/transactions.ts +198 -0
  153. package/src/types.ts +125 -0
  154. package/src/utils.ts +15 -0
@@ -0,0 +1,898 @@
1
+ import type { Collection } from "../collection"
2
+ import type {
3
+ Comparator,
4
+ Condition,
5
+ From,
6
+ JoinClause,
7
+ Limit,
8
+ LiteralValue,
9
+ Offset,
10
+ OrderBy,
11
+ Query,
12
+ Select,
13
+ WithQuery,
14
+ } from "./schema.js"
15
+ import type {
16
+ Context,
17
+ Flatten,
18
+ InferResultTypeFromSelectTuple,
19
+ Input,
20
+ InputReference,
21
+ PropertyReference,
22
+ PropertyReferenceString,
23
+ RemoveIndexSignature,
24
+ Schema,
25
+ } from "./types.js"
26
+
27
+ type CollectionRef = { [K: string]: Collection<any> }
28
+
29
+ export class BaseQueryBuilder<TContext extends Context<Schema>> {
30
+ private readonly query: Partial<Query<TContext>> = {}
31
+
32
+ /**
33
+ * Create a new QueryBuilder instance.
34
+ */
35
+ constructor(query: Partial<Query<TContext>> = {}) {
36
+ this.query = query
37
+ }
38
+
39
+ from<TCollectionRef extends CollectionRef>(
40
+ collectionRef: TCollectionRef
41
+ ): QueryBuilder<{
42
+ baseSchema: Flatten<
43
+ TContext[`baseSchema`] & {
44
+ [K in keyof TCollectionRef & string]: RemoveIndexSignature<
45
+ (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>
46
+ ? T
47
+ : never) &
48
+ Input
49
+ >
50
+ }
51
+ >
52
+ schema: Flatten<{
53
+ [K in keyof TCollectionRef & string]: RemoveIndexSignature<
54
+ (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>
55
+ ? T
56
+ : never) &
57
+ Input
58
+ >
59
+ }>
60
+ default: keyof TCollectionRef & string
61
+ }>
62
+
63
+ from<
64
+ T extends InputReference<{
65
+ baseSchema: TContext[`baseSchema`]
66
+ schema: TContext[`baseSchema`]
67
+ }>,
68
+ >(
69
+ collection: T
70
+ ): QueryBuilder<{
71
+ baseSchema: TContext[`baseSchema`]
72
+ schema: {
73
+ [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>
74
+ }
75
+ default: T
76
+ }>
77
+
78
+ from<
79
+ T extends InputReference<{
80
+ baseSchema: TContext[`baseSchema`]
81
+ schema: TContext[`baseSchema`]
82
+ }>,
83
+ TAs extends string,
84
+ >(
85
+ collection: T,
86
+ as: TAs
87
+ ): QueryBuilder<{
88
+ baseSchema: TContext[`baseSchema`]
89
+ schema: {
90
+ [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][T]>
91
+ }
92
+ default: TAs
93
+ }>
94
+
95
+ /**
96
+ * Specify the collection to query from.
97
+ * This is the first method that must be called in the chain.
98
+ *
99
+ * @param collection The collection name to query from
100
+ * @param as Optional alias for the collection
101
+ * @returns A new QueryBuilder with the from clause set
102
+ */
103
+ from<
104
+ T extends
105
+ | InputReference<{
106
+ baseSchema: TContext[`baseSchema`]
107
+ schema: TContext[`baseSchema`]
108
+ }>
109
+ | CollectionRef,
110
+ TAs extends string | undefined,
111
+ >(collection: T, as?: TAs) {
112
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
113
+ if (typeof collection === `object` && collection !== null) {
114
+ return this.fromCollectionRef(collection)
115
+ } else if (typeof collection === `string`) {
116
+ return this.fromInputReference(
117
+ collection as InputReference<{
118
+ baseSchema: TContext[`baseSchema`]
119
+ schema: TContext[`baseSchema`]
120
+ }>,
121
+ as
122
+ )
123
+ } else {
124
+ throw new Error(`Invalid collection type`)
125
+ }
126
+ }
127
+
128
+ private fromCollectionRef<TCollectionRef extends CollectionRef>(
129
+ collectionRef: TCollectionRef
130
+ ) {
131
+ const keys = Object.keys(collectionRef)
132
+ if (keys.length !== 1) {
133
+ throw new Error(`Expected exactly one key`)
134
+ }
135
+
136
+ const key = keys[0]!
137
+ const collection = collectionRef[key]!
138
+
139
+ const newBuilder = new BaseQueryBuilder()
140
+ Object.assign(newBuilder.query, this.query)
141
+ newBuilder.query.from = key as From<TContext>
142
+ newBuilder.query.collections ??= {}
143
+ newBuilder.query.collections[key] = collection
144
+
145
+ return newBuilder as unknown as QueryBuilder<{
146
+ baseSchema: TContext[`baseSchema`] & {
147
+ [K in keyof TCollectionRef &
148
+ string]: (TCollectionRef[keyof TCollectionRef] extends Collection<
149
+ infer T
150
+ >
151
+ ? T
152
+ : never) &
153
+ Input
154
+ }
155
+ schema: {
156
+ [K in keyof TCollectionRef &
157
+ string]: (TCollectionRef[keyof TCollectionRef] extends Collection<
158
+ infer T
159
+ >
160
+ ? T
161
+ : never) &
162
+ Input
163
+ }
164
+ default: keyof TCollectionRef & string
165
+ }>
166
+ }
167
+
168
+ private fromInputReference<
169
+ T extends InputReference<{
170
+ baseSchema: TContext[`baseSchema`]
171
+ schema: TContext[`baseSchema`]
172
+ }>,
173
+ TAs extends string | undefined,
174
+ >(collection: T, as?: TAs) {
175
+ const newBuilder = new BaseQueryBuilder()
176
+ Object.assign(newBuilder.query, this.query)
177
+ newBuilder.query.from = collection as From<TContext>
178
+ if (as) {
179
+ newBuilder.query.as = as
180
+ }
181
+
182
+ // Calculate the result type without deep nesting
183
+ type ResultSchema = TAs extends undefined
184
+ ? { [K in T]: TContext[`baseSchema`][T] }
185
+ : { [K in string & TAs]: TContext[`baseSchema`][T] }
186
+
187
+ type ResultDefault = TAs extends undefined ? T : string & TAs
188
+
189
+ // Use simpler type assertion to avoid excessive depth
190
+ return newBuilder as unknown as QueryBuilder<{
191
+ baseSchema: TContext[`baseSchema`]
192
+ schema: ResultSchema
193
+ default: ResultDefault
194
+ }>
195
+ }
196
+
197
+ /**
198
+ * Specify what columns to select.
199
+ * Overwrites any previous select clause.
200
+ *
201
+ * @param selects The columns to select
202
+ * @returns A new QueryBuilder with the select clause set
203
+ */
204
+ select<TSelects extends Array<Select<TContext>>>(
205
+ this: QueryBuilder<TContext>,
206
+ ...selects: TSelects
207
+ ) {
208
+ // Validate function calls in the selects
209
+ // Need to use a type assertion to bypass deep recursive type checking
210
+ const validatedSelects = selects.map((select) => {
211
+ // If the select is an object with aliases, validate each value
212
+ if (
213
+ typeof select === `object` &&
214
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
215
+ select !== null &&
216
+ !Array.isArray(select)
217
+ ) {
218
+ const result: Record<string, any> = {}
219
+
220
+ for (const [key, value] of Object.entries(select)) {
221
+ // If it's a function call (object with a single key that is an allowed function name)
222
+ if (
223
+ typeof value === `object` &&
224
+ value !== null &&
225
+ !Array.isArray(value)
226
+ ) {
227
+ const keys = Object.keys(value)
228
+ if (keys.length === 1) {
229
+ const funcName = keys[0]!
230
+ // List of allowed function names from AllowedFunctionName
231
+ const allowedFunctions = [
232
+ `SUM`,
233
+ `COUNT`,
234
+ `AVG`,
235
+ `MIN`,
236
+ `MAX`,
237
+ `DATE`,
238
+ `JSON_EXTRACT`,
239
+ `JSON_EXTRACT_PATH`,
240
+ `UPPER`,
241
+ `LOWER`,
242
+ `COALESCE`,
243
+ `CONCAT`,
244
+ `LENGTH`,
245
+ `ORDER_INDEX`,
246
+ ]
247
+
248
+ if (!allowedFunctions.includes(funcName)) {
249
+ console.warn(
250
+ `Unsupported function: ${funcName}. Expected one of: ${allowedFunctions.join(`, `)}`
251
+ )
252
+ }
253
+ }
254
+ }
255
+
256
+ result[key] = value
257
+ }
258
+
259
+ return result
260
+ }
261
+
262
+ return select
263
+ })
264
+
265
+ // Ensure we have an orderByIndex in the select if we have an orderBy
266
+ // This is required if select is called after orderBy
267
+ if (this._query.orderBy) {
268
+ validatedSelects.push({ _orderByIndex: { ORDER_INDEX: `numeric` } })
269
+ }
270
+
271
+ const newBuilder = new BaseQueryBuilder<TContext>(
272
+ (this as BaseQueryBuilder<TContext>).query
273
+ )
274
+ newBuilder.query.select = validatedSelects as Array<Select<TContext>>
275
+
276
+ return newBuilder as QueryBuilder<
277
+ Flatten<
278
+ Omit<TContext, `result`> & {
279
+ result: InferResultTypeFromSelectTuple<TContext, TSelects>
280
+ }
281
+ >
282
+ >
283
+ }
284
+
285
+ /**
286
+ * Add a where clause comparing two values.
287
+ */
288
+ where(
289
+ left: PropertyReferenceString<TContext> | LiteralValue,
290
+ operator: Comparator,
291
+ right: PropertyReferenceString<TContext> | LiteralValue
292
+ ): QueryBuilder<TContext>
293
+
294
+ /**
295
+ * Add a where clause with a complete condition object.
296
+ */
297
+ where(condition: Condition<TContext>): QueryBuilder<TContext>
298
+
299
+ /**
300
+ * Add a where clause to filter the results.
301
+ * Can be called multiple times to add AND conditions.
302
+ *
303
+ * @param leftOrCondition The left operand or complete condition
304
+ * @param operator Optional comparison operator
305
+ * @param right Optional right operand
306
+ * @returns A new QueryBuilder with the where clause added
307
+ */
308
+ where(
309
+ leftOrCondition: any,
310
+ operator?: any,
311
+ right?: any
312
+ ): QueryBuilder<TContext> {
313
+ // Create a new builder with a copy of the current query
314
+ // Use simplistic approach to avoid deep type errors
315
+ const newBuilder = new BaseQueryBuilder<TContext>()
316
+ Object.assign(newBuilder.query, this.query)
317
+
318
+ let condition: any
319
+
320
+ // Determine if this is a complete condition or individual parts
321
+ if (operator !== undefined && right !== undefined) {
322
+ // Create a condition from parts
323
+ condition = [leftOrCondition, operator, right]
324
+ } else {
325
+ // Use the provided condition directly
326
+ condition = leftOrCondition
327
+ }
328
+
329
+ if (!newBuilder.query.where) {
330
+ newBuilder.query.where = condition
331
+ } else {
332
+ // Create a composite condition with AND
333
+ // Use any to bypass type checking issues
334
+ const andArray: any = [newBuilder.query.where, `and`, condition]
335
+ newBuilder.query.where = andArray
336
+ }
337
+
338
+ return newBuilder as unknown as QueryBuilder<TContext>
339
+ }
340
+
341
+ /**
342
+ * Add a having clause comparing two values.
343
+ * For filtering results after they have been grouped.
344
+ */
345
+ having(
346
+ left: PropertyReferenceString<TContext> | LiteralValue,
347
+ operator: Comparator,
348
+ right: PropertyReferenceString<TContext> | LiteralValue
349
+ ): QueryBuilder<TContext>
350
+
351
+ /**
352
+ * Add a having clause with a complete condition object.
353
+ * For filtering results after they have been grouped.
354
+ */
355
+ having(condition: Condition<TContext>): QueryBuilder<TContext>
356
+
357
+ /**
358
+ * Add a having clause to filter the grouped results.
359
+ * Can be called multiple times to add AND conditions.
360
+ *
361
+ * @param leftOrCondition The left operand or complete condition
362
+ * @param operator Optional comparison operator
363
+ * @param right Optional right operand
364
+ * @returns A new QueryBuilder with the having clause added
365
+ */
366
+ having(
367
+ leftOrCondition: any,
368
+ operator?: any,
369
+ right?: any
370
+ ): QueryBuilder<TContext> {
371
+ // Create a new builder with a copy of the current query
372
+ const newBuilder = new BaseQueryBuilder<TContext>()
373
+ Object.assign(newBuilder.query, this.query)
374
+
375
+ let condition: any
376
+
377
+ // Determine if this is a complete condition or individual parts
378
+ if (operator !== undefined && right !== undefined) {
379
+ // Create a condition from parts
380
+ condition = [leftOrCondition, operator, right]
381
+ } else {
382
+ // Use the provided condition directly
383
+ condition = leftOrCondition
384
+ }
385
+
386
+ if (!newBuilder.query.having) {
387
+ newBuilder.query.having = condition
388
+ } else {
389
+ // Create a composite condition with AND
390
+ // Use any to bypass type checking issues
391
+ const andArray: any = [newBuilder.query.having, `and`, condition]
392
+ newBuilder.query.having = andArray
393
+ }
394
+
395
+ return newBuilder as QueryBuilder<TContext>
396
+ }
397
+
398
+ /**
399
+ * Add a join clause to the query using a CollectionRef.
400
+ */
401
+ join<TCollectionRef extends CollectionRef>(joinClause: {
402
+ type: `inner` | `left` | `right` | `full` | `cross`
403
+ from: TCollectionRef
404
+ on: Condition<
405
+ Flatten<{
406
+ baseSchema: TContext[`baseSchema`]
407
+ schema: TContext[`schema`] & {
408
+ [K in keyof TCollectionRef & string]: RemoveIndexSignature<
409
+ (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>
410
+ ? T
411
+ : never) &
412
+ Input
413
+ >
414
+ }
415
+ }>
416
+ >
417
+ where?: Condition<
418
+ Flatten<{
419
+ baseSchema: TContext[`baseSchema`]
420
+ schema: {
421
+ [K in keyof TCollectionRef & string]: RemoveIndexSignature<
422
+ (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>
423
+ ? T
424
+ : never) &
425
+ Input
426
+ >
427
+ }
428
+ }>
429
+ >
430
+ }): QueryBuilder<
431
+ Flatten<
432
+ Omit<TContext, `schema`> & {
433
+ schema: TContext[`schema`] & {
434
+ [K in keyof TCollectionRef & string]: RemoveIndexSignature<
435
+ (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>
436
+ ? T
437
+ : never) &
438
+ Input
439
+ >
440
+ }
441
+ }
442
+ >
443
+ >
444
+
445
+ /**
446
+ * Add a join clause to the query without specifying an alias.
447
+ * The collection name will be used as the default alias.
448
+ */
449
+ join<
450
+ T extends InputReference<{
451
+ baseSchema: TContext[`baseSchema`]
452
+ schema: TContext[`baseSchema`]
453
+ }>,
454
+ >(joinClause: {
455
+ type: `inner` | `left` | `right` | `full` | `cross`
456
+ from: T
457
+ on: Condition<
458
+ Flatten<{
459
+ baseSchema: TContext[`baseSchema`]
460
+ schema: TContext[`schema`] & {
461
+ [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>
462
+ }
463
+ }>
464
+ >
465
+ where?: Condition<
466
+ Flatten<{
467
+ baseSchema: TContext[`baseSchema`]
468
+ schema: { [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]> }
469
+ }>
470
+ >
471
+ }): QueryBuilder<
472
+ Flatten<
473
+ Omit<TContext, `schema`> & {
474
+ schema: TContext[`schema`] & {
475
+ [K in T]: RemoveIndexSignature<TContext[`baseSchema`][T]>
476
+ }
477
+ }
478
+ >
479
+ >
480
+
481
+ /**
482
+ * Add a join clause to the query with a specified alias.
483
+ */
484
+ join<
485
+ TFrom extends InputReference<{
486
+ baseSchema: TContext[`baseSchema`]
487
+ schema: TContext[`baseSchema`]
488
+ }>,
489
+ TAs extends string,
490
+ >(joinClause: {
491
+ type: `inner` | `left` | `right` | `full` | `cross`
492
+ from: TFrom
493
+ as: TAs
494
+ on: Condition<
495
+ Flatten<{
496
+ baseSchema: TContext[`baseSchema`]
497
+ schema: TContext[`schema`] & {
498
+ [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>
499
+ }
500
+ }>
501
+ >
502
+ where?: Condition<
503
+ Flatten<{
504
+ baseSchema: TContext[`baseSchema`]
505
+ schema: {
506
+ [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>
507
+ }
508
+ }>
509
+ >
510
+ }): QueryBuilder<
511
+ Flatten<
512
+ Omit<TContext, `schema`> & {
513
+ schema: TContext[`schema`] & {
514
+ [K in TAs]: RemoveIndexSignature<TContext[`baseSchema`][TFrom]>
515
+ }
516
+ }
517
+ >
518
+ >
519
+
520
+ join<
521
+ TFrom extends
522
+ | InputReference<{
523
+ baseSchema: TContext[`baseSchema`]
524
+ schema: TContext[`baseSchema`]
525
+ }>
526
+ | CollectionRef,
527
+ TAs extends string | undefined = undefined,
528
+ >(joinClause: {
529
+ type: `inner` | `left` | `right` | `full` | `cross`
530
+ from: TFrom
531
+ as?: TAs
532
+ on: Condition<
533
+ Flatten<{
534
+ baseSchema: TContext[`baseSchema`]
535
+ schema: TContext[`schema`] &
536
+ (TFrom extends CollectionRef
537
+ ? {
538
+ [K in keyof TFrom & string]: RemoveIndexSignature<
539
+ (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &
540
+ Input
541
+ >
542
+ }
543
+ : TFrom extends InputReference<infer TRef>
544
+ ? {
545
+ [K in keyof TRef & string]: RemoveIndexSignature<
546
+ TRef[keyof TRef]
547
+ >
548
+ }
549
+ : never)
550
+ }>
551
+ >
552
+ where?: Condition<
553
+ Flatten<{
554
+ baseSchema: TContext[`baseSchema`]
555
+ schema: TContext[`schema`] &
556
+ (TFrom extends CollectionRef
557
+ ? {
558
+ [K in keyof TFrom & string]: RemoveIndexSignature<
559
+ (TFrom[keyof TFrom] extends Collection<infer T> ? T : never) &
560
+ Input
561
+ >
562
+ }
563
+ : TFrom extends InputReference<infer TRef>
564
+ ? {
565
+ [K in keyof TRef & string]: RemoveIndexSignature<
566
+ TRef[keyof TRef]
567
+ >
568
+ }
569
+ : never)
570
+ }>
571
+ >
572
+ }): QueryBuilder<any> {
573
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
574
+ if (typeof joinClause.from === `object` && joinClause.from !== null) {
575
+ return this.joinCollectionRef(
576
+ joinClause as {
577
+ type: `inner` | `left` | `right` | `full` | `cross`
578
+ from: CollectionRef
579
+ on: Condition<any>
580
+ where?: Condition<any>
581
+ }
582
+ )
583
+ } else {
584
+ return this.joinInputReference(
585
+ joinClause as {
586
+ type: `inner` | `left` | `right` | `full` | `cross`
587
+ from: InputReference<{
588
+ baseSchema: TContext[`baseSchema`]
589
+ schema: TContext[`baseSchema`]
590
+ }>
591
+ as?: TAs
592
+ on: Condition<any>
593
+ where?: Condition<any>
594
+ }
595
+ )
596
+ }
597
+ }
598
+
599
+ private joinCollectionRef<TCollectionRef extends CollectionRef>(joinClause: {
600
+ type: `inner` | `left` | `right` | `full` | `cross`
601
+ from: TCollectionRef
602
+ on: Condition<any>
603
+ where?: Condition<any>
604
+ }): QueryBuilder<any> {
605
+ // Create a new builder with a copy of the current query
606
+ const newBuilder = new BaseQueryBuilder<TContext>()
607
+ Object.assign(newBuilder.query, this.query)
608
+
609
+ // Get the collection key
610
+ const keys = Object.keys(joinClause.from)
611
+ if (keys.length !== 1) {
612
+ throw new Error(`Expected exactly one key in CollectionRef`)
613
+ }
614
+ const key = keys[0]!
615
+ const collection = joinClause.from[key]
616
+ if (!collection) {
617
+ throw new Error(`Collection not found for key: ${key}`)
618
+ }
619
+
620
+ // Create a copy of the join clause for the query
621
+ const joinClauseCopy = {
622
+ type: joinClause.type,
623
+ from: key,
624
+ on: joinClause.on,
625
+ where: joinClause.where,
626
+ } as JoinClause<TContext>
627
+
628
+ // Add the join clause to the query
629
+ if (!newBuilder.query.join) {
630
+ newBuilder.query.join = [joinClauseCopy]
631
+ } else {
632
+ newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]
633
+ }
634
+
635
+ // Add the collection to the collections map
636
+ newBuilder.query.collections ??= {}
637
+ newBuilder.query.collections[key] = collection
638
+
639
+ // Return the new builder with updated schema type
640
+ return newBuilder as QueryBuilder<
641
+ Flatten<
642
+ Omit<TContext, `schema`> & {
643
+ schema: TContext[`schema`] & {
644
+ [K in keyof TCollectionRef & string]: RemoveIndexSignature<
645
+ (TCollectionRef[keyof TCollectionRef] extends Collection<infer T>
646
+ ? T
647
+ : never) &
648
+ Input
649
+ >
650
+ }
651
+ }
652
+ >
653
+ >
654
+ }
655
+
656
+ private joinInputReference<
657
+ TFrom extends InputReference<{
658
+ baseSchema: TContext[`baseSchema`]
659
+ schema: TContext[`baseSchema`]
660
+ }>,
661
+ TAs extends string | undefined = undefined,
662
+ >(joinClause: {
663
+ type: `inner` | `left` | `right` | `full` | `cross`
664
+ from: TFrom
665
+ as?: TAs
666
+ on: Condition<any>
667
+ where?: Condition<any>
668
+ }): QueryBuilder<any> {
669
+ // Create a new builder with a copy of the current query
670
+ const newBuilder = new BaseQueryBuilder<TContext>()
671
+ Object.assign(newBuilder.query, this.query)
672
+
673
+ // Create a copy of the join clause for the query
674
+ const joinClauseCopy = { ...joinClause } as JoinClause<TContext>
675
+
676
+ // Add the join clause to the query
677
+ if (!newBuilder.query.join) {
678
+ newBuilder.query.join = [joinClauseCopy]
679
+ } else {
680
+ newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]
681
+ }
682
+
683
+ // Determine the alias or use the collection name as default
684
+ const _effectiveAlias = joinClause.as ?? joinClause.from
685
+
686
+ // Return the new builder with updated schema type
687
+ return newBuilder as QueryBuilder<
688
+ Flatten<
689
+ Omit<TContext, `schema`> & {
690
+ schema: TContext[`schema`] & {
691
+ [K in typeof _effectiveAlias]: TContext[`baseSchema`][TFrom]
692
+ }
693
+ }
694
+ >
695
+ >
696
+ }
697
+
698
+ /**
699
+ * Add an orderBy clause to sort the results.
700
+ * Overwrites any previous orderBy clause.
701
+ *
702
+ * @param orderBy The order specification
703
+ * @returns A new QueryBuilder with the orderBy clause set
704
+ */
705
+ orderBy(orderBy: OrderBy<TContext>): QueryBuilder<TContext> {
706
+ // Create a new builder with a copy of the current query
707
+ const newBuilder = new BaseQueryBuilder<TContext>()
708
+ Object.assign(newBuilder.query, this.query)
709
+
710
+ // Set the orderBy clause
711
+ newBuilder.query.orderBy = orderBy
712
+
713
+ // Ensure we have an orderByIndex in the select if we have an orderBy
714
+ // This is required if select is called before orderBy
715
+ newBuilder.query.select = [
716
+ ...(newBuilder.query.select ?? []),
717
+ { _orderByIndex: { ORDER_INDEX: `numeric` } },
718
+ ]
719
+
720
+ return newBuilder as QueryBuilder<TContext>
721
+ }
722
+
723
+ /**
724
+ * Set a limit on the number of results returned.
725
+ *
726
+ * @param limit Maximum number of results to return
727
+ * @returns A new QueryBuilder with the limit set
728
+ */
729
+ limit(limit: Limit<TContext>): QueryBuilder<TContext> {
730
+ // Create a new builder with a copy of the current query
731
+ const newBuilder = new BaseQueryBuilder<TContext>()
732
+ Object.assign(newBuilder.query, this.query)
733
+
734
+ // Set the limit
735
+ newBuilder.query.limit = limit
736
+
737
+ return newBuilder as QueryBuilder<TContext>
738
+ }
739
+
740
+ /**
741
+ * Set an offset to skip a number of results.
742
+ *
743
+ * @param offset Number of results to skip
744
+ * @returns A new QueryBuilder with the offset set
745
+ */
746
+ offset(offset: Offset<TContext>): QueryBuilder<TContext> {
747
+ // Create a new builder with a copy of the current query
748
+ const newBuilder = new BaseQueryBuilder<TContext>()
749
+ Object.assign(newBuilder.query, this.query)
750
+
751
+ // Set the offset
752
+ newBuilder.query.offset = offset
753
+
754
+ return newBuilder as QueryBuilder<TContext>
755
+ }
756
+
757
+ /**
758
+ * Specify which column(s) to use as keys in the output keyed stream.
759
+ *
760
+ * @param keyBy The column(s) to use as keys
761
+ * @returns A new QueryBuilder with the keyBy clause set
762
+ */
763
+ keyBy(
764
+ keyBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>
765
+ ): QueryBuilder<TContext> {
766
+ // Create a new builder with a copy of the current query
767
+ const newBuilder = new BaseQueryBuilder<TContext>()
768
+ Object.assign(newBuilder.query, this.query)
769
+
770
+ // Set the keyBy clause
771
+ newBuilder.query.keyBy = keyBy
772
+
773
+ return newBuilder as QueryBuilder<TContext>
774
+ }
775
+
776
+ /**
777
+ * Add a groupBy clause to group the results by one or more columns.
778
+ *
779
+ * @param groupBy The column(s) to group by
780
+ * @returns A new QueryBuilder with the groupBy clause set
781
+ */
782
+ groupBy(
783
+ groupBy: PropertyReference<TContext> | Array<PropertyReference<TContext>>
784
+ ): QueryBuilder<TContext> {
785
+ // Create a new builder with a copy of the current query
786
+ const newBuilder = new BaseQueryBuilder<TContext>()
787
+ Object.assign(newBuilder.query, this.query)
788
+
789
+ // Set the groupBy clause
790
+ newBuilder.query.groupBy = groupBy
791
+
792
+ return newBuilder as QueryBuilder<TContext>
793
+ }
794
+
795
+ /**
796
+ * Define a Common Table Expression (CTE) that can be referenced in the main query.
797
+ * This allows referencing the CTE by name in subsequent from/join clauses.
798
+ *
799
+ * @param name The name of the CTE
800
+ * @param queryBuilderCallback A function that builds the CTE query
801
+ * @returns A new QueryBuilder with the CTE added
802
+ */
803
+ with<TName extends string, TResult = Record<string, unknown>>(
804
+ name: TName,
805
+ queryBuilderCallback: (
806
+ builder: InitialQueryBuilder<{
807
+ baseSchema: TContext[`baseSchema`]
808
+ schema: {}
809
+ }>
810
+ ) => QueryBuilder<any>
811
+ ): InitialQueryBuilder<{
812
+ baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }
813
+ schema: TContext[`schema`]
814
+ }> {
815
+ // Create a new builder with a copy of the current query
816
+ const newBuilder = new BaseQueryBuilder<TContext>()
817
+ Object.assign(newBuilder.query, this.query)
818
+
819
+ // Create a new builder for the CTE
820
+ const cteBuilder = new BaseQueryBuilder<{
821
+ baseSchema: TContext[`baseSchema`]
822
+ schema: {}
823
+ }>()
824
+
825
+ // Get the CTE query from the callback
826
+ const cteQueryBuilder = queryBuilderCallback(
827
+ cteBuilder as InitialQueryBuilder<{
828
+ baseSchema: TContext[`baseSchema`]
829
+ schema: {}
830
+ }>
831
+ )
832
+
833
+ // Get the query from the builder
834
+ const cteQuery = cteQueryBuilder._query
835
+
836
+ // Add an 'as' property to the CTE
837
+ const withQuery: WithQuery<any> = {
838
+ ...cteQuery,
839
+ as: name,
840
+ }
841
+
842
+ // Add the CTE to the with array
843
+ if (!newBuilder.query.with) {
844
+ newBuilder.query.with = [withQuery]
845
+ } else {
846
+ newBuilder.query.with = [...newBuilder.query.with, withQuery]
847
+ }
848
+
849
+ // Use a type cast that simplifies the type structure to avoid recursion
850
+ return newBuilder as unknown as InitialQueryBuilder<{
851
+ baseSchema: TContext[`baseSchema`] & { [K in TName]: TResult }
852
+ schema: TContext[`schema`]
853
+ }>
854
+ }
855
+
856
+ get _query(): Query<TContext> {
857
+ return this.query as Query<TContext>
858
+ }
859
+ }
860
+
861
+ export type InitialQueryBuilder<TContext extends Context<Schema>> = Pick<
862
+ BaseQueryBuilder<TContext>,
863
+ `from` | `with`
864
+ >
865
+
866
+ export type QueryBuilder<TContext extends Context<Schema>> = Omit<
867
+ BaseQueryBuilder<TContext>,
868
+ `from`
869
+ >
870
+
871
+ /**
872
+ * Create a new query builder with the given schema
873
+ */
874
+ export function queryBuilder<TBaseSchema extends Schema = {}>() {
875
+ return new BaseQueryBuilder<{
876
+ baseSchema: TBaseSchema
877
+ schema: {}
878
+ }>() as InitialQueryBuilder<{
879
+ baseSchema: TBaseSchema
880
+ schema: {}
881
+ }>
882
+ }
883
+
884
+ export type ResultsFromContext<TContext extends Context<Schema>> = Flatten<
885
+ TContext[`result`] extends object
886
+ ? TContext[`result`]
887
+ : TContext[`result`] extends undefined
888
+ ? TContext[`schema`]
889
+ : object
890
+ >
891
+
892
+ export type ResultFromQueryBuilder<TQueryBuilder> = Flatten<
893
+ TQueryBuilder extends QueryBuilder<infer C>
894
+ ? C extends { result: infer R }
895
+ ? R
896
+ : never
897
+ : never
898
+ >