@tanstack/db-ivm 0.1.1 → 0.1.3

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 (92) hide show
  1. package/dist/cjs/hashIndex.cjs +83 -0
  2. package/dist/cjs/hashIndex.cjs.map +1 -0
  3. package/dist/cjs/hashIndex.d.cts +25 -0
  4. package/dist/cjs/hashing/hash.cjs +122 -0
  5. package/dist/cjs/hashing/hash.cjs.map +1 -0
  6. package/dist/cjs/hashing/hash.d.cts +1 -0
  7. package/dist/cjs/hashing/index.d.cts +2 -0
  8. package/dist/cjs/hashing/murmur.cjs +103 -0
  9. package/dist/cjs/hashing/murmur.cjs.map +1 -0
  10. package/dist/cjs/hashing/murmur.d.cts +26 -0
  11. package/dist/cjs/index.cjs +3 -0
  12. package/dist/cjs/index.cjs.map +1 -1
  13. package/dist/cjs/indexes.cjs +97 -45
  14. package/dist/cjs/indexes.cjs.map +1 -1
  15. package/dist/cjs/indexes.d.cts +0 -2
  16. package/dist/cjs/multiset.cjs +2 -1
  17. package/dist/cjs/multiset.cjs.map +1 -1
  18. package/dist/cjs/operators/distinct.cjs +2 -2
  19. package/dist/cjs/operators/distinct.cjs.map +1 -1
  20. package/dist/cjs/operators/groupBy.cjs +5 -4
  21. package/dist/cjs/operators/groupBy.cjs.map +1 -1
  22. package/dist/cjs/operators/groupBy.d.cts +1 -1
  23. package/dist/cjs/operators/index.d.cts +1 -0
  24. package/dist/cjs/operators/orderBy.cjs +3 -1
  25. package/dist/cjs/operators/orderBy.cjs.map +1 -1
  26. package/dist/cjs/operators/orderBy.d.cts +6 -2
  27. package/dist/cjs/operators/tap.cjs +46 -0
  28. package/dist/cjs/operators/tap.cjs.map +1 -0
  29. package/dist/cjs/operators/tap.d.cts +18 -0
  30. package/dist/cjs/operators/topKWithFractionalIndex.cjs +13 -3
  31. package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
  32. package/dist/cjs/operators/topKWithFractionalIndex.d.cts +2 -0
  33. package/dist/cjs/utils.cjs +13 -33
  34. package/dist/cjs/utils.cjs.map +1 -1
  35. package/dist/cjs/utils.d.cts +7 -4
  36. package/dist/cjs/valueIndex.cjs +74 -0
  37. package/dist/cjs/valueIndex.cjs.map +1 -0
  38. package/dist/cjs/valueIndex.d.cts +17 -0
  39. package/dist/esm/hashIndex.d.ts +25 -0
  40. package/dist/esm/hashIndex.js +83 -0
  41. package/dist/esm/hashIndex.js.map +1 -0
  42. package/dist/esm/hashing/hash.d.ts +1 -0
  43. package/dist/esm/hashing/hash.js +122 -0
  44. package/dist/esm/hashing/hash.js.map +1 -0
  45. package/dist/esm/hashing/index.d.ts +2 -0
  46. package/dist/esm/hashing/murmur.d.ts +26 -0
  47. package/dist/esm/hashing/murmur.js +103 -0
  48. package/dist/esm/hashing/murmur.js.map +1 -0
  49. package/dist/esm/index.js +3 -0
  50. package/dist/esm/index.js.map +1 -1
  51. package/dist/esm/indexes.d.ts +0 -2
  52. package/dist/esm/indexes.js +98 -46
  53. package/dist/esm/indexes.js.map +1 -1
  54. package/dist/esm/multiset.js +2 -1
  55. package/dist/esm/multiset.js.map +1 -1
  56. package/dist/esm/operators/distinct.js +1 -1
  57. package/dist/esm/operators/distinct.js.map +1 -1
  58. package/dist/esm/operators/groupBy.d.ts +1 -1
  59. package/dist/esm/operators/groupBy.js +5 -4
  60. package/dist/esm/operators/groupBy.js.map +1 -1
  61. package/dist/esm/operators/index.d.ts +1 -0
  62. package/dist/esm/operators/orderBy.d.ts +6 -2
  63. package/dist/esm/operators/orderBy.js +3 -1
  64. package/dist/esm/operators/orderBy.js.map +1 -1
  65. package/dist/esm/operators/tap.d.ts +18 -0
  66. package/dist/esm/operators/tap.js +46 -0
  67. package/dist/esm/operators/tap.js.map +1 -0
  68. package/dist/esm/operators/topKWithFractionalIndex.d.ts +2 -0
  69. package/dist/esm/operators/topKWithFractionalIndex.js +13 -3
  70. package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
  71. package/dist/esm/utils.d.ts +7 -4
  72. package/dist/esm/utils.js +13 -33
  73. package/dist/esm/utils.js.map +1 -1
  74. package/dist/esm/valueIndex.d.ts +17 -0
  75. package/dist/esm/valueIndex.js +74 -0
  76. package/dist/esm/valueIndex.js.map +1 -0
  77. package/package.json +1 -3
  78. package/src/hashIndex.ts +94 -0
  79. package/src/hashing/hash.ts +157 -0
  80. package/src/hashing/index.ts +2 -0
  81. package/src/hashing/murmur.ts +128 -0
  82. package/src/indexes.ts +106 -47
  83. package/src/multiset.ts +1 -1
  84. package/src/operators/distinct.ts +4 -4
  85. package/src/operators/groupBy.ts +7 -4
  86. package/src/operators/index.ts +1 -0
  87. package/src/operators/orderBy.ts +8 -2
  88. package/src/operators/tap.ts +53 -0
  89. package/src/operators/topKWithFractionalIndex.ts +14 -2
  90. package/src/operators/topKWithFractionalIndexBTree.ts +7 -0
  91. package/src/utils.ts +31 -48
  92. package/src/valueIndex.ts +78 -0
