@tanstack/db-ivm 0.1.25 → 0.1.26

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.
Files changed (60) hide show
  1. package/dist/cjs/d2.d.cts +3 -3
  2. package/dist/cjs/graph.d.cts +2 -2
  3. package/dist/cjs/hashing/index.d.cts +2 -2
  4. package/dist/cjs/index.cjs +42 -42
  5. package/dist/cjs/index.d.cts +6 -6
  6. package/dist/cjs/indexes.d.cts +38 -8
  7. package/dist/cjs/multiset.d.cts +8 -2
  8. package/dist/cjs/operators/concat.d.cts +2 -2
  9. package/dist/cjs/operators/consolidate.d.cts +2 -2
  10. package/dist/cjs/operators/count.cjs.map +1 -1
  11. package/dist/cjs/operators/count.d.cts +11 -5
  12. package/dist/cjs/operators/debug.d.cts +2 -2
  13. package/dist/cjs/operators/distinct.d.cts +2 -2
  14. package/dist/cjs/operators/filter.d.cts +3 -3
  15. package/dist/cjs/operators/filterBy.cjs.map +1 -1
  16. package/dist/cjs/operators/filterBy.d.cts +2 -2
  17. package/dist/cjs/operators/groupBy.cjs.map +1 -1
  18. package/dist/cjs/operators/groupBy.d.cts +5 -2
  19. package/dist/cjs/operators/groupedOrderBy.d.cts +8 -2
  20. package/dist/cjs/operators/groupedTopKWithFractionalIndex.d.cts +34 -7
  21. package/dist/cjs/operators/index.d.cts +21 -21
  22. package/dist/cjs/operators/join.cjs.map +1 -1
  23. package/dist/cjs/operators/join.d.cts +49 -10
  24. package/dist/cjs/operators/keying.d.cts +5 -2
  25. package/dist/cjs/operators/map.d.cts +3 -3
  26. package/dist/cjs/operators/negate.d.cts +3 -3
  27. package/dist/cjs/operators/orderBy.d.cts +20 -5
  28. package/dist/cjs/operators/output.d.cts +3 -3
  29. package/dist/cjs/operators/pipe.d.cts +1 -1
  30. package/dist/cjs/operators/reduce.cjs.map +1 -1
  31. package/dist/cjs/operators/reduce.d.cts +29 -5
  32. package/dist/cjs/operators/tap.d.cts +3 -3
  33. package/dist/cjs/operators/topK.cjs.map +1 -1
  34. package/dist/cjs/operators/topK.d.cts +5 -2
  35. package/dist/cjs/operators/topKArray.d.cts +11 -2
  36. package/dist/cjs/operators/topKState.d.cts +44 -8
  37. package/dist/cjs/operators/topKWithFractionalIndex.d.cts +41 -8
  38. package/dist/cjs/operators/topKWithFractionalIndexBTree.d.cts +20 -5
  39. package/dist/cjs/types.d.cts +6 -3
  40. package/dist/cjs/utils.d.cts +4 -1
  41. package/dist/esm/index.js +15 -15
  42. package/dist/esm/operators/count.d.ts +1 -1
  43. package/dist/esm/operators/count.js.map +1 -1
  44. package/dist/esm/operators/filterBy.d.ts +1 -1
  45. package/dist/esm/operators/filterBy.js.map +1 -1
  46. package/dist/esm/operators/groupBy.js.map +1 -1
  47. package/dist/esm/operators/join.d.ts +6 -6
  48. package/dist/esm/operators/join.js.map +1 -1
  49. package/dist/esm/operators/reduce.d.ts +1 -1
  50. package/dist/esm/operators/reduce.js.map +1 -1
  51. package/dist/esm/operators/topK.d.ts +2 -2
  52. package/dist/esm/operators/topK.js.map +1 -1
  53. package/dist/esm/operators/topKArray.js +1 -1
  54. package/package.json +2 -2
  55. package/src/operators/count.ts +2 -2
  56. package/src/operators/filterBy.ts +1 -1
  57. package/src/operators/groupBy.ts +1 -2
  58. package/src/operators/join.ts +6 -6
  59. package/src/operators/reduce.ts +2 -2
  60. package/src/operators/topK.ts +4 -4
@@ -1,6 +1,6 @@
1
- import { TopKWithFractionalIndexOperator, TopKWithFractionalIndexOptions } from './topKWithFractionalIndex.js';
2
- import { IndexedValue, TopK } from './topKArray.js';
3
- import { PipedOperator } from '../types.js';
1
+ import { TopKWithFractionalIndexOperator, TopKWithFractionalIndexOptions } from "./topKWithFractionalIndex.cjs";
2
+ import { IndexedValue, TopK } from "./topKArray.cjs";
3
+ import { PipedOperator } from "../types.cjs";
4
4
  export declare function loadBTree(): Promise<void>;
5
5
  /**
6
6
  * Operator for fractional indexed topK operations
@@ -8,7 +8,16 @@ export declare function loadBTree(): Promise<void>;
8
8
  * and only updates indices when elements move position
9
9
  */
10
10
  export declare class TopKWithFractionalIndexBTreeOperator<K extends string | number, T> extends TopKWithFractionalIndexOperator<K, T> {
11
- protected createTopK(offset: number, limit: number, comparator: (a: [K, T], b: [K, T]) => number): TopK<[K, T]>;
11
+ protected createTopK(offset: number, limit: number, comparator: (a: [
12
+ K,
13
+ T
14
+ ], b: [
15
+ K,
16
+ T
17
+ ]) => number): TopK<[
18
+ K,
19
+ T
20
+ ]>;
12
21
  }
13
22
  /**
14
23
  * Limits the number of results based on a comparator, with optional offset.
@@ -25,4 +34,10 @@ export declare class TopKWithFractionalIndexBTreeOperator<K extends string | num
25
34
  * @param options - An optional object containing limit and offset properties
26
35
  * @returns A piped operator that orders the elements and limits the number of results
27
36
  */
