@tanstack/db-ivm 0.1.12 → 0.1.14

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 (108) hide show
  1. package/README.md +3 -3
  2. package/dist/cjs/d2.cjs.map +1 -1
  3. package/dist/cjs/graph.cjs.map +1 -1
  4. package/dist/cjs/hashing/hash.cjs +22 -3
  5. package/dist/cjs/hashing/hash.cjs.map +1 -1
  6. package/dist/cjs/hashing/murmur.cjs +15 -15
  7. package/dist/cjs/hashing/murmur.cjs.map +1 -1
  8. package/dist/cjs/hashing/murmur.d.cts +1 -1
  9. package/dist/cjs/index.cjs +2 -0
  10. package/dist/cjs/index.cjs.map +1 -1
  11. package/dist/cjs/index.d.cts +1 -0
  12. package/dist/cjs/indexes.cjs.map +1 -1
  13. package/dist/cjs/multiset.cjs.map +1 -1
  14. package/dist/cjs/operators/concat.cjs.map +1 -1
  15. package/dist/cjs/operators/consolidate.cjs.map +1 -1
  16. package/dist/cjs/operators/count.cjs.map +1 -1
  17. package/dist/cjs/operators/debug.cjs.map +1 -1
  18. package/dist/cjs/operators/distinct.cjs.map +1 -1
  19. package/dist/cjs/operators/filter.cjs.map +1 -1
  20. package/dist/cjs/operators/filterBy.cjs.map +1 -1
  21. package/dist/cjs/operators/groupBy.cjs.map +1 -1
  22. package/dist/cjs/operators/join.cjs.map +1 -1
  23. package/dist/cjs/operators/keying.cjs.map +1 -1
  24. package/dist/cjs/operators/map.cjs.map +1 -1
  25. package/dist/cjs/operators/negate.cjs.map +1 -1
  26. package/dist/cjs/operators/orderBy.cjs.map +1 -1
  27. package/dist/cjs/operators/output.cjs.map +1 -1
  28. package/dist/cjs/operators/pipe.cjs.map +1 -1
  29. package/dist/cjs/operators/reduce.cjs.map +1 -1
  30. package/dist/cjs/operators/tap.cjs.map +1 -1
  31. package/dist/cjs/operators/topK.cjs.map +1 -1
  32. package/dist/cjs/operators/topKWithFractionalIndex.cjs +20 -35
  33. package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
  34. package/dist/cjs/operators/topKWithFractionalIndex.d.cts +3 -11
  35. package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +4 -4
  36. package/dist/cjs/utils.cjs +9 -0
  37. package/dist/cjs/utils.cjs.map +1 -1
  38. package/dist/cjs/utils.d.cts +5 -0
  39. package/dist/esm/d2.js.map +1 -1
  40. package/dist/esm/graph.js.map +1 -1
  41. package/dist/esm/hashing/hash.js +22 -3
  42. package/dist/esm/hashing/hash.js.map +1 -1
  43. package/dist/esm/hashing/murmur.d.ts +1 -1
  44. package/dist/esm/hashing/murmur.js +15 -15
  45. package/dist/esm/hashing/murmur.js.map +1 -1
  46. package/dist/esm/index.d.ts +1 -0
  47. package/dist/esm/index.js +2 -0
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/indexes.js.map +1 -1
  50. package/dist/esm/multiset.js.map +1 -1
  51. package/dist/esm/operators/concat.js.map +1 -1
  52. package/dist/esm/operators/consolidate.js.map +1 -1
  53. package/dist/esm/operators/count.js.map +1 -1
  54. package/dist/esm/operators/debug.js.map +1 -1
  55. package/dist/esm/operators/distinct.js.map +1 -1
  56. package/dist/esm/operators/filter.js.map +1 -1
  57. package/dist/esm/operators/filterBy.js.map +1 -1
  58. package/dist/esm/operators/groupBy.js.map +1 -1
  59. package/dist/esm/operators/join.js.map +1 -1
  60. package/dist/esm/operators/keying.js.map +1 -1
  61. package/dist/esm/operators/map.js.map +1 -1
  62. package/dist/esm/operators/negate.js.map +1 -1
  63. package/dist/esm/operators/orderBy.js.map +1 -1
  64. package/dist/esm/operators/output.js.map +1 -1
  65. package/dist/esm/operators/pipe.js.map +1 -1
  66. package/dist/esm/operators/reduce.js.map +1 -1
  67. package/dist/esm/operators/tap.js.map +1 -1
  68. package/dist/esm/operators/topK.js.map +1 -1
  69. package/dist/esm/operators/topKWithFractionalIndex.d.ts +3 -11
  70. package/dist/esm/operators/topKWithFractionalIndex.js +21 -36
  71. package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
  72. package/dist/esm/operators/topKWithFractionalIndexBTree.d.ts +4 -4
  73. package/dist/esm/utils.d.ts +5 -0
  74. package/dist/esm/utils.js +9 -0
  75. package/dist/esm/utils.js.map +1 -1
  76. package/package.json +29 -29
  77. package/src/d2.ts +4 -4
  78. package/src/graph.ts +6 -6
  79. package/src/hashing/hash.ts +39 -14
  80. package/src/hashing/index.ts +2 -2
  81. package/src/hashing/murmur.ts +15 -15
  82. package/src/index.ts +5 -4
  83. package/src/indexes.ts +8 -8
  84. package/src/multiset.ts +5 -5
  85. package/src/operators/concat.ts +6 -6
  86. package/src/operators/consolidate.ts +6 -6
  87. package/src/operators/count.ts +9 -9
  88. package/src/operators/debug.ts +8 -8
  89. package/src/operators/distinct.ts +11 -11
  90. package/src/operators/filter.ts +8 -8
  91. package/src/operators/filterBy.ts +7 -7
  92. package/src/operators/groupBy.ts +22 -22
  93. package/src/operators/index.ts +19 -19
  94. package/src/operators/join.ts +23 -23
  95. package/src/operators/keying.ts +4 -4
  96. package/src/operators/map.ts +8 -8
  97. package/src/operators/negate.ts +6 -6
  98. package/src/operators/orderBy.ts +26 -26
  99. package/src/operators/orderByBTree.ts +7 -7
  100. package/src/operators/output.ts +9 -9
  101. package/src/operators/pipe.ts +1 -1
  102. package/src/operators/reduce.ts +9 -9
  103. package/src/operators/tap.ts +8 -8
  104. package/src/operators/topK.ts +8 -8
  105. package/src/operators/topKWithFractionalIndex.ts +62 -83
  106. package/src/operators/topKWithFractionalIndexBTree.ts +19 -20
  107. package/src/types.ts +22 -22
  108. package/src/utils.ts +19 -4
