UNPKG

@rimbu/stream

Version:

Efficient structure representing a sequence of elements, with powerful operations for TypeScript

873 lines 40.9 kB
import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib"; import { RimbuError } from '@rimbu/base'; import { AsyncOptLazy, CollectFun, Eq, ErrBase, } from '@rimbu/common'; import { Reducer, Stream, } from '@rimbu/stream'; import { AsyncStreamConstructorsImpl, fromAsyncStreamSource, } from '../async-custom/async-stream-custom.mjs'; function identity(value) { return value; } /** * Combines multiple (asynchronous) reducers in an array of the same input type into a single reducer that * forwards each incoming value to all reducers, and when output is requested will return an array containing * the corresponding output of each reducer. */ function combineArr(...reducers) { return AsyncReducer.create(async (initHalt) => { let allHalted = true; const result = await Promise.all(reducers.map(async (reducer) => { const instance = await AsyncReducer.from(reducer).compile(); allHalted = allHalted && instance.halted; return instance; })); if (allHalted) { initHalt(); } return result; }, async (state, elem, index, halt) => { let allHalted = true; await Promise.all(Stream.from(state).mapPure(async (reducer) => { if (reducer.halted) return; await reducer.next(elem); allHalted = allHalted && reducer.halted; })); if (allHalted) { halt(); } return state; }, (state) => Promise.all(Stream.from(state).mapPure((reducerInstance) => reducerInstance.getOutput())), async (state, err) => { await Promise.all(Stream.from(state).mapPure((reducer) => reducer.onClose(err))); }); } /** * Combines multiple (asynchronous) reducers in an object's values of the same input type into a single reducer that * forwards each incoming value to all reducers, and when output is requested will return an object containing * the corresponding output of each reducer at the matching object property. */ function combineObj(reducerObj) { return AsyncReducer.create(async (initHalt) => { const result = {}; let allHalted = true; await Promise.all(Stream.fromObject(reducerObj).mapPure(async ([key, reducer]) => { const instance = await AsyncReducer.from(reducer).compile(); result[key] = instance; allHalted = allHalted && instance.halted; })); if (allHalted) { initHalt(); } return result; }, async (state, elem, index, halt) => { let allHalted = true; await Promise.all(Stream.fromObjectValues(state).mapPure(async (reducerInstance) => { if (!reducerInstance.halted) { await reducerInstance.next(elem); allHalted = allHalted && reducerInstance.halted; } })); if (allHalted) { halt(); } return state; }, async (state) => { const result = {}; await Promise.all(Stream.fromObject(state).mapPure(async ([key, reducerInstance]) => { result[key] = await reducerInstance.getOutput(); })); return result; }, async (state, err) => { await Promise.all(Stream.fromObjectValues(state).mapPure((reducerInstance) => reducerInstance.onClose(err))); }); } export var AsyncReducer; (function (AsyncReducer) { var _InstanceImpl_state, _InstanceImpl_index, _InstanceImpl_initialized, _InstanceImpl_halted, _InstanceImpl_closed; /** * A base class that can be used to easily create `AsyncReducer` instances. * @typeparam I - the input value type * @typeparam O - the output value type * @typeparam S - the internal state type */ class Base { constructor(init, next, stateToResult, onClose) { this.init = init; this.next = next; this.stateToResult = stateToResult; this.onClose = onClose; } filterInput(pred, options = {}) { const { negate = false } = options; return create(() => this.compile(), async (state, elem, index, halt) => { if ((await pred(elem, index, halt)) !== negate) { await state.next(elem); if (state.halted) { halt(); } } return state; }, (state) => state.getOutput(), (state, err) => state.onClose(err)); } mapInput(mapFun) { return create(this.init, async (state, elem, index, halt) => this.next(state, await mapFun(elem, index), index, halt), this.stateToResult, this.onClose); } flatMapInput(flatMapFun) { return create(() => this.compile(), async (state, elem, index, halt) => { if (state.halted) { halt(); return state; } const elems = await flatMapFun(elem, index); const iter = fromAsyncStreamSource(elems)[Symbol.asyncIterator](); const done = Symbol(); let value; while (done !== (value = await iter.fastNext(done))) { await state.next(value); if (state.halted) { halt(); break; } } return state; }, (state) => state.getOutput(), (state, err) => state.onClose(err)); } collectInput(collectFun) { return create(() => this.compile(), async (state, elem, index, halt) => { const nextElem = await collectFun(elem, index, CollectFun.Skip, halt); if (CollectFun.Skip !== nextElem) { await state.next(nextElem); if (state.halted) { halt(); } } return state; }, (state) => state.getOutput(), (state, err) => state.onClose(err)); } mapOutput(mapFun) { return create(this.init, this.next, async (state, index, halted) => mapFun(await this.stateToResult(state, index, halted), index, halted), this.onClose); } takeOutput(amount) { if (amount <= 0) { return create((initHalt) => { initHalt(); return this.init(initHalt); }, this.next, this.stateToResult, this.onClose); } return create(this.init, (state, next, index, halt) => { if (index >= amount - 1) { halt(); } return this.next(state, next, index, halt); }, this.stateToResult, this.onClose); } takeOutputUntil(pred, options = {}) { const { negate = false } = options; return create(this.init, async (state, next, index, halt) => { const nextState = await this.next(state, next, index, halt); const nextOutput = await this.stateToResult(nextState, index, false); if ((await pred(nextOutput, index)) !== negate) { halt(); } return nextState; }, this.stateToResult, this.onClose); } takeInput(amount) { if (amount <= 0) { return create(this.init, identity, this.stateToResult, this.onClose); } return this.filterInput((_, i, halt) => { if (i >= amount - 1) { halt(); } return i < amount; }); } dropInput(amount) { if (amount <= 0) { return this; } return this.filterInput((_, i) => i >= amount); } sliceInput(from = 0, amount) { if (undefined === amount) return this.dropInput(from); if (amount <= 0) return create(this.init, identity, this.stateToResult); if (from <= 0) return this.takeInput(amount); return this.takeInput(amount).dropInput(from); } chain(nextReducers) { return AsyncReducer.create(async (initHalt) => { const iterator = fromAsyncStreamSource(nextReducers)[Symbol.asyncIterator](); let activeInstance = (await this.compile()); if (undefined !== activeInstance && activeInstance.halted) { let output = await activeInstance.getOutput(); do { const creator = await iterator.fastNext(); if (undefined === creator) { initHalt(); return { activeInstance, iterator, }; } const nextReducer = await AsyncOptLazy.toMaybePromise(creator, output); activeInstance = await AsyncReducer.from(nextReducer).compile(); output = await activeInstance.getOutput(); } while (activeInstance.halted); } return { activeInstance, iterator, }; }, async (state, next, index, halt) => { await state.activeInstance.next(next); while (state.activeInstance.halted) { const output = await state.activeInstance.getOutput(); const creator = await state.iterator.fastNext(); if (undefined === creator) { halt(); return state; } const nextReducer = await AsyncOptLazy.toMaybePromise(creator, output); state.activeInstance = await AsyncReducer.from(nextReducer).compile(); } return state; }, (state) => state.activeInstance.getOutput()); } async compile() { const instance = new AsyncReducer.InstanceImpl(this); await instance.initialize(); return instance; } } AsyncReducer.Base = Base; /** * The default `AsyncReducer.Impl` implementation. * @typeparam I - the input element type * @typeparam O - the output element type * @typeparam S - the reducer state type */ class InstanceImpl { constructor(reducer) { this.reducer = reducer; _InstanceImpl_state.set(this, void 0); _InstanceImpl_index.set(this, 0); _InstanceImpl_initialized.set(this, false); _InstanceImpl_halted.set(this, false); _InstanceImpl_closed.set(this, false); this.halt = () => { if (__classPrivateFieldGet(this, _InstanceImpl_closed, "f")) { throw new AsyncReducer.ReducerClosedError(); } __classPrivateFieldSet(this, _InstanceImpl_halted, true, "f"); }; this.next = async (value) => { var _a, _b; if (!__classPrivateFieldGet(this, _InstanceImpl_initialized, "f")) { throw new AsyncReducer.ReducerNotInitializedError(); } if (__classPrivateFieldGet(this, _InstanceImpl_closed, "f")) { throw new AsyncReducer.ReducerClosedError(); } if (__classPrivateFieldGet(this, _InstanceImpl_halted, "f")) { throw new AsyncReducer.ReducerHaltedError(); } __classPrivateFieldSet(this, _InstanceImpl_state, await this.reducer.next(__classPrivateFieldGet(this, _InstanceImpl_state, "f"), value, (__classPrivateFieldSet(this, _InstanceImpl_index, (_b = __classPrivateFieldGet(this, _InstanceImpl_index, "f"), _a = _b++, _b), "f"), _a), this.halt), "f"); }; } async initialize() { if (__classPrivateFieldGet(this, _InstanceImpl_closed, "f")) { throw new AsyncReducer.ReducerClosedError(); } __classPrivateFieldSet(this, _InstanceImpl_state, await this.reducer.init(this.halt), "f"); __classPrivateFieldSet(this, _InstanceImpl_initialized, true, "f"); } get halted() { return __classPrivateFieldGet(this, _InstanceImpl_halted, "f"); } get index() { return __classPrivateFieldGet(this, _InstanceImpl_index, "f"); } async getOutput() { if (!__classPrivateFieldGet(this, _InstanceImpl_initialized, "f")) { throw new AsyncReducer.ReducerNotInitializedError(); } return this.reducer.stateToResult(__classPrivateFieldGet(this, _InstanceImpl_state, "f"), this.index, this.halted); } async onClose(err) { if (__classPrivateFieldGet(this, _InstanceImpl_closed, "f")) { throw new AsyncReducer.ReducerClosedError(); } __classPrivateFieldSet(this, _InstanceImpl_closed, true, "f"); await this.reducer.onClose?.(__classPrivateFieldGet(this, _InstanceImpl_state, "f"), err); } } _InstanceImpl_state = new WeakMap(), _InstanceImpl_index = new WeakMap(), _InstanceImpl_initialized = new WeakMap(), _InstanceImpl_halted = new WeakMap(), _InstanceImpl_closed = new WeakMap(); AsyncReducer.InstanceImpl = InstanceImpl; /** * Returns an `AsyncReducer` with the given options: * @param init - the optionally lazy and/or promised initial state value * @param next - returns (potentially asynchronously) the next state value based on the given inputs:<br/> * - current: the current state<br/> * - next: the current input value<br/> * - index: the input index value<br/> * - halt: function that, when called, ensures no more elements are passed to the reducer * @param stateToResult - a potentially asynchronous function that converts the current state to an output value * @param onClose - (optional) a function that will be called when the reducer will no longer receive values * @typeparam I - the input value type * @typeparam O - the output value type * @typeparam S - the internal state type */ function create(init, next, stateToResult, onClose) { return new AsyncReducer.Base(init, next, stateToResult, onClose); } AsyncReducer.create = create; /** * Returns an `AsyncReducer` of which the input, state, and output types are the same. * @param init - the optionally lazy and/or promised initial state value * @param next - returns (potentially asynchronously) the next state value based on the given inputs:<br/> * - current: the current state<br/> * - next: the current input value<br/> * - index: the input index value<br/> * - halt: function that, when called, ensures no more elements are passed to the reducer * @param stateToResult - a potentially asynchronous function that converts the current state to an output value * @param onClose - (optional) a function that will be called when the reducer will no longer receive values * @typeparam T - the overall value type */ function createMono(init, next, stateToResult, onClose) { return create(init, next, stateToResult ?? identity, onClose); } AsyncReducer.createMono = createMono; /** * Returns an `AsyncReducer` of which the state and output types are the same. * @param init - the optionally lazy and/or promised initial state value * @param next - returns (potentially asynchronously) the next state value based on the given inputs:<br/> * - current: the current state<br/> * - next: the current input value<br/> * - index: the input index value<br/> * - halt: function that, when called, ensures no more elements are passed to the reducer * @param stateToResult - a potentially asynchronous function that converts the current state to an output value * @param onClose - (optional) a function that will be called when the reducer will no longer receive values * @typeparam I - the input value type * @typeparam O - the output value type */ function createOutput(init, next, stateToResult, onClose) { return create(init, next, stateToResult ?? identity, onClose); } AsyncReducer.createOutput = createOutput; /** * Returns an `AsyncReducer` that uses the given `init` and `next` values to fold the input values into * result values. * @param init - an (optionally lazy) initial result value * @param next - a (potentially async) function taking the following arguments:<br/> * - current - the current result value<br/> * - value - the next input value<br/> * - index: the input index value<br/> * - halt: function that, when called, ensures no more elements are passed to the reducer * @typeparam T - the input type * @typeparam R - the output type */ function fold(init, next) { return AsyncReducer.createOutput(() => AsyncOptLazy.toMaybePromise(init), next); } AsyncReducer.fold = fold; /** * Returns an `AsyncReducer` from a given `Reducer` or `AsyncReducer` instance. * @param reducer - the input reducer to convert * @typeparam I - the input element type * @typeparam O - the output element type */ function from(reducer) { if (reducer instanceof AsyncReducer.Base) { return reducer; } return AsyncReducer.create(reducer.init, reducer.next, reducer.stateToResult); } AsyncReducer.from = from; /** * Returns a `Reducer` that remembers the minimum value of the inputs using the given `compFun` to compare input values * @param compFun - a comparison function for two input values, returning 0 when equal, positive when greater, negetive when smaller * @param otherwise - (default: undefineds) a fallback value when there were no input values given * @typeparam T - the element type * @typeparam O - the fallback value type * @example * ```ts * const stream = Stream.of('abc', 'a', 'abcde', 'ab') * console.log(stream.minBy((s1, s2) => s1.length - s2.length)) * // 'a' * ``` */ AsyncReducer.minBy = (compFun, otherwise) => { const token = Symbol(); return create(() => token, async (state, next) => { if (token === state) return next; return (await compFun(state, next)) < 0 ? state : next; }, (state) => token === state ? AsyncOptLazy.toMaybePromise(otherwise) : state); }; /** * Returns a `Reducer` that remembers the minimum value of the numberic inputs. * @param otherwise - (default: undefined) a fallback value when there were no input values given * @typeparam O - the fallback value type * @example * ```ts * console.log(Stream.of(5, 3, 7, 4).reduce(Reducer.min())) * // => 3 * ``` */ // prettier-ignore AsyncReducer.min = (otherwise) => { return create(() => undefined, (state, next) => undefined !== state && state < next ? state : next, (state) => state ?? AsyncOptLazy.toMaybePromise(otherwise)); }; /** * Returns a `Reducer` that remembers the maximum value of the inputs using the given `compFun` to compare input values * @param compFun - a comparison function for two input values, returning 0 when equal, positive when greater, negetive when smaller * @param otherwise - (default: undefined) a fallback value when there were no input values given * @typeparam T - the element type * @typeparam O - the fallback value type * @example * ```ts * const stream = Stream.of('abc', 'a', 'abcde', 'ab') * console.log(stream.maxBy((s1, s2) => s1.length - s2.length)) * // 'abcde' * ``` */ AsyncReducer.maxBy = (compFun, otherwise) => { const token = Symbol(); return create(() => token, async (state, next) => { if (token === state) return next; return (await compFun(state, next)) > 0 ? state : next; }, (state) => token === state ? AsyncOptLazy.toMaybePromise(otherwise) : state); }; /** * Returns a `Reducer` that remembers the maximum value of the numberic inputs. * @param otherwise - (default: undefined) a fallback value when there were no input values given * @typeparam O - the fallback value type * @example * ```ts * console.log(Stream.of(5, 3, 7, 4).reduce(Reducer.max())) * // => 7 * ``` */ // prettier-ignore AsyncReducer.max = (otherwise) => { return create(() => undefined, (state, next) => undefined !== state && state > next ? state : next, (state) => state ?? AsyncOptLazy.toMaybePromise(otherwise)); }; /** * Returns an `AsyncReducer` that remembers the first input value. * @param otherwise - (default: undefined) a fallback value to output if no input value has been provided * @typeparam T - the input value type * @typeparam O - the fallback value type * @example * ```ts * await AsyncStream.from(Stream.range{ amount: 10 })).reduce(AsyncReducer.first()) * // => 0 * ``` */ AsyncReducer.first = (otherwise) => { return create(() => undefined, (state, next, _, halt) => { halt(); return next; }, (state, index) => index <= 0 ? AsyncOptLazy.toMaybePromise(otherwise) : state); }; /** * Returns an `AsyncReducer` that remembers the last input value. * @param otherwise - (default: undefined) a fallback value to output if no input value has been provided * @typeparam T - the input value type * @typeparam O - the fallback value type * @example * ```ts * await AsyncStream.from(Stream.range{ amount: 10 })).reduce(AsyncReducer.last()) * // => 9 * ``` */ AsyncReducer.last = (otherwise) => { return create(() => undefined, (_, next) => next, (state, index) => index <= 0 ? AsyncOptLazy.toMaybePromise(otherwise) : state); }; /** * Returns an AsyncReducer that only produces an output value when having receives exactly one * input value, otherwise will return the `otherwise` value or undefined. * @param otherwise - the fallback value to return when more or less than one value is received. * @typeparam T - the element type * @typeparam O - the fallback value type */ AsyncReducer.single = (otherwise) => { return create(() => undefined, (state, next, index, halt) => { if (index > 1) { halt(); } return next; }, (state, index) => index !== 1 ? AsyncOptLazy.toMaybePromise(otherwise) : state); }; /** * Returns an `AsyncReducer` that ouputs false as long as no input value satisfies given `pred`, true otherwise. * @typeparam T - the element type * @param pred - a potentiall async function taking an input value and its index, and returning true if the value satisfies the predicate * @param options - (optional) an object containing the following properties:<br/> * - negate: (default: false) when true will invert the given predicate */ function some(pred, options = {}) { return AsyncReducer.nonEmpty.filterInput(pred, options); } AsyncReducer.some = some; /** * Returns an `AsyncReducer` that ouputs true as long as all input values satisfy the given `pred`, false otherwise. * @typeparam T - the element type * @param pred - a potentially async function taking an input value and its index, and returning true if the value satisfies the predicate * @param options - (optional) an object containing the following properties:<br/> * - negate: (default: false) when true will invert the given predicate */ function every(pred, options = {}) { const { negate = false } = options; return AsyncReducer.isEmpty.filterInput(pred, { negate: !negate }); } AsyncReducer.every = every; /** * Returns an `AsyncReducer` that ouputs true when the received elements match the given `other` async stream source according to the `eq` instance, false otherwise. * @typeparam T - the element type * @param other - an async stream source containg elements to match against * @param options - (optional) an object containing the following properties:<br/> * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements * - negate: (default: false) when true will invert the given predicate */ function equals(other, options = {}) { const { eq = Eq.objectIs, negate = false } = options; const sliceStream = fromAsyncStreamSource(other); const done = Symbol(); return AsyncReducer.create(async () => { const iter = sliceStream[Symbol.asyncIterator](); const nextSeq = await iter.fastNext(done); return { iter, nextSeq, result: false }; }, async (state, next, _, halt) => { if (done === state.nextSeq) { halt(); state.result = false; return state; } if (eq(next, state.nextSeq) === negate) { halt(); state.result = false; return state; } state.nextSeq = await state.iter.fastNext(done); if (done === state.nextSeq) { state.result = true; } return state; }, (state, index, halted) => !halted && done === state.nextSeq); } AsyncReducer.equals = equals; /** * An `AsyncReducer` that outputs true if no input values are received, false otherwise. * @example * ```ts * await AsyncStream.of(1, 2, 3).reduce(AsyncReducer.isEmpty)) * // => false * ``` */ AsyncReducer.isEmpty = createOutput(() => true, (_, __, ___, halt) => { halt(); return false; }); /** * An `AsyncReducer` that outputs true if one or more input values are received, false otherwise. * @example * ```ts * await AsyncStream.of(1, 2, 3).reduce(AsyncReducer.nonEmpty)) * // => true * ``` */ AsyncReducer.nonEmpty = createOutput(() => false, (_, __, ___, halt) => { halt(); return true; }); /** * Returns a `AsyncReducer` that returns true if the first input values match the given `slice` values repeated `amount` times. Otherwise, * returns false. * @param slice - a async sequence of elements to match against * @param options - (optional) an object containing the following properties:<br/> * - amount: (detaulf: 1) the amount of elements to find * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements */ function startsWithSlice(slice, options = {}) { const sliceStream = fromAsyncStreamSource(slice); const done = Symbol(); const { eq = Eq.objectIs, amount = 1 } = options; return AsyncReducer.create(async (initHalt) => { const sliceIter = sliceStream[Symbol.asyncIterator](); const sliceValue = await sliceIter.fastNext(done); if (done === sliceValue || amount <= 0) { initHalt(); return { sliceIter, sliceValue, remain: 0 }; } return { sliceIter, sliceValue, remain: amount, }; }, async (state, next, _, halt) => { if (done === state.sliceValue) { RimbuError.throwInvalidStateError(); } if (eq(next, state.sliceValue)) { state.sliceValue = await state.sliceIter.fastNext(done); if (done === state.sliceValue) { state.remain--; if (state.remain <= 0) { halt(); } else { state.sliceIter = sliceStream[Symbol.asyncIterator](); state.sliceValue = await state.sliceIter.fastNext(done); } } } else { halt(); } return state; }, (state) => state.remain <= 0); } AsyncReducer.startsWithSlice = startsWithSlice; /** * Returns an `AsyncReducer` that returns true if the last input values match the given `slice` values repeated `amount` times. Otherwise, * returns false. * @param slice - a async sequence of elements to match against * @param options - (optional) an object containing the following properties:<br/> * - amount: (detaulf: 1) the amount of elements to find * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements */ function endsWithSlice(slice, options = {}) { const sliceStream = AsyncStreamConstructorsImpl.from(slice); const done = Symbol(); const newReducerSpec = AsyncReducer.startsWithSlice(slice, options); return AsyncReducer.create(async (initHalt) => { const sliceIter = sliceStream[Symbol.asyncIterator](); const sliceValue = await sliceIter.fastNext(done); if (done === sliceValue) { initHalt(); } return new Set([await newReducerSpec.compile()]); }, async (state, nextValue) => { for (const instance of state) { if (instance.halted) { state.delete(instance); } else { await instance.next(nextValue); } } const newReducerInstance = await newReducerSpec.compile(); await newReducerInstance.next(nextValue); state.add(newReducerInstance); return state; }, (state) => state.size === 0 || AsyncStreamConstructorsImpl.from(state).some((instance) => instance.getOutput())); } AsyncReducer.endsWithSlice = endsWithSlice; /** * Returns an `AsyncReducer` that returns true if the input values contain the given `slice` sequence `amount` times. Otherwise, * returns false. * @param slice - a async sequence of elements to match against * @param options - (optional) an object containing the following properties:<br/> * - amount: (detaulf: 1) the amount of elements to find * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements */ function containsSlice(slice, options = {}) { const { eq, amount = 1 } = options; return AsyncReducer.pipe(endsWithSlice(slice, { eq }), Reducer.contains(true, { amount })); } AsyncReducer.containsSlice = containsSlice; /** * Returns an `AsyncReducer` that splits the incoming values into two separate outputs based on the given `pred` predicate. Values for which the predicate is true * are fed into the `collectorTrue` reducer, and other values are fed into the `collectorFalse` instance. If no collectors are provided the values are collected * into arrays. * @param pred - a potentially async predicate receiving the value and its index * @param options - (optional) an object containing the following properties:<br/> * - collectorTrue: (default: Reducer.toArray()) a reducer that collects the values for which the predicate is true<br/> * - collectorFalse: (default: Reducer.toArray()) a reducer that collects the values for which the predicate is false * @typeparam T - the input element type * @typeparam RT - the reducer result type for the `collectorTrue` value * @typeparam RF - the reducer result type for the `collectorFalse` value * @note if the predicate is a type guard, the return type is automatically inferred * ``` */ AsyncReducer.partition = (pred, options = {}) => { const { collectorTrue = Reducer.toArray(), collectorFalse = Reducer.toArray(), } = options; return AsyncReducer.create(() => Promise.all([ AsyncReducer.from(collectorTrue).compile(), AsyncReducer.from(collectorFalse).compile(), ]), async (state, value, index) => { const instanceIndex = (await pred(value, index)) ? 0 : 1; await state[instanceIndex].next(value); return state; }, (state) => Promise.all(Stream.from(state).mapPure((v) => v.getOutput()))); }; /** * Returns an `AsyncReducer` that uses the `valueToKey` function to calculate a key for each value, and feeds the tuple of the key and the value to the * `collector` reducer. Finally, it returns the output of the `collector`. If no collector is given, the default collector will return a JS multimap * of the type `Map<K, V[]>`. * @param valueToKey - potentially async function taking a value and its index, and returning the corresponding key * @param options - (optional) an object containing the following properties:<br/> * - collector: (default: Reducer.toArray()) a reducer that collects the incoming tuple of key and value, and provides the output * @typeparam T - the input value type * @typeparam K - the key type * @typeparam R - the collector output type * ``` */ AsyncReducer.groupBy = (valueToKey, options = {}) => { const { collector = Reducer.toJSMultiMap(), } = options; return AsyncReducer.create(() => AsyncReducer.from(collector).compile(), async (state, value, index) => { const key = await valueToKey(value, index); await state.next([key, value]); return state; }, (state) => state.getOutput()); }; /** * Returns an `AsyncReducer` that feeds incoming values to all reducers in the provided `reducers` source, and halts when the first * reducer in the array is halted and returns the output of that reducer. Returns the `otherwise` value if no reducer is yet halted. * @param reducers - a stream source of async reducers that will receive the incoming values * @param otherwise - a fallback value to return if none of the reducers has been halted * @typeparam T - the input value type * @typeparam R - the output value type * @typeparam O - the fallback value type */ AsyncReducer.race = (reducers, otherwise) => { return AsyncReducer.create(async (initHalt) => { const instances = await Promise.all(Stream.from(reducers).mapPure((reducer) => AsyncReducer.from(reducer).compile())); const doneInstance = instances.find((instance) => instance.halted); if (undefined !== doneInstance) { initHalt(); } return { instances, doneInstance }; }, async (state, next, _, halt) => { for (const instance of state.instances) { await instance.next(next); if (instance.halted) { state.doneInstance = instance; halt(); return state; } } return state; }, (state) => state.doneInstance === undefined ? AsyncOptLazy.toMaybePromise(otherwise) : state.doneInstance.getOutput()); }; class InvalidCombineShapeError extends ErrBase.CustomError { constructor() { super('Invalid reducer combine shape supplied'); } } AsyncReducer.InvalidCombineShapeError = InvalidCombineShapeError; class ReducerHaltedError extends ErrBase.CustomError { constructor() { super('A halted reducer cannot receive more values'); } } AsyncReducer.ReducerHaltedError = ReducerHaltedError; class ReducerClosedError extends ErrBase.CustomError { constructor() { super('A closed async reducer cannot perform more actions'); } } AsyncReducer.ReducerClosedError = ReducerClosedError; class ReducerNotInitializedError extends ErrBase.CustomError { constructor() { super('The async reducer instance was not yet initialized'); } } AsyncReducer.ReducerNotInitializedError = ReducerNotInitializedError; /** * Returns an `AsyncReducer` that combines multiple input `reducers` according to the given "shape" by providing input values to all of them and collecting the outputs in the shape. * @typeparam T - the input value type for all the reducers * @typeparam S - the desired result shape type * @param shape - a shape defining where reducer outputs will be located in the result. It can consist of a single reducer, an array of shapes, or an object with string keys and shapes as values. */ function combine(shape) { if (shape instanceof AsyncReducer.Base) { return shape; } if (shape instanceof Reducer.Base) { return AsyncReducer.from(shape); } if (Array.isArray(shape)) { return combineArr(...shape.map((item) => AsyncReducer.combine(item))); } if (typeof shape === 'object' && shape !== null) { const result = {}; for (const key in shape) { result[key] = AsyncReducer.combine(shape[key]); } return combineObj(result); } throw new AsyncReducer.InvalidCombineShapeError(); } AsyncReducer.combine = combine; /** * Returns an `AsyncReducer` instance that first applies this reducer, and then applies the given `next` reducer to each output produced * by the previous reducer. * @typeparam I - the input type of the `reducer1` reducer * @typeparam O1 - the output type of the `reducer1` reducer * @typeparam O2 - the output type of the `reducer2` reducer * @typeparam O3 - the output type of the `reducer3` reducer * @typeparam O4 - the output type of the `reducer4` reducer * @typeparam O5 - the output type of the `reducer5` reducer * @param reducer1 - the next reducer to apply to each output of this reducer. * @param reducer2 - (optional) the next reducer to apply to each output of this reducer. * @param reducer3 - (optional) the next reducer to apply to each output of this reducer. * @param reducer4 - (optional) the next reducer to apply to each output of this reducer. * @param reducer5 - (optional) the next reducer to apply to each output of this reducer. * @example * ```ts * AsyncStream * .from(Stream.of(1, 2, 3)) * .reduce( * AsyncReducer.pipe(Reducer.product, Reducer.sum) * ) * // => 9 * ``` */ AsyncReducer.pipe = (...nextReducers) => { if (nextReducers.length < 2) { RimbuError.throwInvalidUsageError('Reducer.pipe should have at least two arguments'); } const [current, next, ...others] = nextReducers; if (others.length > 0) { return AsyncReducer.pipe(current, AsyncReducer.pipe(next, ...others)); } return AsyncReducer.create(async (inithalt) => { const currentInstance = await AsyncReducer.from(current).compile(); const nextInstance = await AsyncReducer.from(next).compile(); if (currentInstance.halted || nextInstance.halted) { inithalt(); } return { currentInstance, nextInstance, }; }, async (state, next, index, halt) => { const { currentInstance, nextInstance } = state; await currentInstance.next(next); await nextInstance.next(await currentInstance.getOutput()); if (currentInstance.halted || nextInstance.halted) { halt(); } return state; }, async (state, index, halted) => { if (halted && index === 0) { await state.nextInstance.next(await state.currentInstance.getOutput()); } return state.nextInstance.getOutput(); }, async (state, err) => { await Promise.all([ state.currentInstance.onClose(err), state.nextInstance.onClose(err), ]); }); }; })(AsyncReducer || (AsyncReducer = {})); //# sourceMappingURL=async-reducer.mjs.map