28
- export declare function topKWithFractionalIndexBTree<KType extends string | number, T>(comparator: (a: T, b: T) => number, options?: TopKWithFractionalIndexOptions): PipedOperator<[KType, T], [KType, IndexedValue<T>]>;
37
+ export declare function topKWithFractionalIndexBTree<KType extends string | number, T>(comparator: (a: T, b: T) => number, options?: TopKWithFractionalIndexOptions): PipedOperator<[
38
+ KType,
39
+ T
40
+ ], [
41
+ KType,
42
+ IndexedValue<T>
43
+ ]>;
@@ -1,6 +1,9 @@
1
- import { MultiSet, MultiSetArray } from './multiset.js';
2
- import { BinaryOperator, DifferenceStreamReader, DifferenceStreamWriter, UnaryOperator } from './graph.js';
3
- export type KeyValue<K, V> = [K, V];
1
+ import { MultiSet, MultiSetArray } from "./multiset.cjs";
2
+ import { BinaryOperator, DifferenceStreamReader, DifferenceStreamWriter, UnaryOperator } from "./graph.cjs";
3
+ export type KeyValue<K, V> = [
4
+ K,
5
+ V
6
+ ];
4
7
  export interface IOperator<_T> {
5
8
  run: () => void;
6
9
  hasPendingWork: () => boolean;
@@ -12,7 +12,10 @@ export declare function binarySearch<T>(array: Array<T>, value: T, comparator: (
12
12
  export declare function getStringId(value: unknown, referenceIds: Map<object | symbol, number>): string;
13
13
  export declare function concatIterable<T>(...iterables: Array<Iterable<T>>): Iterable<T>;
14
14
  export declare function mapIterable<T, U>(it: Iterable<T>, fn: (t: T) => U): Iterable<U>;
15
- export type HRange = [number, number];
15
+ export type HRange = [
16
+ number,
17
+ number
18
+ ];
16
19
  /**
17
20
  * Computes the difference between two half-open ranges.
18
21
  * @param a - The first half-open range
package/dist/esm/index.js CHANGED
@@ -2,27 +2,27 @@ import { D2, RootStreamBuilder, StreamBuilder } from "./d2.js";
2
2
  import { MultiSet } from "./multiset.js";
3
3
  import { compareKeys, serializeValue } from "./utils.js";
4
4
  import { registerOpaqueHash } from "./hashing/hash.js";
5
- import { groupBy, groupByOperators } from "./operators/groupBy.js";
6
- import { pipe } from "./operators/pipe.js";
7
- import { MapOperator, map } from "./operators/map.js";
8
- import { TapOperator, tap } from "./operators/tap.js";
9
- import { FilterOperator, filter } from "./operators/filter.js";
10
- import { NegateOperator, negate } from "./operators/negate.js";
11
5
  import { ConcatOperator, concat } from "./operators/concat.js";
12
- import { DebugOperator, debug } from "./operators/debug.js";
13
- import { OutputOperator, output } from "./operators/output.js";
14
6
  import { ConsolidateOperator, consolidate } from "./operators/consolidate.js";
15
- import { JoinOperator, antiJoin, fullJoin, innerJoin, join, leftJoin, rightJoin } from "./operators/join.js";
16
- import { ReduceOperator, reduce } from "./operators/reduce.js";
17
7
  import { CountOperator, count } from "./operators/count.js";
8
+ import { DebugOperator, debug } from "./operators/debug.js";
18
9
  import { DistinctOperator, distinct } from "./operators/distinct.js";
19
- import { keyBy, rekey, unkey } from "./operators/keying.js";
20
- import { topK, topKWithIndex } from "./operators/topK.js";
21
- import { TopKWithFractionalIndexOperator, topKWithFractionalIndex } from "./operators/topKWithFractionalIndex.js";
10
+ import { FilterOperator, filter } from "./operators/filter.js";
22
11
  import { GroupedTopKWithFractionalIndexOperator, groupedTopKWithFractionalIndex } from "./operators/groupedTopKWithFractionalIndex.js";
23
- import { orderBy, orderByWithFractionalIndex, orderByWithFractionalIndexBase, orderByWithIndex } from "./operators/orderBy.js";
24
- import { groupedOrderByWithFractionalIndex } from "./operators/groupedOrderBy.js";
12
+ import { JoinOperator, antiJoin, fullJoin, innerJoin, join, leftJoin, rightJoin } from "./operators/join.js";
13
+ import { MapOperator, map } from "./operators/map.js";
14
+ import { NegateOperator, negate } from "./operators/negate.js";
15
+ import { OutputOperator, output } from "./operators/output.js";
16
+ import { ReduceOperator, reduce } from "./operators/reduce.js";
17
+ import { TapOperator, tap } from "./operators/tap.js";
18
+ import { TopKWithFractionalIndexOperator, topKWithFractionalIndex } from "./operators/topKWithFractionalIndex.js";
25
19
  import { filterBy } from "./operators/filterBy.js";
20
+ import { groupBy, groupByOperators } from "./operators/groupBy.js";
21
+ import { groupedOrderByWithFractionalIndex } from "./operators/groupedOrderBy.js";
22
+ import { keyBy, rekey, unkey } from "./operators/keying.js";
23
+ import { orderBy, orderByWithFractionalIndex, orderByWithFractionalIndexBase, orderByWithIndex } from "./operators/orderBy.js";
24
+ import { pipe } from "./operators/pipe.js";
25
+ import { topK, topKWithIndex } from "./operators/topK.js";
26
26
  export {
27
27
  ConcatOperator,
28
28
  ConsolidateOperator,
@@ -10,4 +10,4 @@ export declare class CountOperator<K, V> extends ReduceOperator<K, V, number> {
10
10
  /**
11
11
  * Counts the number of elements by key (version-free)
12
12
  */
13
- export declare function count<KType extends T extends KeyValue<infer K, infer _V> ? K : never, VType extends T extends KeyValue<KType, infer V> ? V : never, T>(): (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<KType, number>>;
13
+ export declare function count<KType extends (T extends KeyValue<infer K, infer _V> ? K : never), VType extends (T extends KeyValue<KType, infer V> ? V : never), T>(): (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<KType, number>>;
@@ -1 +1 @@
1
- {"version":3,"file":"count.js","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":[],"mappings":";;;AASO,MAAM,sBAA4B,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,IAAI;AAAA,MACjB,OAAO;AAAA,MACP,IAAI,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
+ {"version":3,"file":"count.js","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":[],"mappings":";;;AASO,MAAM,sBAA4B,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,IAAI;AAAA,MACjB,OAAO;AAAA,MACP,IAAI,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;"}
@@ -6,4 +6,4 @@ import { IStreamBuilder, KeyValue, PipedOperator } from '../types.js';
6
6
  *
7
7
  * @param other - The other stream to filter by, which must have the same key type as the input stream
8
8
  */
9
- export declare function filterBy<K, V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never, T>(other: IStreamBuilder<KeyValue<K, unknown>>): PipedOperator<T, KeyValue<K, V1>>;
9
+ export declare function filterBy<K, V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never), T>(other: IStreamBuilder<KeyValue<K, unknown>>): PipedOperator<T, KeyValue<K, V1>>;
@@ -1 +1 @@
1
- {"version":3,"file":"filterBy.js","sources":["../../../src/operators/filterBy.ts"],"sourcesContent":["import { map } from './map.js'\nimport { innerJoin } from './join.js'\nimport { consolidate } from './consolidate.js'\nimport type { IStreamBuilder, KeyValue, PipedOperator } from '../types.js'\n\n/**\n * Filters the elements of a keyed stream, by keys of another stream.\n * This allows you to build pipelies where you have multiple outputs that are related,\n * such as a streams of issues and comments for a project.\n *\n * @param other - The other stream to filter by, which must have the same key type as the input stream\n */\nexport function filterBy<\n K,\n V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, unknown>>,\n): PipedOperator<T, KeyValue<K, V1>> {\n return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<K, V1>> => {\n const otherKeys = other.pipe(\n map(([key, _]) => [key, null] as KeyValue<K, null>),\n )\n return stream.pipe(\n innerJoin(otherKeys),\n map(([key, [value, _]]) => [key, value] as KeyValue<K, V1>),\n consolidate(),\n )\n }\n}\n"],"names":[],"mappings":";;;AAYO,SAAS,SAKd,OACmC;AACnC,SAAO,CAAC,WAA+D;AACrE,UAAM,YAAY,MAAM;AAAA,MACtB,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,CAAsB;AAAA,IAAA;AAEpD,WAAO,OAAO;AAAA,MACZ,UAAU,SAAS;AAAA,MACnB,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,CAAoB;AAAA,MAC1D,YAAA;AAAA,IAAY;AAAA,EAEhB;AACF;"}
1
+ {"version":3,"file":"filterBy.js","sources":["../../../src/operators/filterBy.ts"],"sourcesContent":["import { map } from './map.js'\nimport { innerJoin } from './join.js'\nimport { consolidate } from './consolidate.js'\nimport type { IStreamBuilder, KeyValue, PipedOperator } from '../types.js'\n\n/**\n * Filters the elements of a keyed stream, by keys of another stream.\n * This allows you to build pipelies where you have multiple outputs that are related,\n * such as a streams of issues and comments for a project.\n *\n * @param other - The other stream to filter by, which must have the same key type as the input stream\n */\nexport function filterBy<\n K,\n V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),\n T,\n>(\n other: IStreamBuilder<KeyValue<K, unknown>>,\n): PipedOperator<T, KeyValue<K, V1>> {\n return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<K, V1>> => {\n const otherKeys = other.pipe(\n map(([key, _]) => [key, null] as KeyValue<K, null>),\n )\n return stream.pipe(\n innerJoin(otherKeys),\n map(([key, [value, _]]) => [key, value] as KeyValue<K, V1>),\n consolidate(),\n )\n }\n}\n"],"names":[],"mappings":";;;AAYO,SAAS,SAKd,OACmC;AACnC,SAAO,CAAC,WAA+D;AACrE,UAAM,YAAY,MAAM;AAAA,MACtB,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,IAAI,CAAsB;AAAA,IAAA;AAEpD,WAAO,OAAO;AAAA,MACZ,UAAU,SAAS;AAAA,MACnB,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,KAAK,CAAoB;AAAA,MAC1D,YAAA;AAAA,IAAY;AAAA,EAEhB;AACF;"}
@@ -1 +1 @@
1
- {"version":3,"file":"groupBy.js","sources":["../../../src/operators/groupBy.ts"],"sourcesContent":["import { serializeValue } from '../utils.js'\nimport { map } from './map.js'\nimport { reduce } from './reduce.js'\nimport type { IStreamBuilder, KeyValue } from '../types.js'\n\ntype GroupKey = Record<string, unknown>\n\ntype BasicAggregateFunction<T, R, V = unknown> = {\n preMap: (data: T) => V\n reduce: (values: Array<[V, number]>) => V\n postMap?: (result: V) => R\n}\n\ntype PipedAggregateFunction<T, R> = {\n pipe: (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<string, R>>\n}\n\ntype AggregateFunction<T, R, V = unknown> =\n | BasicAggregateFunction<T, R, V>\n | PipedAggregateFunction<T, R>\n\ntype ExtractAggregateReturnType<T, A> =\n A extends AggregateFunction<T, infer R, any> ? R : never\n\ntype AggregatesReturnType<T, A> = {\n [K in keyof A]: ExtractAggregateReturnType<T, A[K]>\n}\n\nfunction isPipedAggregateFunction<T, R>(\n aggregate: AggregateFunction<T, R>,\n): aggregate is PipedAggregateFunction<T, R> {\n return `pipe` in aggregate\n}\n\n/**\n * Groups data by key and applies multiple aggregate operations\n * @param keyExtractor Function to extract grouping key from data\n * @param aggregates Object mapping aggregate names to aggregate functions\n */\nexport function groupBy<\n T,\n K extends GroupKey,\n A extends Record<string, AggregateFunction<T, any, any>>,\n>(keyExtractor: (data: T) => K, aggregates: A = {} as A) {\n type ResultType = K & AggregatesReturnType<T, A>\n\n const basicAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(\n ([_, aggregate]) => !isPipedAggregateFunction(aggregate),\n ),\n ) as Record<string, BasicAggregateFunction<T, any, any>>\n\n // @ts-expect-error - TODO: we don't use this yet, but we will\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const pipedAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(([_, aggregate]) =>\n isPipedAggregateFunction(aggregate),\n ),\n ) as Record<string, PipedAggregateFunction<T, any>>\n\n return (\n stream: IStreamBuilder<T>,\n ): IStreamBuilder<KeyValue<string, ResultType>> => {\n // Special key to store the original key object\n const KEY_SENTINEL = Symbol(`original_group_key`)\n\n // First map to extract keys and pre-aggregate values\n const withKeysAndValues = stream.pipe(\n map((data) => {\n const key = keyExtractor(data)\n const keyString = serializeValue(key)\n\n // Create values object with pre-aggregated values\n const values: Record<string | symbol, unknown> = {}\n\n // Store the original key object\n values[KEY_SENTINEL] = key\n\n // Add pre-aggregated values\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n values[name] = aggregate.preMap(data)\n }\n\n return [keyString, values] as KeyValue<\n string,\n Record<string | symbol, unknown>\n >\n }),\n )\n\n // Then reduce to compute aggregates\n const reduced = withKeysAndValues.pipe(\n reduce((values) => {\n // Calculate total multiplicity to check if the group should exist\n let totalMultiplicity = 0\n for (const [_, multiplicity] of values) {\n totalMultiplicity += multiplicity\n }\n\n // If total multiplicity is 0 or negative, the group should be removed completely\n if (totalMultiplicity <= 0) {\n return []\n }\n\n const result: Record<PropertyKey, unknown> = {}\n\n // Get the original key from first value in group\n const originalKey = values[0]?.[0]?.[KEY_SENTINEL]\n result[KEY_SENTINEL] = originalKey\n\n // Apply each aggregate function\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n const preValues = values.map(\n ([v, m]) => [v[name], m] as [any, number],\n )\n result[name] = aggregate.reduce(preValues)\n }\n\n return [[result, 1]]\n }),\n )\n\n // Finally map to extract the key and include all values\n return reduced.pipe(\n map(([keyString, values]) => {\n // Extract the original key\n const key = values[KEY_SENTINEL] as K\n\n // Create intermediate result with key values and aggregate results\n const result: Record<string, unknown> = {}\n\n // Add key properties to result\n Object.assign(result, key)\n\n // Apply postMap if provided\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n if (aggregate.postMap) {\n result[name] = aggregate.postMap(values[name])\n } else {\n result[name] = values[name]\n }\n }\n\n // Return with the string key instead of the object\n return [keyString, result] as KeyValue<string, ResultType>\n }),\n )\n }\n}\n\n/**\n * Creates a sum aggregate function\n */\nexport function sum<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number,\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let total = 0\n for (const [value, multiplicity] of values) {\n total += value * multiplicity\n }\n return total\n },\n }\n}\n\n/**\n * Creates a count aggregate function\n */\nexport function count<T>(\n valueExtractor: (value: T) => any = (v) => v,\n): AggregateFunction<T, number, number> {\n return {\n // Count only not-null values (the `== null` comparison gives true for both null and undefined)\n preMap: (data: T) => (valueExtractor(data) == null ? 0 : 1),\n reduce: (values: Array<[number, number]>) => {\n let totalCount = 0\n for (const [nullMultiplier, multiplicity] of values) {\n totalCount += nullMultiplier * multiplicity\n }\n return totalCount\n },\n }\n}\n\n/**\n * Creates an average aggregate function\n */\nexport function avg<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number,\n): AggregateFunction<T, number, { sum: number; count: number }> {\n return {\n preMap: (data: T) => ({\n sum: valueExtractor(data),\n count: 0,\n }),\n reduce: (values: Array<[{ sum: number; count: number }, number]>) => {\n let totalSum = 0\n let totalCount = 0\n for (const [value, multiplicity] of values) {\n totalSum += value.sum * multiplicity\n totalCount += multiplicity\n }\n return {\n sum: totalSum,\n count: totalCount,\n }\n },\n postMap: (result: { sum: number; count: number }) => {\n return result.sum / result.count\n },\n }\n}\n\ntype CanMinMax = number | Date | bigint | string\n\n/**\n * Creates a min aggregate function that computes the minimum value in a group\n * @param valueExtractor Function to extract a comparable value from each data entry\n */\nexport function min<T extends CanMinMax>(): AggregateFunction<\n T,\n T | undefined,\n T | undefined\n>\nexport function min<T, V extends CanMinMax>(\n valueExtractor: (value: T) => V,\n): AggregateFunction<T, V | undefined, V | undefined>\nexport function min<T, V extends CanMinMax>(\n valueExtractor?: (value: T) => V,\n): AggregateFunction<T, V | undefined, V | undefined> {\n const extractor = valueExtractor ?? ((v: T) => v as unknown as V)\n return {\n preMap: (data: T) => extractor(data),\n reduce: (values) => {\n let minValue: V | undefined\n for (const [value, _multiplicity] of values) {\n if (\n value !== undefined &&\n (minValue === undefined || value < minValue)\n ) {\n minValue = value\n }\n }\n return minValue\n },\n }\n}\n\n/**\n * Creates a max aggregate function that computes the maximum value in a group\n * @param valueExtractor Function to extract a comparable value from each data entry\n */\nexport function max<T extends CanMinMax>(): AggregateFunction<\n T,\n T | undefined,\n T | undefined\n>\nexport function max<T, V extends CanMinMax>(\n valueExtractor: (value: T) => V,\n): AggregateFunction<T, V | undefined, V | undefined>\nexport function max<T, V extends CanMinMax>(\n valueExtractor?: (value: T) => V,\n): AggregateFunction<T, V | undefined, V | undefined> {\n const extractor = valueExtractor ?? ((v: T) => v as unknown as V)\n return {\n preMap: (data: T) => extractor(data),\n reduce: (values) => {\n let maxValue: V | undefined\n for (const [value, _multiplicity] of values) {\n if (\n value !== undefined &&\n (maxValue === undefined || value > maxValue)\n ) {\n maxValue = value\n }\n }\n return maxValue\n },\n }\n}\n\n/**\n * Creates a median aggregate function that computes the middle value in a sorted group\n * If there's an even number of values, returns the average of the two middle values\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function median<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number,\n): AggregateFunction<T, number, Array<number>> {\n return {\n preMap: (data: T) => [valueExtractor(data)],\n reduce: (values: Array<[Array<number>, number]>) => {\n // Flatten all values, taking multiplicity into account\n const allValues: Array<number> = []\n for (const [valueArray, multiplicity] of values) {\n for (const value of valueArray) {\n // Add each value multiple times based on multiplicity\n for (let i = 0; i < multiplicity; i++) {\n allValues.push(value)\n }\n }\n }\n\n // Return empty array if no values\n if (allValues.length === 0) {\n return []\n }\n\n // Sort values\n allValues.sort((a, b) => a - b)\n\n return allValues\n },\n postMap: (result: Array<number>) => {\n if (result.length === 0) return 0\n\n const mid = Math.floor(result.length / 2)\n\n // If even number of values, average the two middle values\n if (result.length % 2 === 0) {\n return (result[mid - 1]! + result[mid]!) / 2\n }\n\n // If odd number of values, return the middle value\n return result[mid]!\n },\n }\n}\n\n/**\n * Creates a mode aggregate function that computes the most frequent value in a group\n * If multiple values have the same highest frequency, returns the first one encountered\n * @param valueExtractor Function to extract a value from each data entry\n */\nexport function mode<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number,\n): AggregateFunction<T, number, Map<number, number>> {\n return {\n preMap: (data: T) => {\n const value = valueExtractor(data)\n const frequencyMap = new Map<number, number>()\n frequencyMap.set(value, 1)\n return frequencyMap\n },\n reduce: (values: Array<[Map<number, number>, number]>) => {\n // Combine all frequency maps\n const combinedMap = new Map<number, number>()\n\n for (const [frequencyMap, multiplicity] of values) {\n for (const [value, frequencyCount] of frequencyMap.entries()) {\n const currentCount = combinedMap.get(value) || 0\n combinedMap.set(value, currentCount + frequencyCount * multiplicity)\n }\n }\n\n return combinedMap\n },\n postMap: (result: Map<number, number>) => {\n if (result.size === 0) return 0\n\n let modeValue = 0\n let maxFrequency = 0\n\n for (const [value, frequency] of result.entries()) {\n if (frequency > maxFrequency) {\n maxFrequency = frequency\n modeValue = value\n }\n }\n\n return modeValue\n },\n }\n}\n\nexport const groupByOperators = {\n sum,\n count,\n avg,\n min,\n max,\n median,\n mode,\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"groupBy.js","sources":["../../../src/operators/groupBy.ts"],"sourcesContent":["import { serializeValue } from '../utils.js'\nimport { map } from './map.js'\nimport { reduce } from './reduce.js'\nimport type { IStreamBuilder, KeyValue } from '../types.js'\n\ntype GroupKey = Record<string, unknown>\n\ntype BasicAggregateFunction<T, R, V = unknown> = {\n preMap: (data: T) => V\n reduce: (values: Array<[V, number]>) => V\n postMap?: (result: V) => R\n}\n\ntype PipedAggregateFunction<T, R> = {\n pipe: (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<string, R>>\n}\n\ntype AggregateFunction<T, R, V = unknown> =\n BasicAggregateFunction<T, R, V> | PipedAggregateFunction<T, R>\n\ntype ExtractAggregateReturnType<T, A> =\n A extends AggregateFunction<T, infer R, any> ? R : never\n\ntype AggregatesReturnType<T, A> = {\n [K in keyof A]: ExtractAggregateReturnType<T, A[K]>\n}\n\nfunction isPipedAggregateFunction<T, R>(\n aggregate: AggregateFunction<T, R>,\n): aggregate is PipedAggregateFunction<T, R> {\n return `pipe` in aggregate\n}\n\n/**\n * Groups data by key and applies multiple aggregate operations\n * @param keyExtractor Function to extract grouping key from data\n * @param aggregates Object mapping aggregate names to aggregate functions\n */\nexport function groupBy<\n T,\n K extends GroupKey,\n A extends Record<string, AggregateFunction<T, any, any>>,\n>(keyExtractor: (data: T) => K, aggregates: A = {} as A) {\n type ResultType = K & AggregatesReturnType<T, A>\n\n const basicAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(\n ([_, aggregate]) => !isPipedAggregateFunction(aggregate),\n ),\n ) as Record<string, BasicAggregateFunction<T, any, any>>\n\n // @ts-expect-error - TODO: we don't use this yet, but we will\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const pipedAggregates = Object.fromEntries(\n Object.entries(aggregates).filter(([_, aggregate]) =>\n isPipedAggregateFunction(aggregate),\n ),\n ) as Record<string, PipedAggregateFunction<T, any>>\n\n return (\n stream: IStreamBuilder<T>,\n ): IStreamBuilder<KeyValue<string, ResultType>> => {\n // Special key to store the original key object\n const KEY_SENTINEL = Symbol(`original_group_key`)\n\n // First map to extract keys and pre-aggregate values\n const withKeysAndValues = stream.pipe(\n map((data) => {\n const key = keyExtractor(data)\n const keyString = serializeValue(key)\n\n // Create values object with pre-aggregated values\n const values: Record<string | symbol, unknown> = {}\n\n // Store the original key object\n values[KEY_SENTINEL] = key\n\n // Add pre-aggregated values\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n values[name] = aggregate.preMap(data)\n }\n\n return [keyString, values] as KeyValue<\n string,\n Record<string | symbol, unknown>\n >\n }),\n )\n\n // Then reduce to compute aggregates\n const reduced = withKeysAndValues.pipe(\n reduce((values) => {\n // Calculate total multiplicity to check if the group should exist\n let totalMultiplicity = 0\n for (const [_, multiplicity] of values) {\n totalMultiplicity += multiplicity\n }\n\n // If total multiplicity is 0 or negative, the group should be removed completely\n if (totalMultiplicity <= 0) {\n return []\n }\n\n const result: Record<PropertyKey, unknown> = {}\n\n // Get the original key from first value in group\n const originalKey = values[0]?.[0]?.[KEY_SENTINEL]\n result[KEY_SENTINEL] = originalKey\n\n // Apply each aggregate function\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n const preValues = values.map(\n ([v, m]) => [v[name], m] as [any, number],\n )\n result[name] = aggregate.reduce(preValues)\n }\n\n return [[result, 1]]\n }),\n )\n\n // Finally map to extract the key and include all values\n return reduced.pipe(\n map(([keyString, values]) => {\n // Extract the original key\n const key = values[KEY_SENTINEL] as K\n\n // Create intermediate result with key values and aggregate results\n const result: Record<string, unknown> = {}\n\n // Add key properties to result\n Object.assign(result, key)\n\n // Apply postMap if provided\n for (const [name, aggregate] of Object.entries(basicAggregates)) {\n if (aggregate.postMap) {\n result[name] = aggregate.postMap(values[name])\n } else {\n result[name] = values[name]\n }\n }\n\n // Return with the string key instead of the object\n return [keyString, result] as KeyValue<string, ResultType>\n }),\n )\n }\n}\n\n/**\n * Creates a sum aggregate function\n */\nexport function sum<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number,\n): AggregateFunction<T, number, number> {\n return {\n preMap: (data: T) => valueExtractor(data),\n reduce: (values: Array<[number, number]>) => {\n let total = 0\n for (const [value, multiplicity] of values) {\n total += value * multiplicity\n }\n return total\n },\n }\n}\n\n/**\n * Creates a count aggregate function\n */\nexport function count<T>(\n valueExtractor: (value: T) => any = (v) => v,\n): AggregateFunction<T, number, number> {\n return {\n // Count only not-null values (the `== null` comparison gives true for both null and undefined)\n preMap: (data: T) => (valueExtractor(data) == null ? 0 : 1),\n reduce: (values: Array<[number, number]>) => {\n let totalCount = 0\n for (const [nullMultiplier, multiplicity] of values) {\n totalCount += nullMultiplier * multiplicity\n }\n return totalCount\n },\n }\n}\n\n/**\n * Creates an average aggregate function\n */\nexport function avg<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number,\n): AggregateFunction<T, number, { sum: number; count: number }> {\n return {\n preMap: (data: T) => ({\n sum: valueExtractor(data),\n count: 0,\n }),\n reduce: (values: Array<[{ sum: number; count: number }, number]>) => {\n let totalSum = 0\n let totalCount = 0\n for (const [value, multiplicity] of values) {\n totalSum += value.sum * multiplicity\n totalCount += multiplicity\n }\n return {\n sum: totalSum,\n count: totalCount,\n }\n },\n postMap: (result: { sum: number; count: number }) => {\n return result.sum / result.count\n },\n }\n}\n\ntype CanMinMax = number | Date | bigint | string\n\n/**\n * Creates a min aggregate function that computes the minimum value in a group\n * @param valueExtractor Function to extract a comparable value from each data entry\n */\nexport function min<T extends CanMinMax>(): AggregateFunction<\n T,\n T | undefined,\n T | undefined\n>\nexport function min<T, V extends CanMinMax>(\n valueExtractor: (value: T) => V,\n): AggregateFunction<T, V | undefined, V | undefined>\nexport function min<T, V extends CanMinMax>(\n valueExtractor?: (value: T) => V,\n): AggregateFunction<T, V | undefined, V | undefined> {\n const extractor = valueExtractor ?? ((v: T) => v as unknown as V)\n return {\n preMap: (data: T) => extractor(data),\n reduce: (values) => {\n let minValue: V | undefined\n for (const [value, _multiplicity] of values) {\n if (\n value !== undefined &&\n (minValue === undefined || value < minValue)\n ) {\n minValue = value\n }\n }\n return minValue\n },\n }\n}\n\n/**\n * Creates a max aggregate function that computes the maximum value in a group\n * @param valueExtractor Function to extract a comparable value from each data entry\n */\nexport function max<T extends CanMinMax>(): AggregateFunction<\n T,\n T | undefined,\n T | undefined\n>\nexport function max<T, V extends CanMinMax>(\n valueExtractor: (value: T) => V,\n): AggregateFunction<T, V | undefined, V | undefined>\nexport function max<T, V extends CanMinMax>(\n valueExtractor?: (value: T) => V,\n): AggregateFunction<T, V | undefined, V | undefined> {\n const extractor = valueExtractor ?? ((v: T) => v as unknown as V)\n return {\n preMap: (data: T) => extractor(data),\n reduce: (values) => {\n let maxValue: V | undefined\n for (const [value, _multiplicity] of values) {\n if (\n value !== undefined &&\n (maxValue === undefined || value > maxValue)\n ) {\n maxValue = value\n }\n }\n return maxValue\n },\n }\n}\n\n/**\n * Creates a median aggregate function that computes the middle value in a sorted group\n * If there's an even number of values, returns the average of the two middle values\n * @param valueExtractor Function to extract a numeric value from each data entry\n */\nexport function median<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number,\n): AggregateFunction<T, number, Array<number>> {\n return {\n preMap: (data: T) => [valueExtractor(data)],\n reduce: (values: Array<[Array<number>, number]>) => {\n // Flatten all values, taking multiplicity into account\n const allValues: Array<number> = []\n for (const [valueArray, multiplicity] of values) {\n for (const value of valueArray) {\n // Add each value multiple times based on multiplicity\n for (let i = 0; i < multiplicity; i++) {\n allValues.push(value)\n }\n }\n }\n\n // Return empty array if no values\n if (allValues.length === 0) {\n return []\n }\n\n // Sort values\n allValues.sort((a, b) => a - b)\n\n return allValues\n },\n postMap: (result: Array<number>) => {\n if (result.length === 0) return 0\n\n const mid = Math.floor(result.length / 2)\n\n // If even number of values, average the two middle values\n if (result.length % 2 === 0) {\n return (result[mid - 1]! + result[mid]!) / 2\n }\n\n // If odd number of values, return the middle value\n return result[mid]!\n },\n }\n}\n\n/**\n * Creates a mode aggregate function that computes the most frequent value in a group\n * If multiple values have the same highest frequency, returns the first one encountered\n * @param valueExtractor Function to extract a value from each data entry\n */\nexport function mode<T>(\n valueExtractor: (value: T) => number = (v) => v as unknown as number,\n): AggregateFunction<T, number, Map<number, number>> {\n return {\n preMap: (data: T) => {\n const value = valueExtractor(data)\n const frequencyMap = new Map<number, number>()\n frequencyMap.set(value, 1)\n return frequencyMap\n },\n reduce: (values: Array<[Map<number, number>, number]>) => {\n // Combine all frequency maps\n const combinedMap = new Map<number, number>()\n\n for (const [frequencyMap, multiplicity] of values) {\n for (const [value, frequencyCount] of frequencyMap.entries()) {\n const currentCount = combinedMap.get(value) || 0\n combinedMap.set(value, currentCount + frequencyCount * multiplicity)\n }\n }\n\n return combinedMap\n },\n postMap: (result: Map<number, number>) => {\n if (result.size === 0) return 0\n\n let modeValue = 0\n let maxFrequency = 0\n\n for (const [value, frequency] of result.entries()) {\n if (frequency > maxFrequency) {\n maxFrequency = frequency\n modeValue = value\n }\n }\n\n return modeValue\n },\n }\n}\n\nexport const groupByOperators = {\n sum,\n count,\n avg,\n min,\n max,\n median,\n mode,\n}\n"],"names":[],"mappings":"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"}
@@ -23,29 +23,29 @@ export declare class JoinOperator<K, V1, V2> extends BinaryOperator<[
23
23
  * @param other - The other stream to join with
24
24
  * @param type - The type of join to perform
25
25
  */
26
- export declare function join<K, V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never, V2, T>(other: IStreamBuilder<KeyValue<K, V2>>, type?: JoinType): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>>;
26
+ export declare function join<K, V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never), V2, T>(other: IStreamBuilder<KeyValue<K, V2>>, type?: JoinType): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>>;
27
27
  /**
28
28
  * Joins two input streams (inner join)
29
29
  * @param other - The other stream to join with
30
30
  */
31
- export declare function innerJoin<K, V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never, V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1, V2]>>;
31
+ export declare function innerJoin<K, V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never), V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1, V2]>>;
32
32
  /**
33
33
  * Joins two input streams (anti join)
34
34
  * @param other - The other stream to join with
35
35
  */
36
- export declare function antiJoin<K, V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never, V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1, null]>>;
36
+ export declare function antiJoin<K, V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never), V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1, null]>>;
37
37
  /**
38
38
  * Joins two input streams (left join)
39
39
  * @param other - The other stream to join with
40
40
  */
41
- export declare function leftJoin<K, V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never, V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1, V2 | null]>>;
41
+ export declare function leftJoin<K, V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never), V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1, V2 | null]>>;
42
42
  /**
43
43
  * Joins two input streams (right join)
44
44
  * @param other - The other stream to join with
45
45
  */
