@tanstack/db 0.4.20 → 0.5.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 (127) hide show
  1. package/dist/cjs/collection/change-events.cjs +10 -12
  2. package/dist/cjs/collection/change-events.cjs.map +1 -1
  3. package/dist/cjs/collection/change-events.d.cts +1 -8
  4. package/dist/cjs/collection/index.cjs +18 -0
  5. package/dist/cjs/collection/index.cjs.map +1 -1
  6. package/dist/cjs/collection/index.d.cts +7 -5
  7. package/dist/cjs/index.cjs +21 -3
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/index.d.cts +2 -0
  10. package/dist/cjs/indexes/auto-index.cjs +7 -3
  11. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  12. package/dist/cjs/local-storage.cjs.map +1 -1
  13. package/dist/cjs/local-storage.d.cts +2 -2
  14. package/dist/cjs/query/builder/functions.cjs +34 -0
  15. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  16. package/dist/cjs/query/builder/functions.d.cts +5 -0
  17. package/dist/cjs/query/builder/index.cjs +2 -2
  18. package/dist/cjs/query/builder/index.cjs.map +1 -1
  19. package/dist/cjs/query/builder/types.d.cts +3 -22
  20. package/dist/cjs/query/compiler/evaluators.cjs +57 -4
  21. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  22. package/dist/cjs/query/compiler/evaluators.d.cts +13 -0
  23. package/dist/cjs/query/compiler/expressions.cjs +4 -1
  24. package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
  25. package/dist/cjs/query/compiler/group-by.cjs +3 -3
  26. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  27. package/dist/cjs/query/compiler/index.cjs +2 -2
  28. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  29. package/dist/cjs/query/compiler/order-by.cjs +18 -6
  30. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  31. package/dist/cjs/query/compiler/order-by.d.cts +7 -1
  32. package/dist/cjs/query/expression-helpers.cjs +217 -0
  33. package/dist/cjs/query/expression-helpers.cjs.map +1 -0
  34. package/dist/cjs/query/expression-helpers.d.cts +216 -0
  35. package/dist/cjs/query/index.d.cts +2 -0
  36. package/dist/cjs/query/live/collection-config-builder.cjs +13 -0
  37. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  38. package/dist/cjs/query/live/collection-config-builder.d.cts +1 -0
  39. package/dist/cjs/query/live/types.d.cts +6 -1
  40. package/dist/cjs/query/predicate-utils.cjs +816 -0
  41. package/dist/cjs/query/predicate-utils.cjs.map +1 -0
  42. package/dist/cjs/query/predicate-utils.d.cts +116 -0
  43. package/dist/cjs/query/subset-dedupe.cjs +111 -0
  44. package/dist/cjs/query/subset-dedupe.cjs.map +1 -0
  45. package/dist/cjs/query/subset-dedupe.d.cts +66 -0
  46. package/dist/cjs/types.d.cts +29 -0
  47. package/dist/cjs/utils/comparison.cjs +30 -0
  48. package/dist/cjs/utils/comparison.cjs.map +1 -1
  49. package/dist/cjs/utils/comparison.d.cts +7 -1
  50. package/dist/cjs/utils/index-optimization.cjs +26 -22
  51. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  52. package/dist/cjs/utils/index-optimization.d.cts +5 -4
  53. package/dist/esm/collection/change-events.d.ts +1 -8
  54. package/dist/esm/collection/change-events.js +7 -9
  55. package/dist/esm/collection/change-events.js.map +1 -1
  56. package/dist/esm/collection/index.d.ts +7 -5
  57. package/dist/esm/collection/index.js +18 -0
  58. package/dist/esm/collection/index.js.map +1 -1
  59. package/dist/esm/index.d.ts +2 -0
  60. package/dist/esm/index.js +19 -1
  61. package/dist/esm/index.js.map +1 -1
  62. package/dist/esm/indexes/auto-index.js +7 -3
  63. package/dist/esm/indexes/auto-index.js.map +1 -1
  64. package/dist/esm/local-storage.d.ts +2 -2
  65. package/dist/esm/local-storage.js.map +1 -1
  66. package/dist/esm/query/builder/functions.d.ts +5 -0
  67. package/dist/esm/query/builder/functions.js +34 -0
  68. package/dist/esm/query/builder/functions.js.map +1 -1
  69. package/dist/esm/query/builder/index.js +2 -2
  70. package/dist/esm/query/builder/index.js.map +1 -1
  71. package/dist/esm/query/builder/types.d.ts +3 -22
  72. package/dist/esm/query/compiler/evaluators.d.ts +13 -0
  73. package/dist/esm/query/compiler/evaluators.js +59 -6
  74. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  75. package/dist/esm/query/compiler/expressions.js +4 -1
  76. package/dist/esm/query/compiler/expressions.js.map +1 -1
  77. package/dist/esm/query/compiler/group-by.js +4 -4
  78. package/dist/esm/query/compiler/group-by.js.map +1 -1
  79. package/dist/esm/query/compiler/index.js +3 -3
  80. package/dist/esm/query/compiler/index.js.map +1 -1
  81. package/dist/esm/query/compiler/order-by.d.ts +7 -1
  82. package/dist/esm/query/compiler/order-by.js +18 -6
  83. package/dist/esm/query/compiler/order-by.js.map +1 -1
  84. package/dist/esm/query/expression-helpers.d.ts +216 -0
  85. package/dist/esm/query/expression-helpers.js +217 -0
  86. package/dist/esm/query/expression-helpers.js.map +1 -0
  87. package/dist/esm/query/index.d.ts +2 -0
  88. package/dist/esm/query/live/collection-config-builder.d.ts +1 -0
  89. package/dist/esm/query/live/collection-config-builder.js +13 -0
  90. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  91. package/dist/esm/query/live/types.d.ts +6 -1
  92. package/dist/esm/query/predicate-utils.d.ts +116 -0
  93. package/dist/esm/query/predicate-utils.js +816 -0
  94. package/dist/esm/query/predicate-utils.js.map +1 -0
  95. package/dist/esm/query/subset-dedupe.d.ts +66 -0
  96. package/dist/esm/query/subset-dedupe.js +111 -0
  97. package/dist/esm/query/subset-dedupe.js.map +1 -0
  98. package/dist/esm/types.d.ts +29 -0
  99. package/dist/esm/utils/comparison.d.ts +7 -1
  100. package/dist/esm/utils/comparison.js +30 -0
  101. package/dist/esm/utils/comparison.js.map +1 -1
  102. package/dist/esm/utils/index-optimization.d.ts +5 -4
  103. package/dist/esm/utils/index-optimization.js +26 -22
  104. package/dist/esm/utils/index-optimization.js.map +1 -1
  105. package/package.json +2 -2
  106. package/src/collection/change-events.ts +14 -24
  107. package/src/collection/index.ts +32 -4
  108. package/src/index.ts +4 -0
  109. package/src/indexes/auto-index.ts +8 -4
  110. package/src/local-storage.ts +11 -3
  111. package/src/query/builder/functions.ts +39 -0
  112. package/src/query/builder/index.ts +2 -2
  113. package/src/query/builder/types.ts +3 -25
  114. package/src/query/compiler/evaluators.ts +103 -5
  115. package/src/query/compiler/expressions.ts +3 -0
  116. package/src/query/compiler/group-by.ts +4 -4
  117. package/src/query/compiler/index.ts +3 -3
  118. package/src/query/compiler/order-by.ts +33 -7
  119. package/src/query/expression-helpers.ts +522 -0
  120. package/src/query/index.ts +12 -0
  121. package/src/query/live/collection-config-builder.ts +27 -0
  122. package/src/query/live/types.ts +11 -1
  123. package/src/query/predicate-utils.ts +1415 -0
  124. package/src/query/subset-dedupe.ts +243 -0
  125. package/src/types.ts +39 -0
  126. package/src/utils/comparison.ts +70 -1
  127. package/src/utils/index-optimization.ts +77 -63
