@tanstack/db-ivm 0.1.9 → 0.1.11

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 (95) hide show
  1. package/dist/cjs/d2.cjs +25 -49
  2. package/dist/cjs/d2.cjs.map +1 -1
  3. package/dist/cjs/graph.cjs +8 -20
  4. package/dist/cjs/graph.cjs.map +1 -1
  5. package/dist/cjs/indexes.cjs +118 -79
  6. package/dist/cjs/indexes.cjs.map +1 -1
  7. package/dist/cjs/indexes.d.cts +23 -0
  8. package/dist/cjs/multiset.cjs +106 -115
  9. package/dist/cjs/multiset.cjs.map +1 -1
  10. package/dist/cjs/multiset.d.cts +1 -0
  11. package/dist/cjs/operators/debug.cjs +5 -15
  12. package/dist/cjs/operators/debug.cjs.map +1 -1
  13. package/dist/cjs/operators/distinct.cjs +11 -22
  14. package/dist/cjs/operators/distinct.cjs.map +1 -1
  15. package/dist/cjs/operators/filter.cjs +3 -12
  16. package/dist/cjs/operators/filter.cjs.map +1 -1
  17. package/dist/cjs/operators/filterBy.cjs.map +1 -1
  18. package/dist/cjs/operators/filterBy.d.cts +1 -2
  19. package/dist/cjs/operators/groupBy.cjs +1 -2
  20. package/dist/cjs/operators/groupBy.cjs.map +1 -1
  21. package/dist/cjs/operators/join.cjs +104 -93
  22. package/dist/cjs/operators/join.cjs.map +1 -1
  23. package/dist/cjs/operators/join.d.cts +11 -8
  24. package/dist/cjs/operators/map.cjs +3 -12
  25. package/dist/cjs/operators/map.cjs.map +1 -1
  26. package/dist/cjs/operators/orderBy.cjs +13 -11
  27. package/dist/cjs/operators/orderBy.cjs.map +1 -1
  28. package/dist/cjs/operators/orderBy.d.cts +5 -2
  29. package/dist/cjs/operators/output.cjs +3 -12
  30. package/dist/cjs/operators/output.cjs.map +1 -1
  31. package/dist/cjs/operators/reduce.cjs +11 -22
  32. package/dist/cjs/operators/reduce.cjs.map +1 -1
  33. package/dist/cjs/operators/tap.cjs +3 -12
  34. package/dist/cjs/operators/tap.cjs.map +1 -1
  35. package/dist/cjs/operators/topK.cjs +4 -4
  36. package/dist/cjs/operators/topK.cjs.map +1 -1
  37. package/dist/cjs/operators/topK.d.cts +1 -2
  38. package/dist/cjs/operators/topKWithFractionalIndex.cjs +117 -82
  39. package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
  40. package/dist/cjs/operators/topKWithFractionalIndex.d.cts +22 -0
  41. package/dist/cjs/utils.cjs +21 -0
  42. package/dist/cjs/utils.cjs.map +1 -1
  43. package/dist/cjs/utils.d.cts +11 -0
  44. package/dist/esm/d2.js +25 -49
  45. package/dist/esm/d2.js.map +1 -1
  46. package/dist/esm/graph.js +8 -20
  47. package/dist/esm/graph.js.map +1 -1
  48. package/dist/esm/indexes.d.ts +23 -0
  49. package/dist/esm/indexes.js +118 -79
  50. package/dist/esm/indexes.js.map +1 -1
  51. package/dist/esm/multiset.d.ts +1 -0
  52. package/dist/esm/multiset.js +106 -115
  53. package/dist/esm/multiset.js.map +1 -1
  54. package/dist/esm/operators/debug.js +5 -15
  55. package/dist/esm/operators/debug.js.map +1 -1
  56. package/dist/esm/operators/distinct.js +11 -22
  57. package/dist/esm/operators/distinct.js.map +1 -1
  58. package/dist/esm/operators/filter.js +3 -12
  59. package/dist/esm/operators/filter.js.map +1 -1
  60. package/dist/esm/operators/filterBy.d.ts +1 -2
  61. package/dist/esm/operators/filterBy.js.map +1 -1
  62. package/dist/esm/operators/groupBy.js +1 -2
  63. package/dist/esm/operators/groupBy.js.map +1 -1
  64. package/dist/esm/operators/join.d.ts +11 -8
  65. package/dist/esm/operators/join.js +104 -93
  66. package/dist/esm/operators/join.js.map +1 -1
  67. package/dist/esm/operators/map.js +3 -12
  68. package/dist/esm/operators/map.js.map +1 -1
  69. package/dist/esm/operators/orderBy.d.ts +5 -2
  70. package/dist/esm/operators/orderBy.js +13 -11
  71. package/dist/esm/operators/orderBy.js.map +1 -1
  72. package/dist/esm/operators/output.js +3 -12
  73. package/dist/esm/operators/output.js.map +1 -1
  74. package/dist/esm/operators/reduce.js +11 -22
  75. package/dist/esm/operators/reduce.js.map +1 -1
  76. package/dist/esm/operators/tap.js +3 -12
  77. package/dist/esm/operators/tap.js.map +1 -1
  78. package/dist/esm/operators/topK.d.ts +1 -2
  79. package/dist/esm/operators/topK.js +4 -4
  80. package/dist/esm/operators/topK.js.map +1 -1
  81. package/dist/esm/operators/topKWithFractionalIndex.d.ts +22 -0
  82. package/dist/esm/operators/topKWithFractionalIndex.js +118 -83
  83. package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
  84. package/dist/esm/utils.d.ts +11 -0
  85. package/dist/esm/utils.js +21 -0
  86. package/dist/esm/utils.js.map +1 -1
  87. package/package.json +1 -1
  88. package/src/indexes.ts +54 -0
  89. package/src/multiset.ts +6 -0
  90. package/src/operators/filterBy.ts +1 -2
  91. package/src/operators/join.ts +249 -139
  92. package/src/operators/orderBy.ts +6 -2
  93. package/src/operators/topK.ts +1 -2
  94. package/src/operators/topKWithFractionalIndex.ts +111 -13
  95. package/src/utils.ts +33 -0
