ixfx
Version:
Bundle of ixfx libraries
1,888 lines (1,865 loc) • 83 kB
JavaScript
import { n as __exportAll } from "./chunk-CaR5F9JI.js";
import { Et as isEqualDefault, H as continuously, Lt as toStringDefault, Ot as isEqualValueIgnoreOrder, R as elapsedSince, _t as some$3, f as promiseFromEvent, h as intervalToMs, n as sleep, y as compareIterableValuesShallow } from "./src-BUqDa_u7.js";
import { M as resultThrow, v as integerTest } from "./src-C_hvyftg.js";
import { F as isEqual } from "./src-CxEyGbiK.js";
import { a as sum$1, i as rank$1, n as max$4, o as tally$1, r as min$4, t as average$1 } from "./basic-Bepd6Tc6.js";
import { w as numberArrayCompute } from "./src-Cebc3sfq.js";
//#region ../packages/iterables/src/guard.ts
const isAsyncIterable = (v) => {
if (typeof v !== `object`) return false;
if (v === null) return false;
return Symbol.asyncIterator in v;
};
const isIterable = (v) => {
if (typeof v !== `object`) return false;
if (v === null) return false;
return Symbol.iterator in v;
};
//#endregion
//#region ../packages/iterables/src/async.ts
var async_exports = /* @__PURE__ */ __exportAll({
asCallback: () => asCallback$3,
chunks: () => chunks$2,
concat: () => concat$2,
dropWhile: () => dropWhile$2,
equals: () => equals$2,
every: () => every$2,
fill: () => fill$2,
filter: () => filter$3,
find: () => find$2,
flatten: () => flatten$2,
forEach: () => forEach$2,
fromArray: () => fromArray$2,
fromIterable: () => fromIterable$2,
last: () => last$2,
map: () => map$2,
max: () => max$3,
min: () => min$3,
nextWithTimeout: () => nextWithTimeout,
reduce: () => reduce$3,
repeat: () => repeat$1,
slice: () => slice$2,
some: () => some$2,
toArray: () => toArray$2,
unique: () => unique$2,
uniqueByValue: () => uniqueByValue$2,
until: () => until$2,
withDelay: () => withDelay,
zip: () => zip$2
});
/**
* Yield values from `array`, one at a time.
* Use `interval` to add time between each item.
* The first item is yielded without delay.
*
* @param array Array of values
* @param interval Interval (defaults: 1ms)
*/
async function* fromArray$2(array, interval = 1) {
for (const v of array) {
yield v;
await sleep(interval);
}
}
/**
* Yield values from `iterable`, one at a time.
* Use `interval` to add time between each item.
* The first item is yielded without delay.
* @param iterable Iterable or AsyncIterable
* @param [interval=1] Interval to wait between yield
*/
async function* fromIterable$2(iterable, interval = 1) {
for await (const v of iterable) {
yield v;
await sleep(interval);
}
}
async function* chunks$2(it, size) {
let buffer = [];
for await (const v of it) {
buffer.push(v);
if (buffer.length === size) {
yield buffer;
buffer = [];
}
}
if (buffer.length > 0) yield buffer;
}
async function* concat$2(...its) {
for await (const it of its) yield* it;
}
async function* dropWhile$2(it, f) {
for await (const v of it) if (!f(v)) yield v;
}
/**
* Loops over a generator until it finishes, calling `callback`.
* Useful if you don't care about the value generator produces, just the number of loops.
*
* In this version, we do a `for await of` over `gen`, and also `await callback()`.
* ```js
* await until(count(5), () => {
* // do something 5 times
* });
* ```
*
* If you want the value from the generator, use a `for of` loop as usual.
*
* If `callback` explicitly returns _false_, the generator is aborted.
* @param it Generator to run
* @param callback Code to call for each iteration
*/
const until$2 = async (it, callback) => {
for await (const _ of it) {
const value = await callback();
if (typeof value === `boolean` && !value) break;
}
};
/**
* This generator will repeat another generator up until some condition. This is the version
* that can handle async generators.
*
* For example, {@link https://api.ixfx.fun/_ixfx/numbers/count/ @ixfx/numbers.count} will count from 0..number and then finish:
* ```js
* import { count } from '@ixfx/numbers'
* for (const v of count(5)) {
* // v: 0, 1, 2, 3, 4
* }
* ```
*
* But what if we want to repeat the count? We have to provide a function to create the generator,
* rather than using the generator directly, since it's "one time use"
* ```js
* for await (const v of repeat(() => count(5))) {
* // v: 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, ...
* // warning: never ends
* }
* ```
*
* Limiting the number of repeats can be done by passing in extra parameters
* ```js
* repeat(generator, { count: 5} ); // Iterate over `generator` five times
* ```
*
* ```js
* const ac = new AbortController();
* repeat(generator, { signal: ac.signal }); // Pass in signal
* ...
* ac.abort(); // Trigger signal at some point
* ```
* @param genCreator
* @param repeatsOrSignal
*/
const repeat$1 = async function* (genCreator, repeatsOrSignal) {
const repeats = typeof repeatsOrSignal === `number` ? repeatsOrSignal : Number.POSITIVE_INFINITY;
const signal = typeof repeatsOrSignal === `number` ? void 0 : repeatsOrSignal;
let count = repeats;
while (true) {
for await (const v of genCreator()) {
yield v;
if (signal?.aborted) break;
}
if (Number.isFinite(repeats)) {
count--;
if (count === 0) break;
}
if (signal?.aborted) break;
}
};
/**
* Returns true if items in two iterables are equal, and in the same order.
*
* Uses === semantics by default.
* @param it1
* @param it2
* @param equality
* @returns
*/
async function equals$2(it1, it2, comparerOrKey = isEqualDefault) {
const iit1 = it1[Symbol.asyncIterator]();
const iit2 = it2[Symbol.asyncIterator]();
let isUnknownFunction = true;
let eqFunction;
let keyFunction;
while (true) {
const index1 = await iit1.next();
const index2 = await iit2.next();
const v1 = index1.value;
const v2 = index2.value;
if (isUnknownFunction) {
if (typeof comparerOrKey(v1, v2) === `string`) keyFunction = comparerOrKey;
else eqFunction = comparerOrKey;
isUnknownFunction = false;
}
if (keyFunction) {
if (keyFunction(v1) !== keyFunction(v2)) return false;
} else if (eqFunction) {
if (!eqFunction(v1, v2)) return false;
} else throw new Error(`Something wrong with comparer function?`);
if (index1.done ?? index2.done) return index1.done && index2.done;
}
}
async function every$2(it, f) {
for await (const v of it) if (!await f(v)) return false;
return true;
}
async function* fill$2(it, v) {
for await (const _ of it) yield v;
}
/**
* Filters an iterable, only yielding items which match `f`.
*
* ```js
* filter([1, 2, 3, 4], e => e % 2 == 0);
* returns [2, 4]
* ```
* @param it
* @param f
*/
async function* filter$3(it, f) {
for await (const v of it) {
if (!await f(v)) continue;
yield v;
}
}
async function find$2(it, f) {
for await (const v of it) if (await f(v)) return v;
}
async function* flatten$2(it) {
for await (const v of it) if (typeof v === `object`) {
if (Array.isArray(v)) for (const vv of v) yield vv;
else if (isAsyncIterable(v)) for await (const vv of v) yield vv;
else if (isIterable(v)) for (const vv of v) yield vv;
} else yield v;
}
/**
* Iterates over an async iterable or array, calling `fn` for each value, with optional
* interval between each loop. If the async `fn` returns _false_, iterator cancels.
*
* ```
* import { forEach } from "@ixfx/flow.js"
* // Prints items from array every second
* await forEach([0,1,2,3], i => console.log(i), 1000);
* ```
*
* ```
* // Retry up to five times, with 5 seconds between each attempt
* await forEach(count(5), i=> {
* try {
* await doSomething();
* return false; // Succeeded, exit early
* } catch (ex) {
* console.log(ex);
* return true; // Keep trying
* }
* }, 5000);
* ```
* @param iterator Iterable thing to loop over
* @param fn Function to invoke on each item. If it returns _false_ loop ends.
* @param options Options
* @typeParam V Type of iterable
*/
const forEach$2 = async function(iterator, fn, options = {}) {
const interval = options.interval;
if (Array.isArray(iterator)) for (const x of iterator) {
const r = await fn(x);
if (typeof r === `boolean` && !r) break;
if (interval) await sleep(interval);
}
else for await (const x of iterator) {
const r = await fn(x);
if (typeof r === `boolean` && !r) break;
if (interval) await sleep(interval);
}
};
/**
* Returns last value from an iterable, or _undefined_
* if no values are generated
* @param it
*/
async function last$2(it, opts = {}) {
const abort = opts.abort;
let returnValue;
for await (const value of it) {
if (abort?.aborted) return void 0;
returnValue = value;
}
return returnValue;
}
/**
* Maps an iterable through function `f`
* ```js
* // For every input value, multiply it by itself
* map([1, 2, 3], e => e*e)
* // Yields: 1, 4, 9
* ```
*
* It can also be used to transform types:
* ```js
* map([1, 2, 3], e => { value: e });
* // Yields: { value: 1 }, { value: 2 }, { value: 3 }
* ```
* @param it
* @param f
*/
async function* map$2(it, f) {
for await (const v of it) yield f(v);
}
async function* max$3(it, gt = ((a, b) => a > b)) {
let max;
for await (const v of it) {
if (max === void 0) {
max = v;
yield max;
continue;
}
if (gt(v, max)) {
max = v;
yield v;
}
}
}
/**
* Returns the minimum seen of an iterable as it changes.
* Streaming result: works with endless iterables.
*
* Note that `gt` function returns true if A is _greater_ than B, even
* though we're looking for the minimum.
*
* ```js
* // Rank objects based on 'v' value
* const rank = (a,b) => a.v > b.v;
* min([
* {i:0,v:1},
* {i:1,v:9},
* {i:2,v:-2}
* ], rank);
* // Yields: {i:2, v:1}, {i:2,v:-2}
* ```
* @param it Iterable
* @param gt Should return _true_ if `a` is greater than `b`.
* @returns
*/
async function* min$3(it, gt = (a, b) => a > b) {
let min;
for await (const v of it) {
if (min === void 0) {
min = v;
yield min;
continue;
}
if (gt(min, v)) {
min = v;
yield v;
}
}
return min;
}
async function reduce$3(it, f, start) {
for await (const v of it) start = f(start, v);
return start;
}
/**
* Calls `callback` whenever the async generator produces a value.
*
* When using `asCallback`, call it with `await` to let generator
* run its course before continuing:
* ```js
* await asCallback(tick({ interval:1000, loops:5 }), x => {
* // Gets called 5 times, with 1000ms interval
* });
* console.log(`Hi`); // Prints after 5 seconds
* ```
*
* Or if you skip the `await`, code continues and callback will still run:
* ```js
* asCallback(tick({ interval: 1000, loops: 5}), x => {
* // Gets called 5 times, with 1000ms interval
* });
* console.log(`Hi`); // Prints immediately
* ```
* @param input
* @param callback
*/
async function asCallback$3(input, callback, onDone) {
for await (const value of input) callback(value);
if (onDone) onDone();
}
async function* slice$2(it, start = 0, end = Number.POSITIVE_INFINITY) {
const iit = it[Symbol.asyncIterator]();
if (end < start) throw new Error(`Param 'end' should be more than 'start'`);
for (; start > 0; start--, end--) await iit.next();
for await (const v of it) if (end-- > 0) yield v;
else break;
}
/**
* Enumerates over an input iterable, with a delay between items.
* @param it
* @param delay
*/
async function* withDelay(it, delay) {
for (const v of it) {
await sleep(delay);
yield v;
}
}
/***
* Returns the next IteratorResult,
* throwing an error if it does not happen
* within `interval` (default: 1s)
*/
async function nextWithTimeout(it, options) {
const ms = intervalToMs(options, 1e3);
const value = await Promise.race([(async () => {
await sleep({
millis: ms,
signal: options.signal
});
})(), (async () => {
return await it.next();
})()]);
if (value === void 0) throw new Error(`Timeout`);
return value;
}
async function some$2(it, f) {
for await (const v of it) if (await f(v)) return true;
return false;
}
/**
* Returns an array of values from an iterator.
*
* ```js
* const data = await toArray(adsrIterable(opts, 10));
* ```
*
* Note: If the iterator is infinite, be sure to provide limits via the options.
* ```js
* // Return maximum five items
* const data = await toArray(iterable, { limit: 5 });
* // Return results for a maximum of 5 seconds
* const data = await toArray(iterable, { elapsed: 5000 });
* ```
* Note that limits are ORed, `toArray` will finish if either of them is true.
*
* @param it Asynchronous iterable
* @param options Options when converting to array
* @returns
*/
async function toArray$2(it, options = {}) {
const result = [];
const iterator = it[Symbol.asyncIterator]();
const started = Date.now();
const maxItems = options.limit ?? Number.POSITIVE_INFINITY;
const whileFunction = options.while;
const maxElapsed = intervalToMs(options.elapsed, Number.POSITIVE_INFINITY);
while (result.length < maxItems && Date.now() - started < maxElapsed) {
if (whileFunction) {
if (!whileFunction(result.length)) break;
}
const r = await iterator.next();
if (r.done) break;
result.push(r.value);
}
return result;
}
async function* unique$2(iterable) {
const buffer = [];
const itera = Array.isArray(iterable) ? iterable : [iterable];
for await (const it of itera) for await (const v of it) {
if (buffer.includes(v)) continue;
buffer.push(v);
yield v;
}
}
async function* uniqueByValue$2(input, toString = toStringDefault, seen = /* @__PURE__ */ new Set()) {
for await (const v of input) {
const key = toString(v);
if (seen.has(key)) continue;
seen.add(key);
yield v;
}
}
/**
* Returns unique items from iterables, given a particular key function
* ```js
* unique([{i:0,v:2},{i:1,v:3},{i:2,v:2}], e => e.v);
* Yields: [{i:0,v:2},{i:1,v:3}]
* @param it
* @param f
*/
/**
* Combine same-positioned items from several iterables
* ```js
* zip( [1, 2, 3], [4, 5, 6], [7, 8, 9] );
* Yields: [ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]
* ```
* @param its
* @returns
*/
async function* zip$2(...its) {
const iits = its.map((it) => it[Symbol.asyncIterator]());
while (true) {
const vs = await Promise.all(iits.map((it) => it.next()));
if (vs.some((v) => v.done)) return;
yield vs.map((v) => v.value);
}
}
//#endregion
//#region ../packages/iterables/src/sync/slice.ts
function* slice$1(it, start = 0, end = Number.POSITIVE_INFINITY) {
if (end < start) throw new Error(`Param 'end' should be more than 'start'`);
if (start < 0) throw new Error(`Param 'start' should be at least 0`);
let index = 0;
for (const v of it) {
if (index < start) {
index++;
continue;
}
if (index > end) break;
yield v;
index++;
}
}
//#endregion
//#region ../packages/iterables/src/sync/reduce.ts
function reduce$2(it, f, start) {
for (const v of it) start = f(start, v);
return start;
}
//#endregion
//#region ../packages/iterables/src/sync.ts
var sync_exports = /* @__PURE__ */ __exportAll({
asCallback: () => asCallback$2,
chunks: () => chunks$1,
chunksOverlapping: () => chunksOverlapping,
concat: () => concat$1,
dropWhile: () => dropWhile$1,
equals: () => equals$1,
every: () => every$1,
fill: () => fill$1,
filter: () => filter$2,
find: () => find$1,
first: () => first,
flatten: () => flatten$1,
forEach: () => forEach$1,
fromArray: () => fromArray$1,
fromIterable: () => fromIterable$1,
last: () => last$1,
map: () => map$1,
max: () => max$2,
min: () => min$2,
next: () => next,
reduce: () => reduce$2,
repeat: () => repeat,
slice: () => slice$1,
some: () => some$1,
toArray: () => toArray$1,
unique: () => unique$1,
uniqueByValue: () => uniqueByValue$1,
until: () => until$1,
yieldNumber: () => yieldNumber,
zip: () => zip$1
});
function* uniqueByValue$1(input, toString = toStringDefault, seen = /* @__PURE__ */ new Set()) {
for (const v of input) {
const key = toString(v);
if (seen.has(key)) continue;
seen.add(key);
yield v;
}
}
/**
* Calls `callback` whenever the generator produces a value.
*
* When using `asCallback`, call it with `await` to let generator
* run its course before continuing:
* ```js
* await asCallback(tick({ interval:1000, loops:5 }), x => {
* // Gets called 5 times, with 1000ms interval
* });
* console.log(`Hi`); // Prints after 5 seconds
* ```
*
* Or if you skip the `await`, code continues and callback will still run:
* ```js
* asCallback(tick({ interval: 1000, loops: 5}), x => {
* // Gets called 5 times, with 1000ms interval
* });
* console.log(`Hi`); // Prints immediately
* ```
* @param input
* @param callback
*/
function asCallback$2(input, callback, onDone) {
for (const value of input) callback(value);
if (onDone) onDone();
}
/**
* Returns a function that yields a value from a generator.
* ```js
* const spring = yieldNumber(Oscillators.spring());
*
* spring(); // latest value
* ```
*
* Instead of:
* ```js
* const spring = Oscillators.spring();
*
* spring.next().value
* ```
*
* A `defaultValue` can be provided if the source generator returns undefined:
* ```js
* const spring = yieldNumber(Oscillators.spring(), 0);
* spring(); // Returns 0 if the generator returns undefined
* ```
* @param generator
* @param defaultValue
* @returns
*/
function yieldNumber(generator, defaultValue) {
return () => {
const v = generator.next().value;
if (v === void 0) return defaultValue;
return v;
};
}
/**
* Return first value from an iterable, or _undefined_ if
* no values are generated
* @param it
* @returns
*/
function first(it) {
for (const value of it) return value;
}
/**
* Returns last value from an iterable, or _undefined_
* if no values are generated
* @param it
*/
function last$1(it) {
let returnValue;
for (const value of it) returnValue = value;
return returnValue;
}
/**
* Yields chunks of the iterable `it` such that the end of a chunk is the
* start of the next chunk.
*
* Eg, with the input [1,2,3,4,5] and a size of 2, we would get back
* [1,2], [2,3], [3,4], [4,5].
*
*
* @param it
* @param size
* @returns
*/
function* chunksOverlapping(it, size) {
if (size <= 1) throw new Error(`Size should be at least 2`);
let buffer = [];
for (const v of it) {
buffer.push(v);
if (buffer.length === size) {
yield buffer;
buffer = [buffer.at(-1)];
}
}
if (buffer.length <= 1) return;
if (buffer.length > 0) yield buffer;
}
function* chunks$1(it, size) {
let buffer = [];
for (const v of it) {
buffer.push(v);
if (buffer.length === size) {
yield buffer;
buffer = [];
}
}
if (buffer.length > 0) yield buffer;
}
function* concat$1(...its) {
for (const it of its) yield* it;
}
function* dropWhile$1(it, f) {
for (const v of it) if (!f(v)) yield v;
}
/**
* Loops over a generator until it finishes, calling `callback`.
* Useful if you don't care about the value generator produces, just the number of loops.
*
* ```js
* until(count(5), () => {
* // do something 5 times
* });
* ```
*
* If you want the value from the generator, use a `for of` loop as usual.
* If `callback` explicitly returns _false_, the generator is aborted.
* @param it Generator to run
* @param callback Code to call for each iteration
*/
const until$1 = (it, callback) => {
for (const _ of it) {
const value = callback();
if (typeof value === `boolean` && !value) break;
}
};
const next = (it) => {
return () => {
const r = it.next();
if (r.done) return;
return r.value;
};
};
/**
* Returns true if items in two iterables are equal, by value and order
*
* By default uses === comparison
* @param it1
* @param it2
* @param comparerOrKey Function to produce a key for value or compare two values
* @returns
*/
function equals$1(it1, it2, comparerOrKey = isEqualDefault) {
let isUnknownFunction = true;
let eqFunction;
let keyFunction;
while (true) {
const index1 = it1.next();
const index2 = it2.next();
const v1 = index1.value;
const v2 = index2.value;
if (isUnknownFunction) {
if (typeof comparerOrKey(v1, v2) === `string`) keyFunction = comparerOrKey;
else eqFunction = comparerOrKey;
isUnknownFunction = false;
}
if (keyFunction) {
if (keyFunction(v1) !== keyFunction(v2)) return false;
} else if (eqFunction) {
if (!eqFunction(v1, v2)) return false;
} else throw new Error(`Something wrong with comparer function?`);
if (index1.done ?? index2.done) return index1.done && index2.done;
}
}
function every$1(it, f) {
for (const v of it) if (!f(v)) return false;
return true;
}
function* fill$1(it, v) {
for (const _ of it) yield v;
}
/**
* Iterates over `iterator` (iterable/array), calling `fn` for each value.
* If `fn` returns _false_, iterator cancels.
*
* Over the default JS `forEach` function, this one allows you to exit the
* iteration early.
*
* @example
* ```js
* import { Sync } from "@ixfx/iterables.js"
* Sync.forEach(count(5), () => console.log(`Hi`)); // Prints `Hi` 5x
* Sync.forEach(count(5), i => console.log(i)); // Prints 0 1 2 3 4
* Sync.forEach([0,1,2,3,4], i => console.log(i)); // Prints 0 1 2 3 4
* ```
*
* Use {@link forEach} if you want to use an async `iterator` and async `fn`.
*
* Alternatives:
* * {@link https://api.ixfx.fun/_ixfx/flow/repeat/ @ixfx/flow.repeat}/{@link https://api.ixfx.fun/_ixfx/flow/repeatSync/ @ixfx/flow.repeatSync}: if you want to call something a given number of times and get the result
* @param iterator Iterable or array
* @typeParam T Type of iterable's values
* @param fn Function to call for each item. If function returns _false_, iteration cancels
*/
function forEach$1(iterator, fn) {
for (const v of iterator) {
const result = fn(v);
if (typeof result === `boolean` && !result) break;
}
}
/**
* ```js
* filter([1, 2, 3, 4], e => e % 2 == 0);
* returns [2, 4]
* ```
* @param it
* @param f
*/
function* filter$2(it, f) {
for (const v of it) {
if (!f(v)) continue;
yield v;
}
}
function find$1(it, f) {
for (const v of it) if (f(v)) return v;
}
function* flatten$1(it) {
for (const v of it) if (typeof v === `object`) {
if (Array.isArray(v)) for (const vv of v) yield vv;
else if (isIterable(v)) for (const vv of v) yield vv;
} else yield v;
}
/**
* Maps an iterable of type `V` to type `X`.
* ```js
* map([1, 2, 3], e => e*e)
* returns [1, 4, 9]
* ```
* @param it
* @param f
*/
function* map$1(it, f) {
for (const v of it) yield f(v);
}
function* max$2(it, gt = (a, b) => a > b) {
let max;
for (const v of it) {
if (max === void 0) {
max = v;
yield max;
continue;
}
if (gt(v, max)) {
max = v;
yield max;
}
}
return max;
}
function* min$2(it, gt = (a, b) => a > b) {
let min;
for (const v of it) {
if (min === void 0) {
min = v;
yield min;
}
if (gt(min, v)) {
min = v;
yield min;
}
}
}
function some$1(it, f) {
for (const v of it) if (f(v)) return true;
return false;
}
function* repeat(genCreator, repeatsOrSignal) {
const repeats = typeof repeatsOrSignal === `number` ? repeatsOrSignal : Number.POSITIVE_INFINITY;
const signal = typeof repeatsOrSignal === `number` ? void 0 : repeatsOrSignal;
let count = repeats;
while (true) {
for (const v of genCreator()) {
yield v;
if (signal?.aborted) break;
}
if (Number.isFinite(repeats)) {
count--;
if (count === 0) break;
}
if (signal?.aborted) break;
}
}
function* unique$1(iterable) {
const buffer = [];
let itera = [];
itera = Array.isArray(iterable) ? iterable : [iterable];
for (const it of itera) for (const v of it) {
if (buffer.includes(v)) continue;
buffer.push(v);
yield v;
}
}
/**
* Combine same-positioned items from several iterables
* ```js
* zip( [1, 2, 3], [4, 5, 6], [7, 8, 9] );
* Yields: [ [1, 4, 7], [2, 5, 8], [3, 6, 9] ]
* ```
* @param its
* @returns
*/
function* zip$1(...its) {
const iits = its.map((it) => it[Symbol.iterator]());
while (true) {
const vs = iits.map((it) => it.next());
if (vs.some((v) => v.done)) return;
yield vs.map((v) => v.value);
}
}
function* fromIterable$1(iterable) {
for (const v of iterable) yield v;
}
/**
* Returns an array of values from an iterator.
*
* ```js
* const data = await toArray(adsrIterable(opts, 10));
* ```
*
* Note: If the iterator is infinite, be sure to provide a limit via the options or the function
* will never return.
*
* @param it Asynchronous iterable
* @param options Options when converting to array.
* @returns
*/
function toArray$1(it, options = {}) {
const result = [];
const started = Date.now();
const whileFunction = options.while;
const maxItems = options.limit ?? Number.POSITIVE_INFINITY;
const maxElapsed = intervalToMs(options.elapsed, Number.POSITIVE_INFINITY);
for (const v of it) {
if (whileFunction) {
if (!whileFunction(result.length)) break;
}
if (result.length >= maxItems) break;
if (Date.now() - started > maxElapsed) break;
result.push(v);
}
return result;
}
/**
* Yield values from `array`, one at a time.
* Use `interval` to add time between each item.
* The first item is yielded without delay.
* @param array Array of values
*/
function* fromArray$1(array) {
for (const v of array) yield v;
}
//#endregion
//#region ../packages/iterables/src/chain/utility.ts
function isGenFactoryNoInput(c) {
if (typeof c !== `object` || c === null) return false;
if (!(`_type` in c)) return false;
if (c._type === `GenFactoryNoInput`) return true;
return false;
}
/**
* Wrap the primitive value as generator
* @param value
*/
function* primitiveToGenerator(value) {
yield value;
}
/**
* Wrap the primitive value as an async generator
* @param value
*/
async function* primitiveToAsyncGenerator(value) {
yield value;
await sleep(1);
}
/**
* Resolve the array, data or function to a Generator
* @param input
* @returns
*/
function resolveToGen(input) {
if (Array.isArray(input)) {
const a = input.values();
a._name = `arrayInput`;
return a;
} else if (typeof input === `number` || typeof input === `boolean` || typeof input === `string`) return primitiveToGenerator(input);
else if (typeof input === `function`) return input();
return input;
}
/**
* Resolve the data, primitive or function to an AsyncGenerator
* @param input
* @returns
*/
function resolveToAsyncGen(input) {
if (input === void 0) return;
if (Array.isArray(input)) return fromArray$2(input);
else if (typeof input === `number` || typeof input === `boolean` || typeof input === `string`) return primitiveToAsyncGenerator(input);
else if (typeof input === `function`) return input();
else if (isAsyncIterable(input)) return input;
return fromIterable$2(input);
}
//#endregion
//#region ../packages/iterables/src/util/dom.ts
function resolveEl(elOrQuery) {
if (typeof elOrQuery === `string`) {
const el = document.querySelector(elOrQuery);
if (!el) throw new Error(`Element not found '${elOrQuery}'`);
return el;
}
return elOrQuery;
}
//#endregion
//#region ../packages/iterables/src/chain/dom.ts
var dom_exports = /* @__PURE__ */ __exportAll({
perValue: () => perValue,
query: () => query
});
const createMap = (key) => {
const keyFunction = key ?? ((value) => value);
const map = /* @__PURE__ */ new Map();
return {
has(key) {
return map.has(keyFunction(key));
},
get(key) {
return map.get(keyFunction(key));
},
set(key, value) {
map.set(keyFunction(key), value);
},
entries() {
return map.entries();
},
delete(key) {
map.delete(key);
}
};
};
/**
* Creates a HTML element per value. By default compares
* values by `JSON.stringify`. Set `byReference:true` to
* compare values based on reference. Or provide a toString
* function via `key`.
*
* ```js
* // Generate a random number between 0...4 every second
* const looper = Generators.interval(() => Math.floor(Math.random()*5), 1000);
*
* // Make a chain
* const ch = Chains.run(
* looper,
* Chains.Links.delay({before:1000}),
* Chains.Dom.perValue()
* );
*
* setTimeout(async () => {
* for await (const v of ch) {
* const {el,value} = v;
* el.textContent = `${value} - ${Date.now().toString()}`;
* }
* console.log(`ch iteration done`);
* });
* ```
*/
function perValue(options = {}) {
const byReference = options.byReference;
const tagName = options.tagName ?? `div`;
if (byReference && options.key) throw new Error(`byReference and key options are mutually exclusive`);
const map = createMap(byReference ? void 0 : options.key ?? toStringDefault);
const parentEl = resolveEl(options.parentEl ?? document.body);
const usedElements = /* @__PURE__ */ new Set();
async function* perValue(input) {
for await (const value of resolveToGen(input)) {
let el = map.get(value);
if (!el) {
el = document.createElement(tagName);
map.set(value, el);
if (options.beforeInsert) options.beforeInsert(el);
parentEl.append(el);
if (options.afterInsert) options.afterInsert(el);
}
usedElements.add(el);
yield {
el,
value
};
}
for (const [id, el] of map.entries()) {
if (usedElements.has(el)) continue;
if (options.beforeRemove) options.beforeRemove(el);
el.remove();
map.delete(id);
}
}
perValue._name = `dom.perValue`;
return perValue;
}
/**
* From an input stream of strings, yields an output of HTMLElememnts
* @param options
* @returns
*/
function query(options = {}) {
const baseElement = options.baseElement ?? document;
async function* query(input) {
const gen = resolveToGen(input);
for await (const value of gen) for (const element of baseElement.querySelectorAll(value)) yield element;
}
query._name = `dom.query`;
return query;
}
//#endregion
//#region ../packages/iterables/src/chain/links.ts
var links_exports = /* @__PURE__ */ __exportAll({
average: () => average,
chunk: () => chunk,
debounce: () => debounce,
delay: () => delay,
drop: () => drop,
duration: () => duration,
filter: () => filter$1,
max: () => max$1,
min: () => min$1,
rank: () => rank,
rankArray: () => rankArray,
reduce: () => reduce$1,
sum: () => sum,
take: () => take,
tally: () => tally,
transform: () => transform
});
/**
* Transform values from one type to another. Just like a map function.
* @param transformer
* @returns
*/
function transform(transformer) {
async function* transform(input) {
input = resolveToGen(input);
for await (const value of input) yield transformer(value);
}
transform._name = `transform`;
return transform;
}
/**
* Take `limit` number of results from the stream, before closing
* @param limit
* @returns
*/
function take(limit) {
async function* take(input) {
input = resolveToGen(input);
let yielded = 0;
for await (const value of input) {
if (++yielded > limit) break;
yield value;
}
}
take._name = `take`;
return take;
}
/**
* Takes an array of values, flattening to a single one
* using the provided `reducer` function.
*
* ```js
* // Create a chain that flattens values
* const reduce = Chains.reduce(values => Math.max(...values));
* // Feed it a single input (an array), get a single output back:
* const result = await Chains.single(reduce, [ 1, 2, 3]); // 3
* ```
* @param reducer Function to reduce array of values to a single value
* @returns
*/
function reduce$1(reducer) {
async function* reduce(input) {
input = resolveToGen(input);
for await (const value of input) yield reducer(value);
}
reduce._name = `reduce`;
return reduce;
}
/**
* Allow values through until a duration has elapsed. After
* that, the chain stops.
* @param elapsed
* @returns
*/
function duration(elapsed) {
const durationMs = intervalToMs(elapsed, 0);
async function* duration(input) {
input = resolveToGen(input);
const elapsed = elapsedSince();
for await (const value of input) {
if (elapsed() > durationMs) break;
yield value;
}
}
duration._name = `duration`;
return duration;
}
/**
* Add delay before/after values are emitted from the input stream.
* @param options
* @returns
*/
function delay(options) {
const before = intervalToMs(options.before, 0);
const after = intervalToMs(options.after, 0);
async function* delay(input) {
input = resolveToGen(input);
for await (const value of input) {
if (before > 0) await sleep(before);
yield value;
if (after > 0) await sleep(after);
}
}
delay._name = `delay`;
return delay;
}
/**
* Ensure a minimum length of time between values.
* Values being produced too quickly are dropped.
*
* In the following example, only three values will be let through.
* ```js
* const chain = Chains.run(
* // Produce values every 10ms for 350ms
* Chains.From.timestamp({ interval: 10, elapsed: 350 }),
* // Only let a value through every 100ms
* Chains.Links.debounce(100)
* );
* ```
* @param rate
* @returns
*/
function debounce(rate) {
const rateMs = intervalToMs(rate, 0);
async function* debounce(input) {
input = resolveToGen(input);
let elapsed = elapsedSince();
for await (const value of input) {
if (elapsed() < rateMs) continue;
yield value;
elapsed = elapsedSince();
}
}
debounce._name = `debounce`;
return debounce;
}
/**
* Returns a running tally of how many items have been
* emitted from the input source.
* ```js
* const ch = Chains.run(
* Chains.From.timestamp({ interval: 100 }),
* Chains.Links.tally()
* );
*
* for await (const v of ch) {
* // Produces: 1, 2, 3 ... every 100ms
* }
* ```
* This is different than {@link sum} which adds up numeric values.
* By default it adds up individual array items
* @returns
*/
function tally(countArrayItems = true) {
async function* tally(input) {
input = resolveToGen(input);
const p = tally$1(countArrayItems);
for await (const v of input) yield p(v);
}
tally._name = `tally`;
return tally;
}
/**
* Returns the smallest value from the input.
* Can work with numbers or number[] as input.
* Non-numeric data is filtered out.
* @returns
*/
function min$1() {
async function* min(input) {
input = resolveToGen(input);
const p = min$4();
for await (const value of input) {
const x = p(value);
if (x === void 0) continue;
yield x;
}
}
min._name = `min`;
return min;
}
/**
* Returns the largest value from the input.
* - Non-numeric data is filtered out.
* - Looks inside of numeric arrays.
* @returns
*/
function max$1() {
async function* max(input) {
input = resolveToGen(input);
const p = max$4();
for await (const value of input) {
const x = p(value);
if (x === void 0) continue;
yield x;
}
}
max._name = `max`;
return max;
}
/**
* Emits the currently ranked 'highest' value from a stream. Only
* values exceeding the current highest are emitted.
*
* eg, if we are ranking on numerical value, an input stream of:
* ```
* 4, 1, 6, 10, 2, 4
* ```
*
* Results in the output stream of:
* ```
* 4, 6, 10
* ```
*
* @example
* ```js
* // Rank based on a field
* Chains.Links.rank((a,b) => {
* if (a.size > b.size) return `a`; // Signals the first param is highest
* if (a.size < b.size) return `b`; // Signals the second param is highest
* return `eq`;
* });
* ```
* @param options
* @returns
*/
function rank(r, options = {}) {
async function* rank(input) {
input = resolveToGen(input);
const p = rank$1(r, options);
for await (const value of input) {
const x = p(value);
if (x === void 0) continue;
yield x;
}
}
rank._name = `rank`;
return rank;
}
/**
* Emits the highest-ranked value from amongst an array of values.
*
* By default, it tracks the highest-ranked _between_ arrays.
*
* For example:
* ```js
* // Input
* [ [4,5,6], [1,2,3] ]
* // Outputs:
* [ 6 ]
* ```
*
* This behaviour can be modified with an option to only compare _within_ arrays.
* ```
* // Input
* [ [4,5,6], [1,2,3] ]
* // Output:
* [ 6, 3 ]
* ```
*
* Uses the `rank` option to determine which is more highly ranked.
* ```js
* Chains.Links.rankArray(
* (a, b) => {
* if (a > b) return `a`; // a is higher
* else if (b > a) return `b`; // b is higher
* return `eq`; // same
* }
* )
* ```
* @param options
* @returns
*/
function rankArray(r, options = {}) {
const includeType = options.includeType;
const emitEqualRanked = options.emitEqualRanked ?? false;
const emitRepeatHighest = options.emitRepeatHighest ?? false;
const withinArrays = options.withinArrays ?? false;
async function* rankArray(input) {
input = resolveToGen(input);
let best;
for await (const value of input) {
let emit = false;
if (withinArrays) best = void 0;
for (const subValue of value) {
if (includeType && typeof subValue !== includeType) continue;
if (best === void 0) {
best = subValue;
emit = true;
} else {
const result = r(subValue, best);
if (result == `a`) {
best = subValue;
emit = true;
} else if (result === `eq` && emitEqualRanked) emit = true;
else if (emitRepeatHighest) emit = true;
}
}
if (emit && best) yield best;
}
}
rankArray._name = `rankArray`;
return rankArray;
}
/**
* Returns the average from the input.
* Non-numeric values are filtered out.
* @returns
*/
function average() {
async function* average(input) {
input = resolveToGen(input);
const p = average$1();
for await (const value of input) {
const x = p(value);
if (x === void 0) continue;
yield x;
}
}
average._name = `average`;
return average;
}
/**
* Returns the total of the numeric values.
* Non-numeric values are filtered out.
* @returns
*/
function sum() {
async function* total(input) {
input = resolveToGen(input);
const p = sum$1();
for await (const value of input) {
const x = p(value);
if (x === void 0) continue;
yield x;
}
}
total._name = `total`;
return total;
}
/**
* Chunks an input stream into `size` chunks.
*
* Eg, with a chunk size of 3, the input stream of:
* 1, 2, 3, 4, 5, 6
* Yields:
* [ 1, 2, 3 ], [ 4, 5, 6 ]
*
* If `returnRemainders` is _true_ (default), any left over values are returned even if
* it's less than `size`.
* @param size
* @param returnRemainders If true (default) left over data that didn't make a full chunk is also returned
* @returns
*/
function chunk(size, returnRemainders = true) {
resultThrow(integerTest(size, `aboveZero`, `size`));
async function* chunk(input) {
input = resolveToGen(input);
let buffer = [];
for await (const value of input) {
buffer.push(value);
if (buffer.length >= size) {
yield buffer;
buffer = [];
}
}
if (returnRemainders && buffer.length > 0) yield buffer;
}
chunk._name = `chunk`;
return chunk;
}
/**
* Filters the input source, only allowing through
* data for which `predicate` returns _true_
*
* {@link drop}, on the other hand excludes values for which predicate is _true_
* @param predicate
* @returns
*/
function filter$1(predicate) {
async function* filter(input) {
input = resolveToGen(input);
for await (const value of input) if (predicate(value)) yield value;
}
filter._name = `filter`;
return filter;
}
/**
* Drops all values from input stream for which `predicate` returns _true_
*
* {@link filter}, on the other hand includes values where the predicate is _true_
* @param predicate
* @returns
*/
function drop(predicate) {
async function* drop(input) {
input = resolveToGen(input);
for await (const value of input) if (!predicate(value)) yield value;
}
drop._name = `drop`;
return drop;
}
//#endregion
//#region ../packages/iterables/src/chain/from/array.ts
/**
* Creates a chain from an array, reading values at a given interval
* @param it
* @param delay
* @returns
*/
function array(it, delay = 5) {
async function* fromArray() {
for (const v of it) {
await sleep(delay);
yield v;
}
}
fromArray._name = `fromArray`;
fromArray._type = `GenFactoryNoInput`;
return fromArray;
}
//#endregion
//#region ../packages/iterables/src/chain/from/event.ts
/**
* Create an iterable from an event
* @param target Event source (eg HTML element)
* @param name Name of event (eg. 'pointermove')
* @returns
*/
function event(target, name) {
async function* event() {
while (true) yield await promiseFromEvent(target, name);
}
event._name = `event`;
event._type = `GenFactoryNoInput`;
return event;
}
//#endregion
//#region ../packages/iterables/src/chain/from/function.ts
/**
* Produce a value from a callback. When
* the callback returns _undefined_ it is considered done.
*
* ```js
* const callback = () => Math.random();
*
* const f = Chains.From.func(callback);
* for await (const v of f) {
* // v is a new random number
* }
* ```
*
* In the context of a chain:
* ```js
* let produced = 0;
* const chain = Chains.chain<number, string>(
* // Produce incrementing numbers
* Chains.From.func(() => produced++),
* // Convert to `x:0`, `x:1` ...
* Chains.transform(v => `x:${ v }`),
* // Take first 5 results
* Chains.cap(5)
* );
* const data = await Chains.asArray(chain);
* ```
* @param callback
* @returns
*/
function func(callback) {
async function* fromFunction() {
while (true) {
const v = await callback();
if (typeof v === `undefined`) break;
yield v;
}
}
fromFunction._name = `fromFunction`;
fromFunction._type = `GenFactoryNoInput`;
return fromFunction;
}
//#endregion
//#region ../packages/iterables/src/chain/from/iterable.ts
/**
* Creates a chain from an interable
* @param it
* @returns
*/
function iterable(it) {
async function* fromIterable() {
for await (const v of it) yield v;
}
fromIterable._name = `fromIterable`;
fromIterable._type = `GenFactoryNoInput`;
return fromIterable;
}
//#endregion
//#region ../packages/iterables/src/chain/from/ticks.ts
/**
* Generate timestamp values at `interval` rate. By default it runs forever.
* Use `loops` or `elapsed` to set upper limit on how long it should run.
*
* ```js
* const c = Chains.From.timestamp({ interval: 1000 });
* ```
* Options:
* - `asClockTime`: If _true_, yielded value will be clock time rather than elapsed milliseconds
* @param options
* @returns
*/
function timestamp(options) {
const intervalMs = intervalToMs(options.interval, 0);
const asClockTime = options.asClockTime ?? false;
const loops = options.loops ?? Number.MAX_SAFE_INTEGER;
let looped = 0;
const durationTime = intervalToMs(options.elapsed, Number.MAX_SAFE_INTEGER);
async function* ts() {
const elapsed = elapsedSince();
while (looped < loops && elapsed() < durationTime) {
yield asClockTime ? Date.now() : elapsed();
const expectedTimeDiff = looped * intervalMs - elapsed();
await sleep(Math.max(0, intervalMs + expectedTimeDiff));
looped++;
}
}
ts._name = `timestamp`;
ts._type = `GenFactoryNoInput`;
return ts;
}
//#endregion
//#region ../packages/iterables/src/chain/from/index.ts
var from_exports = /* @__PURE__ */ __exportAll({
array: () => array,
event: () => event,
func: () => func,
iterable: () => iterable,
timestamp: () => timestamp
});
//#endregion
//#region ../packages/iterables/src/chain/add-to-array.ts
/**
* Adds values to the provided array as they are produced,
* mutating array.
*
* ```js
* const data = [];
* addToArray(data, tick({ interval: 1000, loops: 5 }));
* // Execution continues immediately, with `data` mutated over time
* ```
* @param valueToWrap
* @param array
*/
async function addToArray(array, valueToWrap) {
const outputType = typeof valueToWrap === `function` ? valueToWrap() : valueToWrap;
for await (const value of outputType) array.push(value);
}
//#endregion
//#region ../packages/iterables/src/chain/as-array.ts
/**
* Async function that returns the chain as an array of values
* ```js
* const values = await asArray(tick( { interval: 1000, loops: 5 }));
* // After 5 seconds, values will be a set of timestamps.
* ```
*
* If the chain is infinite, be sure to specify limits:
* ```js
* // Stop after we have five items
* const values = await asArray(chain, { limit: 5 });
* // Stop after 5 seconds has elapsed
* const values = await asArray(chain, { elapsed: 5000 });
* ```
* @param valueToWrap
* @returns
*/
async function asArray(valueToWrap, options = {}) {
return toArray$2(typeof valueToWrap === `function` ? valueToWrap() : valueToWrap, options);
}
//#endregion
//#region ../packages/iterables/src/chain/as-callback.ts
/**
* Calls `callback` whenever the chain/generator produces a value.
*
* When using `asCallback`, call it with `await` to let generator
* run its course before continuing:
* ```js
* await asCallback(tick({ interval:1000, loops:5 }), x => {
* // Gets called 5 times, with 1000ms interval
* });
* console.log(`Hi`); // Prints after 5 seconds
* ```
*
* Or if you skip the `await`, code continues and callback will still run:
* ```js
* asCallback(tick({ interval: 1000, loops: 5}), x => {
* // Gets called 5 times, with 1000ms interval
* });
* console.log(`Hi`); // Prints immediately
* ```
* @param valueToWrap
* @param callback
*/
async function asCallback$1(valueToWrap, callback, onDone) {
const outputType = typeof valueToWrap === `function` ? valueToWrap() : valueToWrap;
for await (const value of outputType) callback(value);
if (onDone) onDone();
}
//#endregion
//#region ../packages/iterables/src/chain/as-promise.ts
/**
* Treats the chain/generator as a promise
*
* ```js
* const ticker = asPromise(tick({ interval: 1000 }));
* const x = await ticker(); // Waits for 1000ms before giving a value
* ```
*
* This will only ever return one value. To return multiple values, it's necessary
* to call `asPromise` and `await` the result in a loop.
* @param valueToWrap
* @returns
*/
function asPromise(valueToWrap) {
let lastValue;
const outputType = typeof valueToWrap === `function` ? valueToWrap() : valueToWrap;
async function asPromise() {
const v = await outputType.next();
if (v.done) return;
lastValue = v.value;
return lastValue;
}
return asPromise;
}
//#endregion
//#region ../packages/iterables/src/chain/as-value.ts
/**
* Returns the most recent value from the chain/generator, or
* `initialValue` (defaulting to _undefined_) if no value
* has been emitted yet.
*
* ```js
* const ticker = asValue(tick({ interval: 1000 }));
* x = ticker(); // Get the most recent value
* ```
*
* Every time it's called, it fetches a new value from the generator, assuming
* it isn't already awaiting a result.
*
* In the meantime, the last value (or `initialValue`) is returned.
* @param valueToWrap Value to wrap
* @param initialValue Initial value
* @returns
*/
function asValue(valueToWrap, initialValue) {
let lastValue = initialValue;
let awaiting = false;
const outputType = typeof valueToWrap === `function` ? valueToWrap() : valueToWrap;
function asValue() {
if (!awaiting) {
awaiting = true;
outputType.next().then((v) => {
lastValue = v.value;
awaiting = false;
}).catch((error) => {
awaiting = false;
throw error;
});
}
return lastValue;
}
return asValue;
}
//#endregion
//#region ../packages/iterables/src/chain/combine-latest-to-array.ts
/**
* Monitors sources, storing as they happen to an array.
* Whenever a new value is emitted, the whole array is sent out, containing current
* values from each source, or _undefined_ if not yet emitted.
*
* The tempo of this stream will be set by the fastest source stream.
* See {@link syncToArray} to have pace determined by slowest source, and only
* send when each source has produce a new value compared to last time.
*
* Set `onSourceDone` to choose behaviour if a source stops. The default is
* 'break', meaning the whole combined stream stops.
*
* If a source completes and onSourceDone = 'allow', the option
* 'finalValue' sets the logic for what values get returned for the source.
* By default the setting is 'undefined', thus _undefined_ results. 'last' will be the last (old) value
* from that source.
*/
async function* combineLatestToArray(sources, options = {}) {
const onSourceDone = options.onSourceDone ?? `break`;
const finalValue = options.finalValue ?? `undefined`;
const afterEmit = options.afterEmit ?? `last`;
const inputs = sources.map((source, index) => ({
waiting: void 0,
index,
gen: resolveToGen(source),
done: false,
lastValue: void 0
}));
const isDone = () => !inputs.some((v) => !v.done);
const isWaiting = () => inputs.some((v) => v.waiting !== void 0);
const allEmpty = (d) => !d.some((v) => v !== void 0);
let lastEmitted = [];
while (true) {
const promises = [];
for (const input of inputs) {
if (input.done) continue;
if (input.waiting !== void 0) {
promises.push(input.waiting);
continue;
}
const p = Promise.resolve((async () => {
if (input.done) return input;
const v = await input.gen.next();
input.waiting = void 0;
if (v.done) {
input.done = true;
if (finalValue === `undefined`) input.lastValue = void 0;
} else input.lastValue = v.value;
return input;
})());
input.waiting = p;
promises.push(p);
}
const won = await Promise.race(promises);
if (`done` in won) {
if (won.done && onSourceDone === `break`) break;
} else throw new Error(`Missing 'done' property`);
const d = inputs.map((v) => v.lastValue);
if (d.length === 0) return;
const dataEmpty = allEmpty(d);
if (dataEmpty && !isWaiting()) return;
if (!isEqual(lastEmitted, d) && !dataEmpty) {
lastEmitted = d;
yield d;
}
if (afterEmit === `undefined`) for (const input of inputs) {
if (input.waiting !== void 0) continue;
input.lastValue = void 0;
}
if (isDone()) break;
}
}
//#endregion
//#region ../packages/iterables/src/chain/combine-latest-to-object.ts
/**
* Monitors sources, storing as they happen to an object.
* Whenever a new value is emitted, the object is sent out, containing current
* values from each source, or _undefined_ if not yet emitted.
*
* The tempo of this stream will be set by the fastest source stream.
* See {@link syncToObject} to have pace determined by slowest source, and only
* send when each source has produce a new value compared to last time.
*
* Set `onSourceDone` to choose behaviour if a source stops. By default it
* is 'break', meaning the whole merged stream stops.
*
* If a source completes and onSourceDone = 'allow', the option
* 'finalValue' sets the logic for what values get returned for the source.
* By default the setting is 'undefined', thus _undefined_ results. 'last' will be the last (old) value
* from that source.
*/
async function* combineLatestToObject(sources, options = {}) {
const onSourceDone = options.onSourceDone ?? `break`;
const finalValue = options.finalValue ?? `undefined`;
const afterEmit = options.afterEmit ??