@tanstack/db 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/cjs/collection.cjs +112 -6
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +12 -3
  4. package/dist/cjs/errors.cjs +6 -0
  5. package/dist/cjs/errors.cjs.map +1 -1
  6. package/dist/cjs/errors.d.cts +3 -0
  7. package/dist/cjs/index.cjs +1 -0
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/indexes/auto-index.cjs +30 -19
  10. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  11. package/dist/cjs/indexes/auto-index.d.cts +1 -0
  12. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  13. package/dist/cjs/indexes/base-index.d.cts +2 -1
  14. package/dist/cjs/indexes/btree-index.cjs +29 -3
  15. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  16. package/dist/cjs/indexes/btree-index.d.cts +7 -0
  17. package/dist/cjs/indexes/index-options.d.cts +1 -1
  18. package/dist/cjs/query/builder/index.cjs +9 -2
  19. package/dist/cjs/query/builder/index.cjs.map +1 -1
  20. package/dist/cjs/query/builder/index.d.cts +2 -2
  21. package/dist/cjs/query/builder/types.d.cts +27 -6
  22. package/dist/cjs/query/compiler/evaluators.cjs +2 -2
  23. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  24. package/dist/cjs/query/compiler/evaluators.d.cts +1 -1
  25. package/dist/cjs/query/compiler/group-by.cjs +3 -1
  26. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  27. package/dist/cjs/query/compiler/index.cjs +72 -6
  28. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  29. package/dist/cjs/query/compiler/index.d.cts +16 -2
  30. package/dist/cjs/query/compiler/joins.cjs +111 -12
  31. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  32. package/dist/cjs/query/compiler/joins.d.cts +9 -2
  33. package/dist/cjs/query/compiler/order-by.cjs +80 -23
  34. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  35. package/dist/cjs/query/compiler/order-by.d.cts +12 -2
  36. package/dist/cjs/query/ir.cjs.map +1 -1
  37. package/dist/cjs/query/ir.d.cts +2 -1
  38. package/dist/cjs/query/live-query-collection.cjs +196 -23
  39. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  40. package/dist/cjs/types.d.cts +1 -0
  41. package/dist/cjs/utils/btree.cjs +15 -0
  42. package/dist/cjs/utils/btree.cjs.map +1 -1
  43. package/dist/cjs/utils/btree.d.cts +8 -0
  44. package/dist/cjs/utils/comparison.cjs +29 -7
  45. package/dist/cjs/utils/comparison.cjs.map +1 -1
  46. package/dist/cjs/utils/comparison.d.cts +6 -2
  47. package/dist/esm/collection.d.ts +12 -3
  48. package/dist/esm/collection.js +113 -7
  49. package/dist/esm/collection.js.map +1 -1
  50. package/dist/esm/errors.d.ts +3 -0
  51. package/dist/esm/errors.js +6 -0
  52. package/dist/esm/errors.js.map +1 -1
  53. package/dist/esm/index.js +2 -1
  54. package/dist/esm/indexes/auto-index.d.ts +1 -0
  55. package/dist/esm/indexes/auto-index.js +31 -20
  56. package/dist/esm/indexes/auto-index.js.map +1 -1
  57. package/dist/esm/indexes/base-index.d.ts +2 -1
  58. package/dist/esm/indexes/base-index.js.map +1 -1
  59. package/dist/esm/indexes/btree-index.d.ts +7 -0
  60. package/dist/esm/indexes/btree-index.js +29 -3
  61. package/dist/esm/indexes/btree-index.js.map +1 -1
  62. package/dist/esm/indexes/index-options.d.ts +1 -1
  63. package/dist/esm/query/builder/index.d.ts +2 -2
  64. package/dist/esm/query/builder/index.js +9 -2
  65. package/dist/esm/query/builder/index.js.map +1 -1
  66. package/dist/esm/query/builder/types.d.ts +27 -6
  67. package/dist/esm/query/compiler/evaluators.d.ts +1 -1
  68. package/dist/esm/query/compiler/evaluators.js +2 -2
  69. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  70. package/dist/esm/query/compiler/group-by.js +3 -1
  71. package/dist/esm/query/compiler/group-by.js.map +1 -1
  72. package/dist/esm/query/compiler/index.d.ts +16 -2
  73. package/dist/esm/query/compiler/index.js +73 -7
  74. package/dist/esm/query/compiler/index.js.map +1 -1
  75. package/dist/esm/query/compiler/joins.d.ts +9 -2
  76. package/dist/esm/query/compiler/joins.js +114 -15
  77. package/dist/esm/query/compiler/joins.js.map +1 -1
  78. package/dist/esm/query/compiler/order-by.d.ts +12 -2
  79. package/dist/esm/query/compiler/order-by.js +81 -24
  80. package/dist/esm/query/compiler/order-by.js.map +1 -1
  81. package/dist/esm/query/ir.d.ts +2 -1
  82. package/dist/esm/query/ir.js.map +1 -1
  83. package/dist/esm/query/live-query-collection.js +196 -23
  84. package/dist/esm/query/live-query-collection.js.map +1 -1
  85. package/dist/esm/types.d.ts +1 -0
  86. package/dist/esm/utils/btree.d.ts +8 -0
  87. package/dist/esm/utils/btree.js +15 -0
  88. package/dist/esm/utils/btree.js.map +1 -1
  89. package/dist/esm/utils/comparison.d.ts +6 -2
  90. package/dist/esm/utils/comparison.js +30 -8
  91. package/dist/esm/utils/comparison.js.map +1 -1
  92. package/package.json +2 -2
  93. package/src/collection.ts +237 -11
  94. package/src/errors.ts +6 -0
  95. package/src/indexes/auto-index.ts +53 -31
  96. package/src/indexes/base-index.ts +6 -1
  97. package/src/indexes/btree-index.ts +32 -3
  98. package/src/indexes/index-options.ts +2 -2
  99. package/src/query/builder/index.ts +19 -2
  100. package/src/query/builder/types.ts +48 -15
  101. package/src/query/compiler/evaluators.ts +6 -3
  102. package/src/query/compiler/group-by.ts +3 -1
  103. package/src/query/compiler/index.ts +112 -5
  104. package/src/query/compiler/joins.ts +216 -20
  105. package/src/query/compiler/order-by.ts +117 -26
  106. package/src/query/ir.ts +2 -1
  107. package/src/query/live-query-collection.ts +352 -24
  108. package/src/types.ts +1 -0
  109. package/src/utils/btree.ts +17 -0
  110. package/src/utils/comparison.ts +40 -7
