@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 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
@@ -0,0 +1,4 @@
1
+ export * from "./d2.js"
2
+ export * from "./multiset.js"
3
+ export * from "./operators/index.js"
4
+ export * from "./types.js"
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
+ }