/** The MIT License (MIT) Copyright (c) 2015 Simon Friis Vindum Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); // AMD. Register as an anonymous module. } else if (typeof exports === 'object') { module.exports = factory(); // NodeJS } else { // Browser globals (root is window) root.flyd = factory(); } }(this, function () { 'use strict'; function isFunction(obj) { return !!(obj && obj.constructor && obj.call && obj.apply); } // Globals var toUpdate = []; var inStream; function map(f, s) { return stream([s], function(self) { self(f(s())); }); } function boundMap(f) { return map(f, this); } var scan = curryN(3, function(f, acc, s) { var ns = stream([s], function() { return (acc = f(acc, s())); }); if (!ns.hasVal) ns(acc); return ns; }); var merge = curryN(2, function(s1, s2) { var s = immediate(stream([s1, s2], function(n, changed) { return changed[0] ? changed[0]() : s1.hasVal ? s1() : s2(); })); endsOn(stream([s1.end, s2.end], function(self, changed) { return true; }), s); return s; }); function ap(s2) { var s1 = this; return stream([s1, s2], function() { return s1()(s2()); }); } function initialDepsNotMet(stream) { stream.depsMet = stream.deps.every(function(s) { return s.hasVal; }); return !stream.depsMet; } function updateStream(s) { if ((s.depsMet !== true && initialDepsNotMet(s)) || (s.end !== undefined && s.end.val === true)) return; inStream = s; var returnVal = s.fn(s, s.depsChanged); if (returnVal !== undefined) { s(returnVal); } inStream = undefined; if (s.depsChanged !== undefined) { while (s.depsChanged.length > 0) s.depsChanged.shift(); } s.shouldUpdate = false; } var order = []; var orderNextIdx = -1; function findDeps(s) { var i, listeners = s.listeners; if (s.queued === false) { s.queued = true; for (i = 0; i < listeners.length; ++i) { findDeps(listeners[i]); } order[++orderNextIdx] = s; } } function updateDeps(s) { var i, o, list, listeners = s.listeners; for (i = 0; i < listeners.length; ++i) { list = listeners[i]; if (list.end === s) { endStream(list); } else { if (list.depsChanged !== undefined) list.depsChanged.push(s); list.shouldUpdate = true; findDeps(list); } } for (; orderNextIdx >= 0; --orderNextIdx) { o = order[orderNextIdx]; if (o.shouldUpdate === true) updateStream(o); o.queued = false; } } function flushUpdate() { while (toUpdate.length > 0) updateDeps(toUpdate.shift()); } function isStream(stream) { return isFunction(stream) && 'hasVal' in stream; } function streamToString() { return 'stream(' + this.val + ')'; } function createStream() { function s(n) { var i, list; if (arguments.length === 0) { return s.val; } else { if (n !== undefined && n !== null && isFunction(n.then)) { n.then(s); return; } s.val = n; s.hasVal = true; if (inStream === undefined) { updateDeps(s); if (toUpdate.length > 0) flushUpdate(); } else if (inStream === s) { for (i = 0; i < s.listeners.length; ++i) { list = s.listeners[i]; if (list.end !== s) { if (list.depsChanged !== undefined) { list.depsChanged.push(s); } list.shouldUpdate = true; } else { endStream(list); } } } else { toUpdate.push(s); } return s; } } s.hasVal = false; s.val = undefined; s.listeners = []; s.queued = false; s.end = undefined; s.map = boundMap; s.ap = ap; s.of = stream; s.toString = streamToString; return s; } function createDependentStream(deps, fn) { var i, s = createStream(); s.fn = fn; s.deps = deps; s.depsMet = false; s.depsChanged = fn.length > 1 ? [] : undefined; s.shouldUpdate = false; for (i = 0; i < deps.length; ++i) { deps[i].listeners.push(s); } return s; } function immediate(s) { if (s.depsMet === false) { s.depsMet = true; updateStream(s); if (toUpdate.length > 0) flushUpdate(); } return s; } function removeListener(s, listeners) { var idx = listeners.indexOf(s); listeners[idx] = listeners[listeners.length - 1]; listeners.length--; } function detachDeps(s) { for (var i = 0; i < s.deps.length; ++i) { removeListener(s, s.deps[i].listeners); } s.deps.length = 0; } function endStream(s) { if (s.deps !== undefined) detachDeps(s); if (s.end !== undefined) detachDeps(s.end); } function endsOn(endS, s) { detachDeps(s.end); endS.listeners.push(s.end); s.end.deps.push(endS); return s; } function stream(arg, fn) { var i, s, deps, depEndStreams; var endStream = createDependentStream([], function() { return true; }); if (arguments.length > 1) { deps = []; depEndStreams = []; for (i = 0; i < arg.length; ++i) { if (arg[i] !== undefined) { deps.push(arg[i]); if (arg[i].end !== undefined) depEndStreams.push(arg[i].end); } } s = createDependentStream(deps, fn); s.end = endStream; endStream.listeners.push(s); endsOn(createDependentStream(depEndStreams, function() { return true; }, true), s); updateStream(s); if (toUpdate.length > 0) flushUpdate(); } else { s = createStream(); s.end = endStream; endStream.listeners.push(s); if (arguments.length === 1) s(arg); } return s; } var transduce = curryN(2, function(xform, source) { xform = xform(new StreamTransformer()); return stream([source], function(self) { var res = xform['@@transducer/step'](undefined, source()); if (res && res['@@transducer/reduced'] === true) { self.end(true); return res['@@transducer/value']; } else { return res; } }); }); function StreamTransformer() { } StreamTransformer.prototype['@@transducer/init'] = function() { }; StreamTransformer.prototype['@@transducer/result'] = function() { }; StreamTransformer.prototype['@@transducer/step'] = function(s, v) { return v; }; // Own curry implementation snatched from Ramda // Figure out something nicer later on var _ = {placeholder: true}; // Detect both own and Ramda placeholder function isPlaceholder(p) { return p === _ || (p && p.ramda === 'placeholder'); } function toArray(arg) { var arr = []; for (var i = 0; i < arg.length; ++i) { arr[i] = arg[i]; } return arr; } // Modified versions of arity and curryN from Ramda function ofArity(n, fn) { if (arguments.length === 1) { return ofArity.bind(undefined, n); } switch (n) { case 0: return function () { return fn.apply(this, arguments); }; case 1: return function (a0) { void a0; return fn.apply(this, arguments); }; case 2: return function (a0, a1) { void a1; return fn.apply(this, arguments); }; case 3: return function (a0, a1, a2) { void a2; return fn.apply(this, arguments); }; case 4: return function (a0, a1, a2, a3) { void a3; return fn.apply(this, arguments); }; case 5: return function (a0, a1, a2, a3, a4) { void a4; return fn.apply(this, arguments); }; case 6: return function (a0, a1, a2, a3, a4, a5) { void a5; return fn.apply(this, arguments); }; case 7: return function (a0, a1, a2, a3, a4, a5, a6) { void a6; return fn.apply(this, arguments); }; case 8: return function (a0, a1, a2, a3, a4, a5, a6, a7) { void a7; return fn.apply(this, arguments); }; case 9: return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) { void a8; return fn.apply(this, arguments); }; case 10: return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) { void a9; return fn.apply(this, arguments); }; default: throw new Error('First argument to arity must be a non-negative integer no greater than ten'); } } function curryN(length, fn) { return ofArity(length, function () { var n = arguments.length; var shortfall = length - n; var idx = n; while (--idx >= 0) { if (isPlaceholder(arguments[idx])) { shortfall += 1; } } if (shortfall <= 0) { return fn.apply(this, arguments); } else { var initialArgs = toArray(arguments); return curryN(shortfall, function () { var currentArgs = toArray(arguments); var combinedArgs = []; var idx = -1; while (++idx < n) { var val = initialArgs[idx]; combinedArgs[idx] = isPlaceholder(val) ? currentArgs.shift() : val; } return fn.apply(this, combinedArgs.concat(currentArgs)); }); } }); } return { stream: stream, isStream: isStream, transduce: transduce, merge: merge, reduce: scan, // Legacy scan: scan, endsOn: endsOn, map: curryN(2, map), curryN: curryN, _: _, immediate: immediate, }; }));