@tanstack/db-ivm 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,48 @@
1
+ import { DifferenceStreamWriter, LinearUnaryOperator } from "../graph.js"
2
+ import { StreamBuilder } from "../d2.js"
3
+ import type { IStreamBuilder, PipedOperator } from "../types.js"
4
+ import type { DifferenceStreamReader } from "../graph.js"
5
+ import type { MultiSet } from "../multiset.js"
6
+
7
+ /**
8
+ * Operator that applies a function to each element in the input stream
9
+ */
10
+ export class MapOperator<T, U> extends LinearUnaryOperator<T, U> {
11
+ #f: (data: T) => U
12
+
13
+ constructor(
14
+ id: number,
15
+ inputA: DifferenceStreamReader<T>,
16
+ output: DifferenceStreamWriter<U>,
17
+ f: (data: T) => U
18
+ ) {
19
+ super(id, inputA, output)
20
+ this.#f = f
21
+ }
22
+
23
+ inner(collection: MultiSet<T>): MultiSet<U> {
24
+ return collection.map(this.#f)
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Applies a function to each element in the input stream
30
+ * @param f - The function to apply to each element
31
+ */
32
+ export function map<T, O>(f: (data: T) => O): PipedOperator<T, O> {
33
+ return (stream: IStreamBuilder<T>): IStreamBuilder<O> => {
34
+ const output = new StreamBuilder<O>(
35
+ stream.graph,
36
+ new DifferenceStreamWriter<O>()
37
+ )
38
+ const operator = new MapOperator<T, O>(
39
+ stream.graph.getNextOperatorId(),
40
+ stream.connectReader(),
41
+ output.writer,
42
+ f
43
+ )
44
+ stream.graph.addOperator(operator)
45
+ stream.graph.addStream(output.connectReader())
46
+ return output
47
+ }
48
+ }
@@ -0,0 +1,33 @@
1
+ import { DifferenceStreamWriter, LinearUnaryOperator } from "../graph.js"
2
+ import { StreamBuilder } from "../d2.js"
3
+ import type { IStreamBuilder, PipedOperator } from "../types.js"
4
+ import type { MultiSet } from "../multiset.js"
5
+
6
+ /**
7
+ * Operator that negates the multiplicities in the input stream
8
+ */
9
+ export class NegateOperator<T> extends LinearUnaryOperator<T, T> {
10
+ inner(collection: MultiSet<T>): MultiSet<T> {
11
+ return collection.negate()
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Negates the multiplicities in the input stream
17
+ */
18
+ export function negate<T>(): PipedOperator<T, T> {
19
+ return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
20
+ const output = new StreamBuilder<T>(
21
+ stream.graph,
22
+ new DifferenceStreamWriter<T>()
23
+ )
24
+ const operator = new NegateOperator<T>(
25
+ stream.graph.getNextOperatorId(),
26
+ stream.connectReader(),
27
+ output.writer
28
+ )
29
+ stream.graph.addOperator(operator)
30
+ stream.graph.addStream(output.connectReader())
31
+ return output
32
+ }
33
+ }
@@ -0,0 +1,213 @@
1
+ import { topK, topKWithIndex } from "./topK.js"
2
+ import { topKWithFractionalIndex } from "./topKWithFractionalIndex.js"
3
+ import { map } from "./map.js"
4
+ import { innerJoin } from "./join.js"
5
+ import { consolidate } from "./consolidate.js"
6
+ import type { KeyValue } from "../types.js"
7
+ import type { IStreamBuilder } from "../types"
8
+
9
+ export interface OrderByOptions<Ve> {
10
+ comparator?: (a: Ve, b: Ve) => number
11
+ limit?: number
12
+ offset?: number
13
+ }
14
+
15
+ /**
16
+ * Orders the elements and limits the number of results, with optional offset
17
+ * This requires a keyed stream, and uses the `topK` operator to order all the elements.
18
+ *
19
+ * @param valueExtractor - A function that extracts the value to order by from the element
20
+ * @param options - An optional object containing comparator, limit and offset properties
21
+ * @returns A piped operator that orders the elements and limits the number of results
22
+ */
23
+ export function orderBy<T extends KeyValue<unknown, unknown>, Ve = unknown>(
24
+ valueExtractor: (
25
+ value: T extends KeyValue<unknown, infer V> ? V : never
26
+ ) => Ve,
27
+ options?: OrderByOptions<Ve>
28
+ ) {
29
+ const limit = options?.limit ?? Infinity
30
+ const offset = options?.offset ?? 0
31
+ const comparator =
32
+ options?.comparator ??
33
+ ((a, b) => {
34
+ // Default to JS like ordering
35
+ if (a === b) return 0
36
+ if (a < b) return -1
37
+ return 1
38
+ })
39
+
40
+ return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
41
+ type KeyType = T extends KeyValue<infer K, unknown> ? K : never
42
+
43
+ return stream.pipe(
44
+ map(
45
+ ([key, value]) =>
46
+ [
47
+ null,
48
+ [
49
+ key,
50
+ valueExtractor(
51
+ value as T extends KeyValue<unknown, infer V> ? V : never
52
+ ),
53
+ ],
54
+ ] as KeyValue<null, [KeyType, Ve]>
55
+ ),
56
+ topK((a, b) => comparator(a[1], b[1]), { limit, offset }),
57
+ map(([_, [key]]) => [key, null] as KeyValue<KeyType, null>),
58
+ innerJoin(stream),
59
+ map(([key, value]) => {
60
+ return [key, value[1]] as T
61
+ }),
62
+ consolidate()
63
+ )
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Orders the elements and limits the number of results, with optional offset and
69
+ * annotates the value with the index.
70
+ * This requires a keyed stream, and uses the `topKWithIndex` operator to order all the elements.
71
+ *
72
+ * @param valueExtractor - A function that extracts the value to order by from the element
73
+ * @param options - An optional object containing comparator, limit and offset properties
74
+ * @returns A piped operator that orders the elements and limits the number of results
75
+ */
76
+ export function orderByWithIndex<
77
+ T extends KeyValue<unknown, unknown>,
78
+ Ve = unknown,
79
+ >(
80
+ valueExtractor: (
81
+ value: T extends KeyValue<unknown, infer V> ? V : never
82
+ ) => Ve,
83
+ options?: OrderByOptions<Ve>
84
+ ) {
85
+ const limit = options?.limit ?? Infinity
86
+ const offset = options?.offset ?? 0
87
+ const comparator =
88
+ options?.comparator ??
89
+ ((a, b) => {
90
+ // Default to JS like ordering
91
+ if (a === b) return 0
92
+ if (a < b) return -1
93
+ return 1
94
+ })
95
+
96
+ return (
97
+ stream: IStreamBuilder<T>
98
+ ): IStreamBuilder<
99
+ KeyValue<
100
+ T extends KeyValue<infer K, unknown> ? K : never,
101
+ [T extends KeyValue<unknown, infer V> ? V : never, number]
102
+ >
103
+ > => {
104
+ type KeyType = T extends KeyValue<infer K, unknown> ? K : never
105
+ type ValueType = T extends KeyValue<unknown, infer V> ? V : never
106
+
107
+ return stream.pipe(
108
+ map(
109
+ ([key, value]) =>
110
+ [
111
+ null,
112
+ [
113
+ key,
114
+ valueExtractor(
115
+ value as T extends KeyValue<unknown, infer V> ? V : never
116
+ ),
117
+ ],
118
+ ] as KeyValue<null, [KeyType, Ve]>
119
+ ),
120
+ topKWithIndex((a, b) => comparator(a[1], b[1]), { limit, offset }),
121
+ map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, number>),
122
+ innerJoin(stream),
123
+ map(([key, [index, value]]) => {
124
+ return [key, [value, index]] as KeyValue<KeyType, [ValueType, number]>
125
+ }),
126
+ consolidate()
127
+ )
128
+ }
129
+ }
130
+
131
+ export function orderByWithFractionalIndexBase<
132
+ T extends KeyValue<unknown, unknown>,
133
+ Ve = unknown,
134
+ >(
135
+ topKFunction: typeof topKWithFractionalIndex,
136
+ valueExtractor: (
137
+ value: T extends KeyValue<unknown, infer V> ? V : never
138
+ ) => Ve,
139
+ options?: OrderByOptions<Ve>
140
+ ) {
141
+ const limit = options?.limit ?? Infinity
142
+ const offset = options?.offset ?? 0
143
+ const comparator =
144
+ options?.comparator ??
145
+ ((a, b) => {
146
+ // Default to JS like ordering
147
+ if (a === b) return 0
148
+ if (a < b) return -1
149
+ return 1
150
+ })
151
+
152
+ return (
153
+ stream: IStreamBuilder<T>
154
+ ): IStreamBuilder<
155
+ KeyValue<
156
+ T extends KeyValue<infer K, unknown> ? K : never,
157
+ [T extends KeyValue<unknown, infer V> ? V : never, string]
158
+ >
159
+ > => {
160
+ type KeyType = T extends KeyValue<infer K, unknown> ? K : never
161
+ type ValueType = T extends KeyValue<unknown, infer V> ? V : never
162
+
163
+ return stream.pipe(
164
+ map(
165
+ ([key, value]) =>
166
+ [
167
+ null,
168
+ [
169
+ key,
170
+ valueExtractor(
171
+ value as T extends KeyValue<unknown, infer V> ? V : never
172
+ ),
173
+ ],
174
+ ] as KeyValue<null, [KeyType, Ve]>
175
+ ),
176
+ topKFunction((a, b) => comparator(a[1], b[1]), {
177
+ limit,
178
+ offset,
179
+ }),
180
+ map(([_, [[key], index]]) => [key, index] as KeyValue<KeyType, string>),
181
+ innerJoin(stream),
182
+ map(([key, [index, value]]) => {
183
+ return [key, [value, index]] as KeyValue<KeyType, [ValueType, string]>
184
+ }),
185
+ consolidate()
186
+ )
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Orders the elements and limits the number of results, with optional offset and
192
+ * annotates the value with a fractional index.
193
+ * This requires a keyed stream, and uses the `topKWithFractionalIndex` operator to order all the elements.
194
+ *
195
+ * @param valueExtractor - A function that extracts the value to order by from the element
196
+ * @param options - An optional object containing comparator, limit and offset properties
197
+ * @returns A piped operator that orders the elements and limits the number of results
198
+ */
199
+ export function orderByWithFractionalIndex<
200
+ T extends KeyValue<unknown, unknown>,
201
+ Ve = unknown,
202
+ >(
203
+ valueExtractor: (
204
+ value: T extends KeyValue<unknown, infer V> ? V : never
205
+ ) => Ve,
206
+ options?: OrderByOptions<Ve>
207
+ ) {
208
+ return orderByWithFractionalIndexBase(
209
+ topKWithFractionalIndex,
210
+ valueExtractor,
211
+ options
212
+ )
213
+ }
@@ -0,0 +1,20 @@
1
+ import { orderByWithFractionalIndexBase } from "./orderBy.js"
2
+ import { topKWithFractionalIndexBTree } from "./topKWithFractionalIndexBTree.js"
3
+ import type { KeyValue } from "../types.js"
4
+ import type { OrderByOptions } from "./orderBy.js"
5
+
6
+ export function orderByWithFractionalIndexBTree<
7
+ T extends KeyValue<unknown, unknown>,
8
+ Ve = unknown,
9
+ >(
10
+ valueExtractor: (
11
+ value: T extends KeyValue<unknown, infer V> ? V : never
12
+ ) => Ve,
13
+ options?: OrderByOptions<Ve>
14
+ ) {
15
+ return orderByWithFractionalIndexBase(
16
+ topKWithFractionalIndexBTree,
17
+ valueExtractor,
18
+ options
19
+ )
20
+ }
@@ -0,0 +1,53 @@
1
+ import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
2
+ import { StreamBuilder } from "../d2.js"
3
+ import type { IStreamBuilder, PipedOperator } from "../types.js"
4
+ import type { DifferenceStreamReader } from "../graph.js"
5
+ import type { MultiSet } from "../multiset.js"
6
+
7
+ /**
8
+ * Operator that outputs the messages in the stream
9
+ */
10
+ export class OutputOperator<T> extends UnaryOperator<T> {
11
+ #fn: (data: MultiSet<T>) => void
12
+
13
+ constructor(
14
+ id: number,
15
+ inputA: DifferenceStreamReader<T>,
16
+ outputWriter: DifferenceStreamWriter<T>,
17
+ fn: (data: MultiSet<T>) => void
18
+ ) {
19
+ super(id, inputA, outputWriter)
20
+ this.#fn = fn
21
+ }
22
+
23
+ run(): void {
24
+ for (const message of this.inputMessages()) {
25
+ this.#fn(message)
26
+ this.output.sendData(message)
27
+ }
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Outputs the messages in the stream
33
+ * @param fn - The function to call with each message
34
+ */
35
+ export function output<T>(
36
+ fn: (data: MultiSet<T>) => void
37
+ ): PipedOperator<T, T> {
38
+ return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
39
+ const outputStream = new StreamBuilder<T>(
40
+ stream.graph,
41
+ new DifferenceStreamWriter<T>()
42
+ )
43
+ const operator = new OutputOperator<T>(
44
+ stream.graph.getNextOperatorId(),
45
+ stream.connectReader(),
46
+ outputStream.writer,
47
+ fn
48
+ )
49
+ stream.graph.addOperator(operator)
50
+ stream.graph.addStream(outputStream.connectReader())
51
+ return outputStream
52
+ }
53
+ }
@@ -0,0 +1,55 @@
1
+ import type { IStreamBuilder, PipedOperator } from "../types.js"
2
+
3
+ // Don't judge, this is the only way to type this function.
4
+ // rxjs has very similar code to type its pipe function
5
+ // https://github.com/ReactiveX/rxjs/blob/master/packages/rxjs/src/internal/util/pipe.ts
6
+ // We go to 20 operators deep, because surely that's enough for anyone...
7
+ // A user can always split the pipe into multiple pipes to get around this.
8
+ export function pipe<T, O>(o1: PipedOperator<T, O>): PipedOperator<T, O>
9
+ // prettier-ignore
10
+ export function pipe<T, T2, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, O>): PipedOperator<T, O>
11
+ // prettier-ignore
12
+ export function pipe<T, T2, T3, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, O>): PipedOperator<T, O>
13
+ // prettier-ignore
14
+ export function pipe<T, T2, T3, T4, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, O>): PipedOperator<T, O>
15
+ // prettier-ignore
16
+ export function pipe<T, T2, T3, T4, T5, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, O>): PipedOperator<T, O>
17
+ // prettier-ignore
18
+ export function pipe<T, T2, T3, T4, T5, T6, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, O>): PipedOperator<T, O>
19
+ // prettier-ignore
20
+ export function pipe<T, T2, T3, T4, T5, T6, T7, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, O>): PipedOperator<T, O>
21
+ // prettier-ignore
22
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, O>): PipedOperator<T, O>
23
+ // prettier-ignore
24
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, O>): PipedOperator<T, O>
25
+ // prettier-ignore
26
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, O>): PipedOperator<T, O>
27
+ // prettier-ignore
28
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, O>): PipedOperator<T, O>
29
+ // prettier-ignore
30
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, O>): PipedOperator<T, O>
31
+ // prettier-ignore
32
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, O>): PipedOperator<T, O>
33
+ // prettier-ignore
34
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, O>): PipedOperator<T, O>
35
+ // prettier-ignore
36
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, O>): PipedOperator<T, O>
37
+ // prettier-ignore
38
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, O>): PipedOperator<T, O>
39
+ // prettier-ignore
40
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, O>): PipedOperator<T, O>
41
+ // prettier-ignore
42
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, O>): PipedOperator<T, O>
43
+ // prettier-ignore
44
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, T19>, o19: PipedOperator<T19, O>): PipedOperator<T, O>
45
+ // prettier-ignore
46
+ export function pipe<T, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, O>(o1: PipedOperator<T, T2>, o2: PipedOperator<T2, T3>, o3: PipedOperator<T3, T4>, o4: PipedOperator<T4, T5>, o5: PipedOperator<T5, T6>, o6: PipedOperator<T6, T7>, o7: PipedOperator<T7, T8>, o8: PipedOperator<T8, T9>, o9: PipedOperator<T9, T10>, o10: PipedOperator<T10, T11>, o11: PipedOperator<T11, T12>, o12: PipedOperator<T12, T13>, o13: PipedOperator<T13, T14>, o14: PipedOperator<T14, T15>, o15: PipedOperator<T15, T16>, o16: PipedOperator<T16, T17>, o17: PipedOperator<T17, T18>, o18: PipedOperator<T18, T19>, o19: PipedOperator<T19, T20>, o20: PipedOperator<T20, O>): PipedOperator<T, O>
47
+
48
+ /**
49
+ * Creates a new stream by piping the input stream through a series of operators
50
+ */
51
+ export function pipe<T>(...operators: Array<PipedOperator<any, any>>) {
52
+ return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
53
+ return stream.pipe(...operators)
54
+ }
55
+ }
@@ -0,0 +1,124 @@
1
+ import { DifferenceStreamWriter, UnaryOperator } from "../graph.js"
2
+ import { StreamBuilder } from "../d2.js"
3
+ import { MultiSet } from "../multiset.js"
4
+ import { Index } from "../indexes.js"
5
+ import type { DifferenceStreamReader } from "../graph.js"
6
+ import type { IStreamBuilder, KeyValue } from "../types.js"
7
+
8
+ /**
9
+ * Base operator for reduction operations (version-free)
10
+ */
11
+ export class ReduceOperator<K, V1, V2> extends UnaryOperator<[K, V1], [K, V2]> {
12
+ #index = new Index<K, V1>()
13
+ #indexOut = new Index<K, V2>()
14
+ #f: (values: Array<[V1, number]>) => Array<[V2, number]>
15
+
16
+ constructor(
17
+ id: number,
18
+ inputA: DifferenceStreamReader<[K, V1]>,
19
+ output: DifferenceStreamWriter<[K, V2]>,
20
+ f: (values: Array<[V1, number]>) => Array<[V2, number]>
21
+ ) {
22
+ super(id, inputA, output)
23
+ this.#f = f
24
+ }
25
+
26
+ run(): void {
27
+ // Collect all input messages and update the index
28
+ const keysTodo = new Set<K>()
29
+ for (const message of this.inputMessages()) {
30
+ for (const [item, multiplicity] of message.getInner()) {
31
+ const [key, value] = item
32
+ this.#index.addValue(key, [value, multiplicity])
33
+ keysTodo.add(key)
34
+ }
35
+ }
36
+
37
+ // For each key, compute the reduction and delta
38
+ const result: Array<[[K, V2], number]> = []
39
+ for (const key of keysTodo) {
40
+ const curr = this.#index.get(key)
41
+ const currOut = this.#indexOut.get(key)
42
+ const out = this.#f(curr)
43
+
44
+ // Create maps for current and previous outputs using values directly as keys
45
+ const newOutputMap = new Map<V2, number>()
46
+ const oldOutputMap = new Map<V2, number>()
47
+
48
+ // Process new output
49
+ for (const [value, multiplicity] of out) {
50
+ const existing = newOutputMap.get(value) ?? 0
51
+ newOutputMap.set(value, existing + multiplicity)
52
+ }
53
+
54
+ // Process previous output
55
+ for (const [value, multiplicity] of currOut) {
56
+ const existing = oldOutputMap.get(value) ?? 0
57
+ oldOutputMap.set(value, existing + multiplicity)
58
+ }
59
+
60
+ // First, emit removals for old values that are no longer present
61
+ for (const [value, multiplicity] of oldOutputMap) {
62
+ if (!newOutputMap.has(value)) {
63
+ // Remove the old value entirely
64
+ result.push([[key, value], -multiplicity])
65
+ this.#indexOut.addValue(key, [value, -multiplicity])
66
+ }
67
+ }
68
+
69
+ // Then, emit additions for new values that are not present in old
70
+ for (const [value, multiplicity] of newOutputMap) {
71
+ if (!oldOutputMap.has(value)) {
72
+ // Add the new value only if it has non-zero multiplicity
73
+ if (multiplicity !== 0) {
74
+ result.push([[key, value], multiplicity])
75
+ this.#indexOut.addValue(key, [value, multiplicity])
76
+ }
77
+ }
78
+ }
79
+
80
+ // Finally, emit multiplicity changes for values that were present and are still present
81
+ for (const [value, newMultiplicity] of newOutputMap) {
82
+ const oldMultiplicity = oldOutputMap.get(value)
83
+ if (oldMultiplicity !== undefined) {
84
+ const delta = newMultiplicity - oldMultiplicity
85
+ // Only emit actual changes, i.e. non-zero deltas
86
+ if (delta !== 0) {
87
+ result.push([[key, value], delta])
88
+ this.#indexOut.addValue(key, [value, delta])
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ if (result.length > 0) {
95
+ this.output.sendData(new MultiSet(result))
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Reduces the elements in the stream by key (version-free)
102
+ */
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,
106
+ R,
107
+ T,
108
+ >(f: (values: Array<[V1Type, number]>) => Array<[R, number]>) {
109
+ return (stream: IStreamBuilder<T>): IStreamBuilder<KeyValue<KType, R>> => {
110
+ const output = new StreamBuilder<KeyValue<KType, R>>(
111
+ stream.graph,
112
+ new DifferenceStreamWriter<KeyValue<KType, R>>()
113
+ )
114
+ const operator = new ReduceOperator<KType, V1Type, R>(
115
+ stream.graph.getNextOperatorId(),
116
+ stream.connectReader() as DifferenceStreamReader<KeyValue<KType, V1Type>>,
117
+ output.writer,
118
+ f
119
+ )
120
+ stream.graph.addOperator(operator)
121
+ stream.graph.addStream(output.connectReader())
122
+ return output
123
+ }
124
+ }
@@ -0,0 +1,92 @@
1
+ import { MultiSet } from "../multiset.js"
2
+ import { reduce } from "./reduce.js"
3
+ import type { IStreamBuilder, PipedOperator } from "../types"
4
+ import type { KeyValue } from "../types.js"
5
+
6
+ interface TopKOptions {
7
+ limit?: number
8
+ offset?: number
9
+ }
10
+
11
+ /**
12
+ * Limits the number of results based on a comparator, with optional offset.
13
+ * This works on a keyed stream, where the key is the first element of the tuple
14
+ * The ordering is withing a key group, i.e. elements are sorted within a key group
15
+ * and the limit + offset is applied to that sorted group.
16
+ * To order the entire stream, key by the same value for all elements such as null.
17
+ *
18
+ * @param comparator - A function that compares two elements
19
+ * @param options - An optional object containing limit and offset properties
20
+ * @returns A piped operator that limits the number of results
21
+ */
22
+ export function topK<
23
+ KType extends T extends KeyValue<infer K, infer _V> ? K : never,
24
+ V1Type extends T extends KeyValue<KType, infer V> ? V : never,
25
+ T,
26
+ >(
27
+ comparator: (a: V1Type, b: V1Type) => number,
28
+ options?: TopKOptions
29
+ ): PipedOperator<T, T> {
30
+ const limit = options?.limit ?? Infinity
31
+ const offset = options?.offset ?? 0
32
+
33
+ return (stream: IStreamBuilder<T>): IStreamBuilder<T> => {
34
+ const reduced = stream.pipe(
35
+ reduce((values) => {
36
+ // `values` is a list of tuples, first element is the value, second is the multiplicity
37
+ const consolidated = new MultiSet(values).consolidate()
38
+ const sortedValues = consolidated
39
+ .getInner()
40
+ .sort((a, b) => comparator(a[0] as V1Type, b[0] as V1Type))
41
+ return sortedValues.slice(offset, offset + limit)
42
+ })
43
+ )
44
+ return reduced as IStreamBuilder<T>
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Limits the number of results based on a comparator, with optional offset.
50
+ * This works on a keyed stream, where the key is the first element of the tuple
51
+ * The ordering is withing a key group, i.e. elements are sorted within a key group
52
+ * and the limit + offset is applied to that sorted group.
53
+ * To order the entire stream, key by the same value for all elements such as null.
54
+ * Adds the index of the element to the result as [key, [value, index]]
55
+ *
56
+ * @param comparator - A function that compares two elements
57
+ * @param options - An optional object containing limit and offset properties
58
+ * @returns A piped operator that orders the elements and limits the number of results
59
+ */
60
+ export function topKWithIndex<
61
+ KType extends T extends KeyValue<infer K, infer _V> ? K : never,
62
+ V1Type extends T extends KeyValue<KType, infer V> ? V : never,
63
+ T,
64
+ >(
65
+ comparator: (a: V1Type, b: V1Type) => number,
66
+ options?: TopKOptions
67
+ ): PipedOperator<T, KeyValue<KType, [V1Type, number]>> {
68
+ const limit = options?.limit ?? Infinity
69
+ const offset = options?.offset ?? 0
70
+
71
+ return (
72
+ stream: IStreamBuilder<T>
73
+ ): IStreamBuilder<KeyValue<KType, [V1Type, number]>> => {
74
+ const reduced = stream.pipe(
75
+ reduce<KType, V1Type, [V1Type, number], T>((values) => {
76
+ // `values` is a list of tuples, first element is the value, second is the multiplicity
77
+ const consolidated = new MultiSet(values).consolidate()
78
+ let i = offset
79
+ const sortedValues = consolidated
80
+ .getInner()
81
+ .sort((a, b) => comparator(a[0], b[0]))
82
+ .slice(offset, offset + limit)
83
+ .map(([value, multiplicity]): [[V1Type, number], number] => [
84
+ [value, i++],
85
+ multiplicity,
86
+ ])
87
+ return sortedValues
88
+ })
89
+ )
90
+ return reduced
91
+ }
92
+ }