@@ -1,6 +1,7 @@
1
1
  import type { CollectionImpl } from "../../collection.js"
2
- import type { Aggregate, BasicExpression } from "../ir.js"
2
+ import type { Aggregate, BasicExpression, OrderByDirection } from "../ir.js"
3
3
  import type { QueryBuilder } from "./index.js"
4
+ import type { ResolveType } from "../../types.js"
4
5
 
5
6
  export interface Context {
6
7
  // The collections available in the base schema
@@ -27,13 +28,16 @@ export type Source = {
27
28
  }
28
29
 
29
30
  // Helper type to infer collection type from CollectionImpl
31
+ // This uses ResolveType directly to ensure consistency with collection creation logic
30
32
  export type InferCollectionType<T> =
31
- T extends CollectionImpl<infer U> ? U : never
33
+ T extends CollectionImpl<infer U, any, any, infer TSchema, any>
34
+ ? ResolveType<U, TSchema, U>
35
+ : never
32
36
 
33
37
  // Helper type to create schema from source
34
38
  export type SchemaFromSource<T extends Source> = Prettify<{
35
- [K in keyof T]: T[K] extends CollectionImpl<infer U>
36
- ? U
39
+ [K in keyof T]: T[K] extends CollectionImpl<any, any, any, any, any>
40
+ ? InferCollectionType<T[K]>
37
41
  : T[K] extends QueryBuilder<infer TContext>
38
42
  ? GetResult<TContext>
39
43
  : never
@@ -58,16 +62,18 @@ export type SelectObject<
58
62
  // Helper type to get the result type from a select object
59
63
  export type ResultTypeFromSelect<TSelectObject> = {
60
64
  [K in keyof TSelectObject]: TSelectObject[K] extends RefProxy<infer T>
61
- ? // For RefProxy, preserve the type as-is (including optionality from joins)
62
- T
65
+ ? T
63
66
  : TSelectObject[K] extends BasicExpression<infer T>
64
67
  ? T
65
68
  : TSelectObject[K] extends Aggregate<infer T>
66
69
  ? T
67
70
  : TSelectObject[K] extends RefProxyFor<infer T>
68
- ? // For RefProxyFor, preserve the type as-is (including optionality from joins)
69
- T
70
- : never
71
+ ? T
72
+ : TSelectObject[K] extends undefined
73
+ ? undefined
74
+ : TSelectObject[K] extends { __type: infer U }
75
+ ? U
76
+ : never
71
77
  }
72
78
 
73
79
  // Callback type for orderBy clauses
@@ -75,6 +81,29 @@ export type OrderByCallback<TContext extends Context> = (
75
81
  refs: RefProxyForContext<TContext>
76
82
  ) => any
77
83
 
84
+ export type OrderByOptions = {
85
+ direction?: OrderByDirection
86
+ nulls?: `first` | `last`
87
+ } & StringSortOpts
88
+
89
+ export type StringSortOpts =
90
+ | {
91
+ stringSort?: `lexical`
92
+ }
93
+ | {
94
+ stringSort?: `locale`
95
+ locale?: string
96
+ localeOptions?: object
97
+ }
98
+
99
+ export type CompareOptions = {
100
+ direction: OrderByDirection
101
+ nulls: `first` | `last`
102
+ stringSort: `lexical` | `locale`
103
+ locale?: string
104
+ localeOptions?: object
105
+ }
106
+
78
107
  // Callback type for groupBy clauses
79
108
  export type GroupByCallback<TContext extends Context> = (
80
109
  refs: RefProxyForContext<TContext>
@@ -119,12 +148,11 @@ export type RefProxyFor<T> = OmitRefProxy<
119
148
  ? // T is optional (T | undefined) but not exactly undefined
120
149
  NonUndefined<T> extends Record<string, any>
121
150
  ? {
122
- // Properties are accessible and their types become optional
123
- [K in keyof NonUndefined<T>]: NonUndefined<T>[K] extends Record<
151
+ [K in keyof NonUndefined<T>]-?: NonUndefined<T>[K] extends Record<
124
152
  string,
125
153
  any
126
154
  >
127
- ? RefProxyFor<NonUndefined<T>[K] | undefined> &
155
+ ? RefProxyFor<NonUndefined<T>[K]> &
128
156
  RefProxy<NonUndefined<T>[K] | undefined>
129
157
  : RefProxy<NonUndefined<T>[K] | undefined>
130
158
  } & RefProxy<T>
@@ -132,9 +160,14 @@ export type RefProxyFor<T> = OmitRefProxy<
132
160
  : // T is not optional
133
161
  T extends Record<string, any>
134
162
  ? {
135
- [K in keyof T]: T[K] extends Record<string, any>
136
- ? RefProxyFor<T[K]> & RefProxy<T[K]>
137
- : RefProxy<T[K]>
163
+ // Make all properties required, but for optional ones, include undefined in the RefProxy type
164
+ [K in keyof T]-?: undefined extends T[K]
165
+ ? T[K] extends Record<string, any>
166
+ ? RefProxyFor<T[K]> & RefProxy<T[K]>
167
+ : RefProxy<T[K]>
168
+ : T[K] extends Record<string, any>
169
+ ? RefProxyFor<T[K]> & RefProxy<T[K]>
170
+ : RefProxy<T[K]>
138
171
  } & RefProxy<T>
139
172
  : RefProxy<T>
140
173
  >
@@ -20,9 +20,12 @@ export type CompiledSingleRowExpression = (item: Record<string, unknown>) => any
20
20
  * Compiles an expression into an optimized evaluator function.
21
21
  * This eliminates branching during evaluation by pre-compiling the expression structure.
22
22
  */
23
- export function compileExpression(expr: BasicExpression): CompiledExpression {
24
- const compiledFn = compileExpressionInternal(expr, false)
25
- return compiledFn as CompiledExpression
23
+ export function compileExpression(
24
+ expr: BasicExpression,
25
+ isSingleRow: boolean = false
26
+ ): CompiledExpression | CompiledSingleRowExpression {
27
+ const compiledFn = compileExpressionInternal(expr, isSingleRow)
28
+ return compiledFn
26
29
  }
27
30
 
28
31
  /**
@@ -166,7 +166,9 @@ export function processGroupBy(
166
166
  const mapping = validateAndCreateMapping(groupByClause, selectClause)
167
167
 
168
168
  // Pre-compile groupBy expressions
169
- const compiledGroupByExpressions = groupByClause.map(compileExpression)
169
+ const compiledGroupByExpressions = groupByClause.map((e) =>
170
+ compileExpression(e)
171
+ )
170
172
 
171
173
  // Create a key extractor function using simple __key_X format
172
174
  const keyExtractor = ([, row]: [
@@ -7,17 +7,21 @@ import {
7
7
  LimitOffsetRequireOrderByError,
8
8
  UnsupportedFromTypeError,
9
9
  } from "../../errors.js"
10
+ import { PropRef } from "../ir.js"
10
11
  import { compileExpression } from "./evaluators.js"
11
12
  import { processJoins } from "./joins.js"
12
13
  import { processGroupBy } from "./group-by.js"
13
14
  import { processOrderBy } from "./order-by.js"
14
15
  import { processSelectToResults } from "./select.js"
16
+ import type { OrderByOptimizationInfo } from "./order-by.js"
15
17
  import type {
16
18
  BasicExpression,
17
19
  CollectionRef,
18
20
  QueryIR,
19
21
  QueryRef,
20
22
  } from "../ir.js"
23
+ import type { LazyCollectionCallbacks } from "./joins.js"
24
+ import type { Collection } from "../../collection.js"
21
25
  import type {
22
26
  KeyedStream,
23
27
  NamespacedAndKeyedStream,
@@ -29,6 +33,8 @@ import type { QueryCache, QueryMapping } from "./types.js"
29
33
  * Result of query compilation including both the pipeline and collection-specific WHERE clauses
30
34
  */
31
35
  export interface CompilationResult {
36
+ /** The ID of the main collection */
37
+ collectionId: string
32
38
  /** The compiled query pipeline */
33
39
  pipeline: ResultStream
34
40
  /** Map of collection aliases to their WHERE clauses for index optimization */
@@ -46,6 +52,10 @@ export interface CompilationResult {
46
52
  export function compileQuery(
47
53
  rawQuery: QueryIR,
48
54
  inputs: Record<string, KeyedStream>,
55
+ collections: Record<string, Collection<any, any, any, any, any>>,
56
+ callbacks: Record<string, LazyCollectionCallbacks>,
57
+ lazyCollections: Set<string>,
58
+ optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
49
59
  cache: QueryCache = new WeakMap(),
50
60
  queryMapping: QueryMapping = new WeakMap()
51
61
  ): CompilationResult {
@@ -70,9 +80,17 @@ export function compileQuery(
70
80
  const tables: Record<string, KeyedStream> = {}
71
81
 
72
82
  // Process the FROM clause to get the main table
73
- const { alias: mainTableAlias, input: mainInput } = processFrom(
83
+ const {
84
+ alias: mainTableAlias,
85
+ input: mainInput,
86
+ collectionId: mainCollectionId,
87
+ } = processFrom(
74
88
  query.from,
75
89
  allInputs,
90
+ collections,
91
+ callbacks,
92
+ lazyCollections,
93
+ optimizableOrderByCollections,
76
94
  cache,
77
95
  queryMapping
78
96
  )
@@ -96,10 +114,16 @@ export function compileQuery(
96
114
  pipeline,
97
115
  query.join,
98
116
  tables,
117
+ mainCollectionId,
99
118
  mainTableAlias,
100
119
  allInputs,
101
120
  cache,
102
- queryMapping
121
+ queryMapping,
122
+ collections,
123
+ callbacks,
124
+ lazyCollections,
125
+ optimizableOrderByCollections,
126
+ rawQuery
103
127
  )
104
128
  }
105
129
 
@@ -231,8 +255,11 @@ export function compileQuery(
231
255
  // Process orderBy parameter if it exists
232
256
  if (query.orderBy && query.orderBy.length > 0) {
233
257
  const orderedPipeline = processOrderBy(
258
+ rawQuery,
234
259
  pipeline,
235
260
  query.orderBy,
261
+ collections[mainCollectionId]!,
262
+ optimizableOrderByCollections,
236
263
  query.limit,
237
264
  query.offset
238
265
  )
@@ -249,6 +276,7 @@ export function compileQuery(
249
276
  const result = resultPipeline
250
277
  // Cache the result before returning (use original query as key)
251
278
  const compilationResult = {
279
+ collectionId: mainCollectionId,
252
280
  pipeline: result,
253
281
  collectionWhereClauses,
254
282
  }
@@ -275,6 +303,7 @@ export function compileQuery(
275
303
  const result = resultPipeline
276
304
  // Cache the result before returning (use original query as key)
277
305
  const compilationResult = {
306
+ collectionId: mainCollectionId,
278
307
  pipeline: result,
279
308
  collectionWhereClauses,
280
309
  }
@@ -289,16 +318,20 @@ export function compileQuery(
289
318
  function processFrom(
290
319
  from: CollectionRef | QueryRef,
291
320
  allInputs: Record<string, KeyedStream>,
321
+ collections: Record<string, Collection>,
322
+ callbacks: Record<string, LazyCollectionCallbacks>,
323
+ lazyCollections: Set<string>,
324
+ optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
292
325
  cache: QueryCache,
293
326
  queryMapping: QueryMapping
294
- ): { alias: string; input: KeyedStream } {
327
+ ): { alias: string; input: KeyedStream; collectionId: string } {
295
328
  switch (from.type) {
296
329
  case `collectionRef`: {
297
330
  const input = allInputs[from.collection.id]
298
331
  if (!input) {
299
332
  throw new CollectionInputNotFoundError(from.collection.id)
300
333
  }
301
- return { alias: from.alias, input }
334
+ return { alias: from.alias, input, collectionId: from.collection.id }
302
335
  }
303
336
  case `queryRef`: {
304
337
  // Find the original query for caching purposes
@@ -308,6 +341,10 @@ function processFrom(
308
341
  const subQueryResult = compileQuery(
309
342
  originalQuery,
310
343
  allInputs,
344
+ collections,
345
+ callbacks,
346
+ lazyCollections,
347
+ optimizableOrderByCollections,
311
348
  cache,
312
349
  queryMapping
313
350
  )
@@ -324,7 +361,11 @@ function processFrom(
324
361
  })
325
362
  )
326
363
 
327
- return { alias: from.alias, input: extractedInput }
364
+ return {
365
+ alias: from.alias,
366
+ input: extractedInput,
367
+ collectionId: subQueryResult.collectionId,
368
+ }
328
369
  }
329
370
  default:
330
371
  throw new UnsupportedFromTypeError((from as any).type)
@@ -380,3 +421,69 @@ function mapNestedQueries(
380
421
  }
381
422
  }
382
423
  }
424
+
425
+ function getRefFromAlias(
426
+ query: QueryIR,
427
+ alias: string
428
+ ): CollectionRef | QueryRef | void {
429
+ if (query.from.alias === alias) {
430
+ return query.from
431
+ }
432
+
433
+ for (const join of query.join || []) {
434
+ if (join.from.alias === alias) {
435
+ return join.from
436
+ }
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Follows the given reference in a query
442
+ * until its finds the root field the reference points to.
443
+ * @returns The collection, its alias, and the path to the root field in this collection
444
+ */
445
+ export function followRef(
446
+ query: QueryIR,
447
+ ref: PropRef<any>,
448
+ collection: Collection
449
+ ): { collection: Collection; path: Array<string> } | void {
450
+ if (ref.path.length === 0) {
451
+ return
452
+ }
453
+
454
+ if (ref.path.length === 1) {
455
+ // This field should be part of this collection
456
+ const field = ref.path[0]!
457
+ // is it part of the select clause?
458
+ if (query.select) {
459
+ const selectedField = query.select[field]
460
+ if (selectedField && selectedField.type === `ref`) {
461
+ return followRef(query, selectedField, collection)
462
+ }
463
+ }
464
+
465
+ // Either this field is not part of the select clause
466
+ // and thus it must be part of the collection itself
467
+ // or it is part of the select but is not a reference
468
+ // so we can stop here and don't have to follow it
469
+ return { collection, path: [field] }
470
+ }
471
+
472
+ if (ref.path.length > 1) {
473
+ // This is a nested field
474
+ const [alias, ...rest] = ref.path
475
+ const aliasRef = getRefFromAlias(query, alias!)
476
+ if (!aliasRef) {
477
+ return
478
+ }
479
+
480
+ if (aliasRef.type === `queryRef`) {
481
+ return followRef(aliasRef.query, new PropRef(rest), collection)
482
+ } else {
483
+ // This is a reference to a collection
484
+ // we can't follow it further
485
+ // so the field must be on the collection itself
486
+ return { collection: aliasRef.collection, path: rest }
487
+ }
488
+ }
489
+ }