@tanstack/db 0.1.11 → 0.2.0

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 (74) hide show
  1. package/dist/cjs/errors.cjs +18 -6
  2. package/dist/cjs/errors.cjs.map +1 -1
  3. package/dist/cjs/errors.d.cts +9 -3
  4. package/dist/cjs/index.cjs +3 -1
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/query/builder/functions.cjs +4 -1
  7. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  8. package/dist/cjs/query/builder/functions.d.cts +38 -21
  9. package/dist/cjs/query/builder/index.cjs +25 -16
  10. package/dist/cjs/query/builder/index.cjs.map +1 -1
  11. package/dist/cjs/query/builder/index.d.cts +8 -8
  12. package/dist/cjs/query/builder/ref-proxy.cjs +12 -8
  13. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  14. package/dist/cjs/query/builder/ref-proxy.d.cts +2 -1
  15. package/dist/cjs/query/builder/types.d.cts +493 -28
  16. package/dist/cjs/query/compiler/evaluators.cjs +29 -0
  17. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  18. package/dist/cjs/query/compiler/group-by.cjs +4 -2
  19. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  20. package/dist/cjs/query/compiler/index.cjs +13 -4
  21. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  22. package/dist/cjs/query/compiler/joins.cjs +70 -61
  23. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  24. package/dist/cjs/query/compiler/select.cjs +131 -42
  25. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  26. package/dist/cjs/query/compiler/select.d.cts +1 -5
  27. package/dist/cjs/query/ir.cjs +4 -0
  28. package/dist/cjs/query/ir.cjs.map +1 -1
  29. package/dist/cjs/query/ir.d.cts +6 -1
  30. package/dist/cjs/query/optimizer.cjs +61 -20
  31. package/dist/cjs/query/optimizer.cjs.map +1 -1
  32. package/dist/esm/errors.d.ts +9 -3
  33. package/dist/esm/errors.js +18 -6
  34. package/dist/esm/errors.js.map +1 -1
  35. package/dist/esm/index.js +4 -2
  36. package/dist/esm/query/builder/functions.d.ts +38 -21
  37. package/dist/esm/query/builder/functions.js +4 -1
  38. package/dist/esm/query/builder/functions.js.map +1 -1
  39. package/dist/esm/query/builder/index.d.ts +8 -8
  40. package/dist/esm/query/builder/index.js +27 -18
  41. package/dist/esm/query/builder/index.js.map +1 -1
  42. package/dist/esm/query/builder/ref-proxy.d.ts +2 -1
  43. package/dist/esm/query/builder/ref-proxy.js +12 -8
  44. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  45. package/dist/esm/query/builder/types.d.ts +493 -28
  46. package/dist/esm/query/compiler/evaluators.js +29 -0
  47. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  48. package/dist/esm/query/compiler/group-by.js +4 -2
  49. package/dist/esm/query/compiler/group-by.js.map +1 -1
  50. package/dist/esm/query/compiler/index.js +15 -6
  51. package/dist/esm/query/compiler/index.js.map +1 -1
  52. package/dist/esm/query/compiler/joins.js +71 -62
  53. package/dist/esm/query/compiler/joins.js.map +1 -1
  54. package/dist/esm/query/compiler/select.d.ts +1 -5
  55. package/dist/esm/query/compiler/select.js +131 -42
  56. package/dist/esm/query/compiler/select.js.map +1 -1
  57. package/dist/esm/query/ir.d.ts +6 -1
  58. package/dist/esm/query/ir.js +4 -0
  59. package/dist/esm/query/ir.js.map +1 -1
  60. package/dist/esm/query/optimizer.js +62 -21
  61. package/dist/esm/query/optimizer.js.map +1 -1
  62. package/package.json +2 -2
  63. package/src/errors.ts +17 -10
  64. package/src/query/builder/functions.ts +176 -108
  65. package/src/query/builder/index.ts +68 -48
  66. package/src/query/builder/ref-proxy.ts +14 -20
  67. package/src/query/builder/types.ts +622 -110
  68. package/src/query/compiler/evaluators.ts +30 -0
  69. package/src/query/compiler/group-by.ts +6 -1
  70. package/src/query/compiler/index.ts +23 -6
  71. package/src/query/compiler/joins.ts +132 -104
  72. package/src/query/compiler/select.ts +206 -113
  73. package/src/query/ir.ts +14 -1
  74. package/src/query/optimizer.ts +131 -59
@@ -1,5 +1,13 @@
1
1
  import { CollectionImpl } from "../../collection.js"
