cycle-restart
Version:
Restart a Cycle.js application and preserve state.
380 lines (295 loc) • 10.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
exports.default = restartable;
var _xstream = require('xstream');
var _xstream2 = _interopRequireDefault(_xstream);
require('get-own-property-symbols');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function pausable(pause$) {
return function (stream) {
return pause$.map(function (paused) {
return stream.filter(function () {
return paused;
});
}).flatten();
};
}
function disposeAllStreams(streams) {
keys(streams).forEach(function (key) {
var value = streams[key];
delete streams[key];
value && value.dispose && value.dispose();
});
}
function makeDispose(_ref, originalDispose, context) {
var streams = _ref.streams;
return function dispose() {
originalDispose.bind(context)();
disposeAllStreams(streams);
};
}
function record(_ref2, streamToRecord, identifier) {
var streams = _ref2.streams,
addLogEntry = _ref2.addLogEntry,
pause$ = _ref2.pause$,
Time = _ref2.Time;
var stream = streamToRecord.compose(pausable(pause$.startWith(true))).map(function (event) {
if (typeof event.subscribe === 'function') {
var eventStream = event.debug(function (innerEvent) {
var response$ = _xstream2.default.of(innerEvent);
response$.request = event.request;
addLogEntry({ event: response$, time: Time._time(), identifier: identifier, stream: stream });
});
eventStream.request = event.request;
return eventStream;
}
addLogEntry({ event: event, time: Time._time(), identifier: identifier, stream: stream }); // TODO - stream is undefined and unused
return event;
});
streams[identifier] = stream;
return stream.debug(function () {});
}
function wrapSourceFunction(_ref3, name, f, context) {
var streams = _ref3.streams,
addLogEntry = _ref3.addLogEntry,
pause$ = _ref3.pause$,
Time = _ref3.Time;
var scope = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
return function newSource() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var newScope = scope.concat(args);
var returnValue = f.bind.apply(f, [context].concat(args))();
/* Not all names will be strings, some may be symbols. */
if (typeof name === 'string' && name.indexOf('isolate') !== -1 || (typeof returnValue === 'undefined' ? 'undefined' : _typeof(returnValue)) !== 'object') {
return returnValue;
}
if (typeof returnValue.addListener !== 'function') {
return wrapSource({ streams: streams, addLogEntry: addLogEntry, pause$: pause$, Time: Time }, returnValue, newScope);
}
var identifier = newScope.join('/');
return record({ streams: streams, addLogEntry: addLogEntry, pause$: pause$, Time: Time }, returnValue, identifier);
};
}
function keys(obj) {
var _keys = [];
for (var prop in obj) {
_keys.push(prop);
}
/* Expose all symbol properties as keys. */
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = Object.getOwnPropertySymbols(obj)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _prop = _step.value;
_keys.push(_prop);
}
/* Expose all prototype sybmol properties as keys. */
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = Object.getOwnPropertySymbols(Object.getPrototypeOf(obj) || {})[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var _prop2 = _step2.value;
_keys.push(_prop2);
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return _keys;
}
function wrapSource(_ref4, source) {
var streams = _ref4.streams,
addLogEntry = _ref4.addLogEntry,
pause$ = _ref4.pause$,
Time = _ref4.Time;
var scope = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
var returnValue = {};
keys(source).forEach(function (key) {
var value = source[key];
if (key === 'dispose') {
returnValue[key] = makeDispose({ streams: streams }, value, source);
} else if (typeof value === 'function') {
returnValue[key] = wrapSourceFunction({ streams: streams, addLogEntry: addLogEntry, pause$: pause$, Time: Time }, key, value, returnValue, scope);
} else {
returnValue[key] = value;
}
});
return returnValue;
}
var subscribe = function subscribe(f) {
return {
next: f,
error: function error(err) {
return console.error(err);
},
complete: function complete() {}
};
};
function createLog$() {
var logEntry$ = _xstream2.default.create();
var logEntryReducer$ = logEntry$.map(function (entry) {
return function (log) {
return log.concat(entry);
};
});
var logReplace$ = _xstream2.default.create();
var logReplaceReducer$ = logReplace$.map(function (newLog) {
return function () {
return newLog;
};
});
var logReducer$ = _xstream2.default.merge(logEntryReducer$, logReplaceReducer$);
var log$ = logReducer$.fold(function (log, reducer) {
return reducer(log);
}, []);
function addLogEntry(entry) {
logEntry$.shamefullySendNext(entry);
}
function replaceLog(newLog) {
logReplace$.shamefullySendNext(newLog);
}
return { log$: log$, addLogEntry: addLogEntry, replaceLog: replaceLog };
}
function restartable(driver) {
var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _createLog$ = createLog$(),
log$ = _createLog$.log$,
addLogEntry = _createLog$.addLogEntry,
replaceLog = _createLog$.replaceLog;
// TODO - dispose log subscription
log$.addListener(subscribe(function () {}));
var streams = {};
var pauseSinksWhileReplaying = opts.pauseSinksWhileReplaying === undefined ? true : opts.pauseSinksWhileReplaying;
var pause$ = (opts.pause$ || _xstream2.default.empty()).startWith(true);
var replayOnlyLastSink = opts.replayOnlyLastSink || false;
var replaying = void 0;
var isReplaying = function isReplaying() {
return replaying;
};
var setReplaying = function setReplaying(newReplaying) {
return replaying = newReplaying;
};
var finishedReplay$ = _xstream2.default.create();
function restartableDriver(sink$, Time) {
var filteredSink$ = _xstream2.default.create();
var lastSinkEvent$ = _xstream2.default.createWithMemory();
if (sink$) {
if (isReplaying() && replayOnlyLastSink) {
lastSinkEvent$.map(function (lastEvent) {
return finishedReplay$.mapTo(lastEvent);
}).flatten().take(1).addListener(subscribe(function (event) {
filteredSink$.shamefullySendNext(event);
}));
}
if (pauseSinksWhileReplaying) {
sink$.compose(pausable(pause$)).filter(function () {
return !isReplaying();
}).addListener({
next: function next(ev) {
return filteredSink$.shamefullySendNext(ev);
},
error: function error(err) {
return console.error(err);
},
complete: function complete() {}
});
} else {
sink$.compose(pausable(pause$)).addListener(subscribe(function (ev) {
return filteredSink$.shamefullySendNext(ev);
}));
}
}
if (sink$) {
// force sink$ to a normal stream with .filter() to prevent attempting
// imitation of a MemoryStream which can't be imitated
lastSinkEvent$.imitate(sink$.filter(function () {
return true;
}));
}
var source = driver(filteredSink$);
var returnValue = void 0;
if (source === undefined || source === null) {
return source;
} else if (typeof source.addListener === 'function') {
returnValue = record({ streams: streams, addLogEntry: addLogEntry, pause$: pause$, Time: Time }, source, ':root');
} else {
returnValue = wrapSource({ streams: streams, addLogEntry: addLogEntry, pause$: pause$, Time: Time }, source);
}
var oldReturnValueDispose = source.dispose;
returnValue.dispose = function () {
oldReturnValueDispose && oldReturnValueDispose.bind(returnValue)();
sink$ && sink$.dispose && sink$.dispose();
disposeAllStreams(streams);
};
returnValue.log$ = log$;
return returnValue;
}
function replayable(driver) {
driver.onPreReplay = function () {
setReplaying(true);
};
driver.replayLog = function (schedule, newLog$) {
var timeToResetTo = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var logToReplaceWith = void 0;
newLog$.take(1).addListener({
next: function next(newLog) {
logToReplaceWith = newLog;
function scheduleEvent(historicEvent) {
var stream = streams[historicEvent.identifier];
if (stream) {
schedule.next(stream, historicEvent.time, historicEvent.event);
} else {
console.error('Missing replay stream ', historicEvent.identifier);
}
}
newLog.filter(function (event) {
return timeToResetTo === null || event.time <= timeToResetTo;
}).forEach(scheduleEvent);
},
error: function error(err) {
console.error(err);
},
complete: function complete() {
replaceLog(logToReplaceWith);
}
});
};
driver.onPostReplay = function () {
setReplaying(false);
finishedReplay$.shamefullySendNext();
};
return driver;
}
return replayable(restartableDriver);
}