46
- export declare function rightJoin<K, V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never, V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1 | null, V2]>>;
46
+ export declare function rightJoin<K, V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never), V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1 | null, V2]>>;
47
47
  /**
48
48
  * Joins two input streams (full join)
49
49
  * @param other - The other stream to join with
50
50
  */
51
- export declare function fullJoin<K, V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never, V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>>;
51
+ export declare function fullJoin<K, V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never), V2, T>(other: IStreamBuilder<KeyValue<K, V2>>): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>>;
@@ -1 +1 @@
1
- {"version":3,"file":"join.js","sources":["../../../src/operators/join.ts"],"sourcesContent":["/**\n * # Direct Join Algorithms for Incremental View Maintenance\n *\n * High-performance join operations implementing all join types (inner, left, right, full, anti)\n * with minimal state and optimized performance.\n *\n * ## Algorithm\n *\n * For each tick, the algorithm processes incoming changes (deltas) and emits join results:\n *\n * 1. **Build deltas**: Create delta indexes from input messages using `Index.fromMultiSet()`\n * 2. **Inner results**: Emit `ΔA⋈B_old + A_old⋈ΔB + ΔA⋈ΔB` (matched pairs)\n * 3. **Outer results**: For unmatched rows, emit null-extended tuples:\n * - New unmatched rows from deltas (when opposite side empty)\n * - Presence transitions: when key goes `0→>0` (retract nulls) or `>0→0` (emit nulls)\n * 4. **Update state**: Append deltas to indexes (consolidated multiplicity tracking automatic)\n *\n * **Consolidated multiplicity tracking** enables O(1) presence checks instead of scanning index buckets.\n *\n * ## State\n *\n * **Indexes** store the actual data:\n * - `indexA: Index<K, V1>` - all left-side rows accumulated over time\n * - `indexB: Index<K, V2>` - all right-side rows accumulated over time\n *\n * **Consolidated multiplicity tracking** (built into Index):\n * - Each Index maintains sum of multiplicities per key internally\n * - Provides O(1) presence checks: `index.hasPresence(key)` and `index.getConsolidatedMultiplicity(key)`\n * - Avoids scanning entire index buckets just to check if key has any rows\n *\n * ## Join Types\n *\n * - **Inner**: Standard delta terms only\n * - **Outer**: Inner results + null-extended unmatched rows with transition handling\n * - **Anti**: Unmatched rows only (no inner results)\n *\n * ## Key Optimizations\n *\n * - **No temp copying**: Uses `(A⊎ΔA)⋈ΔB = A⋈ΔB ⊎ ΔA⋈ΔB` distributive property\n * - **Early-out checks**: Skip phases when no deltas present\n * - **Zero-entry pruning**: Keep maps compact, O(distinct keys) memory\n * - **Final presence logic**: Avoid emit→retract churn within same tick\n *\n * ## Correctness\n *\n * - **Ordering**: Pre-append snapshots for emissions, post-emit state updates\n * - **Presence**: Key matched iff mass ≠ 0, transitions trigger null handling\n * - **Bag semantics**: Proper multiplicity handling including negatives\n */\n\nimport { BinaryOperator, DifferenceStreamWriter } from '../graph.js'\nimport { StreamBuilder } from '../d2.js'\nimport { MultiSet } from '../multiset.js'\nimport { Index } from '../indexes.js'\nimport type { DifferenceStreamReader } from '../graph.js'\nimport type { IStreamBuilder, KeyValue, PipedOperator } from '../types.js'\n\n/**\n * Type of join to perform\n */\nexport type JoinType = `inner` | `left` | `right` | `full` | `anti`\n\n/**\n * Operator that joins two input streams using direct join algorithms\n */\nexport class JoinOperator<K, V1, V2> extends BinaryOperator<\n [K, V1] | [K, V2] | [K, [V1, V2]] | [K, [V1 | null, V2 | null]]\n> {\n #indexA = new Index<K, V1>()\n #indexB = new Index<K, V2>()\n #mode: JoinType\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, V1]>,\n inputB: DifferenceStreamReader<[K, V2]>,\n output: DifferenceStreamWriter<any>,\n mode: JoinType = `inner`,\n ) {\n super(id, inputA, inputB, output)\n this.#mode = mode\n }\n\n run(): void {\n // Build deltas from input messages\n const deltaA = Index.fromMultiSets<K, V1>(\n this.inputAMessages() as Array<MultiSet<[K, V1]>>,\n )\n const deltaB = Index.fromMultiSets<K, V2>(\n this.inputBMessages() as Array<MultiSet<[K, V2]>>,\n )\n\n // Early-out if nothing changed\n if (deltaA.size === 0 && deltaB.size === 0) return\n\n const results = new MultiSet<any>()\n\n // Emit inner results (all modes except anti)\n if (this.#mode !== `anti`) {\n this.emitInnerResults(deltaA, deltaB, results)\n }\n\n // Emit left outer/anti results\n if (\n this.#mode === `left` ||\n this.#mode === `full` ||\n this.#mode === `anti`\n ) {\n this.emitLeftOuterResults(deltaA, deltaB, results)\n }\n\n // Emit right outer results\n if (this.#mode === `right` || this.#mode === `full`) {\n this.emitRightOuterResults(deltaA, deltaB, results)\n }\n\n // Update state and send results\n // IMPORTANT: All emissions use pre-append snapshots of indexA/indexB.\n // Now append ALL deltas to indices - this happens unconditionally for every key,\n // regardless of whether presence flipped. Consolidated multiplicity tracking is automatic.\n this.#indexA.append(deltaA)\n this.#indexB.append(deltaB)\n\n // Send results\n if (results.getInner().length > 0) {\n this.output.sendData(results)\n }\n }\n\n private emitInnerResults(\n deltaA: Index<K, V1>,\n deltaB: Index<K, V2>,\n results: MultiSet<any>,\n ): void {\n // Emit the three standard delta terms: ΔA⋈B_old, A_old⋈ΔB, ΔA⋈ΔB\n if (deltaA.size > 0) results.extend(deltaA.join(this.#indexB))\n if (deltaB.size > 0) results.extend(this.#indexA.join(deltaB))\n if (deltaA.size > 0 && deltaB.size > 0) results.extend(deltaA.join(deltaB))\n }\n\n private emitLeftOuterResults(\n deltaA: Index<K, V1>,\n deltaB: Index<K, V2>,\n results: MultiSet<any>,\n ): void {\n // Emit unmatched left rows from deltaA\n if (deltaA.size > 0) {\n for (const [key, valueIterator] of deltaA.entriesIterators()) {\n const currentMultiplicityB =\n this.#indexB.getConsolidatedMultiplicity(key)\n const deltaMultiplicityB = deltaB.getConsolidatedMultiplicity(key)\n const finalMultiplicityB = currentMultiplicityB + deltaMultiplicityB\n\n if (finalMultiplicityB === 0) {\n for (const [value, multiplicity] of valueIterator) {\n if (multiplicity !== 0) {\n results.add([key, [value, null]], multiplicity)\n }\n }\n }\n }\n }\n\n // Handle presence transitions from right side changes\n if (deltaB.size > 0) {\n for (const key of deltaB.getPresenceKeys()) {\n const before = this.#indexB.getConsolidatedMultiplicity(key)\n const deltaMult = deltaB.getConsolidatedMultiplicity(key)\n if (deltaMult === 0) continue\n const after = before + deltaMult\n\n // Skip transition handling if presence doesn't flip (both zero or both non-zero)\n // Note: Index updates happen later regardless - we're only skipping null-extension emissions here\n if ((before === 0) === (after === 0)) continue\n\n // Determine the type of transition:\n // - 0 → non-zero: Right becomes non-empty, left rows transition from unmatched to matched\n // → RETRACT previously emitted null-extended rows (emit with negative multiplicity)\n // - non-zero → 0: Right becomes empty, left rows transition from matched to unmatched\n // → EMIT new null-extended rows (emit with positive multiplicity)\n const transitioningToMatched = before === 0\n\n for (const [value, multiplicity] of this.#indexA.getIterator(key)) {\n if (multiplicity !== 0) {\n results.add(\n [key, [value, null]],\n transitioningToMatched ? -multiplicity : +multiplicity,\n )\n }\n }\n }\n }\n }\n\n private emitRightOuterResults(\n deltaA: Index<K, V1>,\n deltaB: Index<K, V2>,\n results: MultiSet<any>,\n ): void {\n // Emit unmatched right rows from deltaB\n if (deltaB.size > 0) {\n for (const [key, valueIterator] of deltaB.entriesIterators()) {\n const currentMultiplicityA =\n this.#indexA.getConsolidatedMultiplicity(key)\n const deltaMultiplicityA = deltaA.getConsolidatedMultiplicity(key)\n const finalMultiplicityA = currentMultiplicityA + deltaMultiplicityA\n\n if (finalMultiplicityA === 0) {\n for (const [value, multiplicity] of valueIterator) {\n if (multiplicity !== 0) {\n results.add([key, [null, value]], multiplicity)\n }\n }\n }\n }\n }\n\n // Handle presence transitions from left side changes\n if (deltaA.size > 0) {\n for (const key of deltaA.getPresenceKeys()) {\n const before = this.#indexA.getConsolidatedMultiplicity(key)\n const deltaMult = deltaA.getConsolidatedMultiplicity(key)\n if (deltaMult === 0) continue\n const after = before + deltaMult\n\n // Skip transition handling if presence doesn't flip (both zero or both non-zero)\n // Note: Index updates happen later regardless - we're only skipping null-extension emissions here\n if ((before === 0) === (after === 0)) continue\n\n // Determine the type of transition:\n // - 0 → non-zero: Left becomes non-empty, right rows transition from unmatched to matched\n // → RETRACT previously emitted null-extended rows (emit with negative multiplicity)\n // - non-zero → 0: Left becomes empty, right rows transition from matched to unmatched\n // → EMIT new null-extended rows (emit with positive multiplicity)\n const transitioningToMatched = before === 0\n\n for (const [value, multiplicity] of this.#indexB.getIterator(key)) {\n if (multiplicity !== 0) {\n results.add(\n [key, [null, value]],\n transitioningToMatched ? -multiplicity : +multiplicity,\n )\n }\n }\n }\n }\n }\n}\n\n/**\n * Joins two input streams\n * @param other - The other stream to join with\n * @param type - The type of join to perform\n */\nexport function join<\n K,\n V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n type: JoinType = `inner`,\n): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>> {\n return (\n stream: IStreamBuilder<T>,\n ): IStreamBuilder<KeyValue<K, [V1 | null, V2 | null]>> => {\n if (stream.graph !== other.graph) {\n throw new Error(`Cannot join streams from different graphs`)\n }\n const output = new StreamBuilder<KeyValue<K, [V1 | null, V2 | null]>>(\n stream.graph,\n new DifferenceStreamWriter<KeyValue<K, [V1 | null, V2 | null]>>(),\n )\n const operator = new JoinOperator<K, V1, V2>(\n stream.graph.getNextOperatorId(),\n stream.connectReader() as DifferenceStreamReader<KeyValue<K, V1>>,\n other.connectReader(),\n output.writer,\n type,\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n\n/**\n * Joins two input streams (inner join)\n * @param other - The other stream to join with\n */\nexport function innerJoin<\n K,\n V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1, V2]>> {\n return join(other, `inner`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1, V2]>\n >\n}\n\n/**\n * Joins two input streams (anti join)\n * @param other - The other stream to join with\n */\nexport function antiJoin<\n K,\n V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1, null]>> {\n return join(other, `anti`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1, null]>\n >\n}\n\n/**\n * Joins two input streams (left join)\n * @param other - The other stream to join with\n */\nexport function leftJoin<\n K,\n V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1, V2 | null]>> {\n return join(other, `left`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1, V2 | null]>\n >\n}\n\n/**\n * Joins two input streams (right join)\n * @param other - The other stream to join with\n */\nexport function rightJoin<\n K,\n V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1 | null, V2]>> {\n return join(other, `right`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1 | null, V2]>\n >\n}\n\n/**\n * Joins two input streams (full join)\n * @param other - The other stream to join with\n */\nexport function fullJoin<\n K,\n V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>> {\n return join(other, `full`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1 | null, V2 | null]>\n >\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"join.js","sources":["../../../src/operators/join.ts"],"sourcesContent":["/**\n * # Direct Join Algorithms for Incremental View Maintenance\n *\n * High-performance join operations implementing all join types (inner, left, right, full, anti)\n * with minimal state and optimized performance.\n *\n * ## Algorithm\n *\n * For each tick, the algorithm processes incoming changes (deltas) and emits join results:\n *\n * 1. **Build deltas**: Create delta indexes from input messages using `Index.fromMultiSet()`\n * 2. **Inner results**: Emit `ΔA⋈B_old + A_old⋈ΔB + ΔA⋈ΔB` (matched pairs)\n * 3. **Outer results**: For unmatched rows, emit null-extended tuples:\n * - New unmatched rows from deltas (when opposite side empty)\n * - Presence transitions: when key goes `0→>0` (retract nulls) or `>0→0` (emit nulls)\n * 4. **Update state**: Append deltas to indexes (consolidated multiplicity tracking automatic)\n *\n * **Consolidated multiplicity tracking** enables O(1) presence checks instead of scanning index buckets.\n *\n * ## State\n *\n * **Indexes** store the actual data:\n * - `indexA: Index<K, V1>` - all left-side rows accumulated over time\n * - `indexB: Index<K, V2>` - all right-side rows accumulated over time\n *\n * **Consolidated multiplicity tracking** (built into Index):\n * - Each Index maintains sum of multiplicities per key internally\n * - Provides O(1) presence checks: `index.hasPresence(key)` and `index.getConsolidatedMultiplicity(key)`\n * - Avoids scanning entire index buckets just to check if key has any rows\n *\n * ## Join Types\n *\n * - **Inner**: Standard delta terms only\n * - **Outer**: Inner results + null-extended unmatched rows with transition handling\n * - **Anti**: Unmatched rows only (no inner results)\n *\n * ## Key Optimizations\n *\n * - **No temp copying**: Uses `(A⊎ΔA)⋈ΔB = A⋈ΔB ⊎ ΔA⋈ΔB` distributive property\n * - **Early-out checks**: Skip phases when no deltas present\n * - **Zero-entry pruning**: Keep maps compact, O(distinct keys) memory\n * - **Final presence logic**: Avoid emit→retract churn within same tick\n *\n * ## Correctness\n *\n * - **Ordering**: Pre-append snapshots for emissions, post-emit state updates\n * - **Presence**: Key matched iff mass ≠ 0, transitions trigger null handling\n * - **Bag semantics**: Proper multiplicity handling including negatives\n */\n\nimport { BinaryOperator, DifferenceStreamWriter } from '../graph.js'\nimport { StreamBuilder } from '../d2.js'\nimport { MultiSet } from '../multiset.js'\nimport { Index } from '../indexes.js'\nimport type { DifferenceStreamReader } from '../graph.js'\nimport type { IStreamBuilder, KeyValue, PipedOperator } from '../types.js'\n\n/**\n * Type of join to perform\n */\nexport type JoinType = `inner` | `left` | `right` | `full` | `anti`\n\n/**\n * Operator that joins two input streams using direct join algorithms\n */\nexport class JoinOperator<K, V1, V2> extends BinaryOperator<\n [K, V1] | [K, V2] | [K, [V1, V2]] | [K, [V1 | null, V2 | null]]\n> {\n #indexA = new Index<K, V1>()\n #indexB = new Index<K, V2>()\n #mode: JoinType\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, V1]>,\n inputB: DifferenceStreamReader<[K, V2]>,\n output: DifferenceStreamWriter<any>,\n mode: JoinType = `inner`,\n ) {\n super(id, inputA, inputB, output)\n this.#mode = mode\n }\n\n run(): void {\n // Build deltas from input messages\n const deltaA = Index.fromMultiSets<K, V1>(\n this.inputAMessages() as Array<MultiSet<[K, V1]>>,\n )\n const deltaB = Index.fromMultiSets<K, V2>(\n this.inputBMessages() as Array<MultiSet<[K, V2]>>,\n )\n\n // Early-out if nothing changed\n if (deltaA.size === 0 && deltaB.size === 0) return\n\n const results = new MultiSet<any>()\n\n // Emit inner results (all modes except anti)\n if (this.#mode !== `anti`) {\n this.emitInnerResults(deltaA, deltaB, results)\n }\n\n // Emit left outer/anti results\n if (\n this.#mode === `left` ||\n this.#mode === `full` ||\n this.#mode === `anti`\n ) {\n this.emitLeftOuterResults(deltaA, deltaB, results)\n }\n\n // Emit right outer results\n if (this.#mode === `right` || this.#mode === `full`) {\n this.emitRightOuterResults(deltaA, deltaB, results)\n }\n\n // Update state and send results\n // IMPORTANT: All emissions use pre-append snapshots of indexA/indexB.\n // Now append ALL deltas to indices - this happens unconditionally for every key,\n // regardless of whether presence flipped. Consolidated multiplicity tracking is automatic.\n this.#indexA.append(deltaA)\n this.#indexB.append(deltaB)\n\n // Send results\n if (results.getInner().length > 0) {\n this.output.sendData(results)\n }\n }\n\n private emitInnerResults(\n deltaA: Index<K, V1>,\n deltaB: Index<K, V2>,\n results: MultiSet<any>,\n ): void {\n // Emit the three standard delta terms: ΔA⋈B_old, A_old⋈ΔB, ΔA⋈ΔB\n if (deltaA.size > 0) results.extend(deltaA.join(this.#indexB))\n if (deltaB.size > 0) results.extend(this.#indexA.join(deltaB))\n if (deltaA.size > 0 && deltaB.size > 0) results.extend(deltaA.join(deltaB))\n }\n\n private emitLeftOuterResults(\n deltaA: Index<K, V1>,\n deltaB: Index<K, V2>,\n results: MultiSet<any>,\n ): void {\n // Emit unmatched left rows from deltaA\n if (deltaA.size > 0) {\n for (const [key, valueIterator] of deltaA.entriesIterators()) {\n const currentMultiplicityB =\n this.#indexB.getConsolidatedMultiplicity(key)\n const deltaMultiplicityB = deltaB.getConsolidatedMultiplicity(key)\n const finalMultiplicityB = currentMultiplicityB + deltaMultiplicityB\n\n if (finalMultiplicityB === 0) {\n for (const [value, multiplicity] of valueIterator) {\n if (multiplicity !== 0) {\n results.add([key, [value, null]], multiplicity)\n }\n }\n }\n }\n }\n\n // Handle presence transitions from right side changes\n if (deltaB.size > 0) {\n for (const key of deltaB.getPresenceKeys()) {\n const before = this.#indexB.getConsolidatedMultiplicity(key)\n const deltaMult = deltaB.getConsolidatedMultiplicity(key)\n if (deltaMult === 0) continue\n const after = before + deltaMult\n\n // Skip transition handling if presence doesn't flip (both zero or both non-zero)\n // Note: Index updates happen later regardless - we're only skipping null-extension emissions here\n if ((before === 0) === (after === 0)) continue\n\n // Determine the type of transition:\n // - 0 → non-zero: Right becomes non-empty, left rows transition from unmatched to matched\n // → RETRACT previously emitted null-extended rows (emit with negative multiplicity)\n // - non-zero → 0: Right becomes empty, left rows transition from matched to unmatched\n // → EMIT new null-extended rows (emit with positive multiplicity)\n const transitioningToMatched = before === 0\n\n for (const [value, multiplicity] of this.#indexA.getIterator(key)) {\n if (multiplicity !== 0) {\n results.add(\n [key, [value, null]],\n transitioningToMatched ? -multiplicity : +multiplicity,\n )\n }\n }\n }\n }\n }\n\n private emitRightOuterResults(\n deltaA: Index<K, V1>,\n deltaB: Index<K, V2>,\n results: MultiSet<any>,\n ): void {\n // Emit unmatched right rows from deltaB\n if (deltaB.size > 0) {\n for (const [key, valueIterator] of deltaB.entriesIterators()) {\n const currentMultiplicityA =\n this.#indexA.getConsolidatedMultiplicity(key)\n const deltaMultiplicityA = deltaA.getConsolidatedMultiplicity(key)\n const finalMultiplicityA = currentMultiplicityA + deltaMultiplicityA\n\n if (finalMultiplicityA === 0) {\n for (const [value, multiplicity] of valueIterator) {\n if (multiplicity !== 0) {\n results.add([key, [null, value]], multiplicity)\n }\n }\n }\n }\n }\n\n // Handle presence transitions from left side changes\n if (deltaA.size > 0) {\n for (const key of deltaA.getPresenceKeys()) {\n const before = this.#indexA.getConsolidatedMultiplicity(key)\n const deltaMult = deltaA.getConsolidatedMultiplicity(key)\n if (deltaMult === 0) continue\n const after = before + deltaMult\n\n // Skip transition handling if presence doesn't flip (both zero or both non-zero)\n // Note: Index updates happen later regardless - we're only skipping null-extension emissions here\n if ((before === 0) === (after === 0)) continue\n\n // Determine the type of transition:\n // - 0 → non-zero: Left becomes non-empty, right rows transition from unmatched to matched\n // → RETRACT previously emitted null-extended rows (emit with negative multiplicity)\n // - non-zero → 0: Left becomes empty, right rows transition from matched to unmatched\n // → EMIT new null-extended rows (emit with positive multiplicity)\n const transitioningToMatched = before === 0\n\n for (const [value, multiplicity] of this.#indexB.getIterator(key)) {\n if (multiplicity !== 0) {\n results.add(\n [key, [null, value]],\n transitioningToMatched ? -multiplicity : +multiplicity,\n )\n }\n }\n }\n }\n }\n}\n\n/**\n * Joins two input streams\n * @param other - The other stream to join with\n * @param type - The type of join to perform\n */\nexport function join<\n K,\n V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n type: JoinType = `inner`,\n): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>> {\n return (\n stream: IStreamBuilder<T>,\n ): IStreamBuilder<KeyValue<K, [V1 | null, V2 | null]>> => {\n if (stream.graph !== other.graph) {\n throw new Error(`Cannot join streams from different graphs`)\n }\n const output = new StreamBuilder<KeyValue<K, [V1 | null, V2 | null]>>(\n stream.graph,\n new DifferenceStreamWriter<KeyValue<K, [V1 | null, V2 | null]>>(),\n )\n const operator = new JoinOperator<K, V1, V2>(\n stream.graph.getNextOperatorId(),\n stream.connectReader() as DifferenceStreamReader<KeyValue<K, V1>>,\n other.connectReader(),\n output.writer,\n type,\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n\n/**\n * Joins two input streams (inner join)\n * @param other - The other stream to join with\n */\nexport function innerJoin<\n K,\n V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1, V2]>> {\n return join(other, `inner`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1, V2]>\n >\n}\n\n/**\n * Joins two input streams (anti join)\n * @param other - The other stream to join with\n */\nexport function antiJoin<\n K,\n V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1, null]>> {\n return join(other, `anti`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1, null]>\n >\n}\n\n/**\n * Joins two input streams (left join)\n * @param other - The other stream to join with\n */\nexport function leftJoin<\n K,\n V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1, V2 | null]>> {\n return join(other, `left`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1, V2 | null]>\n >\n}\n\n/**\n * Joins two input streams (right join)\n * @param other - The other stream to join with\n */\nexport function rightJoin<\n K,\n V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1 | null, V2]>> {\n return join(other, `right`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1 | null, V2]>\n >\n}\n\n/**\n * Joins two input streams (full join)\n * @param other - The other stream to join with\n */\nexport function fullJoin<\n K,\n V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),\n V2,\n T,\n>(\n other: IStreamBuilder<KeyValue<K, V2>>,\n): PipedOperator<T, KeyValue<K, [V1 | null, V2 | null]>> {\n return join(other, `full`) as unknown as PipedOperator<\n T,\n KeyValue<K, [V1 | null, V2 | null]>\n >\n}\n"],"names":[],"mappings":"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"}
@@ -11,4 +11,4 @@ export declare class ReduceOperator<K, V1, V2> extends UnaryOperator<[K, V1], [K
11
11
  /**
12
12
  * Reduces the elements in the stream by key (version-free)
13
13
  */
14
- export declare function reduce<KType extends T extends KeyValue<infer K, infer _V> ? K : never, V1Type extends T extends KeyValue<KType, infer V> ? V : never, R, T>(f: (values: Array<[V1Type, number]>) => Array<[R, number]>): (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<KType, R>>;
14
+ export declare function reduce<KType extends (T extends KeyValue<infer K, infer _V> ? K : never), V1Type extends (T extends KeyValue<KType, infer V> ? V : never), R, T>(f: (values: Array<[V1Type, number]>) => Array<[R, number]>): (stream: IStreamBuilder<T>) => IStreamBuilder<KeyValue<KType, R>>;
@@ -1 +1 @@
1
- {"version":3,"file":"reduce.js","sources":["../../../src/operators/reduce.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from '../graph.js'\nimport { StreamBuilder } from '../d2.js'\nimport { MultiSet } from '../multiset.js'\nimport { Index } from '../indexes.js'\nimport type { DifferenceStreamReader } from '../graph.js'\nimport type { IStreamBuilder, KeyValue } from '../types.js'\n\n/**\n * Base operator for reduction operations (version-free)\n */\nexport class ReduceOperator<K, V1, V2> extends UnaryOperator<[K, V1], [K, V2]> {\n #index = new Index<K, V1>()\n #indexOut = new Index<K, V2>()\n #f: (values: Array<[V1, number]>) => Array<[V2, number]>\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, V1]>,\n output: DifferenceStreamWriter<[K, V2]>,\n f: (values: Array<[V1, number]>) => Array<[V2, number]>,\n ) {\n super(id, inputA, output)\n this.#f = f\n }\n\n run(): void {\n // Collect all input messages and update the index\n const keysTodo = new Set<K>()\n for (const message of this.inputMessages()) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n this.#index.addValue(key, [value, multiplicity])\n keysTodo.add(key)\n }\n }\n\n // For each key, compute the reduction and delta\n const result: Array<[[K, V2], number]> = []\n for (const key of keysTodo) {\n const curr = this.#index.get(key)\n const currOut = this.#indexOut.get(key)\n const out = this.#f(curr)\n\n // Create maps for current and previous outputs using values directly as keys\n const newOutputMap = new Map<V2, number>()\n const oldOutputMap = new Map<V2, number>()\n\n // Process new output\n for (const [value, multiplicity] of out) {\n const existing = newOutputMap.get(value) ?? 0\n newOutputMap.set(value, existing + multiplicity)\n }\n\n // Process previous output\n for (const [value, multiplicity] of currOut) {\n const existing = oldOutputMap.get(value) ?? 0\n oldOutputMap.set(value, existing + multiplicity)\n }\n\n // First, emit removals for old values that are no longer present\n for (const [value, multiplicity] of oldOutputMap) {\n if (!newOutputMap.has(value)) {\n // Remove the old value entirely\n result.push([[key, value], -multiplicity])\n this.#indexOut.addValue(key, [value, -multiplicity])\n }\n }\n\n // Then, emit additions for new values that are not present in old\n for (const [value, multiplicity] of newOutputMap) {\n if (!oldOutputMap.has(value)) {\n // Add the new value only if it has non-zero multiplicity\n if (multiplicity !== 0) {\n result.push([[key, value], multiplicity])\n this.#indexOut.addValue(key, [value, multiplicity])\n }\n }\n }\n\n // Finally, emit multiplicity changes for values that were present and are still present\n for (const [value, newMultiplicity] of newOutputMap) {\n const oldMultiplicity = oldOutputMap.get(value)\n if (oldMultiplicity !== undefined) {\n const delta = newMultiplicity - oldMultiplicity\n // Only emit actual changes, i.e. non-zero deltas\n if (delta !== 0) {\n result.push([[key, value], delta])\n this.#indexOut.addValue(key, [value, delta])\n }\n }\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n}\n\n/**\n * Reduces the elements in the stream by key (version-free)\n */\nexport function reduce<\n KType extends T extends KeyValue<infer K, infer _V> ? K : never,\n V1Type extends T extends KeyValue<KType, infer V> ? V : never,\n R,\n T,\n>(f: (values: Array<[V1Type, number]>) => Array<[R, number]>) {\n return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<KType, R>> => {\n const output = new StreamBuilder<KeyValue<KType, R>>(\n stream.graph,\n new DifferenceStreamWriter<KeyValue<KType, R>>(),\n )\n const operator = new ReduceOperator<KType, V1Type, R>(\n stream.graph.getNextOperatorId(),\n stream.connectReader() as DifferenceStreamReader<KeyValue<KType, V1Type>>,\n output.writer,\n f,\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n"],"names":[],"mappings":";;;;AAUO,MAAM,uBAAkC,cAAgC;AAAA,EAC7E,SAAS,IAAI,MAAA;AAAA,EACb,YAAY,IAAI,MAAA;AAAA,EAChB;AAAA,EAEA,YACE,IACA,QACA,QACA,GACA;AACA,UAAM,IAAI,QAAQ,MAAM;AACxB,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,MAAY;AAEV,UAAM,+BAAe,IAAA;AACrB,eAAW,WAAW,KAAK,iBAAiB;AAC1C,iBAAW,CAAC,MAAM,YAAY,KAAK,QAAQ,YAAY;AACrD,cAAM,CAAC,KAAK,KAAK,IAAI;AACrB,aAAK,OAAO,SAAS,KAAK,CAAC,OAAO,YAAY,CAAC;AAC/C,iBAAS,IAAI,GAAG;AAAA,MAClB;AAAA,IACF;AAGA,UAAM,SAAmC,CAAA;AACzC,eAAW,OAAO,UAAU;AAC1B,YAAM,OAAO,KAAK,OAAO,IAAI,GAAG;AAChC,YAAM,UAAU,KAAK,UAAU,IAAI,GAAG;AACtC,YAAM,MAAM,KAAK,GAAG,IAAI;AAGxB,YAAM,mCAAmB,IAAA;AACzB,YAAM,mCAAmB,IAAA;AAGzB,iBAAW,CAAC,OAAO,YAAY,KAAK,KAAK;AACvC,cAAM,WAAW,aAAa,IAAI,KAAK,KAAK;AAC5C,qBAAa,IAAI,OAAO,WAAW,YAAY;AAAA,MACjD;AAGA,iBAAW,CAAC,OAAO,YAAY,KAAK,SAAS;AAC3C,cAAM,WAAW,aAAa,IAAI,KAAK,KAAK;AAC5C,qBAAa,IAAI,OAAO,WAAW,YAAY;AAAA,MACjD;AAGA,iBAAW,CAAC,OAAO,YAAY,KAAK,cAAc;AAChD,YAAI,CAAC,aAAa,IAAI,KAAK,GAAG;AAE5B,iBAAO,KAAK,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,YAAY,CAAC;AACzC,eAAK,UAAU,SAAS,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;AAAA,QACrD;AAAA,MACF;AAGA,iBAAW,CAAC,OAAO,YAAY,KAAK,cAAc;AAChD,YAAI,CAAC,aAAa,IAAI,KAAK,GAAG;AAE5B,cAAI,iBAAiB,GAAG;AACtB,mBAAO,KAAK,CAAC,CAAC,KAAK,KAAK,GAAG,YAAY,CAAC;AACxC,iBAAK,UAAU,SAAS,KAAK,CAAC,OAAO,YAAY,CAAC;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAGA,iBAAW,CAAC,OAAO,eAAe,KAAK,cAAc;AACnD,cAAM,kBAAkB,aAAa,IAAI,KAAK;AAC9C,YAAI,oBAAoB,QAAW;AACjC,gBAAM,QAAQ,kBAAkB;AAEhC,cAAI,UAAU,GAAG;AACf,mBAAO,KAAK,CAAC,CAAC,KAAK,KAAK,GAAG,KAAK,CAAC;AACjC,iBAAK,UAAU,SAAS,KAAK,CAAC,OAAO,KAAK,CAAC;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,WAAK,OAAO,SAAS,IAAI,SAAS,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AACF;AAKO,SAAS,OAKd,GAA4D;AAC5D,SAAO,CAAC,WAAkE;AACxE,UAAM,SAAS,IAAI;AAAA,MACjB,OAAO;AAAA,MACP,IAAI,uBAAA;AAAA,IAA2C;AAEjD,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"reduce.js","sources":["../../../src/operators/reduce.ts"],"sourcesContent":["import { DifferenceStreamWriter, UnaryOperator } from '../graph.js'\nimport { StreamBuilder } from '../d2.js'\nimport { MultiSet } from '../multiset.js'\nimport { Index } from '../indexes.js'\nimport type { DifferenceStreamReader } from '../graph.js'\nimport type { IStreamBuilder, KeyValue } from '../types.js'\n\n/**\n * Base operator for reduction operations (version-free)\n */\nexport class ReduceOperator<K, V1, V2> extends UnaryOperator<[K, V1], [K, V2]> {\n #index = new Index<K, V1>()\n #indexOut = new Index<K, V2>()\n #f: (values: Array<[V1, number]>) => Array<[V2, number]>\n\n constructor(\n id: number,\n inputA: DifferenceStreamReader<[K, V1]>,\n output: DifferenceStreamWriter<[K, V2]>,\n f: (values: Array<[V1, number]>) => Array<[V2, number]>,\n ) {\n super(id, inputA, output)\n this.#f = f\n }\n\n run(): void {\n // Collect all input messages and update the index\n const keysTodo = new Set<K>()\n for (const message of this.inputMessages()) {\n for (const [item, multiplicity] of message.getInner()) {\n const [key, value] = item\n this.#index.addValue(key, [value, multiplicity])\n keysTodo.add(key)\n }\n }\n\n // For each key, compute the reduction and delta\n const result: Array<[[K, V2], number]> = []\n for (const key of keysTodo) {\n const curr = this.#index.get(key)\n const currOut = this.#indexOut.get(key)\n const out = this.#f(curr)\n\n // Create maps for current and previous outputs using values directly as keys\n const newOutputMap = new Map<V2, number>()\n const oldOutputMap = new Map<V2, number>()\n\n // Process new output\n for (const [value, multiplicity] of out) {\n const existing = newOutputMap.get(value) ?? 0\n newOutputMap.set(value, existing + multiplicity)\n }\n\n // Process previous output\n for (const [value, multiplicity] of currOut) {\n const existing = oldOutputMap.get(value) ?? 0\n oldOutputMap.set(value, existing + multiplicity)\n }\n\n // First, emit removals for old values that are no longer present\n for (const [value, multiplicity] of oldOutputMap) {\n if (!newOutputMap.has(value)) {\n // Remove the old value entirely\n result.push([[key, value], -multiplicity])\n this.#indexOut.addValue(key, [value, -multiplicity])\n }\n }\n\n // Then, emit additions for new values that are not present in old\n for (const [value, multiplicity] of newOutputMap) {\n if (!oldOutputMap.has(value)) {\n // Add the new value only if it has non-zero multiplicity\n if (multiplicity !== 0) {\n result.push([[key, value], multiplicity])\n this.#indexOut.addValue(key, [value, multiplicity])\n }\n }\n }\n\n // Finally, emit multiplicity changes for values that were present and are still present\n for (const [value, newMultiplicity] of newOutputMap) {\n const oldMultiplicity = oldOutputMap.get(value)\n if (oldMultiplicity !== undefined) {\n const delta = newMultiplicity - oldMultiplicity\n // Only emit actual changes, i.e. non-zero deltas\n if (delta !== 0) {\n result.push([[key, value], delta])\n this.#indexOut.addValue(key, [value, delta])\n }\n }\n }\n }\n\n if (result.length > 0) {\n this.output.sendData(new MultiSet(result))\n }\n }\n}\n\n/**\n * Reduces the elements in the stream by key (version-free)\n */\nexport function reduce<\n KType extends (T extends KeyValue<infer K, infer _V> ? K : never),\n V1Type extends (T extends KeyValue<KType, infer V> ? V : never),\n R,\n T,\n>(f: (values: Array<[V1Type, number]>) => Array<[R, number]>) {\n return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<KType, R>> => {\n const output = new StreamBuilder<KeyValue<KType, R>>(\n stream.graph,\n new DifferenceStreamWriter<KeyValue<KType, R>>(),\n )\n const operator = new ReduceOperator<KType, V1Type, R>(\n stream.graph.getNextOperatorId(),\n stream.connectReader() as DifferenceStreamReader<KeyValue<KType, V1Type>>,\n output.writer,\n f,\n )\n stream.graph.addOperator(operator)\n return output\n }\n}\n"],"names":[],"mappings":";;;;AAUO,MAAM,uBAAkC,cAAgC;AAAA,EAC7E,SAAS,IAAI,MAAA;AAAA,EACb,YAAY,IAAI,MAAA;AAAA,EAChB;AAAA,EAEA,YACE,IACA,QACA,QACA,GACA;AACA,UAAM,IAAI,QAAQ,MAAM;AACxB,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,MAAY;AAEV,UAAM,+BAAe,IAAA;AACrB,eAAW,WAAW,KAAK,iBAAiB;AAC1C,iBAAW,CAAC,MAAM,YAAY,KAAK,QAAQ,YAAY;AACrD,cAAM,CAAC,KAAK,KAAK,IAAI;AACrB,aAAK,OAAO,SAAS,KAAK,CAAC,OAAO,YAAY,CAAC;AAC/C,iBAAS,IAAI,GAAG;AAAA,MAClB;AAAA,IACF;AAGA,UAAM,SAAmC,CAAA;AACzC,eAAW,OAAO,UAAU;AAC1B,YAAM,OAAO,KAAK,OAAO,IAAI,GAAG;AAChC,YAAM,UAAU,KAAK,UAAU,IAAI,GAAG;AACtC,YAAM,MAAM,KAAK,GAAG,IAAI;AAGxB,YAAM,mCAAmB,IAAA;AACzB,YAAM,mCAAmB,IAAA;AAGzB,iBAAW,CAAC,OAAO,YAAY,KAAK,KAAK;AACvC,cAAM,WAAW,aAAa,IAAI,KAAK,KAAK;AAC5C,qBAAa,IAAI,OAAO,WAAW,YAAY;AAAA,MACjD;AAGA,iBAAW,CAAC,OAAO,YAAY,KAAK,SAAS;AAC3C,cAAM,WAAW,aAAa,IAAI,KAAK,KAAK;AAC5C,qBAAa,IAAI,OAAO,WAAW,YAAY;AAAA,MACjD;AAGA,iBAAW,CAAC,OAAO,YAAY,KAAK,cAAc;AAChD,YAAI,CAAC,aAAa,IAAI,KAAK,GAAG;AAE5B,iBAAO,KAAK,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,YAAY,CAAC;AACzC,eAAK,UAAU,SAAS,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;AAAA,QACrD;AAAA,MACF;AAGA,iBAAW,CAAC,OAAO,YAAY,KAAK,cAAc;AAChD,YAAI,CAAC,aAAa,IAAI,KAAK,GAAG;AAE5B,cAAI,iBAAiB,GAAG;AACtB,mBAAO,KAAK,CAAC,CAAC,KAAK,KAAK,GAAG,YAAY,CAAC;AACxC,iBAAK,UAAU,SAAS,KAAK,CAAC,OAAO,YAAY,CAAC;AAAA,UACpD;AAAA,QACF;AAAA,MACF;AAGA,iBAAW,CAAC,OAAO,eAAe,KAAK,cAAc;AACnD,cAAM,kBAAkB,aAAa,IAAI,KAAK;AAC9C,YAAI,oBAAoB,QAAW;AACjC,gBAAM,QAAQ,kBAAkB;AAEhC,cAAI,UAAU,GAAG;AACf,mBAAO,KAAK,CAAC,CAAC,KAAK,KAAK,GAAG,KAAK,CAAC;AACjC,iBAAK,UAAU,SAAS,KAAK,CAAC,OAAO,KAAK,CAAC;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,WAAK,OAAO,SAAS,IAAI,SAAS,MAAM,CAAC;AAAA,IAC3C;AAAA,EACF;AACF;AAKO,SAAS,OAKd,GAA4D;AAC5D,SAAO,CAAC,WAAkE;AACxE,UAAM,SAAS,IAAI;AAAA,MACjB,OAAO;AAAA,MACP,IAAI,uBAAA;AAAA,IAA2C;AAEjD,UAAM,WAAW,IAAI;AAAA,MACnB,OAAO,MAAM,kBAAA;AAAA,MACb,OAAO,cAAA;AAAA,MACP,OAAO;AAAA,MACP;AAAA,IAAA;AAEF,WAAO,MAAM,YAAY,QAAQ;AACjC,WAAO;AAAA,EACT;AACF;"}
@@ -14,7 +14,7 @@ interface TopKOptions {
14
14
  * @param options - An optional object containing limit and offset properties
15
15
  * @returns A piped operator that limits the number of results
16
16
  */
17
- export declare function topK<KType extends T extends KeyValue<infer K, infer _V> ? K : never, V1Type extends T extends KeyValue<KType, infer V> ? V : never, T>(comparator: (a: V1Type, b: V1Type) => number, options?: TopKOptions): PipedOperator<T, T>;
17
+ export declare function topK<KType extends (T extends KeyValue<infer K, infer _V> ? K : never), V1Type extends (T extends KeyValue<KType, infer V> ? V : never), T>(comparator: (a: V1Type, b: V1Type) => number, options?: TopKOptions): PipedOperator<T, T>;
18
18
  /**
19
19
  * Limits the number of results based on a comparator, with optional offset.
20
20
  * This works on a keyed stream, where the key is the first element of the tuple
@@ -27,5 +27,5 @@ export declare function topK<KType extends T extends KeyValue<infer K, infer _V>
27
27
  * @param options - An optional object containing limit and offset properties
28
28
  * @returns A piped operator that orders the elements and limits the number of results
29
29
  */
30
- export declare function topKWithIndex<KType extends T extends KeyValue<infer K, infer _V> ? K : never, V1Type extends T extends KeyValue<KType, infer V> ? V : never, T>(comparator: (a: V1Type, b: V1Type) => number, options?: TopKOptions): PipedOperator<T, KeyValue<KType, [V1Type, number]>>;
30
+ export declare function topKWithIndex<KType extends (T extends KeyValue<infer K, infer _V> ? K : never), V1Type extends (T extends KeyValue<KType, infer V> ? V : never), T>(comparator: (a: V1Type, b: V1Type) => number, options?: TopKOptions): PipedOperator<T, KeyValue<KType, [V1Type, number]>>;
31
31
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"topK.js","sources":["../../../src/operators/topK.ts"],"sourcesContent":["import { MultiSet } from '../multiset.js'\nimport { reduce } from './reduce.js'\nimport type { IStreamBuilder, KeyValue, PipedOperator } from '../types'\n\ninterface TopKOptions {\n limit?: number\n offset?: number\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * This works on a keyed stream, where the key is the first element of the tuple\n * The ordering is withing a key group, i.e. elements are sorted within a key group\n * and the limit + offset is applied to that sorted group.\n * To order the entire stream, key by the same value for all elements such as null.\n *\n * @param comparator - A function that compares two elements\n * @param options - An optional object containing limit and offset properties\n * @returns A piped operator that limits the number of results\n */\nexport function topK<\n KType extends T extends KeyValue<infer K, infer _V> ? K : never,\n V1Type extends T extends KeyValue<KType, infer V> ? V : never,\n T,\n>(\n comparator: (a: V1Type, b: V1Type) => number,\n options?: TopKOptions,\n): PipedOperator<T, T> {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const reduced = stream.pipe(\n reduce((values) => {\n // `values` is a list of tuples, first element is the value, second is the multiplicity\n const consolidated = new MultiSet(values).consolidate()\n const sortedValues = consolidated\n .getInner()\n .sort((a, b) => comparator(a[0] as V1Type, b[0] as V1Type))\n return sortedValues.slice(offset, offset + limit)\n }),\n )\n return reduced as IStreamBuilder<T>\n }\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * This works on a keyed stream, where the key is the first element of the tuple\n * The ordering is withing a key group, i.e. elements are sorted within a key group\n * and the limit + offset is applied to that sorted group.\n * To order the entire stream, key by the same value for all elements such as null.\n * Adds the index of the element to the result as [key, [value, index]]\n *\n * @param comparator - A function that compares two elements\n * @param options - An optional object containing limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function topKWithIndex<\n KType extends T extends KeyValue<infer K, infer _V> ? K : never,\n V1Type extends T extends KeyValue<KType, infer V> ? V : never,\n T,\n>(\n comparator: (a: V1Type, b: V1Type) => number,\n options?: TopKOptions,\n): PipedOperator<T, KeyValue<KType, [V1Type, number]>> {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n\n return (\n stream: IStreamBuilder<T>,\n ): IStreamBuilder<KeyValue<KType, [V1Type, number]>> => {\n const reduced = stream.pipe(\n reduce<KType, V1Type, [V1Type, number], T>((values) => {\n // `values` is a list of tuples, first element is the value, second is the multiplicity\n const consolidated = new MultiSet(values).consolidate()\n let i = offset\n const sortedValues = consolidated\n .getInner()\n .sort((a, b) => comparator(a[0], b[0]))\n .slice(offset, offset + limit)\n .map(([value, multiplicity]): [[V1Type, number], number] => [\n [value, i++],\n multiplicity,\n ])\n return sortedValues\n }),\n )\n return reduced\n }\n}\n"],"names":[],"mappings":";;AAoBO,SAAS,KAKd,YACA,SACqB;AACrB,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,SAAS,SAAS,UAAU;AAElC,SAAO,CAAC,WAAiD;AACvD,UAAM,UAAU,OAAO;AAAA,MACrB,OAAO,CAAC,WAAW;AAEjB,cAAM,eAAe,IAAI,SAAS,MAAM,EAAE,YAAA;AAC1C,cAAM,eAAe,aAClB,SAAA,EACA,KAAK,CAAC,GAAG,MAAM,WAAW,EAAE,CAAC,GAAa,EAAE,CAAC,CAAW,CAAC;AAC5D,eAAO,aAAa,MAAM,QAAQ,SAAS,KAAK;AAAA,MAClD,CAAC;AAAA,IAAA;AAEH,WAAO;AAAA,EACT;AACF;AAcO,SAAS,cAKd,YACA,SACqD;AACrD,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,SAAS,SAAS,UAAU;AAElC,SAAO,CACL,WACsD;AACtD,UAAM,UAAU,OAAO;AAAA,MACrB,OAA2C,CAAC,WAAW;AAErD,cAAM,eAAe,IAAI,SAAS,MAAM,EAAE,YAAA;AAC1C,YAAI,IAAI;AACR,cAAM,eAAe,aAClB,SAAA,EACA,KAAK,CAAC,GAAG,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EACrC,MAAM,QAAQ,SAAS,KAAK,EAC5B,IAAI,CAAC,CAAC,OAAO,YAAY,MAAkC;AAAA,UAC1D,CAAC,OAAO,GAAG;AAAA,UACX;AAAA,QAAA,CACD;AACH,eAAO;AAAA,MACT,CAAC;AAAA,IAAA;AAEH,WAAO;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"topK.js","sources":["../../../src/operators/topK.ts"],"sourcesContent":["import { MultiSet } from '../multiset.js'\nimport { reduce } from './reduce.js'\nimport type { IStreamBuilder, KeyValue, PipedOperator } from '../types'\n\ninterface TopKOptions {\n limit?: number\n offset?: number\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * This works on a keyed stream, where the key is the first element of the tuple\n * The ordering is withing a key group, i.e. elements are sorted within a key group\n * and the limit + offset is applied to that sorted group.\n * To order the entire stream, key by the same value for all elements such as null.\n *\n * @param comparator - A function that compares two elements\n * @param options - An optional object containing limit and offset properties\n * @returns A piped operator that limits the number of results\n */\nexport function topK<\n KType extends (T extends KeyValue<infer K, infer _V> ? K : never),\n V1Type extends (T extends KeyValue<KType, infer V> ? V : never),\n T,\n>(\n comparator: (a: V1Type, b: V1Type) => number,\n options?: TopKOptions,\n): PipedOperator<T, T> {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n\n return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {\n const reduced = stream.pipe(\n reduce((values) => {\n // `values` is a list of tuples, first element is the value, second is the multiplicity\n const consolidated = new MultiSet(values).consolidate()\n const sortedValues = consolidated\n .getInner()\n .sort((a, b) => comparator(a[0] as V1Type, b[0] as V1Type))\n return sortedValues.slice(offset, offset + limit)\n }),\n )\n return reduced as IStreamBuilder<T>\n }\n}\n\n/**\n * Limits the number of results based on a comparator, with optional offset.\n * This works on a keyed stream, where the key is the first element of the tuple\n * The ordering is withing a key group, i.e. elements are sorted within a key group\n * and the limit + offset is applied to that sorted group.\n * To order the entire stream, key by the same value for all elements such as null.\n * Adds the index of the element to the result as [key, [value, index]]\n *\n * @param comparator - A function that compares two elements\n * @param options - An optional object containing limit and offset properties\n * @returns A piped operator that orders the elements and limits the number of results\n */\nexport function topKWithIndex<\n KType extends (T extends KeyValue<infer K, infer _V> ? K : never),\n V1Type extends (T extends KeyValue<KType, infer V> ? V : never),\n T,\n>(\n comparator: (a: V1Type, b: V1Type) => number,\n options?: TopKOptions,\n): PipedOperator<T, KeyValue<KType, [V1Type, number]>> {\n const limit = options?.limit ?? Infinity\n const offset = options?.offset ?? 0\n\n return (\n stream: IStreamBuilder<T>,\n ): IStreamBuilder<KeyValue<KType, [V1Type, number]>> => {\n const reduced = stream.pipe(\n reduce<KType, V1Type, [V1Type, number], T>((values) => {\n // `values` is a list of tuples, first element is the value, second is the multiplicity\n const consolidated = new MultiSet(values).consolidate()\n let i = offset\n const sortedValues = consolidated\n .getInner()\n .sort((a, b) => comparator(a[0], b[0]))\n .slice(offset, offset + limit)\n .map(([value, multiplicity]): [[V1Type, number], number] => [\n [value, i++],\n multiplicity,\n ])\n return sortedValues\n }),\n )\n return reduced\n }\n}\n"],"names":[],"mappings":";;AAoBO,SAAS,KAKd,YACA,SACqB;AACrB,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,SAAS,SAAS,UAAU;AAElC,SAAO,CAAC,WAAiD;AACvD,UAAM,UAAU,OAAO;AAAA,MACrB,OAAO,CAAC,WAAW;AAEjB,cAAM,eAAe,IAAI,SAAS,MAAM,EAAE,YAAA;AAC1C,cAAM,eAAe,aAClB,SAAA,EACA,KAAK,CAAC,GAAG,MAAM,WAAW,EAAE,CAAC,GAAa,EAAE,CAAC,CAAW,CAAC;AAC5D,eAAO,aAAa,MAAM,QAAQ,SAAS,KAAK;AAAA,MAClD,CAAC;AAAA,IAAA;AAEH,WAAO;AAAA,EACT;AACF;AAcO,SAAS,cAKd,YACA,SACqD;AACrD,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,SAAS,SAAS,UAAU;AAElC,SAAO,CACL,WACsD;AACtD,UAAM,UAAU,OAAO;AAAA,MACrB,OAA2C,CAAC,WAAW;AAErD,cAAM,eAAe,IAAI,SAAS,MAAM,EAAE,YAAA;AAC1C,YAAI,IAAI;AACR,cAAM,eAAe,aAClB,SAAA,EACA,KAAK,CAAC,GAAG,MAAM,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EACrC,MAAM,QAAQ,SAAS,KAAK,EAC5B,IAAI,CAAC,CAAC,OAAO,YAAY,MAAkC;AAAA,UAC1D,CAAC,OAAO,GAAG;AAAA,UACX;AAAA,QAAA,CACD;AACH,eAAO;AAAA,MACT,CAAC;AAAA,IAAA;AAEH,WAAO;AAAA,EACT;AACF;"}
@@ -1,5 +1,5 @@
1
1
  import { generateKeyBetween } from "fractional-indexing";
2
- import { compareKeys, diffHalfOpen, binarySearch } from "../utils.js";
2
+ import { compareKeys, binarySearch, diffHalfOpen } from "../utils.js";
3
3
  function indexedValue(value, index) {
4
4
  return [value, index];
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/db-ivm",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Incremental View Maintenance for TanStack DB based on Differential Dataflow",
5
5
  "author": "Sam Willis",
6
6
  "license": "MIT",
@@ -57,7 +57,7 @@
57
57
  "dev": "vite build --watch",
58
58
  "lint": "eslint . --fix",
59
59
  "test": "vitest --run",
60
- "test:incrementalization": "vitest --run tests/incrementalization-law.property.test.ts",
60
+ "test:incrementalization": "vitest --run tests/incrementalization-law-oracle.property.test.ts",
61
61
  "typecheck": "tsc --noEmit",
62
62
  "bench:hash": "vitest bench --run tests/hash.bench.ts --coverage.enabled=false"
63
63
  }
@@ -29,8 +29,8 @@ export class CountOperator<K, V> extends ReduceOperator<K, V, number> {
29
29
  * Counts the number of elements by key (version-free)
30
30
  */
31
31
  export function count<
32
- KType extends T extends KeyValue<infer K, infer _V> ? K : never,
33
- VType extends T extends KeyValue<KType, infer V> ? V : never,
32
+ KType extends (T extends KeyValue<infer K, infer _V> ? K : never),
33
+ VType extends (T extends KeyValue<KType, infer V> ? V : never),
34
34
  T,
35
35
  >() {
36
36
  return (
@@ -12,7 +12,7 @@ import type { IStreamBuilder, KeyValue, PipedOperator } from '../types.js'
12
12
  */
13
13
  export function filterBy<
14
14
  K,
15
- V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,
15
+ V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),
16
16
  T,
17
17
  >(
18
18
  other: IStreamBuilder<KeyValue<K, unknown>>,
@@ -16,8 +16,7 @@ type PipedAggregateFunction<T, R> = {
16
16
  }
17
17
 
18
18
  type AggregateFunction<T, R, V = unknown> =
19
- | BasicAggregateFunction<T, R, V>
20
- | PipedAggregateFunction<T, R>
19
+ BasicAggregateFunction<T, R, V> | PipedAggregateFunction<T, R>
21
20
 
22
21
  type ExtractAggregateReturnType<T, A> =
23
22
  A extends AggregateFunction<T, infer R, any> ? R : never
@@ -254,7 +254,7 @@ export class JoinOperator<K, V1, V2> extends BinaryOperator<
254
254
  */
255
255
  export function join<
256
256
  K,
257
- V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,
257
+ V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),
258
258
  V2,
259
259
  T,
260
260
  >(
@@ -289,7 +289,7 @@ export function join<
289
289
  */
290
290
  export function innerJoin<
291
291
  K,
292
- V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,
292
+ V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),
293
293
  V2,
294
294
  T,
295
295
  >(
@@ -307,7 +307,7 @@ export function innerJoin<
307
307
  */
308
308
  export function antiJoin<
309
309
  K,
310
- V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,
310
+ V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),
311
311
  V2,
312
312
  T,
313
313
  >(
@@ -325,7 +325,7 @@ export function antiJoin<
325
325
  */
326
326
  export function leftJoin<
327
327
  K,
328
- V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,
328
+ V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),
329
329
  V2,
330
330
  T,
331
331
  >(
@@ -343,7 +343,7 @@ export function leftJoin<
343
343
  */
344
344
  export function rightJoin<
345
345
  K,
346
- V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,
346
+ V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),
347
347
  V2,
348
348
  T,
349
349
  >(
@@ -361,7 +361,7 @@ export function rightJoin<
361
361
  */
362
362
  export function fullJoin<
363
363
  K,
364
- V1 extends T extends KeyValue<infer _KT, infer VT> ? VT : never,
364
+ V1 extends (T extends KeyValue<infer _KT, infer VT> ? VT : never),
365
365
  V2,
366
366
  T,
367
367
  >(
@@ -101,8 +101,8 @@ export class ReduceOperator<K, V1, V2> extends UnaryOperator<[K, V1], [K, V2]> {
101
101
  * Reduces the elements in the stream by key (version-free)
102
102
  */
103
103
  export function reduce<
104
- KType extends T extends KeyValue<infer K, infer _V> ? K : never,
105
- V1Type extends T extends KeyValue<KType, infer V> ? V : never,
104
+ KType extends (T extends KeyValue<infer K, infer _V> ? K : never),
105
+ V1Type extends (T extends KeyValue<KType, infer V> ? V : never),
106
106
  R,
107
107
  T,
108
108
  >(f: (values: Array<[V1Type, number]>) => Array<[R, number]>) {
@@ -19,8 +19,8 @@ interface TopKOptions {
19
19
  * @returns A piped operator that limits the number of results
20
20
  */
21
21
  export function topK<
22
- KType extends T extends KeyValue<infer K, infer _V> ? K : never,
23
- V1Type extends T extends KeyValue<KType, infer V> ? V : never,
22
+ KType extends (T extends KeyValue<infer K, infer _V> ? K : never),
23
+ V1Type extends (T extends KeyValue<KType, infer V> ? V : never),
24
24
  T,
25
25
  >(
26
26
  comparator: (a: V1Type, b: V1Type) => number,
@@ -57,8 +57,8 @@ export function topK<
57
57
  * @returns A piped operator that orders the elements and limits the number of results
58
58
  */
59
59
  export function topKWithIndex<
60
- KType extends T extends KeyValue<infer K, infer _V> ? K : never,
61
- V1Type extends T extends KeyValue<KType, infer V> ? V : never,
60
+ KType extends (T extends KeyValue<infer K, infer _V> ? K : never),
61
+ V1Type extends (T extends KeyValue<KType, infer V> ? V : never),
62
62
  T,
63
63
  >(
64
64
  comparator: (a: V1Type, b: V1Type) => number,