@tanstack/db-ivm 0.1.25 → 0.2.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 (151) hide show
  1. package/README.md +5 -6
  2. package/dist/cjs/d2.d.cts +3 -3
  3. package/dist/cjs/graph.d.cts +2 -2
  4. package/dist/cjs/hashing/hash.cjs +58 -3
  5. package/dist/cjs/hashing/hash.cjs.map +1 -1
  6. package/dist/cjs/hashing/hash.d.cts +9 -0
  7. package/dist/cjs/hashing/index.d.cts +2 -2
  8. package/dist/cjs/hashing/murmur.cjs +26 -15
  9. package/dist/cjs/hashing/murmur.cjs.map +1 -1
  10. package/dist/cjs/hashing/murmur.d.cts +2 -0
  11. package/dist/cjs/index.cjs +25 -51
  12. package/dist/cjs/index.cjs.map +1 -1
  13. package/dist/cjs/index.d.cts +6 -6
  14. package/dist/cjs/indexes.cjs +52 -16
  15. package/dist/cjs/indexes.cjs.map +1 -1
  16. package/dist/cjs/indexes.d.cts +42 -9
  17. package/dist/cjs/multiset.d.cts +8 -2
  18. package/dist/cjs/operators/concat.d.cts +2 -2
  19. package/dist/cjs/operators/consolidate.d.cts +2 -2
  20. package/dist/cjs/operators/distinct.d.cts +2 -2
  21. package/dist/cjs/operators/filter.d.cts +3 -3
  22. package/dist/cjs/operators/groupBy.cjs +33 -36
  23. package/dist/cjs/operators/groupBy.cjs.map +1 -1
  24. package/dist/cjs/operators/groupBy.d.cts +6 -7
  25. package/dist/cjs/operators/groupedOrderBy.d.cts +8 -2
  26. package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs +3 -9
  27. package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs.map +1 -1
  28. package/dist/cjs/operators/groupedTopKWithFractionalIndex.d.cts +24 -11
  29. package/dist/cjs/operators/index.d.cts +15 -21
  30. package/dist/cjs/operators/join.cjs +0 -20
  31. package/dist/cjs/operators/join.cjs.map +1 -1
  32. package/dist/cjs/operators/join.d.cts +29 -30
  33. package/dist/cjs/operators/map.d.cts +3 -3
  34. package/dist/cjs/operators/orderBy.cjs +2 -77
  35. package/dist/cjs/operators/orderBy.cjs.map +1 -1
  36. package/dist/cjs/operators/orderBy.d.cts +10 -24
  37. package/dist/cjs/operators/output.d.cts +3 -3
  38. package/dist/cjs/operators/pipe.d.cts +1 -1
  39. package/dist/cjs/operators/reduce.cjs +36 -5
  40. package/dist/cjs/operators/reduce.cjs.map +1 -1
  41. package/dist/cjs/operators/reduce.d.cts +38 -5
  42. package/dist/cjs/operators/tap.d.cts +3 -3
  43. package/dist/cjs/operators/topKArray.cjs +9 -0
  44. package/dist/cjs/operators/topKArray.cjs.map +1 -1
  45. package/dist/cjs/operators/topKArray.d.cts +17 -2
  46. package/dist/cjs/operators/topKState.cjs +3 -0
  47. package/dist/cjs/operators/topKState.cjs.map +1 -1
  48. package/dist/cjs/operators/topKState.d.cts +45 -8
  49. package/dist/cjs/operators/topKWithFractionalIndex.cjs +5 -9
  50. package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
  51. package/dist/cjs/operators/topKWithFractionalIndex.d.cts +37 -9
  52. package/dist/cjs/types.d.cts +6 -3
  53. package/dist/cjs/utils.cjs.map +1 -1
  54. package/dist/cjs/utils.d.cts +4 -8
  55. package/dist/esm/hashing/hash.d.ts +9 -0
  56. package/dist/esm/hashing/hash.js +60 -5
  57. package/dist/esm/hashing/hash.js.map +1 -1
  58. package/dist/esm/hashing/murmur.d.ts +2 -0
  59. package/dist/esm/hashing/murmur.js +26 -15
  60. package/dist/esm/hashing/murmur.js.map +1 -1
  61. package/dist/esm/index.js +11 -37
  62. package/dist/esm/index.js.map +1 -1
  63. package/dist/esm/indexes.d.ts +4 -1
  64. package/dist/esm/indexes.js +52 -16
  65. package/dist/esm/indexes.js.map +1 -1
  66. package/dist/esm/operators/groupBy.d.ts +1 -5
  67. package/dist/esm/operators/groupBy.js +33 -36
  68. package/dist/esm/operators/groupBy.js.map +1 -1
  69. package/dist/esm/operators/groupedTopKWithFractionalIndex.d.ts +1 -6
  70. package/dist/esm/operators/groupedTopKWithFractionalIndex.js +3 -9
  71. package/dist/esm/operators/groupedTopKWithFractionalIndex.js.map +1 -1
  72. package/dist/esm/operators/index.d.ts +0 -6
  73. package/dist/esm/operators/join.d.ts +1 -26
  74. package/dist/esm/operators/join.js +1 -21
  75. package/dist/esm/operators/join.js.map +1 -1
  76. package/dist/esm/operators/orderBy.d.ts +2 -22
  77. package/dist/esm/operators/orderBy.js +3 -78
  78. package/dist/esm/operators/orderBy.js.map +1 -1
  79. package/dist/esm/operators/reduce.d.ts +11 -2
  80. package/dist/esm/operators/reduce.js +36 -5
  81. package/dist/esm/operators/reduce.js.map +1 -1
  82. package/dist/esm/operators/topKArray.d.ts +6 -0
  83. package/dist/esm/operators/topKArray.js +10 -1
  84. package/dist/esm/operators/topKArray.js.map +1 -1
  85. package/dist/esm/operators/topKState.d.ts +1 -0
  86. package/dist/esm/operators/topKState.js +3 -0
  87. package/dist/esm/operators/topKState.js.map +1 -1
  88. package/dist/esm/operators/topKWithFractionalIndex.d.ts +7 -3
  89. package/dist/esm/operators/topKWithFractionalIndex.js +6 -10
  90. package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
  91. package/dist/esm/utils.d.ts +0 -7
  92. package/dist/esm/utils.js.map +1 -1
  93. package/package.json +3 -5
  94. package/src/hashing/hash.ts +87 -5
  95. package/src/hashing/murmur.ts +31 -17
  96. package/src/indexes.ts +65 -16
  97. package/src/operators/groupBy.ts +48 -59
  98. package/src/operators/groupedTopKWithFractionalIndex.ts +4 -15
  99. package/src/operators/index.ts +0 -6
  100. package/src/operators/join.ts +1 -91
  101. package/src/operators/orderBy.ts +6 -141
  102. package/src/operators/reduce.ts +62 -5
  103. package/src/operators/topKArray.ts +15 -0
  104. package/src/operators/topKState.ts +7 -0
  105. package/src/operators/topKWithFractionalIndex.ts +15 -16
  106. package/src/utils.ts +0 -30
  107. package/dist/cjs/operators/count.cjs +0 -35
  108. package/dist/cjs/operators/count.cjs.map +0 -1
  109. package/dist/cjs/operators/count.d.cts +0 -13
  110. package/dist/cjs/operators/debug.cjs +0 -39
  111. package/dist/cjs/operators/debug.cjs.map +0 -1
  112. package/dist/cjs/operators/debug.d.cts +0 -16
  113. package/dist/cjs/operators/filterBy.cjs +0 -19
  114. package/dist/cjs/operators/filterBy.cjs.map +0 -1
  115. package/dist/cjs/operators/filterBy.d.cts +0 -9
  116. package/dist/cjs/operators/keying.cjs +0 -16
  117. package/dist/cjs/operators/keying.cjs.map +0 -1
  118. package/dist/cjs/operators/keying.d.cts +0 -19
  119. package/dist/cjs/operators/negate.cjs +0 -27
  120. package/dist/cjs/operators/negate.cjs.map +0 -1
  121. package/dist/cjs/operators/negate.d.cts +0 -13
  122. package/dist/cjs/operators/topK.cjs +0 -39
  123. package/dist/cjs/operators/topK.cjs.map +0 -1
  124. package/dist/cjs/operators/topK.d.cts +0 -31
  125. package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +0 -28
  126. package/dist/esm/operators/count.d.ts +0 -13
  127. package/dist/esm/operators/count.js +0 -35
  128. package/dist/esm/operators/count.js.map +0 -1
  129. package/dist/esm/operators/debug.d.ts +0 -16
  130. package/dist/esm/operators/debug.js +0 -39
  131. package/dist/esm/operators/debug.js.map +0 -1
  132. package/dist/esm/operators/filterBy.d.ts +0 -9
  133. package/dist/esm/operators/filterBy.js +0 -19
  134. package/dist/esm/operators/filterBy.js.map +0 -1
  135. package/dist/esm/operators/keying.d.ts +0 -19
  136. package/dist/esm/operators/keying.js +0 -16
  137. package/dist/esm/operators/keying.js.map +0 -1
  138. package/dist/esm/operators/negate.d.ts +0 -13
  139. package/dist/esm/operators/negate.js +0 -27
  140. package/dist/esm/operators/negate.js.map +0 -1
  141. package/dist/esm/operators/topK.d.ts +0 -31
  142. package/dist/esm/operators/topK.js +0 -39
  143. package/dist/esm/operators/topK.js.map +0 -1
  144. package/dist/esm/operators/topKWithFractionalIndexBTree.d.ts +0 -28
  145. package/src/operators/count.ts +0 -51
  146. package/src/operators/debug.ts +0 -57
  147. package/src/operators/filterBy.ts +0 -30
  148. package/src/operators/keying.ts +0 -34
  149. package/src/operators/negate.ts +0 -32
  150. package/src/operators/topK.ts +0 -91
  151. package/src/operators/topKWithFractionalIndexBTree.ts +0 -297
