@cycle/isolate
Version:
A utility function to make scoped dataflow components in Cycle.js
168 lines • 6.8 kB
JavaScript
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
;