@tanstack/db 0.1.1 → 0.1.3

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 (38) hide show
  1. package/dist/cjs/collection.cjs.map +1 -1
  2. package/dist/cjs/collection.d.cts +9 -1
  3. package/dist/cjs/indexes/btree-index.cjs +3 -3
  4. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  5. package/dist/cjs/query/builder/index.cjs +9 -2
  6. package/dist/cjs/query/builder/index.cjs.map +1 -1
  7. package/dist/cjs/query/builder/index.d.cts +2 -2
  8. package/dist/cjs/query/builder/types.d.cts +27 -6
  9. package/dist/cjs/query/compiler/order-by.cjs +19 -21
  10. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  11. package/dist/cjs/query/ir.cjs.map +1 -1
  12. package/dist/cjs/query/ir.d.cts +2 -1
  13. package/dist/cjs/utils/comparison.cjs +29 -7
  14. package/dist/cjs/utils/comparison.cjs.map +1 -1
  15. package/dist/cjs/utils/comparison.d.cts +6 -2
  16. package/dist/esm/collection.d.ts +9 -1
  17. package/dist/esm/collection.js.map +1 -1
  18. package/dist/esm/indexes/btree-index.js +3 -3
  19. package/dist/esm/indexes/btree-index.js.map +1 -1
  20. package/dist/esm/query/builder/index.d.ts +2 -2
  21. package/dist/esm/query/builder/index.js +9 -2
  22. package/dist/esm/query/builder/index.js.map +1 -1
  23. package/dist/esm/query/builder/types.d.ts +27 -6
  24. package/dist/esm/query/compiler/order-by.js +20 -22
  25. package/dist/esm/query/compiler/order-by.js.map +1 -1
  26. package/dist/esm/query/ir.d.ts +2 -1
  27. package/dist/esm/query/ir.js.map +1 -1
  28. package/dist/esm/utils/comparison.d.ts +6 -2
  29. package/dist/esm/utils/comparison.js +30 -8
  30. package/dist/esm/utils/comparison.js.map +1 -1
  31. package/package.json +2 -2
  32. package/src/collection.ts +74 -1
  33. package/src/indexes/btree-index.ts +3 -3
  34. package/src/query/builder/index.ts +19 -2
  35. package/src/query/builder/types.ts +48 -15
  36. package/src/query/compiler/order-by.ts +22 -26
  37. package/src/query/ir.ts +2 -1
  38. package/src/utils/comparison.ts +40 -7
@@ -19,12 +19,14 @@ import type {
19
19
  QueryIR,
20
20
  } from "../ir.js"
