Skip to content

Commit f0eda36

Browse files
move development of d2mini to the db monorepo and rename db-ivm (#330)
Co-authored-by: Kyle Mathews <[email protected]>
1 parent d5b4c9d commit f0eda36

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+12954
-24
lines changed

.changeset/slimy-lizards-flash.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@tanstack/svelte-db": patch
3+
"@tanstack/react-db": patch
4+
"@tanstack/solid-db": patch
5+
"@tanstack/db-ivm": patch
6+
"@tanstack/vue-db": patch
7+
"@tanstack/db": patch
8+
---
9+
10+
We have moved development of the differential dataflow implementation from @electric-sql/d2mini to a new @tanstack/db-ivm package inside the tanstack db monorepo to make development simpler.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
],
7878
"overrides": {
7979
"@tanstack/db": "workspace:*",
80+
"@tanstack/db-ivm": "workspace:*",
8081
"@tanstack/react-db": "workspace:*",
8182
"@tanstack/vue-db": "workspace:*"
8283
}

packages/db-ivm/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
```

packages/db-ivm/package.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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": "[email protected]",
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+
}

packages/db-ivm/src/d2.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)