@tanstack/db-ivm 0.0.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.
- package/README.md +58 -0
- package/package.json +63 -0
- package/src/d2.ts +150 -0
- package/src/graph.ts +131 -0
- package/src/index.ts +4 -0
- package/src/indexes.ts +117 -0
- package/src/multiset.ts +215 -0
- package/src/operators/concat.ts +45 -0
- package/src/operators/consolidate.ts +50 -0
- package/src/operators/count.ts +52 -0
- package/src/operators/debug.ts +58 -0
- package/src/operators/distinct.ts +98 -0
- package/src/operators/filter.ts +48 -0
- package/src/operators/filterBy.ts +31 -0
- package/src/operators/groupBy.ts +354 -0
- package/src/operators/index.ts +18 -0
- package/src/operators/join.ts +265 -0
- package/src/operators/keying.ts +34 -0
- package/src/operators/map.ts +48 -0
- package/src/operators/negate.ts +33 -0
- package/src/operators/orderBy.ts +213 -0
- package/src/operators/orderByBTree.ts +20 -0
- package/src/operators/output.ts +53 -0
- package/src/operators/pipe.ts +55 -0
- package/src/operators/reduce.ts +124 -0
- package/src/operators/topK.ts +92 -0
- package/src/operators/topKWithFractionalIndex.ts +347 -0
- package/src/operators/topKWithFractionalIndexBTree.ts +306 -0
- package/src/types.ts +376 -0
- package/src/utils.ts +163 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# IVM implementation for TanStack DB based on Differential Dataflow
|
|
2
|
+
|
|
3
|
+
This is an implementation of differential dataflow used by TanStack DB, forked from [@electric-sql/d2ts](https://github.com/electric-sql/d2ts), but simplified and without the complexities of multi-dimensional versioning.
|
|
4
|
+
|
|
5
|
+
It is used internally by the TanStack DB with the live queries compiled to a D2 graph. This library doesn't depend on the TanStack DB and so could be used in other projects.
|
|
6
|
+
|
|
7
|
+
The API is almost identical to D2TS, but without the need to specify a version when sending data, or to send a frontier to mark the end of a version.
|
|
8
|
+
|
|
9
|
+
### Basic Usage
|
|
10
|
+
|
|
11
|
+
Here's a simple example that demonstrates the core concepts:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { D2, map, filter, debug, MultiSet } from "@tanstack/db-ivm"
|
|
15
|
+
|
|
16
|
+
// Create a new D2 graph
|
|
17
|
+
const graph = new D2()
|
|
18
|
+
|
|
19
|
+
// Create an input stream
|
|
20
|
+
// We can specify the type of the input stream, here we are using number.
|
|
21
|
+
const input = graph.newInput<number>()
|
|
22
|
+
|
|
23
|
+
// Build a simple pipeline that:
|
|
24
|
+
// 1. Takes numbers as input
|
|
25
|
+
// 2. Adds 5 to each number
|
|
26
|
+
// 3. Filters to keep only even numbers
|
|
27
|
+
// Pipelines can have multiple inputs and outputs.
|
|
28
|
+
const output = input.pipe(
|
|
29
|
+
map((x) => x + 5),
|
|
30
|
+
filter((x) => x % 2 === 0),
|
|
31
|
+
debug("output")
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Finalize the pipeline, after this point we can no longer add operators or
|
|
35
|
+
// inputs
|
|
36
|
+
graph.finalize()
|
|
37
|
+
|
|
38
|
+
// Send some data
|
|
39
|
+
// Data is sent as a MultiSet, which is a map of values to their multiplicity
|
|
40
|
+
// Here we are sending 3 numbers (1-3), each with a multiplicity of 1
|
|
41
|
+
// The key thing to understand is that the MultiSet represents a *change* to
|
|
42
|
+
// the data, not the data itself. "Inserts" and "Deletes" are represented as
|
|
43
|
+
// an element with a multiplicity of 1 or -1 respectively.
|
|
44
|
+
input.sendData(
|
|
45
|
+
new MultiSet([
|
|
46
|
+
[1, 1],
|
|
47
|
+
[2, 1],
|
|
48
|
+
[3, 1],
|
|
49
|
+
])
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// Process the data
|
|
53
|
+
graph.run()
|
|
54
|
+
|
|
55
|
+
// Output will show:
|
|
56
|
+
// 6 (from 1 + 5)
|
|
57
|
+
// 8 (from 3 + 5)
|
|
58
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/db-ivm",
|
|
3
|
+
"description": "Incremental View Maintenance for TanStack DB based on Differential Dataflow",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"fractional-indexing": "^3.2.0",
|
|
7
|
+
"murmurhash-js": "^1.0.0",
|
|
8
|
+
"sorted-btree": "^1.8.1"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/debug": "^4.1.12",
|
|
12
|
+
"@types/murmurhash-js": "^1.0.6",
|
|
13
|
+
"@vitest/coverage-istanbul": "^3.0.9"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": {
|
|
18
|
+
"types": "./dist/esm/index.d.ts",
|
|
19
|
+
"default": "./dist/esm/index.js"
|
|
20
|
+
},
|
|
21
|
+
"require": {
|
|
22
|
+
"types": "./dist/cjs/index.d.cts",
|
|
23
|
+
"default": "./dist/cjs/index.cjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src"
|
|
31
|
+
],
|
|
32
|
+
"main": "dist/cjs/index.cjs",
|
|
33
|
+
"module": "dist/esm/index.js",
|
|
34
|
+
"packageManager": "pnpm@10.6.3",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"typescript": ">=4.7"
|
|
37
|
+
},
|
|
38
|
+
"author": "Sam Willis",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/TanStack/db.git",
|
|
43
|
+
"directory": "packages/db-ivm"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://tanstack.com/db",
|
|
46
|
+
"keywords": [
|
|
47
|
+
"electric",
|
|
48
|
+
"sql",
|
|
49
|
+
"optimistic",
|
|
50
|
+
"typescript",
|
|
51
|
+
"ivm",
|
|
52
|
+
"differential-dataflow"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "vite build",
|
|
56
|
+
"dev": "vite build --watch",
|
|
57
|
+
"lint": "eslint . --fix",
|
|
58
|
+
"test": "npx vitest --run"
|
|
59
|
+
},
|
|
60
|
+
"sideEffects": false,
|
|
61
|
+
"type": "module",
|
|
62
|
+
"types": "dist/esm/index.d.ts"
|
|
63
|
+
}
|
package/src/d2.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { DifferenceStreamWriter } from "./graph.js"
|
|
2
|
+
import type {
|
|
3
|
+
BinaryOperator,
|
|
4
|
+
DifferenceStreamReader,
|
|
5
|
+
UnaryOperator,
|
|
6
|
+
} from "./graph.js"
|
|
7
|
+
import type { MultiSet, MultiSetArray } from "./multiset.js"
|
|
8
|
+
import type { ID2, IStreamBuilder, PipedOperator } from "./types.js"
|
|
9
|
+
|
|
10
|
+
export class D2 implements ID2 {
|
|
11
|
+
#streams: Array<DifferenceStreamReader<any>> = []
|
|
12
|
+
#operators: Array<UnaryOperator<any> | BinaryOperator<any>> = []
|
|
13
|
+
#nextOperatorId = 0
|
|
14
|
+
#finalized = false
|
|
15
|
+
|
|
16
|
+
constructor() {}
|
|
17
|
+
|
|
18
|
+
#checkNotFinalized(): void {
|
|
19
|
+
if (this.#finalized) {
|
|
20
|
+
throw new Error(`Graph already finalized`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getNextOperatorId(): number {
|
|
25
|
+
this.#checkNotFinalized()
|
|
26
|
+
return this.#nextOperatorId++
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
newInput<T>(): RootStreamBuilder<T> {
|
|
30
|
+
this.#checkNotFinalized()
|
|
31
|
+
const writer = new DifferenceStreamWriter<T>()
|
|
32
|
+
// Use the root stream builder that exposes the sendData and sendFrontier methods
|
|
33
|
+
const streamBuilder = new RootStreamBuilder<T>(this, writer)
|
|
34
|
+
this.#streams.push(streamBuilder.connectReader())
|
|
35
|
+
return streamBuilder
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
addOperator(operator: UnaryOperator<any> | BinaryOperator<any>): void {
|
|
39
|
+
this.#checkNotFinalized()
|
|
40
|
+
this.#operators.push(operator)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
addStream(stream: DifferenceStreamReader<any>): void {
|
|
44
|
+
this.#checkNotFinalized()
|
|
45
|
+
this.#streams.push(stream)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
finalize() {
|
|
49
|
+
this.#checkNotFinalized()
|
|
50
|
+
this.#finalized = true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
step(): void {
|
|
54
|
+
if (!this.#finalized) {
|
|
55
|
+
throw new Error(`Graph not finalized`)
|
|
56
|
+
}
|
|
57
|
+
for (const op of this.#operators) {
|
|
58
|
+
op.run()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pendingWork(): boolean {
|
|
63
|
+
return this.#operators.some((op) => op.hasPendingWork())
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
run(): void {
|
|
67
|
+
while (this.pendingWork()) {
|
|
68
|
+
this.step()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class StreamBuilder<T> implements IStreamBuilder<T> {
|
|
74
|
+
#graph: ID2
|
|
75
|
+
#writer: DifferenceStreamWriter<T>
|
|
76
|
+
|
|
77
|
+
constructor(graph: ID2, writer: DifferenceStreamWriter<T>) {
|
|
78
|
+
this.#graph = graph
|
|
79
|
+
this.#writer = writer
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
connectReader(): DifferenceStreamReader<T> {
|
|
83
|
+
return this.#writer.newReader()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get writer(): DifferenceStreamWriter<T> {
|
|
87
|
+
return this.#writer
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get graph(): ID2 {
|
|
91
|
+
return this.#graph
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Don't judge, this is the only way to type this function.
|
|
95
|
+
// rxjs has very similar code to type its pipe function
|
|
96
|
+
// https://github.com/ReactiveX/rxjs/blob/master/packages/rxjs/src/internal/util/pipe.ts
|
|
97
|
+
// We go to 20 operators deep, because surly that's enough for anyone...
|
|
98
|
+
// A user can always split the pipe into multiple pipes to get around this.
|
|
99
|
+
pipe<O>(o1: PipedOperator<T, O>): IStreamBuilder<O>
|
|
100
|
+
// prettier-ignore
|
|
101
|
+
pipe<T2, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, O>): IStreamBuilder<O>
|
|
102
|
+
// prettier-ignore
|
|
103
|
+
pipe<T2, T3, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, O>): IStreamBuilder<O>
|
|
104
|
+
// prettier-ignore
|
|
105
|
+
pipe<T2, T3, T4, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, O>): IStreamBuilder<O>
|
|
106
|
+
// prettier-ignore
|
|
107
|
+
pipe<T2, T3, T4, T5, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, O>): IStreamBuilder<O>
|
|
108
|
+
// prettier-ignore
|
|
109
|
+
pipe<T2, T3, T4, T5, T6, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, O>): IStreamBuilder<O>
|
|
110
|
+
// prettier-ignore
|
|
111
|
+
pipe<T2, T3, T4, T5, T6, T7, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, O>): IStreamBuilder<O>
|
|
112
|
+
// prettier-ignore
|
|
113
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, O>): IStreamBuilder<O>
|
|
114
|
+
// prettier-ignore
|
|
115
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, O>): IStreamBuilder<O>
|
|
116
|
+
// prettier-ignore
|
|
117
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, O>): IStreamBuilder<O>
|
|
118
|
+
// prettier-ignore
|
|
119
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, O>): IStreamBuilder<O>
|
|
120
|
+
// prettier-ignore
|
|
121
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, O>): IStreamBuilder<O>
|
|
122
|
+
// prettier-ignore
|
|
123
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, O>): IStreamBuilder<O>
|
|
124
|
+
// prettier-ignore
|
|
125
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, O>): IStreamBuilder<O>
|
|
126
|
+
// prettier-ignore
|
|
127
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, O>): IStreamBuilder<O>
|
|
128
|
+
// prettier-ignore
|
|
129
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, O>): IStreamBuilder<O>
|
|
130
|
+
// prettier-ignore
|
|
131
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, O>): IStreamBuilder<O>
|
|
132
|
+
// prettier-ignore
|
|
133
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, O>): IStreamBuilder<O>
|
|
134
|
+
// prettier-ignore
|
|
135
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, T19>, o19: PipedOperator<T19, O>): IStreamBuilder<O>
|
|
136
|
+
// prettier-ignore
|
|
137
|
+
pipe<T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, T19>, o19: PipedOperator<T19, T20>, o20: PipedOperator<T20, O>): IStreamBuilder<O>
|
|
138
|
+
|
|
139
|
+
pipe(...operators: Array<PipedOperator<any, any>>): IStreamBuilder<any> {
|
|
140
|
+
return operators.reduce((stream, operator) => {
|
|
141
|
+
return operator(stream)
|
|
142
|
+
}, this as IStreamBuilder<any>)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export class RootStreamBuilder<T> extends StreamBuilder<T> {
|
|
147
|
+
sendData(collection: MultiSet<T> | MultiSetArray<T>): void {
|
|
148
|
+
this.writer.sendData(collection)
|
|
149
|
+
}
|
|
150
|
+
}
|
package/src/graph.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { MultiSet } from "./multiset.js"
|
|
2
|
+
import type { MultiSetArray } from "./multiset.js"
|
|
3
|
+
import type {
|
|
4
|
+
IDifferenceStreamReader,
|
|
5
|
+
IDifferenceStreamWriter,
|
|
6
|
+
IOperator,
|
|
7
|
+
} from "./types.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A read handle to a dataflow edge that receives data from a writer.
|
|
11
|
+
*/
|
|
12
|
+
export class DifferenceStreamReader<T> implements IDifferenceStreamReader<T> {
|
|
13
|
+
#queue: Array<MultiSet<T>>
|
|
14
|
+
|
|
15
|
+
constructor(queue: Array<MultiSet<T>>) {
|
|
16
|
+
this.#queue = queue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
drain(): Array<MultiSet<T>> {
|
|
20
|
+
const out = [...this.#queue].reverse()
|
|
21
|
+
this.#queue.length = 0
|
|
22
|
+
return out
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
isEmpty(): boolean {
|
|
26
|
+
return this.#queue.length === 0
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A write handle to a dataflow edge that is allowed to publish data.
|
|
32
|
+
*/
|
|
33
|
+
export class DifferenceStreamWriter<T> implements IDifferenceStreamWriter<T> {
|
|
34
|
+
#queues: Array<Array<MultiSet<T>>> = []
|
|
35
|
+
|
|
36
|
+
sendData(collection: MultiSet<T> | MultiSetArray<T>): void {
|
|
37
|
+
if (!(collection instanceof MultiSet)) {
|
|
38
|
+
collection = new MultiSet(collection)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const q of this.#queues) {
|
|
42
|
+
q.unshift(collection)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
newReader(): DifferenceStreamReader<T> {
|
|
47
|
+
const q: Array<MultiSet<T>> = []
|
|
48
|
+
this.#queues.push(q)
|
|
49
|
+
return new DifferenceStreamReader(q)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A generic implementation of a dataflow operator (node) that has multiple incoming edges (read handles) and
|
|
55
|
+
* one outgoing edge (write handle).
|
|
56
|
+
*/
|
|
57
|
+
export abstract class Operator<T> implements IOperator<T> {
|
|
58
|
+
protected inputs: Array<DifferenceStreamReader<T>>
|
|
59
|
+
protected output: DifferenceStreamWriter<T>
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
public id: number,
|
|
63
|
+
inputs: Array<DifferenceStreamReader<T>>,
|
|
64
|
+
output: DifferenceStreamWriter<T>
|
|
65
|
+
) {
|
|
66
|
+
this.inputs = inputs
|
|
67
|
+
this.output = output
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
abstract run(): void
|
|
71
|
+
|
|
72
|
+
hasPendingWork(): boolean {
|
|
73
|
+
return this.inputs.some((input) => !input.isEmpty())
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A convenience implementation of a dataflow operator that has a handle to one
|
|
79
|
+
* incoming stream of data, and one handle to an outgoing stream of data.
|
|
80
|
+
*/
|
|
81
|
+
export abstract class UnaryOperator<Tin, Tout = Tin> extends Operator<
|
|
82
|
+
Tin | Tout
|
|
83
|
+
> {
|
|
84
|
+
constructor(
|
|
85
|
+
public id: number,
|
|
86
|
+
inputA: DifferenceStreamReader<Tin>,
|
|
87
|
+
output: DifferenceStreamWriter<Tout>
|
|
88
|
+
) {
|
|
89
|
+
super(id, [inputA], output)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
inputMessages(): Array<MultiSet<Tin>> {
|
|
93
|
+
return this.inputs[0]!.drain() as Array<MultiSet<Tin>>
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A convenience implementation of a dataflow operator that has a handle to two
|
|
99
|
+
* incoming streams of data, and one handle to an outgoing stream of data.
|
|
100
|
+
*/
|
|
101
|
+
export abstract class BinaryOperator<T> extends Operator<T> {
|
|
102
|
+
constructor(
|
|
103
|
+
public id: number,
|
|
104
|
+
inputA: DifferenceStreamReader<T>,
|
|
105
|
+
inputB: DifferenceStreamReader<T>,
|
|
106
|
+
output: DifferenceStreamWriter<T>
|
|
107
|
+
) {
|
|
108
|
+
super(id, [inputA, inputB], output)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
inputAMessages(): Array<MultiSet<T>> {
|
|
112
|
+
return this.inputs[0]!.drain()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
inputBMessages(): Array<MultiSet<T>> {
|
|
116
|
+
return this.inputs[1]!.drain()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Base class for operators that process a single input stream
|
|
122
|
+
*/
|
|
123
|
+
export abstract class LinearUnaryOperator<T, U> extends UnaryOperator<T | U> {
|
|
124
|
+
abstract inner(collection: MultiSet<T | U>): MultiSet<U>
|
|
125
|
+
|
|
126
|
+
run(): void {
|
|
127
|
+
for (const message of this.inputMessages()) {
|
|
128
|
+
this.output.sendData(this.inner(message))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/index.ts
ADDED
package/src/indexes.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { MultiSet } from "./multiset.js"
|
|
2
|
+
import { DefaultMap } from "./utils.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A map from a difference collection trace's keys -> (value, multiplicities) that changed.
|
|
6
|
+
* Used in operations like join and reduce where the operation needs to
|
|
7
|
+
* exploit the key-value structure of the data to run efficiently.
|
|
8
|
+
*/
|
|
9
|
+
export class Index<K, V> {
|
|
10
|
+
#inner: DefaultMap<K, Map<V, number>>
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
this.#inner = new DefaultMap<K, Map<V, number>>(() => new Map<V, number>())
|
|
14
|
+
// #inner is a map of:
|
|
15
|
+
// {
|
|
16
|
+
// [key]: Map<V, number> // Direct value-to-multiplicity mapping
|
|
17
|
+
// }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
toString(indent = false): string {
|
|
21
|
+
return `Index(${JSON.stringify(
|
|
22
|
+
[...this.#inner].map(([k, valueMap]) => [k, [...valueMap]]),
|
|
23
|
+
undefined,
|
|
24
|
+
indent ? ` ` : undefined
|
|
25
|
+
)})`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get(key: K): Array<[V, number]> {
|
|
29
|
+
const valueMap = this.#inner.get(key)
|
|
30
|
+
return [...valueMap.entries()]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getMultiplicity(key: K, value: V): number {
|
|
34
|
+
const valueMap = this.#inner.get(key)
|
|
35
|
+
return valueMap.get(value) ?? 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
entries() {
|
|
39
|
+
return this.#inner.entries()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
keys() {
|
|
43
|
+
return this.#inner.keys()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
has(key: K): boolean {
|
|
47
|
+
return this.#inner.has(key)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get size(): number {
|
|
51
|
+
return this.#inner.size
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
addValue(key: K, value: [V, number]): void {
|
|
55
|
+
const [val, multiplicity] = value
|
|
56
|
+
const valueMap = this.#inner.get(key)
|
|
57
|
+
const existingMultiplicity = valueMap.get(val) ?? 0
|
|
58
|
+
const newMultiplicity = existingMultiplicity + multiplicity
|
|
59
|
+
|
|
60
|
+
if (multiplicity !== 0) {
|
|
61
|
+
if (newMultiplicity === 0) {
|
|
62
|
+
valueMap.delete(val)
|
|
63
|
+
} else {
|
|
64
|
+
valueMap.set(val, newMultiplicity)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
append(other: Index<K, V>): void {
|
|
70
|
+
for (const [key, otherValueMap] of other.entries()) {
|
|
71
|
+
const thisValueMap = this.#inner.get(key)
|
|
72
|
+
for (const [value, multiplicity] of otherValueMap.entries()) {
|
|
73
|
+
const existingMultiplicity = thisValueMap.get(value) ?? 0
|
|
74
|
+
const newMultiplicity = existingMultiplicity + multiplicity
|
|
75
|
+
if (newMultiplicity === 0) {
|
|
76
|
+
thisValueMap.delete(value)
|
|
77
|
+
} else {
|
|
78
|
+
thisValueMap.set(value, newMultiplicity)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
join<V2>(other: Index<K, V2>): MultiSet<[K, [V, V2]]> {
|
|
85
|
+
const result: Array<[[K, [V, V2]], number]> = []
|
|
86
|
+
|
|
87
|
+
// We want to iterate over the smaller of the two indexes to reduce the
|
|
88
|
+
// number of operations we need to do.
|
|
89
|
+
if (this.size <= other.size) {
|
|
90
|
+
for (const [key, valueMap] of this.entries()) {
|
|
91
|
+
if (!other.has(key)) continue
|
|
92
|
+
const otherValues = other.get(key)
|
|
93
|
+
for (const [val1, mul1] of valueMap.entries()) {
|
|
94
|
+
for (const [val2, mul2] of otherValues) {
|
|
95
|
+
if (mul1 !== 0 && mul2 !== 0) {
|
|
96
|
+
result.push([[key, [val1, val2]], mul1 * mul2])
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
for (const [key, otherValueMap] of other.entries()) {
|
|
103
|
+
if (!this.has(key)) continue
|
|
104
|
+
const values = this.get(key)
|
|
105
|
+
for (const [val2, mul2] of otherValueMap.entries()) {
|
|
106
|
+
for (const [val1, mul1] of values) {
|
|
107
|
+
if (mul1 !== 0 && mul2 !== 0) {
|
|
108
|
+
result.push([[key, [val1, val2]], mul1 * mul2])
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return new MultiSet(result)
|
|
116
|
+
}
|
|
117
|
+
}
|