@@ -3,10 +3,33 @@ import {
3
3
  UnknownExpressionTypeError,
4
4
  UnknownFunctionError,
5
5
  } from "../../errors.js"
6
- import { normalizeValue } from "../../utils/comparison.js"
6
+ import { areValuesEqual, normalizeValue } from "../../utils/comparison.js"
7
7
  import type { BasicExpression, Func, PropRef } from "../ir.js"
8
8
  import type { NamespacedRow } from "../../types.js"
9
9
 
10
+ /**
11
+ * Helper function to check if a value is null or undefined (represents UNKNOWN in 3-valued logic)
12
+ */
13
+ function isUnknown(value: any): boolean {
14
+ return value === null || value === undefined
15
+ }
16
+
17
+ /**
18
+ * Converts a 3-valued logic result to a boolean for use in WHERE/HAVING filters.
19
+ * In SQL, UNKNOWN (null) values in WHERE clauses exclude rows, matching false behavior.
20
+ *
21
+ * @param result - The 3-valued logic result: true, false, or null (UNKNOWN)
22
+ * @returns true only if result is explicitly true, false otherwise
23
+ *
24
+ * Truth table:
25
+ * - true → true (include row)
26
+ * - false → false (exclude row)
27
+ * - null (UNKNOWN) → false (exclude row, matching SQL behavior)
28
+ */
29
+ export function toBooleanPredicate(result: boolean | null): boolean {
30
+ return result === true
31
+ }
32
+
10
33
  /**
11
34
  * Compiled expression evaluator function type
12
35
  */
