@tanstack/db 0.5.11 → 0.5.12

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 (221) hide show
  1. package/dist/cjs/SortedMap.cjs +40 -26
  2. package/dist/cjs/SortedMap.cjs.map +1 -1
  3. package/dist/cjs/SortedMap.d.cts +10 -15
  4. package/dist/cjs/collection/change-events.cjs.map +1 -1
  5. package/dist/cjs/collection/changes.cjs.map +1 -1
  6. package/dist/cjs/collection/events.cjs.map +1 -1
  7. package/dist/cjs/collection/events.d.cts +12 -4
  8. package/dist/cjs/collection/index.cjs +2 -1
  9. package/dist/cjs/collection/index.cjs.map +1 -1
  10. package/dist/cjs/collection/indexes.cjs.map +1 -1
  11. package/dist/cjs/collection/lifecycle.cjs.map +1 -1
  12. package/dist/cjs/collection/mutations.cjs +5 -2
  13. package/dist/cjs/collection/mutations.cjs.map +1 -1
  14. package/dist/cjs/collection/state.cjs +6 -5
  15. package/dist/cjs/collection/state.cjs.map +1 -1
  16. package/dist/cjs/collection/state.d.cts +4 -1
  17. package/dist/cjs/collection/subscription.cjs +60 -53
  18. package/dist/cjs/collection/subscription.cjs.map +1 -1
  19. package/dist/cjs/collection/subscription.d.cts +18 -4
  20. package/dist/cjs/collection/sync.cjs.map +1 -1
  21. package/dist/cjs/errors.cjs +9 -0
  22. package/dist/cjs/errors.cjs.map +1 -1
  23. package/dist/cjs/errors.d.cts +3 -0
  24. package/dist/cjs/event-emitter.cjs.map +1 -1
  25. package/dist/cjs/index.cjs +2 -0
  26. package/dist/cjs/index.cjs.map +1 -1
  27. package/dist/cjs/index.d.cts +1 -1
  28. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  29. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  30. package/dist/cjs/indexes/btree-index.cjs +8 -6
  31. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  32. package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
  33. package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
  34. package/dist/cjs/local-only.cjs.map +1 -1
  35. package/dist/cjs/local-storage.cjs.map +1 -1
  36. package/dist/cjs/optimistic-action.cjs.map +1 -1
  37. package/dist/cjs/paced-mutations.cjs.map +1 -1
  38. package/dist/cjs/proxy.cjs.map +1 -1
  39. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  40. package/dist/cjs/query/builder/index.cjs.map +1 -1
  41. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  42. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  43. package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
  44. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  45. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  46. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  47. package/dist/cjs/query/compiler/order-by.cjs +91 -38
  48. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  49. package/dist/cjs/query/compiler/order-by.d.cts +6 -2
  50. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  51. package/dist/cjs/query/expression-helpers.cjs.map +1 -1
  52. package/dist/cjs/query/index.d.cts +1 -1
  53. package/dist/cjs/query/ir.cjs.map +1 -1
  54. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  55. package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
  56. package/dist/cjs/query/live/collection-subscriber.cjs +30 -15
  57. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  58. package/dist/cjs/query/live/internal.cjs.map +1 -1
  59. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  60. package/dist/cjs/query/optimizer.cjs.map +1 -1
  61. package/dist/cjs/query/predicate-utils.cjs +19 -2
  62. package/dist/cjs/query/predicate-utils.cjs.map +1 -1
  63. package/dist/cjs/query/predicate-utils.d.cts +32 -1
  64. package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
  65. package/dist/cjs/scheduler.cjs.map +1 -1
  66. package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
  67. package/dist/cjs/strategies/queueStrategy.cjs.map +1 -1
  68. package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
  69. package/dist/cjs/transactions.cjs.map +1 -1
  70. package/dist/cjs/types.d.cts +43 -5
  71. package/dist/cjs/utils/browser-polyfills.cjs.map +1 -1
  72. package/dist/cjs/utils/btree.cjs.map +1 -1
  73. package/dist/cjs/utils/comparison.cjs.map +1 -1
  74. package/dist/cjs/utils/cursor.cjs +39 -0
  75. package/dist/cjs/utils/cursor.cjs.map +1 -0
  76. package/dist/cjs/utils/cursor.d.cts +18 -0
  77. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  78. package/dist/cjs/utils.cjs.map +1 -1
  79. package/dist/esm/SortedMap.d.ts +10 -15
  80. package/dist/esm/SortedMap.js +40 -26
  81. package/dist/esm/SortedMap.js.map +1 -1
  82. package/dist/esm/collection/change-events.js.map +1 -1
  83. package/dist/esm/collection/changes.js.map +1 -1
  84. package/dist/esm/collection/events.d.ts +12 -4
  85. package/dist/esm/collection/events.js.map +1 -1
  86. package/dist/esm/collection/index.js +2 -1
  87. package/dist/esm/collection/index.js.map +1 -1
  88. package/dist/esm/collection/indexes.js.map +1 -1
  89. package/dist/esm/collection/lifecycle.js.map +1 -1
  90. package/dist/esm/collection/mutations.js +6 -3
  91. package/dist/esm/collection/mutations.js.map +1 -1
  92. package/dist/esm/collection/state.d.ts +4 -1
  93. package/dist/esm/collection/state.js +6 -5
  94. package/dist/esm/collection/state.js.map +1 -1
  95. package/dist/esm/collection/subscription.d.ts +18 -4
  96. package/dist/esm/collection/subscription.js +61 -54
  97. package/dist/esm/collection/subscription.js.map +1 -1
  98. package/dist/esm/collection/sync.js.map +1 -1
  99. package/dist/esm/errors.d.ts +3 -0
  100. package/dist/esm/errors.js +9 -0
  101. package/dist/esm/errors.js.map +1 -1
  102. package/dist/esm/event-emitter.js.map +1 -1
  103. package/dist/esm/index.d.ts +1 -1
  104. package/dist/esm/index.js +4 -2
  105. package/dist/esm/indexes/auto-index.js.map +1 -1
  106. package/dist/esm/indexes/base-index.js.map +1 -1
  107. package/dist/esm/indexes/btree-index.js +8 -6
  108. package/dist/esm/indexes/btree-index.js.map +1 -1
  109. package/dist/esm/indexes/lazy-index.js.map +1 -1
  110. package/dist/esm/indexes/reverse-index.js.map +1 -1
  111. package/dist/esm/local-only.js.map +1 -1
  112. package/dist/esm/local-storage.js.map +1 -1
  113. package/dist/esm/optimistic-action.js.map +1 -1
  114. package/dist/esm/paced-mutations.js.map +1 -1
  115. package/dist/esm/proxy.js.map +1 -1
  116. package/dist/esm/query/builder/functions.js.map +1 -1
  117. package/dist/esm/query/builder/index.js.map +1 -1
  118. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  119. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  120. package/dist/esm/query/compiler/expressions.js.map +1 -1
  121. package/dist/esm/query/compiler/group-by.js.map +1 -1
  122. package/dist/esm/query/compiler/index.js.map +1 -1
  123. package/dist/esm/query/compiler/joins.js.map +1 -1
  124. package/dist/esm/query/compiler/order-by.d.ts +6 -2
  125. package/dist/esm/query/compiler/order-by.js +91 -38
  126. package/dist/esm/query/compiler/order-by.js.map +1 -1
  127. package/dist/esm/query/compiler/select.js.map +1 -1
  128. package/dist/esm/query/expression-helpers.js.map +1 -1
  129. package/dist/esm/query/index.d.ts +1 -1
  130. package/dist/esm/query/ir.js.map +1 -1
  131. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  132. package/dist/esm/query/live/collection-registry.js.map +1 -1
  133. package/dist/esm/query/live/collection-subscriber.js +30 -15
  134. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  135. package/dist/esm/query/live/internal.js.map +1 -1
  136. package/dist/esm/query/live-query-collection.js.map +1 -1
  137. package/dist/esm/query/optimizer.js.map +1 -1
  138. package/dist/esm/query/predicate-utils.d.ts +32 -1
  139. package/dist/esm/query/predicate-utils.js +19 -2
  140. package/dist/esm/query/predicate-utils.js.map +1 -1
  141. package/dist/esm/query/subset-dedupe.js.map +1 -1
  142. package/dist/esm/scheduler.js.map +1 -1
  143. package/dist/esm/strategies/debounceStrategy.js.map +1 -1
  144. package/dist/esm/strategies/queueStrategy.js.map +1 -1
  145. package/dist/esm/strategies/throttleStrategy.js.map +1 -1
  146. package/dist/esm/transactions.js.map +1 -1
  147. package/dist/esm/types.d.ts +43 -5
  148. package/dist/esm/utils/browser-polyfills.js.map +1 -1
  149. package/dist/esm/utils/btree.js.map +1 -1
  150. package/dist/esm/utils/comparison.js.map +1 -1
  151. package/dist/esm/utils/cursor.d.ts +18 -0
  152. package/dist/esm/utils/cursor.js +39 -0
  153. package/dist/esm/utils/cursor.js.map +1 -0
  154. package/dist/esm/utils/index-optimization.js.map +1 -1
  155. package/dist/esm/utils.js.map +1 -1
  156. package/package.json +30 -28
  157. package/src/SortedMap.ts +50 -31
  158. package/src/collection/change-events.ts +20 -20
  159. package/src/collection/changes.ts +12 -12
  160. package/src/collection/events.ts +20 -10
  161. package/src/collection/index.ts +47 -46
  162. package/src/collection/indexes.ts +14 -14
  163. package/src/collection/lifecycle.ts +16 -16
  164. package/src/collection/mutations.ts +25 -20
  165. package/src/collection/state.ts +43 -36
  166. package/src/collection/subscription.ts +114 -83
  167. package/src/collection/sync.ts +13 -13
  168. package/src/duplicate-instance-check.ts +1 -1
  169. package/src/errors.ts +49 -40
  170. package/src/event-emitter.ts +5 -5
  171. package/src/index.ts +21 -21
  172. package/src/indexes/auto-index.ts +11 -11
  173. package/src/indexes/base-index.ts +13 -13
  174. package/src/indexes/btree-index.ts +21 -17
  175. package/src/indexes/index-options.ts +3 -3
  176. package/src/indexes/lazy-index.ts +8 -8
  177. package/src/indexes/reverse-index.ts +5 -5
  178. package/src/local-only.ts +12 -12
  179. package/src/local-storage.ts +17 -17
  180. package/src/optimistic-action.ts +5 -5
  181. package/src/paced-mutations.ts +6 -6
  182. package/src/proxy.ts +43 -43
  183. package/src/query/builder/functions.ts +28 -28
  184. package/src/query/builder/index.ts +22 -22
  185. package/src/query/builder/ref-proxy.ts +4 -4
  186. package/src/query/builder/types.ts +8 -8
  187. package/src/query/compiler/evaluators.ts +9 -9
  188. package/src/query/compiler/expressions.ts +6 -6
  189. package/src/query/compiler/group-by.ts +24 -24
  190. package/src/query/compiler/index.ts +44 -44
  191. package/src/query/compiler/joins.ts +37 -37
  192. package/src/query/compiler/order-by.ts +170 -77
  193. package/src/query/compiler/select.ts +13 -13
  194. package/src/query/compiler/types.ts +2 -2
  195. package/src/query/expression-helpers.ts +16 -16
  196. package/src/query/index.ts +10 -9
  197. package/src/query/ir.ts +13 -13
  198. package/src/query/live/collection-config-builder.ts +53 -53
  199. package/src/query/live/collection-registry.ts +6 -6
  200. package/src/query/live/collection-subscriber.ts +87 -48
  201. package/src/query/live/internal.ts +1 -1
  202. package/src/query/live/types.ts +4 -4
  203. package/src/query/live-query-collection.ts +15 -15
  204. package/src/query/optimizer.ts +29 -29
  205. package/src/query/predicate-utils.ts +105 -50
  206. package/src/query/subset-dedupe.ts +6 -6
  207. package/src/scheduler.ts +3 -3
  208. package/src/strategies/debounceStrategy.ts +6 -6
  209. package/src/strategies/index.ts +4 -4
  210. package/src/strategies/queueStrategy.ts +5 -5
  211. package/src/strategies/throttleStrategy.ts +6 -6
  212. package/src/strategies/types.ts +2 -2
  213. package/src/transactions.ts +9 -9
  214. package/src/types.ts +51 -12
  215. package/src/utils/array-utils.ts +1 -1
  216. package/src/utils/browser-polyfills.ts +2 -2
  217. package/src/utils/btree.ts +22 -22
  218. package/src/utils/comparison.ts +3 -3
  219. package/src/utils/cursor.ts +78 -0
  220. package/src/utils/index-optimization.ts +14 -14
  221. package/src/utils.ts +4 -4