@@ -12,6 +12,10 @@ export interface OrderByOptions<Ve> {
12
12
  offset?: number
13
13
  }
14
14
 
15
+ type OrderByWithFractionalIndexOptions<Ve> = OrderByOptions<Ve> & {
16
+ setSizeCallback?: (getSize: () => number) => void
17
+ }
18
+
15
19
  /**
16
20
  * Orders the elements and limits the number of results, with optional offset
17
21
  * This requires a keyed stream, and uses the `topK` operator to order all the elements.
@@ -136,13 +140,14 @@ export function orderByWithFractionalIndexBase<
136
140
  valueExtractor: (
137
141
  value: T extends KeyValue<unknown, infer V> ? V : never
138
142
  ) => Ve,
139
- options?: OrderByOptions<Ve>
143
+ options?: OrderByWithFractionalIndexOptions<Ve>
140
144
  ) {
141
145
  type KeyType = T extends KeyValue<infer K, unknown> ? K : never
142
146
  type ValueType = T extends KeyValue<unknown, infer V> ? V : never
143
147
 
144
148
  const limit = options?.limit ?? Infinity
145
149
  const offset = options?.offset ?? 0
150
+ const setSizeCallback = options?.setSizeCallback
146
151
  const comparator =
147
152
  options?.comparator ??
148
153
  ((a, b) => {
@@ -162,6 +167,7 @@ export function orderByWithFractionalIndexBase<
162
167
  {
163
168
  limit,
164
169
  offset,
170
+ setSizeCallback,
165
171
  }
166
172
  ),
167
173
  consolidate()
@@ -185,7 +191,7 @@ export function orderByWithFractionalIndex<
185
191
  valueExtractor: (
186
192
  value: T extends KeyValue<unknown, infer V> ? V : never
187
193
  ) => Ve,
188
- options?: OrderByOptions<Ve>
194
+ options?: OrderByWithFractionalIndexOptions<Ve>
189
195
  ) {
190
196
  return orderByWithFractionalIndexBase(
191
197
  topKWithFractionalIndex,
@@ -0,0 +1,53 @@
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
+
7
+ /**
8
+ * Operator that applies a function to each element in the input stream
9
+ */
10
+ export class TapOperator<T> extends LinearUnaryOperator<T, T> {
11
+ #f: (data: T) => void
12
+
13
+ constructor(
14
+ id: number,
15
+ inputA: DifferenceStreamReader<T>,
16
+ output: DifferenceStreamWriter<T>,
17
+ f: (data: T) => void
18
+ ) {
19
+ super(id, inputA, output)
20
+ this.#f = f
21
+ }
22
+
23
+ inner(collection: MultiSet<T>): MultiSet<T> {
24
+ return collection.map((data) => {
25
+ this.#f(data)
26
+ return data
27
+ })
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Invokes a function for each element in the input stream.
33
+ * This operator doesn't modify the stream and is used to perform side effects.
34
+ * @param f - The function to invoke on each element
35
+ * @returns The input stream
36
+ */
37
+ export function tap<T>(f: (data: T) => void): PipedOperator<T, T> {
38
+ return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
39
+ const output = new StreamBuilder<T>(
40
+ stream.graph,
41
+ new DifferenceStreamWriter<T>()
42
+ )
43
+ const operator = new TapOperator<T>(
44
+ stream.graph.getNextOperatorId(),
45
+ stream.connectReader(),
46
+ output.writer,
47
+ f
48
+ )
49
+ stream.graph.addOperator(operator)
50
+ stream.graph.addStream(output.connectReader())
51
+ return output
52
+ }
53
+ }
@@ -9,6 +9,7 @@ import type { IStreamBuilder, PipedOperator } from "../types.js"
9
9
  export interface TopKWithFractionalIndexOptions {
10
10
  limit?: number
11
11
  offset?: number
12
+ setSizeCallback?: (getSize: () => number) => void
12
13
  }
13
14
 
14
15
  export type TopKChanges<V> = {
@@ -23,6 +24,7 @@ export type TopKChanges<V> = {
23
24
  * and returns changes to the topK.
24
25
  */
25
26
  export interface TopK<V> {
27
+ size: number
26
28
  insert: (value: V) => TopKChanges<V>
27
29
  delete: (value: V) => TopKChanges<V>
28
30
  }
@@ -49,6 +51,13 @@ class TopKArray<V> implements TopK<V> {
49
51
  this.#comparator = comparator
50
52
  }
51
53
 
54
+ get size(): number {
55
+ const offset = this.#topKStart
56
+ const limit = this.#topKEnd - this.#topKStart
57
+ const available = this.#sortedValues.length - offset
58
+ return Math.max(0, Math.min(limit, available))
59
+ }
60
+
52
61
  insert(value: V): TopKChanges<V> {
53
62
  const result: TopKChanges<V> = { moveIn: null, moveOut: null }
54
63
 
@@ -169,6 +178,8 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
169
178
  */
170
179
  #topK: TopK<TaggedValue<K, T>>
171
180
 
181
+ #limit: number
182
+
172
183
  constructor(
173
184
  id: number,
174
185
  inputA: DifferenceStreamReader<[K, T]>,
@@ -177,7 +188,7 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
177
188
  options: TopKWithFractionalIndexOptions
178
189
  ) {
179
190
  super(id, inputA, output)
180
- const limit = options.limit ?? Infinity
191
+ this.#limit = options.limit ?? Infinity
181
192
  const offset = options.offset ?? 0
182
193
  const compareTaggedValues = (
183
194
  a: TaggedValue<K, T>,
@@ -193,7 +204,8 @@ export class TopKWithFractionalIndexOperator<K, T> extends UnaryOperator<
193
204
  const tieBreakerB = getTag(b)
194
205
  return tieBreakerA - tieBreakerB
195
206
  }
196
- this.#topK = this.createTopK(offset, limit, compareTaggedValues)
207
+ this.#topK = this.createTopK(offset, this.#limit, compareTaggedValues)
208
+ options.setSizeCallback?.(() => this.#topK.size)
197
209
  }
198
210
 
199
211
  protected createTopK(
@@ -75,6 +75,13 @@ class TopKTree<V> implements TopK<V> {
75
75
  this.#tree = new BTree(undefined, comparator)
76
76
  }
77
77
 
78
+ get size(): number {
79
+ const offset = this.#topKStart
80
+ const limit = this.#topKEnd - this.#topKStart
81
+ const available = this.#tree.size - offset
82
+ return Math.max(0, Math.min(limit, available))
83
+ }
84
+
78
85
  /**
79
86
  * Insert a *new* value.
80
87
  * Ignores the value if it is already present.
package/src/utils.ts CHANGED
@@ -1,4 +1,15 @@
1
- import murmurhash from "murmurhash-js"
1
+ /**
2
+ * Simple assertion function for runtime checks.
3
+ * Throws an error if the condition is false.
4
+ */
5
+ export function assert(
6
+ condition: unknown,
7
+ message?: string
8
+ ): asserts condition {
9
+ if (!condition) {
10
+ throw new Error(message || `Assertion failed`)
11
+ }
12
+ }
2
13
 
3
14
  /**
4
15
  * A map that returns a default value for keys that are not present.
@@ -13,7 +24,8 @@ export class DefaultMap<K, V> extends Map<K, V> {
13
24
 
14
25
  get(key: K): V {
15
26
  if (!this.has(key)) {
16
- this.set(key, this.defaultValue())
27
+ // this.set(key, this.defaultValue())
28
+ return this.defaultValue()
17
29
  }
18
30
  return super.get(key)!
19
31
  }
@@ -45,52 +57,6 @@ export function chunkedArrayPush(array: Array<unknown>, other: Array<unknown>) {
45
57
  }
46
58
  }
47
59
 
48
- const hashCache = new WeakMap()
49
-
50
- /**
51
- * Replacer function for JSON.stringify that converts unsupported types to strings
52
- */
53
- function hashReplacer(_key: string, value: any): any {
54
- if (typeof value === `bigint`) {
55
- return String(value)
56
- } else if (typeof value === `symbol`) {
57
- return String(value)
58
- } else if (typeof value === `function`) {
59
- return String(value)
60
- } else if (value === undefined) {
61
- return `undefined`
62
- } else if (value instanceof Map) {
63
- return `Map(${JSON.stringify(Array.from(value.entries()), hashReplacer)})`
64
- } else if (value instanceof Set) {
65
- return `Set(${JSON.stringify(Array.from(value.values()), hashReplacer)})`
66
- }
67
- return value
68
- }
69
-
70
- /**
71
- * A hash method that caches the hash of a value in a week map
72
- */
73
- export function hash(data: any): string {
74
- if (
75
- data === null ||
76
- data === undefined ||
77
- (typeof data !== `object` && typeof data !== `function`)
78
- ) {
79
- // Can't be cached in the weak map because it's not an object
80
- const serialized = JSON.stringify(data, hashReplacer)
81
- return murmurhash.murmur3(serialized).toString(16)
82
- }
83
-
84
- if (hashCache.has(data)) {
85
- return hashCache.get(data)
86
- }
87
-
88
- const serialized = JSON.stringify(data, hashReplacer)
89
- const hashValue = murmurhash.murmur3(serialized).toString(16)
90
- hashCache.set(data, hashValue)
91
- return hashValue
92
- }
93
-
94
60
  export function binarySearch<T>(
95
61
  array: Array<T>,
96
62
  value: T,
@@ -161,3 +127,20 @@ export class ObjectIdGenerator {
161
127
  * Global instance for cases where a shared object ID space is needed.
162
128
  */
163
129
  export const globalObjectIdGenerator = new ObjectIdGenerator()
130
+
131
+ export function* concatIterable<T>(
132
+ ...iterables: Array<Iterable<T>>
133
+ ): Iterable<T> {
134
+ for (const iterable of iterables) {
135
+ yield* iterable
136
+ }
137
+ }
138
+
139
+ export function* mapIterable<T, U>(
140
+ it: Iterable<T>,
141
+ fn: (t: T) => U
142
+ ): Iterable<U> {
143
+ for (const t of it) {
144
+ yield fn(t)
145
+ }
146
+ }
@@ -0,0 +1,78 @@
1
+ import { hash } from "./hashing/index.js"
2
+
3
+ /**
4
+ * A map from a difference collection trace's keys -> (value, multiplicities) that changed.
5
+ * Used in operations like join and reduce where the operation needs to
6
+ * exploit the key-value structure of the data to run efficiently.
7
+ */
8
+ export class ValueIndex<K, V> {
9
+ #inner: Map<K, [V, number]> // Maps key to the value and its multiplicity
10
+
11
+ constructor() {
12
+ this.#inner = new Map()
13
+ }
14
+
15
+ toString(indent = false): string {
16
+ return `ValueIndex(${JSON.stringify(
17
+ [...this.#inner.entries()],
18
+ undefined,
19
+ indent ? 2 : undefined
20
+ )})`
21
+ }
22
+
23
+ get(key: K): [V, number] | undefined {
24
+ return this.#inner.get(key)
25
+ }
26
+
27
+ getMultiplicity(key: K): number {
28
+ return this.get(key)?.[1] ?? 0
29
+ }
30
+
31
+ entries() {
32
+ return this.#inner.entries()
33
+ }
34
+
35
+ has(key: K): boolean {
36
+ return this.#inner.has(key)
37
+ }
38
+
39
+ delete(key: K): void {
40
+ this.#inner.delete(key)
41
+ }
42
+
43
+ get size(): number {
44
+ return this.#inner.size
45
+ }
46
+
47
+ addValue(key: K, v: [V, number]): void {
48
+ const [value, multiplicity] = v
49
+
50
+ if (multiplicity === 0) {
51
+ return
52
+ }
53
+
54
+ if (this.has(key)) {
55
+ const [currValue, currMultiplicity] = this.get(key)!
56
+ if (hash(value) === hash(currValue)) {
57
+ // Update the multiplicity
58
+ this.#setMultiplicity(key, value, currMultiplicity + multiplicity)
59
+ return
60
+ }
61
+ // Different value, not allowed.
62
+ // ValueIndex only supports one value per key.
63
+ throw new Error(
64
+ `Cannot add value for key ${key} because it already exists in ValueIndex with a different value`
65
+ )
66
+ }
67
+
68
+ this.#inner.set(key, [value, multiplicity])
69
+ }
70
+
71
+ #setMultiplicity(key: K, value: V, multiplicity: number): void {
72
+ if (multiplicity === 0) {
73
+ this.#inner.delete(key)
74
+ } else {
75
+ this.#inner.set(key, [value, multiplicity])
76
+ }
77
+ }
78
+ }