ixfx
Version:
A framework for programming interactivity
1,462 lines (1,450 loc) • 57.5 kB
TypeScript
import { P as Primitive } from './PrimitiveTypes-F6miV4Zn.js';
import { I as Interval } from './IntervalType-B4PbUkjV.js';
import { R as Result } from './Results-ByWkmocN.js';
import { I as IsEqual } from './IsEqual-CTTf-Oj9.js';
import { R as RankFunction, a as RankOptions } from './Types-DaSeYFCm.js';
import { a as RecursivePartial } from './TsUtil-D3MueCxS.js';
type ChangeKind = `mutate` | `add` | `del`;
type ChangeRecord<TKey extends string | number | symbol> = [kind: ChangeKind, path: TKey, value: any];
/**
* Result of {@link compareData}
*/
type CompareChangeSet<TKey extends string | number> = {
/**
* _True_ if there are any changes
*/
hasChanged: boolean;
/**
* Results for child objects
*/
children: Record<TKey, CompareChangeSet<any>>;
/**
* Values that have changed
*/
changed: Record<TKey, any>;
/**
* Fields that have been added
*/
added: Record<TKey, any>;
/**
* Fields that have been removed
*/
removed: Array<TKey>;
isArray: boolean;
/**
* Summary of changes
*/
summary: Array<ChangeRecord<TKey>>;
};
/**
* Compares the keys of two objects, returning a set of those in
* common, and those in either A or B exclusively.
* ```js
* const a = { colour: `red`, intensity: 5 };
* const b = { colour: `pink`, size: 10 };
* const c = compareKeys(a, b);
* // c.shared = [ `colour` ]
* // c.a = [ `intensity` ]
* // c.b = [ `size` ]
* ```
* @param a
* @param b
* @returns
*/
declare const compareKeys: (a: object, b: object) => {
shared: string[];
isSame: boolean;
a: string[];
b: string[];
};
/**
* Returns the changed fields from A -> B. It's assumed that A and B have the same shape.
* ie. returns an object that only consists of fields which have changed in B compared to A.
*
* ```js
* const a = { msg: `hi`, v: 10 };
*
* changedDataFields(a, { msg: `hi`, v: 10 }); // {}
* changedDataFields(a, { msg: `hi!!`, v: 10 }); // { msg: `hi!!` }
* changedDataFields(a, { msg: `hi!!` }); // { msg: `hi!!`, v: undefined }
* ```
*
* Under the hood, we use {@link compareData}(a, b, true). If B has additional or removed fields,
* this is considered an error.
*
* If a field is an array, the whole array is returned, rather than a diff.
* @param a
* @param b
*/
declare const changedDataFields: (a: object, b: object) => any[] | Record<string, any>;
/**
* Produces a {@link CompareChangeSet} between two arrays.
*
* @param a Earlier array to compare
* @param b Later array to compare
* @param eq Equality comparison for values
* @returns Change set.
*/
declare const compareArrays: <TValue>(a: Array<TValue>, b: Array<TValue>, eq?: IsEqual<TValue>) => CompareChangeSet<number>;
/**
* Compares A to B. Assumes they are simple objects, essentially key-value pairs, where the
* values are primitive values or other simple objects. It also works with arrays.
*
* Uses === equality semantics by default.
* @param a
* @param b
*/
declare const compareData$1: (a: object, b: object, assumeSameShape?: boolean, eq?: IsEqual<any>) => CompareChangeSet<string>;
/**
* Returns _true_ if Object.entries() is empty for `value`
* @param value
* @returns
*/
declare const isEmptyEntries: (value: object) => boolean;
/**
* Return _true_ if `a` and `b` ought to be considered equal
* at a given path
*/
type IsEqualContext<V> = (a: V, b: V | undefined, path: string) => boolean;
/**
* Returns _true_ if `a` and `b are equal based on their JSON representations.
* `path` is ignored.
* @param a
* @param b
* @param path
* @returns
*/
declare const isEqualContextString: IsEqualContext<any>;
type PathData<V> = {
path: string;
value: V;
};
type PathDataChange<V> = PathData<V> & {
previous?: V;
state: `change` | `added` | `removed`;
};
type CompareDataOptions<V> = {
/**
* If _true_, it treats the B value as a partial
* version of B. Only the things present in B are compared.
* Omissions from B are not treated as removed keys.
*/
asPartial: boolean;
/**
* If _true_ (default), if a value is undefined,
* it signals that the key itself is removed.
*/
undefinedValueMeansRemoved: boolean;
pathPrefix: string;
/**
* Comparison function for values. By default uses
* JSON.stringify() to compare by value.
*/
eq: IsEqualContext<V>;
/**
* If true, inherited fields are also compared.
* This is necessary for events, for example.
*
* Only plain-object values are used, the other keys are ignored.
*/
deepEntries: boolean;
/**
* If _true_, includes fields that are present in B, but missing in A.
* _False_ by default.
*/
includeMissingFromA: boolean;
/**
* If _true_, emits a change under the path of a parent if its child has changed.
* If _false_ (default) only changed keys are emitted.
*
* Eg if data is:
* `{ colour: { h:0.5, s: 0.3, l: 0.5 }}`
* and we compare with:
* `{ colour: { h:1, s: 0.3, l: 0.5 }}`
*
* By default only 'colour.h' is emitted. If _true_ is set, 'colour' and 'colour.h' is emitted.
*/
includeParents: boolean;
};
/**
* Scans object, producing a list of changed fields where B's value (newer) differs from A (older).
*
* Options:
* - `deepEntries` (_false_): If _false_ Object.entries are used to scan the object. However this won't work for some objects, eg event args, thus _true_ is needed.
* - `eq` (JSON.stringify): By-value comparison function
* - `includeMissingFromA` (_false): If _true_ includes fields present on B but missing on A.
* - `asPartial` (_false): If _true_, treats B as a partial update to B. This means that things missing from B are not considered removals.
* @param a 'Old' value
* @param b 'New' value
* @param options Options for comparison
* @returns
*/
declare function compareData<V extends Record<string, any>>(a: V, b: Partial<V>, options?: Partial<CompareDataOptions<V>>): Generator<PathDataChange<any>>;
/**
* Returns a copy of `source` with `changes` applied.
* @param source
* @param changes
*/
declare const applyChanges: <V extends Record<string, any>>(source: V, changes: Array<PathDataChange<any>>) => V;
/**
* Returns a copy of `target` object with a specified path changed to `value`.
*
* ```js
* const a = {
* message: `Hello`,
* position: { x: 10, y: 20 }
* }
*
* const a1 = updateByPath(a, `message`, `new message`);
* // a1 = { message: `new message`, position: { x: 10, y: 20 }}
* const a2 = updateByPath(a, `position.x`, 20);
* // a2 = { message: `hello`, position: { x: 20, y: 20 }}
* ```
*
* Paths can also be array indexes:
* ```js
* updateByPath([`a`,`b`,`c`], 2, `d`);
* // Yields: [ `a`, `b`, `d` ]
* ```
*
* By default, only existing array indexes can be updated. Use the `allowShapeChange` parameter
* to allow setting arbitrary indexes.
* ```js
* // Throws because array index 3 is undefined
* updateByPath([ `a`, `b`, `c` ], `3`, `d`);
*
* // With allowShapeChange flag
* updateByPath([ `a`, `b`, `c` ], `3`, `d`, true);
* // Returns: [ `a`, `b`, `c`, `d` ]
* ```
*
* Throws an error if:
* * `path` cannot be resolved (eg. `position.z` in the above example)
* * `value` applied to `target` results in the object having a different shape (eg missing a field, field
* changing type, or array index out of bounds). Use `allowShapeChange` to suppress this error.
* * Path is undefined or not a string
* * Target is undefined/null
* @param target Object to update
* @param path Path to set value
* @param value Value to set
* @param allowShapeChange By default _false_, throwing an error if an update change the shape of the original object.
* @returns
*/
declare const updateByPath: <V extends Record<string, any>>(target: V, path: string, value: any, allowShapeChange?: boolean) => V;
/**
* Gets the data at `path` in `object`. Assumes '.' separates each segment of path.
* ```js
* getField({ name: { first: `Thom`, last: `Yorke` }}, `name.first`); // 'Thom'
* getField({ colours: [`red`, `green`, `blue` ]}, `colours.1`); // `green`
* ```
*
* Returns _undefined_ if path could not be resolved.
*
* Throws if:
* * `path` is not a string or empty
* * `object` is _undefined_ or null
* @param object
* @param path
* @returns
*/
declare const getField: <V>(object: Record<string, any>, path: string) => Result<V>;
/**
* Iterates 'paths' for all the fields on `o`
* ```
* const d = {
* accel: { x: 1, y: 2, z: 3 },
* gyro: { x: 4, y: 5, z: 6 }
* };
* const paths = [...getFieldPaths(d)];
* // Yields [ `accel`, `gyro`, `accel.x`, `accel.y`,`accel.z`,`gyro.x`,`gyro.y`,`gyro.z` ]
* ```
*
* Use {@link getField} to fetch data based on a path
*
* If object is _null_ or _undefined_, no results are returned.
*
* If `onlyLeaves` is _true_ (default: _false_), only 'leaf' nodes are included.
* Leaf nodes are those that contain a primitive value.
* ```js
* const paths = getFieldPaths(d, true);
* // Yields [ `accel.x`, `accel.y`,`accel.z`,`gyro.x`,`gyro.y`,`gyro.z` ]
* ```
*
* @param object Object to get paths for.
* @param onlyLeaves If true, only paths with a primitive value are returned.
* @returns
*/
declare function getPaths(object: object | null, onlyLeaves?: boolean): Generator<string>;
/**
* Returns a representation of the object as a set of paths and data.
* ```js
* const o = { name: `hello`, size: 20, colour: { r:200, g:100, b:40 } }
* const pd = [...getPathsAndData(o)];
* // Yields:
* // [
* // { path: `name`, value: `hello` },
* // { path: `size`, value: `20` },
* // { path: `colour.r`, value: `200` },
* // { path: `colour.g`, value: `100` },
* // { path: `colour.b`, value: `40` }
* //]
* ```
* @param o Object to get paths and data for
* @param maxDepth Set maximum recursion depth. By default unlimited.
* @param prefix Manually set a path prefix if it's necessary
* @returns
*/
declare function getPathsAndData(o: object, onlyLeaves?: boolean, maxDepth?: number, prefix?: string): Generator<PathData<any>>;
type Pathed_CompareDataOptions<V> = CompareDataOptions<V>;
type Pathed_PathData<V> = PathData<V>;
type Pathed_PathDataChange<V> = PathDataChange<V>;
declare const Pathed_applyChanges: typeof applyChanges;
declare const Pathed_compareData: typeof compareData;
declare const Pathed_getField: typeof getField;
declare const Pathed_getPaths: typeof getPaths;
declare const Pathed_getPathsAndData: typeof getPathsAndData;
declare const Pathed_updateByPath: typeof updateByPath;
declare namespace Pathed {
export { type Pathed_CompareDataOptions as CompareDataOptions, type Pathed_PathData as PathData, type Pathed_PathDataChange as PathDataChange, Pathed_applyChanges as applyChanges, Pathed_compareData as compareData, Pathed_getField as getField, Pathed_getPaths as getPaths, Pathed_getPathsAndData as getPathsAndData, Pathed_updateByPath as updateByPath };
}
/**
* Outputs the current largest-seen value
* @returns
*/
declare const max$1: () => Process<number | Array<number>, number>;
/**
* Outputs the current smallest-seen value
* @returns
*/
declare const min$1: () => Process<number | Array<number>, number>;
/**
* Returns a sum of values
* @returns
*/
declare const sum$1: () => Process<number | Array<number>, number>;
/**
* Returns the current average of input values
* @returns
*/
declare const average$1: () => Process<number | Array<number>, number>;
/**
* Returns the tally (ie number of) values
* @param countArrayItems
* @returns
*/
declare const tally$1: (countArrayItems: boolean) => Process<any, number>;
/**
* Returns the 'best' value seen so far as determined by a ranking function.
* This is similar to min/max but usable for objects.
* @param r
* @param options
* @returns
*/
declare function rank$1<In>(r: RankFunction<In>, options?: Partial<RankOptions>): (value: In) => In | undefined;
type Process<TIn, TOut> = (value: TIn) => TOut;
type ProcessFactory<TIn, TOut> = () => Process<TIn, TOut>;
type Processors1<T1, T2> = [
Process<T1, T2>
];
type Processors2<T1, T2, T3> = [
Process<T1, T2>,
Process<T2, T3>
];
type Processors3<T1, T2, T3, T4> = [
Process<T1, T2>,
Process<T2, T3>,
Process<T3, T4>
];
type Processors4<T1, T2, T3, T4, T5> = [
Process<T1, T2>,
Process<T2, T3>,
Process<T3, T4>,
Process<T4, T5>
];
type Processors5<T1, T2, T3, T4, T5, T6> = [
Process<T1, T2>,
Process<T2, T3>,
Process<T3, T4>,
Process<T4, T5>,
Process<T5, T6>
];
type Processors<T1, T2, T3, T4, T5, T6> = Processors1<T1, T2> | Processors2<T1, T2, T3> | Processors3<T1, T2, T3, T4> | Processors4<T1, T2, T3, T4, T5> | Processors5<T1, T2, T3, T4, T5, T6>;
declare function flow<T1, T2>(...processors: [Process<T1, T2>]): (value: T1) => T2;
declare function flow<T1, T2, T3>(...processors: [Process<T1, T2>, Process<T2, T3>]): (value: T1) => T3;
declare function flow<T1, T2, T3, T4>(...processors: [Process<T1, T2>, Process<T2, T3>, Process<T3, T4>]): (value: T1) => T4;
declare function flow<T1, T2, T3, T4, T5>(...processors: [Process<T1, T2>, Process<T2, T3>, Process<T3, T4>, Process<T4, T5>]): (value: T1) => T5;
declare function flow<T1, T2, T3, T4, T5, T6>(...processors: [Process<T1, T2>, Process<T2, T3>, Process<T3, T4>, Process<T4, T5>, Process<T5, T6>]): (value: T1) => T6;
/**
* If a value is same as the previous value, _undefined_ is emitted instead.
* @param eq Equality function. If not specified, === semantics are used.
* @returns
*/
declare function seenLastToUndefined<TIn>(eq?: (a: TIn, b: TIn) => boolean): Process<TIn, TIn | undefined>;
/**
* If a value is same as any previously-seen value, _undefined_ is emitted instead.
* It stores all previous values and compares against them for each new value.
* This would likely be not very efficient compared to {@link seenToUndefinedByKey} which uses a one-time computed
* key and efficient storage of only the keys (using a Set).
*
* @param eq Equality function. If not specified, === semantics are used.
* @returns
*/
declare function seenToUndefined<TIn>(eq?: (a: TIn, b: TIn) => boolean): Process<TIn, TIn | undefined>;
/**
* If a value is the same as any previously-seen value, _undefined_ is emitted instead.
* This version uses a function to create a string key of the object, by default JSON.stringify.
* Thus we don't need to store all previously seen objects, just their keys.
*
* Alternatively, if a key function doesn't make sense for the value, use
* {@link seenToUndefined}, which stores the values (less efficient).
*
* @param toString
* @returns
*/
declare function seenToUndefinedByKey<TIn>(toString?: (value: TIn) => string): Process<TIn, TIn | undefined>;
/**
* Calls a function if the input value is not undefined.
* Return value from function is passed to next function in flow.
*
* ```js
* const flow = Process.flow(
* Process.max(),
* Process.seenLastToUndefined(),
* Process.ifNotUndefined(v => {
* console.log(`v:`, v);
* })
* );
* flow(100); // Prints 'v:100'
* flow(90); // Nothing happens max value has not changed
* flow(110); // Prints 'v:110'
* ```
* @param fn
* @returns
*/
declare function ifNotUndefined<TIn, TOut>(fn: (value: Exclude<TIn, undefined>) => TOut): (value: TIn) => TIn | TOut;
declare class CancelError extends Error {
constructor(message: any);
}
/**
* Cancels the remaining flow operations if _undefined_ is an input.
* See also {@link ifUndefined} or {@link ifNotUndefined}.
*
* ```js
* const c3 = Process.flow(
* Basic.max(),
* Process.seenLastToUndefined(),
* Process.cancelIfUndefined(),
* (v => {
* console.log(v);
* })
* );
* c3(100); // Prints '100'
* c3(90); // Doesn't print anything since max does not change
* c3(110); // Prints '110'
* ```
* @returns
*/
declare function cancelIfUndefined<TIn>(): (value: TIn | undefined) => TIn;
/**
* Returns the output of `fn` if the input value is _undefined_.
* See also: {@link ifNotUndefined} and {@link cancelIfUndefined}.
* @param fn
* @returns
*/
declare function ifUndefined<TIn, TOut>(fn: () => TOut): (value: TIn) => TOut | (TIn & ({} | null));
type Process$1_CancelError = CancelError;
declare const Process$1_CancelError: typeof CancelError;
type Process$1_Process<TIn, TOut> = Process<TIn, TOut>;
type Process$1_ProcessFactory<TIn, TOut> = ProcessFactory<TIn, TOut>;
type Process$1_Processors<T1, T2, T3, T4, T5, T6> = Processors<T1, T2, T3, T4, T5, T6>;
type Process$1_Processors1<T1, T2> = Processors1<T1, T2>;
type Process$1_Processors2<T1, T2, T3> = Processors2<T1, T2, T3>;
type Process$1_Processors3<T1, T2, T3, T4> = Processors3<T1, T2, T3, T4>;
type Process$1_Processors4<T1, T2, T3, T4, T5> = Processors4<T1, T2, T3, T4, T5>;
type Process$1_Processors5<T1, T2, T3, T4, T5, T6> = Processors5<T1, T2, T3, T4, T5, T6>;
declare const Process$1_cancelIfUndefined: typeof cancelIfUndefined;
declare const Process$1_flow: typeof flow;
declare const Process$1_ifNotUndefined: typeof ifNotUndefined;
declare const Process$1_ifUndefined: typeof ifUndefined;
declare const Process$1_seenLastToUndefined: typeof seenLastToUndefined;
declare const Process$1_seenToUndefined: typeof seenToUndefined;
declare const Process$1_seenToUndefinedByKey: typeof seenToUndefinedByKey;
declare namespace Process$1 {
export { Process$1_CancelError as CancelError, type Process$1_Process as Process, type Process$1_ProcessFactory as ProcessFactory, type Process$1_Processors as Processors, type Process$1_Processors1 as Processors1, type Process$1_Processors2 as Processors2, type Process$1_Processors3 as Processors3, type Process$1_Processors4 as Processors4, type Process$1_Processors5 as Processors5, average$1 as average, Process$1_cancelIfUndefined as cancelIfUndefined, Process$1_flow as flow, Process$1_ifNotUndefined as ifNotUndefined, Process$1_ifUndefined as ifUndefined, max$1 as max, min$1 as min, rank$1 as rank, Process$1_seenLastToUndefined as seenLastToUndefined, Process$1_seenToUndefined as seenToUndefined, Process$1_seenToUndefinedByKey as seenToUndefinedByKey, sum$1 as sum, tally$1 as tally };
}
type SyncOptions = {
/**
* How to handle when a source completes.
* * 'allow' means we continue synchronising with remaining alive sources. Use 'finalValue' option to control what data is returned for completed sources
* * 'break' means we stop the stream, because synchronisation across all sources is no longer possible.
*
* Default: 'break'.
*/
onSourceDone: `allow` | `break`;
/**
* Maximum time to wait for synchronisation to happen.
* If interval is exceeded, stream closes.
* Default: 2s
*/
maximumWait: Interval;
/**
* If we continue synchronisation when a source is done (via `onSourceDone:'allow'`),
* what source should be returned for a completed source?
* * 'undefined': _undefined_
* * 'last': the last received value, or _undefined_
*
* Default: 'undefined'
*/
finalValue: `undefined` | `last`;
};
/**
* Switcher options.
*
* match (default: 'first')
* * 'first': Outputs to first case where predicate is _true_
* * 'all': Outputs to all cases where predicate is _true_
*/
type SwitcherOptions = {
match: `first` | `all`;
};
/**
* Transform options
*/
type TransformOpts = InitStreamOptions & {
traceInput: boolean;
traceOutput: boolean;
};
type ChunkOptions = InitStreamOptions & {
/**
* If _true_ (default) remaining results are yielded
* if source closes. If _false_, only chunks that meet
* `elapsed` or `quantity` are emitted.
*/
returnRemainder: boolean;
/**
* Amount of time to gather results for a chunk.
* 'elapsed' and 'quantity' is ORed. Meaning a chunk will the minimum of
* 'elapsed' and 'quantity'
*/
elapsed: Interval;
/**
* Number of items to gather for a chunk.
* 'elapsed' and 'quantity' is ORed. Meaning a chunk will the minimum of
* 'elapsed' and 'quantity'
*/
quantity: number;
};
type DebounceOptions = InitStreamOptions & {
/**
* Minimum time between events. Default 50ms
*/
elapsed: Interval;
};
type FilterPredicate<In> = (value: In) => boolean;
type ThrottleOptions = InitStreamOptions & {
elapsed: Interval;
};
type SplitOptions = {
quantity: number;
};
type FieldOptions<TSource, TValue> = InitStreamOptions & {
/**
* If `field` is missing on a value, it is query from this object instead.
* If this also is missing, `fallbackFieldValue` is attempted.
*/
fallbackObject: TSource;
/**
* If `field` is missing on a value and `fallbackObject` (if specified),
* this value is used in its place.
* If not set, no value is emitted when the field is missing.
*/
fallbackFieldValue: TValue;
};
type SingleFromArrayOptions<V> = {
/**
* Function to select a single value from array
* @param value
* @returns
*/
predicate: (value: V) => boolean;
/**
* `default`: leave array in same order (default option)
* `random`: shuffles array before further processing
* function: function that sorts values
*/
order: `default` | `random` | ((a: V, b: V) => number);
/**
* Selects an index from array. 0 being first, 1 being second.
* Reverse indexing also works: -1 being last, -2 being second last...
*
* If index exceeds length of array, _undefined_ is returned
*/
at: number;
};
type OpAsAnnotation = {
annotate: true;
};
type OpMathOptions = Partial<InitStreamOptions> & {
annotate?: boolean;
/**
* If _true_ (default) operations that return _undefined_ do not emit a value.
* If _false_, _undefined_ is potentially emitted
*/
skipUndefined?: boolean;
/**
* If _true_ (default) operations only emit a value if it has changed.
* In the case of `max()`, for example, a stream of '1, 2, 3, 2, 1' would emit '1, 2, 3'.
* If _false_ was used, same input would emit '1, 2, 3, 3, 3'
*/
skipIdentical?: boolean;
};
type TriggerValue<TTriggerValue> = {
value: TTriggerValue;
};
/**
* Options for the 'count' source.
*/
type CountOptions = {
/**
* Determines when counting starts
* @defaultValue 'initial'
*/
lazy: Lazy;
/**
* Amount to increment by
* @defaultValue 1
*/
amount: number;
/**
* Where to begin counting
* @defaultValue 0
*/
offset: number;
/**
* How long to wait before incrementing.
* @defaultValue 1 second
*/
interval: Interval;
/**
* Abort signal to trigger the source to close.
*/
signal: AbortSignal;
};
/**
* Function which returns a result. Or promised result.
*
* `abort` value is a callback to exit out of looped execution.
*/
type FunctionFunction<T> = ((abort: (reason: string) => void) => T) | ((abort: (reason: string) => void) => Promise<T>);
type ArrayOptions = {
/**
* Interval between each item being read. Default: 5ms.
*/
interval: Interval;
lazy: Lazy;
/**
* Behaviour when reactive stops, for example due to having no subscribers
* * continue: iteration continues through array where it left off
* * reset: iteration begins from start of array
*/
whenStopped: `continue` | `reset`;
debugLifecycle: boolean;
signal: AbortSignal;
};
/**
* Function which returns a result. Or promised result.
* Takes a `value` as first parameter, and callback to signal an abort as the second.
*/
type PingedFunctionFunction<T, TSource> = ((value: TSource, abort: (reason: string) => void) => T) | ((value: TSource, abort: (reason: string) => void) => Promise<T>);
/**
* Trigger that calls a `fn`.
* If `fn` returns _undefined_, it means the trigger is complete
*/
type TriggerFunction<TTriggerValue> = {
fn: () => TTriggerValue;
};
type TriggerGenerator<TTriggerValue> = {
gen: IterableIterator<TTriggerValue>;
};
/**
* A trigger can be a value, a function or generator. Value triggers never complete.
*
* A trigger function is considered complete if it returns undefined.
* A trigger generator is considered complete if it returns done.
*
*/
type Trigger<TTriggerValue> = TriggerValue<TTriggerValue> | TriggerFunction<TTriggerValue> | TriggerGenerator<TTriggerValue>;
type TimeoutPingOptions = Interval & {
/**
* If abort signals, it will disable
*/
abort?: AbortSignal;
};
type TimeoutValueOptions<TTriggerValue> = Trigger<TTriggerValue> & {
/**
* Whether to repeatedly trigger even if upstream source doesn't emit values.
* When _false_ (default) it will emit a max of one value after a source value if `interval` is reached.
* When _true_, it will continue emitting values at `interval`.
* Default: false
*/
repeat?: boolean;
/**
* Interval before emitting trigger value
* Default: 1s
*/
interval: Interval;
/**
* If _true_ (default) start the timeout
* immediately, even before the first value.
* If _false_, it won't timeout until the first
* upstream value happens.
*/
immediate?: boolean;
};
/**
* Options when creating a reactive object.
*/
type ObjectOptions<V extends Record<string, any>> = {
/**
* _false_ by default.
* If _true_, inherited fields are included. This is necessary for event args, for example.
*/
deepEntries: boolean;
/**
* Uses JSON.stringify() by default.
* Fn that returns _true_ if two values are equal, given a certain path.
*/
eq: IsEqualContext<V>;
};
type ValueToPingOptions<TUpstream> = {
/**
* If set, this function acts as a threshold gate.
* If the function returns _true_ the upstream value will trigger a ping
* Otherwise the value won't trigger a ping.
*
* By default all values trigger a ping.
* @param value
* @returns
*/
gate: (value: TUpstream) => boolean;
/**
* Laziness
* * start: only begins on first subscriber. Keeps running even when there are no subscribers
* * very: only begins on first subscriber. Stops looping if there are no subscribers
* * never: begins calling function when initalised and doesn't stop until Reactive is disposed
*/
lazy: Lazy;
/**
* If specified, signal is checked to prevent function execution.
* Also used for aborting a looped fromFunction.
*/
signal: AbortSignal;
};
type PingedFunctionOptions = {
/**
* If _true_, stream closes if function throws an error.
* If _false_, errors are emitted as signals, but stream is not closed.
* Default: _true_
*/
closeOnError: boolean;
/**
* Laziness
* * start: only begins on first subscriber. Keeps running even when there are no subscribers
* * very: only begins on first subscriber. Stops looping if there are no subscribers
* * never: begins calling function when initalised and doesn't stop until Reactive is disposed
*/
lazy: Lazy;
/**
* If specified, a time before invoking function.
* If `repeat` is used, this is in addition to `interval` time.
*/
predelay: Interval;
/***
* If specified, signal is checked to prevent function execution.
* Also used for aborting a looped fromFunction.
*/
signal: AbortSignal;
};
/**
* Options when creating a reactive object.
*/
type ArrayObjectOptions<V> = {
/**
* Uses JSON.stringify() by default.
* Fn that returns _true_ if two values are equal, given a certain path.
*/
eq: IsEqual<V>;
};
type FunctionOptions = Partial<InitLazyStreamOptions> & {
/**
* If _true_, stream closes if function throws an error.
* If _false_, errors are emitted as signals, but stream is not closed.
* Default: _true_
*/
closeOnError: boolean;
/**
* Laziness
* * start: only begins on first subscriber. Keeps running even when there are no subscribers
* * very: only begins on first subscriber. Stops looping if there are no subscribers
* * never: begins calling function when initalised and doesn't stop until Reactive is disposed
*/
lazy: Lazy;
/**
* If _true_, no automatic calling of function will happen, it will only
* be executed if the reactive gets a ping
* When this is set, 'interval' is ignored. 'maximumRepeats' and 'predelay' still apply.
* Default: _false_
*/
manual: boolean;
/**
* If specified, sets an upper limit of how many times we loop
* (if this is also enabled)
*/
maximumRepeats: number;
/**
* If specified, function is called repeatedly with this delay
*/
interval: Interval;
/**
* If specified, a time before invoking function.
* If `repeat` is used, this is in addition to `interval` time.
*/
predelay: Interval;
/***
* If specified, signal is checked to prevent function execution.
* Also used for aborting a looped fromFunction.
*/
signal: AbortSignal;
};
type GeneratorOptions = {
traceLifecycle: boolean;
/**
* Wait between reading from generator
* Default: 5ms
*/
readInterval: Interval;
/**
* Timeout when waiting for a value
* Default: `{ mins: 5 }`
*/
readTimeout: Interval;
/**
* If _true_, only accesses the generator if there is a subscriber.
* Default: true
*/
lazy: Lazy;
signal: AbortSignal;
/**
* Behaviour when reactive stops, for example due to having no subscribers
* * continue: iteration continues through array where it left off
* * reset: iteration begins from start of array
*/
whenStopped: `continue` | `reset`;
};
type EventOptions = {
lazy?: Lazy;
/**
* If true, log messages are emitted
* when event handlers are added/removed
*/
debugLifecycle?: boolean;
/**
* If true, log messages are emitted
* when the source event fires
*/
debugFiring?: boolean;
};
type EventTriggerOptions = EventOptions & {
/**
* If _true_ sends an initial trigger when starting
* Default: false
*/
fireInitial: boolean;
};
type EventPluckedFieldOptions<T> = {
lazy?: Lazy;
initialValue: T;
};
type EventPluckedFieldOptions2<TDomSource, TValueDestination> = {
lazy?: Lazy;
initialValue: TValueDestination;
domToValue: (value: TDomSource | undefined) => TValueDestination | undefined;
valueToDom: (value: TValueDestination) => TDomSource;
};
type DomValueOptions = EventOptions & {
/**
* If true, the current value will be emitted even though it wasn't
* triggered by an event.
* Default: false
*/
emitInitialValue: boolean;
attributeName: string;
fieldName: string;
/**
* Respond to when value has changed or when value is changing
* Default: `changed`
*/
when: `changed` | `changing`;
fallbackValue: string;
upstreamSource?: Reactive<any>;
upstreamFilter?: (value: any) => string;
};
type DomFormOptions<T extends Record<string, any>> = EventOptions & {
/**
* If true, the current value will be emitted even though it wasn't
* triggered by an event.
* Default: false
*/
emitInitialValue: boolean;
/**
* Respond to when value has changed or when value is changing
* Default: `changed`
*/
when: `changed` | `changing`;
upstreamSource?: Reactive<T>;
upstreamFilter?: (name: string, value: any) => string;
};
type DomNumberInputValueOptions = DomValueOptions & {
/**
* If true, sets up INPUT element to operate with relative values
*/
relative?: boolean;
/**
* If true, when setting up, sets max to be on left side
*/
inverted?: boolean;
upstreamSource?: Reactive<number>;
};
type DerivedFunction<TOutput> = (...args: Array<any>) => TOutput;
type DerivedOptions<TResult, T> = {
ignoreIdentical: boolean;
eq: (a: TResult, b: TResult) => boolean;
} & CombineLatestOptions & UpstreamOptions<T>;
type SetHtmlOptionsQuery = {
query: string;
};
type SetHtmlOptionsElement = {
el: HTMLElement;
};
type SetHtmlOptions = (SetHtmlOptionsQuery | SetHtmlOptionsElement) & {
/**
* If _true_ .innerHTML is used
* If _false_ (default) .textContent is used
*/
asHtml?: boolean;
};
/**
* Values from `input` are set to the textContent/innerHTML of an element.
* ```js
* const rxSource = Rx.From.string('hello');
* const rxSet = Rx.Sinks.setHtmlText(rxSource, { query: })
* ```
* @param rxOrSource
* @param optionsOrElementOrQuery
*/
declare const setHtmlText: (rxOrSource: ReactiveOrSource<any>, optionsOrElementOrQuery: SetHtmlOptions | string | HTMLElement) => Unsubscriber;
declare function max(input: ReactiveOrSource<any>, options: OpMathOptions): Reactive<number>;
declare function max(input: ReactiveOrSource<any>, options: OpAsAnnotation & OpMathOptions): Reactive<{
value: number;
max: number;
}>;
declare function min(input: ReactiveOrSource<any>, options: OpMathOptions): Reactive<number>;
declare function min(input: ReactiveOrSource<any>, options: OpAsAnnotation & OpMathOptions): Reactive<{
value: number;
min: number;
}>;
declare function average(input: ReactiveOrSource<any>, options: OpMathOptions): Reactive<number>;
declare function average(input: ReactiveOrSource<any>, options: OpAsAnnotation & OpMathOptions): Reactive<{
value: number;
average: number;
}>;
declare function sum(input: ReactiveOrSource<any>, options: OpMathOptions): Reactive<number>;
declare function sum(input: ReactiveOrSource<any>, options: OpAsAnnotation & OpMathOptions): Reactive<{
value: number;
sum: number;
}>;
type TallyOptions = OpMathOptions & {
countArrayItems: boolean;
};
declare function tally(input: ReactiveOrSource<any>, options: Partial<TallyOptions>): Reactive<number>;
declare function tally<TIn>(input: ReactiveOrSource<TIn>, options: OpAsAnnotation & Partial<TallyOptions>): Reactive<{
value: TIn;
tally: number;
}>;
declare function rank<TIn>(input: ReactiveOrSource<any>, rank: RankFunction<TIn>, options: Partial<RankOptions & OpMathOptions>): Reactive<TIn>;
declare function rank<TIn>(input: ReactiveOrSource<any>, rank: RankFunction<TIn>, options: OpAsAnnotation & Partial<RankOptions & OpMathOptions>): Reactive<{
value: TIn;
rank: TIn;
}>;
type CombineLatestOptions = {
/**
* If _true_, disposes all the merged sources when the merged reactive closes.
* Default: _true_.
*/
disposeSources: boolean;
/**
* How to handle when a source ends.
* * 'allow': continue combined stream, last value for done stream will kept
* * 'break': stop combined stream
*
* Default: 'break'
*/
onSourceDone: `allow` | `break`;
/**
* If _true_ (default), emits a value when initialised.
*/
emitInitial: boolean;
};
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
declare const symbol: unique symbol;
type SignalKinds = `done` | `warn`;
type Passed<V> = {
value: V | undefined;
signal?: SignalKinds;
context?: string;
};
type PassedSignal = Passed<any> & {
value: undefined;
signal: SignalKinds;
context: string;
};
type PassedValue<V> = Passed<V> & {
value: V;
};
type UpstreamOptions<In> = {
lazy: Lazy;
/**
* If _true_ (default), we dispose the underlying stream if the upstream closes. This happens after onStop() is called.
*/
disposeIfSourceDone: boolean;
onValue: (v: In) => void;
/**
* Called just before we subscribe to source
* @returns
*/
onStart: () => void;
/**
* Called after we unsubscribe from source
* @returns
*/
onStop: () => void;
debugLabel: string;
onDispose: (reason: string) => void;
};
type UpstreamInitialOptions<In> = UpstreamOptions<In> & {
initialValue: In;
};
/**
* Wrapped Reactive for object-oriented access
*/
type Wrapped<TIn> = {
enacts: {
setHtmlText: (options: SetHtmlOptions) => () => void;
};
source: Reactive<TIn>;
/**
* Annotate values with output from the `annotation` function.
* Returned values will be in the form `{ value:TIn, annotation:TAnnotation }`
* @param transformer
* @returns
*/
annotate: <TAnnotation>(transformer: (value: TIn) => TAnnotation) => Wrapped<{
value: TIn;
annotation: TAnnotation;
}>;
annotateWithOp: <TOut>(op: ReactiveOp<TIn, TOut>) => Wrapped<{
value: TIn;
annotation: TOut;
}>;
/**
* Accumulate a chunk of values, emitted as an array
* @param options
* @returns
*/
chunk: (options: Partial<ChunkOptions>) => Wrapped<Array<TIn>>;
debounce: (options: Partial<DebounceOptions>) => Wrapped<TIn>;
/**
* Pluck and emit a single field from values
* @param fieldName
* @param options
* @returns
*/
field: <TSource, TFieldType>(fieldName: keyof TIn, options: Partial<FieldOptions<TSource, TFieldType>>) => Wrapped<TFieldType>;
/**
* Throws away values that don't match `predicate`
* @param predicate
* @param options
* @returns
*/
filter: (predicate: FilterPredicate<TIn>, options: Partial<InitStreamOptions>) => Wrapped<TIn>;
combineLatestToArray: <const T extends ReadonlyArray<ReactiveOrSource<any>>>(sources: T, options: Partial<CombineLatestOptions>) => Wrapped<RxValueTypes<T>>;
combineLatestToObject: <const T extends Record<string, ReactiveOrSource<any>>>(sources: T, options: {
name: string;
} & Partial<CombineLatestOptions>) => Wrapped<RxValueTypeObject<T>>;
min: (options?: Partial<OpMathOptions>) => Wrapped<number>;
max: (options?: Partial<OpMathOptions>) => Wrapped<number>;
average: (options?: Partial<OpMathOptions>) => Wrapped<number>;
sum: (options?: Partial<OpMathOptions>) => Wrapped<number>;
tally: (options?: Partial<TallyOptions>) => Wrapped<number>;
/**
* Converts one source stream into two, with values being emitted by both
* @param options
* @returns
*/
split: (options?: Partial<SplitOptions>) => Array<Wrapped<TIn>>;
/**
* Emits values when this stream and any additional streams produce a value. The resulting stream is
* thus an array of values, each source at a given index.
* Waits to output a value until each stream has produced a value. Thus, the pace is determined by
* the slowest stream.
* @returns
*/
syncToArray: <const T extends ReadonlyArray<ReactiveOrSource<any>>>(reactiveSources: T, options?: Partial<SyncOptions>) => Wrapped<[TIn, ...RxValueTypes<T>]>;
syncToObject: <const T extends Record<string, ReactiveOrSource<any>>>(reactiveSources: T, options?: {
name?: string;
} & Partial<SyncOptions>) => Wrapped<RxValueTypeObject<T>>;
/**
* Creates new streams for each case, sending values to the stream if they match the filter predicate
* @param cases
* @param options
* @returns
*/
switcher: <TRec extends Record<string, FilterPredicate<TIn>>, TLabel extends keyof TRec>(cases: TRec, options: Partial<SwitcherOptions>) => Record<TLabel, Wrapped<TIn>>;
/**
* Creates new streams for each case
* @param labels
* @returns
*/
splitLabelled: <K extends keyof TIn>(...labels: Array<K>) => Record<K, Wrapped<TIn>>;
/**
* Taps the stream, passing values to one or more 'processor' functions.
* This processing essentially happens in parallel, not affecting the main stream.
*
* ```js
* // Stream of pointermove events with {x:0,y:0} as default
* const move = Rx.From.event(document.body, `pointermove`, {x:0,y:0});
* // Wrap it for fluent access
* const ptr = Rx.wrap(move)
* .tapProcess(
* // Create a string representation
* v => `${v.x},${v.y}`
* // Set to DOM
* v => {
* document.getElementById(`coords`).innerText = v;
* }
* )
* .onValue(value => {
* // 'value' will be original PointerEvent, since .tapProcess happened in parallel,
* // not affecting stream
* });
* ```
* @param processors One-five processing functions
* @returns
*/
tapProcess: <T2, T3, T4, T5, T6>(...processors: Processors<TIn, T2, T3, T4, T5, T6>) => Wrapped<TIn>;
tapStream: (divergedStream: ReactiveWritable<TIn>) => Wrapped<TIn>;
tapOps: <TOut>(source: ReactiveOrSource<TIn>, ...ops: Array<ReactiveOp<TIn, TOut>>) => Wrapped<TIn>;
/**
* Transforms all values
* @param transformer
* @param options
* @returns
*/
transform: <TOut>(transformer: (value: TIn) => TOut, options?: Partial<TransformOpts>) => Wrapped<TOut>;
/**
* Only allow values through if a minimum of time has elapsed. Throws away values.
* Ie. converts a fast stream into a slower one.
* @param options
* @returns
*/
throttle: (options: Partial<ThrottleOptions>) => Wrapped<TIn>;
/**
* Emits a value if `source` does not emit a value after `interval`
* has elapsed. This can be useful to reset a reactive to some
* 'zero' state if nothing is going on.
*
* If `source` emits faster than the `interval`, it won't get triggered.
*
* Default for 'timeout': 1000s.
*
* ```js
* // Emit 'hello' if 'source' doesn't emit a value after 1 minute
* const r = Rx.timeoutValue(source, { value: 'hello', interval: { mins: 1 } });
* ```
*
* Can also emit results from a function or generator
* ```js
* // Emits a random number if 'source' doesn't emit a value after 500ms
* const r = Rx.timeoutValue(source, { fn: Math.random, interval: 500 });
* ```
*
* If `immediate` option is _true_ (default), the timer starts from stream initialisation.
* Otherwise it won't start until it observes the first value from `source`.
* @param options
*/
timeoutValue: <TTriggerValue>(options: TimeoutValueOptions<TTriggerValue>) => Wrapped<TIn | TTriggerValue>;
/**
* 'Pings' reactive (if it supports it) if a value is not received within a given interval.
* Behaviour can be stopped using an abort signal.
* @param options
* @returns
*/
timeoutPing: (options: TimeoutPingOptions) => Wrapped<TIn>;
/**
* Copies values from source into an array, throwing
* an error if expected number of items is not reached
* @param options
* @returns
*/
toArrayOrThrow: (options: Partial<ToArrayOptions<TIn>>) => Promise<Array<TIn>>;
/**
* Copies values from source into an array.
* @param options
* @returns
*/
toArray: (options: Partial<ToArrayOptions<TIn>>) => Promise<Array<TIn | undefined>>;
/**
* Listen for values
* @param callback
* @returns
*/
onValue: (callback: (value: TIn) => void) => void;
};
type ToArrayOptions<V> = {
/**
* Maximim time to wait for `limit` to be reached. 10s by default.
*/
maximumWait: Interval;
/**
* Number of items to read
*/
limit: number;
/**
* Behaviour if threshold is not reached.
* partial: return partial results
* throw: throw an error
* fill: fill remaining array slots with `fillValue`
*/
underThreshold: `partial` | `throw` | `fill`;
/**
* Value to fill empty slots with if `underThreshold = 'fill'`.
*/
fillValue: V;
};
/**
* Laziness
* * start: only begins on first subscriber. Keeps running even when there are no subscribers
* * very: only begins on first subscriber. Stops looping if there are no subscribers
* * never: begins calling function when initalised and doesn't stop until Reactive is disposed
*/
type Lazy = `initial` | `never` | `very`;
type InitLazyStreamOptions = Partial<InitStreamOptions> & {
lazy?: Lazy;
debugLabel?: string;
onStart: () => void;
onStop: () => void;
};
type InitLazyStreamInitedOptions<T> = InitLazyStreamOptions & {
initialValue: T;
};
type ReactiveOrSource<V> = Wrapped<V> | Reactive<V> | IterableIterator<V> | AsyncIterableIterator<V> | Generator<V> | AsyncGenerator<V> | Array<V> | (() => V);
type BindUpdateOpts<V> = {
initial: (v: V, el: HTMLElement) => void;
binds: Record<string, DomBindValueTarget & {
transform?: (value: any) => string;
}>;
};
/**
* A Reactive
*/
type Reactive<V> = {
/**
* Subscribes to a reactive. Receives
* data as well as signals. Use `onValue` if you
* just care about values.
*
* Return result unsubscribes.
*
* ```js
* const unsub = someReactive.on(msg => {
* // Do something with msg.value
* });
*
* unsub(); // Unsubscribe
* ```
* @param handler
*/
on(handler: (value: Passed<V>) => void): Unsubscriber;
/**
* Subscribes to a reactive's values.
* Returns a function that unsubscribes.
* @param handler
*/
onValue(handler: (value: V) => void): Unsubscriber;
/**
* Disposes the reactive, providing a reason for debug tracing
* @param reason
*/
dispose(reason: string): void;
/**
* Returns _true_ if Reactive is disposed
*/
isDisposed(): boolean;
/**
* Optional 'set' to write a value. Use {@link ReactiveWritable} if you want this non-optional
* @param value
*/
set?(value: V): void;
};
/**
* A reactive that can be 'pinged' to produce a value.
*
* Use {@link isPingable} to check if a reactive is pingable.
*
* Pingable reactives are returned from
* * interpolate
* * computeWithPrevious
* * valueToPing
*/
type ReactivePingable<V> = Reactive<V> & {
ping(): void;
};
type Unsubscriber = () => void;
type ReactiveNonInitial<V> = Reactive<V> & {
last(): V | undefined;
};
/**
* A stream that can be written to
*/
type ReactiveWritable<TIn, TOut = TIn> = Reactive<TOut> & {
/**
* Sets a value
* @param value Value to write
*/
set(value: TIn): void;
};
type ReactiveInitial<V> = Reactive<V> & {
last(): V;
};
type ReactiveFinite = {
isDone(): boolean;
};
type ReactiveArray<V> = ReactiveWritable<Array<V>> & {
push(value: V): void;
deleteAt(index: number): void;
deleteWhere(filter: (value: V) => boolean): number;
setAt(index: number, value: V): void;
insertAt(index: number, value: V): void;
onArray(handler: (changes: Passed<Array<ChangeRecord<number>>>) => void): () => void;
};
type ObjectFieldHandler = {
value: any;
fieldName: string;
pattern: string;
};
type ReactiveDiff<V> = Reactive<V> & ReactiveWritable<V> & {
/**
* Notifies when the value of `fieldName` is changed.
*
* Use the returned function to unsubscribe.
* @param fieldName
* @param handler
*/
onField(fieldName: string, handler: (result: ObjectFieldHandler) => void): () => void;
/**
* Notifies of which field(s) were changed.
* If you just care about the whole, changed data use the `value` event.
*
* Use the returned function to unsubscribe.
* @param changes
*/
onDiff(changes: (changes: Array<PathDataChange<any>>) => void): () => void;
/**
* Updates the reactive with some partial key-value pairs.
* Keys omitted are left the same as the current value.
* @param changedPart
* @returns Returns new value
*/
update(changedPart: RecursivePartial<V>): V;
/**
* Updates a particular field by its path
* @param field
* @param value
*/
updateField(field: string, value: any): void;
};
/**
* A reactive stream which can be read and written to
*/
type ReactiveStream<V> = Reactive<V> & ReactiveWritable<V> & {
/**
* Removes all the subscribers from this stream.
*/
removeAllSubscribers(): void;
/**
* Dispatches a signal
* @param signal
* @param context
*/
signal(signal: SignalKinds, context?: string): void;
};
type ReactiveInitialStream<V> = ReactiveStream<V> & ReactiveInitial<V>;
type DomBindValueTarget = {
/**
* If _true_ `innerHTML` is set (a shortcut for elField:`innerHTML`)
*/
htmlContent?: boolean;
/**
* If _true_, 'textContent' is set (a shortcut for elField:'textContext')
*/
textContent?: boolean;
/**
* If set, this DOM element field is set. Eg 'textContent'
*/
elField?: string;
/**
* If set, this DOM attribute is set, Eg 'width'
*/
attribName?: string;
/**
* If set, this CSS variable is set, Eg 'hue' (sets '--hue')
*/
cssVariable?: string;
/**
* If set, this CSS property is set, Eg 'background-color'
*/
cssProperty?: string;
};
type ElementBind = {
/**
* Tag name for this binding.
* Overrides `defaultTag`
*/
tagName?: string;
/**
* If _true_, sub-paths are appended to element, rather than `container`
*/
nestChildren?: boolean;
transform?: (value: any) => string;
};
type ElementsOptions = {
container: HTMLElement | string;
defaultTag: string;
binds: Record<string, DomBindValueTarget & ElementBind>;
};
type DomBindTargetNode = {
query?: string;
element?: HTMLElement;
};
type DomBindTargetNodeResolved = {
element: HTMLElement;
};
type DomBindUnresolvedSource<TSource, TDestination> = DomBindTargetNode & DomBindSourceValue<TSource, TDestination> & DomBindValueTarget;
type DomBindResolvedSource<TSource, TDestination> = DomBindTargetNodeResolved & DomBindSourceValue<TSource, TDestination> & DomBindValueTarget;
type DomBindSourceValue<TSource, TDestination> = {
twoway?: boolean;
/**
* Field from source value to pluck and use.
* This will also be the value passed to the transform
*/
source