@tanstack/db-ivm 0.1.20 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/d2.cjs +12 -2
- package/dist/cjs/d2.cjs.map +1 -1
- package/dist/cjs/hashing/hash.cjs +92 -104
- package/dist/cjs/hashing/hash.cjs.map +1 -1
- package/dist/cjs/hashing/hash.d.cts +5 -0
- package/dist/cjs/hashing/temporal.cjs +18 -0
- package/dist/cjs/hashing/temporal.cjs.map +1 -0
- package/dist/cjs/hashing/temporal.d.cts +5 -0
- package/dist/cjs/multiset.cjs +47 -92
- package/dist/cjs/multiset.cjs.map +1 -1
- package/dist/cjs/multiset.d.cts +16 -0
- package/dist/cjs/operators/distinct.cjs.map +1 -1
- package/dist/cjs/operators/distinct.d.cts +1 -1
- package/dist/cjs/operators/groupBy.cjs +2 -2
- package/dist/cjs/operators/groupBy.cjs.map +1 -1
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs +4 -5
- package/dist/cjs/operators/groupedTopKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/operators/topKArray.cjs +2 -0
- package/dist/cjs/operators/topKArray.cjs.map +1 -1
- package/dist/cjs/operators/topKState.cjs +30 -0
- package/dist/cjs/operators/topKState.cjs.map +1 -1
- package/dist/cjs/operators/topKState.d.cts +9 -0
- package/dist/cjs/operators/topKWithFractionalIndex.cjs +4 -5
- package/dist/cjs/operators/topKWithFractionalIndex.cjs.map +1 -1
- package/dist/cjs/utils.cjs +15 -61
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +3 -32
- package/dist/esm/d2.js +12 -2
- package/dist/esm/d2.js.map +1 -1
- package/dist/esm/hashing/hash.d.ts +5 -0
- package/dist/esm/hashing/hash.js +92 -104
- package/dist/esm/hashing/hash.js.map +1 -1
- package/dist/esm/hashing/temporal.d.ts +5 -0
- package/dist/esm/hashing/temporal.js +18 -0
- package/dist/esm/hashing/temporal.js.map +1 -0
- package/dist/esm/multiset.d.ts +16 -0
- package/dist/esm/multiset.js +48 -93
- package/dist/esm/multiset.js.map +1 -1
- package/dist/esm/operators/distinct.d.ts +1 -1
- package/dist/esm/operators/distinct.js.map +1 -1
- package/dist/esm/operators/groupBy.js +2 -2
- package/dist/esm/operators/groupBy.js.map +1 -1
- package/dist/esm/operators/groupedTopKWithFractionalIndex.js +5 -6
- package/dist/esm/operators/groupedTopKWithFractionalIndex.js.map +1 -1
- package/dist/esm/operators/topKArray.js +2 -0
- package/dist/esm/operators/topKArray.js.map +1 -1
- package/dist/esm/operators/topKState.d.ts +9 -0
- package/dist/esm/operators/topKState.js +31 -1
- package/dist/esm/operators/topKState.js.map +1 -1
- package/dist/esm/operators/topKWithFractionalIndex.js +5 -6
- package/dist/esm/operators/topKWithFractionalIndex.js.map +1 -1
- package/dist/esm/utils.d.ts +3 -32
- package/dist/esm/utils.js +15 -61
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -1
- package/src/d2.ts +15 -2
- package/src/hashing/hash.ts +147 -135
- package/src/hashing/temporal.ts +25 -0
- package/src/multiset.ts +73 -132
- package/src/operators/distinct.ts +5 -3
- package/src/operators/groupBy.ts +8 -2
- package/src/operators/groupedTopKWithFractionalIndex.ts +10 -6
- package/src/operators/topKArray.ts +4 -0
- package/src/operators/topKState.ts +42 -0
- package/src/operators/topKWithFractionalIndex.ts +10 -6
- package/src/operators/topKWithFractionalIndexBTree.ts +7 -8
- package/src/utils.ts +20 -76
package/dist/cjs/d2.cjs
CHANGED
|
@@ -42,8 +42,18 @@ class D2 {
|
|
|
42
42
|
return this.#operators.some((op) => op.hasPendingWork());
|
|
43
43
|
}
|
|
44
44
|
run() {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
if (!this.#finalized) {
|
|
46
|
+
throw new Error(`Graph not finalized`);
|
|
47
|
+
}
|
|
48
|
+
let ran = true;
|
|
49
|
+
while (ran) {
|
|
50
|
+
ran = false;
|
|
51
|
+
for (const op of this.#operators) {
|
|
52
|
+
if (op.hasPendingWork()) {
|
|
53
|
+
op.run();
|
|
54
|
+
ran = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
59
|
}
|
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 if (!this.#finalized) {\n throw new Error(`Graph not finalized`)\n }\n\n // A later registered operator can send work to an earlier one. Continue\n // until a complete pass runs none.\n let ran = true\n while (ran) {\n ran = false\n for (const op of this.#operators) {\n if (op.hasPendingWork()) {\n op.run()\n ran = true\n }\n }\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,QAAI,CAAC,KAAK,YAAY;AACpB,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC;AAIA,QAAI,MAAM;AACV,WAAO,KAAK;AACV,YAAM;AACN,iBAAW,MAAM,KAAK,YAAY;AAChC,YAAI,GAAG,kBAAkB;AACvB,aAAG,IAAA;AACH,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;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,37 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const murmur = require("./murmur.cjs");
|
|
4
|
+
const temporal = require("./temporal.cjs");
|
|
4
5
|
const TRUE = murmur.randomHash();
|
|
5
6
|
const FALSE = murmur.randomHash();
|
|
6
7
|
const NULL = murmur.randomHash();
|
|
7
8
|
const UNDEFINED = murmur.randomHash();
|
|
8
9
|
const KEY = murmur.randomHash();
|
|
9
10
|
const FUNCTIONS = murmur.randomHash();
|
|
10
|
-
const DATE_MARKER = murmur.randomHash();
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
11
|
+
const DATE_MARKER = { hash: murmur.randomHash() };
|
|
12
|
+
const REGEXP_MARKER = { hash: murmur.randomHash() };
|
|
13
|
+
const OBJECT_MARKER = { hash: murmur.randomHash() };
|
|
14
|
+
const ARRAY_MARKER = { hash: murmur.randomHash() };
|
|
15
|
+
const MAP_MARKER = { hash: murmur.randomHash() };
|
|
16
|
+
const SET_MARKER = { hash: murmur.randomHash() };
|
|
17
|
+
const UINT8ARRAY_MARKER = { hash: murmur.randomHash() };
|
|
18
|
+
const TEMPORAL_MARKER = { hash: murmur.randomHash() };
|
|
17
19
|
const MAX_STRUCTURAL_HASH_WORK = 1e6;
|
|
18
20
|
const MAX_STRUCTURAL_HASH_DEPTH = 768;
|
|
19
|
-
const temporalTypes = /* @__PURE__ */ new Set([
|
|
20
|
-
`Temporal.Duration`,
|
|
21
|
-
`Temporal.Instant`,
|
|
22
|
-
`Temporal.PlainDate`,
|
|
23
|
-
`Temporal.PlainDateTime`,
|
|
24
|
-
`Temporal.PlainMonthDay`,
|
|
25
|
-
`Temporal.PlainTime`,
|
|
26
|
-
`Temporal.PlainYearMonth`,
|
|
27
|
-
`Temporal.ZonedDateTime`
|
|
28
|
-
]);
|
|
29
|
-
function isTemporal(input) {
|
|
30
|
-
const tag = input[Symbol.toStringTag];
|
|
31
|
-
return typeof tag === `string` && temporalTypes.has(tag);
|
|
32
|
-
}
|
|
33
21
|
const UINT8ARRAY_CONTENT_HASH_THRESHOLD = 128;
|
|
34
22
|
const hashCache = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
const referenceValues = /* @__PURE__ */ new WeakSet();
|
|
35
24
|
function registerOpaqueHash(value) {
|
|
36
25
|
cachedReferenceHash(value);
|
|
37
26
|
}
|
|
@@ -49,73 +38,51 @@ function hashObject(input, context) {
|
|
|
49
38
|
context.activeObjects.add(input);
|
|
50
39
|
let valueHash;
|
|
51
40
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
41
|
+
const [marker, header, body] = objectParts(input);
|
|
42
|
+
const hasher = new murmur.MurmurHashStream();
|
|
43
|
+
hasher.update(marker.hash);
|
|
44
|
+
for (const value of header)
|
|
45
|
+
if (body) updateHasher(hasher, value, context);
|
|
46
|
+
else hasher.update(value);
|
|
47
|
+
if (body) {
|
|
48
|
+
const keys = Object.keys(body).sort(keySort);
|
|
49
|
+
keys.push(
|
|
50
|
+
...Object.getOwnPropertySymbols(body).filter(
|
|
51
|
+
(key) => Object.prototype.propertyIsEnumerable.call(body, key)
|
|
52
|
+
).sort(
|
|
53
|
+
(left, right) => murmur.getSymbolIdentity(left) - murmur.getSymbolIdentity(right)
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
for (const key of keys) {
|
|
57
|
+
hasher.update(KEY);
|
|
58
|
+
hasher.update(key);
|
|
59
|
+
updateHasher(hasher, body[key], context);
|
|
71
60
|
}
|
|
72
|
-
valueHash = hashPlainObject(plainObjectInput, marker, context);
|
|
73
61
|
}
|
|
62
|
+
valueHash = hasher.digest();
|
|
74
63
|
} finally {
|
|
75
64
|
context.activeObjects.delete(input);
|
|
76
65
|
}
|
|
77
66
|
context.pendingHashes.set(input, valueHash);
|
|
78
67
|
return valueHash;
|
|
79
68
|
}
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return hasher.digest();
|
|
85
|
-
}
|
|
86
|
-
function hashUint8Array(input) {
|
|
87
|
-
const hasher = new murmur.MurmurHashStream();
|
|
88
|
-
hasher.update(UINT8ARRAY_MARKER);
|
|
89
|
-
hasher.update(input.byteLength);
|
|
90
|
-
for (let i = 0; i < input.byteLength; i++) {
|
|
91
|
-
hasher.writeByte(input[i]);
|
|
92
|
-
}
|
|
93
|
-
return hasher.digest();
|
|
94
|
-
}
|
|
95
|
-
function hashTemporal(input) {
|
|
96
|
-
const hasher = new murmur.MurmurHashStream();
|
|
97
|
-
hasher.update(TEMPORAL_MARKER);
|
|
98
|
-
hasher.update(input[Symbol.toStringTag]);
|
|
99
|
-
hasher.update(input.toString());
|
|
100
|
-
return hasher.digest();
|
|
101
|
-
}
|
|
102
|
-
function hashPlainObject(input, marker, context) {
|
|
103
|
-
const hasher = new murmur.MurmurHashStream();
|
|
104
|
-
hasher.update(marker);
|
|
105
|
-
const keys = Object.keys(input);
|
|
106
|
-
keys.sort(keySort);
|
|
107
|
-
for (const key of keys) {
|
|
108
|
-
hasher.update(KEY);
|
|
109
|
-
hasher.update(key);
|
|
110
|
-
updateHasher(hasher, input[key], context);
|
|
69
|
+
function objectParts(input) {
|
|
70
|
+
if (input instanceof Date) {
|
|
71
|
+
const time = input.getTime();
|
|
72
|
+
return [DATE_MARKER, [Number.isNaN(time) ? `invalid` : time]];
|
|
111
73
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
74
|
+
if (isBinaryValue(input))
|
|
75
|
+
return [
|
|
76
|
+
UINT8ARRAY_MARKER,
|
|
77
|
+
[String.fromCharCode.apply(null, input)]
|
|
78
|
+
];
|
|
79
|
+
if (temporal.isTemporal(input))
|
|
80
|
+
return [TEMPORAL_MARKER, [input[Symbol.toStringTag], input.toString()]];
|
|
81
|
+
if (input instanceof RegExp)
|
|
82
|
+
return [REGEXP_MARKER, [input.source, input.flags, input.lastIndex], input];
|
|
83
|
+
if (input instanceof Map) return [MAP_MARKER, [], [...input.entries()]];
|
|
84
|
+
if (input instanceof Set) return [SET_MARKER, [], [...input.values()]];
|
|
85
|
+
return input instanceof Array ? [ARRAY_MARKER, [input.length], input] : [OBJECT_MARKER, [], input];
|
|
119
86
|
}
|
|
120
87
|
function updateHasher(hasher, input, context) {
|
|
121
88
|
if (context && ++context.work > MAX_STRUCTURAL_HASH_WORK) {
|
|
@@ -145,43 +112,62 @@ function updateHasher(hasher, input, context) {
|
|
|
145
112
|
return;
|
|
146
113
|
case `function`:
|
|
147
114
|
hasher.update(cachedReferenceHash(input));
|
|
148
|
-
return;
|
|
149
|
-
default:
|
|
150
|
-
console.warn(
|
|
151
|
-
`Ignored input during hashing because it is of type ${typeof input} which is not supported`
|
|
152
|
-
);
|
|
153
115
|
}
|
|
154
116
|
}
|
|
155
117
|
function getCachedHash(input, context) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
context
|
|
161
|
-
|
|
162
|
-
work: 0,
|
|
163
|
-
pendingHashes: /* @__PURE__ */ new Map()
|
|
164
|
-
};
|
|
165
|
-
const result = hashObject(input, context);
|
|
166
|
-
for (const [object, valueHash2] of context.pendingHashes) {
|
|
167
|
-
hashCache.set(object, valueHash2);
|
|
118
|
+
const cached = hashCache.get(input) ?? context?.pendingHashes.get(input);
|
|
119
|
+
if (cached !== void 0) return cached;
|
|
120
|
+
if (isReferenceHashedObject(input)) return cachedReferenceHash(input);
|
|
121
|
+
if (context) {
|
|
122
|
+
if (context.activeObjects.has(input)) {
|
|
123
|
+
throw new TypeError(`Cannot hash cyclic structural values`);
|
|
168
124
|
}
|
|
169
|
-
return
|
|
125
|
+
return hashObject(input, context);
|
|
170
126
|
}
|
|
171
|
-
|
|
172
|
-
|
|
127
|
+
const root = {
|
|
128
|
+
activeObjects: /* @__PURE__ */ new Set(),
|
|
129
|
+
work: 0,
|
|
130
|
+
pendingHashes: /* @__PURE__ */ new Map()
|
|
131
|
+
};
|
|
132
|
+
const result = hashObject(input, root);
|
|
133
|
+
for (const [object, valueHash] of root.pendingHashes) {
|
|
134
|
+
hashCache.set(object, valueHash);
|
|
173
135
|
}
|
|
174
|
-
|
|
175
|
-
const valueHash = hashCache.get(input) ?? context.pendingHashes.get(input);
|
|
176
|
-
if (valueHash !== void 0) return valueHash;
|
|
177
|
-
return hashObject(input, context);
|
|
136
|
+
return result;
|
|
178
137
|
}
|
|
179
138
|
function isReferenceHashedObject(input) {
|
|
180
|
-
return input instanceof File || isBinaryValue(input) && input.byteLength > UINT8ARRAY_CONTENT_HASH_THRESHOLD;
|
|
139
|
+
return referenceValues.has(input) || typeof File !== `undefined` && input instanceof File || isBinaryValue(input) && input.byteLength > UINT8ARRAY_CONTENT_HASH_THRESHOLD;
|
|
181
140
|
}
|
|
182
141
|
function isBinaryValue(input) {
|
|
183
142
|
return typeof Buffer !== `undefined` && input instanceof Buffer || input instanceof Uint8Array;
|
|
184
143
|
}
|
|
144
|
+
function equalHashValues(left, right) {
|
|
145
|
+
const compared = /* @__PURE__ */ new Map();
|
|
146
|
+
const keys = (value) => Reflect.ownKeys(value).filter(
|
|
147
|
+
(key) => Object.prototype.propertyIsEnumerable.call(value, key)
|
|
148
|
+
);
|
|
149
|
+
function equal(a, b) {
|
|
150
|
+
if (a === b || Number.isNaN(a) && Number.isNaN(b)) return true;
|
|
151
|
+
if (a === null || b === null || typeof a !== `object` || typeof b !== `object` || isReferenceHashedObject(a) || isReferenceHashedObject(b))
|
|
152
|
+
return false;
|
|
153
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length !== b.length)
|
|
154
|
+
return false;
|
|
155
|
+
const peers = compared.get(a);
|
|
156
|
+
if (peers?.has(b)) return true;
|
|
157
|
+
const [aMarker, aHeader, aBody] = objectParts(a);
|
|
158
|
+
const [bMarker, bHeader, bBody] = objectParts(b);
|
|
159
|
+
if (aMarker !== bMarker || !aHeader.every((value, index) => value === bHeader[index]))
|
|
160
|
+
return false;
|
|
161
|
+
if (!aBody || !bBody) return true;
|
|
162
|
+
if (peers) peers.add(b);
|
|
163
|
+
else compared.set(a, /* @__PURE__ */ new Set([b]));
|
|
164
|
+
const aKeys = keys(aBody);
|
|
165
|
+
return aKeys.length === keys(bBody).length && aKeys.every(
|
|
166
|
+
(key) => Object.prototype.propertyIsEnumerable.call(bBody, key) && equal(aBody[key], bBody[key])
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return equal(left, right);
|
|
170
|
+
}
|
|
185
171
|
let nextRefId = 1;
|
|
186
172
|
function cachedReferenceHash(fn) {
|
|
187
173
|
let valueHash = hashCache.get(fn);
|
|
@@ -189,12 +175,14 @@ function cachedReferenceHash(fn) {
|
|
|
189
175
|
valueHash = nextRefId ^ FUNCTIONS;
|
|
190
176
|
nextRefId++;
|
|
191
177
|
hashCache.set(fn, valueHash);
|
|
178
|
+
referenceValues.add(fn);
|
|
192
179
|
}
|
|
193
180
|
return valueHash;
|
|
194
181
|
}
|
|
195
182
|
function keySort(a, b) {
|
|
196
183
|
return a.localeCompare(b);
|
|
197
184
|
}
|
|
185
|
+
exports.equalHashValues = equalHashValues;
|
|
198
186
|
exports.hash = hash;
|
|
199
187
|
exports.registerOpaqueHash = registerOpaqueHash;
|
|
200
188
|
//# sourceMappingURL=hash.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.cjs","sources":["../../../src/hashing/hash.ts"],"sourcesContent":["import { MurmurHashStream, getSymbolIdentity, 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()\nconst TEMPORAL_MARKER = randomHash()\n// Bound structural recursion and value visits. Shared acyclic subtrees are\n// cached; cycles are rejected rather than given context-dependent hashes.\nconst MAX_STRUCTURAL_HASH_WORK = 1_000_000\nconst MAX_STRUCTURAL_HASH_DEPTH = 768\n\nconst temporalTypes = new Set([\n `Temporal.Duration`,\n `Temporal.Instant`,\n `Temporal.PlainDate`,\n `Temporal.PlainDateTime`,\n `Temporal.PlainMonthDay`,\n `Temporal.PlainTime`,\n `Temporal.PlainYearMonth`,\n `Temporal.ZonedDateTime`,\n])\n\ninterface TemporalLike {\n [Symbol.toStringTag]: string\n toString: () => string\n}\n\nfunction isTemporal(input: object): input is TemporalLike {\n const tag = (input as Record<symbol, unknown>)[Symbol.toStringTag]\n return typeof tag === `string` && temporalTypes.has(tag)\n}\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\n/** @internal Register a mutable handle before it enters a structural value. */\nexport function registerOpaqueHash(value: object): void {\n cachedReferenceHash(value)\n}\n\ntype HashContext = {\n activeObjects: Set<object>\n work: number\n pendingHashes: Map<object, number>\n}\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, context: HashContext): number {\n if (context.activeObjects.size >= MAX_STRUCTURAL_HASH_DEPTH) {\n throw new RangeError(\n `Value is too complex to hash safely: structural depth`,\n )\n }\n\n context.activeObjects.add(input)\n\n let valueHash: number | undefined\n try {\n if (input instanceof Date) {\n valueHash = hashDate(input)\n } else if (isBinaryValue(input)) {\n valueHash = hashUint8Array(input)\n } else if (isTemporal(input)) {\n valueHash = hashTemporal(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, context)\n }\n } finally {\n context.activeObjects.delete(input)\n }\n\n context.pendingHashes.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 hashTemporal(input: TemporalLike): number {\n const hasher = new MurmurHashStream()\n hasher.update(TEMPORAL_MARKER)\n hasher.update(input[Symbol.toStringTag])\n hasher.update(input.toString())\n return hasher.digest()\n}\n\nfunction hashPlainObject(\n input: object,\n marker: number,\n context: HashContext,\n): 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], context)\n }\n const symbolKeys = Object.getOwnPropertySymbols(input)\n .filter((key) => Object.prototype.propertyIsEnumerable.call(input, key))\n .sort((left, right) => getSymbolIdentity(left) - getSymbolIdentity(right))\n for (const key of symbolKeys) {\n hasher.update(KEY)\n hasher.update(key)\n updateHasher(hasher, input[key as keyof typeof input], context)\n }\n\n return hasher.digest()\n}\n\nfunction updateHasher(\n hasher: Hasher,\n input: unknown,\n context?: HashContext,\n): void {\n if (context && ++context.work > MAX_STRUCTURAL_HASH_WORK) {\n throw new RangeError(`Value is too complex to hash safely: structural work`)\n }\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, context))\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, context?: HashContext): number {\n if (!context) {\n const cached = hashCache.get(input)\n if (cached !== undefined) return cached\n if (isReferenceHashedObject(input)) return cachedReferenceHash(input)\n\n // Only an uncached structural root needs graph traversal state. Commit its\n // cache entries after success so a failed traversal cannot poison retries.\n context = {\n activeObjects: new Set(),\n work: 0,\n pendingHashes: new Map(),\n }\n const result = hashObject(input, context)\n for (const [object, valueHash] of context.pendingHashes) {\n hashCache.set(object, valueHash)\n }\n return result\n }\n\n if (context.activeObjects.has(input)) {\n throw new TypeError(`Cannot hash cyclic structural values`)\n }\n\n // Opaque leaves cannot contain structural back-references. Resolve them\n // before entering structural recursion, even when they have user properties.\n if (isReferenceHashedObject(input)) return cachedReferenceHash(input)\n\n const valueHash = hashCache.get(input) ?? context.pendingHashes.get(input)\n if (valueHash !== undefined) return valueHash\n\n return hashObject(input, context)\n}\n\nfunction isReferenceHashedObject(input: object): boolean {\n return (\n input instanceof File ||\n (isBinaryValue(input) &&\n input.byteLength > UINT8ARRAY_CONTENT_HASH_THRESHOLD)\n )\n}\n\nfunction isBinaryValue(input: object): input is Uint8Array {\n return (\n (typeof Buffer !== `undefined` && input instanceof Buffer) ||\n input instanceof Uint8Array\n )\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","getSymbolIdentity","valueHash"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hash.cjs","sources":["../../../src/hashing/hash.ts"],"sourcesContent":["import { MurmurHashStream, getSymbolIdentity, randomHash } from './murmur.js'\nimport { isTemporal } from './temporal.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()\n// Identity is distinct even if two random hash values happen to collide.\nconst DATE_MARKER = { hash: randomHash() }\nconst REGEXP_MARKER = { hash: randomHash() }\nconst OBJECT_MARKER = { hash: randomHash() }\nconst ARRAY_MARKER = { hash: randomHash() }\nconst MAP_MARKER = { hash: randomHash() }\nconst SET_MARKER = { hash: randomHash() }\nconst UINT8ARRAY_MARKER = { hash: randomHash() }\nconst TEMPORAL_MARKER = { hash: randomHash() }\n// Bound structural recursion and value visits. Shared acyclic subtrees are\n// cached; cycles are rejected rather than given context-dependent hashes.\nconst MAX_STRUCTURAL_HASH_WORK = 1_000_000\nconst MAX_STRUCTURAL_HASH_DEPTH = 768\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>()\nconst referenceValues = new WeakSet<object>()\n\n/** @internal Register a mutable handle before it enters a structural value. */\nexport function registerOpaqueHash(value: object): void {\n cachedReferenceHash(value)\n}\n\ntype HashContext = {\n activeObjects: Set<object>\n work: number\n pendingHashes: Map<object, number>\n}\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, context: HashContext): number {\n if (context.activeObjects.size >= MAX_STRUCTURAL_HASH_DEPTH) {\n throw new RangeError(\n `Value is too complex to hash safely: structural depth`,\n )\n }\n\n context.activeObjects.add(input)\n\n let valueHash: number | undefined\n try {\n const [marker, header, body] = objectParts(input)\n const hasher = new MurmurHashStream()\n hasher.update(marker.hash)\n // Header values of a type with a body (array length, RegExp fields)\n // count toward the work cap; Date, binary, and Temporal headers do not.\n for (const value of header)\n if (body) updateHasher(hasher, value, context)\n else hasher.update(value as string | number)\n if (body) {\n // Sorted string keys, then enumerable symbol keys by identity.\n const keys: Array<string | symbol> = Object.keys(body).sort(keySort)\n keys.push(\n ...Object.getOwnPropertySymbols(body)\n .filter((key) =>\n Object.prototype.propertyIsEnumerable.call(body, key),\n )\n .sort(\n (left, right) => getSymbolIdentity(left) - getSymbolIdentity(right),\n ),\n )\n for (const key of keys) {\n hasher.update(KEY)\n hasher.update(key)\n updateHasher(hasher, body[key as keyof typeof body], context)\n }\n }\n valueHash = hasher.digest()\n } finally {\n context.activeObjects.delete(input)\n }\n\n context.pendingHashes.set(input, valueHash)\n return valueHash\n}\n\n/**\n * The one type dispatch shared by structural hashing and equality: a type\n * marker, primitive header values, and an optional object whose enumerable\n * own properties are compared structurally.\n */\ntype ObjectParts = [\n marker: Readonly<{ hash: number }>,\n // A RegExp `lastIndex` can hold any value; the other headers are strings\n // or numbers.\n header: ReadonlyArray<unknown>,\n body?: object,\n]\n\nfunction objectParts(input: object): ObjectParts {\n if (input instanceof Date) {\n const time = input.getTime()\n // Invalid dates are equal to each other; headers compare with `===`.\n return [DATE_MARKER, [Number.isNaN(time) ? `invalid` : time]]\n }\n if (isBinaryValue(input))\n return [\n UINT8ARRAY_MARKER,\n [String.fromCharCode.apply(null, input as unknown as Array<number>)],\n ]\n if (isTemporal(input))\n return [TEMPORAL_MARKER, [input[Symbol.toStringTag], input.toString()]]\n if (input instanceof RegExp)\n return [REGEXP_MARKER, [input.source, input.flags, input.lastIndex], input]\n if (input instanceof Map) return [MAP_MARKER, [], [...input.entries()]]\n if (input instanceof Set) return [SET_MARKER, [], [...input.values()]]\n return input instanceof Array\n ? [ARRAY_MARKER, [input.length], input]\n : [OBJECT_MARKER, [], input]\n}\n\nfunction updateHasher(\n hasher: Hasher,\n input: unknown,\n context?: HashContext,\n): void {\n if (context && ++context.work > MAX_STRUCTURAL_HASH_WORK) {\n throw new RangeError(`Value is too complex to hash safely: structural work`)\n }\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, context))\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 }\n}\n\nfunction getCachedHash(input: object, context?: HashContext): number {\n // Active objects are neither cached nor pending, so the cycle check below\n // still sees every back-reference.\n const cached = hashCache.get(input) ?? context?.pendingHashes.get(input)\n if (cached !== undefined) return cached\n\n // Opaque leaves cannot contain structural back-references. Resolve them\n // before entering structural recursion, even when they have user properties.\n if (isReferenceHashedObject(input)) return cachedReferenceHash(input)\n\n if (context) {\n if (context.activeObjects.has(input)) {\n throw new TypeError(`Cannot hash cyclic structural values`)\n }\n return hashObject(input, context)\n }\n\n // Only an uncached structural root needs graph traversal state. Commit its\n // cache entries after success so a failed traversal cannot poison retries.\n const root: HashContext = {\n activeObjects: new Set(),\n work: 0,\n pendingHashes: new Map(),\n }\n const result = hashObject(input, root)\n for (const [object, valueHash] of root.pendingHashes) {\n hashCache.set(object, valueHash)\n }\n return result\n}\n\nfunction isReferenceHashedObject(input: object): boolean {\n return (\n referenceValues.has(input) ||\n (typeof File !== `undefined` && input instanceof File) ||\n (isBinaryValue(input) &&\n input.byteLength > UINT8ARRAY_CONTENT_HASH_THRESHOLD)\n )\n}\n\n// Buffer can come from another realm than Uint8Array (for example under jsdom).\nfunction isBinaryValue(input: object): input is Uint8Array {\n return (\n (typeof Buffer !== `undefined` && input instanceof Buffer) ||\n input instanceof Uint8Array\n )\n}\n\n/** @internal Compare immutable structural values without computing a digest.\n * Pair memoization also permits cyclic values that structural hashing rejects.\n * Reference-valued leaves remain opaque, including registered mutable handles.\n */\nexport function equalHashValues(left: unknown, right: unknown): boolean {\n const compared = new Map<object, Set<object>>()\n const keys = (value: object) =>\n Reflect.ownKeys(value).filter((key) =>\n Object.prototype.propertyIsEnumerable.call(value, key),\n )\n function equal(a: unknown, b: unknown): boolean {\n if (a === b || (Number.isNaN(a) && Number.isNaN(b))) return true\n if (\n a === null ||\n b === null ||\n typeof a !== `object` ||\n typeof b !== `object` ||\n isReferenceHashedObject(a) ||\n isReferenceHashedObject(b)\n )\n return false\n // Arrays from another realm take the object marker but keep this check.\n if (Array.isArray(a) && Array.isArray(b) && a.length !== b.length)\n return false\n\n // Revisited pairs close cycles and avoid expanding shared subtrees. Check\n // before objectParts copies Map or Set entries; record only pairs with a\n // body, after their markers and headers match.\n const peers = compared.get(a)\n if (peers?.has(b)) return true\n const [aMarker, aHeader, aBody] = objectParts(a)\n const [bMarker, bHeader, bBody] = objectParts(b)\n if (\n aMarker !== bMarker ||\n !aHeader.every((value, index) => value === bHeader[index])\n )\n return false\n if (!aBody || !bBody) return true\n if (peers) peers.add(b)\n else compared.set(a, new Set([b]))\n const aKeys = keys(aBody)\n return (\n aKeys.length === keys(bBody).length &&\n aKeys.every(\n (key) =>\n Object.prototype.propertyIsEnumerable.call(bBody, key) &&\n equal(aBody[key as keyof object], bBody[key as keyof object]),\n )\n )\n }\n return equal(left, right)\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 referenceValues.add(fn)\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","getSymbolIdentity","isTemporal"],"mappings":"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"}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
/** @internal Register a mutable handle before it enters a structural value. */
|
|
2
2
|
export declare function registerOpaqueHash(value: object): void;
|
|
3
3
|
export declare function hash(input: any): number;
|
|
4
|
+
/** @internal Compare immutable structural values without computing a digest.
|
|
5
|
+
* Pair memoization also permits cyclic values that structural hashing rejects.
|
|
6
|
+
* Reference-valued leaves remain opaque, including registered mutable handles.
|
|
7
|
+
*/
|
|
8
|
+
export declare function equalHashValues(left: unknown, right: unknown): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const temporalTypes = /* @__PURE__ */ new Set([
|
|
4
|
+
`Temporal.Duration`,
|
|
5
|
+
`Temporal.Instant`,
|
|
6
|
+
`Temporal.PlainDate`,
|
|
7
|
+
`Temporal.PlainDateTime`,
|
|
8
|
+
`Temporal.PlainMonthDay`,
|
|
9
|
+
`Temporal.PlainTime`,
|
|
10
|
+
`Temporal.PlainYearMonth`,
|
|
11
|
+
`Temporal.ZonedDateTime`
|
|
12
|
+
]);
|
|
13
|
+
function isTemporal(input) {
|
|
14
|
+
const tag = input[Symbol.toStringTag];
|
|
15
|
+
return typeof tag === `string` && temporalTypes.has(tag) && Object.getPrototypeOf(input)?.[Symbol.toStringTag] === tag;
|
|
16
|
+
}
|
|
17
|
+
exports.isTemporal = isTemporal;
|
|
18
|
+
//# sourceMappingURL=temporal.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporal.cjs","sources":["../../../src/hashing/temporal.ts"],"sourcesContent":["const temporalTypes = new Set([\n `Temporal.Duration`,\n `Temporal.Instant`,\n `Temporal.PlainDate`,\n `Temporal.PlainDateTime`,\n `Temporal.PlainMonthDay`,\n `Temporal.PlainTime`,\n `Temporal.PlainYearMonth`,\n `Temporal.ZonedDateTime`,\n])\n\nexport interface TemporalLike {\n [Symbol.toStringTag]: string\n toString: () => string\n}\n\nexport function isTemporal(input: object): input is TemporalLike {\n const tag = (input as Record<symbol, unknown>)[Symbol.toStringTag]\n // An own tag on a plain object does not make it a Temporal value.\n return (\n typeof tag === `string` &&\n temporalTypes.has(tag) &&\n Object.getPrototypeOf(input)?.[Symbol.toStringTag] === tag\n )\n}\n"],"names":[],"mappings":";;AAAA,MAAM,oCAAoB,IAAI;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,SAAS,WAAW,OAAsC;AAC/D,QAAM,MAAO,MAAkC,OAAO,WAAW;AAEjE,SACE,OAAO,QAAQ,YACf,cAAc,IAAI,GAAG,KACrB,OAAO,eAAe,KAAK,IAAI,OAAO,WAAW,MAAM;AAE3D;;"}
|
package/dist/cjs/multiset.cjs
CHANGED
|
@@ -51,102 +51,42 @@ class MultiSet {
|
|
|
51
51
|
* Produce as output a collection that is logically equivalent to the input
|
|
52
52
|
* but which combines identical instances of the same record into one
|
|
53
53
|
* (record, multiplicity) pair.
|
|
54
|
-
*/
|
|
55
|
-
consolidate() {
|
|
56
|
-
if (this.#inner.length > 0) {
|
|
57
|
-
const firstItem = this.#inner[0]?.[0];
|
|
58
|
-
if (Array.isArray(firstItem) && firstItem.length === 2) {
|
|
59
|
-
return this.#consolidateKeyed();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return this.#consolidateUnkeyed();
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Private method for consolidating keyed multisets where keys are strings/numbers
|
|
66
|
-
* and values are compared by reference equality.
|
|
67
54
|
*
|
|
68
|
-
*
|
|
69
|
-
* by using WeakMap for object reference tracking and avoiding expensive serialization.
|
|
55
|
+
* Record identity depends on the shape of the whole multiset:
|
|
70
56
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
for (const [data, multiplicity] of this.#inner) {
|
|
85
|
-
if (!Array.isArray(data) || data.length !== 2) {
|
|
86
|
-
return this.#consolidateUnkeyed();
|
|
87
|
-
}
|
|
88
|
-
const [key, value] = data;
|
|
89
|
-
if (typeof key !== `string` && typeof key !== `number`) {
|
|
90
|
-
return this.#consolidateUnkeyed();
|
|
91
|
-
}
|
|
92
|
-
let valueId;
|
|
93
|
-
if (Array.isArray(value) && value.length === 2) {
|
|
94
|
-
valueId = getTupleId(value);
|
|
95
|
-
} else {
|
|
96
|
-
valueId = utils.globalObjectIdGenerator.getStringId(value);
|
|
97
|
-
}
|
|
98
|
-
const compositeKey = key + `|` + valueId;
|
|
99
|
-
consolidated.set(
|
|
100
|
-
compositeKey,
|
|
101
|
-
(consolidated.get(compositeKey) || 0) + multiplicity
|
|
102
|
-
);
|
|
103
|
-
if (!values.has(compositeKey)) {
|
|
104
|
-
values.set(compositeKey, data);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const result = [];
|
|
108
|
-
for (const [compositeKey, multiplicity] of consolidated) {
|
|
109
|
-
if (multiplicity !== 0) {
|
|
110
|
-
result.push([values.get(compositeKey), multiplicity]);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return new MultiSet(result);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Private method for consolidating unkeyed multisets using the original approach.
|
|
57
|
+
* - Keyed: every record is a `[key, value]` pair with a string or number
|
|
58
|
+
* key. Keys and primitive values compare by value, and object values by
|
|
59
|
+
* reference. A value that is an array of length 2 is a join tuple, and
|
|
60
|
+
* its two elements compare by the same rule. The keyed identity text
|
|
61
|
+
* has known collisions: https://github.com/TanStack/db/issues/1948
|
|
62
|
+
* - Unkeyed, one primitive type: every record is a string, or every record
|
|
63
|
+
* is a number. Records compare as `Map` keys do, and a `-0` record is
|
|
64
|
+
* returned as `0`.
|
|
65
|
+
* - Otherwise, records compare by structure (`hash`).
|
|
66
|
+
*
|
|
67
|
+
* Each identity keeps its first record in input order with the summed
|
|
68
|
+
* multiplicity. Identities whose sum is zero are dropped. The input is not
|
|
69
|
+
* changed.
|
|
117
70
|
*/
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
hasNumber = true;
|
|
129
|
-
} else {
|
|
130
|
-
hasOther = true;
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const requireJson = hasOther || hasString && hasNumber;
|
|
135
|
-
for (const [data, multiplicity] of this.#inner) {
|
|
136
|
-
const key = requireJson ? hash.hash(data) : data;
|
|
137
|
-
if (requireJson && !values.has(key)) {
|
|
138
|
-
values.set(key, data);
|
|
139
|
-
}
|
|
140
|
-
consolidated.update(key, (count) => count + multiplicity);
|
|
141
|
-
}
|
|
142
|
-
const result = [];
|
|
143
|
-
for (const [key, multiplicity] of consolidated.entries()) {
|
|
144
|
-
if (multiplicity !== 0) {
|
|
145
|
-
const parsedKey = requireJson ? values.get(key) : key;
|
|
146
|
-
result.push([parsedKey, multiplicity]);
|
|
147
|
-
}
|
|
71
|
+
consolidate() {
|
|
72
|
+
const inner = this.#inner;
|
|
73
|
+
if (inner.every(
|
|
74
|
+
([data]) => Array.isArray(data) && data.length === 2 && (typeof data[0] === `string` || typeof data[0] === `number`)
|
|
75
|
+
)) {
|
|
76
|
+
return consolidateBy(inner, (data) => {
|
|
77
|
+
const [key, value] = data;
|
|
78
|
+
const valueId = Array.isArray(value) && value.length === 2 ? `${utils.getStringId(value[0])}|${utils.getStringId(value[1])}` : utils.getStringId(value);
|
|
79
|
+
return key + `|` + valueId;
|
|
80
|
+
});
|
|
148
81
|
}
|
|
149
|
-
|
|
82
|
+
let primitiveType;
|
|
83
|
+
const requireHash = inner.some(([data]) => {
|
|
84
|
+
const type = typeof data;
|
|
85
|
+
if (type !== `string` && type !== `number`) return true;
|
|
86
|
+
primitiveType ??= type;
|
|
87
|
+
return type !== primitiveType;
|
|
88
|
+
});
|
|
89
|
+
return requireHash ? consolidateBy(inner, hash.hash) : consolidateBy(inner, (data) => data, true);
|
|
150
90
|
}
|
|
151
91
|
extend(other) {
|
|
152
92
|
const otherArray = other instanceof MultiSet ? other.getInner() : other;
|
|
@@ -161,5 +101,20 @@ class MultiSet {
|
|
|
161
101
|
return this.#inner;
|
|
162
102
|
}
|
|
163
103
|
}
|
|
104
|
+
function consolidateBy(inner, identityOf, keepIdentity = false) {
|
|
105
|
+
const consolidated = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const [data, multiplicity] of inner) {
|
|
107
|
+
const identity = identityOf(data);
|
|
108
|
+
const entry = consolidated.get(identity);
|
|
109
|
+
if (entry) entry[1] += multiplicity;
|
|
110
|
+
else consolidated.set(identity, [data, multiplicity]);
|
|
111
|
+
}
|
|
112
|
+
const result = [];
|
|
113
|
+
for (const [identity, [data, multiplicity]] of consolidated) {
|
|
114
|
+
if (multiplicity !== 0)
|
|
115
|
+
result.push([keepIdentity ? identity : data, multiplicity]);
|
|
116
|
+
}
|
|
117
|
+
return new MultiSet(result);
|
|
118
|
+
}
|
|
164
119
|
exports.MultiSet = MultiSet;
|
|
165
120
|
//# sourceMappingURL=multiset.cjs.map
|