UNPKG

5.48 kBTypeScriptView Raw
1import { Stream } from 'xstream';
2export declare type Component<So, Si> = (sources: So, ...rest: Array<any>) => Si;
3export declare type FirstArg<T extends (r: any, ...args: Array<any>) => any> = T extends (r: infer R, ...args: Array<any>) => any ? R : any;
4export declare type IsolateableSource<A = any, B = any> = {
5 isolateSource(source: IsolateableSource<A, B>, scope: any): IsolateableSource<A, B>;
6 isolateSink(sink: A, scope: any): B;
7};
8export declare type Sources = {
9 [name: string]: IsolateableSource;
10};
11export declare type WildcardScope = {
12 ['*']?: string;
13};
14export declare type ScopesPerChannel<So> = {
15 [K in keyof So]: any;
16};
17export declare type Scopes<So> = (Partial<ScopesPerChannel<So>> & WildcardScope) | string;
18/**
19 * `isolate` takes a small component as input, and returns a big component.
20 * A "small" component is a component that operates in a deeper scope.
21 * A "big" component is a component that operates on a scope that
22 * includes/wraps/nests the small component's scope. This is specially true for
23 * isolation contexts such as onionify.
24 *
25 * Notice that we type BigSo/BigSi as any. This is unfortunate, since ideally
26 * these would be generics in `isolate`. TypeScript's inference isn't strong
27 * enough yet for us to automatically provide the typings that would make
28 * `isolate` return a big component. However, we still keep these aliases here
29 * in case TypeScript's inference becomes better, then we know how to proceed
30 * to provide proper types.
31 */
32export declare type OuterSo<ISo> = {
33 [K in keyof ISo]: ISo[K] extends IsolateableSource ? FirstArg<IsolateableSource['isolateSource']> : ISo[K];
34};
35export declare type OuterSi<ISo, ISi> = {
36 [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];
37} & {
38 [K in Exclude<keyof ISi, keyof ISo>]: ISi[K];
39};
40/**
41 * Takes a `component` function and a `scope`, and returns an isolated version
42 * of the `component` function.
43 *
44 * When the isolated component is invoked, each source provided to it is
45 * isolated to the given `scope` using `source.isolateSource(source, scope)`,
46 * if possible. Likewise, the sinks returned from the isolated component are
47 * isolated to the given `scope` using `source.isolateSink(sink, scope)`.
48 *
49 * The `scope` can be a string or an object. If it is anything else than those
50 * two types, it will be converted to a string. If `scope` is an object, it
51 * represents "scopes per channel", allowing you to specify a different scope
52 * for each key of sources/sinks. For instance
53 *
54 * ```js
55 * const childSinks = isolate(Child, {DOM: 'foo', HTTP: 'bar'})(sources);
56 * ```
57 *
58 * You can also use a wildcard `'*'` to use as a default for source/sinks
59 * channels that did not receive a specific scope:
60 *
61 * ```js
62 * // Uses 'bar' as the isolation scope for HTTP and other channels
63 * const childSinks = isolate(Child, {DOM: 'foo', '*': 'bar'})(sources);
64 * ```
65 *
66 * If a channel's value is null, then that channel's sources and sinks won't be
67 * isolated. If the wildcard is null and some channels are unspecified, those
68 * channels won't be isolated. If you don't have a wildcard and some channels
69 * are unspecified, then `isolate` will generate a random scope.
70 *
71 * ```js
72 * // Does not isolate HTTP requests
73 * const childSinks = isolate(Child, {DOM: 'foo', HTTP: null})(sources);
74 * ```
75 *
76 * If the `scope` argument is not provided at all, a new scope will be
77 * automatically created. This means that while **`isolate(component, scope)` is
78 * pure** (referentially transparent), **`isolate(component)` is impure** (not
79 * referentially transparent). Two calls to `isolate(Foo, bar)` will generate
80 * the same component. But, two calls to `isolate(Foo)` will generate two
81 * distinct components.
82 *
83 * ```js
84 * // Uses some arbitrary string as the isolation scope for HTTP and other channels
85 * const childSinks = isolate(Child, {DOM: 'foo'})(sources);
86 * ```
87 *
88 * Note that both `isolateSource()` and `isolateSink()` are static members of
89 * `source`. The reason for this is that drivers produce `source` while the
90 * application produces `sink`, and it's the driver's responsibility to
91 * implement `isolateSource()` and `isolateSink()`.
92 *
93 * _Note for Typescript users:_ `isolate` is not currently type-transparent and
94 * will explicitly convert generic type arguments to `any`. To preserve types in
95 * your components, you can use a type assertion:
96 *
97 * ```ts
98 * // if Child is typed `Component<Sources, Sinks>`
99 * const isolatedChild = isolate( Child ) as Component<Sources, Sinks>;
100 * ```
101 *
102 * @param {Function} component a function that takes `sources` as input
103 * and outputs a collection of `sinks`.
104 * @param {String} scope an optional string that is used to isolate each
105 * `sources` and `sinks` when the returned scoped component is invoked.
106 * @return {Function} the scoped component function that, as the original
107 * `component` function, takes `sources` and returns `sinks`.
108 * @function isolate
109 */
110declare function isolate<InnerSo, InnerSi>(component: Component<InnerSo, InnerSi>, scope?: any): Component<OuterSo<InnerSo>, OuterSi<InnerSo, InnerSi>>;
111export default isolate;
112export declare function toIsolated<InnerSo, InnerSi>(scope?: any): (c: Component<InnerSo, InnerSi>) => Component<OuterSo<InnerSo>, OuterSi<InnerSo, InnerSi>>;