2
- import { CollectionRef, QueryRef } from "../ir.js"
2
+ import {
3
+ Aggregate as AggregateExpr,
4
+ CollectionRef,
5
+ Func as FuncExpr,
6
+ PropRef,
7
+ QueryRef,
8
+ Value as ValueExpr,
9
+ isExpressionLike,
10
+ } from "../ir.js"
3
11
  import {
4
12
  InvalidSourceError,
5
13
  JoinConditionMustBeEqualityError,
@@ -7,7 +15,7 @@ import {
7
15
  QueryMustHaveFromClauseError,
8
16
  SubQueryMustHaveFromClauseError,
9
17
  } from "../../errors.js"
10
- import { createRefProxy, isRefProxy, toExpression } from "./ref-proxy.js"
18
+ import { createRefProxy, toExpression } from "./ref-proxy.js"
11
19
  import type { NamespacedRow } from "../../types.js"
12
20
  import type {
13
21
  Aggregate,
@@ -22,11 +30,11 @@ import type {
22
30
  Context,
23
31
  GroupByCallback,
24
32
  JoinOnCallback,
25
- MergeContext,
33
+ MergeContextForJoinCallback,
26
34
  MergeContextWithJoinType,
27
35
  OrderByCallback,
28
36
  OrderByOptions,
29
- RefProxyForContext,
37
+ RefsForContext,
30
38
  ResultTypeFromSelect,
31
39
  SchemaFromSource,
32
40
  SelectObject,
@@ -141,7 +149,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
141
149
  >(
142
150
  source: TSource,
143
151
  onCallback: JoinOnCallback<
144
- MergeContext<TContext, SchemaFromSource<TSource>>
152
+ MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>
145
153
  >,
146
154
  type: TJoinType = `left` as TJoinType
147
155
  ): QueryBuilder<
@@ -152,8 +160,8 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
152
160
  // Create a temporary context for the callback
153
161
  const currentAliases = this._getCurrentAliases()
154
162
  const newAliases = [...currentAliases, alias]
155
- const refProxy = createRefProxy(newAliases) as RefProxyForContext<
156
- MergeContext<TContext, SchemaFromSource<TSource>>
163
+ const refProxy = createRefProxy(newAliases) as RefsForContext<
164
+ MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>
157
165
  >
158
166
 
159
167
  // Get the join condition expression
@@ -208,7 +216,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
208
216
  leftJoin<TSource extends Source>(
209
217
  source: TSource,
210
218
  onCallback: JoinOnCallback<
211
- MergeContext<TContext, SchemaFromSource<TSource>>
219
+ MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>
212
220
  >
213
221
  ): QueryBuilder<
214
222
  MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `left`>
@@ -234,7 +242,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
234
242
  rightJoin<TSource extends Source>(
235
243
  source: TSource,
236
244
  onCallback: JoinOnCallback<
237
- MergeContext<TContext, SchemaFromSource<TSource>>
245
+ MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>
238
246
  >
239
247
  ): QueryBuilder<
240
248
  MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `right`>
@@ -260,7 +268,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
260
268
  innerJoin<TSource extends Source>(
261
269
  source: TSource,
262
270
  onCallback: JoinOnCallback<
263
- MergeContext<TContext, SchemaFromSource<TSource>>
271
+ MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>
264
272
  >
265
273
  ): QueryBuilder<
266
274
  MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `inner`>
@@ -286,7 +294,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
286
294
  fullJoin<TSource extends Source>(
287
295
  source: TSource,
288
296
  onCallback: JoinOnCallback<
289
- MergeContext<TContext, SchemaFromSource<TSource>>
297
+ MergeContextForJoinCallback<TContext, SchemaFromSource<TSource>>
290
298
  >
291
299
  ): QueryBuilder<
292
300
  MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `full`>
@@ -324,7 +332,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
324
332
  */
325
333
  where(callback: WhereCallback<TContext>): QueryBuilder<TContext> {
326
334
  const aliases = this._getCurrentAliases()
327
- const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>
335
+ const refProxy = createRefProxy(aliases) as RefsForContext<TContext>
328
336
  const expression = callback(refProxy)
329
337
 
330
338
  const existingWhere = this.query.where || []
@@ -365,7 +373,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
365
373
  */
366
374
  having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {
367
375
  const aliases = this._getCurrentAliases()
368
- const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>
376
+ const refProxy = createRefProxy(aliases) as RefsForContext<TContext>
369
377
  const expression = callback(refProxy)
370
378
 
371
379
  const existingHaving = this.query.having || []
@@ -411,42 +419,16 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
411
419
  * ```
412
420
  */
413
421
  select<TSelectObject extends SelectObject>(
414
- callback: (refs: RefProxyForContext<TContext>) => TSelectObject
422
+ callback: (refs: RefsForContext<TContext>) => TSelectObject
415
423
  ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {
416
424
  const aliases = this._getCurrentAliases()
417
- const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>
425
+ const refProxy = createRefProxy(aliases) as RefsForContext<TContext>
418
426
  const selectObject = callback(refProxy)
419
-
420
- // Check if any tables were spread during the callback
421
- const spreadSentinels = (refProxy as any).__spreadSentinels as Set<string>
422
-
423
- // Convert the select object to use expressions, including spread sentinels
424
- const select: Record<string, BasicExpression | Aggregate> = {}
425
-
426
- // First, add spread sentinels for any tables that were spread
427
- for (const spreadAlias of spreadSentinels) {
428
- const sentinelKey = `__SPREAD_SENTINEL__${spreadAlias}`
429
- select[sentinelKey] = toExpression(spreadAlias) // Use alias as a simple reference
430
- }
431
-
432
- // Then add the explicit select fields
433
- for (const [key, value] of Object.entries(selectObject)) {
434
- if (isRefProxy(value)) {
435
- select[key] = toExpression(value)
436
- } else if (
437
- typeof value === `object` &&
438
- `type` in value &&
439
- (value.type === `agg` || value.type === `func`)
440
- ) {
441
- select[key] = value as BasicExpression | Aggregate
442
- } else {
443
- select[key] = toExpression(value)
444
- }
445
- }
427
+ const select = buildNestedSelect(selectObject)
446
428
 
447
429
  return new BaseQueryBuilder({
448
430
  ...this.query,
449
- select,
431
+ select: select,
450
432
  fnSelect: undefined, // remove the fnSelect clause if it exists
451
433
  }) as any
452
434
  }
@@ -482,7 +464,7 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
482
464
  options: OrderByDirection | OrderByOptions = `asc`
483
465
  ): QueryBuilder<TContext> {
484
466
  const aliases = this._getCurrentAliases()
485
- const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>
467
+ const refProxy = createRefProxy(aliases) as RefsForContext<TContext>
486
468
  const result = callback(refProxy)
487
469
 
488
470
  const opts: CompareOptions =
@@ -550,17 +532,18 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
550
532
  */
551
533
  groupBy(callback: GroupByCallback<TContext>): QueryBuilder<TContext> {
552
534
  const aliases = this._getCurrentAliases()
553
- const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>
535
+ const refProxy = createRefProxy(aliases) as RefsForContext<TContext>
554
536
  const result = callback(refProxy)
555
537
 
556
538
  const newExpressions = Array.isArray(result)
557
539
  ? result.map((r) => toExpression(r))
558
540
  : [toExpression(result)]
559
541
 
560
- // Replace existing groupBy expressions instead of extending them
542
+ // Extend existing groupBy expressions (multiple groupBy calls should accumulate)
543
+ const existingGroupBy = this.query.groupBy || []
561
544
  return new BaseQueryBuilder({
562
545
  ...this.query,
563
- groupBy: newExpressions,
546
+ groupBy: [...existingGroupBy, ...newExpressions],
564
547
  }) as any
565
548
  }
566
549
 
@@ -758,6 +741,43 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
758
741
  }
759
742
  }
760
743
 
744
+ // Helper to ensure we have a BasicExpression/Aggregate for a value
745
+ function toExpr(value: any): BasicExpression | Aggregate {
746
+ if (value === undefined) return toExpression(null)
747
+ if (
748
+ value instanceof AggregateExpr ||
749
+ value instanceof FuncExpr ||
750
+ value instanceof PropRef ||
751
+ value instanceof ValueExpr
752
+ ) {
753
+ return value as BasicExpression | Aggregate
754
+ }
755
+ return toExpression(value)
756
+ }
757
+
758
+ function isPlainObject(value: any): value is Record<string, any> {
759
+ return (
760
+ value !== null &&
761
+ typeof value === `object` &&
762
+ !isExpressionLike(value) &&
763
+ !value.__refProxy
764
+ )
765
+ }
766
+
767
+ function buildNestedSelect(obj: any): any {
768
+ if (!isPlainObject(obj)) return toExpr(obj)
769
+ const out: Record<string, any> = {}
770
+ for (const [k, v] of Object.entries(obj)) {
771
+ if (typeof k === `string` && k.startsWith(`__SPREAD_SENTINEL__`)) {
772
+ // Preserve sentinel key and its value (value is unimportant at compile time)
773
+ out[k] = v
774
+ continue
775
+ }
776
+ out[k] = buildNestedSelect(v)
777
+ }
778
+ return out
779
+ }
780
+
761
781
  // Internal function to build a query from a callback
762
782
  // used by liveQueryCollectionOptions.query
763
783
  export function buildQuery<TContext extends Context>(
@@ -797,4 +817,4 @@ export type ExtractContext<T> =
797
817
  : never
798
818
 
799
819
  // Export the types from types.ts for convenience
800
- export type { Context, Source, GetResult } from "./types.js"
820
+ export type { Context, Source, GetResult, RefLeaf as Ref } from "./types.js"
@@ -1,5 +1,6 @@
1
1
  import { PropRef, Value } from "../ir.js"
2
2
  import type { BasicExpression } from "../ir.js"
3
+ import type { RefLeaf } from "./types.js"
3
4
 
4
5
  export interface RefProxy<T = any> {
5
6
  /** @internal */
@@ -19,7 +20,7 @@ export type SingleRowRefProxy<T> =
19
20
  ? {
20
21
  [K in keyof T]: T[K] extends Record<string, any>
21
22
  ? SingleRowRefProxy<T[K]> & RefProxy<T[K]>
22
- : RefProxy<T[K]>
23
+ : RefLeaf<T[K]>
23
24
  } & RefProxy<T>
24
25
  : RefProxy<T>
25
26
 
@@ -83,7 +84,7 @@ export function createRefProxy<T extends Record<string, any>>(
83
84
  aliases: Array<string>
84
85
  ): RefProxy<T> & T {
85
86
  const cache = new Map<string, any>()
86
- const spreadSentinels = new Set<string>() // Track which aliases have been spread
87
+ let accessId = 0 // Monotonic counter to record evaluation order
87
88
 
88
89
  function createProxy(path: Array<string>): any {
89
90
  const pathKey = path.join(`.`)
@@ -109,10 +110,14 @@ export function createRefProxy<T extends Record<string, any>>(
109
110
  },
110
111
 
111
112
  ownKeys(target) {
112
- // If this is a table-level proxy (path length 1), mark it as spread
113
- if (path.length === 1) {
114
- const aliasName = path[0]!
115
- spreadSentinels.add(aliasName)
113
+ const id = ++accessId
114
+ const sentinelKey = `__SPREAD_SENTINEL__${path.join(`.`)}__${id}`
115
+ if (!Object.prototype.hasOwnProperty.call(target, sentinelKey)) {
116
+ Object.defineProperty(target, sentinelKey, {
117
+ enumerable: true,
118
+ configurable: true,
119
+ value: true,
120
+ })
116
121
  }
117
122
  return Reflect.ownKeys(target)
118
123
  },
@@ -135,7 +140,6 @@ export function createRefProxy<T extends Record<string, any>>(
135
140
  if (prop === `__refProxy`) return true
136
141
  if (prop === `__path`) return []
137
142
  if (prop === `__type`) return undefined // Type is only for TypeScript inference
138
- if (prop === `__spreadSentinels`) return spreadSentinels // Expose spread sentinels
139
143
  if (typeof prop === `symbol`) return Reflect.get(target, prop, receiver)
140
144
 
141
145
  const propStr = String(prop)
@@ -147,28 +151,18 @@ export function createRefProxy<T extends Record<string, any>>(
147
151
  },
148
152
 
149
153
  has(target, prop) {
150
- if (
151
- prop === `__refProxy` ||
152
- prop === `__path` ||
153
- prop === `__type` ||
154
- prop === `__spreadSentinels`
155
- )
154
+ if (prop === `__refProxy` || prop === `__path` || prop === `__type`)
156
155
  return true
157
156
  if (typeof prop === `string` && aliases.includes(prop)) return true
158
157
  return Reflect.has(target, prop)
159
158
  },
160
159
 
161
160
  ownKeys(_target) {
162
- return [...aliases, `__refProxy`, `__path`, `__type`, `__spreadSentinels`]
161
+ return [...aliases, `__refProxy`, `__path`, `__type`]
163
162
  },
164
163
 
165
164
  getOwnPropertyDescriptor(target, prop) {
166
- if (
167
- prop === `__refProxy` ||
168
- prop === `__path` ||
169
- prop === `__type` ||
170
- prop === `__spreadSentinels`
171
- ) {
165
+ if (prop === `__refProxy` || prop === `__path` || prop === `__type`) {
172
166
  return { enumerable: false, configurable: true }
173
167
  }
174
168
  if (typeof prop === `string` && aliases.includes(prop)) {