@tanstack/db-ivm 0.1.12 → 0.1.14
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 +3 -3
- package/dist/cjs/d2.cjs.map +1 -1
- package/dist/cjs/graph.cjs.map +1 -1
- package/dist/cjs/hashing/hash.cjs +22 -3
- package/dist/cjs/hashing/hash.cjs.map +1 -1
- package/dist/cjs/hashing/murmur.cjs +15 -15
- package/dist/cjs/hashing/murmur.cjs.map +1 -1
- package/dist/cjs/hashing/murmur.d.cts +1 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/indexes.cjs.map +1 -1
- package/dist/cjs/multiset.cjs.map +1 -1
- package/dist/cjs/operators/concat.cjs.map +1 -1
- package/dist/cjs/operators/consolidate.cjs.map +1 -1
- package/dist/cjs/operators/count.cjs.map +1 -1
- package/dist/cjs/operators/debug.cjs.map +1 -1
- package/dist/cjs/operators/distinct.cjs.map +1 -1
- package/dist/cjs/operators/filter.cjs.map +1 -1
- package/dist/cjs/operators/filterBy.cjs.map +1 -1
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/operators/join.cjs.map +1 -1
- package/dist/cjs/operators/keying.cjs.map +1 -1
- package/dist/cjs/operators/map.cjs.map +1 -1
- package/dist/cjs/operators/negate.cjs.map +1 -1
- package/dist/cjs/operators/orderBy.cjs.map +1 -1
- package/dist/cjs/operators/output.cjs.map +1 -1
- package/dist/cjs/operators/pipe.cjs.map +1 -1
- package/dist/cjs/operators/reduce.cjs.map +1 -1
- package/dist/cjs/operators/tap.cjs.map +1 -1
- package/dist/cjs/operators/topK.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +20 -35
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKWithFractionalIndex.d.cts +3 -11
- package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +4 -4
- package/dist/cjs/utils.cjs +9 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +5 -0
- package/dist/esm/d2.js.map +1 -1
- package/dist/esm/graph.js.map +1 -1
- package/dist/esm/hashing/hash.js +22 -3
- package/dist/esm/hashing/hash.js.map +1 -1
- package/dist/esm/hashing/murmur.d.ts +1 -1
- package/dist/esm/hashing/murmur.js +15 -15
- package/dist/esm/hashing/murmur.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes.js.map +1 -1
- package/dist/esm/multiset.js.map +1 -1
- package/dist/esm/operators/concat.js.map +1 -1
- package/dist/esm/operators/consolidate.js.map +1 -1
- package/dist/esm/operators/count.js.map +1 -1
- package/dist/esm/operators/debug.js.map +1 -1
- package/dist/esm/operators/distinct.js.map +1 -1
- package/dist/esm/operators/filter.js.map +1 -1
- package/dist/esm/operators/filterBy.js.map +1 -1
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/operators/join.js.map +1 -1
- package/dist/esm/operators/keying.js.map +1 -1
- package/dist/esm/operators/map.js.map +1 -1
- package/dist/esm/operators/negate.js.map +1 -1
- package/dist/esm/operators/orderBy.js.map +1 -1
- package/dist/esm/operators/output.js.map +1 -1
- package/dist/esm/operators/pipe.js.map +1 -1
- package/dist/esm/operators/reduce.js.map +1 -1
- package/dist/esm/operators/tap.js.map +1 -1
- package/dist/esm/operators/topK.js.map +1 -1
- package/dist/esm/operators/topKWithFractionalIndex.d.ts +3 -11
- package/dist/esm/operators/topKWithFractionalIndex.js +21 -36
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/dist/esm/operators/topKWithFractionalIndexBTree.d.ts +4 -4
- package/dist/esm/utils.d.ts +5 -0
- package/dist/esm/utils.js +9 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +29 -29
- package/src/d2.ts +4 -4
- package/src/graph.ts +6 -6
- package/src/hashing/hash.ts +39 -14
- package/src/hashing/index.ts +2 -2
- package/src/hashing/murmur.ts +15 -15
- package/src/index.ts +5 -4
- package/src/indexes.ts +8 -8
- package/src/multiset.ts +5 -5
- package/src/operators/concat.ts +6 -6
- package/src/operators/consolidate.ts +6 -6
- package/src/operators/count.ts +9 -9
- package/src/operators/debug.ts +8 -8
- package/src/operators/distinct.ts +11 -11
- package/src/operators/filter.ts +8 -8
- package/src/operators/filterBy.ts +7 -7
- package/src/operators/groupBy.ts +22 -22
- package/src/operators/index.ts +19 -19
- package/src/operators/join.ts +23 -23
- package/src/operators/keying.ts +4 -4
- package/src/operators/map.ts +8 -8
- package/src/operators/negate.ts +6 -6
- package/src/operators/orderBy.ts +26 -26
- package/src/operators/orderByBTree.ts +7 -7
- package/src/operators/output.ts +9 -9
- package/src/operators/pipe.ts +1 -1
- package/src/operators/reduce.ts +9 -9
- package/src/operators/tap.ts +8 -8
- package/src/operators/topK.ts +8 -8
- package/src/operators/topKWithFractionalIndex.ts +62 -83
- package/src/operators/topKWithFractionalIndexBTree.ts +19 -20
- package/src/types.ts +22 -22
- package/src/utils.ts +19 -4
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The API is almost identical to D2TS, but without the need to specify a version w
|
|
|
11
11
|
Here's a simple example that demonstrates the core concepts:
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import { D2, map, filter, debug, MultiSet } from
|
|
14
|
+
import { D2, map, filter, debug, MultiSet } from '@tanstack/db-ivm'
|
|
15
15
|
|
|
16
16
|
// Create a new D2 graph
|
|
17
17
|
const graph = new D2()
|
|
@@ -28,7 +28,7 @@ const input = graph.newInput<number>()
|
|
|
28
28
|
const output = input.pipe(
|
|
29
29
|
map((x) => x + 5),
|
|
30
30
|
filter((x) => x % 2 === 0),
|
|
31
|
-
debug(
|
|
31
|
+
debug('output'),
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
// Finalize the pipeline, after this point we can no longer add operators or
|
|
@@ -46,7 +46,7 @@ input.sendData(
|
|
|
46
46
|
[1, 1],
|
|
47
47
|
[2, 1],
|
|
48
48
|
[3, 1],
|
|
49
|
-
])
|
|
49
|
+
]),
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
// Process the data
|
package/dist/cjs/d2.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"d2.cjs","sources":["../../src/d2.ts"],"sourcesContent":["import { DifferenceStreamWriter } from \"./graph.js\"\nimport type {\n BinaryOperator,\n DifferenceStreamReader,\n UnaryOperator,\n} from \"./graph.js\"\nimport type { MultiSet, MultiSetArray } from \"./multiset.js\"\nimport type { ID2, IStreamBuilder, PipedOperator } from \"./types.js\"\n\nexport class D2 implements ID2 {\n #operators: Array<UnaryOperator<any> | BinaryOperator<any>> = []\n #nextOperatorId = 0\n #finalized = false\n\n constructor() {}\n\n #checkNotFinalized(): void {\n if (this.#finalized) {\n throw new Error(`Graph already finalized`)\n }\n }\n\n getNextOperatorId(): number {\n this.#checkNotFinalized()\n return this.#nextOperatorId++\n }\n\n newInput<T>(): RootStreamBuilder<T> {\n this.#checkNotFinalized()\n const writer = new DifferenceStreamWriter<T>()\n // Use the root stream builder that exposes the sendData and sendFrontier methods\n const streamBuilder = new RootStreamBuilder<T>(this, writer)\n return streamBuilder\n }\n\n addOperator(operator: UnaryOperator<any> | BinaryOperator<any>): void {\n this.#checkNotFinalized()\n this.#operators.push(operator)\n }\n\n finalize() {\n this.#checkNotFinalized()\n this.#finalized = true\n }\n\n step(): void {\n if (!this.#finalized) {\n throw new Error(`Graph not finalized`)\n }\n for (const op of this.#operators) {\n op.run()\n }\n }\n\n pendingWork(): boolean {\n return this.#operators.some((op) => op.hasPendingWork())\n }\n\n run(): void {\n while (this.pendingWork()) {\n this.step()\n }\n }\n}\n\nexport class StreamBuilder<T> implements IStreamBuilder<T> {\n #graph: ID2\n #writer: DifferenceStreamWriter<T>\n\n constructor(graph: ID2, writer: DifferenceStreamWriter<T>) {\n this.#graph = graph\n this.#writer = writer\n }\n\n connectReader(): DifferenceStreamReader<T> {\n return this.#writer.newReader()\n }\n\n get writer(): DifferenceStreamWriter<T> {\n return this.#writer\n }\n\n get graph(): ID2 {\n return this.#graph\n }\n\n // Don't judge, this is the only way to type this function.\n // rxjs has very similar code to type its pipe function\n // https://github.com/ReactiveX/rxjs/blob/master/packages/rxjs/src/internal/util/pipe.ts\n // We go to 20 operators deep, because surly that's enough for anyone...\n // A user can always split the pipe into multiple pipes to get around this.\n pipe<O>(o1: PipedOperator<T, O>): IStreamBuilder<O>\n // prettier-ignore\n pipe<T2, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, O>): IStreamBuilder<O>\n // prettier-ignore\n pipe<T2, T3, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, O>): IStreamBuilder<O>\n // prettier-ignore\n pipe<T2, T3, T4, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, O>): IStreamBuilder<O>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n\n pipe(...operators: Array<PipedOperator<any, any>>): IStreamBuilder<any> {\n return operators.reduce((stream, operator) => {\n return operator(stream)\n }, this as IStreamBuilder<any>)\n }\n}\n\nexport class RootStreamBuilder<T> extends StreamBuilder<T> {\n sendData(collection: MultiSet<T> | MultiSetArray<T>): void {\n this.writer.sendData(collection)\n }\n}\n"],"names":["DifferenceStreamWriter","graph"],"mappings":";;;AASO,MAAM,GAAkB;AAAA,EAC7B,aAA8D,CAAA;AAAA,EAC9D,kBAAkB;AAAA,EAClB,aAAa;AAAA,EAEb,cAAc;AAAA,EAAC;AAAA,EAEf,qBAA2B;AACzB,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,oBAA4B;AAC1B,SAAK,mBAAA;AACL,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAoC;AAClC,SAAK,mBAAA;AACL,UAAM,SAAS,IAAIA,6BAAA;AAEnB,UAAM,gBAAgB,IAAI,kBAAqB,MAAM,MAAM;AAC3D,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,UAA0D;AACpE,SAAK,mBAAA;AACL,SAAK,WAAW,KAAK,QAAQ;AAAA,EAC/B;AAAA,EAEA,WAAW;AACT,SAAK,mBAAA;AACL,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,OAAa;AACX,QAAI,CAAC,KAAK,YAAY;AACpB,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC;AACA,eAAW,MAAM,KAAK,YAAY;AAChC,SAAG,IAAA;AAAA,IACL;AAAA,EACF;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK,WAAW,KAAK,CAAC,OAAO,GAAG,gBAAgB;AAAA,EACzD;AAAA,EAEA,MAAY;AACV,WAAO,KAAK,eAAe;AACzB,WAAK,KAAA;AAAA,IACP;AAAA,EACF;AACF;AAEO,MAAM,cAA8C;AAAA,EACzD;AAAA,EACA;AAAA,EAEA,YAAYC,QAAY,QAAmC;AACzD,SAAK,SAASA;AACd,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,gBAA2C;AACzC,WAAO,KAAK,QAAQ,UAAA;AAAA,EACtB;AAAA,EAEA,IAAI,SAAoC;AACtC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAa;AACf,WAAO,KAAK;AAAA,EACd;AAAA,EA+CA,QAAQ,WAAgE;AACtE,WAAO,UAAU,OAAO,CAAC,QAAQ,aAAa;AAC5C,aAAO,SAAS,MAAM;AAAA,IACxB,GAAG,IAA2B;AAAA,EAChC;AACF;AAEO,MAAM,0BAA6B,cAAiB;AAAA,EACzD,SAAS,YAAkD;AACzD,SAAK,OAAO,SAAS,UAAU;AAAA,EACjC;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"d2.cjs","sources":["../../src/d2.ts"],"sourcesContent":["import { DifferenceStreamWriter } from './graph.js'\nimport type {\n BinaryOperator,\n DifferenceStreamReader,\n UnaryOperator,\n} from './graph.js'\nimport type { MultiSet, MultiSetArray } from './multiset.js'\nimport type { ID2, IStreamBuilder, PipedOperator } from './types.js'\n\nexport class D2 implements ID2 {\n #operators: Array<UnaryOperator<any> | BinaryOperator<any>> = []\n #nextOperatorId = 0\n #finalized = false\n\n constructor() {}\n\n #checkNotFinalized(): void {\n if (this.#finalized) {\n throw new Error(`Graph already finalized`)\n }\n }\n\n getNextOperatorId(): number {\n this.#checkNotFinalized()\n return this.#nextOperatorId++\n }\n\n newInput<T>(): RootStreamBuilder<T> {\n this.#checkNotFinalized()\n const writer = new DifferenceStreamWriter<T>()\n // Use the root stream builder that exposes the sendData and sendFrontier methods\n const streamBuilder = new RootStreamBuilder<T>(this, writer)\n return streamBuilder\n }\n\n addOperator(operator: UnaryOperator<any> | BinaryOperator<any>): void {\n this.#checkNotFinalized()\n this.#operators.push(operator)\n }\n\n finalize() {\n this.#checkNotFinalized()\n this.#finalized = true\n }\n\n step(): void {\n if (!this.#finalized) {\n throw new Error(`Graph not finalized`)\n }\n for (const op of this.#operators) {\n op.run()\n }\n }\n\n pendingWork(): boolean {\n return this.#operators.some((op) => op.hasPendingWork())\n }\n\n run(): void {\n while (this.pendingWork()) {\n this.step()\n }\n }\n}\n\nexport class StreamBuilder<T> implements IStreamBuilder<T> {\n #graph: ID2\n #writer: DifferenceStreamWriter<T>\n\n constructor(graph: ID2, writer: DifferenceStreamWriter<T>) {\n this.#graph = graph\n this.#writer = writer\n }\n\n connectReader(): DifferenceStreamReader<T> {\n return this.#writer.newReader()\n }\n\n get writer(): DifferenceStreamWriter<T> {\n return this.#writer\n }\n\n get graph(): ID2 {\n return this.#graph\n }\n\n // Don't judge, this is the only way to type this function.\n // rxjs has very similar code to type its pipe function\n // https://github.com/ReactiveX/rxjs/blob/master/packages/rxjs/src/internal/util/pipe.ts\n // We go to 20 operators deep, because surly that's enough for anyone...\n // A user can always split the pipe into multiple pipes to get around this.\n pipe<O>(o1: PipedOperator<T, O>): IStreamBuilder<O>\n // prettier-ignore\n pipe<T2, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, O>): IStreamBuilder<O>\n // prettier-ignore\n pipe<T2, T3, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, O>): IStreamBuilder<O>\n // prettier-ignore\n pipe<T2, T3, T4, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, O>): IStreamBuilder<O>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n // prettier-ignore\n 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>\n\n pipe(...operators: Array<PipedOperator<any, any>>): IStreamBuilder<any> {\n return operators.reduce((stream, operator) => {\n return operator(stream)\n }, this as IStreamBuilder<any>)\n }\n}\n\nexport class RootStreamBuilder<T> extends StreamBuilder<T> {\n sendData(collection: MultiSet<T> | MultiSetArray<T>): void {\n this.writer.sendData(collection)\n }\n}\n"],"names":["DifferenceStreamWriter","graph"],"mappings":";;;AASO,MAAM,GAAkB;AAAA,EAC7B,aAA8D,CAAA;AAAA,EAC9D,kBAAkB;AAAA,EAClB,aAAa;AAAA,EAEb,cAAc;AAAA,EAAC;AAAA,EAEf,qBAA2B;AACzB,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,oBAA4B;AAC1B,SAAK,mBAAA;AACL,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAoC;AAClC,SAAK,mBAAA;AACL,UAAM,SAAS,IAAIA,6BAAA;AAEnB,UAAM,gBAAgB,IAAI,kBAAqB,MAAM,MAAM;AAC3D,WAAO;AAAA,EACT;AAAA,EAEA,YAAY,UAA0D;AACpE,SAAK,mBAAA;AACL,SAAK,WAAW,KAAK,QAAQ;AAAA,EAC/B;AAAA,EAEA,WAAW;AACT,SAAK,mBAAA;AACL,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,OAAa;AACX,QAAI,CAAC,KAAK,YAAY;AACpB,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC;AACA,eAAW,MAAM,KAAK,YAAY;AAChC,SAAG,IAAA;AAAA,IACL;AAAA,EACF;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK,WAAW,KAAK,CAAC,OAAO,GAAG,gBAAgB;AAAA,EACzD;AAAA,EAEA,MAAY;AACV,WAAO,KAAK,eAAe;AACzB,WAAK,KAAA;AAAA,IACP;AAAA,EACF;AACF;AAEO,MAAM,cAA8C;AAAA,EACzD;AAAA,EACA;AAAA,EAEA,YAAYC,QAAY,QAAmC;AACzD,SAAK,SAASA;AACd,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,gBAA2C;AACzC,WAAO,KAAK,QAAQ,UAAA;AAAA,EACtB;AAAA,EAEA,IAAI,SAAoC;AACtC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAa;AACf,WAAO,KAAK;AAAA,EACd;AAAA,EA+CA,QAAQ,WAAgE;AACtE,WAAO,UAAU,OAAO,CAAC,QAAQ,aAAa;AAC5C,aAAO,SAAS,MAAM;AAAA,IACxB,GAAG,IAA2B;AAAA,EAChC;AACF;AAEO,MAAM,0BAA6B,cAAiB;AAAA,EACzD,SAAS,YAAkD;AACzD,SAAK,OAAO,SAAS,UAAU;AAAA,EACjC;AACF;;;;"}
|
package/dist/cjs/graph.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.cjs","sources":["../../src/graph.ts"],"sourcesContent":["import { MultiSet } from
|
|
1
|
+
{"version":3,"file":"graph.cjs","sources":["../../src/graph.ts"],"sourcesContent":["import { MultiSet } from './multiset.js'\nimport type { MultiSetArray } from './multiset.js'\nimport type {\n IDifferenceStreamReader,\n IDifferenceStreamWriter,\n IOperator,\n} from './types.js'\n\n/**\n * A read handle to a dataflow edge that receives data from a writer.\n */\nexport class DifferenceStreamReader<T> implements IDifferenceStreamReader<T> {\n #queue: Array<MultiSet<T>>\n\n constructor(queue: Array<MultiSet<T>>) {\n this.#queue = queue\n }\n\n drain(): Array<MultiSet<T>> {\n const out = [...this.#queue].reverse()\n this.#queue.length = 0\n return out\n }\n\n isEmpty(): boolean {\n return this.#queue.length === 0\n }\n}\n\n/**\n * A write handle to a dataflow edge that is allowed to publish data.\n */\nexport class DifferenceStreamWriter<T> implements IDifferenceStreamWriter<T> {\n #queues: Array<Array<MultiSet<T>>> = []\n\n sendData(collection: MultiSet<T> | MultiSetArray<T>): void {\n if (!(collection instanceof MultiSet)) {\n collection = new MultiSet(collection)\n }\n\n for (const q of this.#queues) {\n q.unshift(collection)\n }\n }\n\n newReader(): DifferenceStreamReader<T> {\n const q: Array<MultiSet<T>> = []\n this.#queues.push(q)\n return new DifferenceStreamReader(q)\n }\n}\n\n/**\n * A generic implementation of a dataflow operator (node) that has multiple incoming edges (read handles) and\n * one outgoing edge (write handle).\n */\nexport abstract class Operator<T> implements IOperator<T> {\n protected inputs: Array<DifferenceStreamReader<T>>\n protected output: DifferenceStreamWriter<T>\n\n constructor(\n public id: number,\n inputs: Array<DifferenceStreamReader<T>>,\n output: DifferenceStreamWriter<T>,\n ) {\n this.inputs = inputs\n this.output = output\n }\n\n abstract run(): void\n\n hasPendingWork(): boolean {\n return this.inputs.some((input) => !input.isEmpty())\n }\n}\n\n/**\n * A convenience implementation of a dataflow operator that has a handle to one\n * incoming stream of data, and one handle to an outgoing stream of data.\n */\nexport abstract class UnaryOperator<Tin, Tout = Tin> extends Operator<\n Tin | Tout\n> {\n constructor(\n public id: number,\n inputA: DifferenceStreamReader<Tin>,\n output: DifferenceStreamWriter<Tout>,\n ) {\n super(id, [inputA], output)\n }\n\n inputMessages(): Array<MultiSet<Tin>> {\n return this.inputs[0]!.drain() as Array<MultiSet<Tin>>\n }\n}\n\n/**\n * A convenience implementation of a dataflow operator that has a handle to two\n * incoming streams of data, and one handle to an outgoing stream of data.\n */\nexport abstract class BinaryOperator<T> extends Operator<T> {\n constructor(\n public id: number,\n inputA: DifferenceStreamReader<T>,\n inputB: DifferenceStreamReader<T>,\n output: DifferenceStreamWriter<T>,\n ) {\n super(id, [inputA, inputB], output)\n }\n\n inputAMessages(): Array<MultiSet<T>> {\n return this.inputs[0]!.drain()\n }\n\n inputBMessages(): Array<MultiSet<T>> {\n return this.inputs[1]!.drain()\n }\n}\n\n/**\n * Base class for operators that process a single input stream\n */\nexport abstract class LinearUnaryOperator<T, U> extends UnaryOperator<T | U> {\n abstract inner(collection: MultiSet<T | U>): MultiSet<U>\n\n run(): void {\n for (const message of this.inputMessages()) {\n this.output.sendData(this.inner(message))\n }\n }\n}\n"],"names":["MultiSet"],"mappings":";;;AAWO,MAAM,uBAAgE;AAAA,EAC3E;AAAA,EAEA,YAAY,OAA2B;AACrC,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,QAA4B;AAC1B,UAAM,MAAM,CAAC,GAAG,KAAK,MAAM,EAAE,QAAA;AAC7B,SAAK,OAAO,SAAS;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,UAAmB;AACjB,WAAO,KAAK,OAAO,WAAW;AAAA,EAChC;AACF;AAKO,MAAM,uBAAgE;AAAA,EAC3E,UAAqC,CAAA;AAAA,EAErC,SAAS,YAAkD;AACzD,QAAI,EAAE,sBAAsBA,SAAAA,WAAW;AACrC,mBAAa,IAAIA,SAAAA,SAAS,UAAU;AAAA,IACtC;AAEA,eAAW,KAAK,KAAK,SAAS;AAC5B,QAAE,QAAQ,UAAU;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,YAAuC;AACrC,UAAM,IAAwB,CAAA;AAC9B,SAAK,QAAQ,KAAK,CAAC;AACnB,WAAO,IAAI,uBAAuB,CAAC;AAAA,EACrC;AACF;AAMO,MAAe,SAAoC;AAAA,EAIxD,YACS,IACP,QACA,QACA;AAHO,SAAA,KAAA;AAIP,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,EAChB;AAAA,EAIA,iBAA0B;AACxB,WAAO,KAAK,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,SAAS;AAAA,EACrD;AACF;AAMO,MAAe,sBAAuC,SAE3D;AAAA,EACA,YACS,IACP,QACA,QACA;AACA,UAAM,IAAI,CAAC,MAAM,GAAG,MAAM;AAJnB,SAAA,KAAA;AAAA,EAKT;AAAA,EAEA,gBAAsC;AACpC,WAAO,KAAK,OAAO,CAAC,EAAG,MAAA;AAAA,EACzB;AACF;AAMO,MAAe,uBAA0B,SAAY;AAAA,EAC1D,YACS,IACP,QACA,QACA,QACA;AACA,UAAM,IAAI,CAAC,QAAQ,MAAM,GAAG,MAAM;AAL3B,SAAA,KAAA;AAAA,EAMT;AAAA,EAEA,iBAAqC;AACnC,WAAO,KAAK,OAAO,CAAC,EAAG,MAAA;AAAA,EACzB;AAAA,EAEA,iBAAqC;AACnC,WAAO,KAAK,OAAO,CAAC,EAAG,MAAA;AAAA,EACzB;AACF;AAKO,MAAe,4BAAkC,cAAqB;AAAA,EAG3E,MAAY;AACV,eAAW,WAAW,KAAK,iBAAiB;AAC1C,WAAK,OAAO,SAAS,KAAK,MAAM,OAAO,CAAC;AAAA,IAC1C;AAAA,EACF;AACF;;;;;;;"}
|
|
@@ -12,6 +12,8 @@ const OBJECT_MARKER = murmur.randomHash();
|
|
|
12
12
|
const ARRAY_MARKER = murmur.randomHash();
|
|
13
13
|
const MAP_MARKER = murmur.randomHash();
|
|
14
14
|
const SET_MARKER = murmur.randomHash();
|
|
15
|
+
const UINT8ARRAY_MARKER = murmur.randomHash();
|
|
16
|
+
const UINT8ARRAY_CONTENT_HASH_THRESHOLD = 128;
|
|
15
17
|
const hashCache = /* @__PURE__ */ new WeakMap();
|
|
16
18
|
function hash(input) {
|
|
17
19
|
const hasher = new murmur.MurmurHashStream();
|
|
@@ -26,6 +28,17 @@ function hashObject(input) {
|
|
|
26
28
|
let valueHash;
|
|
27
29
|
if (input instanceof Date) {
|
|
28
30
|
valueHash = hashDate(input);
|
|
31
|
+
} else if (
|
|
32
|
+
// Check if input is a Uint8Array or Buffer
|
|
33
|
+
typeof Buffer !== `undefined` && input instanceof Buffer || input instanceof Uint8Array
|
|
34
|
+
) {
|
|
35
|
+
if (input.byteLength <= UINT8ARRAY_CONTENT_HASH_THRESHOLD) {
|
|
36
|
+
valueHash = hashUint8Array(input);
|
|
37
|
+
} else {
|
|
38
|
+
return cachedReferenceHash(input);
|
|
39
|
+
}
|
|
40
|
+
} else if (input instanceof File) {
|
|
41
|
+
return cachedReferenceHash(input);
|
|
29
42
|
} else {
|
|
30
43
|
let plainObjectInput = input;
|
|
31
44
|
let marker = OBJECT_MARKER;
|
|
@@ -40,9 +53,6 @@ function hashObject(input) {
|
|
|
40
53
|
marker = SET_MARKER;
|
|
41
54
|
plainObjectInput = [...input.entries()];
|
|
42
55
|
}
|
|
43
|
-
if (typeof Buffer !== `undefined` && input instanceof Buffer || input instanceof Uint8Array || input instanceof File) {
|
|
44
|
-
return cachedReferenceHash(input);
|
|
45
|
-
}
|
|
46
56
|
valueHash = hashPlainObject(plainObjectInput, marker);
|
|
47
57
|
}
|
|
48
58
|
hashCache.set(input, valueHash);
|
|
@@ -54,6 +64,15 @@ function hashDate(input) {
|
|
|
54
64
|
hasher.update(input.getTime());
|
|
55
65
|
return hasher.digest();
|
|
56
66
|
}
|
|
67
|
+
function hashUint8Array(input) {
|
|
68
|
+
const hasher = new murmur.MurmurHashStream();
|
|
69
|
+
hasher.update(UINT8ARRAY_MARKER);
|
|
70
|
+
hasher.update(input.byteLength);
|
|
71
|
+
for (let i = 0; i < input.byteLength; i++) {
|
|
72
|
+
hasher.writeByte(input[i]);
|
|
73
|
+
}
|
|
74
|
+
return hasher.digest();
|
|
75
|
+
}
|
|
57
76
|
function hashPlainObject(input, marker) {
|
|
58
77
|
const hasher = new murmur.MurmurHashStream();
|
|
59
78
|
hasher.update(marker);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.cjs","sources":["../../../src/hashing/hash.ts"],"sourcesContent":["import { MurmurHashStream, randomHash } from
|
|
1
|
+
{"version":3,"file":"hash.cjs","sources":["../../../src/hashing/hash.ts"],"sourcesContent":["import { MurmurHashStream, randomHash } from './murmur.js'\nimport type { Hasher } from './murmur.js'\n\n/*\n * Implementation of structural hashing based on the Composites polyfill implementation:\n * https://github.com/tc39/proposal-composites\n */\n\nconst TRUE = randomHash()\nconst FALSE = randomHash()\nconst NULL = randomHash()\nconst UNDEFINED = randomHash()\nconst KEY = randomHash()\nconst FUNCTIONS = randomHash()\nconst DATE_MARKER = randomHash()\nconst OBJECT_MARKER = randomHash()\nconst ARRAY_MARKER = randomHash()\nconst MAP_MARKER = randomHash()\nconst SET_MARKER = randomHash()\nconst UINT8ARRAY_MARKER = randomHash()\n\n// Maximum byte length for Uint8Arrays to hash by content instead of reference\n// Arrays smaller than this will be hashed by content, allowing proper equality comparisons\n// for small arrays like ULIDs (16 bytes) while still avoiding performance costs for large arrays\nconst UINT8ARRAY_CONTENT_HASH_THRESHOLD = 128\n\nconst hashCache = new WeakMap<object, number>()\n\nexport function hash(input: any): number {\n const hasher = new MurmurHashStream()\n updateHasher(hasher, input)\n return hasher.digest()\n}\n\nfunction hashObject(input: object): number {\n const cachedHash = hashCache.get(input)\n if (cachedHash !== undefined) {\n return cachedHash\n }\n\n let valueHash: number | undefined\n if (input instanceof Date) {\n valueHash = hashDate(input)\n } else if (\n // Check if input is a Uint8Array or Buffer\n (typeof Buffer !== `undefined` && input instanceof Buffer) ||\n input instanceof Uint8Array\n ) {\n // For small Uint8Arrays/Buffers (e.g., ULIDs, UUIDs), hash by content\n // to enable proper equality comparisons. For large arrays, hash by reference\n // to avoid performance costs.\n if (input.byteLength <= UINT8ARRAY_CONTENT_HASH_THRESHOLD) {\n valueHash = hashUint8Array(input)\n } else {\n // Deeply hashing large arrays would be too costly\n // so we track them by reference and cache them in a weak map\n return cachedReferenceHash(input)\n }\n } else if (input instanceof File) {\n // Files are always hashed by reference due to their potentially large size\n return cachedReferenceHash(input)\n } else {\n let plainObjectInput = input\n let marker = OBJECT_MARKER\n\n if (input instanceof Array) {\n marker = ARRAY_MARKER\n }\n\n if (input instanceof Map) {\n marker = MAP_MARKER\n plainObjectInput = [...input.entries()]\n }\n\n if (input instanceof Set) {\n marker = SET_MARKER\n plainObjectInput = [...input.entries()]\n }\n\n valueHash = hashPlainObject(plainObjectInput, marker)\n }\n\n hashCache.set(input, valueHash)\n return valueHash\n}\n\nfunction hashDate(input: Date): number {\n const hasher = new MurmurHashStream()\n hasher.update(DATE_MARKER)\n hasher.update(input.getTime())\n return hasher.digest()\n}\n\nfunction hashUint8Array(input: Uint8Array): number {\n const hasher = new MurmurHashStream()\n hasher.update(UINT8ARRAY_MARKER)\n // Hash the byte length first to differentiate arrays of different sizes\n hasher.update(input.byteLength)\n // Hash each byte in the array\n for (let i = 0; i < input.byteLength; i++) {\n hasher.writeByte(input[i]!)\n }\n return hasher.digest()\n}\n\nfunction hashPlainObject(input: object, marker: number): number {\n const hasher = new MurmurHashStream()\n\n // Mark the type of the input\n hasher.update(marker)\n const keys = Object.keys(input)\n keys.sort(keySort)\n for (const key of keys) {\n hasher.update(KEY)\n hasher.update(key)\n updateHasher(hasher, input[key as keyof typeof input])\n }\n\n return hasher.digest()\n}\n\nfunction updateHasher(hasher: Hasher, input: unknown): void {\n if (input === null) {\n hasher.update(NULL)\n return\n }\n switch (typeof input) {\n case `undefined`:\n hasher.update(UNDEFINED)\n return\n case `boolean`:\n hasher.update(input ? TRUE : FALSE)\n return\n case `number`:\n // Normalize NaNs and -0\n hasher.update(isNaN(input) ? NaN : input === 0 ? 0 : input)\n return\n case `bigint`:\n case `string`:\n case `symbol`:\n hasher.update(input)\n return\n case `object`:\n hasher.update(getCachedHash(input))\n return\n case `function`:\n // Functions are assigned a globally unique ID\n // and that ID is cached in the weak map\n hasher.update(cachedReferenceHash(input))\n return\n default:\n console.warn(\n `Ignored input during hashing because it is of type ${typeof input} which is not supported`,\n )\n }\n}\n\nfunction getCachedHash(input: object): number {\n let valueHash = hashCache.get(input)\n if (valueHash === undefined) {\n valueHash = hashObject(input)\n }\n return valueHash\n}\n\nlet nextRefId = 1\nfunction cachedReferenceHash(fn: object): number {\n let valueHash = hashCache.get(fn)\n if (valueHash === undefined) {\n valueHash = nextRefId ^ FUNCTIONS\n nextRefId++\n hashCache.set(fn, valueHash)\n }\n return valueHash\n}\n\n/**\n * Strings sorted lexicographically.\n */\nfunction keySort(a: string, b: string): number {\n return a.localeCompare(b)\n}\n"],"names":["randomHash","MurmurHashStream"],"mappings":"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"}
|
|
@@ -26,7 +26,7 @@ class MurmurHashStream {
|
|
|
26
26
|
this.hash = this.hash << 13 | this.hash >>> 19;
|
|
27
27
|
this.hash = Math.imul(this.hash, 5) + 3864292196;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
writeByte(byte) {
|
|
30
30
|
this.carry |= (byte & 255) << 8 * this.carryBytes;
|
|
31
31
|
this.carryBytes++;
|
|
32
32
|
this.length++;
|
|
@@ -46,8 +46,8 @@ class MurmurHashStream {
|
|
|
46
46
|
}
|
|
47
47
|
for (let i = 0; i < description.length; i++) {
|
|
48
48
|
const code = description.charCodeAt(i);
|
|
49
|
-
this.
|
|
50
|
-
this.
|
|
49
|
+
this.writeByte(code & 255);
|
|
50
|
+
this.writeByte(code >>> 8 & 255);
|
|
51
51
|
}
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
@@ -55,20 +55,20 @@ class MurmurHashStream {
|
|
|
55
55
|
this.update(STRING_MARKER);
|
|
56
56
|
for (let i = 0; i < chunk.length; i++) {
|
|
57
57
|
const code = chunk.charCodeAt(i);
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
58
|
+
this.writeByte(code & 255);
|
|
59
|
+
this.writeByte(code >>> 8 & 255);
|
|
60
60
|
}
|
|
61
61
|
return;
|
|
62
62
|
case `number`:
|
|
63
63
|
dv.setFloat64(0, chunk, true);
|
|
64
|
-
this.
|
|
65
|
-
this.
|
|
66
|
-
this.
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
64
|
+
this.writeByte(u8[0]);
|
|
65
|
+
this.writeByte(u8[1]);
|
|
66
|
+
this.writeByte(u8[2]);
|
|
67
|
+
this.writeByte(u8[3]);
|
|
68
|
+
this.writeByte(u8[4]);
|
|
69
|
+
this.writeByte(u8[5]);
|
|
70
|
+
this.writeByte(u8[6]);
|
|
71
|
+
this.writeByte(u8[7]);
|
|
72
72
|
return;
|
|
73
73
|
case `bigint`: {
|
|
74
74
|
let value = chunk;
|
|
@@ -79,10 +79,10 @@ class MurmurHashStream {
|
|
|
79
79
|
this.update(BIG_INT_MARKER);
|
|
80
80
|
}
|
|
81
81
|
while (value > 0n) {
|
|
82
|
-
this.
|
|
82
|
+
this.writeByte(Number(value & 0xffn));
|
|
83
83
|
value >>= 8n;
|
|
84
84
|
}
|
|
85
|
-
if (chunk === 0n) this.
|
|
85
|
+
if (chunk === 0n) this.writeByte(0);
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
default:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"murmur.cjs","sources":["../../../src/hashing/murmur.ts"],"sourcesContent":["/*\n * Implementation of murmur hash based on the Composites polyfill implementation:\n * https://github.com/tc39/proposal-composites\n */\n\nconst RANDOM_SEED = randomHash()\nconst STRING_MARKER = randomHash()\nconst BIG_INT_MARKER = randomHash()\nconst NEG_BIG_INT_MARKER = randomHash()\nconst SYMBOL_MARKER = randomHash()\n\nexport type Hash = number\n\nexport function randomHash() {\n return (Math.random() * (2 ** 31 - 1)) >>> 0\n}\n\nexport interface Hasher {\n update: (val: symbol | string | number | bigint) => void\n digest: () => number\n}\n\n// Allocate these once, outside the hot path\nconst buf = new ArrayBuffer(8)\n// dv and u8 are 2 different views on the same buffer `buf`\nconst dv = new DataView(buf)\nconst u8 = new Uint8Array(buf)\n\n/**\n * This implementation of Murmur hash uses a random initial seed and random markers.\n * This means that hashes aren't deterministic across app restarts.\n * This is intentional in the composites polyfill to be resistent to hash-flooding attacks\n * where malicious users would precompute lots of different objects whose hashes collide with each other.\n *\n * Currently, for ts/db-ivm this is fine because we don't persist client state.\n * However, when we will introduce persistence we will either need to store the seeds or remove the randomness\n * to ensure deterministic hashes across app restarts.\n */\nexport class MurmurHashStream implements Hasher {\n private hash: number = RANDOM_SEED\n private length = 0\n private carry = 0\n private carryBytes = 0\n\n private _mix(k1: number): void {\n k1 = Math.imul(k1, 0xcc9e2d51)\n k1 = (k1 << 15) | (k1 >>> 17)\n k1 = Math.imul(k1, 0x1b873593)\n this.hash ^= k1\n this.hash = (this.hash << 13) | (this.hash >>> 19)\n this.hash = Math.imul(this.hash, 5) + 0xe6546b64\n }\n\n
|
|
1
|
+
{"version":3,"file":"murmur.cjs","sources":["../../../src/hashing/murmur.ts"],"sourcesContent":["/*\n * Implementation of murmur hash based on the Composites polyfill implementation:\n * https://github.com/tc39/proposal-composites\n */\n\nconst RANDOM_SEED = randomHash()\nconst STRING_MARKER = randomHash()\nconst BIG_INT_MARKER = randomHash()\nconst NEG_BIG_INT_MARKER = randomHash()\nconst SYMBOL_MARKER = randomHash()\n\nexport type Hash = number\n\nexport function randomHash() {\n return (Math.random() * (2 ** 31 - 1)) >>> 0\n}\n\nexport interface Hasher {\n update: (val: symbol | string | number | bigint) => void\n digest: () => number\n}\n\n// Allocate these once, outside the hot path\nconst buf = new ArrayBuffer(8)\n// dv and u8 are 2 different views on the same buffer `buf`\nconst dv = new DataView(buf)\nconst u8 = new Uint8Array(buf)\n\n/**\n * This implementation of Murmur hash uses a random initial seed and random markers.\n * This means that hashes aren't deterministic across app restarts.\n * This is intentional in the composites polyfill to be resistent to hash-flooding attacks\n * where malicious users would precompute lots of different objects whose hashes collide with each other.\n *\n * Currently, for ts/db-ivm this is fine because we don't persist client state.\n * However, when we will introduce persistence we will either need to store the seeds or remove the randomness\n * to ensure deterministic hashes across app restarts.\n */\nexport class MurmurHashStream implements Hasher {\n private hash: number = RANDOM_SEED\n private length = 0\n private carry = 0\n private carryBytes = 0\n\n private _mix(k1: number): void {\n k1 = Math.imul(k1, 0xcc9e2d51)\n k1 = (k1 << 15) | (k1 >>> 17)\n k1 = Math.imul(k1, 0x1b873593)\n this.hash ^= k1\n this.hash = (this.hash << 13) | (this.hash >>> 19)\n this.hash = Math.imul(this.hash, 5) + 0xe6546b64\n }\n\n writeByte(byte: number): void {\n this.carry |= (byte & 0xff) << (8 * this.carryBytes)\n this.carryBytes++\n this.length++\n\n if (this.carryBytes === 4) {\n this._mix(this.carry >>> 0)\n this.carry = 0\n this.carryBytes = 0\n }\n }\n\n update(chunk: symbol | string | number | bigint): void {\n switch (typeof chunk) {\n case `symbol`: {\n this.update(SYMBOL_MARKER)\n const description = chunk.description\n if (!description) {\n return\n }\n\n for (let i = 0; i < description.length; i++) {\n const code = description.charCodeAt(i)\n this.writeByte(code & 0xff)\n this.writeByte((code >>> 8) & 0xff)\n }\n return\n }\n case `string`:\n this.update(STRING_MARKER)\n for (let i = 0; i < chunk.length; i++) {\n const code = chunk.charCodeAt(i)\n this.writeByte(code & 0xff)\n this.writeByte((code >>> 8) & 0xff)\n }\n return\n case `number`:\n dv.setFloat64(0, chunk, true) // fixed little-endian\n this.writeByte(u8[0]!)\n this.writeByte(u8[1]!)\n this.writeByte(u8[2]!)\n this.writeByte(u8[3]!)\n this.writeByte(u8[4]!)\n this.writeByte(u8[5]!)\n this.writeByte(u8[6]!)\n this.writeByte(u8[7]!)\n return\n case `bigint`: {\n let value = chunk\n if (value < 0n) {\n value = -value\n this.update(NEG_BIG_INT_MARKER)\n } else {\n this.update(BIG_INT_MARKER)\n }\n while (value > 0n) {\n this.writeByte(Number(value & 0xffn))\n value >>= 8n\n }\n if (chunk === 0n) this.writeByte(0)\n return\n }\n default:\n throw new TypeError(`Unsupported input type: ${typeof chunk}`)\n }\n }\n\n digest(): number {\n if (this.carryBytes > 0) {\n let k1 = this.carry >>> 0\n k1 = Math.imul(k1, 0xcc9e2d51)\n k1 = (k1 << 15) | (k1 >>> 17)\n k1 = Math.imul(k1, 0x1b873593)\n this.hash ^= k1\n }\n\n this.hash ^= this.length\n this.hash ^= this.hash >>> 16\n this.hash = Math.imul(this.hash, 0x85ebca6b)\n this.hash ^= this.hash >>> 13\n this.hash = Math.imul(this.hash, 0xc2b2ae35)\n this.hash ^= this.hash >>> 16\n\n return this.hash >>> 0\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const d2 = require("./d2.cjs");
|
|
4
4
|
const multiset = require("./multiset.cjs");
|
|
5
|
+
const utils = require("./utils.cjs");
|
|
5
6
|
const groupBy = require("./operators/groupBy.cjs");
|
|
6
7
|
const pipe = require("./operators/pipe.cjs");
|
|
7
8
|
const map = require("./operators/map.cjs");
|
|
@@ -25,6 +26,7 @@ exports.D2 = d2.D2;
|
|
|
25
26
|
exports.RootStreamBuilder = d2.RootStreamBuilder;
|
|
26
27
|
exports.StreamBuilder = d2.StreamBuilder;
|
|
27
28
|
exports.MultiSet = multiset.MultiSet;
|
|
29
|
+
exports.compareKeys = utils.compareKeys;
|
|
28
30
|
exports.groupBy = groupBy.groupBy;
|
|
29
31
|
exports.groupByOperators = groupBy.groupByOperators;
|
|
30
32
|
exports.pipe = pipe.pipe;
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/cjs/index.d.cts
CHANGED
package/dist/cjs/indexes.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexes.cjs","sources":["../../src/indexes.ts"],"sourcesContent":["/**\n * # Optimized Index Data Structure\n *\n * Multi-level index that adapts storage strategy based on data patterns to minimize memory\n * usage, eliminate wasteful lookups, and avoid hashing whenever possible.\n *\n * ## Storage Strategy\n *\n * **Single value**: `IndexMap['key'] → [value, multiplicity]` (no hashing needed)\n *\n * **Multiple unprefixed values**: Direct ValueMap (avoids NO_PREFIX lookup)\n * ```\n * IndexMap['key'] → ValueMap { hash(value1) → [value1, mult1], ... }\n * ```\n *\n * **Values with prefixes**: PrefixMap uses prefix keys directly (no hashing)\n * ```\n * IndexMap['key'] → PrefixMap { 'prefix1' → [value1, mult1], NO_PREFIX → ValueMap{...} }\n * ```\n *\n * **Multiple values per prefix**: ValueMap within PrefixMap (hash only suffixes)\n * ```\n * PrefixMap['prefix'] → ValueMap { hash(suffix1) → [full_value1, mult1], ... }\n * ```\n *\n * ## Dynamic Evolution\n *\n * Structure automatically evolves as data is added:\n * - Single → ValueMap (when both values unprefixed)\n * - Single → PrefixMap (when at least one prefixed)\n * - ValueMap → PrefixMap (adding prefixed value to unprefixed)\n *\n * Prefixes extracted from array values: `['prefix', 'suffix']` → prefix='prefix'\n */\n\nimport { MultiSet } from \"./multiset.js\"\nimport { hash } from \"./hashing/index.js\"\nimport type { Hash } from \"./hashing/index.js\"\n\n// We use a symbol to represent the absence of a prefix, unprefixed values a stored\n// against this key.\nconst NO_PREFIX = Symbol(`NO_PREFIX`)\ntype NO_PREFIX = typeof NO_PREFIX\n\n// A single value is a tuple of the value and the multiplicity.\ntype SingleValue<TValue> = [TValue, number]\n\n// Base map type for the index. Stores single values, prefix maps, or value maps against a key.\ntype IndexMap<TKey, TValue, TPrefix> = Map<\n TKey,\n SingleValue<TValue> | PrefixMap<TValue, TPrefix> | ValueMap<TValue>\n>\n\n// Second level map type for the index, stores single values or value maps against a prefix.\nclass PrefixMap<TValue, TPrefix> extends Map<\n TPrefix | NO_PREFIX,\n SingleValue<TValue> | ValueMap<TValue>\n> {\n /**\n * Add a value to the PrefixMap. Returns true if the map becomes empty after the operation.\n */\n addValue(value: TValue, multiplicity: number): boolean {\n if (multiplicity === 0) return this.size === 0\n\n const prefix = getPrefix<TValue, TPrefix>(value)\n const valueMapOrSingleValue = this.get(prefix)\n\n if (isSingleValue(valueMapOrSingleValue)) {\n const [currentValue, currentMultiplicity] = valueMapOrSingleValue\n const currentPrefix = getPrefix<TValue, TPrefix>(currentValue)\n\n if (currentPrefix !== prefix) {\n throw new Error(`Mismatching prefixes, this should never happen`)\n }\n\n if (currentValue === value || hash(currentValue) === hash(value)) {\n // Same value, update multiplicity\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.delete(prefix)\n } else {\n this.set(prefix, [value, newMultiplicity])\n }\n } else {\n // Different suffixes, need to create ValueMap\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), valueMapOrSingleValue)\n valueMap.set(hash(value), [value, multiplicity])\n this.set(prefix, valueMap)\n }\n } else if (valueMapOrSingleValue === undefined) {\n // No existing value for this prefix\n this.set(prefix, [value, multiplicity])\n } else {\n // Existing ValueMap\n const isEmpty = valueMapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.delete(prefix)\n }\n }\n\n return this.size === 0\n }\n}\n\n// Third level map type for the index, stores single values or value maps against a hash.\nclass ValueMap<TValue> extends Map<Hash, [TValue, number]> {\n /**\n * Add a value to the ValueMap. Returns true if the map becomes empty after the operation.\n * @param value - The full value to store\n * @param multiplicity - The multiplicity to add\n * @param hashKey - Optional hash key to use instead of hashing the full value (used when in PrefixMap context)\n */\n addValue(value: TValue, multiplicity: number): boolean {\n if (multiplicity === 0) return this.size === 0\n\n const key = hash(value)\n const currentValue = this.get(key)\n\n if (currentValue) {\n const [, currentMultiplicity] = currentValue\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.delete(key)\n } else {\n this.set(key, [value, newMultiplicity])\n }\n } else {\n this.set(key, [value, multiplicity])\n }\n\n return this.size === 0\n }\n}\n\n/**\n * A map from a difference collection trace's keys -> (value, multiplicities) that changed.\n * Used in operations like join and reduce where the operation needs to\n * exploit the key-value structure of the data to run efficiently.\n */\nexport class Index<TKey, TValue, TPrefix = any> {\n /*\n * This index maintains a nested map of keys -> (value, multiplicities), where:\n * - initially the values are stored against the key as a single value tuple\n * - when a key gets additional values, the values are stored against the key in a\n * prefix map\n * - the prefix is extract where possible from values that are structured as\n * [rowPrimaryKey, rowValue], as they are in the Tanstack DB query pipeline.\n * - only when there are multiple values for a given prefix do we fall back to a\n * hash to identify identical values, storing them in a third level value map.\n */\n #inner: IndexMap<TKey, TValue, TPrefix>\n #consolidatedMultiplicity: Map<TKey, number> = new Map() // sum of multiplicities per key\n\n constructor() {\n this.#inner = new Map()\n }\n\n /**\n * Create an Index from multiple MultiSet messages.\n * @param messages - Array of MultiSet messages to build the index from.\n * @returns A new Index containing all the data from the messages.\n */\n static fromMultiSets<K, V>(messages: Array<MultiSet<[K, V]>>): Index<K, V> {\n const index = new Index<K, V>()\n\n for (const message of messages) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n index.addValue(key, [value, multiplicity])\n }\n }\n\n return index\n }\n\n /**\n * This method returns a string representation of the index.\n * @param indent - Whether to indent the string representation.\n * @returns A string representation of the index.\n */\n toString(indent = false): string {\n return `Index(${JSON.stringify(\n [...this.entries()],\n undefined,\n indent ? 2 : undefined\n )})`\n }\n\n /**\n * The size of the index.\n */\n get size(): number {\n return this.#inner.size\n }\n\n /**\n * This method checks if the index has a given key.\n * @param key - The key to check.\n * @returns True if the index has the key, false otherwise.\n */\n has(key: TKey): boolean {\n return this.#inner.has(key)\n }\n\n /**\n * Check if a key has presence (non-zero consolidated multiplicity).\n * @param key - The key to check.\n * @returns True if the key has non-zero consolidated multiplicity, false otherwise.\n */\n hasPresence(key: TKey): boolean {\n return (this.#consolidatedMultiplicity.get(key) || 0) !== 0\n }\n\n /**\n * Get the consolidated multiplicity (sum of multiplicities) for a key.\n * @param key - The key to get the consolidated multiplicity for.\n * @returns The consolidated multiplicity for the key.\n */\n getConsolidatedMultiplicity(key: TKey): number {\n return this.#consolidatedMultiplicity.get(key) || 0\n }\n\n /**\n * Get all keys that have presence (non-zero consolidated multiplicity).\n * @returns An iterator of keys with non-zero consolidated multiplicity.\n */\n getPresenceKeys(): Iterable<TKey> {\n return this.#consolidatedMultiplicity.keys()\n }\n\n /**\n * This method returns all values for a given key.\n * @param key - The key to get the values for.\n * @returns An array of value tuples [value, multiplicity].\n */\n get(key: TKey): Array<[TValue, number]> {\n return [...this.getIterator(key)]\n }\n\n /**\n * This method returns an iterator over all values for a given key.\n * @param key - The key to get the values for.\n * @returns An iterator of value tuples [value, multiplicity].\n */\n *getIterator(key: TKey): Iterable<[TValue, number]> {\n const mapOrSingleValue = this.#inner.get(key)\n if (isSingleValue(mapOrSingleValue)) {\n yield mapOrSingleValue\n } else if (mapOrSingleValue === undefined) {\n return\n } else if (mapOrSingleValue instanceof ValueMap) {\n // Direct ValueMap - all values have NO_PREFIX\n for (const valueTuple of mapOrSingleValue.values()) {\n yield valueTuple\n }\n } else {\n // PrefixMap - iterate through all prefixes\n for (const singleValueOrValueMap of mapOrSingleValue.values()) {\n if (isSingleValue(singleValueOrValueMap)) {\n yield singleValueOrValueMap\n } else {\n for (const valueTuple of singleValueOrValueMap.values()) {\n yield valueTuple\n }\n }\n }\n }\n }\n\n /**\n * This returns an iterator that iterates over all key-value pairs.\n * @returns An iterable of all key-value pairs (and their multiplicities) in the index.\n */\n *entries(): Iterable<[TKey, [TValue, number]]> {\n for (const key of this.#inner.keys()) {\n for (const valueTuple of this.getIterator(key)) {\n yield [key, valueTuple]\n }\n }\n }\n\n /**\n * This method only iterates over the keys and not over the values.\n * Hence, it is more efficient than the `#entries` method.\n * It returns an iterator that you can use if you need to iterate over the values for a given key.\n * @returns An iterator of all *keys* in the index and their corresponding value iterator.\n */\n *entriesIterators(): Iterable<[TKey, Iterable<[TValue, number]>]> {\n for (const key of this.#inner.keys()) {\n yield [key, this.getIterator(key)]\n }\n }\n\n /**\n * This method adds a value to the index.\n * @param key - The key to add the value to.\n * @param valueTuple - The value tuple [value, multiplicity] to add to the index.\n */\n addValue(key: TKey, valueTuple: SingleValue<TValue>) {\n const [value, multiplicity] = valueTuple\n // If the multiplicity is 0, do nothing\n if (multiplicity === 0) return\n\n // Update consolidated multiplicity tracking\n const newConsolidatedMultiplicity =\n (this.#consolidatedMultiplicity.get(key) || 0) + multiplicity\n if (newConsolidatedMultiplicity === 0) {\n this.#consolidatedMultiplicity.delete(key)\n } else {\n this.#consolidatedMultiplicity.set(key, newConsolidatedMultiplicity)\n }\n\n const mapOrSingleValue = this.#inner.get(key)\n\n if (mapOrSingleValue === undefined) {\n // First value for this key\n this.#inner.set(key, valueTuple)\n return\n }\n\n if (isSingleValue(mapOrSingleValue)) {\n // Handle transition from single value to map\n this.#handleSingleValueTransition(\n key,\n mapOrSingleValue,\n value,\n multiplicity\n )\n return\n }\n\n if (mapOrSingleValue instanceof ValueMap) {\n // Handle existing ValueMap\n const prefix = getPrefix<TValue, TPrefix>(value)\n if (prefix !== NO_PREFIX) {\n // Convert ValueMap to PrefixMap since we have a prefixed value\n const prefixMap = new PrefixMap<TValue, TPrefix>()\n prefixMap.set(NO_PREFIX, mapOrSingleValue)\n prefixMap.set(prefix, valueTuple)\n this.#inner.set(key, prefixMap)\n } else {\n // Add to existing ValueMap\n const isEmpty = mapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.#inner.delete(key)\n }\n }\n } else {\n // Handle existing PrefixMap\n const isEmpty = mapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.#inner.delete(key)\n }\n }\n }\n\n /**\n * Handle the transition from a single value to either a ValueMap or PrefixMap\n */\n #handleSingleValueTransition(\n key: TKey,\n currentSingleValue: SingleValue<TValue>,\n newValue: TValue,\n multiplicity: number\n ) {\n const [currentValue, currentMultiplicity] = currentSingleValue\n\n // Check for exact same value (reference equality)\n if (currentValue === newValue) {\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#inner.delete(key)\n } else {\n this.#inner.set(key, [newValue, newMultiplicity])\n }\n return\n }\n\n // Get prefixes for both values\n const newPrefix = getPrefix<TValue, TPrefix>(newValue)\n const currentPrefix = getPrefix<TValue, TPrefix>(currentValue)\n\n // Check if they're the same value by prefix/suffix comparison\n if (\n currentPrefix === newPrefix &&\n (currentValue === newValue || hash(currentValue) === hash(newValue))\n ) {\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#inner.delete(key)\n } else {\n this.#inner.set(key, [newValue, newMultiplicity])\n }\n return\n }\n\n // Different values - choose appropriate map type\n if (currentPrefix === NO_PREFIX && newPrefix === NO_PREFIX) {\n // Both have NO_PREFIX, use ValueMap directly\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), currentSingleValue)\n valueMap.set(hash(newValue), [newValue, multiplicity])\n this.#inner.set(key, valueMap)\n } else {\n // At least one has a prefix, use PrefixMap\n const prefixMap = new PrefixMap<TValue, TPrefix>()\n\n if (currentPrefix === newPrefix) {\n // Same prefix, different suffixes - need ValueMap within PrefixMap\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), currentSingleValue)\n valueMap.set(hash(newValue), [newValue, multiplicity])\n prefixMap.set(currentPrefix, valueMap)\n } else {\n // Different prefixes - store as separate single values\n prefixMap.set(currentPrefix, currentSingleValue)\n prefixMap.set(newPrefix, [newValue, multiplicity])\n }\n\n this.#inner.set(key, prefixMap)\n }\n }\n\n /**\n * This method appends another index to the current index.\n * @param other - The index to append to the current index.\n */\n append(other: Index<TKey, TValue>): void {\n for (const [key, value] of other.entries()) {\n this.addValue(key, value)\n }\n }\n\n /**\n * This method joins two indexes.\n * @param other - The index to join with the current index.\n * @returns A multiset of the joined values.\n */\n join<TValue2>(\n other: Index<TKey, TValue2>\n ): MultiSet<[TKey, [TValue, TValue2]]> {\n const result: Array<[[TKey, [TValue, TValue2]], number]> = []\n // We want to iterate over the smaller of the two indexes to reduce the\n // number of operations we need to do.\n if (this.size <= other.size) {\n for (const [key, valueIt] of this.entriesIterators()) {\n if (!other.has(key)) continue\n const otherValues = other.get(key)\n for (const [val1, mul1] of valueIt) {\n for (const [val2, mul2] of otherValues) {\n if (mul1 !== 0 && mul2 !== 0) {\n result.push([[key, [val1, val2]], mul1 * mul2])\n }\n }\n }\n }\n } else {\n for (const [key, otherValueIt] of other.entriesIterators()) {\n if (!this.has(key)) continue\n const values = this.get(key)\n for (const [val2, mul2] of otherValueIt) {\n for (const [val1, mul1] of values) {\n if (mul1 !== 0 && mul2 !== 0) {\n result.push([[key, [val1, val2]], mul1 * mul2])\n }\n }\n }\n }\n }\n\n return new MultiSet(result)\n }\n}\n\n/**\n * This function extracts the prefix from a value.\n * @param value - The value to extract the prefix from.\n * @returns The prefix and the suffix.\n */\nfunction getPrefix<TValue, TPrefix>(value: TValue): TPrefix | NO_PREFIX {\n // If the value is an array and the first element is a string or number, then the\n // first element is the prefix. This is used to distinguish between values without\n // the need for hashing unless there are multiple values for the same prefix.\n if (\n Array.isArray(value) &&\n (typeof value[0] === `string` ||\n typeof value[0] === `number` ||\n typeof value[0] === `bigint`)\n ) {\n return value[0] as TPrefix\n }\n return NO_PREFIX\n}\n\n/**\n * This function checks if a value is a single value.\n * @param value - The value to check.\n * @returns True if the value is a single value, false otherwise.\n */\nfunction isSingleValue<TValue>(\n value: SingleValue<TValue> | unknown\n): value is SingleValue<TValue> {\n return Array.isArray(value)\n}\n"],"names":["hash","MultiSet"],"mappings":";;;;AAyCA,MAAM,YAAY,OAAO,WAAW;AAapC,MAAM,kBAAmC,IAGvC;AAAA;AAAA;AAAA;AAAA,EAIA,SAAS,OAAe,cAA+B;AACrD,QAAI,iBAAiB,EAAG,QAAO,KAAK,SAAS;AAE7C,UAAM,SAAS,UAA2B,KAAK;AAC/C,UAAM,wBAAwB,KAAK,IAAI,MAAM;AAE7C,QAAI,cAAc,qBAAqB,GAAG;AACxC,YAAM,CAAC,cAAc,mBAAmB,IAAI;AAC5C,YAAM,gBAAgB,UAA2B,YAAY;AAE7D,UAAI,kBAAkB,QAAQ;AAC5B,cAAM,IAAI,MAAM,gDAAgD;AAAA,MAClE;AAEA,UAAI,iBAAiB,SAASA,KAAAA,KAAK,YAAY,MAAMA,KAAAA,KAAK,KAAK,GAAG;AAEhE,cAAM,kBAAkB,sBAAsB;AAC9C,YAAI,oBAAoB,GAAG;AACzB,eAAK,OAAO,MAAM;AAAA,QACpB,OAAO;AACL,eAAK,IAAI,QAAQ,CAAC,OAAO,eAAe,CAAC;AAAA,QAC3C;AAAA,MACF,OAAO;AAEL,cAAM,WAAW,IAAI,SAAA;AACrB,iBAAS,IAAIA,KAAAA,KAAK,YAAY,GAAG,qBAAqB;AACtD,iBAAS,IAAIA,UAAK,KAAK,GAAG,CAAC,OAAO,YAAY,CAAC;AAC/C,aAAK,IAAI,QAAQ,QAAQ;AAAA,MAC3B;AAAA,IACF,WAAW,0BAA0B,QAAW;AAE9C,WAAK,IAAI,QAAQ,CAAC,OAAO,YAAY,CAAC;AAAA,IACxC,OAAO;AAEL,YAAM,UAAU,sBAAsB,SAAS,OAAO,YAAY;AAClE,UAAI,SAAS;AACX,aAAK,OAAO,MAAM;AAAA,MACpB;AAAA,IACF;AAEA,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;AAGA,MAAM,iBAAyB,IAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzD,SAAS,OAAe,cAA+B;AACrD,QAAI,iBAAiB,EAAG,QAAO,KAAK,SAAS;AAE7C,UAAM,MAAMA,KAAAA,KAAK,KAAK;AACtB,UAAM,eAAe,KAAK,IAAI,GAAG;AAEjC,QAAI,cAAc;AAChB,YAAM,CAAA,EAAG,mBAAmB,IAAI;AAChC,YAAM,kBAAkB,sBAAsB;AAC9C,UAAI,oBAAoB,GAAG;AACzB,aAAK,OAAO,GAAG;AAAA,MACjB,OAAO;AACL,aAAK,IAAI,KAAK,CAAC,OAAO,eAAe,CAAC;AAAA,MACxC;AAAA,IACF,OAAO;AACL,WAAK,IAAI,KAAK,CAAC,OAAO,YAAY,CAAC;AAAA,IACrC;AAEA,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;AAOO,MAAM,MAAmC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW9C;AAAA,EACA,gDAAmD,IAAA;AAAA;AAAA,EAEnD,cAAc;AACZ,SAAK,6BAAa,IAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,cAAoB,UAAgD;AACzE,UAAM,QAAQ,IAAI,MAAA;AAElB,eAAW,WAAW,UAAU;AAC9B,iBAAW,CAAC,MAAM,YAAY,KAAK,QAAQ,YAAY;AACrD,cAAM,CAAC,KAAK,KAAK,IAAI;AACrB,cAAM,SAAS,KAAK,CAAC,OAAO,YAAY,CAAC;AAAA,MAC3C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,SAAS,OAAe;AAC/B,WAAO,SAAS,KAAK;AAAA,MACnB,CAAC,GAAG,KAAK,SAAS;AAAA,MAClB;AAAA,MACA,SAAS,IAAI;AAAA,IAAA,CACd;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAe;AACjB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,KAAoB;AACtB,WAAO,KAAK,OAAO,IAAI,GAAG;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,KAAoB;AAC9B,YAAQ,KAAK,0BAA0B,IAAI,GAAG,KAAK,OAAO;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,4BAA4B,KAAmB;AAC7C,WAAO,KAAK,0BAA0B,IAAI,GAAG,KAAK;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkC;AAChC,WAAO,KAAK,0BAA0B,KAAA;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,KAAoC;AACtC,WAAO,CAAC,GAAG,KAAK,YAAY,GAAG,CAAC;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,CAAC,YAAY,KAAuC;AAClD,UAAM,mBAAmB,KAAK,OAAO,IAAI,GAAG;AAC5C,QAAI,cAAc,gBAAgB,GAAG;AACnC,YAAM;AAAA,IACR,WAAW,qBAAqB,QAAW;AACzC;AAAA,IACF,WAAW,4BAA4B,UAAU;AAE/C,iBAAW,cAAc,iBAAiB,UAAU;AAClD,cAAM;AAAA,MACR;AAAA,IACF,OAAO;AAEL,iBAAW,yBAAyB,iBAAiB,UAAU;AAC7D,YAAI,cAAc,qBAAqB,GAAG;AACxC,gBAAM;AAAA,QACR,OAAO;AACL,qBAAW,cAAc,sBAAsB,UAAU;AACvD,kBAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,CAAC,UAA8C;AAC7C,eAAW,OAAO,KAAK,OAAO,KAAA,GAAQ;AACpC,iBAAW,cAAc,KAAK,YAAY,GAAG,GAAG;AAC9C,cAAM,CAAC,KAAK,UAAU;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,CAAC,mBAAiE;AAChE,eAAW,OAAO,KAAK,OAAO,KAAA,GAAQ;AACpC,YAAM,CAAC,KAAK,KAAK,YAAY,GAAG,CAAC;AAAA,IACnC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,KAAW,YAAiC;AACnD,UAAM,CAAC,OAAO,YAAY,IAAI;AAE9B,QAAI,iBAAiB,EAAG;AAGxB,UAAM,+BACH,KAAK,0BAA0B,IAAI,GAAG,KAAK,KAAK;AACnD,QAAI,gCAAgC,GAAG;AACrC,WAAK,0BAA0B,OAAO,GAAG;AAAA,IAC3C,OAAO;AACL,WAAK,0BAA0B,IAAI,KAAK,2BAA2B;AAAA,IACrE;AAEA,UAAM,mBAAmB,KAAK,OAAO,IAAI,GAAG;AAE5C,QAAI,qBAAqB,QAAW;AAElC,WAAK,OAAO,IAAI,KAAK,UAAU;AAC/B;AAAA,IACF;AAEA,QAAI,cAAc,gBAAgB,GAAG;AAEnC,WAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAEF;AAAA,IACF;AAEA,QAAI,4BAA4B,UAAU;AAExC,YAAM,SAAS,UAA2B,KAAK;AAC/C,UAAI,WAAW,WAAW;AAExB,cAAM,YAAY,IAAI,UAAA;AACtB,kBAAU,IAAI,WAAW,gBAAgB;AACzC,kBAAU,IAAI,QAAQ,UAAU;AAChC,aAAK,OAAO,IAAI,KAAK,SAAS;AAAA,MAChC,OAAO;AAEL,cAAM,UAAU,iBAAiB,SAAS,OAAO,YAAY;AAC7D,YAAI,SAAS;AACX,eAAK,OAAO,OAAO,GAAG;AAAA,QACxB;AAAA,MACF;AAAA,IACF,OAAO;AAEL,YAAM,UAAU,iBAAiB,SAAS,OAAO,YAAY;AAC7D,UAAI,SAAS;AACX,aAAK,OAAO,OAAO,GAAG;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,6BACE,KACA,oBACA,UACA,cACA;AACA,UAAM,CAAC,cAAc,mBAAmB,IAAI;AAG5C,QAAI,iBAAiB,UAAU;AAC7B,YAAM,kBAAkB,sBAAsB;AAC9C,UAAI,oBAAoB,GAAG;AACzB,aAAK,OAAO,OAAO,GAAG;AAAA,MACxB,OAAO;AACL,aAAK,OAAO,IAAI,KAAK,CAAC,UAAU,eAAe,CAAC;AAAA,MAClD;AACA;AAAA,IACF;AAGA,UAAM,YAAY,UAA2B,QAAQ;AACrD,UAAM,gBAAgB,UAA2B,YAAY;AAG7D,QACE,kBAAkB,cACjB,iBAAiB,YAAYA,UAAK,YAAY,MAAMA,KAAAA,KAAK,QAAQ,IAClE;AACA,YAAM,kBAAkB,sBAAsB;AAC9C,UAAI,oBAAoB,GAAG;AACzB,aAAK,OAAO,OAAO,GAAG;AAAA,MACxB,OAAO;AACL,aAAK,OAAO,IAAI,KAAK,CAAC,UAAU,eAAe,CAAC;AAAA,MAClD;AACA;AAAA,IACF;AAGA,QAAI,kBAAkB,aAAa,cAAc,WAAW;AAE1D,YAAM,WAAW,IAAI,SAAA;AACrB,eAAS,IAAIA,KAAAA,KAAK,YAAY,GAAG,kBAAkB;AACnD,eAAS,IAAIA,UAAK,QAAQ,GAAG,CAAC,UAAU,YAAY,CAAC;AACrD,WAAK,OAAO,IAAI,KAAK,QAAQ;AAAA,IAC/B,OAAO;AAEL,YAAM,YAAY,IAAI,UAAA;AAEtB,UAAI,kBAAkB,WAAW;AAE/B,cAAM,WAAW,IAAI,SAAA;AACrB,iBAAS,IAAIA,KAAAA,KAAK,YAAY,GAAG,kBAAkB;AACnD,iBAAS,IAAIA,UAAK,QAAQ,GAAG,CAAC,UAAU,YAAY,CAAC;AACrD,kBAAU,IAAI,eAAe,QAAQ;AAAA,MACvC,OAAO;AAEL,kBAAU,IAAI,eAAe,kBAAkB;AAC/C,kBAAU,IAAI,WAAW,CAAC,UAAU,YAAY,CAAC;AAAA,MACnD;AAEA,WAAK,OAAO,IAAI,KAAK,SAAS;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,OAAkC;AACvC,eAAW,CAAC,KAAK,KAAK,KAAK,MAAM,WAAW;AAC1C,WAAK,SAAS,KAAK,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KACE,OACqC;AACrC,UAAM,SAAqD,CAAA;AAG3D,QAAI,KAAK,QAAQ,MAAM,MAAM;AAC3B,iBAAW,CAAC,KAAK,OAAO,KAAK,KAAK,oBAAoB;AACpD,YAAI,CAAC,MAAM,IAAI,GAAG,EAAG;AACrB,cAAM,cAAc,MAAM,IAAI,GAAG;AACjC,mBAAW,CAAC,MAAM,IAAI,KAAK,SAAS;AAClC,qBAAW,CAAC,MAAM,IAAI,KAAK,aAAa;AACtC,gBAAI,SAAS,KAAK,SAAS,GAAG;AAC5B,qBAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC;AAAA,YAChD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,CAAC,KAAK,YAAY,KAAK,MAAM,oBAAoB;AAC1D,YAAI,CAAC,KAAK,IAAI,GAAG,EAAG;AACpB,cAAM,SAAS,KAAK,IAAI,GAAG;AAC3B,mBAAW,CAAC,MAAM,IAAI,KAAK,cAAc;AACvC,qBAAW,CAAC,MAAM,IAAI,KAAK,QAAQ;AACjC,gBAAI,SAAS,KAAK,SAAS,GAAG;AAC5B,qBAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,OAAO,IAAI,CAAC;AAAA,YAChD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO,IAAIC,SAAAA,SAAS,MAAM;AAAA,EAC5B;AACF;AAOA,SAAS,UAA2B,OAAoC;AAItE,MACE,MAAM,QAAQ,KAAK,MAClB,OAAO,MAAM,CAAC,MAAM,YACnB,OAAO,MAAM,CAAC,MAAM,YACpB,OAAO,MAAM,CAAC,MAAM,WACtB;AACA,WAAO,MAAM,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAOA,SAAS,cACP,OAC8B;AAC9B,SAAO,MAAM,QAAQ,KAAK;AAC5B;;"}
|
|
1
|
+
{"version":3,"file":"indexes.cjs","sources":["../../src/indexes.ts"],"sourcesContent":["/**\n * # Optimized Index Data Structure\n *\n * Multi-level index that adapts storage strategy based on data patterns to minimize memory\n * usage, eliminate wasteful lookups, and avoid hashing whenever possible.\n *\n * ## Storage Strategy\n *\n * **Single value**: `IndexMap['key'] → [value, multiplicity]` (no hashing needed)\n *\n * **Multiple unprefixed values**: Direct ValueMap (avoids NO_PREFIX lookup)\n * ```\n * IndexMap['key'] → ValueMap { hash(value1) → [value1, mult1], ... }\n * ```\n *\n * **Values with prefixes**: PrefixMap uses prefix keys directly (no hashing)\n * ```\n * IndexMap['key'] → PrefixMap { 'prefix1' → [value1, mult1], NO_PREFIX → ValueMap{...} }\n * ```\n *\n * **Multiple values per prefix**: ValueMap within PrefixMap (hash only suffixes)\n * ```\n * PrefixMap['prefix'] → ValueMap { hash(suffix1) → [full_value1, mult1], ... }\n * ```\n *\n * ## Dynamic Evolution\n *\n * Structure automatically evolves as data is added:\n * - Single → ValueMap (when both values unprefixed)\n * - Single → PrefixMap (when at least one prefixed)\n * - ValueMap → PrefixMap (adding prefixed value to unprefixed)\n *\n * Prefixes extracted from array values: `['prefix', 'suffix']` → prefix='prefix'\n */\n\nimport { MultiSet } from './multiset.js'\nimport { hash } from './hashing/index.js'\nimport type { Hash } from './hashing/index.js'\n\n// We use a symbol to represent the absence of a prefix, unprefixed values a stored\n// against this key.\nconst NO_PREFIX = Symbol(`NO_PREFIX`)\ntype NO_PREFIX = typeof NO_PREFIX\n\n// A single value is a tuple of the value and the multiplicity.\ntype SingleValue<TValue> = [TValue, number]\n\n// Base map type for the index. Stores single values, prefix maps, or value maps against a key.\ntype IndexMap<TKey, TValue, TPrefix> = Map<\n TKey,\n SingleValue<TValue> | PrefixMap<TValue, TPrefix> | ValueMap<TValue>\n>\n\n// Second level map type for the index, stores single values or value maps against a prefix.\nclass PrefixMap<TValue, TPrefix> extends Map<\n TPrefix | NO_PREFIX,\n SingleValue<TValue> | ValueMap<TValue>\n> {\n /**\n * Add a value to the PrefixMap. Returns true if the map becomes empty after the operation.\n */\n addValue(value: TValue, multiplicity: number): boolean {\n if (multiplicity === 0) return this.size === 0\n\n const prefix = getPrefix<TValue, TPrefix>(value)\n const valueMapOrSingleValue = this.get(prefix)\n\n if (isSingleValue(valueMapOrSingleValue)) {\n const [currentValue, currentMultiplicity] = valueMapOrSingleValue\n const currentPrefix = getPrefix<TValue, TPrefix>(currentValue)\n\n if (currentPrefix !== prefix) {\n throw new Error(`Mismatching prefixes, this should never happen`)\n }\n\n if (currentValue === value || hash(currentValue) === hash(value)) {\n // Same value, update multiplicity\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.delete(prefix)\n } else {\n this.set(prefix, [value, newMultiplicity])\n }\n } else {\n // Different suffixes, need to create ValueMap\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), valueMapOrSingleValue)\n valueMap.set(hash(value), [value, multiplicity])\n this.set(prefix, valueMap)\n }\n } else if (valueMapOrSingleValue === undefined) {\n // No existing value for this prefix\n this.set(prefix, [value, multiplicity])\n } else {\n // Existing ValueMap\n const isEmpty = valueMapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.delete(prefix)\n }\n }\n\n return this.size === 0\n }\n}\n\n// Third level map type for the index, stores single values or value maps against a hash.\nclass ValueMap<TValue> extends Map<Hash, [TValue, number]> {\n /**\n * Add a value to the ValueMap. Returns true if the map becomes empty after the operation.\n * @param value - The full value to store\n * @param multiplicity - The multiplicity to add\n * @param hashKey - Optional hash key to use instead of hashing the full value (used when in PrefixMap context)\n */\n addValue(value: TValue, multiplicity: number): boolean {\n if (multiplicity === 0) return this.size === 0\n\n const key = hash(value)\n const currentValue = this.get(key)\n\n if (currentValue) {\n const [, currentMultiplicity] = currentValue\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.delete(key)\n } else {\n this.set(key, [value, newMultiplicity])\n }\n } else {\n this.set(key, [value, multiplicity])\n }\n\n return this.size === 0\n }\n}\n\n/**\n * A map from a difference collection trace's keys -> (value, multiplicities) that changed.\n * Used in operations like join and reduce where the operation needs to\n * exploit the key-value structure of the data to run efficiently.\n */\nexport class Index<TKey, TValue, TPrefix = any> {\n /*\n * This index maintains a nested map of keys -> (value, multiplicities), where:\n * - initially the values are stored against the key as a single value tuple\n * - when a key gets additional values, the values are stored against the key in a\n * prefix map\n * - the prefix is extract where possible from values that are structured as\n * [rowPrimaryKey, rowValue], as they are in the Tanstack DB query pipeline.\n * - only when there are multiple values for a given prefix do we fall back to a\n * hash to identify identical values, storing them in a third level value map.\n */\n #inner: IndexMap<TKey, TValue, TPrefix>\n #consolidatedMultiplicity: Map<TKey, number> = new Map() // sum of multiplicities per key\n\n constructor() {\n this.#inner = new Map()\n }\n\n /**\n * Create an Index from multiple MultiSet messages.\n * @param messages - Array of MultiSet messages to build the index from.\n * @returns A new Index containing all the data from the messages.\n */\n static fromMultiSets<K, V>(messages: Array<MultiSet<[K, V]>>): Index<K, V> {\n const index = new Index<K, V>()\n\n for (const message of messages) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n index.addValue(key, [value, multiplicity])\n }\n }\n\n return index\n }\n\n /**\n * This method returns a string representation of the index.\n * @param indent - Whether to indent the string representation.\n * @returns A string representation of the index.\n */\n toString(indent = false): string {\n return `Index(${JSON.stringify(\n [...this.entries()],\n undefined,\n indent ? 2 : undefined,\n )})`\n }\n\n /**\n * The size of the index.\n */\n get size(): number {\n return this.#inner.size\n }\n\n /**\n * This method checks if the index has a given key.\n * @param key - The key to check.\n * @returns True if the index has the key, false otherwise.\n */\n has(key: TKey): boolean {\n return this.#inner.has(key)\n }\n\n /**\n * Check if a key has presence (non-zero consolidated multiplicity).\n * @param key - The key to check.\n * @returns True if the key has non-zero consolidated multiplicity, false otherwise.\n */\n hasPresence(key: TKey): boolean {\n return (this.#consolidatedMultiplicity.get(key) || 0) !== 0\n }\n\n /**\n * Get the consolidated multiplicity (sum of multiplicities) for a key.\n * @param key - The key to get the consolidated multiplicity for.\n * @returns The consolidated multiplicity for the key.\n */\n getConsolidatedMultiplicity(key: TKey): number {\n return this.#consolidatedMultiplicity.get(key) || 0\n }\n\n /**\n * Get all keys that have presence (non-zero consolidated multiplicity).\n * @returns An iterator of keys with non-zero consolidated multiplicity.\n */\n getPresenceKeys(): Iterable<TKey> {\n return this.#consolidatedMultiplicity.keys()\n }\n\n /**\n * This method returns all values for a given key.\n * @param key - The key to get the values for.\n * @returns An array of value tuples [value, multiplicity].\n */\n get(key: TKey): Array<[TValue, number]> {\n return [...this.getIterator(key)]\n }\n\n /**\n * This method returns an iterator over all values for a given key.\n * @param key - The key to get the values for.\n * @returns An iterator of value tuples [value, multiplicity].\n */\n *getIterator(key: TKey): Iterable<[TValue, number]> {\n const mapOrSingleValue = this.#inner.get(key)\n if (isSingleValue(mapOrSingleValue)) {\n yield mapOrSingleValue\n } else if (mapOrSingleValue === undefined) {\n return\n } else if (mapOrSingleValue instanceof ValueMap) {\n // Direct ValueMap - all values have NO_PREFIX\n for (const valueTuple of mapOrSingleValue.values()) {\n yield valueTuple\n }\n } else {\n // PrefixMap - iterate through all prefixes\n for (const singleValueOrValueMap of mapOrSingleValue.values()) {\n if (isSingleValue(singleValueOrValueMap)) {\n yield singleValueOrValueMap\n } else {\n for (const valueTuple of singleValueOrValueMap.values()) {\n yield valueTuple\n }\n }\n }\n }\n }\n\n /**\n * This returns an iterator that iterates over all key-value pairs.\n * @returns An iterable of all key-value pairs (and their multiplicities) in the index.\n */\n *entries(): Iterable<[TKey, [TValue, number]]> {\n for (const key of this.#inner.keys()) {\n for (const valueTuple of this.getIterator(key)) {\n yield [key, valueTuple]\n }\n }\n }\n\n /**\n * This method only iterates over the keys and not over the values.\n * Hence, it is more efficient than the `#entries` method.\n * It returns an iterator that you can use if you need to iterate over the values for a given key.\n * @returns An iterator of all *keys* in the index and their corresponding value iterator.\n */\n *entriesIterators(): Iterable<[TKey, Iterable<[TValue, number]>]> {\n for (const key of this.#inner.keys()) {\n yield [key, this.getIterator(key)]\n }\n }\n\n /**\n * This method adds a value to the index.\n * @param key - The key to add the value to.\n * @param valueTuple - The value tuple [value, multiplicity] to add to the index.\n */\n addValue(key: TKey, valueTuple: SingleValue<TValue>) {\n const [value, multiplicity] = valueTuple\n // If the multiplicity is 0, do nothing\n if (multiplicity === 0) return\n\n // Update consolidated multiplicity tracking\n const newConsolidatedMultiplicity =\n (this.#consolidatedMultiplicity.get(key) || 0) + multiplicity\n if (newConsolidatedMultiplicity === 0) {\n this.#consolidatedMultiplicity.delete(key)\n } else {\n this.#consolidatedMultiplicity.set(key, newConsolidatedMultiplicity)\n }\n\n const mapOrSingleValue = this.#inner.get(key)\n\n if (mapOrSingleValue === undefined) {\n // First value for this key\n this.#inner.set(key, valueTuple)\n return\n }\n\n if (isSingleValue(mapOrSingleValue)) {\n // Handle transition from single value to map\n this.#handleSingleValueTransition(\n key,\n mapOrSingleValue,\n value,\n multiplicity,\n )\n return\n }\n\n if (mapOrSingleValue instanceof ValueMap) {\n // Handle existing ValueMap\n const prefix = getPrefix<TValue, TPrefix>(value)\n if (prefix !== NO_PREFIX) {\n // Convert ValueMap to PrefixMap since we have a prefixed value\n const prefixMap = new PrefixMap<TValue, TPrefix>()\n prefixMap.set(NO_PREFIX, mapOrSingleValue)\n prefixMap.set(prefix, valueTuple)\n this.#inner.set(key, prefixMap)\n } else {\n // Add to existing ValueMap\n const isEmpty = mapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.#inner.delete(key)\n }\n }\n } else {\n // Handle existing PrefixMap\n const isEmpty = mapOrSingleValue.addValue(value, multiplicity)\n if (isEmpty) {\n this.#inner.delete(key)\n }\n }\n }\n\n /**\n * Handle the transition from a single value to either a ValueMap or PrefixMap\n */\n #handleSingleValueTransition(\n key: TKey,\n currentSingleValue: SingleValue<TValue>,\n newValue: TValue,\n multiplicity: number,\n ) {\n const [currentValue, currentMultiplicity] = currentSingleValue\n\n // Check for exact same value (reference equality)\n if (currentValue === newValue) {\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#inner.delete(key)\n } else {\n this.#inner.set(key, [newValue, newMultiplicity])\n }\n return\n }\n\n // Get prefixes for both values\n const newPrefix = getPrefix<TValue, TPrefix>(newValue)\n const currentPrefix = getPrefix<TValue, TPrefix>(currentValue)\n\n // Check if they're the same value by prefix/suffix comparison\n if (\n currentPrefix === newPrefix &&\n (currentValue === newValue || hash(currentValue) === hash(newValue))\n ) {\n const newMultiplicity = currentMultiplicity + multiplicity\n if (newMultiplicity === 0) {\n this.#inner.delete(key)\n } else {\n this.#inner.set(key, [newValue, newMultiplicity])\n }\n return\n }\n\n // Different values - choose appropriate map type\n if (currentPrefix === NO_PREFIX && newPrefix === NO_PREFIX) {\n // Both have NO_PREFIX, use ValueMap directly\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), currentSingleValue)\n valueMap.set(hash(newValue), [newValue, multiplicity])\n this.#inner.set(key, valueMap)\n } else {\n // At least one has a prefix, use PrefixMap\n const prefixMap = new PrefixMap<TValue, TPrefix>()\n\n if (currentPrefix === newPrefix) {\n // Same prefix, different suffixes - need ValueMap within PrefixMap\n const valueMap = new ValueMap<TValue>()\n valueMap.set(hash(currentValue), currentSingleValue)\n valueMap.set(hash(newValue), [newValue, multiplicity])\n prefixMap.set(currentPrefix, valueMap)\n } else {\n // Different prefixes - store as separate single values\n prefixMap.set(currentPrefix, currentSingleValue)\n prefixMap.set(newPrefix, [newValue, multiplicity])\n }\n\n this.#inner.set(key, prefixMap)\n }\n }\n\n /**\n * This method appends another index to the current index.\n * @param other - The index to append to the current index.\n */\n append(other: Index<TKey, TValue>): void {\n for (const [key, value] of other.entries()) {\n this.addValue(key, value)\n }\n }\n\n /**\n * This method joins two indexes.\n * @param other - The index to join with the current index.\n * @returns A multiset of the joined values.\n */\n join<TValue2>(\n other: Index<TKey, TValue2>,\n ): MultiSet<[TKey, [TValue, TValue2]]> {\n const result: Array<[[TKey, [TValue, TValue2]], number]> = []\n // We want to iterate over the smaller of the two indexes to reduce the\n // number of operations we need to do.\n if (this.size <= other.size) {\n for (const [key, valueIt] of this.entriesIterators()) {\n if (!other.has(key)) continue\n const otherValues = other.get(key)\n for (const [val1, mul1] of valueIt) {\n for (const [val2, mul2] of otherValues) {\n if (mul1 !== 0 && mul2 !== 0) {\n result.push([[key, [val1, val2]], mul1 * mul2])\n }\n }\n }\n }\n } else {\n for (const [key, otherValueIt] of other.entriesIterators()) {\n if (!this.has(key)) continue\n const values = this.get(key)\n for (const [val2, mul2] of otherValueIt) {\n for (const [val1, mul1] of values) {\n if (mul1 !== 0 && mul2 !== 0) {\n result.push([[key, [val1, val2]], mul1 * mul2])\n }\n }\n }\n }\n }\n\n return new MultiSet(result)\n }\n}\n\n/**\n * This function extracts the prefix from a value.\n * @param value - The value to extract the prefix from.\n * @returns The prefix and the suffix.\n */\nfunction getPrefix<TValue, TPrefix>(value: TValue): TPrefix | NO_PREFIX {\n // If the value is an array and the first element is a string or number, then the\n // first element is the prefix. This is used to distinguish between values without\n // the need for hashing unless there are multiple values for the same prefix.\n if (\n Array.isArray(value) &&\n (typeof value[0] === `string` ||\n typeof value[0] === `number` ||\n typeof value[0] === `bigint`)\n ) {\n return value[0] as TPrefix\n }\n return NO_PREFIX\n}\n\n/**\n * This function checks if a value is a single value.\n * @param value - The value to check.\n * @returns True if the value is a single value, false otherwise.\n */\nfunction isSingleValue<TValue>(\n value: SingleValue<TValue> | unknown,\n): value is SingleValue<TValue> {\n return Array.isArray(value)\n}\n"],"names":["hash","MultiSet"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multiset.cjs","sources":["../../src/multiset.ts"],"sourcesContent":["import {\n DefaultMap,\n chunkedArrayPush,\n globalObjectIdGenerator,\n} from \"./utils.js\"\nimport { hash } from \"./hashing/index.js\"\n\nexport type MultiSetArray<T> = Array<[T, number]>\nexport type KeyedData<T> = [key: string, value: T]\n\n/**\n * A multiset of data.\n */\nexport class MultiSet<T> {\n #inner: MultiSetArray<T>\n\n constructor(data: MultiSetArray<T> = []) {\n this.#inner = data\n }\n\n toString(indent = false): string {\n return `MultiSet(${JSON.stringify(this.#inner, null, indent ? 2 : undefined)})`\n }\n\n toJSON(): string {\n return JSON.stringify(Array.from(this.getInner()))\n }\n\n static fromJSON<U>(json: string): MultiSet<U> {\n return new MultiSet(JSON.parse(json))\n }\n\n /**\n * Apply a function to all records in the collection.\n */\n map<U>(f: (data: T) => U): MultiSet<U> {\n return new MultiSet(\n this.#inner.map(([data, multiplicity]) => [f(data), multiplicity])\n )\n }\n\n /**\n * Filter out records for which a function f(record) evaluates to False.\n */\n filter(f: (data: T) => boolean): MultiSet<T> {\n return new MultiSet(this.#inner.filter(([data, _]) => f(data)))\n }\n\n /**\n * Negate all multiplicities in the collection.\n */\n negate(): MultiSet<T> {\n return new MultiSet(\n this.#inner.map(([data, multiplicity]) => [data, -multiplicity])\n )\n }\n\n /**\n * Concatenate two collections together.\n */\n concat(other: MultiSet<T>): MultiSet<T> {\n const out: MultiSetArray<T> = []\n chunkedArrayPush(out, this.#inner)\n chunkedArrayPush(out, other.getInner())\n return new MultiSet(out)\n }\n\n /**\n * Produce as output a collection that is logically equivalent to the input\n * but which combines identical instances of the same record into one\n * (record, multiplicity) pair.\n */\n consolidate(): MultiSet<T> {\n // Check if this looks like a keyed multiset (first item is a tuple of length 2)\n if (this.#inner.length > 0) {\n const firstItem = this.#inner[0]?.[0]\n if (Array.isArray(firstItem) && firstItem.length === 2) {\n return this.#consolidateKeyed()\n }\n }\n\n // Fall back to original method for unkeyed data\n return this.#consolidateUnkeyed()\n }\n\n /**\n * Private method for consolidating keyed multisets where keys are strings/numbers\n * and values are compared by reference equality.\n *\n * This method provides significant performance improvements over the hash-based approach\n * by using WeakMap for object reference tracking and avoiding expensive serialization.\n *\n * Special handling for join operations: When values are tuples of length 2 (common in joins),\n * we unpack them and compare each element individually to maintain proper equality semantics.\n */\n #consolidateKeyed(): MultiSet<T> {\n const consolidated = new Map<string, number>()\n const values = new Map<string, T>()\n\n // Use global object ID generator for consistent reference equality\n\n /**\n * Special handler for tuples (arrays of length 2) commonly produced by join operations.\n * Unpacks the tuple and generates an ID based on both elements to ensure proper\n * consolidation of join results like ['A', null] and [null, 'X'].\n */\n const getTupleId = (tuple: Array<any>): string => {\n if (tuple.length !== 2) {\n throw new Error(`Expected tuple of length 2`)\n }\n const [first, second] = tuple\n return `${globalObjectIdGenerator.getStringId(first)}|${globalObjectIdGenerator.getStringId(second)}`\n }\n\n // Process each item in the multiset\n for (const [data, multiplicity] of this.#inner) {\n // Verify this is still a keyed item (should be [key, value] pair)\n if (!Array.isArray(data) || data.length !== 2) {\n // Found non-keyed item, fall back to unkeyed consolidation\n return this.#consolidateUnkeyed()\n }\n\n const [key, value] = data\n\n // Verify key is string or number as expected for keyed multisets\n if (typeof key !== `string` && typeof key !== `number`) {\n // Found non-string/number key, fall back to unkeyed consolidation\n return this.#consolidateUnkeyed()\n }\n\n // Generate value ID with special handling for join tuples\n let valueId: string\n if (Array.isArray(value) && value.length === 2) {\n // Special case: value is a tuple from join operations\n valueId = getTupleId(value)\n } else {\n // Regular case: use reference/value equality\n valueId = globalObjectIdGenerator.getStringId(value)\n }\n\n // Create composite key and consolidate\n const compositeKey = key + `|` + valueId\n consolidated.set(\n compositeKey,\n (consolidated.get(compositeKey) || 0) + multiplicity\n )\n\n // Store the original data for the first occurrence\n if (!values.has(compositeKey)) {\n values.set(compositeKey, data as T)\n }\n }\n\n // Build result array, filtering out zero multiplicities\n const result: MultiSetArray<T> = []\n for (const [compositeKey, multiplicity] of consolidated) {\n if (multiplicity !== 0) {\n result.push([values.get(compositeKey)!, multiplicity])\n }\n }\n\n return new MultiSet(result)\n }\n\n /**\n * Private method for consolidating unkeyed multisets using the original approach.\n */\n #consolidateUnkeyed(): MultiSet<T> {\n const consolidated = new DefaultMap<string | number, number>(() => 0)\n const values = new Map<string, any>()\n\n let hasString = false\n let hasNumber = false\n let hasOther = false\n for (const [data, _] of this.#inner) {\n if (typeof data === `string`) {\n hasString = true\n } else if (typeof data === `number`) {\n hasNumber = true\n } else {\n hasOther = true\n break\n }\n }\n\n const requireJson = hasOther || (hasString && hasNumber)\n\n for (const [data, multiplicity] of this.#inner) {\n const key = requireJson ? hash(data) : (data as string | number)\n if (requireJson && !values.has(key as string)) {\n values.set(key as string, data)\n }\n consolidated.update(key, (count) => count + multiplicity)\n }\n\n const result: MultiSetArray<T> = []\n for (const [key, multiplicity] of consolidated.entries()) {\n if (multiplicity !== 0) {\n const parsedKey = requireJson ? values.get(key as string) : key\n result.push([parsedKey as T, multiplicity])\n }\n }\n\n return new MultiSet(result)\n }\n\n extend(other: MultiSet<T> | MultiSetArray<T>): void {\n const otherArray = other instanceof MultiSet ? other.getInner() : other\n chunkedArrayPush(this.#inner, otherArray)\n }\n\n add(item: T, multiplicity: number): void {\n if (multiplicity !== 0) {\n this.#inner.push([item, multiplicity])\n }\n }\n\n getInner(): MultiSetArray<T> {\n return this.#inner\n }\n}\n"],"names":["chunkedArrayPush","globalObjectIdGenerator","DefaultMap","hash"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"multiset.cjs","sources":["../../src/multiset.ts"],"sourcesContent":["import {\n DefaultMap,\n chunkedArrayPush,\n globalObjectIdGenerator,\n} from './utils.js'\nimport { hash } from './hashing/index.js'\n\nexport type MultiSetArray<T> = Array<[T, number]>\nexport type KeyedData<T> = [key: string, value: T]\n\n/**\n * A multiset of data.\n */\nexport class MultiSet<T> {\n #inner: MultiSetArray<T>\n\n constructor(data: MultiSetArray<T> = []) {\n this.#inner = data\n }\n\n toString(indent = false): string {\n return `MultiSet(${JSON.stringify(this.#inner, null, indent ? 2 : undefined)})`\n }\n\n toJSON(): string {\n return JSON.stringify(Array.from(this.getInner()))\n }\n\n static fromJSON<U>(json: string): MultiSet<U> {\n return new MultiSet(JSON.parse(json))\n }\n\n /**\n * Apply a function to all records in the collection.\n */\n map<U>(f: (data: T) => U): MultiSet<U> {\n return new MultiSet(\n this.#inner.map(([data, multiplicity]) => [f(data), multiplicity]),\n )\n }\n\n /**\n * Filter out records for which a function f(record) evaluates to False.\n */\n filter(f: (data: T) => boolean): MultiSet<T> {\n return new MultiSet(this.#inner.filter(([data, _]) => f(data)))\n }\n\n /**\n * Negate all multiplicities in the collection.\n */\n negate(): MultiSet<T> {\n return new MultiSet(\n this.#inner.map(([data, multiplicity]) => [data, -multiplicity]),\n )\n }\n\n /**\n * Concatenate two collections together.\n */\n concat(other: MultiSet<T>): MultiSet<T> {\n const out: MultiSetArray<T> = []\n chunkedArrayPush(out, this.#inner)\n chunkedArrayPush(out, other.getInner())\n return new MultiSet(out)\n }\n\n /**\n * Produce as output a collection that is logically equivalent to the input\n * but which combines identical instances of the same record into one\n * (record, multiplicity) pair.\n */\n consolidate(): MultiSet<T> {\n // Check if this looks like a keyed multiset (first item is a tuple of length 2)\n if (this.#inner.length > 0) {\n const firstItem = this.#inner[0]?.[0]\n if (Array.isArray(firstItem) && firstItem.length === 2) {\n return this.#consolidateKeyed()\n }\n }\n\n // Fall back to original method for unkeyed data\n return this.#consolidateUnkeyed()\n }\n\n /**\n * Private method for consolidating keyed multisets where keys are strings/numbers\n * and values are compared by reference equality.\n *\n * This method provides significant performance improvements over the hash-based approach\n * by using WeakMap for object reference tracking and avoiding expensive serialization.\n *\n * Special handling for join operations: When values are tuples of length 2 (common in joins),\n * we unpack them and compare each element individually to maintain proper equality semantics.\n */\n #consolidateKeyed(): MultiSet<T> {\n const consolidated = new Map<string, number>()\n const values = new Map<string, T>()\n\n // Use global object ID generator for consistent reference equality\n\n /**\n * Special handler for tuples (arrays of length 2) commonly produced by join operations.\n * Unpacks the tuple and generates an ID based on both elements to ensure proper\n * consolidation of join results like ['A', null] and [null, 'X'].\n */\n const getTupleId = (tuple: Array<any>): string => {\n if (tuple.length !== 2) {\n throw new Error(`Expected tuple of length 2`)\n }\n const [first, second] = tuple\n return `${globalObjectIdGenerator.getStringId(first)}|${globalObjectIdGenerator.getStringId(second)}`\n }\n\n // Process each item in the multiset\n for (const [data, multiplicity] of this.#inner) {\n // Verify this is still a keyed item (should be [key, value] pair)\n if (!Array.isArray(data) || data.length !== 2) {\n // Found non-keyed item, fall back to unkeyed consolidation\n return this.#consolidateUnkeyed()\n }\n\n const [key, value] = data\n\n // Verify key is string or number as expected for keyed multisets\n if (typeof key !== `string` && typeof key !== `number`) {\n // Found non-string/number key, fall back to unkeyed consolidation\n return this.#consolidateUnkeyed()\n }\n\n // Generate value ID with special handling for join tuples\n let valueId: string\n if (Array.isArray(value) && value.length === 2) {\n // Special case: value is a tuple from join operations\n valueId = getTupleId(value)\n } else {\n // Regular case: use reference/value equality\n valueId = globalObjectIdGenerator.getStringId(value)\n }\n\n // Create composite key and consolidate\n const compositeKey = key + `|` + valueId\n consolidated.set(\n compositeKey,\n (consolidated.get(compositeKey) || 0) + multiplicity,\n )\n\n // Store the original data for the first occurrence\n if (!values.has(compositeKey)) {\n values.set(compositeKey, data as T)\n }\n }\n\n // Build result array, filtering out zero multiplicities\n const result: MultiSetArray<T> = []\n for (const [compositeKey, multiplicity] of consolidated) {\n if (multiplicity !== 0) {\n result.push([values.get(compositeKey)!, multiplicity])\n }\n }\n\n return new MultiSet(result)\n }\n\n /**\n * Private method for consolidating unkeyed multisets using the original approach.\n */\n #consolidateUnkeyed(): MultiSet<T> {\n const consolidated = new DefaultMap<string | number, number>(() => 0)\n const values = new Map<string, any>()\n\n let hasString = false\n let hasNumber = false\n let hasOther = false\n for (const [data, _] of this.#inner) {\n if (typeof data === `string`) {\n hasString = true\n } else if (typeof data === `number`) {\n hasNumber = true\n } else {\n hasOther = true\n break\n }\n }\n\n const requireJson = hasOther || (hasString && hasNumber)\n\n for (const [data, multiplicity] of this.#inner) {\n const key = requireJson ? hash(data) : (data as string | number)\n if (requireJson && !values.has(key as string)) {\n values.set(key as string, data)\n }\n consolidated.update(key, (count) => count + multiplicity)\n }\n\n const result: MultiSetArray<T> = []\n for (const [key, multiplicity] of consolidated.entries()) {\n if (multiplicity !== 0) {\n const parsedKey = requireJson ? values.get(key as string) : key\n result.push([parsedKey as T, multiplicity])\n }\n }\n\n return new MultiSet(result)\n }\n\n extend(other: MultiSet<T> | MultiSetArray<T>): void {\n const otherArray = other instanceof MultiSet ? other.getInner() : other\n chunkedArrayPush(this.#inner, otherArray)\n }\n\n add(item: T, multiplicity: number): void {\n if (multiplicity !== 0) {\n this.#inner.push([item, multiplicity])\n }\n }\n\n getInner(): MultiSetArray<T> {\n return this.#inner\n }\n}\n"],"names":["chunkedArrayPush","globalObjectIdGenerator","DefaultMap","hash"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"concat.cjs","sources":["../../../src/operators/concat.ts"],"sourcesContent":["import { BinaryOperator, DifferenceStreamWriter } from
|
|
1
|
+
{"version":3,"file":"concat.cjs","sources":["../../../src/operators/concat.ts"],"sourcesContent":["import { BinaryOperator, DifferenceStreamWriter } from '../graph.js'\nimport { StreamBuilder } from '../d2.js'\nimport type { IStreamBuilder, PipedOperator } from '../types.js'\n\n/**\n * Operator that concatenates two input streams\n */\nexport class ConcatOperator<T, T2> extends BinaryOperator<T | T2> {\n run(): void {\n for (const message of this.inputAMessages()) {\n this.output.sendData(message)\n }\n\n for (const message of this.inputBMessages()) {\n this.output.sendData(message)\n }\n }\n}\n\n/**\n * Concatenates two input streams\n * @param other - The other stream to concatenate\n */\nexport function concat<T, T2>(\n other: IStreamBuilder<T2>,\n): PipedOperator<T, T | T2> {\n return (stream: IStreamBuilder<T | T2>): IStreamBuilder<T | T2> => {\n if (stream.graph !== other.graph) {\n throw new Error(`Cannot concat streams from different graphs`)\n }\n const output = new StreamBuilder<T | T2>(\n stream.graph,\n new DifferenceStreamWriter<T | T2>(),\n )\n const operator = new ConcatOperator<T, T2>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n other.connectReader(),\n output.writer,\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n"],"names":["BinaryOperator","StreamBuilder","DifferenceStreamWriter"],"mappings":";;;;AAOO,MAAM,uBAA8BA,MAAAA,eAAuB;AAAA,EAChE,MAAY;AACV,eAAW,WAAW,KAAK,kBAAkB;AAC3C,WAAK,OAAO,SAAS,OAAO;AAAA,IAC9B;AAEA,eAAW,WAAW,KAAK,kBAAkB;AAC3C,WAAK,OAAO,SAAS,OAAO;AAAA,IAC9B;AAAA,EACF;AACF;AAMO,SAAS,OACd,OAC0B;AAC1B,SAAO,CAAC,WAA2D;AACjE,QAAI,OAAO,UAAU,MAAM,OAAO;AAChC,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AACA,UAAM,SAAS,IAAIC,GAAAA;AAAAA,MACjB,OAAO;AAAA,MACP,IAAIC,MAAAA,uBAAA;AAAA,IAA+B;AAErC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,MAAM,cAAA;AAAA,MACN,OAAO;AAAA,IAAA;AAET,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO;AAAA,EACT;AACF;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consolidate.cjs","sources":["../../../src/operators/consolidate.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from
|
|
1
|
+
{"version":3,"file":"consolidate.cjs","sources":["../../../src/operators/consolidate.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from '../graph.js'\nimport { StreamBuilder } from '../d2.js'\nimport { MultiSet } from '../multiset.js'\nimport type { IStreamBuilder, PipedOperator } from '../types.js'\n\n/**\n * Operator that consolidates collections\n */\nexport class ConsolidateOperator<T> extends UnaryOperator<T> {\n run(): void {\n const messages = this.inputMessages()\n if (messages.length === 0) {\n return\n }\n\n // Combine all messages into a single MultiSet\n const combined = new MultiSet<T>()\n for (const message of messages) {\n combined.extend(message)\n }\n\n // Consolidate the combined MultiSet\n const consolidated = combined.consolidate()\n\n // Only send if there are results\n if (consolidated.getInner().length > 0) {\n this.output.sendData(consolidated)\n }\n }\n}\n\n/**\n * Consolidates the elements in the stream\n */\nexport function consolidate<T>(): PipedOperator<T, T> {\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const output = new StreamBuilder<T>(\n stream.graph,\n new DifferenceStreamWriter<T>(),\n )\n const operator = new ConsolidateOperator<T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n"],"names":["UnaryOperator","MultiSet","StreamBuilder","DifferenceStreamWriter"],"mappings":";;;;;AAQO,MAAM,4BAA+BA,MAAAA,cAAiB;AAAA,EAC3D,MAAY;AACV,UAAM,WAAW,KAAK,cAAA;AACtB,QAAI,SAAS,WAAW,GAAG;AACzB;AAAA,IACF;AAGA,UAAM,WAAW,IAAIC,kBAAA;AACrB,eAAW,WAAW,UAAU;AAC9B,eAAS,OAAO,OAAO;AAAA,IACzB;AAGA,UAAM,eAAe,SAAS,YAAA;AAG9B,QAAI,aAAa,WAAW,SAAS,GAAG;AACtC,WAAK,OAAO,SAAS,YAAY;AAAA,IACnC;AAAA,EACF;AACF;AAKO,SAAS,cAAsC;AACpD,SAAO,CAAC,WAAiD;AACvD,UAAM,SAAS,IAAIC,GAAAA;AAAAA,MACjB,OAAO;AAAA,MACP,IAAIC,MAAAA,uBAAA;AAAA,IAA0B;AAEhC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,IAAA;AAET,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO;AAAA,EACT;AACF;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"count.cjs","sources":["../../../src/operators/count.ts"],"sourcesContent":["import { DifferenceStreamWriter } from
|
|
1
|
+
{"version":3,"file":"count.cjs","sources":["../../../src/operators/count.ts"],"sourcesContent":["import { DifferenceStreamWriter } from '../graph.js'\nimport { StreamBuilder } from '../d2.js'\nimport { ReduceOperator } from './reduce.js'\nimport type { DifferenceStreamReader } from '../graph.js'\nimport type { IStreamBuilder, KeyValue } from '../types.js'\n\n/**\n * Operator that counts elements by key (version-free)\n */\nexport class CountOperator<K, V> extends ReduceOperator<K, V, number> {\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, V]>,\n output: DifferenceStreamWriter<[K, number]>,\n ) {\n const countInner = (vals: Array<[V, number]>): Array<[number, number]> => {\n let totalCount = 0\n for (const [_, diff] of vals) {\n totalCount += diff\n }\n return [[totalCount, 1]]\n }\n\n super(id, inputA, output, countInner)\n }\n}\n\n/**\n * Counts the number of elements by key (version-free)\n */\nexport function count<\n KType extends T extends KeyValue<infer K, infer _V> ? K : never,\n VType extends T extends KeyValue<KType, infer V> ? V : never,\n T,\n>() {\n return (\n stream: IStreamBuilder<T>,\n ): IStreamBuilder<KeyValue<KType, number>> => {\n const output = new StreamBuilder<KeyValue<KType, number>>(\n stream.graph,\n new DifferenceStreamWriter<KeyValue<KType, number>>(),\n )\n const operator = new CountOperator<KType, VType>(\n stream.graph.getNextOperatorId(),\n stream.connectReader() as DifferenceStreamReader<KeyValue<KType, VType>>,\n output.writer,\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n"],"names":["ReduceOperator","StreamBuilder","DifferenceStreamWriter"],"mappings":";;;;;AASO,MAAM,sBAA4BA,OAAAA,eAA6B;AAAA,EACpE,YACE,IACA,QACA,QACA;AACA,UAAM,aAAa,CAAC,SAAsD;AACxE,UAAI,aAAa;AACjB,iBAAW,CAAC,GAAG,IAAI,KAAK,MAAM;AAC5B,sBAAc;AAAA,MAChB;AACA,aAAO,CAAC,CAAC,YAAY,CAAC,CAAC;AAAA,IACzB;AAEA,UAAM,IAAI,QAAQ,QAAQ,UAAU;AAAA,EACtC;AACF;AAKO,SAAS,QAIZ;AACF,SAAO,CACL,WAC4C;AAC5C,UAAM,SAAS,IAAIC,GAAAA;AAAAA,MACjB,OAAO;AAAA,MACP,IAAIC,MAAAA,uBAAA;AAAA,IAAgD;AAEtD,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,IAAA;AAET,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO;AAAA,EACT;AACF;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.cjs","sources":["../../../src/operators/debug.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from
|
|
1
|
+
{"version":3,"file":"debug.cjs","sources":["../../../src/operators/debug.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from '../graph.js'\nimport { StreamBuilder } from '../d2.js'\nimport type { IStreamBuilder, PipedOperator } from '../types.js'\nimport type { DifferenceStreamReader } from '../graph.js'\n\n/**\n * Operator that logs debug information about the stream\n */\nexport class DebugOperator<T> extends UnaryOperator<T> {\n #name: string\n #indent: boolean\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<T>,\n output: DifferenceStreamWriter<T>,\n name: string,\n indent: boolean = false,\n ) {\n super(id, inputA, output)\n this.#name = name\n this.#indent = indent\n }\n\n run(): void {\n for (const message of this.inputMessages()) {\n console.log(`debug ${this.#name} data: ${message.toString(this.#indent)}`)\n this.output.sendData(message)\n }\n }\n}\n\n/**\n * Logs debug information about the stream using console.log\n * @param name - The name to prefix debug messages with\n * @param indent - Whether to indent the debug output\n */\nexport function debug<T>(\n name: string,\n indent: boolean = false,\n): PipedOperator<T, T> {\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const output = new StreamBuilder<T>(\n stream.graph,\n new DifferenceStreamWriter<T>(),\n )\n const operator = new DebugOperator<T>(\n stream.graph.getNextOperatorId(),\n stream.connectReader(),\n output.writer,\n name,\n indent,\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n"],"names":["UnaryOperator","StreamBuilder","DifferenceStreamWriter"],"mappings":";;;;AAQO,MAAM,sBAAyBA,MAAAA,cAAiB;AAAA,EACrD;AAAA,EACA;AAAA,EAEA,YACE,IACA,QACA,QACA,MACA,SAAkB,OAClB;AACA,UAAM,IAAI,QAAQ,MAAM;AACxB,SAAK,QAAQ;AACb,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAY;AACV,eAAW,WAAW,KAAK,iBAAiB;AAC1C,cAAQ,IAAI,SAAS,KAAK,KAAK,UAAU,QAAQ,SAAS,KAAK,OAAO,CAAC,EAAE;AACzE,WAAK,OAAO,SAAS,OAAO;AAAA,IAC9B;AAAA,EACF;AACF;AAOO,SAAS,MACd,MACA,SAAkB,OACG;AACrB,SAAO,CAAC,WAAiD;AACvD,UAAM,SAAS,IAAIC,GAAAA;AAAAA,MACjB,OAAO;AAAA,MACP,IAAIC,MAAAA,uBAAA;AAAA,IAA0B;AAEhC,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,MACA;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO;AAAA,EACT;AACF;;;"}
|