21
21
  import type {
22
+ CompareOptions,
22
23
  Context,
23
24
  GroupByCallback,
24
25
  JoinOnCallback,
25
26
  MergeContext,
26
27
  MergeContextWithJoinType,
27
28
  OrderByCallback,
29
+ OrderByOptions,
28
30
  RefProxyForContext,
29
31
  ResultTypeFromSelect,
30
32
  SchemaFromSource,
@@ -478,16 +480,31 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
478
480
  */
479
481
  orderBy(
480
482
  callback: OrderByCallback<TContext>,
481
- direction: OrderByDirection = `asc`
483
+ options: OrderByDirection | OrderByOptions = `asc`
482
484
  ): QueryBuilder<TContext> {
483
485
  const aliases = this._getCurrentAliases()
484
486
  const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>
485
487
  const result = callback(refProxy)
486
488
 
489
+ const opts: CompareOptions =
490
+ typeof options === `string`
491
+ ? { direction: options, nulls: `first`, stringSort: `locale` }
492
+ : {
493
+ direction: options.direction ?? `asc`,
494
+ nulls: options.nulls ?? `first`,
495
+ stringSort: options.stringSort ?? `locale`,
496
+ locale:
497
+ options.stringSort === `locale` ? options.locale : undefined,
498
+ localeOptions:
499
+ options.stringSort === `locale`
500
+ ? options.localeOptions
501
+ : undefined,
502
+ }
503
+
487
504
  // Create the new OrderBy structure with expression and direction
488
505
  const orderByClause: OrderByClause = {
489
506
  expression: toExpression(result),
490
- direction,
507
+ compareOptions: opts,
491
508
  }
492
509
 
493
510
  const existingOrderBy: OrderBy = this.query.orderBy || []
@@ -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
  >
@@ -1,5 +1,5 @@
1
1
  import { orderByWithFractionalIndex } from "@tanstack/db-ivm"
2
- import { ascComparator, descComparator } from "../../utils/comparison.js"
2
+ import { defaultComparator, makeComparator } from "../../utils/comparison.js"
3
3
  import { compileExpression } from "./evaluators.js"
4
4
  import type { OrderByClause } from "../ir.js"
5
5
  import type { NamespacedAndKeyedStream, NamespacedRow } from "../../types.js"
@@ -19,7 +19,7 @@ export function processOrderBy(
19
19
  // Pre-compile all order by expressions
20
20
  const compiledOrderBy = orderByClause.map((clause) => ({
21
21
  compiledExpression: compileExpression(clause.expression),
22
- direction: clause.direction,
22
+ compareOptions: clause.compareOptions,
23
23
  }))
24
24
 
25
25
  // Create a value extractor function for the orderBy operator
@@ -53,35 +53,31 @@ export function processOrderBy(
53
53
  }
54
54
 
55
55
  // Create a multi-property comparator that respects the order and direction of each property
56
- const makeComparator = () => {
57
- return (a: unknown, b: unknown) => {
58
- // If we're comparing arrays (multiple properties), compare each property in order
59
- if (orderByClause.length > 1) {
60
- const arrayA = a as Array<unknown>
61
- const arrayB = b as Array<unknown>
62
- for (let i = 0; i < orderByClause.length; i++) {
63
- const direction = orderByClause[i]!.direction
64
- const compareFn =
65
- direction === `desc` ? descComparator : ascComparator
66
- const result = compareFn(arrayA[i], arrayB[i])
67
- if (result !== 0) {
68
- return result
69
- }
56
+ const comparator = (a: unknown, b: unknown) => {
57
+ // If we're comparing arrays (multiple properties), compare each property in order
58
+ if (orderByClause.length > 1) {
59
+ const arrayA = a as Array<unknown>
60
+ const arrayB = b as Array<unknown>
61
+ for (let i = 0; i < orderByClause.length; i++) {
62
+ const clause = orderByClause[i]!
63
+ const compareFn = makeComparator(clause.compareOptions)
64
+ const result = compareFn(arrayA[i], arrayB[i])
65
+ if (result !== 0) {
66
+ return result
70
67
  }
71
- return arrayA.length - arrayB.length
72
- }
73
-
74
- // Single property comparison
75
- if (orderByClause.length === 1) {
76
- const direction = orderByClause[0]!.direction
77
- return direction === `desc` ? descComparator(a, b) : ascComparator(a, b)
78
68
  }
69
+ return arrayA.length - arrayB.length
70
+ }
79
71
 
80
- return ascComparator(a, b)
72
+ // Single property comparison
73
+ if (orderByClause.length === 1) {
74
+ const clause = orderByClause[0]!
75
+ const compareFn = makeComparator(clause.compareOptions)
76
+ return compareFn(a, b)
81
77
  }
82
- }
83
78
 
84
- const comparator = makeComparator()
79
+ return defaultComparator(a, b)
80
+ }
85
81
 
86
82
  // Use fractional indexing and return the tuple [value, index]
87
83
  return pipeline.pipe(
package/src/query/ir.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  This is the intermediate representation of the query.
3
3
  */
4
4
 
5
+ import type { CompareOptions } from "./builder/types"
5
6
  import type { CollectionImpl } from "../collection"
6
7
  import type { NamespacedRow } from "../types"
7
8
 
@@ -48,7 +49,7 @@ export type OrderBy = Array<OrderByClause>
48
49
 
49
50
  export type OrderByClause = {
50
51
  expression: BasicExpression
51
- direction: OrderByDirection
52
+ compareOptions: CompareOptions
52
53
  }
53
54
 
54
55
  export type OrderByDirection = `asc` | `desc`
@@ -1,3 +1,5 @@
1
+ import type { CompareOptions } from "../query/builder/types"
2
+
1
3
  // WeakMap to store stable IDs for objects
2
4
  const objectIds = new WeakMap<object, number>()
3
5
  let nextObjectId = 1
@@ -19,21 +21,26 @@ function getObjectId(obj: object): number {
19
21
  * Handles null/undefined, strings, arrays, dates, objects, and primitives
20
22
  * Always sorts null/undefined values first
21
23
  */
22
- export const ascComparator = (a: any, b: any): number => {
24
+ export const ascComparator = (a: any, b: any, opts: CompareOptions): number => {
25
+ const { nulls } = opts
26
+
23
27
  // Handle null/undefined
24
28
  if (a == null && b == null) return 0
25
- if (a == null) return -1
26
- if (b == null) return 1
29
+ if (a == null) return nulls === `first` ? -1 : 1
30
+ if (b == null) return nulls === `first` ? 1 : -1
27
31
 
28
32
  // if a and b are both strings, compare them based on locale
29
33
  if (typeof a === `string` && typeof b === `string`) {
30
- return a.localeCompare(b)
34
+ if (opts.stringSort === `locale`) {
35
+ return a.localeCompare(b, opts.locale, opts.localeOptions)
36
+ }
37
+ // For lexical sort we rely on direct comparison for primitive values
31
38
  }
32
39
 
33
40
  // if a and b are both arrays, compare them element by element
34
41
  if (Array.isArray(a) && Array.isArray(b)) {
35
42
  for (let i = 0; i < Math.min(a.length, b.length); i++) {
36
- const result = ascComparator(a[i], b[i])
43
+ const result = ascComparator(a[i], b[i], opts)
37
44
  if (result !== 0) {
38
45
  return result
39
46
  }
@@ -74,6 +81,32 @@ export const ascComparator = (a: any, b: any): number => {
74
81
  * Descending comparator function for ordering values
75
82
  * Handles null/undefined as largest values (opposite of ascending)
76
83
  */
77
- export const descComparator = (a: unknown, b: unknown): number => {
78
- return ascComparator(b, a)
84
+ export const descComparator = (
85
+ a: unknown,
86
+ b: unknown,
87
+ opts: CompareOptions
88
+ ): number => {
89
+ return ascComparator(b, a, {
90
+ ...opts,
91
+ nulls: opts.nulls === `first` ? `last` : `first`,
92
+ })
93
+ }
94
+
95
+ export function makeComparator(
96
+ opts: CompareOptions
97
+ ): (a: any, b: any) => number {
98
+ return (a, b) => {
99
+ if (opts.direction === `asc`) {
100
+ return ascComparator(a, b, opts)
101
+ } else {
102
+ return descComparator(a, b, opts)
103
+ }
104
+ }
79
105
  }
106
+
107
+ /** Default comparator orders values in ascending order with nulls first and locale string comparison. */
108
+ export const defaultComparator = makeComparator({
109
+ direction: `asc`,
110
+ nulls: `first`,
111
+ stringSort: `locale`,
112
+ })