@@ -1,9 +1,9 @@
1
- import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
2
- import { StreamBuilder } from "../d2.js"
3
- import { MultiSet } from "../multiset.js"
4
- import { Index } from "../indexes.js"
5
- import type { DifferenceStreamReader } from "../graph.js"
6
- import type { IStreamBuilder, KeyValue } from "../types.js"
1
+ import { DifferenceStreamWriter, UnaryOperator } from '../graph.js'
2
+ import { StreamBuilder } from '../d2.js'
3
+ import { MultiSet } from '../multiset.js'
4
+ import { Index } from '../indexes.js'
5
+ import type { DifferenceStreamReader } from '../graph.js'
6
+ import type { IStreamBuilder, KeyValue } from '../types.js'
7
7
 
8
8
  /**
9
9
  * Base operator for reduction operations (version-free)
@@ -17,7 +17,7 @@ export class ReduceOperator<K, V1, V2> extends UnaryOperator<[K, V1], [K, V2]> {
17
17
  id: number,
18
18
  inputA: DifferenceStreamReader<[K, V1]>,
19
19
  output: DifferenceStreamWriter<[K, V2]>,
20
- f: (values: Array<[V1, number]>) => Array<[V2, number]>
20
+ f: (values: Array<[V1, number]>) => Array<[V2, number]>,
21
21
  ) {
22
22
  super(id, inputA, output)
23
23
  this.#f = f
@@ -109,13 +109,13 @@ export function reduce<
109
109
  return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<KType, R>> => {
110
110
  const output = new StreamBuilder<KeyValue<KType, R>>(
111
111
  stream.graph,
112
- new DifferenceStreamWriter<KeyValue<KType, R>>()
112
+ new DifferenceStreamWriter<KeyValue<KType, R>>(),
113
113
  )
114
114
  const operator = new ReduceOperator<KType, V1Type, R>(
115
115
  stream.graph.getNextOperatorId(),
116
116
  stream.connectReader() as DifferenceStreamReader<KeyValue<KType, V1Type>>,
117
117
  output.writer,
118
- f
118
+ f,
119
119
  )
120
120
  stream.graph.addOperator(operator)
121
121
  return output
@@ -1,8 +1,8 @@
1
- import { DifferenceStreamWriter, LinearUnaryOperator } from "../graph.js"
2
- import { StreamBuilder } from "../d2.js"
3
- import type { IStreamBuilder, PipedOperator } from "../types.js"
4
- import type { DifferenceStreamReader } from "../graph.js"
5
- import type { MultiSet } from "../multiset.js"
1
+ import { DifferenceStreamWriter, LinearUnaryOperator } from '../graph.js'
2
+ import { StreamBuilder } from '../d2.js'
3
+ import type { IStreamBuilder, PipedOperator } from '../types.js'
4
+ import type { DifferenceStreamReader } from '../graph.js'
5
+ import type { MultiSet } from '../multiset.js'
6
6
 
7
7
  /**
8
8
  * Operator that applies a function to each multi-set in the input stream
@@ -14,7 +14,7 @@ export class TapOperator<T> extends LinearUnaryOperator<T, T> {
14
14
  id: number,
15
15
  inputA: DifferenceStreamReader<T>,
16
16
  output: DifferenceStreamWriter<T>,
17
- f: (data: MultiSet<T>) => void
17
+ f: (data: MultiSet<T>) => void,
18
18
  ) {
19
19
  super(id, inputA, output)
20
20
  this.#f = f
@@ -36,13 +36,13 @@ export function tap<T>(f: (data: MultiSet<T>) => void): PipedOperator<T, T> {
36
36
  return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
37
37
  const output = new StreamBuilder<T>(
38
38
  stream.graph,
39
- new DifferenceStreamWriter<T>()
39
+ new DifferenceStreamWriter<T>(),
40
40
  )
41
41
  const operator = new TapOperator<T>(
42
42
  stream.graph.getNextOperatorId(),
43
43
  stream.connectReader(),
44
44
  output.writer,
45
- f
45
+ f,
46
46
  )
47
47
  stream.graph.addOperator(operator)
48
48
  return output
@@ -1,6 +1,6 @@
1
- import { MultiSet } from "../multiset.js"
2
- import { reduce } from "./reduce.js"
3
- import type { IStreamBuilder, KeyValue, PipedOperator } from "../types"
1
+ import { MultiSet } from '../multiset.js'
2
+ import { reduce } from './reduce.js'
3
+ import type { IStreamBuilder, KeyValue, PipedOperator } from '../types'
4
4
 
5
5
  interface TopKOptions {
6
6
  limit?: number
@@ -24,7 +24,7 @@ export function topK<
24
24
  T,
25
25
  >(
26
26
  comparator: (a: V1Type, b: V1Type) => number,
27
- options?: TopKOptions
27
+ options?: TopKOptions,
28
28
  ): PipedOperator<T, T> {
29
29
  const limit = options?.limit ?? Infinity
30
30
  const offset = options?.offset ?? 0
@@ -38,7 +38,7 @@ export function topK<
38
38
  .getInner()
39
39
  .sort((a, b) => comparator(a[0] as V1Type, b[0] as V1Type))
40
40
  return sortedValues.slice(offset, offset + limit)
41
- })
41
+ }),
42
42
  )
43
43
  return reduced as IStreamBuilder<T>
44
44
  }
@@ -62,13 +62,13 @@ export function topKWithIndex<
62
62
  T,
63
63
  >(
64
64
  comparator: (a: V1Type, b: V1Type) => number,
65
- options?: TopKOptions
65
+ options?: TopKOptions,
66
66
  ): PipedOperator<T, KeyValue<KType, [V1Type, number]>> {
67
67
  const limit = options?.limit ?? Infinity
68
68
  const offset = options?.offset ?? 0
69
69
 
70
70
  return (
71
- stream: IStreamBuilder<T>
71
+ stream: IStreamBuilder<T>,
72
72
  ): IStreamBuilder<KeyValue<KType, [V1Type, number]>> => {
73
73
  const reduced = stream.pipe(
74
74
  reduce<KType, V1Type, [V1Type, number], T>((values) => {
@@ -84,7 +84,7 @@ export function topKWithIndex<
84
84
  multiplicity,
85
85
  ])
86
86
  return sortedValues
87
- })
87
+ }),
88
88
  )
89
89
  return reduced
90
90
  }
@@ -1,22 +1,18 @@
1
- import { generateKeyBetween } from "fractional-indexing"
2
- import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
3
- import { StreamBuilder } from "../d2.js"
4
- import { MultiSet } from "../multiset.js"
5
- import {
6
- binarySearch,
7
- diffHalfOpen,
8
- globalObjectIdGenerator,
9
- } from "../utils.js"
10
- import type { HRange } from "../utils.js"
11
- import type { DifferenceStreamReader } from "../graph.js"
12
- import type { IStreamBuilder, PipedOperator } from "../types.js"
1
+ import { generateKeyBetween } from 'fractional-indexing'
2
+ import { DifferenceStreamWriter, UnaryOperator } from '../graph.js'
3
+ import { StreamBuilder } from '../d2.js'
4
+ import { MultiSet } from '../multiset.js'
5
+ import { binarySearch, compareKeys, diffHalfOpen } from '../utils.js'
6
+ import type { HRange } from '../utils.js'
7
+ import type { DifferenceStreamReader } from '../graph.js'
8
+ import type { IStreamBuilder, PipedOperator } from '../types.js'
13
9
 
14
10
  export interface TopKWithFractionalIndexOptions {
15
11
  limit?: number
16
12
  offset?: number
17
13
  setSizeCallback?: (getSize: () => number) => void
18
14
  setWindowFn?: (
19
- windowFn: (options: { offset?: number; limit?: number }) => void
15
+ windowFn: (options: { offset?: number; limit?: number }) => void,
20
16
  ) => void
21
17
  }
22
18
 
@@ -61,7 +57,7 @@ class TopKArray<V> implements TopK<V> {
61
57
  constructor(
62
58
  offset: number,
63
59
  limit: number,
64
- comparator: (a: V, b: V) => number
60
+ comparator: (a: V, b: V) => number,
65
61
  ) {
66
62
  this.#topKStart = offset
67
63
  this.#topKEnd = offset + limit
@@ -229,7 +225,7 @@ class TopKArray<V> implements TopK<V> {
229
225
 
230
226
  #findIndex(value: V): number {
231
227
  return binarySearch(this.#sortedValues, indexedValue(value, ``), (a, b) =>
232
- this.#comparator(getValue(a), getValue(b))
228
+ this.#comparator(getValue(a), getValue(b)),
233
229
  )
234
230
  }
235
231
  }
@@ -239,43 +235,34 @@ class TopKArray<V> implements TopK<V> {
239
235
  * This operator maintains fractional indices for sorted elements
240
236
  * and only updates indices when elements move position
241
237
  */
