@rimbu/common 1.1.0 → 2.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.
@@ -1,1157 +0,0 @@
1
- import {
2
- type AsyncCollectFun,
3
- AsyncOptLazy,
4
- CollectFun,
5
- type MaybePromise,
6
- Reducer,
7
- Eq,
8
- } from './internal.mjs';
9
-
10
- /**
11
- * An `AsyncReducer` is a stand-alone asynchronous calculation that takes input values of type I,
12
- * and, when requested, produces an output value of type O.
13
- * @typeparam I - the input value type
14
- * @typeparam O - the output value type
15
- */
16
- export type AsyncReducer<I, O = I> = AsyncReducer.Impl<I, O, unknown>;
17
-
18
- function identity<T>(value: T): T {
19
- return value;
20
- }
21
-
22
- export namespace AsyncReducer {
23
- export interface Impl<I, O, S> {
24
- /**
25
- * The initial state value for the reducer algorithm.
26
- */
27
- readonly init: AsyncOptLazy<S>;
28
- /**
29
- * Returns the next state based on the given input values
30
- * @param state - the current state
31
- * @param elem - the current input value
32
- * @param index - the current input index
33
- * @param halt - a function that, when called, ensures no more values are passed to the reducer
34
- */
35
- next(state: S, elem: I, index: number, halt: () => void): MaybePromise<S>;
36
- /**
37
- * Returns the output value based on the given `state`
38
- * @param state - the current state
39
- */
40
- stateToResult(state: S): MaybePromise<O>;
41
- /**
42
- * An optional function that is called when the reducer will no longer receive values.
43
- * @param state - the final reducer state
44
- * @param error - (optional) if an error has occured, it ix passed here
45
- */
46
- onClose?: ((state: S, error?: unknown) => MaybePromise<void>) | undefined;
47
- /**
48
- * Returns an `AsyncReducer` instance that only passes values to the reducer that satisy the given `pred` predicate.
49
- * @param pred - a potaentially asynchronous function that returns true if the value should be passed to the reducer based on the following inputs:<br/>
50
- * - value: the current input value<br/>
51
- * - index: the current input index<br/>
52
- * - halt: function that, when called, ensures no more new values are passed to the reducer
53
- * @example
54
- * ```ts
55
- * AsyncReducer
56
- * .createMono(0, async (c, v) => c + v)
57
- * .filterInput(async v => v > 10)
58
- * // this reducer will only sum values larger than 10
59
- * ```
60
- */
61
- filterInput(
62
- pred: (value: I, index: number, halt: () => void) => MaybePromise<boolean>
63
- ): AsyncReducer<I, O>;
64
- /**
65
- * Returns an `AsyncReducer` instance that converts its input values using given `mapFun` before passing them to the reducer.
66
- * @param mapFun - a potentially asynchronous function that returns a new value to pass to the reducer based on the following inputs:<br/>
67
- * - value: the current input value<br/>
68
- * - index: the current input index
69
- * @example
70
- * ```ts
71
- * AsyncReducer
72
- * .createMono(0, async (c, v) => c + v)
73
- * .mapInput(async v => v * 2)
74
- * // this reducer will double all input values before summing them
75
- * ```
76
- */
77
- mapInput<I2>(
78
- mapFun: (value: I2, index: number) => MaybePromise<I>
79
- ): AsyncReducer<I2, O>;
80
- /**
81
- * Returns an `AsyncReducer` instance that converts or filters its input values using given `collectFun` before passing them to the reducer.
82
- * @param collectFun - a function receiving<br/>
83
- * - `value`: the next value<br/>
84
- * - `index`: the value index<br/>
85
- * - `skip`: a token that, when returned, will not add a value to the resulting collection<br/>
86
- * - `halt`: a function that, when called, ensures no next elements are passed
87
- * @example
88
- * ```ts
89
- * AsyncReducer
90
- * .createMono(0, async (c, v) => c + v)
91
- * .collectInput(async (v, _, skip) => v <= 10 ? skip : v * 2)
92
- * // this reducer will double all input values larger thant 10 before summing them,
93
- * // and will skip all values smaller than 10
94
- * ```
95
- */
96
- collectInput<I2>(collectFun: AsyncCollectFun<I2, I>): AsyncReducer<I2, O>;
97
- /**
98
- * Returns an `AsyncReducer` instance that converts its output values using given `mapFun`.
99
- * @param mapFun - a potentially asynchronous function that takes the current output value and converts it to a new output value
100
- * @example
101
- * ```ts
102
- * AsyncReducer
103
- * .createMono(0, async (c, v) => c + v)
104
- * .mapOutput(async v => String(v))
105
- * // this reducer will convert all its results to string before returning them
106
- * ```
107
- */
108
- mapOutput<O2>(mapFun: (value: O) => O2): AsyncReducer<I, O2>;
109
- mapOutput<O2>(mapFun: (value: O) => Promise<O2>): AsyncReducer<I, O2>;
110
- /**
111
- * Returns an `AsyncReducer` instance that takes at most the given `amount` of input elements, and will ignore subsequent elements.
112
- * @param amount - the amount of elements to accept
113
- * @example
114
- * ```ts
115
- * await AsyncStream
116
- * .from(Stream.range({ end: 10 }))
117
- * .reduce(
118
- * AsyncReducer
119
- * .createMono(0, async (c, v) => c + v)
120
- * .takeInput(2)
121
- * )
122
- * // => 1
123
- * ```
124
- */
125
- takeInput(amount: number): AsyncReducer<I, O>;
126
- /**
127
- * Returns a `Reducer` instance that skips the first given `amount` of input elements, and will process subsequent elements.
128
- * @param amount - the amount of elements to skip
129
- * @example
130
- * ```ts
131
- * await AsyncStream
132
- * .from(Stream.range({ end: 10 }))
133
- * .reduce(
134
- * AsyncReducer
135
- * .createMono(0, async (c, v) => c + v)
136
- * .dropInput(9)
137
- * )
138
- * // => 19
139
- * ```
140
- */
141
- dropInput(amount: number): AsyncReducer<I, O>;
142
- /**
143
- * Returns a `Reducer` instance that takes given `amount` of elements starting at given `from` index, and ignores other elements.
144
- * @param from - (default: 0) the index at which to start processing elements
145
- * @param amount - (optional) the amount of elements to process, if not given, processes all elements from the `from` index
146
- * @example
147
- * ```ts
148
- * await AsyncStream
149
- * .from(Stream.range({ end: 10 }))
150
- * .reduce(
151
- * AsyncReducer
152
- * .createMono(0, async (c, v) => c + v)
153
- * .sliceInput(1, 2)
154
- * )
155
- * // => 3
156
- * ```
157
- */
158
- sliceInput(from?: number, amount?: number): AsyncReducer<I, O>;
159
- }
160
-
161
- /**
162
- * A base class that can be used to easily create `AsyncReducer` instances.
163
- * @typeparam I - the input value type
164
- * @typeparam O - the output value type
165
- * @typeparam S - the internal state type
166
- */
167
- export class Base<I, O, S> implements AsyncReducer.Impl<I, O, S> {
168
- constructor(
169
- readonly init: AsyncOptLazy<S>,
170
- readonly next: (
171
- state: S,
172
- elem: I,
173
- index: number,
174
- halt: () => void
175
- ) => MaybePromise<S>,
176
- readonly stateToResult: (state: S) => MaybePromise<O>,
177
- readonly onClose?: (state: S, error?: unknown) => MaybePromise<void>
178
- ) {}
179
-
180
- filterInput(
181
- pred: (value: I, index: number, halt: () => void) => MaybePromise<boolean>
182
- ): AsyncReducer<I, O> {
183
- return create(
184
- async (): Promise<{ state: S; nextIndex: number }> => ({
185
- nextIndex: 0,
186
- state: await AsyncOptLazy.toMaybePromise(this.init),
187
- }),
188
- async (
189
- state,
190
- elem,
191
- index,
192
- halt
193
- ): Promise<{ state: S; nextIndex: number }> => {
194
- if (pred(elem, index, halt)) {
195
- state.state = await this.next(
196
- state.state,
197
- elem,
198
- state.nextIndex++,
199
- halt
200
- );
201
- }
202
- return state;
203
- },
204
- (state): MaybePromise<O> => this.stateToResult(state.state),
205
- (state, error) => this.onClose?.(state.state, error)
206
- );
207
- }
208
-
209
- mapInput<I2>(
210
- mapFun: (value: I2, index: number) => MaybePromise<I>
211
- ): AsyncReducer<I2, O> {
212
- return create(
213
- this.init,
214
- async (state, elem, index, halt): Promise<S> =>
215
- this.next(state, await mapFun(elem, index), index, halt),
216
- this.stateToResult,
217
- this.onClose
218
- );
219
- }
220
-
221
- collectInput<I2>(collectFun: AsyncCollectFun<I2, I>): AsyncReducer<I2, O> {
222
- return create(
223
- async (): Promise<{ state: S; nextIndex: number }> => ({
224
- nextIndex: 0,
225
- state: await AsyncOptLazy.toMaybePromise(this.init),
226
- }),
227
- async (
228
- state,
229
- elem,
230
- index,
231
- halt
232
- ): Promise<{ state: S; nextIndex: number }> => {
233
- const nextElem = await collectFun(elem, index, CollectFun.Skip, halt);
234
-
235
- if (CollectFun.Skip !== nextElem) {
236
- state.state = await this.next(
237
- state.state,
238
- nextElem,
239
- state.nextIndex++,
240
- halt
241
- );
242
- }
243
-
244
- return state;
245
- },
246
- (state): MaybePromise<O> => this.stateToResult(state.state),
247
- (state, error) => this.onClose?.(state.state, error)
248
- );
249
- }
250
-
251
- mapOutput<O2>(mapFun: (value: O) => MaybePromise<O2>): AsyncReducer<I, O2> {
252
- return create(
253
- this.init,
254
- this.next,
255
- async (state): Promise<O2> => mapFun(await this.stateToResult(state)),
256
- this.onClose
257
- );
258
- }
259
-
260
- takeInput(amount: number): AsyncReducer<I, O> {
261
- if (amount <= 0) {
262
- return create(this.init, identity, this.stateToResult);
263
- }
264
-
265
- return this.filterInput((_, i, halt): boolean => {
266
- const more = i < amount;
267
- if (!more) halt();
268
- return more;
269
- });
270
- }
271
-
272
- dropInput(amount: number): AsyncReducer<I, O> {
273
- if (amount <= 0) {
274
- return this as AsyncReducer<I, O>;
275
- }
276
-
277
- return this.filterInput((_, i): boolean => i >= amount);
278
- }
279
-
280
- sliceInput(from = 0, amount?: number): AsyncReducer<I, O> {
281
- if (undefined === amount) return this.dropInput(from);
282
- if (amount <= 0) return create(this.init, identity, this.stateToResult);
283
- if (from <= 0) return this.takeInput(amount);
284
- return this.takeInput(amount).dropInput(from);
285
- }
286
- }
287
-
288
- /**
289
- * Returns an `AsyncReducer` with the given options:
290
- * @param init - the optionally lazy and/or promised initial state value
291
- * @param next - returns (potentially asynchronously) the next state value based on the given inputs:<br/>
292
- * - current: the current state<br/>
293
- * - next: the current input value<br/>
294
- * - index: the input index value<br/>
295
- * - halt: function that, when called, ensures no more elements are passed to the reducer
296
- * @param stateToResult - a potentially asynchronous function that converts the current state to an output value
297
- * @param onClose - (optional) a function that will be called when the reducer will no longer receive values
298
- * @typeparam I - the input value type
299
- * @typeparam O - the output value type
300
- * @typeparam S - the internal state type
301
- */
302
- export function create<I, O = I, S = O>(
303
- init: AsyncOptLazy<S>,
304
- next: (
305
- current: S,
306
- next: I,
307
- index: number,
308
- halt: () => void
309
- ) => MaybePromise<S>,
310
- stateToResult: (state: S) => MaybePromise<O>,
311
- onClose?: (state: S, error?: unknown) => MaybePromise<void>
312
- ): AsyncReducer<I, O> {
313
- return new AsyncReducer.Base(
314
- init,
315
- next,
316
- stateToResult,
317
- onClose
318
- ) as AsyncReducer<I, O>;
319
- }
320
-
321
- /**
322
- * Returns an `AsyncReducer` of which the input, state, and output types are the same.
323
- * @param init - the optionally lazy and/or promised initial state value
324
- * @param next - returns (potentially asynchronously) the next state value based on the given inputs:<br/>
325
- * - current: the current state<br/>
326
- * - next: the current input value<br/>
327
- * - index: the input index value<br/>
328
- * - halt: function that, when called, ensures no more elements are passed to the reducer
329
- * @param stateToResult - a potentially asynchronous function that converts the current state to an output value
330
- * @param onClose - (optional) a function that will be called when the reducer will no longer receive values
331
- * @typeparam I - the input value type
332
- * @typeparam O - the output value type
333
- * @typeparam S - the internal state type
334
- */
335
- export function createMono<T>(
336
- init: AsyncOptLazy<T>,
337
- next: (
338
- current: T,
339
- next: T,
340
- index: number,
341
- halt: () => void
342
- ) => MaybePromise<T>,
343
- stateToResult?: (state: T) => MaybePromise<T>,
344
- onClose?: (state: T, error?: unknown) => MaybePromise<void>
345
- ): AsyncReducer<T> {
346
- return create(init, next, stateToResult ?? identity, onClose);
347
- }
348
-
349
- /**
350
- * Returns an `AsyncReducer` of which the state and output types are the same.
351
- * @param init - the optionally lazy and/or promised initial state value
352
- * @param next - returns (potentially asynchronously) the next state value based on the given inputs:<br/>
353
- * - current: the current state<br/>
354
- * - next: the current input value<br/>
355
- * - index: the input index value<br/>
356
- * - halt: function that, when called, ensures no more elements are passed to the reducer
357
- * @param stateToResult - a potentially asynchronous function that converts the current state to an output value
358
- * @param onClose - (optional) a function that will be called when the reducer will no longer receive values
359
- * @typeparam I - the input value type
360
- * @typeparam O - the output value type
361
- * @typeparam S - the internal state type
362
- */
363
- export function createOutput<I, O = I>(
364
- init: AsyncOptLazy<O>,
365
- next: (
366
- current: O,
367
- next: I,
368
- index: number,
369
- halt: () => void
370
- ) => MaybePromise<O>,
371
- stateToResult?: (state: O) => MaybePromise<O>,
372
- onClose?: (state: O, error?: unknown) => MaybePromise<void>
373
- ): AsyncReducer<I, O> {
374
- return create(init, next, stateToResult ?? identity, onClose);
375
- }
376
-
377
- export function from<I, O>(reducer: Reducer<I, O>): AsyncReducer<I, O> {
378
- return AsyncReducer.create(
379
- reducer.init,
380
- reducer.next,
381
- reducer.stateToResult
382
- );
383
- }
384
-
385
- /**
386
- * A `Reducer` that sums all given numeric input values.
387
- * @example
388
- * ```ts
389
- * console.log(Stream.range({ amount: 5 }).reduce(Reducer.sum))
390
- * // => 10
391
- * ```
392
- */
393
- export const sum = createMono(0, (state, next): number => state + next);
394
-
395
- /**
396
- * A `Reducer` that calculates the product of all given numeric input values.
397
- * @example
398
- * ```ts
399
- * console.log(Stream.range({ start: 1, amount: 5 }).reduce(product))
400
- * // => 120
401
- * ```
402
- */
403
- export const product = createMono(1, (state, next, _, halt): number => {
404
- if (0 === next) halt();
405
- return state * next;
406
- });
407
-
408
- /**
409
- * A `Reducer` that calculates the average of all given numberic input values.
410
- * @example
411
- * ```ts
412
- * console.log(Stream.range({ amount: 5 }).reduce(Reducer.average));
413
- * // => 2
414
- * ```
415
- */
416
- export const average = createMono(
417
- 0,
418
- (avg, value, index): number => avg + (value - avg) / (index + 1)
419
- );
420
-
421
- /**
422
- * Returns a `Reducer` that remembers the minimum value of the inputs using the given `compFun` to compare input values
423
- * @param compFun - a comparison function for two input values, returning 0 when equal, positive when greater, negetive when smaller
424
- * @param otherwise - (default: undefineds) a fallback value when there were no input values given
425
- * @example
426
- * ```ts
427
- * const stream = Stream.of('abc', 'a', 'abcde', 'ab')
428
- * console.log(stream.minBy((s1, s2) => s1.length - s2.length))
429
- * // 'a'
430
- * ```
431
- */
432
- export const minBy: {
433
- <T>(compFun: (v1: T, v2: T) => MaybePromise<number>): AsyncReducer<
434
- T,
435
- T | undefined
436
- >;
437
- <T, O>(
438
- compFun: (v1: T, v2: T) => MaybePromise<number>,
439
- otherwise: AsyncOptLazy<O>
440
- ): AsyncReducer<T, T | O>;
441
- } = <T, O>(
442
- compFun: (v1: T, v2: T) => MaybePromise<number>,
443
- otherwise?: AsyncOptLazy<O>
444
- ) => {
445
- const token = Symbol();
446
- return create<T, T | O, T | typeof token>(
447
- token,
448
- async (state, next): Promise<T> => {
449
- if (token === state) return next;
450
- return (await compFun(state, next)) < 0 ? state : next;
451
- },
452
- (state): MaybePromise<T | O> =>
453
- token === state ? AsyncOptLazy.toMaybePromise(otherwise!) : state
454
- );
455
- };
456
-
457
- /**
458
- * Returns a `Reducer` that remembers the minimum value of the numberic inputs.
459
- * @param otherwise - (default: undefined) a fallback value when there were no input values given
460
- * @example
461
- * ```ts
462
- * console.log(Stream.of(5, 3, 7, 4).reduce(Reducer.min()))
463
- * // => 3
464
- * ```
465
- */
466
- // prettier-ignore
467
- export const min: {
468
- (): AsyncReducer<number, number | undefined>;
469
- <O>(otherwise: AsyncOptLazy<O>): AsyncReducer<number, number | O>;
470
- } = <O,>(otherwise?: AsyncOptLazy<O>) => {
471
- return create<number, number | O, number | undefined>(
472
- undefined,
473
- (state, next): number =>
474
- undefined !== state && state < next ? state : next,
475
- (state): MaybePromise<number | O> =>
476
- state ?? AsyncOptLazy.toMaybePromise(otherwise!)
477
- );
478
- };
479
-
480
- /**
481
- * Returns a `Reducer` that remembers the maximum value of the inputs using the given `compFun` to compare input values
482
- * @param compFun - a comparison function for two input values, returning 0 when equal, positive when greater, negetive when smaller
483
- * @param otherwise - (default: undefined) a fallback value when there were no input values given
484
- * @example
485
- * ```ts
486
- * const stream = Stream.of('abc', 'a', 'abcde', 'ab')
487
- * console.log(stream.maxBy((s1, s2) => s1.length - s2.length))
488
- * // 'abcde'
489
- * ```
490
- */
491
- export const maxBy: {
492
- <T>(compFun: (v1: T, v2: T) => MaybePromise<number>): AsyncReducer<
493
- T,
494
- T | undefined
495
- >;
496
- <T, O>(
497
- compFun: (v1: T, v2: T) => MaybePromise<number>,
498
- otherwise: AsyncOptLazy<O>
499
- ): AsyncReducer<T, T | O>;
500
- } = <T, O>(
501
- compFun: (v1: T, v2: T) => MaybePromise<number>,
502
- otherwise?: AsyncOptLazy<O>
503
- ): AsyncReducer<T, T | O> => {
504
- const token = Symbol();
505
- return create<T, T | O, T | typeof token>(
506
- token,
507
- async (state, next): Promise<T> => {
508
- if (token === state) return next;
509
- return (await compFun(state, next)) > 0 ? state : next;
510
- },
511
- (state): MaybePromise<T | O> =>
512
- token === state ? AsyncOptLazy.toMaybePromise(otherwise!) : state
513
- );
514
- };
515
-
516
- /**
517
- * Returns a `Reducer` that remembers the maximum value of the numberic inputs.
518
- * @param otherwise - (default: undefined) a fallback value when there were no input values given
519
- * @example
520
- * ```ts
521
- * console.log(Stream.of(5, 3, 7, 4).reduce(Reducer.max()))
522
- * // => 7
523
- * ```
524
- */
525
- // prettier-ignore
526
- export const max: {
527
- (): AsyncReducer<number, number | undefined>;
528
- <O>(otherwise: AsyncOptLazy<O>): AsyncReducer<number, number | O>;
529
- } = <O,>(otherwise?: AsyncOptLazy<O>): AsyncReducer<number, number | O> => {
530
- return create<number, number | O, number | undefined>(
531
- undefined,
532
- (state, next): number =>
533
- undefined !== state && state > next ? state : next,
534
- (state): MaybePromise<number | O> =>
535
- state ?? AsyncOptLazy.toMaybePromise(otherwise!)
536
- );
537
- };
538
-
539
- /**
540
- * Returns a `Reducer` that joins the given input values into a string using the given options.
541
- * @param options - an object containing:<br/>
542
- * - sep: (optional) a seperator string value between values in the output<br/>
543
- * - start: (optional) a start string to prepend to the output<br/>
544
- * - end: (optional) an end string to append to the output<br/>
545
- * @example
546
- * ```ts
547
- * console.log(Stream.of(1, 2, 3).reduce(Reducer.join({ sep: '-' })))
548
- * // => '1-2-3'
549
- * ```
550
- */
551
- export function join<T>({
552
- sep = '',
553
- start = '',
554
- end = '',
555
- valueToString = String as (value: T) => string,
556
- } = {}): AsyncReducer<T, string> {
557
- let curSep = '';
558
- let curStart = start;
559
- return create(
560
- '',
561
- (state, next): string => {
562
- const result = curStart.concat(state, curSep, valueToString(next));
563
- curSep = sep;
564
- curStart = '';
565
- return result;
566
- },
567
- (state): string => state.concat(end)
568
- );
569
- }
570
-
571
- /**
572
- * Returns an `AsyncReducer` that remembers the amount of input items provided.
573
- * @param pred - (optional) a predicate that returns false if the item should not be counted given:<br/>
574
- * - value: the current input value<br/>
575
- * - index: the input value index
576
- * @example
577
- * ```ts
578
- * const stream = AsyncStream.from(Stream.range({ amount: 10 }))
579
- * console.log(await stream.reduce(AsyncReducer.count()))
580
- * // => 10
581
- * console.log(await stream.reduce(AsyncReducer.count(async v => v < 5)))
582
- * // => 5
583
- * ```
584
- */
585
- export const count: {
586
- (): AsyncReducer<never, number>;
587
- <T>(pred: (value: T, index: number) => MaybePromise<boolean>): AsyncReducer<
588
- T,
589
- number
590
- >;
591
- } = (
592
- pred?: (value: any, index: number) => MaybePromise<boolean>
593
- ): AsyncReducer<never, number> => {
594
- if (undefined === pred) return createOutput(0, (_, __, i): number => i + 1);
595
-
596
- return createOutput(0, async (state, next, i): Promise<number> => {
597
- if (await pred?.(next, i)) return state + 1;
598
- return state;
599
- });
600
- };
601
-
602
- /**
603
- * Returns an `AsyncReducer` that remembers the first input value for which the given `pred` function returns true.
604
- * @param pred - a function taking an input value and its index, and returning true if the value should be remembered
605
- * @param otherwise - (default: undefined) a fallback value to output if no input value yet has satisfied the given predicate
606
- * @typeparam T - the input value type
607
- * @typeparam O - the fallback value type
608
- * @example
609
- * ```ts
610
- * await AsyncStream.from(Stream.range({ amount: 10 })).reduce(AsyncReducer.firstWhere(async v => v > 5)))
611
- * // => 6
612
- * ```
613
- */
614
- export const firstWhere: {
615
- <T>(pred: (value: T, index: number) => MaybePromise<boolean>): AsyncReducer<
616
- T,
617
- T | undefined
618
- >;
619
- <T, O>(
620
- pred: (value: T, index: number) => MaybePromise<boolean>,
621
- otherwise: AsyncOptLazy<O>
622
- ): AsyncReducer<T, T | O>;
623
- } = <T, O>(
624
- pred: (value: T, index: number) => MaybePromise<boolean>,
625
- otherwise?: AsyncOptLazy<O>
626
- ) => {
627
- const token = Symbol();
628
-
629
- return create<T, T | O, T | typeof token>(
630
- token,
631
- async (state, next, i, halt): Promise<T | typeof token> => {
632
- if (token === state && (await pred(next, i))) {
633
- halt();
634
- return next;
635
- }
636
- return state;
637
- },
638
- (state): MaybePromise<T | O> =>
639
- token === state ? AsyncOptLazy.toMaybePromise(otherwise!) : state
640
- );
641
- };
642
-
643
- /**
644
- * Returns an `AsyncReducer` that remembers the first input value.
645
- * @param otherwise - (default: undefined) a fallback value to output if no input value has been provided
646
- * @typeparam T - the input value type
647
- * @typeparam O - the fallback value type
648
- * @example
649
- * ```ts
650
- * await AsyncStream.from(Stream.range{ amount: 10 })).reduce(AsyncReducer.first())
651
- * // => 0
652
- * ```
653
- */
654
- export const first: {
655
- <T>(): AsyncReducer<T, T | undefined>;
656
- <T, O>(otherwise: AsyncOptLazy<O>): AsyncReducer<T, T | O>;
657
- } = <T, O>(otherwise?: AsyncOptLazy<O>): AsyncReducer<T, T | O> => {
658
- const token = Symbol();
659
-
660
- return create<T, T | O, T | typeof token>(
661
- token,
662
- (state, next, _, halt): T => {
663
- halt();
664
- if (token === state) return next;
665
- return state;
666
- },
667
- (state): MaybePromise<T | O> =>
668
- token === state ? AsyncOptLazy.toMaybePromise(otherwise!) : state
669
- );
670
- };
671
-
672
- /**
673
- * Returns an `AsyncReducer` that remembers the last input value for which the given `pred` function returns true.
674
- * @param pred - a function taking an input value and its index, and returning true if the value should be remembered
675
- * @param otherwise - (default: undefined) a fallback value to output if no input value yet has satisfied the given predicate
676
- * @typeparam T - the input value type
677
- * @typeparam O - the fallback value type
678
- * @example
679
- * ```ts
680
- * await AsyncStream.from(Stream.range({ amount: 10 })).reduce(AsyncReducer.lastWhere(async v => v > 5)))
681
- * // => 9
682
- * ```
683
- */
684
- export const lastWhere: {
685
- <T>(pred: (value: T, index: number) => MaybePromise<boolean>): AsyncReducer<
686
- T,
687
- T | undefined
688
- >;
689
- <T, O>(
690
- pred: (value: T, index: number) => MaybePromise<boolean>,
691
- otherwise: AsyncOptLazy<O>
692
- ): AsyncReducer<T, T | O>;
693
- } = <T, O>(
694
- pred: (value: T, index: number) => MaybePromise<boolean>,
695
- otherwise?: AsyncOptLazy<O>
696
- ) => {
697
- const token = Symbol();
698
-
699
- return create<T, T | O, T | typeof token>(
700
- token,
701
- async (state, next, i): Promise<T | typeof token> => {
702
- if (await pred(next, i)) return next;
703
- return state;
704
- },
705
- (state): MaybePromise<T | O> =>
706
- token === state ? AsyncOptLazy.toMaybePromise(otherwise!) : state
707
- );
708
- };
709
-
710
- /**
711
- * Returns an `AsyncReducer` that remembers the last input value.
712
- * @param otherwise - (default: undefined) a fallback value to output if no input value has been provided
713
- * @typeparam T - the input value type
714
- * @typeparam O - the fallback value type
715
- * @example
716
- * ```ts
717
- * await AsyncStream.from(Stream.range{ amount: 10 })).reduce(AsyncReducer.last())
718
- * // => 9
719
- * ```
720
- */
721
- export const last: {
722
- <T>(): AsyncReducer<T, T | undefined>;
723
- <T, O>(otherwise: AsyncOptLazy<O>): AsyncReducer<T, T | O>;
724
- } = <T, O>(otherwise?: AsyncOptLazy<O>): AsyncReducer<T, T | O> => {
725
- const token = Symbol();
726
-
727
- return create<T, T | O, T | typeof token>(
728
- () => token,
729
- (_, next): T => next,
730
- (state): MaybePromise<T | O> =>
731
- token === state ? AsyncOptLazy.toMaybePromise(otherwise!) : state
732
- );
733
- };
734
-
735
- /**
736
- * Returns a `Reducer` that ouputs false as long as no input value satisfies given `pred`, true otherwise.
737
- * @typeparam T - the element type
738
- * @param pred - a function taking an input value and its index, and returning true if the value satisfies the predicate
739
- * @example
740
- * ```ts
741
- * await AsyncStream.from(Stream.range{ amount: 10 })).reduce(AsyncReducer.some(async v => v > 5))
742
- * // => true
743
- * ```
744
- */
745
- export function some<T>(
746
- pred: (value: T, index: number) => MaybePromise<boolean>
747
- ): AsyncReducer<T, boolean> {
748
- return createOutput<T, boolean>(
749
- false,
750
- async (state, next, i, halt): Promise<boolean> => {
751
- if (state) return state;
752
- const satisfies = await pred(next, i);
753
-
754
- if (satisfies) {
755
- halt();
756
- }
757
-
758
- return satisfies;
759
- }
760
- );
761
- }
762
-
763
- /**
764
- * Returns an `AsyncReducer` that ouputs true as long as all input values satisfy the given `pred`, false otherwise.
765
- * @typeparam T - the element type
766
- * @param pred - a function taking an input value and its index, and returning true if the value satisfies the predicate
767
- * @example
768
- * ```ts
769
- * await AsyncStream.from(Stream.range{ amount: 10 })).reduce(AsyncReducer.every(async v => v < 5))
770
- * // => false
771
- * ```
772
- */
773
- export function every<T>(
774
- pred: (value: T, index: number) => MaybePromise<boolean>
775
- ): AsyncReducer<T, boolean> {
776
- return createOutput<T, boolean>(
777
- true,
778
- async (state, next, i, halt): Promise<boolean> => {
779
- if (!state) return state;
780
-
781
- const satisfies = await pred(next, i);
782
-
783
- if (!satisfies) {
784
- halt();
785
- }
786
-
787
- return satisfies;
788
- }
789
- );
790
- }
791
-
792
- /**
793
- * Returns an `AsyncReducer` that outputs false as long as the given `elem` has not been encountered in the input values, true otherwise.
794
- * @typeparam T - the element type
795
- * @param elem - the element to search for
796
- * @param eq - (optional) a comparison function that returns true if te two given input values are considered equal
797
- * @example
798
- * ```ts
799
- * await AsyncStream.from(Stream.range({ amount: 10 })).reduce(AsyncReducer.contains(5)))
800
- * // => true
801
- * ```
802
- */
803
- export function contains<T>(
804
- elem: T,
805
- eq: Eq<T> = Object.is
806
- ): AsyncReducer<T, boolean> {
807
- return createOutput<T, boolean>(false, (state, next, _, halt): boolean => {
808
- if (state) return state;
809
- const satisfies = eq(next, elem);
810
-
811
- if (satisfies) {
812
- halt();
813
- }
814
-
815
- return satisfies;
816
- });
817
- }
818
-
819
- /**
820
- * Returns an `AsyncReducer` that takes boolean values and outputs true if all input values are true, and false otherwise.
821
- * @example
822
- * ```ts
823
- * await AsyncStream.of(true, false, true)).reduce(AsyncReducer.and))
824
- * // => false
825
- * ```
826
- */
827
- export const and = createMono(true, (state, next, _, halt): boolean => {
828
- if (!state) return state;
829
- if (!next) halt();
830
- return next;
831
- });
832
-
833
- /**
834
- * Returns an `AsyncReducer` that takes boolean values and outputs true if one or more input values are true, and false otherwise.
835
- * @example
836
- * ```ts
837
- * await AsyncStream.of(true, false, true)).reduce(AsyncReducer.or))
838
- * // => true
839
- * ```
840
- */
841
- export const or = createMono(false, (state, next, _, halt): boolean => {
842
- if (state) return state;
843
- if (next) halt();
844
- return next;
845
- });
846
-
847
- /**
848
- * Returns an `AsyncReducer` that outputs true if no input values are received, false otherwise.
849
- * @example
850
- * ```ts
851
- * await AsyncStream.of(1, 2, 3).reduce(AsyncReducer.isEmpty))
852
- * // => false
853
- * ```
854
- */
855
- export const isEmpty = createOutput<never, boolean>(
856
- true,
857
- (_, __, ___, halt): false => {
858
- halt();
859
- return false;
860
- }
861
- );
862
-
863
- /**
864
- * Returns an `AsyncReducer` that outputs true if one or more input values are received, false otherwise.
865
- * @example
866
- * ```ts
867
- * await AsyncStream.of(1, 2, 3).reduce(AsyncReducer.nonEmpty))
868
- * // => true
869
- * ```
870
- */
871
- export const nonEmpty = createOutput<never, boolean>(
872
- false,
873
- (_, __, ___, halt): true => {
874
- halt();
875
- return true;
876
- }
877
- );
878
-
879
- /**
880
- * Returns an `AsyncReducer` that collects received input values in an array, and returns a copy of that array as an output value when requested.
881
- * @typeparam T - the element type
882
- * @example
883
- * ```ts
884
- * await AsyncStream.of(1, 2, 3).reduce(AsyncReducer.toArray()))
885
- * // => [1, 2, 3]
886
- * ```
887
- */
888
- export function toArray<T>(): AsyncReducer<T, T[]> {
889
- return create(
890
- (): T[] => [],
891
- (state, next): T[] => {
892
- state.push(next);
893
- return state;
894
- },
895
- (state): T[] => state.slice()
896
- );
897
- }
898
-
899
- /**
900
- * Returns a `AsyncReducer` that collects received input tuples into a mutable JS Map, and returns
901
- * a copy of that map when output is requested.
902
- * @typeparam K - the map key type
903
- * @typeparam V - the map value type
904
- * @example
905
- * ```ts
906
- * await AsyncStream.of([1, 'a'], [2, 'b']).reduce(AsyncReducer.toJSMap()))
907
- * // Map { 1 => 'a', 2 => 'b' }
908
- * ```
909
- */
910
- export function toJSMap<K, V>(): AsyncReducer<[K, V], Map<K, V>> {
911
- return create(
912
- (): Map<K, V> => new Map(),
913
- (state, next): Map<K, V> => {
914
- state.set(next[0], next[1]);
915
- return state;
916
- },
917
- (s): Map<K, V> => new Map(s)
918
- );
919
- }
920
-
921
- /**
922
- * Returns an `AsyncReducer` that collects received input values into a mutable JS Set, and returns
923
- * a copy of that map when output is requested.
924
- * @typeparam T - the element type
925
- * @example
926
- * ```ts
927
- * await AsyncStream.of(1, 2, 3).reduce(AsyncReducer.toJSSet()))
928
- * // Set {1, 2, 3}
929
- * ```
930
- */
931
- export function toJSSet<T>(): AsyncReducer<T, Set<T>> {
932
- return create(
933
- (): Set<T> => new Set<T>(),
934
- (state, next): Set<T> => {
935
- state.add(next);
936
- return state;
937
- },
938
- (s): Set<T> => new Set(s)
939
- );
940
- }
941
-
942
- /**
943
- * Returns an `AsyncReducer` that collects 2-tuples containing keys and values into a plain JS object, and
944
- * returns a copy of that object when output is requested.
945
- * @typeparam K - the result object key type
946
- * @typeparam V - the result object value type
947
- * @example
948
- * ```ts
949
- * await AsyncStream.of(['a', 1], ['b', true]).reduce(AsyncReducer.toJSObject()))
950
- * // { a: 1, b: true }
951
- * ```
952
- */
953
- export function toJSObject<
954
- K extends string | number | symbol,
955
- V
956
- >(): AsyncReducer<[K, V], Record<K, V>> {
957
- return create(
958
- () => ({} as Record<K, V>),
959
- (state, entry) => {
960
- state[entry[0]] = entry[1];
961
- return state;
962
- },
963
- (s) => ({ ...s })
964
- );
965
- }
966
-
967
- /**
968
- * Returns an `AsyncReducer` that combines multiple input `reducers` by providing input values to all of them and collecting the outputs in an array.
969
- * @param reducers - 2 or more reducers to combine
970
- * @example
971
- * ```ts
972
- * const red = AsyncReducer.combineArr(AsyncReducer.sum, AsyncReducer.average)
973
- *
974
- * await AsyncStream.from(Stream.range({ amount: 9 }))
975
- * .reduce(red)
976
- * // => [36, 4]
977
- * ```
978
- */
979
- export function combineArr<
980
- T,
981
- R extends readonly [unknown, unknown, ...unknown[]]
982
- >(
983
- ...reducers: { [K in keyof R]: AsyncReducer<T, R[K]> } & AsyncReducer<
984
- T,
985
- unknown
986
- >[]
987
- ): AsyncReducer<T, R> {
988
- const createState = (): Promise<
989
- {
990
- reducer: AsyncReducer<T, unknown>;
991
- halted: boolean;
992
- halt(): void;
993
- state: unknown;
994
- }[]
995
- > => {
996
- return Promise.all(
997
- reducers.map(async (reducer) => {
998
- const result = {
999
- reducer,
1000
- halted: false,
1001
- halt(): void {
1002
- result.halted = true;
1003
- },
1004
- state: await AsyncOptLazy.toMaybePromise(reducer.init),
1005
- };
1006
-
1007
- return result;
1008
- })
1009
- );
1010
- };
1011
-
1012
- return create<
1013
- T,
1014
- R,
1015
- {
1016
- reducer: AsyncReducer<T, unknown>;
1017
- halted: boolean;
1018
- halt(): void;
1019
- state: unknown;
1020
- }[]
1021
- >(
1022
- createState,
1023
- async (allState, next, index, halt) => {
1024
- let anyNotHalted = false;
1025
-
1026
- await Promise.all(
1027
- allState.map(async (red) => {
1028
- if (red.halted) return;
1029
-
1030
- red.state = await red.reducer.next(
1031
- red.state,
1032
- next,
1033
- index,
1034
- red.halt
1035
- );
1036
- if (!red.halted) anyNotHalted = true;
1037
- })
1038
- );
1039
-
1040
- if (!anyNotHalted) halt();
1041
-
1042
- return allState;
1043
- },
1044
- (allState) =>
1045
- Promise.all(
1046
- allState.map((st) => st.reducer.stateToResult(st.state))
1047
- ) as any
1048
- );
1049
- }
1050
-
1051
- /**
1052
- * Returns an `AsyncReducer` that combines multiple input `reducers` by providing input values to all of them and collecting the outputs in the shape of the given object.
1053
- * @typeparam T - the input type for all the reducers
1054
- * @typeparam R - the result object shape
1055
- * @param reducerObj - an object of keys, and reducers corresponding to those keys
1056
- * @example
1057
- * ```ts
1058
- * const red = AsyncReducer.combineObj({
1059
- * theSum: Reducer.sum,
1060
- * theAverage: Reducer.average
1061
- * });
1062
- *
1063
- * await AsyncStream.from(Stream.range({ amount: 9 }))
1064
- * .reduce(red));
1065
- * // => { theSum: 36, theAverage: 4 }
1066
- * ```
1067
- */
1068
- export function combineObj<T, R extends { readonly [key: string]: unknown }>(
1069
- reducerObj: { readonly [K in keyof R]: AsyncReducer<T, R[K]> } & Record<
1070
- string,
1071
- AsyncReducer<T, unknown>
1072
- >
1073
- ): AsyncReducer<T, R> {
1074
- const createState = async (): Promise<
1075
- Record<
1076
- keyof R,
1077
- {
1078
- reducer: AsyncReducer<T, unknown>;
1079
- halted: boolean;
1080
- halt(): void;
1081
- state: unknown;
1082
- }
1083
- >
1084
- > => {
1085
- const entries = await Promise.all(
1086
- Object.entries(reducerObj).map(async ([key, reducer]) => {
1087
- const result = {
1088
- reducer,
1089
- halted: false,
1090
- halt(): void {
1091
- result.halted = true;
1092
- },
1093
- state: await AsyncOptLazy.toMaybePromise(reducer.init),
1094
- };
1095
-
1096
- return [key, result] as const;
1097
- })
1098
- );
1099
-
1100
- return Object.fromEntries(entries) as any;
1101
- };
1102
-
1103
- return create<
1104
- T,
1105
- R,
1106
- Record<
1107
- keyof R,
1108
- {
1109
- reducer: AsyncReducer<T, unknown>;
1110
- halted: boolean;
1111
- halt(): void;
1112
- state: unknown;
1113
- }
1114
- >
1115
- >(
1116
- createState,
1117
- async (allState, next, index, halt) => {
1118
- let anyNotHalted = false;
1119
-
1120
- await Promise.all(
1121
- Object.values(allState).map(async (red) => {
1122
- if (red.halted) {
1123
- return;
1124
- }
1125
-
1126
- red.state = await red.reducer.next(
1127
- red.state,
1128
- next,
1129
- index,
1130
- red.halt
1131
- );
1132
-
1133
- if (!red.halted) {
1134
- anyNotHalted = true;
1135
- }
1136
- })
1137
- );
1138
-
1139
- if (!anyNotHalted) {
1140
- halt();
1141
- }
1142
-
1143
- return allState;
1144
- },
1145
- async (allState) => {
1146
- const entries = await Promise.all(
1147
- Object.entries(allState).map(
1148
- async ([key, st]) =>
1149
- [key, await st.reducer.stateToResult(st.state)] as const
1150
- )
1151
- );
1152
-
1153
- return Object.fromEntries(entries) as any;
1154
- }
1155
- );
1156
- }
1157
- }