@cycle/run
Advanced tools
Comparing version 3.1.0 to 4.0.0-rc.1
@@ -7,3 +7,3 @@ <a name="3.1.0"></a> | ||
* **run:** sinks to support TypeScript interface ([c59ec55](https://github.com/cyclejs/cyclejs/tree/master/run/commit/c59ec55)) | ||
* **run:** sinks to support TypeScript interface ([c59ec55](https://github.com/cyclejs/cyclejs/commit/c59ec55)) | ||
@@ -18,4 +18,4 @@ | ||
* **run:** check for matching stream types of sinks and drivers ([4b4094c](https://github.com/cyclejs/cyclejs/tree/master/run/commit/4b4094c)) | ||
* **run:** reintroduce Driver function TS type ([1ad62cb](https://github.com/cyclejs/cyclejs/tree/master/run/commit/1ad62cb)) | ||
* **run:** check for matching stream types of sinks and drivers ([4b4094c](https://github.com/cyclejs/cyclejs/commit/4b4094c)) | ||
* **run:** reintroduce Driver function TS type ([1ad62cb](https://github.com/cyclejs/cyclejs/commit/1ad62cb)) | ||
@@ -39,3 +39,3 @@ | ||
* **run:** type check keyof drivers and main with TypeScript 2.2 ([da528c7](https://github.com/cyclejs/cyclejs/tree/master/run/commit/da528c7)) | ||
* **run:** type check keyof drivers and main with TypeScript 2.2 ([da528c7](https://github.com/cyclejs/cyclejs/commit/da528c7)) | ||
@@ -67,4 +67,4 @@ | ||
* **run:** fix race condition for drivers that subscribe late ([58b7991](https://github.com/cyclejs/cyclejs/tree/master/run/commit/58b7991)) | ||
* **run:** sink proxy completes on dispose, not with setTimeout ([47931fc](https://github.com/cyclejs/cyclejs/tree/master/run/commit/47931fc)) | ||
* **run:** fix race condition for drivers that subscribe late ([58b7991](https://github.com/cyclejs/cyclejs/commit/58b7991)) | ||
* **run:** sink proxy completes on dispose, not with setTimeout ([47931fc](https://github.com/cyclejs/cyclejs/commit/47931fc)) | ||
@@ -87,3 +87,3 @@ | ||
* **run:** fix race condition for drivers that subscribe late ([58b7991](https://github.com/cyclejs/cyclejs/tree/master/run/commit/58b7991)) | ||
* **run:** fix race condition for drivers that subscribe late ([58b7991](https://github.com/cyclejs/cyclejs/commit/58b7991)) | ||
@@ -98,3 +98,3 @@ | ||
* **run:** support drivers that dont return sources ([cda7602](https://github.com/cyclejs/cyclejs/tree/master/run/commit/cda7602)) | ||
* **run:** support drivers that dont return sources ([cda7602](https://github.com/cyclejs/cyclejs/commit/cda7602)) | ||
@@ -109,3 +109,3 @@ | ||
* **run:** adapt() sources, do not adapt() sinks ([0fd15ed](https://github.com/cyclejs/cyclejs/tree/master/run/commit/0fd15ed)) | ||
* **run:** adapt() sources, do not adapt() sinks ([0fd15ed](https://github.com/cyclejs/cyclejs/commit/0fd15ed)) | ||
@@ -120,3 +120,3 @@ | ||
* **run:** update to TypeScript 2.1 ([b7cabbc](https://github.com/cyclejs/cyclejs/tree/master/run/commit/b7cabbc)) | ||
* **run:** update to TypeScript 2.1 ([b7cabbc](https://github.com/cyclejs/cyclejs/commit/b7cabbc)) | ||
@@ -131,3 +131,3 @@ | ||
* **run:** clear buffers after all sink replicators are mutated ([432e4a6](https://github.com/cyclejs/cyclejs/tree/master/run/commit/432e4a6)) | ||
* **run:** clear buffers after all sink replicators are mutated ([432e4a6](https://github.com/cyclejs/cyclejs/commit/432e4a6)) | ||
@@ -142,4 +142,4 @@ | ||
* **run:** adapt sink to target stream lib before calling driver ([5edd925](https://github.com/cyclejs/cyclejs/tree/master/run/commit/5edd925)) | ||
* **run:** only free up buffers after replicator has been mutated ([bf8a7e1](https://github.com/cyclejs/cyclejs/tree/master/run/commit/bf8a7e1)) | ||
* **run:** adapt sink to target stream lib before calling driver ([5edd925](https://github.com/cyclejs/cyclejs/commit/5edd925)) | ||
* **run:** only free up buffers after replicator has been mutated ([bf8a7e1](https://github.com/cyclejs/cyclejs/commit/bf8a7e1)) | ||
@@ -154,3 +154,3 @@ | ||
* **run:** use FantasyObservable in Sinks, not xstream Stream ([d68a50e](https://github.com/cyclejs/cyclejs/tree/master/run/commit/d68a50e)) | ||
* **run:** use FantasyObservable in Sinks, not xstream Stream ([d68a50e](https://github.com/cyclejs/cyclejs/commit/d68a50e)) | ||
@@ -157,0 +157,0 @@ |
@@ -33,3 +33,3 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Cycle = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
if (drivers.hasOwnProperty(name_1)) { | ||
sinkProxies[name_1] = xstream_1.default.createWithMemory(); | ||
sinkProxies[name_1] = xstream_1.default.create(); | ||
} | ||
@@ -54,5 +54,5 @@ } | ||
for (var name_3 in sources) { | ||
if (sources.hasOwnProperty(name_3) | ||
&& sources[name_3] | ||
&& typeof sources[name_3]['shamefullySendNext'] === 'function') { | ||
if (sources.hasOwnProperty(name_3) && | ||
sources[name_3] && | ||
typeof sources[name_3]['shamefullySendNext'] === 'function') { | ||
sources[name_3] = adapt_1.adapt(sources[name_3]); | ||
@@ -75,8 +75,14 @@ } | ||
}); | ||
var subscriptions = sinkNames | ||
.map(function (name) { return xstream_1.default.fromObservable(sinks[name]).subscribe(replicators[name]); }); | ||
var subscriptions = sinkNames.map(function (name) { | ||
return xstream_1.default.fromObservable(sinks[name]).subscribe(replicators[name]); | ||
}); | ||
sinkNames.forEach(function (name) { | ||
var listener = sinkProxies[name]; | ||
var next = function (x) { listener._n(x); }; | ||
var error = function (err) { logToConsoleError(err); listener._e(err); }; | ||
var next = function (x) { | ||
listener._n(x); | ||
}; | ||
var error = function (err) { | ||
logToConsoleError(err); | ||
listener._e(err); | ||
}; | ||
buffers[name]._n.forEach(next); | ||
@@ -99,3 +105,5 @@ buffers[name]._e.forEach(error); | ||
for (var k in sources) { | ||
if (sources.hasOwnProperty(k) && sources[k] && sources[k].dispose) { | ||
if (sources.hasOwnProperty(k) && | ||
sources[k] && | ||
sources[k].dispose) { | ||
sources[k].dispose(); | ||
@@ -138,4 +146,3 @@ } | ||
if (typeof main !== "function") { | ||
throw new Error("First argument given to Cycle must be the 'main' " + | ||
"function."); | ||
throw new Error("First argument given to Cycle must be the 'main' " + "function."); | ||
} | ||
@@ -158,3 +165,3 @@ if (typeof drivers !== "object" || drivers === null) { | ||
} | ||
function run() { | ||
function _run() { | ||
var disposeReplication = replicateMany(sinks, sinkProxies); | ||
@@ -166,4 +173,3 @@ return function dispose() { | ||
} | ||
; | ||
return { sinks: sinks, sources: sources, run: run }; | ||
return { sinks: sinks, sources: sources, run: _run }; | ||
} | ||
@@ -199,7 +205,8 @@ exports.setup = setup; | ||
function run(main, drivers) { | ||
var _a = setup(main, drivers), run = _a.run, sinks = _a.sinks; | ||
if (typeof window !== 'undefined' && window['CyclejsDevTool_startGraphSerializer']) { | ||
window['CyclejsDevTool_startGraphSerializer'](sinks); | ||
var program = setup(main, drivers); | ||
if (typeof window !== 'undefined' && | ||
window['CyclejsDevTool_startGraphSerializer']) { | ||
window['CyclejsDevTool_startGraphSerializer'](program.sinks); | ||
} | ||
return run(); | ||
return program.run(); | ||
} | ||
@@ -206,0 +213,0 @@ exports.run = run; |
@@ -1,1 +0,7 @@ | ||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Cycle=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});var adaptStream=function(x){return x};function setAdapt(f){adaptStream=f}exports.setAdapt=setAdapt;function adapt(stream){return adaptStream(stream)}exports.adapt=adapt},{}],2:[function(require,module,exports){(function(global){"use strict";Object.defineProperty(exports,"__esModule",{value:true});var xstream_1=typeof window!=="undefined"?window["xstream"]:typeof global!=="undefined"?global["xstream"]:null;var adapt_1=require("./adapt");function logToConsoleError(err){var target=err.stack||err;if(console&&console.error){console.error(target)}else if(console&&console.log){console.log(target)}}function makeSinkProxies(drivers){var sinkProxies={};for(var name_1 in drivers){if(drivers.hasOwnProperty(name_1)){sinkProxies[name_1]=xstream_1.default.createWithMemory()}}return sinkProxies}function callDrivers(drivers,sinkProxies){var sources={};for(var name_2 in drivers){if(drivers.hasOwnProperty(name_2)){sources[name_2]=drivers[name_2](sinkProxies[name_2],name_2);if(sources[name_2]&&typeof sources[name_2]==="object"){sources[name_2]._isCycleSource=name_2}}}return sources}function adaptSources(sources){for(var name_3 in sources){if(sources.hasOwnProperty(name_3)&&sources[name_3]&&typeof sources[name_3]["shamefullySendNext"]==="function"){sources[name_3]=adapt_1.adapt(sources[name_3])}}return sources}function replicateMany(sinks,sinkProxies){var sinkNames=Object.keys(sinks).filter(function(name){return!!sinkProxies[name]});var buffers={};var replicators={};sinkNames.forEach(function(name){buffers[name]={_n:[],_e:[]};replicators[name]={next:function(x){return buffers[name]._n.push(x)},error:function(err){return buffers[name]._e.push(err)},complete:function(){}}});var subscriptions=sinkNames.map(function(name){return xstream_1.default.fromObservable(sinks[name]).subscribe(replicators[name])});sinkNames.forEach(function(name){var listener=sinkProxies[name];var next=function(x){listener._n(x)};var error=function(err){logToConsoleError(err);listener._e(err)};buffers[name]._n.forEach(next);buffers[name]._e.forEach(error);replicators[name].next=next;replicators[name].error=error;replicators[name]._n=next;replicators[name]._e=error});buffers=null;return function disposeReplication(){subscriptions.forEach(function(s){return s.unsubscribe()});sinkNames.forEach(function(name){return sinkProxies[name]._c()})}}function disposeSources(sources){for(var k in sources){if(sources.hasOwnProperty(k)&&sources[k]&&sources[k].dispose){sources[k].dispose()}}}function isObjectEmpty(obj){return Object.keys(obj).length===0}function setup(main,drivers){if(typeof main!=="function"){throw new Error("First argument given to Cycle must be the 'main' "+"function.")}if(typeof drivers!=="object"||drivers===null){throw new Error("Second argument given to Cycle must be an object "+"with driver functions as properties.")}if(isObjectEmpty(drivers)){throw new Error("Second argument given to Cycle must be an object "+"with at least one driver function declared as a property.")}var sinkProxies=makeSinkProxies(drivers);var sources=callDrivers(drivers,sinkProxies);var adaptedSources=adaptSources(sources);var sinks=main(adaptedSources);if(typeof window!=="undefined"){window.Cyclejs=window.Cyclejs||{};window.Cyclejs.sinks=sinks}function run(){var disposeReplication=replicateMany(sinks,sinkProxies);return function dispose(){disposeSources(sources);disposeReplication()}}return{sinks:sinks,sources:sources,run:run}}exports.setup=setup;function run(main,drivers){var _a=setup(main,drivers),run=_a.run,sinks=_a.sinks;if(typeof window!=="undefined"&&window["CyclejsDevTool_startGraphSerializer"]){window["CyclejsDevTool_startGraphSerializer"](sinks)}return run()}exports.run=run;exports.default=run}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./adapt":1}]},{},[2])(2)}); | ||
(function(n){"object"===typeof exports&&"undefined"!==typeof module?module.exports=n():"function"===typeof define&&define.amd?define([],n):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).Cycle=n()})(function(){return function l(h,a,g){function p(f,d){if(!a[f]){if(!h[f]){var k="function"==typeof require&&require;if(!d&&k)return k(f,!0);if(m)return m(f,!0);d=Error("Cannot find module '"+f+"'");throw d.code="MODULE_NOT_FOUND",d;}d=a[f]={exports:{}}; | ||
h[f][0].call(d.exports,function(a){var d=h[f][1][a];return p(d?d:a)},d,d.exports,l,h,a,g)}return a[f].exports}for(var m="function"==typeof require&&require,k=0;k<g.length;k++)p(g[k]);return p}({1:[function(l,h,a){Object.defineProperty(a,"__esModule",{value:!0});var g=function(a){return a};a.setAdapt=function(a){g=a};a.adapt=function(a){return g(a)}},{}],2:[function(l,h,a){(function(g){function h(e){var b={},a;for(a in e)e.hasOwnProperty(a)&&(b[a]=r.default.create());return b}function m(a,b){var e= | ||
{},c;for(c in a)a.hasOwnProperty(c)&&(e[c]=a[c](b[c],c),e[c]&&"object"===typeof e[c]&&(e[c]._isCycleSource=c));return e}function k(a){for(var b in a)a.hasOwnProperty(b)&&a[b]&&"function"===typeof a[b].shamefullySendNext&&(a[b]=t.adapt(a[b]));return a}function f(a,b){var e=Object.keys(a).filter(function(a){return!!b[a]}),c={},d={};e.forEach(function(a){c[a]={_n:[],_e:[]};d[a]={next:function(b){return c[a]._n.push(b)},error:function(b){return c[a]._e.push(b)},complete:function(){}}});var f=e.map(function(b){return r.default.fromObservable(a[b]).subscribe(d[b])}); | ||
e.forEach(function(a){var e=b[a],f=function(a){e._n(a)},g=function(a){var b=a.stack||a;console&&console.error?console.error(b):console&&console.log&&console.log(b);e._e(a)};c[a]._n.forEach(f);c[a]._e.forEach(g);d[a].next=f;d[a].error=g;d[a]._n=f;d[a]._e=g});c=null;return function(){f.forEach(function(a){return a.unsubscribe()});e.forEach(function(a){return b[a]._c()})}}function d(a,b){if("function"!==typeof a)throw Error("First argument given to Cycle must be the 'main' function.");if("object"!== | ||
typeof b||null===b)throw Error("Second argument given to Cycle must be an object with driver functions as properties.");if(0===Object.keys(b).length)throw Error("Second argument given to Cycle must be an object with at least one driver function declared as a property.");var d=h(b),c=m(b,d);b=k(c);var e=a(b);"undefined"!==typeof window&&(window.Cyclejs=window.Cyclejs||{},window.Cyclejs.sinks=e);return{sinks:e,sources:c,run:function(){var a=f(e,d);return function(){for(var b in c)c.hasOwnProperty(b)&& | ||
c[b]&&c[b].dispose&&c[b].dispose();a()}}}}function q(a,b){a=d(a,b);"undefined"!==typeof window&&window.CyclejsDevTool_startGraphSerializer&&window.CyclejsDevTool_startGraphSerializer(a.sinks);return a.run()}Object.defineProperty(a,"__esModule",{value:!0});var r="undefined"!==typeof window?window.xstream:"undefined"!==typeof g?g.xstream:null,t=l("./adapt");a.setup=d;a.run=q;a.default=q}).call(this,"undefined"!==typeof global?global:"undefined"!==typeof self?self:"undefined"!==typeof window?window: | ||
{})},{"./adapt":1}]},{},[2])(2)}); |
@@ -1,56 +0,4 @@ | ||
import { MemoryStream } from 'xstream'; | ||
export interface FantasyObserver { | ||
next(x: any): void; | ||
error(err: any): void; | ||
complete(c?: any): void; | ||
} | ||
export interface FantasySubscription { | ||
unsubscribe(): void; | ||
} | ||
export interface FantasyObservable { | ||
subscribe(observer: FantasyObserver): FantasySubscription; | ||
} | ||
export declare type DisposeFunction = () => void; | ||
export interface DevToolEnabledSource { | ||
_isCycleSource: string; | ||
} | ||
export interface Driver<Sink, Source> { | ||
(stream: Sink, driverName?: string): Source; | ||
} | ||
export declare type Drivers<So extends Sources, Si extends Sinks> = { | ||
[P in keyof (So & Si)]: Driver<Si[P], So[P]>; | ||
}; | ||
export declare type Sources = { | ||
[name: string]: any; | ||
}; | ||
export declare type Sinks = { | ||
[name: string]: any; | ||
}; | ||
export declare type FantasySinks<Si> = { | ||
[S in keyof Si]: FantasyObservable; | ||
}; | ||
import { CycleProgram, DisposeFunction, Drivers, Sources, FantasySinks } from './types'; | ||
export { FantasyObserver, FantasySubscription, FantasyObservable, DevToolEnabledSource, Sources, Sinks, SinkProxies, FantasySinks, Driver, Drivers, DisposeFunction, CycleProgram } from './types'; | ||
/** | ||
* Sink proxies should be MemoryStreams in order to fix race conditions for | ||
* drivers that subscribe to sink proxies "later". | ||
* | ||
* Recall that there are two steps: | ||
* 1. Setup (sink proxies -> drivers -> sources -> main -> sink) | ||
* 2. Execution (also known as replication: sink proxies imitate sinks) | ||
* | ||
* If a driver does not synchronously/immediately subscribe to the sink proxy | ||
* in step (1), but instead does that later, if step (2) feeds a value from the | ||
* sink to the sink proxy, then when the driver subscribes to the sink proxy, | ||
* it should receive that value. This is why we need MemoryStreams, not just | ||
* Streams. Note: Cycle DOM driver is an example of such case, since it waits | ||
* for 'readystatechange'. | ||
*/ | ||
export declare type SinkProxies<Si extends Sinks> = { | ||
[P in keyof Si]: MemoryStream<any>; | ||
}; | ||
export interface CycleProgram<So extends Sources, Si extends Sinks> { | ||
sources: So; | ||
sinks: Si; | ||
run(): DisposeFunction; | ||
} | ||
/** | ||
* A function that prepares the Cycle application to be executed. Takes a `main` | ||
@@ -57,0 +5,0 @@ * function and prepares to circularly connects it to the given collection of |
@@ -18,3 +18,3 @@ "use strict"; | ||
if (drivers.hasOwnProperty(name_1)) { | ||
sinkProxies[name_1] = xstream_1.default.createWithMemory(); | ||
sinkProxies[name_1] = xstream_1.default.create(); | ||
} | ||
@@ -39,5 +39,5 @@ } | ||
for (var name_3 in sources) { | ||
if (sources.hasOwnProperty(name_3) | ||
&& sources[name_3] | ||
&& typeof sources[name_3]['shamefullySendNext'] === 'function') { | ||
if (sources.hasOwnProperty(name_3) && | ||
sources[name_3] && | ||
typeof sources[name_3]['shamefullySendNext'] === 'function') { | ||
sources[name_3] = adapt_1.adapt(sources[name_3]); | ||
@@ -60,8 +60,14 @@ } | ||
}); | ||
var subscriptions = sinkNames | ||
.map(function (name) { return xstream_1.default.fromObservable(sinks[name]).subscribe(replicators[name]); }); | ||
var subscriptions = sinkNames.map(function (name) { | ||
return xstream_1.default.fromObservable(sinks[name]).subscribe(replicators[name]); | ||
}); | ||
sinkNames.forEach(function (name) { | ||
var listener = sinkProxies[name]; | ||
var next = function (x) { listener._n(x); }; | ||
var error = function (err) { logToConsoleError(err); listener._e(err); }; | ||
var next = function (x) { | ||
listener._n(x); | ||
}; | ||
var error = function (err) { | ||
logToConsoleError(err); | ||
listener._e(err); | ||
}; | ||
buffers[name]._n.forEach(next); | ||
@@ -84,3 +90,5 @@ buffers[name]._e.forEach(error); | ||
for (var k in sources) { | ||
if (sources.hasOwnProperty(k) && sources[k] && sources[k].dispose) { | ||
if (sources.hasOwnProperty(k) && | ||
sources[k] && | ||
sources[k].dispose) { | ||
sources[k].dispose(); | ||
@@ -123,4 +131,3 @@ } | ||
if (typeof main !== "function") { | ||
throw new Error("First argument given to Cycle must be the 'main' " + | ||
"function."); | ||
throw new Error("First argument given to Cycle must be the 'main' " + "function."); | ||
} | ||
@@ -143,3 +150,3 @@ if (typeof drivers !== "object" || drivers === null) { | ||
} | ||
function run() { | ||
function _run() { | ||
var disposeReplication = replicateMany(sinks, sinkProxies); | ||
@@ -151,4 +158,3 @@ return function dispose() { | ||
} | ||
; | ||
return { sinks: sinks, sources: sources, run: run }; | ||
return { sinks: sinks, sources: sources, run: _run }; | ||
} | ||
@@ -184,7 +190,8 @@ exports.setup = setup; | ||
function run(main, drivers) { | ||
var _a = setup(main, drivers), run = _a.run, sinks = _a.sinks; | ||
if (typeof window !== 'undefined' && window['CyclejsDevTool_startGraphSerializer']) { | ||
window['CyclejsDevTool_startGraphSerializer'](sinks); | ||
var program = setup(main, drivers); | ||
if (typeof window !== 'undefined' && | ||
window['CyclejsDevTool_startGraphSerializer']) { | ||
window['CyclejsDevTool_startGraphSerializer'](program.sinks); | ||
} | ||
return run(); | ||
return program.run(); | ||
} | ||
@@ -191,0 +198,0 @@ exports.run = run; |
{ | ||
"name": "@cycle/run", | ||
"version": "3.1.0", | ||
"version": "4.0.0-rc.1", | ||
"description": "The Cycle.js run() function to use with xstream", | ||
@@ -47,21 +47,7 @@ "license": "MIT", | ||
"scripts": { | ||
"lint": "../node_modules/.bin/tslint --config ../tslint.json --project tsconfig.json", | ||
"prelib": "rm -rf lib/ && mkdir -p lib/", | ||
"lib": "../node_modules/.bin/tsc", | ||
"premocha": "npm run lib", | ||
"mocha": "../node_modules/.bin/mocha test/*.ts --require ts-node/register", | ||
"test": "npm run lint && npm run mocha", | ||
"test": "npm run mocha", | ||
"test-ci": "npm run test", | ||
"browserify": "../node_modules/.bin/browserify lib/index.js --global-transform=browserify-shim --standalone Cycle --exclude xstream -o dist/cycle-run.js", | ||
"uglify": "../node_modules/.bin/uglifyjs dist/cycle-run.js -o dist/cycle-run.min.js", | ||
"predist": "rm -rf dist/ && mkdir -p dist/", | ||
"dist": "npm run lib && npm run browserify && npm run uglify", | ||
"docs": "node ../.scripts/make-api-docs.js ${PWD##*/}", | ||
"preversion": "npm test", | ||
"version": "npm run dist && npm run docs && npm run changelog", | ||
"postversion": "git add -A && git commit -m \"release(${PWD##*/}): v$(cat package.json | ../node_modules/.bin/jase version)\" && git push origin master && npm publish", | ||
"release-patch": "false", | ||
"release-minor": "npm --no-git-tag-version version minor", | ||
"release-major": "npm --no-git-tag-version version major", | ||
"changelog": "node ../.scripts/update-changelogs.js ${PWD##*/}" | ||
"minify": "node ../.scripts/minify.js dist/cycle-run.js dist/cycle-run.min.js" | ||
}, | ||
@@ -68,0 +54,0 @@ "publishConfig": { |
@@ -1,1 +0,2 @@ | ||
[Documentation for Cycle Run on cycle.js.org](https://cycle.js.org/api/run.html) | ||
- [**Read the docs here**](https://cycle.js.org/api/run.html) | ||
- [**Edit the docs here**](https://github.com/cyclejs/cyclejs/blob/master/docs/content/api/run.md) |
@@ -15,2 +15,2 @@ import {Stream} from 'xstream'; | ||
return adaptStream(stream); | ||
} | ||
} |
193
src/index.ts
@@ -1,69 +0,29 @@ | ||
import xs, {Stream, MemoryStream} from 'xstream'; | ||
import xs, {Stream} from 'xstream'; | ||
import {adapt} from './adapt'; | ||
import { | ||
CycleProgram, | ||
DevToolEnabledSource, | ||
DisposeFunction, | ||
Drivers, | ||
SinkProxies, | ||
Sources, | ||
Sinks, | ||
FantasySinks, | ||
} from './types'; | ||
export interface FantasyObserver { | ||
next(x: any): void; | ||
error(err: any): void; | ||
complete(c?: any): void; | ||
} | ||
export { | ||
FantasyObserver, | ||
FantasySubscription, | ||
FantasyObservable, | ||
DevToolEnabledSource, | ||
Sources, | ||
Sinks, | ||
SinkProxies, | ||
FantasySinks, | ||
Driver, | ||
Drivers, | ||
DisposeFunction, | ||
CycleProgram, | ||
} from './types'; | ||
export interface FantasySubscription { | ||
unsubscribe(): void; | ||
} | ||
export interface FantasyObservable { | ||
subscribe(observer: FantasyObserver): FantasySubscription; | ||
} | ||
export type DisposeFunction = () => void; | ||
export interface DevToolEnabledSource { | ||
_isCycleSource: string; | ||
} | ||
export interface Driver<Sink, Source> { | ||
(stream: Sink, driverName?: string): Source; | ||
} | ||
export type Drivers<So extends Sources, Si extends Sinks> = { | ||
[P in keyof (So & Si)]: Driver<Si[P], So[P]>; | ||
}; | ||
export type Sources = { | ||
[name: string]: any; | ||
}; | ||
export type Sinks = { | ||
[name: string]: any; | ||
}; | ||
export type FantasySinks<Si> = { | ||
[S in keyof Si]: FantasyObservable; | ||
}; | ||
/** | ||
* Sink proxies should be MemoryStreams in order to fix race conditions for | ||
* drivers that subscribe to sink proxies "later". | ||
* | ||
* Recall that there are two steps: | ||
* 1. Setup (sink proxies -> drivers -> sources -> main -> sink) | ||
* 2. Execution (also known as replication: sink proxies imitate sinks) | ||
* | ||
* If a driver does not synchronously/immediately subscribe to the sink proxy | ||
* in step (1), but instead does that later, if step (2) feeds a value from the | ||
* sink to the sink proxy, then when the driver subscribes to the sink proxy, | ||
* it should receive that value. This is why we need MemoryStreams, not just | ||
* Streams. Note: Cycle DOM driver is an example of such case, since it waits | ||
* for 'readystatechange'. | ||
*/ | ||
export type SinkProxies<Si extends Sinks> = { | ||
[P in keyof Si]: MemoryStream<any>; | ||
}; | ||
export interface CycleProgram<So extends Sources, Si extends Sinks> { | ||
sources: So; | ||
sinks: Si; | ||
run(): DisposeFunction; | ||
} | ||
function logToConsoleError(err: any) { | ||
@@ -79,7 +39,8 @@ const target = err.stack || err; | ||
function makeSinkProxies<So extends Sources, Si extends Sinks>( | ||
drivers: Drivers<So, Si>): SinkProxies<Si> { | ||
drivers: Drivers<So, Si>, | ||
): SinkProxies<Si> { | ||
const sinkProxies: SinkProxies<Si> = {} as SinkProxies<Si>; | ||
for (const name in drivers) { | ||
if (drivers.hasOwnProperty(name)) { | ||
sinkProxies[name] = xs.createWithMemory<any>(); | ||
sinkProxies[name] = xs.create<any>(); | ||
} | ||
@@ -91,4 +52,5 @@ } | ||
function callDrivers<So extends Sources, Si extends Sinks>( | ||
drivers: Drivers<So, Si>, | ||
sinkProxies: SinkProxies<Si>): So { | ||
drivers: Drivers<So, Si>, | ||
sinkProxies: SinkProxies<Si>, | ||
): So { | ||
const sources: So = {} as So; | ||
@@ -109,6 +71,8 @@ for (const name in drivers) { | ||
for (const name in sources) { | ||
if (sources.hasOwnProperty(name) | ||
&& sources[name] | ||
&& typeof sources[name]['shamefullySendNext'] === 'function') { | ||
sources[name] = adapt(sources[name] as any as Stream<any>); | ||
if ( | ||
sources.hasOwnProperty(name) && | ||
sources[name] && | ||
typeof sources[name]['shamefullySendNext'] === 'function' | ||
) { | ||
sources[name] = adapt((sources[name] as any) as Stream<any>); | ||
} | ||
@@ -132,3 +96,3 @@ } | ||
complete(): void; | ||
}; | ||
} | ||
}; | ||
@@ -140,13 +104,16 @@ | ||
_e: Array<any>; | ||
}; | ||
} | ||
}; | ||
function replicateMany<So extends Sources, Si extends Sinks>( | ||
sinks: Si, | ||
sinkProxies: SinkProxies<Si>): DisposeFunction { | ||
const sinkNames: Array<keyof Si> = Object.keys(sinks).filter(name => !!sinkProxies[name]); | ||
sinks: Si, | ||
sinkProxies: SinkProxies<Si>, | ||
): DisposeFunction { | ||
const sinkNames: Array<keyof Si> = Object.keys(sinks).filter( | ||
name => !!sinkProxies[name], | ||
); | ||
let buffers: ReplicationBuffers<Si> = {} as ReplicationBuffers<Si>; | ||
const replicators: SinkReplicators<Si> = {} as SinkReplicators<Si>; | ||
sinkNames.forEach((name) => { | ||
sinkNames.forEach(name => { | ||
buffers[name] = {_n: [], _e: []}; | ||
@@ -160,9 +127,15 @@ replicators[name] = { | ||
const subscriptions = sinkNames | ||
.map(name => xs.fromObservable(sinks[name] as any).subscribe(replicators[name])); | ||
const subscriptions = sinkNames.map(name => | ||
xs.fromObservable(sinks[name] as any).subscribe(replicators[name]), | ||
); | ||
sinkNames.forEach((name) => { | ||
sinkNames.forEach(name => { | ||
const listener = sinkProxies[name]; | ||
const next = (x: any) => { listener._n(x); }; | ||
const error = (err: any) => { logToConsoleError(err); listener._e(err); }; | ||
const next = (x: any) => { | ||
listener._n(x); | ||
}; | ||
const error = (err: any) => { | ||
logToConsoleError(err); | ||
listener._e(err); | ||
}; | ||
buffers[name]._n.forEach(next); | ||
@@ -181,3 +154,3 @@ buffers[name]._e.forEach(error); | ||
subscriptions.forEach(s => s.unsubscribe()); | ||
sinkNames.forEach((name) => sinkProxies[name]._c()); | ||
sinkNames.forEach(name => sinkProxies[name]._c()); | ||
}; | ||
@@ -188,3 +161,7 @@ } | ||
for (const k in sources) { | ||
if (sources.hasOwnProperty(k) && sources[k] && (sources[k] as any).dispose) { | ||
if ( | ||
sources.hasOwnProperty(k) && | ||
sources[k] && | ||
(sources[k] as any).dispose | ||
) { | ||
(sources[k] as any).dispose(); | ||
@@ -228,15 +205,21 @@ } | ||
export function setup<So extends Sources, Si extends FantasySinks<Si>>( | ||
main: (sources: So) => Si, | ||
drivers: Drivers<So, Si>): CycleProgram<So, Si> { | ||
main: (sources: So) => Si, | ||
drivers: Drivers<So, Si>, | ||
): CycleProgram<So, Si> { | ||
if (typeof main !== `function`) { | ||
throw new Error(`First argument given to Cycle must be the 'main' ` + | ||
`function.`); | ||
throw new Error( | ||
`First argument given to Cycle must be the 'main' ` + `function.`, | ||
); | ||
} | ||
if (typeof drivers !== `object` || drivers === null) { | ||
throw new Error(`Second argument given to Cycle must be an object ` + | ||
`with driver functions as properties.`); | ||
throw new Error( | ||
`Second argument given to Cycle must be an object ` + | ||
`with driver functions as properties.`, | ||
); | ||
} | ||
if (isObjectEmpty(drivers)) { | ||
throw new Error(`Second argument given to Cycle must be an object ` + | ||
`with at least one driver function declared as a property.`); | ||
throw new Error( | ||
`Second argument given to Cycle must be an object ` + | ||
`with at least one driver function declared as a property.`, | ||
); | ||
} | ||
@@ -252,3 +235,3 @@ | ||
} | ||
function run(): DisposeFunction { | ||
function _run(): DisposeFunction { | ||
const disposeReplication = replicateMany(sinks, sinkProxies); | ||
@@ -259,4 +242,4 @@ return function dispose() { | ||
}; | ||
}; | ||
return {sinks, sources, run}; | ||
} | ||
return {sinks, sources, run: _run}; | ||
} | ||
@@ -292,11 +275,15 @@ | ||
export function run<So extends Sources, Si extends FantasySinks<Si>>( | ||
main: (sources: So) => Si, | ||
drivers: Drivers<So, Si>): DisposeFunction { | ||
const {run, sinks} = setup(main, drivers); | ||
if (typeof window !== 'undefined' && window['CyclejsDevTool_startGraphSerializer']) { | ||
window['CyclejsDevTool_startGraphSerializer'](sinks); | ||
main: (sources: So) => Si, | ||
drivers: Drivers<So, Si>, | ||
): DisposeFunction { | ||
const program = setup(main, drivers); | ||
if ( | ||
typeof window !== 'undefined' && | ||
window['CyclejsDevTool_startGraphSerializer'] | ||
) { | ||
window['CyclejsDevTool_startGraphSerializer'](program.sinks); | ||
} | ||
return run(); | ||
return program.run(); | ||
} | ||
export default run; |
@@ -16,8 +16,8 @@ import 'mocha'; | ||
describe('setup', function () { | ||
it('should be a function', function () { | ||
describe('setup', function() { | ||
it('should be a function', function() { | ||
assert.strictEqual(typeof setup, 'function'); | ||
}); | ||
it('should throw if first argument is not a function', function () { | ||
it('should throw if first argument is not a function', function() { | ||
assert.throws(() => { | ||
@@ -28,3 +28,3 @@ (setup as any)('not a function'); | ||
it('should throw if second argument is not an object', function () { | ||
it('should throw if second argument is not an object', function() { | ||
assert.throws(() => { | ||
@@ -35,3 +35,3 @@ (setup as any)(() => {}, 'not an object'); | ||
it('should throw if second argument is an empty object', function () { | ||
it('should throw if second argument is an empty object', function() { | ||
assert.throws(() => { | ||
@@ -42,3 +42,3 @@ (setup as any)(() => {}, {}); | ||
it('should return sinks object and sources object', function () { | ||
it('should return sinks object and sources object', function() { | ||
function app(ext: any): any { | ||
@@ -61,3 +61,3 @@ return { | ||
it('should type-check keyof sources and sinks in main and drivers', function () { | ||
it('should type-check keyof sources and sinks in main and drivers', function() { | ||
type Sources = { | ||
@@ -80,10 +80,11 @@ str: Stream<string>; | ||
const stringDriver: Driver<Stream<string>, Stream<string>> = | ||
(sink: Stream<string>) => xs.of('b'); | ||
const stringDriver: Driver<Stream<string>, Stream<string>> = ( | ||
sink: Stream<string>, | ||
) => xs.of('b'); | ||
const numberWriteOnlyDriver: Driver<Stream<number>, void> = | ||
(sink: Stream<number>) => {}; | ||
const numberWriteOnlyDriver: Driver<Stream<number>, void> = ( | ||
sink: Stream<number>, | ||
) => {}; | ||
const objectReadOnlyDriver: Driver<void, Stream<object>> = | ||
() => xs.of({}); | ||
const objectReadOnlyDriver: Driver<void, Stream<object>> = () => xs.of({}); | ||
@@ -97,7 +98,7 @@ setup(app, { | ||
it('should type-check keyof sources and sinks, supporting interfaces', function () { | ||
it('should type-check keyof sources and sinks, supporting interfaces', function() { | ||
interface Sources { | ||
str: Stream<string>; | ||
obj: Stream<object>; | ||
}; | ||
} | ||
@@ -107,3 +108,3 @@ interface Sinks { | ||
num: Stream<number>; | ||
}; | ||
} | ||
@@ -122,10 +123,11 @@ function app(sources: Sources): Sinks { | ||
const stringDriver: Driver<Stream<string>, Stream<string>> = | ||
(sink: Stream<string>) => xs.of('b'); | ||
const stringDriver: Driver<Stream<string>, Stream<string>> = ( | ||
sink: Stream<string>, | ||
) => xs.of('b'); | ||
const numberWriteOnlyDriver: Driver<Stream<number>, void> = | ||
(sink: Stream<number>) => {}; | ||
const numberWriteOnlyDriver: Driver<Stream<number>, void> = ( | ||
sink: Stream<number>, | ||
) => {}; | ||
const objectReadOnlyDriver: Driver<void, Stream<object>> = | ||
() => xs.of({}); | ||
const objectReadOnlyDriver: Driver<void, Stream<object>> = () => xs.of({}); | ||
@@ -139,3 +141,3 @@ setup(app, { | ||
it('should type-check and allow more drivers than sinks', function () { | ||
it('should type-check and allow more drivers than sinks', function() { | ||
type Sources = { | ||
@@ -148,4 +150,3 @@ str: Stream<string>; | ||
function app(sources: Sources) { | ||
return { | ||
}; | ||
return {}; | ||
} | ||
@@ -168,3 +169,3 @@ | ||
it('should call DevTool internal function to pass sinks', function () { | ||
it('should call DevTool internal function to pass sinks', function() { | ||
let sandbox = sinon.sandbox.create(); | ||
@@ -187,3 +188,3 @@ let spy = sandbox.spy(); | ||
it('should return a run() which in turn returns a dispose()', function (done) { | ||
it('should return a run() which in turn returns a dispose()', function(done) { | ||
type TestSources = { | ||
@@ -220,3 +221,3 @@ other: Stream<number>; | ||
it('should support sink-only drivers', function (done) { | ||
it('should support sink-only drivers', function(done) { | ||
function app(sources: any): any { | ||
@@ -241,3 +242,3 @@ return { | ||
it('should not adapt() sinks', function (done) { | ||
it('should not adapt() sinks', function(done) { | ||
function app(sources: any): any { | ||
@@ -265,3 +266,3 @@ return { | ||
it('should adapt() a simple source (stream)', function (done) { | ||
it('should adapt() a simple source (stream)', function(done) { | ||
let appCalled = false; | ||
@@ -290,3 +291,3 @@ function app(sources: any): any { | ||
it('should not work after has been disposed', function (done) { | ||
it('should not work after has been disposed', function(done) { | ||
type MySources = { | ||
@@ -309,3 +310,3 @@ other: Stream<string>; | ||
sources.other.addListener({ | ||
next: (x) => { | ||
next: x => { | ||
assert.notStrictEqual(x, 'x3'); | ||
@@ -323,8 +324,8 @@ if (x === 'x2') { | ||
describe('run', function () { | ||
it('should be a function', function () { | ||
describe('run', function() { | ||
it('should be a function', function() { | ||
assert.strictEqual(typeof run, 'function'); | ||
}); | ||
it('should throw if first argument is not a function', function () { | ||
it('should throw if first argument is not a function', function() { | ||
assert.throws(() => { | ||
@@ -335,3 +336,3 @@ (run as any)('not a function'); | ||
it('should throw if second argument is not an object', function () { | ||
it('should throw if second argument is not an object', function() { | ||
assert.throws(() => { | ||
@@ -342,3 +343,3 @@ (run as any)(() => {}, 'not an object'); | ||
it('should throw if second argument is an empty object', function () { | ||
it('should throw if second argument is an empty object', function() { | ||
assert.throws(() => { | ||
@@ -349,3 +350,3 @@ (run as any)(() => {}, {}); | ||
it('should return a dispose function', function () { | ||
it('should return a dispose function', function() { | ||
let sandbox = sinon.sandbox.create(); | ||
@@ -377,7 +378,11 @@ const spy = sandbox.spy(); | ||
it('should happen synchronously', function (done) { | ||
it('should happen synchronously', function(done) { | ||
let sandbox = sinon.sandbox.create(); | ||
const spy = sandbox.spy(); | ||
function app(sources: any): any { | ||
sources.other.addListener({next: () => {}, error: () => {}, complete: () => {}}); | ||
sources.other.addListener({ | ||
next: () => {}, | ||
error: () => {}, | ||
complete: () => {}, | ||
}); | ||
return { | ||
@@ -403,3 +408,5 @@ other: xs.of(10), | ||
it('should support driver that asynchronously subscribes to sink', function (done) { | ||
it('should support driver that asynchronously subscribes to sink', function( | ||
done, | ||
) { | ||
function app(sources: any): any { | ||
@@ -413,3 +420,13 @@ return { | ||
function driver(sink: Stream<number>): Stream<any> { | ||
const buffer: Array<number> = []; | ||
sink.addListener({ | ||
next: x => { | ||
buffer.push(x); | ||
}, | ||
}); | ||
setTimeout(() => { | ||
while (buffer.length > 0) { | ||
const x = buffer.shift(); | ||
assert.strictEqual(x, expected.shift()); | ||
} | ||
sink.subscribe({ | ||
@@ -434,3 +451,134 @@ next(x) { | ||
it('should report errors from main() in the console', function (done) { | ||
it('should forbid cross-driver synchronous races (#592)', function(done) { | ||
this.timeout(4000); | ||
function child(sources: any, num: number) { | ||
const vdom$ = sources.HTTP | ||
// .select('cat') | ||
// .flatten() | ||
.map((res: any) => res.body.name) | ||
.map((name: string) => 'My name is ' + name); | ||
const request$ = num === 1 | ||
? xs.of({ | ||
category: 'cat', | ||
url: 'http://jsonplaceholder.typicode.com/users/1', | ||
}) | ||
: xs.never(); | ||
return { | ||
HTTP: request$, | ||
DOM: vdom$, | ||
}; | ||
} | ||
function mainHTTPThenDOM(sources: any) { | ||
const sinks$ = xs.periodic(100).take(6).map(i => { | ||
if (i % 2 === 1) { | ||
return child(sources, i); | ||
} else { | ||
return { | ||
HTTP: xs.empty(), | ||
DOM: xs.of(''), | ||
}; | ||
} | ||
}); | ||
// order of sinks is important to reproduce the bug | ||
return { | ||
HTTP: sinks$.map(sinks => sinks.HTTP).flatten(), | ||
DOM: sinks$.map(sinks => sinks.DOM).flatten(), | ||
}; | ||
} | ||
function mainDOMThenHTTP(sources: any) { | ||
const sinks$ = xs.periodic(100).take(6).map(i => { | ||
if (i % 2 === 1) { | ||
return child(sources, i); | ||
} else { | ||
return { | ||
HTTP: xs.empty(), | ||
DOM: xs.of(''), | ||
}; | ||
} | ||
}); | ||
// order of sinks is important to reproduce the bug | ||
return { | ||
DOM: sinks$.map(sinks => sinks.DOM).flatten(), | ||
HTTP: sinks$.map(sinks => sinks.HTTP).flatten(), | ||
}; | ||
} | ||
let requestsSent = 0; | ||
const expectedDOMSinks = [ | ||
/* HTTP then DOM: */ '', | ||
'My name is Louis', | ||
'', | ||
'', | ||
/* DOM then HTTP: */ '', | ||
'My name is Louis', | ||
'', | ||
'', | ||
]; | ||
function domDriver(sink: Stream<string>) { | ||
sink.addListener({ | ||
next: s => { | ||
assert.strictEqual(s, expectedDOMSinks.shift()); | ||
}, | ||
error: (err: any) => {}, | ||
}); | ||
} | ||
function httpDriver(sink: Stream<any>) { | ||
let isBufferOpen = true; | ||
const buffer: Array<any> = []; | ||
const earlySource = xs.create({ | ||
start(listener: any) { | ||
while (buffer.length > 0) { | ||
listener.next(buffer.shift()); | ||
} | ||
isBufferOpen = false; | ||
}, | ||
stop() {}, | ||
}); | ||
const source = sink.map(req => ({body: {name: 'Louis'}})); | ||
source.addListener({ | ||
next: x => { | ||
if (isBufferOpen) { | ||
buffer.push(x); | ||
} | ||
}, | ||
error: (err: any) => {}, | ||
}); | ||
return xs.merge(earlySource, source).debug(x => { | ||
requestsSent += 1; | ||
}); | ||
} | ||
// HTTP then DOM: | ||
const dispose = run(mainHTTPThenDOM, { | ||
HTTP: httpDriver, | ||
DOM: domDriver, | ||
}); | ||
setTimeout(() => { | ||
assert.strictEqual(expectedDOMSinks.length, 4); | ||
assert.strictEqual(requestsSent, 1); | ||
dispose(); | ||
// DOM then HTTP: | ||
run(mainDOMThenHTTP, { | ||
HTTP: httpDriver, | ||
DOM: domDriver, | ||
}); | ||
setTimeout(() => { | ||
assert.strictEqual(expectedDOMSinks.length, 0); | ||
assert.strictEqual(requestsSent, 2); | ||
done(); | ||
}, 1000); | ||
}, 1000); | ||
}); | ||
it('should report errors from main() in the console', function(done) { | ||
let sandbox = sinon.sandbox.create(); | ||
@@ -463,3 +611,6 @@ sandbox.stub(console, 'error'); | ||
sinon.assert.calledOnce(console.error as any); | ||
sinon.assert.calledWithExactly(console.error as any, sinon.match('malfunction')); | ||
sinon.assert.calledWithExactly( | ||
console.error as any, | ||
sinon.match('malfunction'), | ||
); | ||
@@ -466,0 +617,0 @@ // Should be false because the error was already reported in the console. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59728
21
1363
3
2
1