242
- export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
243
- [K, T],
244
- [K, IndexedValue<T>]
245
- > {
238
+ export class TopKWithFractionalIndexOperator<
239
+ K extends string | number,
240
+ T,
241
+ > extends UnaryOperator<[K, T], [K, IndexedValue<T>]> {
246
242
  #index: Map<K, number> = new Map() // maps keys to their multiplicity
247
243
 
248
244
  /**
249
245
  * topK data structure that supports insertions and deletions
250
246
  * and returns changes to the topK.
247
+ * Elements are stored as [key, value] tuples for stable tie-breaking.
251
248
  */
252
- #topK: TopK<TaggedValue<K, T>>
249
+ #topK: TopK<[K, T]>
253
250
 
254
251
  constructor(
255
252
  id: number,
256
253
  inputA: DifferenceStreamReader<[K, T]>,
257
254
  output: DifferenceStreamWriter<[K, IndexedValue<T>]>,
258
255
  comparator: (a: T, b: T) => number,
259
- options: TopKWithFractionalIndexOptions
256
+ options: TopKWithFractionalIndexOptions,
260
257
  ) {
261
258
  super(id, inputA, output)
262
259
  const limit = options.limit ?? Infinity
263
260
  const offset = options.offset ?? 0
264
- const compareTaggedValues = (
265
- a: TaggedValue<K, T>,
266
- b: TaggedValue<K, T>
267
- ) => {
268
- // First compare on the value
269
- const valueComparison = comparator(getVal(a), getVal(b))
270
- if (valueComparison !== 0) {
271
- return valueComparison
272
- }
273
- // If the values are equal, compare on the tag (object identity)
274
- const tieBreakerA = getTag(a)
275
- const tieBreakerB = getTag(b)
276
- return tieBreakerA - tieBreakerB
277
- }
278
- this.#topK = this.createTopK(offset, limit, compareTaggedValues)
261
+ this.#topK = this.createTopK(
262
+ offset,
263
+ limit,
264
+ createKeyedComparator(comparator),
265
+ )
279
266
  options.setSizeCallback?.(() => this.#topK.size)
280
267
  options.setWindowFn?.(this.moveTopK.bind(this))
281
268
  }
@@ -283,8 +270,8 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
283
270
  protected createTopK(
284
271
  offset: number,
285
272
  limit: number,
286
- comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number
287
- ): TopK<TaggedValue<K, T>> {
273
+ comparator: (a: [K, T], b: [K, T]) => number,
274
+ ): TopK<[K, T]> {
288
275
  return new TopKArray(offset, limit, comparator)
289
276
  }
290
277
 
@@ -295,7 +282,7 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
295
282
  moveTopK({ offset, limit }: { offset?: number; limit?: number }) {
296
283
  if (!(this.#topK instanceof TopKArray)) {
297
284
  throw new Error(
298
- `Cannot move B+-tree implementation of TopK with fractional index`
285
+ `Cannot move B+-tree implementation of TopK with fractional index`,
299
286
  )
300
287
  }
301
288
 
@@ -332,24 +319,22 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
332
319
  key: K,
333
320
  value: T,
334
321
  multiplicity: number,
335
- result: Array<[[K, IndexedValue<T>], number]>
322
+ result: Array<[[K, IndexedValue<T>], number]>,
336
323
  ): void {
337
324
  const { oldMultiplicity, newMultiplicity } = this.addKey(key, multiplicity)
338
325
 
339
- let res: TopKChanges<TaggedValue<K, T>> = {
326
+ let res: TopKChanges<[K, T]> = {
340
327
  moveIn: null,
341
328
  moveOut: null,
342
329
  }
343
330
  if (oldMultiplicity <= 0 && newMultiplicity > 0) {
344
331
  // The value was invisible but should now be visible
345
332
  // Need to insert it into the array of sorted values
346
- const taggedValue = tagValue(key, value)
347
- res = this.#topK.insert(taggedValue)
333
+ res = this.#topK.insert([key, value])
348
334
  } else if (oldMultiplicity > 0 && newMultiplicity <= 0) {
349
335
  // The value was visible but should now be invisible
350
336
  // Need to remove it from the array of sorted values
351
- const taggedValue = tagValue(key, value)
352
- res = this.#topK.delete(taggedValue)
337
+ res = this.#topK.delete([key, value])
353
338
  } else {
354
339
  // The value was invisible and it remains invisible
355
340
  // or it was visible and remains visible
@@ -363,28 +348,22 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
363
348
  }
364
349
 
365
350
  private handleMoveIn(
366
- moveIn: IndexedValue<TaggedValue<K, T>> | null,
367
- result: Array<[[K, IndexedValue<T>], number]>
351
+ moveIn: IndexedValue<[K, T]> | null,
352
+ result: Array<[[K, IndexedValue<T>], number]>,
368
353
  ) {
369
354
  if (moveIn) {
370
- const index = getIndex(moveIn)
371
- const taggedValue = getValue(moveIn)
372
- const k = getKey(taggedValue)
373
- const val = getVal(taggedValue)
374
- result.push([[k, [val, index]], 1])
355
+ const [[key, value], index] = moveIn
356
+ result.push([[key, [value, index]], 1])
375
357
  }
376
358
  }
377
359
 
378
360
  private handleMoveOut(
379
- moveOut: IndexedValue<TaggedValue<K, T>> | null,
380
- result: Array<[[K, IndexedValue<T>], number]>
361
+ moveOut: IndexedValue<[K, T]> | null,
362
+ result: Array<[[K, IndexedValue<T>], number]>,
381
363
  ) {
382
364
  if (moveOut) {
383
- const index = getIndex(moveOut)
384
- const taggedValue = getValue(moveOut)
385
- const k = getKey(taggedValue)
386
- const val = getVal(taggedValue)
387
- result.push([[k, [val, index]], -1])
365
+ const [[key, value], index] = moveOut
366
+ result.push([[key, [value, index]], -1])
388
367
  }
389
368
  }
390
369
 
@@ -394,7 +373,7 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
394
373
 
395
374
  private addKey(
396
375
  key: K,
397
- multiplicity: number
376
+ multiplicity: number,
398
377
  ): { oldMultiplicity: number; newMultiplicity: number } {
399
378
  const oldMultiplicity = this.getMultiplicity(key)
400
379
  const newMultiplicity = oldMultiplicity + multiplicity
@@ -417,25 +396,25 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
417
396
  * @param options - An optional object containing limit and offset properties
418
397
  * @returns A piped operator that orders the elements and limits the number of results
419
398
  */
420
- export function topKWithFractionalIndex<KType, T>(
399
+ export function topKWithFractionalIndex<KType extends string | number, T>(
421
400
  comparator: (a: T, b: T) => number,
422
- options?: TopKWithFractionalIndexOptions
401
+ options?: TopKWithFractionalIndexOptions,
423
402
  ): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {
424
403
  const opts = options || {}
425
404
 
426
405
  return (
427
- stream: IStreamBuilder<[KType, T]>
406
+ stream: IStreamBuilder<[KType, T]>,
428
407
  ): IStreamBuilder<[KType, IndexedValue<T>]> => {
429
408
  const output = new StreamBuilder<[KType, IndexedValue<T>]>(
430
409
  stream.graph,
431
- new DifferenceStreamWriter<[KType, IndexedValue<T>]>()
410
+ new DifferenceStreamWriter<[KType, IndexedValue<T>]>(),
432
411
  )
433
412
  const operator = new TopKWithFractionalIndexOperator<KType, T>(
434
413
  stream.graph.getNextOperatorId(),
435
414
  stream.connectReader(),
436
415
  output.writer,
437
416
  comparator,
438
- opts
417
+ opts,
439
418
  )
440
419
  stream.graph.addOperator(operator)
441
420
  return output
@@ -448,7 +427,7 @@ export type IndexedValue<V> = [V, FractionalIndex]
448
427
 
449
428
  export function indexedValue<V>(
450
429
  value: V,
451
- index: FractionalIndex
430
+ index: FractionalIndex,
452
431
  ): IndexedValue<V> {
453
432
  return [value, index]
454
433
  }
@@ -461,21 +440,21 @@ export function getIndex<V>(indexedVal: IndexedValue<V>): FractionalIndex {
461
440
  return indexedVal[1]
462
441
  }
463
442
 
464
- export type Tag = number
465
- export type TaggedValue<K, V> = [K, V, Tag]
466
-
467
- function tagValue<K, V>(key: K, value: V): TaggedValue<K, V> {
468
- return [key, value, globalObjectIdGenerator.getId(key)]
469
- }
470
-
471
- function getKey<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): K {
472
- return tieBreakerTaggedValue[0]
473
- }
474
-
475
- function getVal<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): V {
476
- return tieBreakerTaggedValue[1]
477
- }
478
-
479
- function getTag<K, V>(tieBreakerTaggedValue: TaggedValue<K, V>): Tag {
480
- return tieBreakerTaggedValue[2]
443
+ /**
444
+ * Creates a comparator for [key, value] tuples that first compares values,
445
+ * then uses the row key as a stable tie-breaker.
446
+ */
447
+ function createKeyedComparator<K extends string | number, T>(
448
+ comparator: (a: T, b: T) => number,
449
+ ): (a: [K, T], b: [K, T]) => number {
450
+ return ([aKey, aVal], [bKey, bVal]) => {
451
+ // First compare on the value
452
+ const valueComparison = comparator(aVal, bVal)
453
+ if (valueComparison !== 0) {
454
+ return valueComparison
455
+ }
456
+ // If the values are equal, use the row key as tie-breaker
457
+ // This provides stable, deterministic ordering since keys are string | number
458
+ return compareKeys(aKey, bKey)
459
+ }
481
460
  }
@@ -1,20 +1,19 @@
1
- import { generateKeyBetween } from "fractional-indexing"
2
- import { DifferenceStreamWriter } from "../graph.js"
3
- import { StreamBuilder } from "../d2.js"
1
+ import { generateKeyBetween } from 'fractional-indexing'
2
+ import { DifferenceStreamWriter } from '../graph.js'
3
+ import { StreamBuilder } from '../d2.js'
4
4
  import {
5
5
  TopKWithFractionalIndexOperator,
6
6
  getIndex,
7
7
  getValue,
8
8
  indexedValue,
9
- } from "./topKWithFractionalIndex.js"
10
- import type { IStreamBuilder, PipedOperator } from "../types.js"
9
+ } from './topKWithFractionalIndex.js'
10
+ import type { IStreamBuilder, PipedOperator } from '../types.js'
11
11
  import type {
12
12
  IndexedValue,
13
- TaggedValue,
14
13
  TopK,
15
14
  TopKChanges,
16
15
  TopKWithFractionalIndexOptions,
17
- } from "./topKWithFractionalIndex.js"
16
+ } from './topKWithFractionalIndex.js'
18
17
 
19
18
  interface BTree<Key, Value> {
20
19
  nextLowerPair: (key: Key) => [Key, Value] | undefined
@@ -30,7 +29,7 @@ interface BTreeClass {
30
29
  new <Key, Value>(
31
30
  entries?: Array<[Key, Value]>,
32
31
  compare?: (a: Key, b: Key) => number,
33
- maxNodeSize?: number
32
+ maxNodeSize?: number,
34
33
  ): BTree<Key, Value>
35
34
  }
36
35
 
@@ -61,11 +60,11 @@ class TopKTree<V> implements TopK<V> {
61
60
  constructor(
62
61
  offset: number,
63
62
  limit: number,
64
- comparator: (a: V, b: V) => number
63
+ comparator: (a: V, b: V) => number,
65
64
  ) {
66
65
  if (BTree === undefined) {
67
66
  throw new Error(
68
- `B+ tree not loaded. You need to call loadBTree() before using TopKTree.`
67
+ `B+ tree not loaded. You need to call loadBTree() before using TopKTree.`,
69
68
  )
70
69
  }
71
70
 
@@ -243,17 +242,17 @@ class TopKTree<V> implements TopK<V> {
243
242
  * and only updates indices when elements move position
244
243
  */
245
244
  export class TopKWithFractionalIndexBTreeOperator<
246
- K,
245
+ K extends string | number,
247
246
  T,
248
247
  > extends TopKWithFractionalIndexOperator<K, T> {
249
248
  protected override createTopK(
250
249
  offset: number,
251
250
  limit: number,
252
- comparator: (a: TaggedValue<K, T>, b: TaggedValue<K, T>) => number
253
- ): TopK<TaggedValue<K, T>> {
251
+ comparator: (a: [K, T], b: [K, T]) => number,
252
+ ): TopK<[K, T]> {
254
253
  if (BTree === undefined) {
255
254
  throw new Error(
256
- `B+ tree not loaded. You need to call loadBTree() before using TopKWithFractionalIndexBTreeOperator.`
255
+ `B+ tree not loaded. You need to call loadBTree() before using TopKWithFractionalIndexBTreeOperator.`,
257
256
  )
258
257
  }
259
258
  return new TopKTree(offset, limit, comparator)
@@ -275,31 +274,31 @@ export class TopKWithFractionalIndexBTreeOperator<
275
274
  * @param options - An optional object containing limit and offset properties
276
275
  * @returns A piped operator that orders the elements and limits the number of results
277
276
  */
278
- export function topKWithFractionalIndexBTree<KType, T>(
277
+ export function topKWithFractionalIndexBTree<KType extends string | number, T>(
279
278
  comparator: (a: T, b: T) => number,
280
- options?: TopKWithFractionalIndexOptions
279
+ options?: TopKWithFractionalIndexOptions,
281
280
  ): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {
282
281
  const opts = options || {}
283
282
 
284
283
  if (BTree === undefined) {
285
284
  throw new Error(
286
- `B+ tree not loaded. You need to call loadBTree() before using topKWithFractionalIndexBTree.`
285
+ `B+ tree not loaded. You need to call loadBTree() before using topKWithFractionalIndexBTree.`,
287
286
  )
288
287
  }
289
288
 
290
289
  return (
291
- stream: IStreamBuilder<[KType, T]>
290
+ stream: IStreamBuilder<[KType, T]>,
292
291
  ): IStreamBuilder<[KType, IndexedValue<T>]> => {
293
292
  const output = new StreamBuilder<[KType, IndexedValue<T>]>(
294
293
  stream.graph,
295
- new DifferenceStreamWriter<[KType, IndexedValue<T>]>()
294
+ new DifferenceStreamWriter<[KType, IndexedValue<T>]>(),
296
295
  )
297
296
  const operator = new TopKWithFractionalIndexBTreeOperator<KType, T>(
298
297
  stream.graph.getNextOperatorId(),
299
298
  stream.connectReader(),
300
299
  output.writer,
301
300
  comparator,
302
- opts
301
+ opts,
303
302
  )
304
303
  stream.graph.addOperator(operator)
305
304
  return output