ixfx
Version:
Bundle of ixfx libraries
1,722 lines (1,721 loc) • 61.9 kB
TypeScript
import { i as Interval, t as BasicType } from "./types-1oz6G7XR.js";
import { H as Result } from "./index-DldIQ_ah.js";
import { n as Comparer } from "./comparers-Pedv6tO4.js";
import { a as HasCompletion, c as continuously, i as ContinuouslySyncCallback, n as ContinuouslyAsyncCallback, o as HasCompletionRunStates, r as ContinuouslyOpts, s as OnStartCalled, t as Continuously } from "./continuously-DBwR-cNf.js";
import { i as ResolveToValueSync, n as ResolveToValue } from "./resolve-core-D20yayn8.js";
import { n as sleep } from "./sleep-B4LzWev2.js";
import { n as SimpleEventEmitter } from "./index-DzASKzet.js";
import { d as LogSet } from "./index-Qd9XxFg7.js";
import { t as state_machine_d_exports } from "./state-machine-CTmK75_L.js";
//#region ../packages/flow/src/behaviour-tree.d.ts
type TaskState = `Failed` | `Running` | `Success`;
type Task = {
readonly state: TaskState;
};
type Traversal = readonly [node: BtNode, path: string];
/**
* Node can have conditions as to whether they should even be considered
* Conditions can have dependencies on values, ideally this is responsive
* Conditions might abort sibling nodes, as in example: https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/ArtificialIntelligence/BehaviorTrees/BehaviorTreesOverview/
*/
type BtNodeBase = {
readonly name?: string;
};
type SeqNode = BtNodeBase & {
readonly seq: ReadonlyArray<BtNode>;
};
type SelNode = BtNodeBase & {
readonly sel: ReadonlyArray<BtNode>;
};
type BtNode = SeqNode | SelNode | string;
declare function iterateBreadth(t: BtNode, pathPrefix?: string): Generator<Traversal>;
declare function iterateDepth(t: BtNode, pathPrefix?: string): Generator<Traversal>;
//#endregion
//#region ../packages/flow/src/delay.d.ts
/**
* Delay options
*/
type DelayOpts = Interval & {
/**
* Signal for cancelling delay
*/
readonly signal?: AbortSignal;
/**
* When delay is applied. "before" is default.
*/
readonly delay?: `before` | `after` | `both`;
};
/**
* Pauses execution for interval after which the asynchronous `callback` is executed and awaited.
* Must be called with `await` if you want the pause effect.
*
* @example Pause and wait for function
* ```js
* const result = await delay(async () => Math.random(), 1000);
* console.log(result); // Prints out result after one second
* ```
*
* If the `interval` option is a number its treated as milliseconds. {@link Interval} can also be used:
* ```js
* const result = await delay(async () => Math.random(), { mins: 1 });
* ```
*
* If `await` is omitted, the function will run after the provided timeout, and code will continue to run.
*
* @example Schedule a function without waiting
* ```js
* await delay(async () => {
* console.log(Math.random())
* }, 1000);
* // Prints out a random number after 1 second.
* ```
*
* {@link delay} and {@link sleep} are similar. `delay()` takes a parameter of what code to execute after the timeout, while `sleep()` just resolves after the timeout.
*
* Optionally takes an AbortSignal to cancel delay.
* ```js
* const ac = new AbortController();
* // Super long wait
* await delay(someFn, { signal: ac.signal, hours: 1 }}
* ...
* ac.abort(); // Cancels long delay
* ```
*
* It also allows choice of when delay should happen.
* If you want to be able to cancel or re-run a delayed function, consider using
* {@link timeout} instead.
*
* @typeParam V - Type of callback return value
* @param callback What to run after interval
* @param optsOrMillis Options for delay, or millisecond delay. By default delay is before `callback` is executed.
* @return Returns result of `callback`.
*/
declare const delay: <V>(callback: () => Promise<V>, optsOrMillis: DelayOpts | number) => Promise<V>;
/**
* Async generator that loops at a given interval.
*
* @example
* For Await loop every second
* ```js
* const loop = delayLoop(1000);
* // Or: const loop = delayLoop({ secs: 1 });
* for await (const o of loop) {
* // Do something...
* // Warning: loops forever
* }
* ```
*
* @example
* Loop runs every second
* ```js
* (async () => {
* const loop = delayLoop(1000);
* // or: loop = delayLoop({ secs: 1 });
* while (true) {
* await loop.next();
*
* // Do something...
* // Warning: loops forever
* }
* })();
* ```
*
* Alternatives:
* * {@link delay} to run a single function after a delay
* * {@link sleep} pause execution
* * {@link continuously} to start/stop/adjust a constantly running loop
*
* @param timeout Delay. If 0 is given, `requestAnimationFrame` is used over `setTimeout`.
*/
declare function delayLoop(timeout: Interval): AsyncGenerator<undefined, void, unknown>;
//#endregion
//#region ../packages/flow/src/debounce.d.ts
/**
* Returns a debounce function which acts to filter calls to a given function `fn`.
*
* Eg, Let's create a debounced wrapped for a function:
* ```js
* const fn = () => console.log('Hello');
* const debouncedFn = debounce(fn, 1000);
* ```
*
* Now we can call `debouncedFn()` as often as we like, but it will only execute
* `fn()` after 1 second has elapsed since the last invocation. It essentially filters
* many calls to fewer calls. Each time `debounceFn()` is called, the timeout is
* reset, so potentially `fn` could never be called if the rate of `debounceFn` being called
* is faster than the provided timeout.
*
* Remember that to benefit from `debounce`, you must call the debounced wrapper, not the original function.
*
* ```js
* // Create
* const d = debounce(fn, 1000);
*
* // Don't do this if we want to benefit from the debounce
* fn();
*
* // Use the debounced wrapper
* d(); // Only calls fn after 1000s
* ```
*
* A practical use for this is handling high-frequency streams of data, where we don't really
* care about processing every event, only last event after a period. Debouncing is commonly
* used on microcontrollers to prevent button presses being counted twice.
*
* @example Handle most recent pointermove event after 1000ms
* ```js
* // Set up debounced handler
* const moveDebounced = debounce((evt) => {
* // Handle event
* }, 500);
*
* // Wire up event
* el.addEventListener(`pointermove`, moveDebounced);
* ```
*
* Arguments can be passed to the debounced function:
*
* ```js
* const fn = (x) => console.log(x);
* const d = debounce(fn, 1000);
* d(10);
* ```
*
* If you want the result of a debounced function when it finally executes, pass
* in the `onResult` parameter:
* ```js
* const isRed = (colour) => colour === `red`;
* const onResult = (result) => {
* if (result.success) {
* console.log(`Value: ${result.value}`);
* }
* }
* const d = debounce(fn, 1000, onResult);
* ```
*
* Note that only the `onResult` handler for the function call that succeeeds is called,
* there's no queuing of callbacks.
*
* If the debounced function throws an error, this will be reported as well:
* ```js
* if (!result.success) {
* console.error(result.error);
* }
* ```
* @param callback Function to filter access to
* @param interval Minimum time between invocations
* @param onResult Callback when the result from the wrapped function is available
* @returns Debounce function
*/
declare function debounce<T extends AnyFn>(callback: T, interval: Interval, onResult?: (result: Result<ReturnType<T>, any>) => void): (...args: Parameters<T>) => void;
type AnyFn = (...args: any[]) => any;
//#endregion
//#region ../packages/flow/src/dispatch-list.d.ts
type Dispatch<V> = ((value: V) => void) | ((value: V) => boolean);
/**
* Maintains a list of listeners to receive data.
*
* Type parameter is the type of events sent.
*
* ```js
* const d = new DispatchList();
*
* // Eg: add a listener
* d.add(v => {
* // Handle a value
* });
*
* // Eg. send a value to all listeners
* d.notify(`some value`);
* ```
*
* If event handler returns true, additional handlers are not called.
*/
declare class DispatchList<V> {
#private;
constructor();
/**
* Returns _true_ if list is empty
* @returns
*/
isEmpty(): boolean;
/**
* Adds a handler. You get back an id which can be used
* to remove the handler later.
*
* Handlers can be added with 'once' flag set to _true_. This will
* automatically remove them after the first value is sent to them.
*
* If handler returns _true_, subsequent handlers are not invoked.
* @param handler
* @param options
* @returns
*/
add(handler: Dispatch<V>, options?: {
once?: boolean;
}): string;
/**
* Remove a handler by its id.
* @param id
* @returns _True_ if handler was removed, _false_ if not found.
*/
remove(id: string): boolean;
/**
* Emit a value to all handlers
* Returns _true_ if at least one handler reported 'true' as a response.
* Also returns true
* @param value
*/
notify(value: V): boolean;
/**
* Remove all handlers
*/
clear(): void;
}
//#endregion
//#region ../packages/flow/src/every.d.ts
/**
* Returns true for every _n_th call, eg 2 for every second call.
*
* If `nth` is 1, returns true for everything. 0 will be false for everything.
*
* Usage:
* ```js
* const tenth = everyNth(10);
* window.addEventListener(`pointermove`, evt => {
* if (!tenth(evt)) return; // Filter out
* // Continue processing, it is the 10th thing.
*
* });
* ```
*
* Alternative:
* ```js
* window.addEventListener(`pointermove`, everyNth(10, evt => {
* // Do something with tenth item...
* });
* ```
* @param nth Every nth item
* @param callback
* @returns Function which in turn returns true if nth call has been hit, false otherwise
*/
declare const everyNth: <T>(nth: number, callback?: (data: T) => void) => (data: T) => boolean;
//#endregion
//#region ../packages/flow/src/execute.d.ts
type ExpressionOrResult<ArgsType, ResultType> = ResultType | ((args: ArgsType | undefined) => Promise<ResultType | undefined> | ResultType | undefined | void);
type RunOpts<ResultType> = {
/**
* If provided, filters the set of results prior to returning.
* @param result
* @returns
*/
readonly filter?: (result: ResultType) => boolean;
/**
* If true, execution order is shuffled each time
*/
readonly shuffle?: boolean;
/**
* Function to rank results. By default uses {@link defaultComparer} which orders
* by numeric value or alphabetical.
*/
readonly rank?: Comparer<ResultType>;
/**
* If provided, stops execution if _true_ is returned.
* Result(s) include most recent execution.
* @param latest Latest result
* @param sorted Sorted list of current results, not including latest
* @returns
*/
readonly stop?: (latest: ResultType | undefined, sorted: readonly ResultType[]) => boolean;
};
type RunSingleOpts<V> = RunOpts<V> & {
readonly at?: number;
};
/**
* Runs a series of async expressions, returning the results.
* Use {@link runSingle} if it's only a single result you care about.
*
* @example Run three functions, returning the highest-ranked result.
* ```js
* const result = runSingle([
* () => 10,
* () => 2,
* () => 3
* ]);
* // Yields: 10
* ```
*
* Options can be passed for evaluation:
* ```js
* const result = run([
* (args) => {
* if (args === 'apple') return 100;
* },
* () => {
* return 10;
* }
* ])
* ```
*
* ```js
* const expr = [
* (opts) => 10,
* (opts) => 2,
* (opts) => 3
* ];
* const opts = {
* rank: (a, b) => {
* if (a < b) return -1;
* if (a > b) return 1;
* return 0;
* }
* }
* const result = await run(expr, opts);
* // Returns: 2
* ```
*
* In terms of typing, it takes an generic arguments `ArgsType` and `ResultType`:
* - `ArgsType`: type of expression arguments. This might be `void` if no arguments are used.
* - `ResultType`: return type of expression functions
*
* Thus the `expressions` parameter is an array of functions:
* ```js
* (args:ArgsType|undefined) => ResultType|undefined
* // or
* (args:ArgsType|undefined) => Promise<ResultType|undefined>
* ```
*
* Example:
* ```js
* const expressions = [
* // Function takes a string arg
* (args:string) => return true; // boolean is the necessary return type
* ];
* const run<string,boolean>(expressions, opts, 'hello');
* ```
* @param expressions
* @param opts
* @param args
* @returns
*/
declare const run: <ArgsType, ResultType>(expressions: ExpressionOrResult<ArgsType, ResultType>[] | ExpressionOrResult<ArgsType, ResultType> | readonly ExpressionOrResult<ArgsType, ResultType>[], opts?: RunOpts<ResultType>, args?: ArgsType) => Promise<ResultType[]>;
/**
* Like {@link run}, but it returns a single result or _undefined_.
* Use the `at` option to specify which index of results to use.
* By default it's -1, which is the presumably the highest-ranked result.
*
* @param expressions
* @param opts
* @param args
* @returns
*/
declare const runSingle: <ArgsType, ResultType>(expressions: readonly ExpressionOrResult<ArgsType, ResultType>[], opts?: RunSingleOpts<ResultType>, args?: ArgsType) => Promise<ResultType | undefined>;
//#endregion
//#region ../packages/flow/src/event-race.d.ts
/**
* Subscribes to events on `target`, returning the event data
* from the first event that fires.
*
* By default waits a maximum of 1 minute.
*
* Automatically unsubscribes on success or failure (ie. timeout)
*
* ```js
* // Event will be data from either event, whichever fires first
* // Exception is thrown if neither fires within 1 second
* const event = await eventRace(document.body, [`pointermove`, `pointerdown`], { timeout: 1000 });
* ```
* @param target Event source
* @param eventNames Event name(s)
* @param options Options
* @returns
*/
declare const eventRace: (target: EventTarget, eventNames: string[], options?: Partial<{
timeoutMs: number;
signal: AbortSignal;
}>) => Promise<Event>;
//#endregion
//#region ../packages/flow/src/moving-average.d.ts
type MovingAverageTimedOptions = Readonly<{
interval: Interval;
default?: number;
abort?: AbortSignal;
}>;
/**
* Uses the same algorithm as {@link movingAverageLight}, but adds values automatically if
* nothing has been manually added.
*
* ```js
* // By default, 0 is added if interval elapses
* const mat = movingAverageTimed({ interval: 1000 });
* mat(10); // Add value of 10, returns latest average
*
* mat(); // Get current average
* ```
*
* This is useful if you are averaging something based on events. For example calculating the
* average speed of the pointer. If there is no speed, there is no pointer move event. Using
* this function, `value` is added at a rate of `updateRateMs`. This timer is reset
* every time a value is added, a bit like the `debounce` function.
*
* Use an AbortSignal to cancel the timer associated with the `movingAverageTimed` function.
* @param options
* @returns
*/
declare const movingAverageTimed: (options: MovingAverageTimedOptions) => (v: number) => number;
//#endregion
//#region ../packages/flow/src/pool.d.ts
/**
* Policy for when the pool is fully used
*/
type FullPolicy = `error` | `evictOldestUser`;
/**
* Pool options
*/
type PoolOptions<V> = {
/**
* Maximum number of resources for this pool
*/
readonly capacity?: number;
/**
* If above 0, users will be removed if there is no activity after this interval.
* Activity is marked whenever `use` us called with that user key.
* Default: disabled
*/
readonly userExpireAfterMs?: number;
/**
* If above 0, resources with no users will be automatically removed after this interval.
* Default: disabled
*/
readonly resourcesWithoutUserExpireAfterMs?: number;
/**
* Maximum number of users per resource. Defaults to 1
*/
readonly capacityPerResource?: number;
/**
* What to do if pool is full and a new resource allocation is requested.
* Default is `error`, throwing an error when pool is full.
*/
readonly fullPolicy?: FullPolicy;
/**
* If true, additional logging will trace activity of pool.
* Default: false
*/
readonly debug?: boolean;
/**
* If specified, this function will generate new resources as needed.
*/
readonly generate?: () => V;
/**
* If specified, this function will be called when a resource is disposed
*/
readonly free?: (v: V) => void;
};
/**
* Function that initialises a pool item
*/
/**
* State of pool
*/
type PoolState = `idle` | `active` | `disposed`;
type PoolUserEventMap<V> = {
readonly disposed: {
readonly data: V;
readonly reason: string;
};
readonly released: {
readonly data: V;
readonly reason: string;
};
};
/**
* A use of a pool resource
*
* Has two events, _disposed_ and _released_.
*/
declare class PoolUser<V> extends SimpleEventEmitter<PoolUserEventMap<V>> {
readonly key: string;
readonly resource: Resource<V>;
private _lastUpdate;
private _pool;
private _state;
private _userExpireAfterMs;
/**
* Constructor
* @param key User key
* @param resource Resource being used
*/
constructor(key: string, resource: Resource<V>);
/**
* Returns a human readable debug string
* @returns
*/
toString(): string;
/**
* Resets countdown for instance expiry.
* Throws an error if instance is disposed.
*/
keepAlive(): void;
/**
* @internal
* @param reason
* @returns
*/
_dispose(reason: string, data: V): void;
/**
* Release this instance
* @param reason
*/
release(reason: string): void;
get data(): V;
/**
* Returns true if this instance has expired.
* Expiry counts if elapsed time is greater than `userExpireAfterMs`
*/
get isExpired(): boolean;
/**
* Returns elapsed time since last 'update'
*/
get elapsed(): number;
/**
* Returns true if instance is disposed
*/
get isDisposed(): boolean;
/**
* Returns true if instance is neither disposed nor expired
*/
get isValid(): boolean;
}
/**
* A resource allocated in the Pool
*/
declare class Resource<V> {
#private;
readonly pool: Pool<V>;
/**
* Constructor.
* @param pool Pool
* @param data Data
*/
constructor(pool: Pool<V>, data: V);
/**
* Gets data associated with resource.
* Throws an error if disposed
*/
get data(): V;
/**
* Changes the data associated with this resource.
* Throws an error if disposed or `data` is undefined.
* @param data
*/
updateData(data: V): void;
/**
* Returns a human-readable debug string for resource
* @returns
*/
toString(): string;
/**
* Assigns a user to this resource.
* @internal
* @param user
*/
_assign(user: PoolUser<V>): void;
/**
* Releases a user from this resource
* @internal
* @param user
*/
_release(user: PoolUser<V>): void;
/**
* Returns true if resource can have additional users allocated
*/
get hasUserCapacity(): boolean;
/**
* Returns number of uses of the resource
*/
get usersCount(): number;
/**
* Returns true if automatic expiry is enabled, and that interval
* has elapsed since the users list has changed for this resource
*/
get isExpiredFromUsers(): boolean;
/**
* Returns true if instance is disposed
*/
get isDisposed(): boolean;
/**
* Disposes the resource.
* If it is already disposed, it does nothing.
* @param reason
* @returns
*/
dispose(reason: string): void;
}
/**
* Resource pool
* It does the housekeeping of managing a limited set of resources which are shared by 'users'.
* All resources in the Pool are meant to be the same kind of object.
*
* An example is an audio sketch driven by TensorFlow. We might want to allocate a sound oscillator per detected human body. A naive implementation would be to make an oscillator for each detected body. However, because poses appear/disappear unpredictably, it's a lot of extra work to maintain the binding between pose and oscillator.
*
* Instead, we might use the Pool to allocate oscillators to poses. This will allow us to limit resources and clean up automatically if they haven't been used for a while.
*
* Resources can be added manually with `addResource()`, or automatically by providing a `generate()` function in the Pool options. They can then be accessed via a _user key_. This is meant to associated with a single 'user' of a resource. For example, if we are associating oscillators with TensorFlow poses, the 'user key' might be the id of the pose.
*/
declare class Pool<V> {
#private;
private _resources;
private _users;
readonly capacity: number;
readonly userExpireAfterMs: number;
readonly resourcesWithoutUserExpireAfterMs: number;
readonly capacityPerResource: number;
readonly fullPolicy: FullPolicy;
private generateResource?;
readonly freeResource?: (v: V) => void;
readonly log: LogSet;
/**
* Constructor.
*
* By default, no capacity limit, one user per resource
* @param options Pool options
*/
constructor(options?: PoolOptions<V>);
/**
* Returns a debug string of Pool state
* @returns
*/
dumpToString(): string;
/**
* Sorts users by longest elapsed time since update
* @returns
*/
getUsersByLongestElapsed(): PoolUser<V>[];
/**
* Returns resources sorted with least used first
* @returns
*/
getResourcesSortedByUse(): Resource<V>[];
/**
* Adds a shared resource to the pool
* @throws Error if the capacity limit is reached or resource is null
* @param resource
* @returns
*/
addResource(resource: V): Resource<V>;
/**
* Performs maintenance, removing disposed/expired resources & users.
* This is called automatically when using a resource.
*/
maintain(): void;
/**
* Iterate over resources in the pool.
* To iterate over the data associated with each resource, use
* `values`.
*/
resources(): Generator<Resource<V>, void, unknown>;
/**
* Iterate over resource values in the pool.
* to iterate over the resources, use `resources`.
*
* Note that values may be returned even though there is no
* active user.
*/
values(): Generator<V, void, unknown>;
/**
* Unassociate a key with a pool item
* @param userKey
*/
release(userKey: string, reason?: string): void;
/**
* @internal
* @param user
*/
_release(user: PoolUser<V>): void;
/**
* @internal
* @param resource
* @param _
*/
_releaseResource(resource: Resource<V>, _: string): void;
/**
* Returns true if `v` has an associted resource in the pool
* @param resource
* @returns
*/
hasResource(resource: V): boolean;
/**
* Returns true if a given `userKey` is in use.
* @param userKey
* @returns
*/
hasUser(userKey: string): boolean;
/**
* @internal
* @param key
* @param resource
* @returns
*/
private _assign;
/**
* Return the number of users
*/
get usersLength(): number;
/**
* 'Uses' a resource, returning the value
* @param userKey
* @returns
*/
useValue(userKey: string): V;
/**
* Gets a pool item based on a 'user' key.
*
* The same key should return the same pool item,
* for as long as it still exists.
*
* If a 'user' already has a resource, it will 'keep alive' their use.
* If a 'user' does not already have resource
* - if there is capacity, a resource is allocated to user
* - if pool is full
* - fullPolicy = 'error': an error is thrown
* - fullPolicy = 'evictOldestUser': evicts an older user
* - Throw error
* @param userKey
* @throws Error If all resources are used and fullPolicy = 'error'
* @returns
*/
use(userKey: string): PoolUser<V>;
}
/**
* Creates an instance of a Pool
* @param options
* @returns
*/
declare const create: <V>(options?: PoolOptions<V>) => Pool<V>;
//#endregion
//#region ../packages/flow/src/promise-with-resolvers.d.ts
/**
* Creates a new Promise, returning the promise
* along with its resolve and reject functions.
*
* ```js
* const { promise, resolve, reject } = promiseWithResolvers();
*
* setTimeout(() => {
* resolve();
* }, 1000);
*
* await promise;
* ```
*
* Promise would be passed somewhere that expects a promise,
* and you're free to call `resolve` or `reject` when needed.
* @returns
*/
declare function promiseWithResolvers<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (reason: any) => void;
};
//#endregion
//#region ../packages/flow/src/rate-minimum.d.ts
type RateMinimumOptions<TInput> = Readonly<{
whatToCall: (args: TInput) => void;
fallback: () => TInput;
interval: Interval;
abort?: AbortSignal;
}>;
/**
* Ensures that `whatToCall` is executed with a given tempo.
*
* ```js
* const rm = rateMinimum({
* fallback: () => {
* return Math.random();
* },
* whatToCall: (value:number) => {
* console.log(value);
* },
* interval: { secs: 10 }
* });
*
* // Invokes `whatToCall`, resetting timeout
* rm(10);
*
* // If we don't call rm() before 'interval' has elapsed,
* // 'fallback' will be invoked
* ```
*
* A practical use for this is to update calculations based on firing of events
* as well as when they don't fire. For example user input.
*
* ```js
* // Average distances
* const average = movingAverageLight();
* const rm = rateMinimum({
* interval: { secs: 1 },
* whatToCall: (distance: number) => {
* average(distance);
* },
* // If there are no pointermove events, distance is 0
* fallback() {
* return 0;
* }
* })
*
* // Report total movemeent
* document.addEventListener(`pointermove`, event => {
* rm(event.movementX + event.movementY);
* });
* ```
*
* @param options
* @returns
*/
declare const rateMinimum: <TInput>(options: RateMinimumOptions<TInput>) => (args: TInput) => void;
//#endregion
//#region ../packages/flow/src/repeat.d.ts
type RepeatDelayOpts = RepeatOpts & Readonly<Partial<{
/**
* Sleep a fixed period of time regardless of how long each invocation of 'produce' takes
*/
delay: Interval;
/**
* Minimum interval. That is, only sleep if there is time left over after 'produce'
* is invoked.
*/
delayMinimum: Interval;
/**
* When to perform delay. Default is before 'produce' is invoked.
* Default: 'before'
*/
delayWhen: `before` | `after` | `both`;
}>>;
/**
* Options for repeat
*/
type RepeatOpts = Partial<Readonly<{
/**
* If specified, repeating stops if this function returns false
* @param count
* @returns
*/
while: (count: number) => boolean;
/**
* By default, if the callback returns
* _undefined_ the repeating exits. Set this to _true_ to
* ignore undefined values
* @default false
*/
allowUndefined: boolean;
/**
* Optional signal to abort
*/
signal: AbortSignal;
/**
* Maximum times to repeat (default: no limit)
*/
count: number;
/**
* Function to call when initialising
* @returns
*/
onStart: () => void;
/**
* Function to call when done (or an error occurs)
* @returns
*/
onComplete: (withError: boolean) => void;
}>>;
/**
* Generates values from `produce` with a time delay.
* `produce` can be a simple function that returns a value, an async function, or a generator.
* If `produce` returns _undefined_, generator exits.
*
* @example
* Produce a random number every 500ms
* ```js
* const randomGenerator = repeat(() => Math.random(), 500);
* for await (const r of randomGenerator) {
* // Random value every 1 second
* // Warning: does not end by itself, a `break` statement is needed
* }
* ```
*
* @example
* Return values from a generator every 500ms
* ```js
* import { repeat } from '@ixfx/flow.js'
* import { count } from '@ixfx/numbers.js'
* for await (const v of repeat(count(10), { fixed: 1000 })) {
* // Do something with `v`
* }
* ```
*
* Options allow either fixed interval (wait this long between iterations), or a minimum interval (wait at least this long). The latter is useful if `produce` takes some time - it will only wait the remaining time or not at all.
*
* If the AbortSignal is triggered, an exception will be thrown, stopping iteration.
*
* @see {@link continuously}: loop that runs at a constant speed. Able to be started and stopped
* @see {@link repeat}: run a function a certain number of times, collecting results
*
* @param produce Function/generator to use
* @param opts
* @typeParam T - Data type
* @returns Returns value of `produce` function
*/
declare function repeat<T extends BasicType>(produce: ResolveToValue<T> | ArrayLike<T>, opts: RepeatDelayOpts): AsyncGenerator<T>;
/**
* Generates values from `produce` with a time delay.
* `produce` can be a simple function that returns a value, an function, or a generator.
* If `produce` returns _undefined_, generator exits.
*
* This is the synchronous version. {@link repeat} allows for delays between loops
* as well as asynchronous callbacks.
*
* If the AbortSignal is triggered, an exception will be thrown, stopping iteration.
*
* @param produce Function/generator to use
* @param opts Options
* @typeParam T - Data type
* @returns Returns value of `produce` function
*/
declare function repeatSync<T extends BasicType>(produce: ResolveToValueSync<T> | ArrayLike<T>, opts: RepeatOpts): Generator<T, void, unknown>;
//#endregion
//#region ../packages/flow/src/req-resp-match.d.ts
type RequestResponseOptions<TRequest, TResp> = {
timeoutMs: number;
key: (requestOrResp: TRequest | TResp) => string;
keyRequest: (request: TRequest) => string;
keyResponse: (resp: TResp) => string;
whenUnmatchedResponse: `ignore` | `throw`;
};
type RequestResponseMatchEvents<TRequest, TResp> = {
match: {
request: TRequest;
response: TResp;
};
completed: {
request: TRequest;
response: TResp | string;
success: boolean;
};
};
/**
* Matches responses with requests, expiring requests if they do not get a response in a timely manner.
*
* Basic usage:
* ```js
* const m = new RequestResponseMatch(options);
* // Listen for when a response matches a request
* m.addEventListener(`match`, event => {
* // event: { request:Req, response:Resp}
* });
* // Or alternatively, listen for success and failures
* m.addEventListener(`completed`, event => {
* // { request:Resp, response:Req|undefined, success:boolean }
* // 'response' will be data or a string error message
* });
* m.request(req); // Note that some request was sent
* ...
* m.response(resp); // Call when a response is received
* ```
*
* It's also possible to wait for specific replies:
* ```js
* // With a promise
* const resp = await m.requestAwait(req);
* // With a callback
* m.requestCallback(req, (success, resp) => {
* // Runs on success or failure
* })
* ```
*
* It relies on creating an id of a request/response for them to be matched up. Use the `key`
* option if the function can generate a key from either request or response.
* Or alternatively set both `keyRequest` and `keyResponse` for two functions that can generate a key for request and response respectively.
*
*
* The easy case is if req & resp both have the same field:
* ```js
* const m = new RequestResponseMatch({
* key: (reqOrResp) => {
* // Requests has an 'id' field
* // Response also has an 'id' field that corresponds to the request id
* return reqOrResp.id;
* }
* });
* ```
*
* A more complicated case:
* ```js
* const m = new RequestResponseMatch({
* keyRequest: (req) => {
* // Requests have an 'id' field
* return req.id;
* },
* keyResponse: (resp) => {
* // Responses have id under a different field
* return resp.reply_to
* }
* })
* ```
*
* By default, error will be thrown if a response is received that doesn't match up to any request.
*/
declare class RequestResponseMatch<TRequest, TResp> extends SimpleEventEmitter<RequestResponseMatchEvents<TRequest, TResp>> {
#private;
timeoutMs: number;
whenUnmatchedResponse: "ignore" | "throw";
keyRequest: (request: TRequest) => string;
keyResponse: (resp: TResp) => string;
constructor(options?: Partial<RequestResponseOptions<TRequest, TResp>>);
/**
* Stops the maintenance loop and cleans up resources.
* Should be called when done using the matcher.
*/
dispose(): void;
/**
* For debugging, logs all pending requests and their time to expiry
*/
debugDump(): void;
/**
* Make a request and get the outcome via a Promise
* @param request
*/
request(request: TRequest): Promise<TResp>;
/**
* Makes a request with a callback for the outcome
* @param request
* @param callback
*/
request(request: TRequest, callback: (error: boolean, response: TResp | string) => void): void;
/**
* Make a request and don't wait for the outcome.
* @param request
*/
requestAndForget(request: TRequest): void;
/**
* Response has been received
* @param response Response
* @returns _True_ if response matched a request
*/
response(response: TResp, keepAlive: boolean): boolean;
}
//#endregion
//#region ../packages/flow/src/retry.d.ts
/**
* Result of backoff
*/
type RetryResult<V> = {
/**
* Message describing outcome.
*
* If retry was aborted, message will be abort reason.
*/
readonly message?: string;
/**
* True if callback function was invoked once where it returned _true_
*/
readonly success: boolean;
/**
* Number of times callback was attempted
*/
readonly attempts: number;
/**
* Total elapsed time since beginning of call to `retry`
*/
readonly elapsed: number;
/**
* Value returned by succeeding function,
* or _undefined_ if it failed
*/
readonly value: V | undefined;
};
type BackoffOptions = {
/**
* Initial value.
* Default: 1
*/
startAt: number;
/**
* Maximum times to run.
* Default: continues forever
*/
limitAttempts: number;
/**
* Stop retrying if this maximum is reached
* Default: no limit
*/
limitValue: number;
/**
* Math power.
* Default: 1.1
*/
power: number;
};
/**
* Generates an expoential backoff series of values
* ```js
* // Default: start at 1, power 1.1
* for (const v of backoffGenerator()) {
* // v: numeric value
* }
* ```
*
* By default the generator runs forever. Use either
* `limitAttempts` or `limitValue` to stop it when it produces a
* given quantity of values, or when the value itself reaches a threshold.
*
* For example:
* ```js
* // `values` will have five values in it
* const values = [...backoffGenerator({ limitAttempts: 5 })];
* // Keep generating values until max is reached
* const values = [...backoffGenerator({ limitValue: 1000 })];
* ```
*
* Options:
* startAt: start value
* limitAttempts: cap the number of values to generate
* limitValue: cap the maximum calculated value
* power: power value (default 1.1)
*
* @param options
* @returns
*/
declare function backoffGenerator(options?: Partial<BackoffOptions>): Generator<number, void, unknown>;
/**
* Backoff options
*/
type RetryOpts<T> = BackoffOptions & {
/**
* Initial waiting period before first attempt (optional)
*/
readonly predelayMs: number;
/**
* Optional abort signal
*/
readonly abort: AbortSignal;
/**
* Log: _true_ monitors the task execution by logging to console
*/
readonly log: boolean;
/***
* Default task value to return if it fails
*/
readonly taskValueFallback: T;
};
type RetryTask<T> = {
/**
* If `probe` returns {success:true} task is considered
* complete and retrying stops
* @returns
*/
probe: (attempts: number) => Promise<Result<T, any>>;
};
/**
* Keeps calling `callback` until it returns something other than _undefined_.
* There is an exponentially-increasing delay between each retry attempt.
*
* If `callback` throws an exception, the retry is cancelled, bubbling the exception.
*
* ```js
* // A function that only works some of the time
* const flakyFn = async () => {
* // do the thing
* if (Math.random() > 0.9) return true; // success
* return; // fake failure
* };
*
* // Retry it up to five times,
* // starting with 1000ms interval
* const result = await retryFunction(flakyFn, {
* limitAttempts: 5
* });
*
* if (result.success) {
* // Yay
* } else {
* console.log(`Failed after ${result.attempts} attempts. Elapsed: ${result.elapsed}`);
* console.log(result.message);
* }
* ```
*
* An `AbortSignal` can be used to cancel process.
* ```js
* const abort = new AbortController();
* const result = await retryFunction(cb, { signal: abort.signal });
*
* // Somewhere else...
* abort('Cancel!'); // Trigger abort
* ```
* @param callback Function to run
* @param options Options
*/
declare function retryFunction<T>(callback: () => Promise<T | undefined>, options?: Partial<RetryOpts<T>>): Promise<RetryResult<T>>;
/**
* Keeps calling a 'probe' function until it returns success.
* If you just want to call a function until it returns a value, use {@link retryFunction} instead.
*
* ```js
* const task = {
* probe:(attempts) => {
* // attempts is number of times it has been retried
* if (Math.random() > 0.5) {
* // Return a succesful result
* return { success: true }
* } else {
* return { success: false }
* }
* }
*
* // Run with retry
* const t = await retryTask(task, opts);
*
* // Handle result
* if (t.success) {
* // Use the value:
* console.log(t.value);
* } else {
* // Handle failure case
* console.log(t.message);
* }
* ```
*
* What you get back is a {@link RetryResult} object, which includes:
* `success`: _true_ if task succeeded, _false_ if it failed or was aborted
* `attempts`: number of times task was attempted
* `elapsed`: milliseconds elapsed since initial call to `retryTask`
* `value`: value returned by task, fallback value if it failed, or _undefined_.
* `message`: message describing outcome. If retry was aborted, message will be abort reason.
* @param task
* @param opts
*/
declare function retryTask<V>(task: RetryTask<V>, opts?: Partial<RetryOpts<V>>): Promise<RetryResult<V>>;
//#endregion
//#region ../packages/flow/src/run-once.d.ts
/**
* Runs a function once
*
* ```js
* const init = runOnce(() => {
* // do some initialisation
* });
*
* init(); // Runs once
* init(); // no-op
* ```
* @param onRun
* @returns
*/
declare const runOnce: (onRun: () => boolean) => (() => boolean);
//#endregion
//#region ../packages/flow/src/sync-wait.d.ts
/**
* Simple synchronisation. Supports only a single signal/waiter.
* Expects one or more calls to .signal() for .forSignal() to resolve
*
* ```js
* const sw = new SyncWait();
* obj.addEventListener(`click`, () => {
* sw.signal();
* });
*
* // Wait until click event
* await sw.forSignal();
* ```
*
* `forSignal` can also take a maximum time to wait. If the
* time elapses, an exception is thrown.
*
* {@link didSignal} returns _true_/_false_ if signal happened rather
* than throwing an exception.
*
*/
declare class SyncWait {
#private;
signal(): void;
/**
* Throw away any previous signalled state.
* This will cause any currently waiters to throw
*/
flush(): void;
/**
* Call with `await` to wait until .signal() happens.
* If a wait period is specified, an exception is thrown if signal does not happen within this time.
* @param maximumWaitMs
*/
forSignal(maximumWaitMs?: number): Promise<void>;
/**
* An alternative to {@link forSignal}, returning _true_
* if signalled, or _false_ if wait period was exceeded
*
* ```js
* const s = await sw.didSignal(5000);
* ```
* @param maximumWaitMs
* @returns
*/
didSignal(maximumWaitMs: number): Promise<boolean>;
}
//#endregion
//#region ../packages/flow/src/task-queue-mutable.d.ts
type AsyncTaskVoid = () => Promise<void>;
type AsyncTaskResult<T> = () => Promise<T>;
type AsyncTask = AsyncTaskVoid | AsyncTaskResult<any>;
type TaskQueueEvents = {
/**
* Task queue has emptied: it has nothing left to do.
* @returns
*/
empty: any;
/**
* Task queue was empty and now processing. This does not fire for each task, only when the queue transitions from empty to non-empty.
* @returns
*/
started: any;
/**
* An error occurred when running a task
* @param error
* @returns
*/
error: {
error: unknown;
task: AsyncTask;
};
/**
* Event fired when a task completes
*/
progress: {
task: AsyncTask;
result?: any;
remaining: number;
};
};
/**
* Simple task queue. Each task is awaited and run
* in turn.
*
* The TaskQueueMutable is shared across your code,
* so you don't create it directly. Rather, use:
*
* ```js
* const queue = TaskQueueMutable.shared;
* ```
*
* @example Usage
* ```js
* const queue = TaskQueueMutable.shared;
* q.enqueue(async () => {
* // Takes one second to run
* await sleep(1000);
* });
* ```
*
* You can listen to events from the TaskQueue:
* ```js
* TaskQueueMutable.shared.addEventListener(`started`, () => {
* // Queue was empty, now started processing
* });
*
* TaskQueueMutable.shared.addEventListener(`empty`, () => {
* // Queue has finished processing all items
* });
*
* TaskQueueMutable.shared.addEventListener(`error`, ({error,task}) => {
* // Reports if a task threw an exception
* });
*
* ```
*/
declare class TaskQueueMutable extends SimpleEventEmitter<TaskQueueEvents> {
static readonly shared: TaskQueueMutable;
private _loop;
private _queue;
private constructor();
/**
* Adds a task. This triggers processing loop if not already started.
*
* ```js
* queue.add(async () => {
* await sleep(1000);
* });
* ```
* @param task Task to run
*/
enqueue(task: () => Promise<void>): number;
private processQueue;
/**
* Clears all tasks, and stops any scheduled processing.
* Currently running tasks will continue.
* Fires: `empty` event if queue was not already empty
* @returns
*/
clear(): void;
/**
* Returns _true_ if queue is empty
*/
get isEmpty(): boolean;
/**
* Number of items in queue
*/
get length(): number;
/**
* Returns the run state of the procesing loop. This is `idle` when no processing is scheduled, `scheduled` when processing is scheduled, and `running` when actively running a task.
*/
get runState(): HasCompletionRunStates;
}
//#endregion
//#region ../packages/flow/src/throttle.d.ts
/***
* Throttles a function. Callback only allowed to run after minimum of `intervalMinMs`.
*
* @example Only handle move event every 500ms
* ```js
* const moveThrottled = throttle( (elapsedMs, args) => {
* // Handle ar
* }, 500);
* el.addEventListener(`pointermove`, moveThrottled)
* ```
*
* Note that `throttle` does not schedule invocations, but rather acts as a filter that
* sometimes allows follow-through to `callback`, sometimes not. There is an expectation then
* that the return function from `throttle` is repeatedly called, such as the case for handling
* a stream of data/events.
*
* @example Manual trigger
* ```js
* // Set up once
* const t = throttle( (elapsedMs, args) => { ... }, 5000);
*
* // Later, trigger throttle. Sometimes the callback will run,
* // with data passed in to args[0]
* t(data);
* ```
*/
declare const throttle: (callback: (elapsedMs: number, ...args: readonly unknown[]) => void | Promise<unknown>, intervalMinMs: number) => (...args: unknown[]) => Promise<void>;
//#endregion
//#region ../packages/flow/src/timeout.d.ts
type TimeoutSyncCallback = (elapsedMs?: number, ...args: readonly unknown[]) => void;
type TimeoutAsyncCallback = (elapsedMs?: number, ...args: readonly unknown[]) => Promise<void>;
/**
* A resettable timeout, returned by {@link timeout}
*/
type Timeout = HasCompletion & {
/**
* Starts the timer.
* If the timer has already been started and has a scheduled execution, this is cancelled
* and re-scheduled.
* @param altTimeoutMs Optional override for the interval. Use _undefined_ to use the original interval
* @param args
*/
start(altTimeoutMs?: number, args?: readonly unknown[]): void;
/**
* Cancels the timer, aborting any scheduled execution.
*/
cancel(): void;
};
/**
* Returns a {@link Timeout} that can be triggered, cancelled and reset. Use {@link continuously} for interval-
* based loops.
*
* Once `start()` is called, `callback` will be scheduled to execute after `interval`.
* If `start()` is called again, the waiting period will be reset to `interval`.
*
* @example Essential functionality
* ```js
* const fn = () => {
* console.log(`Executed`);
* };
* const t = timeout(fn, 60*1000);
* t.start(); // After 1 minute `fn` will run, printing to the console
* ```
*
* @example Control execution functionality
* ```
* t.cancel(); // Cancel it from running
* t.start(); // Schedule again after 1 minute
* t.start(30*1000); // Cancel that, and now scheduled after 30s
*
* // Get the current state of timeout
* t.runState; // "idle", "scheduled" or "running"
* ```
*
* Callback function receives any additional parameters passed in from start. This can be useful for passing through event data:
*
* @example
* ```js
* const t = timeout( (elapsedMs, ...args) => {
* // args contains event data
* }, 1000);
* el.addEventListener(`click`, t.start);
* ```
*
* Asynchronous callbacks can be used as well:
* ```js
* timeout(async () => {...}, 100);
* ```
*
* If you don't expect to need to control the timeout, consider using {@link delay},
* which can run a given function after a specified delay.
* @param callback
* @param interval
* @returns {@link Timeout}
*/
declare const timeout: (callback: TimeoutSyncCallback | TimeoutAsyncCallback, interval: Interval) => Timeout;
//#endregion
//#region ../packages/flow/src/timer.d.ts
/**
* Creates a timer
*/
type TimerSource = () => Timer;
/**
* A timer instance.
* {@link CompletionTimer} also contains an 'isDone' field.
*
* Implementations: {@link elapsedMillisecondsAbsolute}, {@link elapsedTicksAbsolute}, {@link frequencyTimer}
*/
type Timer = {
reset(): void;
get elapsed(): number;
};
/**
* A {@link Timer} that has a sense of completion, when `isDone` returns _true_.
* See {@link relative}
*/
type CompletionTimer = Timer & {
/**
* Returns _true_ if this timer has completed.
*/
get isDone(): boolean;
};
type ModulationTimer = CompletionTimer & {
mod(amt: number): void;
};
type TimerOpts = {
/**
* Timer to use. By default {@link elapsedMillisecondsAbsolute}.
*/
readonly timer: Timer;
};
/**
* Options for relative timer
*/
type RelativeTimerOpts = TimerOpts & {
/**
* If true, returned value will be clamped to 0..1. False by default
*/
readonly clampValue: boolean;
readonly wrapValue: boolean;
};
/**
* A function that returns _true_ when an interval has elapsed
*
* ```js
* const oneSecond = hasElapsed(1000);
*
* // Keep calling to check if time has elapsed.
* // Will return _true_ when it has
* oneSecond();
* ```
*
* @param elapsed
* @returns
*/
declare function hasElapsed(elapsed: Interval): () => boolean;
/**
* Returns a function that returns the percentage of timer completion.
* Starts when return function is first invoked.
*
* ```js
* const timer = Flow.ofTotal(1000);
*
* // Call timer() to find out the completion
* timer(); // Returns 0..1
* ```
*
* Note that timer can exceed 1 (100%). To cap it:
* ```js
* Flow.ofTotal(1000, { clampValue: true });
* ```
*
* Takes an {@link Interval} for more expressive time:
* ```js
* const timer = Flow.ofTotal({ mins: 4 });
* ```
*
* Is a simple wrapper around {@link relative}.
* @param duration
* @see {@link ofTotalTicks} - Use ticks instead of time
* @see {@link hasElapsed} - Simple _true/false_ if interval has elapsed
* @returns
*/
declare function ofTotal(duration: Interval, opts?: {
readonly clampValue?: boolean;
readonly wrapValue?: boolean;
readonly timer?: Timer;
}): () => number;
/**
* Returns a function that returns the percentage (0..1) of timer completion.
* Uses 'ticks' as a measure. Use {@link ofTotal} if you want time-based.
*
* ```js
* const timer = Flow.ofTotalTicks(1000);
* timer(); // Returns 0..1
* ```
*
* Note that timer can exceed 1 (100%). To cap it:
* ```js
* Flow.ofTotalTicks(1000, { clampValue: true });
* ```
*
* This is a a simple wrapper around {@link relative}.
* @see {@link ofTotal}
* @see {@link hasElapsed}: Simple _true/false_ if interval has elapsed
* @param totalTicks
* @returns
*/
declare function ofTotalTicks(totalTicks: number, opts?: {
readonly clampValue?: boolean;
readonly wrapValue?: boolean;
}): () => number;
/**
* Returns a {@link ModulationTimer} that is always at 100%.
* Opposite: {@link timerNeverDone}.
* @returns
*/
declare const timerAlwaysDone: () => ModulationTimer;
/**
* Returns a {@link ModulationTimer} that is always at 0%.
* Opposite: {@link timerAlwaysDone}.
* @returns
*/
declare const timerNeverDone: () => ModulationTimer;
/**
* Wraps a timer, returning a relative elapsed value based on
* a given total. ie. percentage complete toward a total value.
* This is useful because other parts of code don't need to know
* about the absolute time values, you get a nice relative completion number.
*
* If no timer is specified, a milliseconds-based timer is used.
*
* ```js
* const t = relative(1000);
* t.elapsed; // returns % completion (0...1)
* ```
* It can also use a tick based timer
* ```js
* // Timer that is 'done' at 100 ticks
* const t = relative(100, { timer: ticksElapsedTimer() });
* ```
*
* Additional fields/methods on the timer instance
* ```js
* t.isDone; // _true_ if .elapsed has reached (or exceeded) 1
* t.reset(); // start from zero again
* ```
*
* Options:
* * timer: timer to use. If not specified, `elapsedMillisecondsAbsolute()` is used.
* * clampValue: if _true_, return val