@tanstack/db-ivm 0.1.19 → 0.1.22

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 (63) hide show
  1. package/dist/cjs/hashing/hash.cjs +114 -45
  2. package/dist/cjs/hashing/hash.cjs.map +1 -1
  3. package/dist/cjs/hashing/hash.d.cts +7 -0
  4. package/dist/cjs/hashing/murmur.cjs +32 -9
  5. package/dist/cjs/hashing/murmur.cjs.map +1 -1
  6. package/dist/cjs/hashing/murmur.d.cts +1 -0
  7. package/dist/cjs/index.cjs +2 -0
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/index.d.cts +1 -0
  10. package/dist/cjs/operators/distinct.cjs.map +1 -1
  11. package/dist/cjs/operators/distinct.d.cts +1 -1
  12. package/dist/cjs/operators/groupBy.cjs +1 -1
  13. package/dist/cjs/operators/groupBy.cjs.map +1 -1
  14. package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs +4 -5
  15. package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs.map +1 -1
  16. package/dist/cjs/operators/topKArray.cjs +2 -0
  17. package/dist/cjs/operators/topKArray.cjs.map +1 -1
  18. package/dist/cjs/operators/topKState.cjs +30 -0
  19. package/dist/cjs/operators/topKState.cjs.map +1 -1
  20. package/dist/cjs/operators/topKState.d.cts +9 -0
  21. package/dist/cjs/operators/topKWithFractionalIndex.cjs +4 -5
  22. package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
  23. package/dist/cjs/utils.cjs +8 -0
  24. package/dist/cjs/utils.cjs.map +1 -1
  25. package/dist/esm/hashing/hash.d.ts +7 -0
  26. package/dist/esm/hashing/hash.js +116 -47
  27. package/dist/esm/hashing/hash.js.map +1 -1
  28. package/dist/esm/hashing/murmur.d.ts +1 -0
  29. package/dist/esm/hashing/murmur.js +32 -9
  30. package/dist/esm/hashing/murmur.js.map +1 -1
  31. package/dist/esm/index.d.ts +1 -0
  32. package/dist/esm/index.js +2 -0
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/operators/distinct.d.ts +1 -1
  35. package/dist/esm/operators/distinct.js.map +1 -1
  36. package/dist/esm/operators/groupBy.js +1 -1
  37. package/dist/esm/operators/groupBy.js.map +1 -1
  38. package/dist/esm/operators/groupedTopKWithFractionalIndex.js +5 -6
  39. package/dist/esm/operators/groupedTopKWithFractionalIndex.js.map +1 -1
  40. package/dist/esm/operators/topKArray.js +2 -0
  41. package/dist/esm/operators/topKArray.js.map +1 -1
  42. package/dist/esm/operators/topKState.d.ts +9 -0
  43. package/dist/esm/operators/topKState.js +31 -1
  44. package/dist/esm/operators/topKState.js.map +1 -1
  45. package/dist/esm/operators/topKWithFractionalIndex.js +5 -6
  46. package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
  47. package/dist/esm/utils.js +8 -0
  48. package/dist/esm/utils.js.map +1 -1
  49. package/package.json +5 -2
  50. package/src/hashing/hash.ts +189 -57
  51. package/src/hashing/murmur.ts +44 -10
  52. package/src/index.ts +1 -0
  53. package/src/operators/distinct.ts +5 -3
  54. package/src/operators/groupBy.ts +7 -4
  55. package/src/operators/groupedTopKWithFractionalIndex.ts +10 -6
  56. package/src/operators/topKArray.ts +4 -0
  57. package/src/operators/topKState.ts +42 -0
  58. package/src/operators/topKWithFractionalIndex.ts +10 -6
  59. package/src/operators/topKWithFractionalIndexBTree.ts +7 -8
  60. package/src/utils.ts +8 -0
  61. package/dist/cjs/operators/orderByBTree.d.cts +0 -3
  62. package/dist/esm/operators/orderByBTree.d.ts +0 -3
  63. package/src/operators/orderByBTree.ts +0 -20
@@ -1,4 +1,6 @@
1
+ import { equalHashValues } from '../hashing/hash.js'
1
2
  import { TopKArray } from './topKArray.js'