@@ -2,7 +2,12 @@ import { generateKeyBetween } from "fractional-indexing"
2
2
  import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
3
3
  import { StreamBuilder } from "../d2.js"
4
4
  import { MultiSet } from "../multiset.js"
5
- import { binarySearch, globalObjectIdGenerator } from "../utils.js"
5
+ import {
6
+ binarySearch,
7
+ diffHalfOpen,
8
+ globalObjectIdGenerator,
9
+ } from "../utils.js"
10
+ import type { HRange } from "../utils.js"
6
11
  import type { DifferenceStreamReader } from "../graph.js"
7
12
  import type { IStreamBuilder, PipedOperator } from "../types.js"
8
13
 
@@ -10,6 +15,9 @@ export interface TopKWithFractionalIndexOptions {
10
15
  limit?: number
11
16
  offset?: number
12
17
  setSizeCallback?: (getSize: () => number) => void
18
+ setWindowFn?: (
19
+ windowFn: (options: { offset?: number; limit?: number }) => void
20
+ ) => void
13
21
  }
14
22
 
15
23
  export type TopKChanges<V> = {
@@ -19,6 +27,15 @@ export type TopKChanges<V> = {
19
27
  moveOut: IndexedValue<V> | null
20
28
  }
21
29
 
30
+ export type TopKMoveChanges<V> = {
31
+ /** Flag that marks whether there were any changes to the topK */
32
+ changes: boolean
33
+ /** Indicates which elements move into the topK (if any) */
34
+ moveIns: Array<IndexedValue<V>>
35
+ /** Indicates which elements move out of the topK (if any) */
36
+ moveOuts: Array<IndexedValue<V>>
37
+ }
38
+
22
39
  /**
23
40
  * A topK data structure that supports insertions and deletions
24
41
  * and returns changes to the topK.
@@ -58,6 +75,49 @@ class TopKArray<V> implements TopK<V> {
58
75
  return Math.max(0, Math.min(limit, available))
59
76
  }
60
77
 
78
+ /**
79
+ * Moves the topK window
80
+ */
81
+ move({
82
+ offset,
83
+ limit,
84
+ }: {
85
+ offset?: number
86
+ limit?: number
87
+ }): TopKMoveChanges<V> {
88
+ const oldOffset = this.#topKStart
89
+ const oldLimit = this.#topKEnd - this.#topKStart
90
+ const oldRange: HRange = [this.#topKStart, this.#topKEnd]
91
+
92
+ this.#topKStart = offset ?? oldOffset
93
+ this.#topKEnd = this.#topKStart + (limit ?? oldLimit)
94
+
95
+ const newRange: HRange = [this.#topKStart, this.#topKEnd]
96
+ const { onlyInA, onlyInB } = diffHalfOpen(oldRange, newRange)
97
+
98
+ const moveIns: Array<IndexedValue<V>> = []
99
+ onlyInB.forEach((index) => {
100
+ const value = this.#sortedValues[index]
101
+ if (value) {
102
+ moveIns.push(value)
103
+ }
104
+ })
105
+
106
+ const moveOuts: Array<IndexedValue<V>> = []
107
+ onlyInA.forEach((index) => {
108
+ const value = this.#sortedValues[index]
109
+ if (value) {
110
+ moveOuts.push(value)
111
+ }
112
+ })
113
+
114
+ // It could be that there are changes (i.e. moveIns or moveOuts)
115
+ // but that the collection is lazy so we don't have the data yet that needs to move in/out
116
+ // so `moveIns` and `moveOuts` will be empty but `changes` will be true
117
+ // this will tell the caller that it needs to run the graph to load more data
118
+ return { moveIns, moveOuts, changes: onlyInA.length + onlyInB.length > 0 }
119
+ }
120
+
61
121
  insert(value: V): TopKChanges<V> {
62
122
  const result: TopKChanges<V> = { moveIn: null, moveOut: null }
63
123
 
@@ -178,8 +238,6 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
178
238
  */
179
239
  #topK: TopK<TaggedValue<K, T>>
180
240
 
181
- #limit: number
182
-
183
241
  constructor(
184
242
  id: number,
185
243
  inputA: DifferenceStreamReader<[K, T]>,
@@ -188,7 +246,7 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
188
246
  options: TopKWithFractionalIndexOptions
189
247
  ) {
190
248
  super(id, inputA, output)
191
- this.#limit = options.limit ?? Infinity
249
+ const limit = options.limit ?? Infinity
192
250
  const offset = options.offset ?? 0
193
251
  const compareTaggedValues = (
194
252
  a: TaggedValue<K, T>,
@@ -204,8 +262,9 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
204
262
  const tieBreakerB = getTag(b)
205
263
  return tieBreakerA - tieBreakerB
206
264
  }
207
- this.#topK = this.createTopK(offset, this.#limit, compareTaggedValues)
265
+ this.#topK = this.createTopK(offset, limit, compareTaggedValues)
208
266
  options.setSizeCallback?.(() => this.#topK.size)
267
+ options.setWindowFn?.(this.moveTopK.bind(this))
209
268
  }
210
269
 
211
270
  protected createTopK(
@@ -216,6 +275,32 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
216
275
  return new TopKArray(offset, limit, comparator)
217
276
  }
218
277
 
278
+ /**
279
+ * Moves the topK window based on the provided offset and limit.
280
+ * Any changes to the topK are sent to the output.
281
+ */
282
+ moveTopK({ offset, limit }: { offset?: number; limit?: number }) {
283
+ if (!(this.#topK instanceof TopKArray)) {
284
+ throw new Error(
285
+ `Cannot move B+-tree implementation of TopK with fractional index`
286
+ )
287
+ }
288
+
289
+ const result: Array<[[K, IndexedValue<T>], number]> = []
290
+
291
+ const diff = this.#topK.move({ offset, limit })
292
+
293
+ diff.moveIns.forEach((moveIn) => this.handleMoveIn(moveIn, result))
294
+ diff.moveOuts.forEach((moveOut) => this.handleMoveOut(moveOut, result))
295
+
296
+ if (diff.changes) {
297
+ // There are changes to the topK
298
+ // it could be that moveIns and moveOuts are empty
299
+ // because the collection is lazy, so we will run the graph again to load the data
300
+ this.output.sendData(new MultiSet(result))
301
+ }
302
+ }
303
+
219
304
  run(): void {
220
305
  const result: Array<[[K, IndexedValue<T>], number]> = []
221
306
  for (const message of this.inputMessages()) {
@@ -258,23 +343,36 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
258
343
  // so it doesn't affect the topK
259
344
  }
260
345
 
261
- if (res.moveIn) {
262
- const index = getIndex(res.moveIn)
263
- const taggedValue = getValue(res.moveIn)
346
+ this.handleMoveIn(res.moveIn, result)
347
+ this.handleMoveOut(res.moveOut, result)
348
+
349
+ return
350
+ }
351
+
352
+ private handleMoveIn(
353
+ moveIn: IndexedValue<TaggedValue<K, T>> | null,
354
+ result: Array<[[K, IndexedValue<T>], number]>
355
+ ) {
356
+ if (moveIn) {
357
+ const index = getIndex(moveIn)
358
+ const taggedValue = getValue(moveIn)
264
359
  const k = getKey(taggedValue)
265
360
  const val = getVal(taggedValue)
266
361
  result.push([[k, [val, index]], 1])
267
362
  }
363
+ }
268
364
 
269
- if (res.moveOut) {
270
- const index = getIndex(res.moveOut)
271
- const taggedValue = getValue(res.moveOut)
365
+ private handleMoveOut(
366
+ moveOut: IndexedValue<TaggedValue<K, T>> | null,
367
+ result: Array<[[K, IndexedValue<T>], number]>
368
+ ) {
369
+ if (moveOut) {
370
+ const index = getIndex(moveOut)
371
+ const taggedValue = getValue(moveOut)
272
372
  const k = getKey(taggedValue)
273
373
  const val = getVal(taggedValue)
274
374
  result.push([[k, [val, index]], -1])
275
375
  }
276
-
277
- return
278
376
  }
279
377
 
280
378
  private getMultiplicity(key: K): number {
package/src/utils.ts CHANGED
@@ -144,3 +144,36 @@ export function* mapIterable<T, U>(
144
144
  yield fn(t)
145
145
  }
146
146
  }
147
+
148
+ export type HRange = [number, number] // half-open [start, end[ i.e. end is exclusive
149
+
150
+ /**
151
+ * Computes the difference between two half-open ranges.
152
+ * @param a - The first half-open range
153
+ * @param b - The second half-open range
154
+ * @returns The difference between the two ranges
155
+ */
156
+ export function diffHalfOpen(a: HRange, b: HRange) {
157
+ const [a1, a2] = a
158
+ const [b1, b2] = b
159
+
160
+ // A \ B can be up to two segments (left and right of the overlap)
161
+ const onlyInA: Array<number> = [
162
+ ...range(a1, Math.min(a2, b1)), // left side of A outside B
163
+ ...range(Math.max(a1, b2), a2), // right side of A outside B
164
+ ]
165
+
166
+ // B \ A similarly
167
+ const onlyInB: Array<number> = [
168
+ ...range(b1, Math.min(b2, a1)),
169
+ ...range(Math.max(b1, a2), b2),
170
+ ]
171
+
172
+ return { onlyInA, onlyInB }
173
+ }
174
+
175
+ function range(start: number, end: number): Array<number> {
176
+ const out: Array<number> = []
177
+ for (let i = start; i < end; i++) out.push(i)
178
+ return out
179
+ }