@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.
- package/dist/cjs/hashIndex.cjs +83 -0
- package/dist/cjs/hashIndex.cjs.map +1 -0
- package/dist/cjs/hashIndex.d.cts +25 -0
- package/dist/cjs/hashing/hash.cjs +122 -0
- package/dist/cjs/hashing/hash.cjs.map +1 -0
- package/dist/cjs/hashing/hash.d.cts +1 -0
- package/dist/cjs/hashing/index.d.cts +2 -0
- package/dist/cjs/hashing/murmur.cjs +103 -0
- package/dist/cjs/hashing/murmur.cjs.map +1 -0
- package/dist/cjs/hashing/murmur.d.cts +26 -0
- package/dist/cjs/index.cjs +3 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/indexes.cjs +97 -45
- package/dist/cjs/indexes.cjs.map +1 -1
- package/dist/cjs/indexes.d.cts +0 -2
- package/dist/cjs/multiset.cjs +2 -1
- package/dist/cjs/multiset.cjs.map +1 -1
- package/dist/cjs/operators/distinct.cjs +2 -2
- package/dist/cjs/operators/distinct.cjs.map +1 -1
- package/dist/cjs/operators/groupBy.cjs +5 -4
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/operators/groupBy.d.cts +1 -1
- package/dist/cjs/operators/index.d.cts +1 -0
- package/dist/cjs/operators/orderBy.cjs +3 -1
- package/dist/cjs/operators/orderBy.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.d.cts +6 -2
- package/dist/cjs/operators/tap.cjs +46 -0
- package/dist/cjs/operators/tap.cjs.map +1 -0
- package/dist/cjs/operators/tap.d.cts +18 -0
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +13 -3
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.d.cts +2 -0
- package/dist/cjs/utils.cjs +13 -33
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +7 -4
- package/dist/cjs/valueIndex.cjs +74 -0
- package/dist/cjs/valueIndex.cjs.map +1 -0
- package/dist/cjs/valueIndex.d.cts +17 -0
- package/dist/esm/hashIndex.d.ts +25 -0
- package/dist/esm/hashIndex.js +83 -0
- package/dist/esm/hashIndex.js.map +1 -0
- package/dist/esm/hashing/hash.d.ts +1 -0
- package/dist/esm/hashing/hash.js +122 -0
- package/dist/esm/hashing/hash.js.map +1 -0
- package/dist/esm/hashing/index.d.ts +2 -0
- package/dist/esm/hashing/murmur.d.ts +26 -0
- package/dist/esm/hashing/murmur.js +103 -0
- package/dist/esm/hashing/murmur.js.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes.d.ts +0 -2
- package/dist/esm/indexes.js +98 -46
- package/dist/esm/indexes.js.map +1 -1
- package/dist/esm/multiset.js +2 -1
- package/dist/esm/multiset.js.map +1 -1
- package/dist/esm/operators/distinct.js +1 -1
- package/dist/esm/operators/distinct.js.map +1 -1
- package/dist/esm/operators/groupBy.d.ts +1 -1
- package/dist/esm/operators/groupBy.js +5 -4
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/operators/index.d.ts +1 -0
- package/dist/esm/operators/orderBy.d.ts +6 -2
- package/dist/esm/operators/orderBy.js +3 -1
- package/dist/esm/operators/orderBy.js.map +1 -1
- package/dist/esm/operators/tap.d.ts +18 -0
- package/dist/esm/operators/tap.js +46 -0
- package/dist/esm/operators/tap.js.map +1 -0
- package/dist/esm/operators/topKWithFractionalIndex.d.ts +2 -0
- package/dist/esm/operators/topKWithFractionalIndex.js +13 -3
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/dist/esm/utils.d.ts +7 -4
- package/dist/esm/utils.js +13 -33
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/valueIndex.d.ts +17 -0
- package/dist/esm/valueIndex.js +74 -0
- package/dist/esm/valueIndex.js.map +1 -0
- package/package.json +1 -3
- package/src/hashIndex.ts +94 -0
- package/src/hashing/hash.ts +157 -0
- package/src/hashing/index.ts +2 -0
- package/src/hashing/murmur.ts +128 -0
- package/src/indexes.ts +106 -47
- package/src/multiset.ts +1 -1
- package/src/operators/distinct.ts +4 -4
- package/src/operators/groupBy.ts +7 -4
- package/src/operators/index.ts +1 -0
- package/src/operators/orderBy.ts +8 -2
- package/src/operators/tap.ts +53 -0
- package/src/operators/topKWithFractionalIndex.ts +14 -2
- package/src/operators/topKWithFractionalIndexBTree.ts +7 -0
- package/src/utils.ts +31 -48
- package/src/valueIndex.ts +78 -0
package/src/operators/orderBy.ts
CHANGED
|
@@ -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?:
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|