@@ -145,7 +168,12 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
145
168
  return (data) => {
146
169
  const a = normalizeValue(argA(data))
147
170
  const b = normalizeValue(argB(data))
148
- return a === b
171
+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
172
+ if (isUnknown(a) || isUnknown(b)) {
173
+ return null
174
+ }
175
+ // Use areValuesEqual for proper Uint8Array/Buffer comparison
176
+ return areValuesEqual(a, b)
149
177
  }
150
178
  }
151
179
  case `gt`: {
@@ -154,6 +182,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
154
182
  return (data) => {
155
183
  const a = argA(data)
156
184
  const b = argB(data)
185
+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
186
+ if (isUnknown(a) || isUnknown(b)) {
187
+ return null
188
+ }
157
189
  return a > b
158
190
  }
159
191
  }
@@ -163,6 +195,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
163
195
  return (data) => {
164
196
  const a = argA(data)
165
197
  const b = argB(data)
198
+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
199
+ if (isUnknown(a) || isUnknown(b)) {
200
+ return null
201
+ }
166
202
  return a >= b
167
203
  }
168
204
  }
@@ -172,6 +208,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
172
208
  return (data) => {
173
209
  const a = argA(data)
174
210
  const b = argB(data)
211
+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
212
+ if (isUnknown(a) || isUnknown(b)) {
213
+ return null
214
+ }
175
215
  return a < b
176
216
  }
177
217
  }
@@ -181,6 +221,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
181
221
  return (data) => {
182
222
  const a = argA(data)
183
223
  const b = argB(data)
224
+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
225
+ if (isUnknown(a) || isUnknown(b)) {
226
+ return null
227
+ }
184
228
  return a <= b
185
229
  }
186
230
  }
@@ -188,25 +232,67 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
188
232
  // Boolean operators
189
233
  case `and`:
190
234
  return (data) => {
235
+ // 3-valued logic for AND:
236
+ // - false AND anything = false (short-circuit)
237
+ // - null AND false = false
238
+ // - null AND anything (except false) = null
239
+ // - anything (except false) AND null = null
240
+ // - true AND true = true
241
+ let hasUnknown = false
191
242
  for (const compiledArg of compiledArgs) {
192
- if (!compiledArg(data)) {
243
+ const result = compiledArg(data)
244
+ if (result === false) {
193
245
  return false
194
246
  }
247
+ if (isUnknown(result)) {
248
+ hasUnknown = true
249
+ }
250
+ }
251
+ // If we got here, no operand was false
252
+ // If any operand was null, return null (UNKNOWN)
253
+ if (hasUnknown) {
254
+ return null
195
255
  }
256
+
196
257
  return true
197
258
  }
198
259
  case `or`:
199
260
  return (data) => {
261
+ // 3-valued logic for OR:
262
+ // - true OR anything = true (short-circuit)
263
+ // - null OR anything (except true) = null
264
+ // - false OR false = false
265
+ let hasUnknown = false
200
266
  for (const compiledArg of compiledArgs) {
201
- if (compiledArg(data)) {
267
+ const result = compiledArg(data)
268
+ if (result === true) {
202
269
  return true
203
270
  }
271
+ if (isUnknown(result)) {
272
+ hasUnknown = true
273
+ }
274
+ }
275
+ // If we got here, no operand was true
276
+ // If any operand was null, return null (UNKNOWN)
277
+ if (hasUnknown) {
278
+ return null
204
279
  }
280
+
205
281
  return false
206
282
  }
207
283
  case `not`: {
208
284
  const arg = compiledArgs[0]!
209
- return (data) => !arg(data)
285
+ return (data) => {
286
+ // 3-valued logic for NOT:
287
+ // - NOT null = null
288
+ // - NOT true = false
289
+ // - NOT false = true
290
+ const result = arg(data)
291
+ if (isUnknown(result)) {
292
+ return null
293
+ }
294
+ return !result
295
+ }
210
296
  }
211
297
 
212
298
  // Array operators
@@ -216,6 +302,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
216
302
  return (data) => {
217
303
  const value = valueEvaluator(data)
218
304
  const array = arrayEvaluator(data)
305
+ // In 3-valued logic, if the value is null/undefined, return UNKNOWN
306
+ if (isUnknown(value)) {
307
+ return null
308
+ }
219
309
  if (!Array.isArray(array)) {
220
310
  return false
221
311
  }
@@ -230,6 +320,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
230
320
  return (data) => {
231
321
  const value = valueEvaluator(data)
232
322
  const pattern = patternEvaluator(data)
323
+ // In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN
324
+ if (isUnknown(value) || isUnknown(pattern)) {
325
+ return null
326
+ }
233
327
  return evaluateLike(value, pattern, false)
234
328
  }
235
329
  }
@@ -239,6 +333,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
239
333
  return (data) => {
240
334
  const value = valueEvaluator(data)
241
335
  const pattern = patternEvaluator(data)
336
+ // In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN
337
+ if (isUnknown(value) || isUnknown(pattern)) {
338
+ return null
339
+ }
242
340
  return evaluateLike(value, pattern, true)
243
341
  }
244
342
  }
@@ -15,6 +15,9 @@ export const SUPPORTED_COLLECTION_FUNCS = new Set([
15
15
  `and`,
16
16
  `or`,
17
17
  `in`,
18
+ `isNull`,
19
+ `isUndefined`,
20
+ `not`,
18
21
  ])
19
22
 
20
23
  /**
@@ -6,7 +6,7 @@ import {
6
6
  UnknownHavingExpressionTypeError,
7
7
  UnsupportedAggregateFunctionError,
8
8
  } from "../../errors.js"
9
- import { compileExpression } from "./evaluators.js"
9
+ import { compileExpression, toBooleanPredicate } from "./evaluators.js"
10
10
  import type {
11
11
  Aggregate,
12
12
  BasicExpression,
@@ -140,7 +140,7 @@ export function processGroupBy(
140
140
  filter(([, row]) => {
141
141
  // Create a namespaced row structure for HAVING evaluation
142
142
  const namespacedRow = { result: (row as any).__select_results }
143
- return compiledHaving(namespacedRow)
143
+ return toBooleanPredicate(compiledHaving(namespacedRow))
144
144
  })
145
145
  )
146
146
  }
@@ -153,7 +153,7 @@ export function processGroupBy(
153
153
  filter(([, row]) => {
154
154
  // Create a namespaced row structure for functional HAVING evaluation
155
155
  const namespacedRow = { result: (row as any).__select_results }
156
- return fnHaving(namespacedRow)
156
+ return toBooleanPredicate(fnHaving(namespacedRow))
157
157
  })
158
158
  )
159
159
  }
@@ -288,7 +288,7 @@ export function processGroupBy(
288
288
  filter(([, row]) => {
289
289
  // Create a namespaced row structure for functional HAVING evaluation
290
290
  const namespacedRow = { result: (row as any).__select_results }
291
- return fnHaving(namespacedRow)
291
+ return toBooleanPredicate(fnHaving(namespacedRow))
292
292
  })
293
293
  )
294
294
  }
@@ -9,7 +9,7 @@ import {
9
9
  UnsupportedFromTypeError,
10
10
  } from "../../errors.js"
11
11
  import { PropRef, Value as ValClass, getWhereExpression } from "../ir.js"
12
- import { compileExpression } from "./evaluators.js"
12
+ import { compileExpression, toBooleanPredicate } from "./evaluators.js"
13
13
  import { processJoins } from "./joins.js"
14
14
  import { processGroupBy } from "./group-by.js"
15
15
  import { processOrderBy } from "./order-by.js"
@@ -195,7 +195,7 @@ export function compileQuery(
195
195
  const compiledWhere = compileExpression(whereExpression)
196
196
  pipeline = pipeline.pipe(
197
197
  filter(([_key, namespacedRow]) => {
198
- return compiledWhere(namespacedRow)
198
+ return toBooleanPredicate(compiledWhere(namespacedRow))
199
199
  })
200
200
  )
201
201
  }
@@ -206,7 +206,7 @@ export function compileQuery(
206
206
  for (const fnWhere of query.fnWhere) {
207
207
  pipeline = pipeline.pipe(
208
208
  filter(([_key, namespacedRow]) => {
209
- return fnWhere(namespacedRow)
209
+ return toBooleanPredicate(fnWhere(namespacedRow))
210
210
  })
211
211
  )
212
212
  }
@@ -5,10 +5,15 @@ import { ensureIndexForField } from "../../indexes/auto-index.js"
5
5
  import { findIndexForField } from "../../utils/index-optimization.js"
6
6
  import { compileExpression } from "./evaluators.js"
7
7
  import { replaceAggregatesByRefs } from "./group-by.js"
8
+ import type { CompareOptions } from "../builder/types.js"
8
9
  import type { WindowOptions } from "./types.js"
9
10
  import type { CompiledSingleRowExpression } from "./evaluators.js"
10
11
  import type { OrderBy, OrderByClause, QueryIR, Select } from "../ir.js"
11
- import type { NamespacedAndKeyedStream, NamespacedRow } from "../../types.js"
12
+ import type {
13
+ CollectionLike,
14
+ NamespacedAndKeyedStream,
15
+ NamespacedRow,
16
+ } from "../../types.js"
12
17
  import type { IStreamBuilder, KeyValue } from "@tanstack/db-ivm"
13
18
  import type { IndexInterface } from "../../indexes/base-index.js"
14
19
  import type { Collection } from "../../collection/index.js"
@@ -50,9 +55,10 @@ export function processOrderBy(
50
55
  selectClause,
51
56
  `__select_results`
52
57
  )
58
+
53
59
  return {
54
60
  compiledExpression: compileExpression(clauseWithoutAggregates),
55
- compareOptions: clause.compareOptions,
61
+ compareOptions: buildCompareOptions(clause, collection),
56
62
  }
57
63
  })
58
64
 
@@ -87,7 +93,7 @@ export function processOrderBy(
87
93
  const arrayA = a as Array<unknown>
88
94
  const arrayB = b as Array<unknown>
89
95
  for (let i = 0; i < orderByClause.length; i++) {
90
- const clause = orderByClause[i]!
96
+ const clause = compiledOrderBy[i]!
91
97
  const compareFn = makeComparator(clause.compareOptions)
92
98
  const result = compareFn(arrayA[i], arrayB[i])
93
99
  if (result !== 0) {
@@ -99,7 +105,7 @@ export function processOrderBy(
99
105
 
100
106
  // Single property comparison
101
107
  if (orderByClause.length === 1) {
102
- const clause = orderByClause[0]!
108
+ const clause = compiledOrderBy[0]!
103
109
  const compareFn = makeComparator(clause.compareOptions)
104
110
  return compareFn(a, b)
105
111
  }
@@ -127,12 +133,13 @@ export function processOrderBy(
127
133
 
128
134
  const followRefCollection = followRefResult.collection
129
135
  const fieldName = followRefResult.path[0]
136
+ const compareOpts = buildCompareOptions(clause, followRefCollection)
130
137
  if (fieldName) {
131
138
  ensureIndexForField(
132
139
  fieldName,
133
140
  followRefResult.path,
134
141
  followRefCollection,
135
- clause.compareOptions,
142
+ compareOpts,
136
143
  compare
137
144
  )
138
145
  }
@@ -153,9 +160,9 @@ export function processOrderBy(
153
160
 
154
161
  const index: IndexInterface<string | number> | undefined =
155
162
  findIndexForField(
156
- followRefCollection.indexes,
163
+ followRefCollection,
157
164
  followRefResult.path,
158
- clause.compareOptions
165
+ compareOpts
159
166
  )
160
167
 
161
168
  if (index && index.supports(`gt`)) {
@@ -217,3 +224,22 @@ export function processOrderBy(
217
224
  // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
218
225
  )
219
226
  }
227
+
228
+ /**
229
+ * Builds a comparison configuration object that uses the values provided in the orderBy clause.
230
+ * If no string sort configuration is provided it defaults to the collection's string sort configuration.
231
+ */
232
+ export function buildCompareOptions(
233
+ clause: OrderByClause,
234
+ collection: CollectionLike<any, any>
235
+ ): CompareOptions {
236
+ if (clause.compareOptions.stringSort !== undefined) {
237
+ return clause.compareOptions
238
+ }
239
+
240
+ return {
241
+ ...collection.compareOptions,
242
+ direction: clause.compareOptions.direction,
243
+ nulls: clause.compareOptions.nulls,
244
+ }
245
+ }