3
+ import type { MultiSet } from '../multiset.js'
2
4
  import type {
3
5
  IndexedValue,
4
6
  TopK,
@@ -6,6 +8,46 @@ import type {
6
8
  TopKMoveChanges,
7
9
  } from './topKArray.js'
8
10
 
11
+ /** Apply a keyed batch's retractions before additions. An outer join can emit
12
+ * a replacement in the opposite order; key multiplicity alone would hide it.
13
+ * Consolidate first so a transient value added and removed in this same turn
14
+ * cannot be mistaken for the final replacement. Use structural relation
15
+ * identity: map can allocate a fresh value for each side of a cancelling pair.
16
+ * No state survives the run.
17
+ */
18
+ export function* topKBatch<K, T>(messages: Array<MultiSet<[K, T]>>) {
19
+ const keyed = new Map<K, Array<[[K, T], number]>>()
20
+ for (const message of messages) {
21
+ for (const [value, weight] of message.getInner()) {
22
+ const entries = keyed.get(value[0])
23
+ if (entries) entries.push([value, weight])
24
+ else keyed.set(value[0], [[value, weight]])
25
+ }
26
+ }
27
+ const batch: Array<[[K, T], number]> = []
28
+ for (const entries of keyed.values()) {
29
+ // Ordering distinct keys needs only the caller's comparator. In particular,
30
+ // do not traverse irrelevant payloads or merge keys on a hash collision.
31
+ if (entries.length === 1) {
32
+ batch.push(entries[0]!)
33
+ continue
34
+ }
35
+ // A per-key scan keeps normal two-version replacements cheap and permits
36
+ // cycles, at the cost of quadratic comparisons for long same-key histories.
37
+ const consolidated: Array<[[K, T], number]> = []
38
+ for (const [value, weight] of entries) {
39
+ const previous = consolidated.find(([candidate]) =>
40
+ equalHashValues(candidate[1], value[1]),
41
+ )
42
+ if (previous) previous[1] += weight
43
+ else consolidated.push([value, weight])
44
+ }
45
+ batch.push(...consolidated)
46
+ }
47
+ for (const entry of batch) if (entry[1] < 0) yield entry
48
+ for (const entry of batch) if (entry[1] > 0) yield entry
49
+ }
50
+
9
51
  /**
10
52
  * Helper class that manages the state for a single topK window.
11
53
  * Encapsulates the multiplicity tracking and topK data structure,
@@ -1,7 +1,12 @@
1
1
  import { DifferenceStreamWriter, UnaryOperator } from '../graph.js'
2
2
  import { StreamBuilder } from '../d2.js'
3
3
  import { MultiSet } from '../multiset.js'
4
- import { TopKState, handleMoveIn, handleMoveOut } from './topKState.js'
4
+ import {
5
+ TopKState,
6
+ handleMoveIn,
7
+ handleMoveOut,
8
+ topKBatch,
9
+ } from './topKState.js'
5
10
  import { TopKArray, createKeyedComparator } from './topKArray.js'
6
11
  import type { IndexedValue, TopK } from './topKArray.js'
7
12
  import type { DifferenceStreamReader } from '../graph.js'
@@ -76,11 +81,10 @@ export class TopKWithFractionalIndexOperator<
76
81
 
77
82
  run(): void {
78
83
  const result: Array<[[K, IndexedValue<T>], number]> = []
79
- for (const message of this.inputMessages()) {
80
- for (const [item, multiplicity] of message.getInner()) {
81
- const [key, value] = item
82
- this.processElement(key, value, multiplicity, result)
83
- }
84
+ for (const [[key, value], multiplicity] of topKBatch(
85
+ this.inputMessages(),
86
+ )) {
87
+ this.processElement(key, value, multiplicity, result)
84
88
  }
85
89
 
86
90
  if (result.length > 0) {
@@ -106,9 +106,11 @@ class TopKTree<V> implements TopK<V> {
106
106
  return result
107
107
  }
108
108
 
109
- if (this.#tree.size - 1 < this.#topKStart) {
110
- // We don't have a topK yet
111
- // so we don't need to do anything
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.
112
114
  return result
113
115
  }
114
116
 
@@ -178,11 +180,8 @@ class TopKTree<V> implements TopK<V> {
178
180
  return result
179
181
  }
180
182
 
181
- if (this.#comparator(value, getValue(this.#topKFirstElem)) < 0) {
182
- // We deleted an element that was before the topK
183
- // so the topK has shifted one position to the left
184
-
185
- // the old first element moves out of the topK
183
+ if (this.#comparator(value, getValue(this.#topKFirstElem)) <= 0) {
184
+ // Deleting at or before the first selected value advances that boundary.
186
185
  result.moveOut = this.#topKFirstElem
187
186
  // the element that was right after the first element of the topK
188
187
  // is now the new first element of the topK
package/src/utils.ts CHANGED
@@ -185,6 +185,14 @@ function range(start: number, end: number): Array<number> {
185
185
  export function compareKeys(a: string | number, b: string | number): number {
186
186
  // Same type: compare directly
187
187
  if (typeof a === typeof b) {
188
+ if (typeof a === `number` && typeof b === `number`) {
189
+ const aIsNaN = Number.isNaN(a)
190
+ const bIsNaN = Number.isNaN(b)
191
+ if (aIsNaN || bIsNaN) {
192
+ if (aIsNaN && bIsNaN) return 0
193
+ return aIsNaN ? 1 : -1
194
+ }
195
+ }
188
196
  if (a < b) return -1
189
197
  if (a > b) return 1
190
198
  return 0
@@ -1,3 +0,0 @@
1
- import { KeyValue } from '../types.js';
2
- import { OrderByOptions } from './orderBy.js';
3
- export declare function orderByWithFractionalIndexBTree<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: import('../types.js').IStreamBuilder<T>) => import('../types.js').IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
@@ -1,3 +0,0 @@
1
- import { KeyValue } from '../types.js';
2
- import { OrderByOptions } from './orderBy.js';
3
- export declare function orderByWithFractionalIndexBTree<T extends KeyValue<unknown, unknown>, Ve = unknown>(valueExtractor: (value: T extends KeyValue<unknown, infer V> ? V : never) => Ve, options?: OrderByOptions<Ve>): (stream: import('../types.js').IStreamBuilder<T>) => import('../types.js').IStreamBuilder<[T extends KeyValue<infer K, unknown> ? K : never, [T extends KeyValue<unknown, infer V> ? V : never, string]]>;
@@ -1,20 +0,0 @@
1
- import { orderByWithFractionalIndexBase } from './orderBy.js'
2
- import { topKWithFractionalIndexBTree } from './topKWithFractionalIndexBTree.js'
3
- import type { KeyValue } from '../types.js'
4
- import type { OrderByOptions } from './orderBy.js'
5
-
6
- export function orderByWithFractionalIndexBTree<
7
- T extends KeyValue<unknown, unknown>,
8
- Ve = unknown,
9
- >(
10
- valueExtractor: (
11
- value: T extends KeyValue<unknown, infer V> ? V : never,
12
- ) => Ve,
13
- options?: OrderByOptions<Ve>,
14
- ) {
15
- return orderByWithFractionalIndexBase(
16
- topKWithFractionalIndexBTree,
17
- valueExtractor,
18
- options,
19
- )
20
- }