@@ -1,30 +0,0 @@
1
- import { map } from './map.js'
2
- import { innerJoin } from './join.js'
3
- import { consolidate } from './consolidate.js'
4
- import type { IStreamBuilder, KeyValue, PipedOperator } from '../types.js'
5
-
6
- /**
7
- * Filters the elements of a keyed stream, by keys of another stream.
8
- * This allows you to build pipelies where you have multiple outputs that are related,
9
- * such as a streams of issues and comments for a project.
10
- *
11
- * @param other - The other stream to filter by, which must have the same key type as the input stream
12
- */
13
- export function filterBy<
14
- K,
15
- V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,
16
- T,
17
- >(
18
- other: IStreamBuilder<KeyValue<K, unknown>>,
19
- ): PipedOperator<T, KeyValue<K, V1>> {
20
- return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<K, V1>> => {
21
- const otherKeys = other.pipe(
22
- map(([key, _]) => [key, null] as KeyValue<K, null>),
23
- )
24
- return stream.pipe(
25
- innerJoin(otherKeys),
26
- map(([key, [value, _]]) => [key, value] as KeyValue<K, V1>),
27
- consolidate(),
28
- )
29
- }
30
- }
@@ -1,34 +0,0 @@
1
- import { map } from './map.js'
2
- import type { PipedOperator } from '../types.js'
3
-
4
- /**
5
- * Type for a keyed value
6
- */
7
- export type Keyed<K, V> = [K, V]
8
-
9
- /**
10
- * Takes an unkeyed input and returns a keyed result
11
- * @param keyFn - Function to generate the key for each value
12
- */
13
- export function keyBy<T, K>(
14
- keyFn: (value: T) => K,
15
- ): PipedOperator<T, Keyed<K, T>> {
16
- return map((value: T): Keyed<K, T> => [keyFn(value), value])
17
- }
18
-
19
- /**
20
- * Removes the key from a keyed stream
21
- */
22
- export function unkey<K, V>(): PipedOperator<Keyed<K, V>, V> {
23
- return map(([_, value]: Keyed<K, V>): V => value)
24
- }
25
-
26
- /**
27
- * Takes a keyed input and rekeys it with a new key
28
- * @param keyFn - Function to generate the new key for each value
29
- */
30
- export function rekey<K1, K2, V>(
31
- keyFn: (value: V) => K2,
32
- ): PipedOperator<Keyed<K1, V>, Keyed<K2, V>> {
33
- return map(([_, value]: Keyed<K1, V>): Keyed<K2, V> => [keyFn(value), value])
34
- }
@@ -1,32 +0,0 @@
1
- import { DifferenceStreamWriter, LinearUnaryOperator } from '../graph.js'
2
- import { StreamBuilder } from '../d2.js'
3
- import type { IStreamBuilder, PipedOperator } from '../types.js'
4
- import type { MultiSet } from '../multiset.js'
5
-
6
- /**
7
- * Operator that negates the multiplicities in the input stream
8
- */
9
- export class NegateOperator<T> extends LinearUnaryOperator<T, T> {
10
- inner(collection: MultiSet<T>): MultiSet<T> {
11
- return collection.negate()
12
- }
13
- }
14
-
15
- /**
16
- * Negates the multiplicities in the input stream
17
- */
18
- export function negate<T>(): PipedOperator<T, T> {
19
- return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
20
- const output = new StreamBuilder<T>(
21
- stream.graph,
22
- new DifferenceStreamWriter<T>(),
23
- )
24
- const operator = new NegateOperator<T>(
25
- stream.graph.getNextOperatorId(),
26
- stream.connectReader(),
27
- output.writer,
28
- )
29
- stream.graph.addOperator(operator)
30
- return output
31
- }
32
- }
@@ -1,91 +0,0 @@
1
- import { MultiSet } from '../multiset.js'
2
- import { reduce } from './reduce.js'
3
- import type { IStreamBuilder, KeyValue, PipedOperator } from '../types'
4
-
5
- interface TopKOptions {
6
- limit?: number
7
- offset?: number
8
- }
9
-
10
- /**
11
- * Limits the number of results based on a comparator, with optional offset.
12
- * This works on a keyed stream, where the key is the first element of the tuple
13
- * The ordering is withing a key group, i.e. elements are sorted within a key group
14
- * and the limit + offset is applied to that sorted group.
15
- * To order the entire stream, key by the same value for all elements such as null.
16
- *
17
- * @param comparator - A function that compares two elements
18
- * @param options - An optional object containing limit and offset properties
19
- * @returns A piped operator that limits the number of results
20
- */
21
- export function topK<
22
- KType extends T extends KeyValue<infer K, infer _V> ? K : never,
23
- V1Type extends T extends KeyValue<KType, infer V> ? V : never,
24
- T,
25
- >(
26
- comparator: (a: V1Type, b: V1Type) => number,
27
- options?: TopKOptions,
28
- ): PipedOperator<T, T> {
29
- const limit = options?.limit ?? Infinity
30
- const offset = options?.offset ?? 0
31
-
32
- return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
33
- const reduced = stream.pipe(
34
- reduce((values) => {
35
- // `values` is a list of tuples, first element is the value, second is the multiplicity
36
- const consolidated = new MultiSet(values).consolidate()
37
- const sortedValues = consolidated
38
- .getInner()
39
- .sort((a, b) => comparator(a[0] as V1Type, b[0] as V1Type))
40
- return sortedValues.slice(offset, offset + limit)
41
- }),
42
- )
43
- return reduced as IStreamBuilder<T>
44
- }
45
- }
46
-
47
- /**
48
- * Limits the number of results based on a comparator, with optional offset.
49
- * This works on a keyed stream, where the key is the first element of the tuple
50
- * The ordering is withing a key group, i.e. elements are sorted within a key group
51
- * and the limit + offset is applied to that sorted group.
52
- * To order the entire stream, key by the same value for all elements such as null.
53
- * Adds the index of the element to the result as [key, [value, index]]
54
- *
55
- * @param comparator - A function that compares two elements
56
- * @param options - An optional object containing limit and offset properties
57
- * @returns A piped operator that orders the elements and limits the number of results
58
- */
59
- export function topKWithIndex<
60
- KType extends T extends KeyValue<infer K, infer _V> ? K : never,
61
- V1Type extends T extends KeyValue<KType, infer V> ? V : never,
62
- T,
63
- >(
64
- comparator: (a: V1Type, b: V1Type) => number,
65
- options?: TopKOptions,
66
- ): PipedOperator<T, KeyValue<KType, [V1Type, number]>> {
67
- const limit = options?.limit ?? Infinity
68
- const offset = options?.offset ?? 0
69
-
70
- return (
71
- stream: IStreamBuilder<T>,
72
- ): IStreamBuilder<KeyValue<KType, [V1Type, number]>> => {
73
- const reduced = stream.pipe(
74
- reduce<KType, V1Type, [V1Type, number], T>((values) => {
75
- // `values` is a list of tuples, first element is the value, second is the multiplicity
76
- const consolidated = new MultiSet(values).consolidate()
77
- let i = offset
78
- const sortedValues = consolidated
79
- .getInner()
80
- .sort((a, b) => comparator(a[0], b[0]))
81
- .slice(offset, offset + limit)
82
- .map(([value, multiplicity]): [[V1Type, number], number] => [
83
- [value, i++],
84
- multiplicity,
85
- ])
86
- return sortedValues
87
- }),
88
- )
89
- return reduced
90
- }
91
- }
@@ -1,297 +0,0 @@
1
- import { generateKeyBetween } from 'fractional-indexing'
2
- import { DifferenceStreamWriter } from '../graph.js'
3
- import { StreamBuilder } from '../d2.js'
4
- import { TopKWithFractionalIndexOperator } from './topKWithFractionalIndex.js'
5
- import { getIndex, getValue, indexedValue } from './topKArray.js'
6
- import type { IndexedValue, TopK, TopKChanges } from './topKArray.js'
7
- import type { IStreamBuilder, PipedOperator } from '../types.js'
8
- import type { TopKWithFractionalIndexOptions } from './topKWithFractionalIndex.js'
9
-
10
- interface BTree<Key, Value> {
11
- nextLowerPair: (key: Key) => [Key, Value] | undefined
12
- nextHigherPair: (key: Key) => [Key, Value] | undefined
13
- set: (key: Key, value: Value, overwrite?: boolean) => boolean
14
- maxKey: () => Key | undefined
15
- get: (key: Key, defaultValue?: Value) => Value | undefined
16
- delete: (key: Key) => boolean
17
- size: number
18
- }
19
-
20
- interface BTreeClass {
21
- new <Key, Value>(
22
- entries?: Array<[Key, Value]>,
23
- compare?: (a: Key, b: Key) => number,
24
- maxNodeSize?: number,
25
- ): BTree<Key, Value>
26
- }
27
-
28
- let BTree: BTreeClass | undefined
29
-
30
- export async function loadBTree() {
31
- if (BTree === undefined) {
32
- const { default: _BTreeClass } = await import(`sorted-btree`)
33
- BTree = _BTreeClass
34
- }
35
- }
36
-
37
- /**
38
- * Implementation of a topK data structure that uses a B+ tree.
39
- * The tree allows for logarithmic time insertions and deletions.
40
- */
41
- class TopKTree<V> implements TopK<V> {
42
- #comparator: (a: V, b: V) => number
43
- // topK is a window at position [topKStart, topKEnd[
44
- // i.e. `topKStart` is inclusive and `topKEnd` is exclusive
45
- #topKStart: number
46
- #topKEnd: number
47
-
48
- #tree: BTree<V, IndexedValue<V>>
49
- #topKFirstElem: IndexedValue<V> | null = null // inclusive
50
- #topKLastElem: IndexedValue<V> | null = null // inclusive
51
-
52
- constructor(
53
- offset: number,
54
- limit: number,
55
- comparator: (a: V, b: V) => number,
56
- ) {
57
- if (BTree === undefined) {
58
- throw new Error(
59
- `B+ tree not loaded. You need to call loadBTree() before using TopKTree.`,
60
- )
61
- }
62
-
63
- this.#topKStart = offset
64
- this.#topKEnd = offset + limit
65
- this.#comparator = comparator
66
- this.#tree = new BTree(undefined, comparator)
67
- }
68
-
69
- get size(): number {
70
- const offset = this.#topKStart
71
- const limit = this.#topKEnd - this.#topKStart
72
- const available = this.#tree.size - offset
73
- return Math.max(0, Math.min(limit, available))
74
- }
75
-
76
- /**
77
- * Insert a *new* value.
78
- * Ignores the value if it is already present.
79
- */
80
- insert(value: V): TopKChanges<V> {
81
- const result: TopKChanges<V> = { moveIn: null, moveOut: null }
82
-
83
- // Get the elements before and after the value
84
- const [, indexedValueBefore] = this.#tree.nextLowerPair(value) ?? [
85
- null,
86
- null,
87
- ]
88
- const [, indexedValueAfter] = this.#tree.nextHigherPair(value) ?? [
89
- null,
90
- null,
91
- ]
92
-
93
- const indexBefore = indexedValueBefore ? getIndex(indexedValueBefore) : null
94
- const indexAfter = indexedValueAfter ? getIndex(indexedValueAfter) : null
95
-
96
- // Generate a fractional index for the value
97
- // based on the fractional indices of the elements before and after it
98
- const fractionalIndex = generateKeyBetween(indexBefore, indexAfter)
99
- const insertedElem = indexedValue(value, fractionalIndex)
100
-
101
- // Insert the value into the tree
102
- const inserted = this.#tree.set(value, insertedElem, false)
103
- if (!inserted) {
104
- // The value was already present in the tree
105
- // ignore this insertions since we don't support overwrites!
106
- return result
107
- }
108
-
109
- if (
110
- this.#topKStart === this.#topKEnd ||
111
- this.#tree.size - 1 < this.#topKStart
112
- ) {
113
- // The window is empty, or there aren't enough rows to reach its offset.
114
- return result
115
- }
116
-
117
- if (this.#topKFirstElem) {
118
- // We have a topK containing at least 1 element
119
- if (this.#comparator(value, getValue(this.#topKFirstElem)) < 0) {
120
- // The element was inserted before the topK
121
- // so it moves the element that is right before the topK into the topK
122
- const firstElem = getValue(this.#topKFirstElem)
123
- const [, newFirstElem] = this.#tree.nextLowerPair(firstElem)!
124
- this.#topKFirstElem = newFirstElem
125
- result.moveIn = this.#topKFirstElem
126
- } else if (
127
- !this.#topKLastElem ||
128
- this.#comparator(value, getValue(this.#topKLastElem)) < 0
129
- ) {
130
- // The element was inserted within the topK
131
- result.moveIn = insertedElem
132
- }
133
-
134
- if (
135
- this.#topKLastElem &&
136
- this.#comparator(value, getValue(this.#topKLastElem)) < 0
137
- ) {
138
- // The element was inserted before or within the topK
139
- // the newly inserted element pushes the last element of the topK out of the topK
140
- // so the one before that becomes the new last element of the topK
141
- const lastElem = this.#topKLastElem
142
- const lastValue = getValue(lastElem)
143
- const [, newLastElem] = this.#tree.nextLowerPair(lastValue)!
144
- this.#topKLastElem = newLastElem
145
- result.moveOut = lastElem
146
- }
147
- }
148
-
149
- // If the tree has as many elements as the offset (i.e. #topKStart)
150
- // then the insertion shifted the elements 1 position to the right
151
- // and the last element in the tree is now the first element of the topK
152
- if (this.#tree.size - 1 === this.#topKStart) {
153
- const topKFirstKey = this.#tree.maxKey()!
154
- this.#topKFirstElem = this.#tree.get(topKFirstKey)!
155
- result.moveIn = this.#topKFirstElem
156
- }
157
-
158
- // By inserting this new element we now have a complete topK
159
- // store the last element of the topK
160
- if (this.#tree.size === this.#topKEnd) {
161
- const topKLastKey = this.#tree.maxKey()!
162
- this.#topKLastElem = this.#tree.get(topKLastKey)!
163
- }
164
-
165
- return result
166
- }
167
-
168
- delete(value: V): TopKChanges<V> {
169
- const result: TopKChanges<V> = { moveIn: null, moveOut: null }
170
-
171
- const deletedElem = this.#tree.get(value)
172
- const deleted = this.#tree.delete(value)
173
- if (!deleted) {
174
- return result
175
- }
176
-
177
- if (!this.#topKFirstElem) {
178
- // We didn't have a topK before the delete
179
- // so we still can't have a topK after the delete
180
- return result
181
- }
182
-
183
- if (this.#comparator(value, getValue(this.#topKFirstElem)) <= 0) {
184
- // Deleting at or before the first selected value advances that boundary.
185
- result.moveOut = this.#topKFirstElem
186
- // the element that was right after the first element of the topK
187
- // is now the new first element of the topK
188
- const firstElem = getValue(this.#topKFirstElem)
189
- const [, newFirstElem] = this.#tree.nextHigherPair(firstElem) ?? [
190
- null,
191
- null,
192
- ]
193
- this.#topKFirstElem = newFirstElem
194
- } else if (
195
- !this.#topKLastElem ||
196
- // TODO: if on equal order the element is inserted *after* the already existing one
197
- // then this check should become < 0
198
- this.#comparator(value, getValue(this.#topKLastElem)) <= 0
199
- ) {
200
- // The element we deleted was within the topK
201
- // so we need to signal that that element is no longer in the topK
202
- result.moveOut = deletedElem!
203
- }
204
-
205
- if (
206
- this.#topKLastElem &&
207
- // TODO: if on equal order the element is inserted *after* the already existing one
208
- // then this check should become < 0
209
- this.#comparator(value, getValue(this.#topKLastElem)) <= 0
210
- ) {
211
- // The element we deleted was before or within the topK
212
- // So the first element after the topK moved one position to the left
213
- // and thus falls into the topK now
214
- const lastElem = this.#topKLastElem
215
- const lastValue = getValue(lastElem)
216
- const [, newLastElem] = this.#tree.nextHigherPair(lastValue) ?? [
217
- null,
218
- null,
219
- ]
220
- this.#topKLastElem = newLastElem
221
- if (newLastElem) {
222
- result.moveIn = newLastElem
223
- }
224
- }
225
-
226
- return result
227
- }
228
- }
229
-
230
- /**
231
- * Operator for fractional indexed topK operations
232
- * This operator maintains fractional indices for sorted elements
233
- * and only updates indices when elements move position
234
- */
235
- export class TopKWithFractionalIndexBTreeOperator<
236
- K extends string | number,
237
- T,
238
- > extends TopKWithFractionalIndexOperator<K, T> {
239
- protected override createTopK(
240
- offset: number,
241
- limit: number,
242
- comparator: (a: [K, T], b: [K, T]) => number,
243
- ): TopK<[K, T]> {
244
- if (BTree === undefined) {
245
- throw new Error(
246
- `B+ tree not loaded. You need to call loadBTree() before using TopKWithFractionalIndexBTreeOperator.`,
247
- )
248
- }
249
- return new TopKTree(offset, limit, comparator)
250
- }
251
- }
252
-
253
- /**
254
- * Limits the number of results based on a comparator, with optional offset.
255
- * This works on a keyed stream, where the key is the first element of the tuple.
256
- * The ordering is within a key group, i.e. elements are sorted within a key group
257
- * and the limit + offset is applied to that sorted group.
258
- * To order the entire stream, key by the same value for all elements such as null.
259
- *
260
- * Uses fractional indexing to minimize the number of changes when elements move positions.
261
- * Each element is assigned a fractional index that is lexicographically sortable.
262
- * When elements move, only the indices of the moved elements are updated, not all elements.
263
- *
264
- * @param comparator - A function that compares two elements
265
- * @param options - An optional object containing limit and offset properties
266
- * @returns A piped operator that orders the elements and limits the number of results
267
- */
268
- export function topKWithFractionalIndexBTree<KType extends string | number, T>(
269
- comparator: (a: T, b: T) => number,
270
- options?: TopKWithFractionalIndexOptions,
271
- ): PipedOperator<[KType, T], [KType, IndexedValue<T>]> {
272
- const opts = options || {}
273
-
274
- if (BTree === undefined) {
275
- throw new Error(
276
- `B+ tree not loaded. You need to call loadBTree() before using topKWithFractionalIndexBTree.`,
277
- )
278
- }
279
-
280
- return (
281
- stream: IStreamBuilder<[KType, T]>,
282
- ): IStreamBuilder<[KType, IndexedValue<T>]> => {
283
- const output = new StreamBuilder<[KType, IndexedValue<T>]>(
284
- stream.graph,
285
- new DifferenceStreamWriter<[KType, IndexedValue<T>]>(),
286
- )
287
- const operator = new TopKWithFractionalIndexBTreeOperator<KType, T>(
288
- stream.graph.getNextOperatorId(),
289
- stream.connectReader(),
290
- output.writer,
291
- comparator,
292
- opts,
293
- )
294
- stream.graph.addOperator(operator)
295
- return output
296
- }
297
- }