@cycle/isolate
Version:
A utility function to make scoped dataflow components in Cycle.js
113 lines (112 loc) • 5.48 kB
TypeScript
import { Stream } from 'xstream';
export declare type Component<So, Si> = (sources: So, ...rest: Array<any>) => Si;
export declare type FirstArg<T extends (r: any, ...args: Array<any>) => any> = T extends (r: infer R, ...args: Array<any>) => any ? R : any;
export declare type IsolateableSource<A = any, B = any> = {
isolateSource(source: IsolateableSource<A, B>, scope: any): IsolateableSource<A, B>;
isolateSink(sink: A, scope: any): B;
};
export declare type Sources = {
[name: string]: IsolateableSource;
};
export declare type WildcardScope = {
['*']?: string;
};
export declare type ScopesPerChannel<So> = {
[K in keyof So]: any;
};
export declare type Scopes<So> = (Partial<ScopesPerChannel<So>> & WildcardScope) | string;
/**
* `isolate` takes a small component as input, and returns a big component.
* A "small" component is a component that operates in a deeper scope.
* A "big" component is a component that operates on a scope that
* includes/wraps/nests the small component's scope. This is specially true for
* isolation contexts such as onionify.
*
* Notice that we type BigSo/BigSi as any. This is unfortunate, since ideally
* these would be generics in `isolate`. TypeScript's inference isn't strong
* enough yet for us to automatically provide the typings that would make
* `isolate` return a big component. However, we still keep these aliases here
* in case TypeScript's inference becomes better, then we know how to proceed
* to provide proper types.
*/
export declare type OuterSo<ISo> = {
[K in keyof ISo]: ISo[K] extends IsolateableSource ? FirstArg<IsolateableSource['isolateSource']> : ISo[K];
};
export declare type OuterSi<ISo, ISi> = {
[K in keyof ISo & keyof ISi]: ISo[K] extends IsolateableSource ? (ReturnType<ISo[K]['isolateSink']> extends Stream<infer T> ? Stream<T> : (ReturnType<ISo[K]['isolateSink']> extends Stream<any> ? Stream<unknown> : unknown)) : ISi[K];
} & {
[K in Exclude<keyof ISi, keyof ISo>]: ISi[K];
};
/**
* Takes a `component` function and a `scope`, and returns an isolated version
* of the `component` function.
*
* When the isolated component is invoked, each source provided to it is
* isolated to the given `scope` using `source.isolateSource(source, scope)`,
* if possible. Likewise, the sinks returned from the isolated component are
* isolated to the given `scope` using `source.isolateSink(sink, scope)`.
*
* The `scope` can be a string or an object. If it is anything else than those
* two types, it will be converted to a string. If `scope` is an object, it
* represents "scopes per channel", allowing you to specify a different scope
* for each key of sources/sinks. For instance
*
* ```js
* const childSinks = isolate(Child, {DOM: 'foo', HTTP: 'bar'})(sources);
* ```
*
* You can also use a wildcard `'*'` to use as a default for source/sinks
* channels that did not receive a specific scope:
*
* ```js
* // Uses 'bar' as the isolation scope for HTTP and other channels
* const childSinks = isolate(Child, {DOM: 'foo', '*': 'bar'})(sources);
* ```
*
* If a channel's value is null, then that channel's sources and sinks won't be
* isolated. If the wildcard is null and some channels are unspecified, those
* channels won't be isolated. If you don't have a wildcard and some channels
* are unspecified, then `isolate` will generate a random scope.
*
* ```js
* // Does not isolate HTTP requests
* const childSinks = isolate(Child, {DOM: 'foo', HTTP: null})(sources);
* ```
*
* If the `scope` argument is not provided at all, a new scope will be
* automatically created. This means that while **`isolate(component, scope)` is
* pure** (referentially transparent), **`isolate(component)` is impure** (not
* referentially transparent). Two calls to `isolate(Foo, bar)` will generate
* the same component. But, two calls to `isolate(Foo)` will generate two
* distinct components.
*
* ```js
* // Uses some arbitrary string as the isolation scope for HTTP and other channels
* const childSinks = isolate(Child, {DOM: 'foo'})(sources);
* ```
*
* Note that both `isolateSource()` and `isolateSink()` are static members of
* `source`. The reason for this is that drivers produce `source` while the
* application produces `sink`, and it's the driver's responsibility to
* implement `isolateSource()` and `isolateSink()`.
*
* _Note for Typescript users:_ `isolate` is not currently type-transparent and
* will explicitly convert generic type arguments to `any`. To preserve types in
* your components, you can use a type assertion:
*
* ```ts
* // if Child is typed `Component<Sources, Sinks>`
* const isolatedChild = isolate( Child ) as Component<Sources, Sinks>;
* ```
*
* @param {Function} component a function that takes `sources` as input
* and outputs a collection of `sinks`.
* @param {String} scope an optional string that is used to isolate each
* `sources` and `sinks` when the returned scoped component is invoked.
* @return {Function} the scoped component function that, as the original
* `component` function, takes `sources` and returns `sinks`.
* @function isolate
*/
declare function isolate<InnerSo, InnerSi>(component: Component<InnerSo, InnerSi>, scope?: any): Component<OuterSo<InnerSo>, OuterSi<InnerSo, InnerSi>>;
export default isolate;
export declare function toIsolated<InnerSo, InnerSi>(scope?: any): (c: Component<InnerSo, InnerSi>) => Component<OuterSo<InnerSo>, OuterSi<InnerSo, InnerSi>>;