package/src/types.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { IStreamBuilder } from "@tanstack/db-ivm"
2
- import type { Collection } from "./collection/index.js"
3
- import type { StandardSchemaV1 } from "@standard-schema/spec"
4
- import type { Transaction } from "./transactions"
5
- import type { BasicExpression, OrderBy } from "./query/ir.js"
6
- import type { EventEmitter } from "./event-emitter.js"
1
+ import type { IStreamBuilder } from '@tanstack/db-ivm'
2
+ import type { Collection } from './collection/index.js'
3
+ import type { StandardSchemaV1 } from '@standard-schema/spec'
4
+ import type { Transaction } from './transactions'
5
+ import type { BasicExpression, OrderBy } from './query/ir.js'
6
+ import type { EventEmitter } from './event-emitter.js'
7
7
 
8
8
  /**
9
9
  * Interface for a collection-like object that provides the necessary methods
@@ -124,7 +124,7 @@ export type MutationFnParams<T extends object = Record<string, unknown>> = {
124
124
  }
125
125
 
126
126
  export type MutationFn<T extends object = Record<string, unknown>> = (
127
- params: MutationFnParams<T>
127
+ params: MutationFnParams<T>,
128
128
  ) => Promise<any>
129
129
 
130
130
  /**
@@ -238,9 +238,9 @@ export interface SubscriptionUnsubscribedEvent {
238
238
  * All subscription events
239
239
  */
