UNPKG

@cycle/isolate

Version:

A utility function to make scoped dataflow components in Cycle.js

168 lines 6.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var xstream_1 = require("xstream"); var adapt_1 = require("@cycle/run/lib/adapt"); function checkIsolateArgs(dataflowComponent, scope) { if (typeof dataflowComponent !== "function") { throw new Error("First argument given to isolate() must be a " + "'dataflowComponent' function"); } if (scope === null) { throw new Error("Second argument given to isolate() must not be null"); } } function normalizeScopes(sources, scopes, randomScope) { var perChannel = {}; Object.keys(sources).forEach(function (channel) { if (typeof scopes === 'string') { perChannel[channel] = scopes; return; } var candidate = scopes[channel]; if (typeof candidate !== 'undefined') { perChannel[channel] = candidate; return; } var wildcard = scopes['*']; if (typeof wildcard !== 'undefined') { perChannel[channel] = wildcard; return; } perChannel[channel] = randomScope; }); return perChannel; } function isolateAllSources(outerSources, scopes) { var innerSources = {}; for (var channel in outerSources) { var outerSource = outerSources[channel]; if (outerSources.hasOwnProperty(channel) && outerSource && scopes[channel] !== null && typeof outerSource.isolateSource === 'function') { innerSources[channel] = outerSource.isolateSource(outerSource, scopes[channel]); } else if (outerSources.hasOwnProperty(channel)) { innerSources[channel] = outerSources[channel]; } } return innerSources; } function isolateAllSinks(sources, innerSinks, scopes) { var outerSinks = {}; for (var channel in innerSinks) { var source = sources[channel]; var innerSink = innerSinks[channel]; if (innerSinks.hasOwnProperty(channel) && source && scopes[channel] !== null && typeof source.isolateSink === 'function') { outerSinks[channel] = adapt_1.adapt(source.isolateSink(xstream_1.default.fromObservable(innerSink), scopes[channel])); } else if (innerSinks.hasOwnProperty(channel)) { outerSinks[channel] = innerSinks[channel]; } } return outerSinks; } var counter = 0; function newScope() { return "cycle" + ++counter; } /** * 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 */ function isolate(component, scope) { if (scope === void 0) { scope = newScope(); } checkIsolateArgs(component, scope); var randomScope = typeof scope === 'object' ? newScope() : ''; var scopes = typeof scope === 'string' || typeof scope === 'object' ? scope : scope.toString(); return function wrappedComponent(outerSources) { var rest = []; for (var _i = 1; _i < arguments.length; _i++) { rest[_i - 1] = arguments[_i]; } var scopesPerChannel = normalizeScopes(outerSources, scopes, randomScope); var innerSources = isolateAllSources(outerSources, scopesPerChannel); var innerSinks = component.apply(void 0, [innerSources].concat(rest)); var outerSinks = isolateAllSinks(outerSources, innerSinks, scopesPerChannel); return outerSinks; }; } isolate.reset = function () { return (counter = 0); }; exports.default = isolate; function toIsolated(scope) { if (scope === void 0) { scope = newScope(); } return function (component) { return isolate(component, scope); }; } exports.toIsolated = toIsolated; //# sourceMappingURL=index.js.map