240
240
  export type SubscriptionEvents = {
241
- "status:change": SubscriptionStatusChangeEvent
242
- "status:ready": SubscriptionStatusEvent<`ready`>
243
- "status:loadingSubset": SubscriptionStatusEvent<`loadingSubset`>
241
+ 'status:change': SubscriptionStatusChangeEvent
242
+ 'status:ready': SubscriptionStatusEvent<`ready`>
243
+ 'status:loadingSubset': SubscriptionStatusEvent<`loadingSubset`>
244
244
  unsubscribed: SubscriptionUnsubscribedEvent
245
245
  }
246
246
 
@@ -253,13 +253,52 @@ export interface Subscription extends EventEmitter<SubscriptionEvents> {
253
253
  readonly status: SubscriptionStatus
254
254
  }
255
255
 
256
+ /**
257
+ * Cursor expressions for pagination, passed separately from the main `where` clause.
258
+ * The sync layer can choose to use cursor-based pagination (combining these with the where)
259
+ * or offset-based pagination (ignoring these and using the `offset` parameter).
260
+ *
261
+ * Neither expression includes the main `where` clause - they are cursor-specific only.
262
+ */
263
+ export type CursorExpressions = {
264
+ /**
265
+ * Expression for rows greater than (after) the cursor value.
266
+ * For multi-column orderBy, this is a composite cursor using OR of conditions.
267
+ * Example for [col1 ASC, col2 DESC] with values [v1, v2]:
268
+ * or(gt(col1, v1), and(eq(col1, v1), lt(col2, v2)))
269
+ */
270
+ whereFrom: BasicExpression<boolean>
271
+ /**
272
+ * Expression for rows equal to the current cursor value (first orderBy column only).
273
+ * Used to handle tie-breaking/duplicates at the boundary.
274
+ * Example: eq(col1, v1) or for Dates: and(gte(col1, v1), lt(col1, v1+1ms))
275
+ */
276
+ whereCurrent: BasicExpression<boolean>
277
+ /**
278
+ * The key of the last item that was loaded.
279
+ * Can be used by sync layers for tracking or deduplication.
280
+ */
281
+ lastKey?: string | number
282
+ }
283
+
256
284
  export type LoadSubsetOptions = {
257
- /** The where expression to filter the data */
285
+ /** The where expression to filter the data (does NOT include cursor expressions) */
258
286
  where?: BasicExpression<boolean>
259
287
  /** The order by clause to sort the data */
260
288
  orderBy?: OrderBy
261
289
  /** The limit of the data to load */
262
290
  limit?: number
291
+ /**
292
+ * Cursor expressions for cursor-based pagination.
293
+ * These are separate from `where` - the sync layer should combine them if using cursor-based pagination.
294
+ * Neither expression includes the main `where` clause.
295
+ */
296
+ cursor?: CursorExpressions
297
+ /**
298
+ * Row offset for offset-based pagination.
299
+ * The sync layer can use this instead of `cursor` if it prefers offset-based pagination.
300
+ */
301
+ offset?: number
263
302
  /**
264
303
  * The subscription that triggered the load.
265
304
  * Advanced sync implementations can use this for:
@@ -334,7 +373,7 @@ export interface OptimisticChangeMessage<
334
373
  * This follows the standard-schema specification: https://github.com/standard-schema/standard-schema
335
374
  */
336
375
  export type StandardSchema<T> = StandardSchemaV1 & {
337
- "~standard": {
376
+ '~standard': {
338
377
  types?: {
339
378
  input: T
340
379
  output: T
@@ -8,7 +8,7 @@
8
8
  export function findInsertPosition<T>(
9
9
  sortedArray: Array<[T, any]>,
10
10
  value: T,
11
- compareFn: (a: T, b: T) => number
11
+ compareFn: (a: T, b: T) => number,
12
12
  ): number {
13
13
  let left = 0
14
14
  let right = sortedArray.length
@@ -7,7 +7,7 @@ export type IdleCallbackDeadline = {
7
7
  export type IdleCallbackFunction = (deadline: IdleCallbackDeadline) => void
8
8
 
9
9
  const requestIdleCallbackPolyfill = (
10
- callback: IdleCallbackFunction
10
+ callback: IdleCallbackFunction,
11
11
  ): number => {
12
12
  // Use a very small timeout for the polyfill to simulate idle time
13
13
  const timeout = 0
@@ -26,7 +26,7 @@ const cancelIdleCallbackPolyfill = (id: number): void => {
26
26
 
27
27
  export const safeRequestIdleCallback: (
28
28
  callback: IdleCallbackFunction,
29
- options?: { timeout?: number }
29
+ options?: { timeout?: number },
30
30
  ) => number =
31
31
  typeof window !== `undefined` && `requestIdleCallback` in window
32
32
  ? (callback, options) =>
@@ -117,7 +117,7 @@ export class BTree<K = any, V = any> {
117
117
  public constructor(
118
118
  compare: (a: K, b: K) => number,
119
119
  entries?: Array<[K, V]>,
120
- maxNodeSize?: number
120
+ maxNodeSize?: number,
121
121
  ) {
122
122
  this._maxNodeSize = maxNodeSize! >= 4 ? Math.min(maxNodeSize!, 256) : 32
123
123
  this._compare = compare
@@ -230,7 +230,7 @@ export class BTree<K = any, V = any> {
230
230
  0,
231
231
  (k, _v) => {
232
232
  results.push(k)
233
- }
233
+ },
234
234
  )
235
235
  return results
236
236
  }
@@ -250,7 +250,7 @@ export class BTree<K = any, V = any> {
250
250
  key,
251
251
  this._compare,
252
252
  false,
253
- reusedArray
253
+ reusedArray,
254
254
  )
255
255
  }
256
256
 
@@ -306,7 +306,7 @@ export class BTree<K = any, V = any> {
306
306
  high: K,
307
307
  includeHigh: boolean,
308
308
  onFound?: (k: K, v: V, counter: number) => void,
309
- initialCounter?: number
309
+ initialCounter?: number,
310
310
  ): number
311
311
 
312
312
  /**
@@ -331,7 +331,7 @@ export class BTree<K = any, V = any> {
331
331
  high: K,
332
332
  includeHigh: boolean,
333
333
  onFound?: (k: K, v: V, counter: number) => { break?: R } | void,
334
- initialCounter?: number
334
+ initialCounter?: number,
335
335
  ): R | number {
336
336
  const r = this._root.forRange(
337
337
  low,
@@ -340,7 +340,7 @@ export class BTree<K = any, V = any> {
340
340
  false,
341
341
  this,
342
342
  initialCounter || 0,
343
- onFound
343
+ onFound,
344
344
  )
345
345
  return typeof r === `number` ? r : r.break!
346
346
  }
@@ -379,7 +379,7 @@ export class BTree<K = any, V = any> {
379
379
  high: K,
380
380
  includeHigh: boolean,
381
381
  onFound: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void,
382
- initialCounter?: number
382
+ initialCounter?: number,
383
383
  ): R | number {
384
384
  let root = this._root
385
385
  if (root.isShared) this._root = root = root.clone()
@@ -391,7 +391,7 @@ export class BTree<K = any, V = any> {
391
391
  true,
392
392
  this,
393
393
  initialCounter || 0,
394
- onFound
394
+ onFound,
395
395
  )
396
396
  return typeof r === `number` ? r : r.break!
397
397
  } finally {
@@ -500,7 +500,7 @@ class BNode<K, V> {
500
500
  key: K,
501
501
  compare: (a: K, b: K) => number,
502
502
  inclusive: boolean,
503
- reusedArray: [K, V]
503
+ reusedArray: [K, V],
504
504
  ): [K, V] | undefined {
505
505
  const i = this.indexOf(key, -1, compare)
506
506
  const indexOrLower = i < 0 ? ~i - 1 : inclusive ? i : i - 1
@@ -516,7 +516,7 @@ class BNode<K, V> {
516
516
  key: K,
517
517
  compare: (a: K, b: K) => number,
518
518
  inclusive: boolean,
519
- reusedArray: [K, V]
519
+ reusedArray: [K, V],
520
520
  ): [K, V] | undefined {
521
521
  const i = this.indexOf(key, -1, compare)
522
522
  const indexOrLower = i < 0 ? ~i : inclusive ? i : i + 1
@@ -536,7 +536,7 @@ class BNode<K, V> {
536
536
  key: K,
537
537
  value: V,
538
538
  overwrite: boolean | undefined,
539
- tree: BTree<K, V>
539
+ tree: BTree<K, V>,
540
540
  ): boolean | BNode<K, V> {
541
541
  let i = this.indexOf(key, -1, tree._compare)
542
542
  if (i < 0) {
@@ -636,7 +636,7 @@ class BNode<K, V> {
636
636
  editMode: boolean,
637
637
  tree: BTree<K, V>,
638
638
  count: number,
639
- onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void
639
+ onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void,
640
640
  ): EditRangeResult<V, R> | number {
641
641
  const cmp = tree._compare
642
642
  let iLow, iHigh
@@ -732,7 +732,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
732
732
  key: K,
733
733
  compare: (a: K, b: K) => number,
734
734
  inclusive: boolean,
735
- reusedArray: [K, V]
735
+ reusedArray: [K, V],
736
736
  ): [K, V] | undefined {
737
737
  const i = this.indexOf(key, 0, compare),
738
738
  children = this.children
@@ -741,7 +741,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
741
741
  key,
742
742
  compare,
743
743
  inclusive,
744
- reusedArray
744
+ reusedArray,
745
745
  )
746
746
  if (result === undefined && i > 0) {
747
747
  return children[i - 1]!.maxPair(reusedArray)
@@ -753,7 +753,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
753
753
  key: K,
754
754
  compare: (a: K, b: K) => number,
755
755
  inclusive: boolean,
756
- reusedArray: [K, V]
756
+ reusedArray: [K, V],
757
757
  ): [K, V] | undefined {
758
758
  const i = this.indexOf(key, 0, compare),
759
759
  children = this.children,
@@ -763,7 +763,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
763
763
  key,
764
764
  compare,
765
765
  inclusive,
766
- reusedArray
766
+ reusedArray,
767
767
  )
768
768
  if (result === undefined && i < length - 1) {
769
769
  return children[i + 1]!.minPair(reusedArray)
@@ -778,7 +778,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
778
778
  key: K,
779
779
  value: V,
780
780
  overwrite: boolean | undefined,
781
- tree: BTree<K, V>
781
+ tree: BTree<K, V>,
782
782
  ): boolean | BNodeInternal<K, V> {
783
783
  const c = this.children,
784
784
  max = tree._maxNodeSize,
@@ -854,7 +854,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
854
854
  const half = this.children.length >> 1
855
855
  return new BNodeInternal<K, V>(
856
856
  this.children.splice(half),
857
- this.keys.splice(half)
857
+ this.keys.splice(half),
858
858
  )
859
859
  }
860
860
 
@@ -887,7 +887,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
887
887
  editMode: boolean,
888
888
  tree: BTree<K, V>,
889
889
  count: number,
890
- onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void
890
+ onFound?: (k: K, v: V, counter: number) => EditRangeResult<V, R> | void,
891
891
  ): EditRangeResult<V, R> | number {
892
892
  const cmp = tree._compare
893
893
  const keys = this.keys,
@@ -896,7 +896,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
896
896
  i = iLow
897
897
  const iHigh = Math.min(
898
898
  high === low ? iLow : this.indexOf(high, 0, cmp),
899
- keys.length - 1
899
+ keys.length - 1,
900
900
  )
901
901
  if (!editMode) {
902
902
  // Simple case
@@ -908,7 +908,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
908
908
  editMode,
909
909
  tree,
910
910
  count,
911
- onFound
911
+ onFound,
912
912
  )
913
913
  if (typeof result !== `number`) return result
914
914
  count = result
@@ -924,7 +924,7 @@ class BNodeInternal<K, V> extends BNode<K, V> {
924
924
  editMode,
925
925
  tree,
926
926
  count,
927
- onFound
927
+ onFound,
928
928
  )
929
929
  // Note: if children[i] is empty then keys[i]=undefined.
930
930
  // This is an invalid state, but it is fixed below.
@@ -1,4 +1,4 @@
1
- import type { CompareOptions } from "../query/builder/types"
1
+ import type { CompareOptions } from '../query/builder/types'
2
2
 
3
3
  // WeakMap to store stable IDs for objects
4
4
  const objectIds = new WeakMap<object, number>()
@@ -84,7 +84,7 @@ export const ascComparator = (a: any, b: any, opts: CompareOptions): number => {
84
84
  export const descComparator = (
85
85
  a: unknown,
86
86
  b: unknown,
87
- opts: CompareOptions
87
+ opts: CompareOptions,
88
88
  ): number => {
89
89
  return ascComparator(b, a, {
90
90
  ...opts,
@@ -93,7 +93,7 @@ export const descComparator = (
93
93
  }
94
94
 
95
95
  export function makeComparator(
96
- opts: CompareOptions
96
+ opts: CompareOptions,
97
97
  ): (a: any, b: any) => number {
98
98
  return (a, b) => {
99
99
  if (opts.direction === `asc`) {
@@ -0,0 +1,78 @@
1
+ import { and, eq, gt, lt, or } from '../query/builder/functions.js'
2
+ import { Value } from '../query/ir.js'
3
+ import type { BasicExpression, OrderBy } from '../query/ir.js'
4
+
5
+ /**
6
+ * Builds a cursor expression for paginating through ordered results.
7
+ * For multi-column orderBy, creates a composite cursor that respects all columns.
8
+ *
9
+ * For [col1 ASC, col2 DESC] with values [v1, v2], produces:
10
+ * or(
11
+ * gt(col1, v1), // col1 > v1
12
+ * and(eq(col1, v1), lt(col2, v2)) // col1 = v1 AND col2 < v2 (DESC)
13
+ * )
14
+ *
15
+ * This creates a precise cursor that works with composite indexes on the backend.
16
+ *
17
+ * @param orderBy - The order-by clauses defining sort columns and directions
18
+ * @param values - The cursor values corresponding to each order-by column
19
+ * @returns A filter expression for rows after the cursor position, or undefined if empty
20
+ */
21
+ export function buildCursor(
22
+ orderBy: OrderBy,
23
+ values: Array<unknown>,
24
+ ): BasicExpression<boolean> | undefined {
25
+ if (values.length === 0 || orderBy.length === 0) {
26
+ return undefined
27
+ }
28
+
29
+ // For single column, just use simple gt/lt
30
+ if (orderBy.length === 1) {
31
+ const { expression, compareOptions } = orderBy[0]!
32
+ const operator = compareOptions.direction === `asc` ? gt : lt
33
+ return operator(expression, new Value(values[0]))
34
+ }
35
+
36
+ // For multi-column, build the composite cursor:
37
+ // or(
38
+ // gt(col1, v1),
39
+ // and(eq(col1, v1), gt(col2, v2)),
40
+ // and(eq(col1, v1), eq(col2, v2), gt(col3, v3)),
41
+ // ...
42
+ // )
43
+ const clauses: Array<BasicExpression<boolean>> = []
44
+
45
+ for (let i = 0; i < orderBy.length && i < values.length; i++) {
46
+ const clause = orderBy[i]!
47
+ const value = values[i]
48
+
49
+ // Build equality conditions for all previous columns
50
+ const eqConditions: Array<BasicExpression<boolean>> = []
51
+ for (let j = 0; j < i; j++) {
52
+ const prevClause = orderBy[j]!
53
+ const prevValue = values[j]
54
+ eqConditions.push(eq(prevClause.expression, new Value(prevValue)))
55
+ }
56
+
57
+ // Add the comparison for the current column (respecting direction)
58
+ const operator = clause.compareOptions.direction === `asc` ? gt : lt
59
+ const comparison = operator(clause.expression, new Value(value))
60
+
61
+ if (eqConditions.length === 0) {
62
+ // First column: just the comparison
63
+ clauses.push(comparison)
64
+ } else {
65
+ // Subsequent columns: and(eq(prev...), comparison)
66
+ // We need to spread into and() which expects at least 2 args
67
+ const allConditions = [...eqConditions, comparison]
68
+ clauses.push(allConditions.reduce((acc, cond) => and(acc, cond)))
69
+ }
70
+ }
71
+
72
+ // Combine all clauses with OR
73
+ if (clauses.length === 1) {
74
+ return clauses[0]!
75
+ }
76
+ // Use reduce to combine with or() which expects exactly 2 args
77
+ return clauses.reduce((acc, clause) => or(acc, clause))
78
+ }
@@ -15,12 +15,12 @@
15
15
  * - Optimizes IN array expressions
16
16
  */
17
17
 
18
- import { DEFAULT_COMPARE_OPTIONS } from "../utils.js"
19
- import { ReverseIndex } from "../indexes/reverse-index.js"
20
- import type { CompareOptions } from "../query/builder/types.js"
21
- import type { IndexInterface, IndexOperation } from "../indexes/base-index.js"
22
- import type { BasicExpression } from "../query/ir.js"
23
- import type { CollectionLike } from "../types.js"
18
+ import { DEFAULT_COMPARE_OPTIONS } from '../utils.js'
19
+ import { ReverseIndex } from '../indexes/reverse-index.js'
20
+ import type { CompareOptions } from '../query/builder/types.js'
21
+ import type { IndexInterface, IndexOperation } from '../indexes/base-index.js'
22
+ import type { BasicExpression } from '../query/ir.js'
23
+ import type { CollectionLike } from '../types.js'
24
24
 
25
25
  /**
26
26
  * Result of index-based query optimization
@@ -36,7 +36,7 @@ export interface OptimizationResult<TKey> {
36
36
  export function findIndexForField<TKey extends string | number>(
37
37
  collection: CollectionLike<any, TKey>,
38
38
  fieldPath: Array<string>,
39
- compareOptions?: CompareOptions
39
+ compareOptions?: CompareOptions,
40
40
  ): IndexInterface<TKey> | undefined {
41
41
  const compareOpts = compareOptions ?? {
42
42
  ...DEFAULT_COMPARE_OPTIONS,
@@ -98,7 +98,7 @@ export function optimizeExpressionWithIndexes<
98
98
  TKey extends string | number,
99
99
  >(
100
100
  expression: BasicExpression,
101
- collection: CollectionLike<T, TKey>
101
+ collection: CollectionLike<T, TKey>,
102
102
  ): OptimizationResult<TKey> {
103
103
  return optimizeQueryRecursive(expression, collection)
104
104
  }
@@ -108,7 +108,7 @@ export function optimizeExpressionWithIndexes<
108
108
  */
109
109
  function optimizeQueryRecursive<T extends object, TKey extends string | number>(
110
110
  expression: BasicExpression,
111
- collection: CollectionLike<T, TKey>
111
+ collection: CollectionLike<T, TKey>,
112
112
  ): OptimizationResult<TKey> {
113
113
  if (expression.type === `func`) {
114
114
  switch (expression.name) {
@@ -172,7 +172,7 @@ function optimizeCompoundRangeQuery<
172
172
  TKey extends string | number,
173
173
  >(
174
174
  expression: BasicExpression,
175
- collection: CollectionLike<T, TKey>
175
+ collection: CollectionLike<T, TKey>,
176
176
  ): OptimizationResult<TKey> {
177
177
  if (expression.type !== `func` || expression.args.length < 2) {
178
178
  return { canOptimize: false, matchingKeys: new Set() }
@@ -305,7 +305,7 @@ function optimizeSimpleComparison<
305
305
  TKey extends string | number,
306
306
  >(
307
307
  expression: BasicExpression,
308
- collection: CollectionLike<T, TKey>
308
+ collection: CollectionLike<T, TKey>,
309
309
  ): OptimizationResult<TKey> {
310
310
  if (expression.type !== `func` || expression.args.length !== 2) {
311
311
  return { canOptimize: false, matchingKeys: new Set() }
@@ -405,7 +405,7 @@ function canOptimizeSimpleComparison<
405
405
  */
406
406
  function optimizeAndExpression<T extends object, TKey extends string | number>(
407
407
  expression: BasicExpression,
408
- collection: CollectionLike<T, TKey>
408
+ collection: CollectionLike<T, TKey>,
409
409
  ): OptimizationResult<TKey> {
410
410
  if (expression.type !== `func` || expression.args.length < 2) {
411
411
  return { canOptimize: false, matchingKeys: new Set() }
@@ -457,7 +457,7 @@ function canOptimizeAndExpression<
457
457
  */
458
458
  function optimizeOrExpression<T extends object, TKey extends string | number>(
459
459
  expression: BasicExpression,
460
- collection: CollectionLike<T, TKey>
460
+ collection: CollectionLike<T, TKey>,
461
461
  ): OptimizationResult<TKey> {
462
462
  if (expression.type !== `func` || expression.args.length < 2) {
463
463
  return { canOptimize: false, matchingKeys: new Set() }
@@ -506,7 +506,7 @@ function optimizeInArrayExpression<
506
506
  TKey extends string | number,
507
507
  >(
508
508
  expression: BasicExpression,
509
- collection: CollectionLike<T, TKey>
509
+ collection: CollectionLike<T, TKey>,
510
510
  ): OptimizationResult<TKey> {
511
511
  if (expression.type !== `func` || expression.args.length !== 2) {
512
512
  return { canOptimize: false, matchingKeys: new Set() }
package/src/utils.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Generic utility functions
3
3
  */
4
4
 
5
- import type { CompareOptions } from "./query/builder/types"
5
+ import type { CompareOptions } from './query/builder/types'
6
6
 
7
7
  interface TypedArray {
8
8
  length: number
@@ -36,7 +36,7 @@ export function deepEquals(a: any, b: any): boolean {
36
36
  function deepEqualsInternal(
37
37
  a: any,
38
38
  b: any,
39
- visited: Map<object, object>
39
+ visited: Map<object, object>,
40
40
  ): boolean {
41
41
  // Handle strict equality (primitives, same reference)
42
42
  if (a === b) return true
@@ -154,7 +154,7 @@ function deepEqualsInternal(
154
154
  visited.set(a, b)
155
155
 
156
156
  const result = a.every((item, index) =>
157
- deepEqualsInternal(item, b[index], visited)
157
+ deepEqualsInternal(item, b[index], visited),
158
158
  )
159
159
  visited.delete(a)
160
160
  return result
@@ -180,7 +180,7 @@ function deepEqualsInternal(
180
180
 
181
181
  // Check if all keys exist in both objects and their values are equal
182
182
  const result = keysA.every(
183
- (key) => key in b && deepEqualsInternal(a[key], b[key], visited)
183
+ (key) => key in b && deepEqualsInternal(a[key], b[key], visited),
184
184
  )
185
185
 
186
186
  visited.delete(a)