1013 lines
39 KiB
JavaScript
1013 lines
39 KiB
JavaScript
"use strict";
|
|
var __assign = (this && this.__assign) || function () {
|
|
__assign = Object.assign || function(t) {
|
|
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
s = arguments[i];
|
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
t[p] = s[p];
|
|
}
|
|
return t;
|
|
};
|
|
return __assign.apply(this, arguments);
|
|
};
|
|
var __values = (this && this.__values) || function (o) {
|
|
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
|
|
if (m) return m.call(o);
|
|
return {
|
|
next: function () {
|
|
if (o && i >= o.length) o = void 0;
|
|
return { value: o && o[i++], done: !o };
|
|
}
|
|
};
|
|
};
|
|
var __read = (this && this.__read) || function (o, n) {
|
|
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
if (!m) return o;
|
|
var i = m.call(o), r, ar = [], e;
|
|
try {
|
|
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
}
|
|
catch (error) { e = { error: error }; }
|
|
finally {
|
|
try {
|
|
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
}
|
|
finally { if (e) throw e.error; }
|
|
}
|
|
return ar;
|
|
};
|
|
var __spread = (this && this.__spread) || function () {
|
|
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
|
|
return ar;
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
var types_1 = require("./types");
|
|
var State_1 = require("./State");
|
|
var actionTypes = require("./actionTypes");
|
|
var actions_1 = require("./actions");
|
|
var environment_1 = require("./environment");
|
|
var utils_1 = require("./utils");
|
|
var scheduler_1 = require("./scheduler");
|
|
var Actor_1 = require("./Actor");
|
|
var DEFAULT_SPAWN_OPTIONS = { sync: false, autoForward: false };
|
|
/**
|
|
* Maintains a stack of the current service in scope.
|
|
* This is used to provide the correct service to spawn().
|
|
*
|
|
* @private
|
|
*/
|
|
var withServiceScope = (function () {
|
|
var serviceStack = [];
|
|
return function (service, fn) {
|
|
service && serviceStack.push(service);
|
|
var result = fn(service || serviceStack[serviceStack.length - 1]);
|
|
service && serviceStack.pop();
|
|
return result;
|
|
};
|
|
})();
|
|
var Interpreter = /** @class */ (function () {
|
|
/**
|
|
* Creates a new Interpreter instance (i.e., service) for the given machine with the provided options, if any.
|
|
*
|
|
* @param machine The machine to be interpreted
|
|
* @param options Interpreter options
|
|
*/
|
|
function Interpreter(machine, options) {
|
|
var _this = this;
|
|
if (options === void 0) { options = Interpreter.defaultOptions; }
|
|
this.machine = machine;
|
|
this.scheduler = new scheduler_1.Scheduler();
|
|
this.delayedEventsMap = {};
|
|
this.listeners = new Set();
|
|
this.contextListeners = new Set();
|
|
this.stopListeners = new Set();
|
|
this.doneListeners = new Set();
|
|
this.eventListeners = new Set();
|
|
this.sendListeners = new Set();
|
|
/**
|
|
* Whether the service is started.
|
|
*/
|
|
this.initialized = false;
|
|
this.children = new Map();
|
|
this.forwardTo = new Set();
|
|
/**
|
|
* Alias for Interpreter.prototype.start
|
|
*/
|
|
this.init = this.start;
|
|
/**
|
|
* Sends an event to the running interpreter to trigger a transition.
|
|
*
|
|
* An array of events (batched) can be sent as well, which will send all
|
|
* batched events to the running interpreter. The listeners will be
|
|
* notified only **once** when all events are processed.
|
|
*
|
|
* @param event The event(s) to send
|
|
*/
|
|
this.send = function (event, payload) {
|
|
if (utils_1.isArray(event)) {
|
|
_this.batch(event);
|
|
return _this.state;
|
|
}
|
|
var eventObject = actions_1.toEventObject(event, payload);
|
|
if (!_this.initialized && _this.options.deferEvents) {
|
|
// tslint:disable-next-line:no-console
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(false, "Event \"" + eventObject.type + "\" was sent to uninitialized service \"" + _this.machine.id + "\" and is deferred. Make sure .start() is called for this service.\nEvent: " + JSON.stringify(event));
|
|
}
|
|
}
|
|
else if (!_this.initialized) {
|
|
throw new Error("Event \"" + eventObject.type + "\" was sent to uninitialized service \"" + _this.machine.id + "\". Make sure .start() is called for this service, or set { deferEvents: true } in the service options.\nEvent: " + JSON.stringify(eventObject));
|
|
}
|
|
_this.scheduler.schedule(function () {
|
|
var nextState = _this.nextState(eventObject);
|
|
_this.update(nextState, eventObject);
|
|
// Forward copy of event to child interpreters
|
|
_this.forward(eventObject);
|
|
});
|
|
return _this.state; // TODO: deprecate (should return void)
|
|
// tslint:disable-next-line:semicolon
|
|
};
|
|
this.sendTo = function (event, to) {
|
|
var isParent = to === types_1.SpecialTargets.Parent;
|
|
var target = isParent
|
|
? _this.parent
|
|
: Actor_1.isActor(to)
|
|
? to
|
|
: _this.children.get(to);
|
|
if (!target) {
|
|
if (!isParent) {
|
|
throw new Error("Unable to send event to child '" + to + "' from service '" + _this.id + "'.");
|
|
}
|
|
// tslint:disable-next-line:no-console
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(false, "Service '" + _this.id + "' has no parent: unable to send event " + event.type);
|
|
}
|
|
return;
|
|
}
|
|
target.send(event);
|
|
};
|
|
var resolvedOptions = __assign({}, Interpreter.defaultOptions, options);
|
|
var clock = resolvedOptions.clock, logger = resolvedOptions.logger, parent = resolvedOptions.parent, id = resolvedOptions.id;
|
|
var resolvedId = id !== undefined ? id : machine.id;
|
|
this.id = resolvedId;
|
|
this.logger = logger;
|
|
this.clock = clock;
|
|
this.parent = parent;
|
|
this.options = resolvedOptions;
|
|
this.scheduler = new scheduler_1.Scheduler({
|
|
deferEvents: this.options.deferEvents
|
|
});
|
|
this.initialState = this.state = withServiceScope(this, function () { return _this.machine.initialState; });
|
|
}
|
|
/**
|
|
* Executes the actions of the given state, with that state's `context` and `event`.
|
|
*
|
|
* @param state The state whose actions will be executed
|
|
* @param actionsConfig The action implementations to use
|
|
*/
|
|
Interpreter.prototype.execute = function (state, actionsConfig) {
|
|
var e_1, _a;
|
|
try {
|
|
for (var _b = __values(state.actions), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
var action = _c.value;
|
|
this.exec(action, state.context, state.event, actionsConfig);
|
|
}
|
|
}
|
|
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
finally {
|
|
try {
|
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
}
|
|
finally { if (e_1) throw e_1.error; }
|
|
}
|
|
};
|
|
Interpreter.prototype.update = function (state, event) {
|
|
var e_2, _a, e_3, _b, e_4, _c, e_5, _d;
|
|
// Update state
|
|
this.state = state;
|
|
// Execute actions
|
|
if (this.options.execute) {
|
|
this.execute(this.state);
|
|
}
|
|
// Dev tools
|
|
if (this.devTools) {
|
|
this.devTools.send(event, state);
|
|
}
|
|
// Execute listeners
|
|
if (state.event) {
|
|
try {
|
|
for (var _e = __values(this.eventListeners), _f = _e.next(); !_f.done; _f = _e.next()) {
|
|
var listener = _f.value;
|
|
listener(state.event);
|
|
}
|
|
}
|
|
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
finally {
|
|
try {
|
|
if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
|
|
}
|
|
finally { if (e_2) throw e_2.error; }
|
|
}
|
|
}
|
|
try {
|
|
for (var _g = __values(this.listeners), _h = _g.next(); !_h.done; _h = _g.next()) {
|
|
var listener = _h.value;
|
|
listener(state, state.event);
|
|
}
|
|
}
|
|
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
finally {
|
|
try {
|
|
if (_h && !_h.done && (_b = _g.return)) _b.call(_g);
|
|
}
|
|
finally { if (e_3) throw e_3.error; }
|
|
}
|
|
try {
|
|
for (var _j = __values(this.contextListeners), _k = _j.next(); !_k.done; _k = _j.next()) {
|
|
var contextListener = _k.value;
|
|
contextListener(this.state.context, this.state.history ? this.state.history.context : undefined);
|
|
}
|
|
}
|
|
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
finally {
|
|
try {
|
|
if (_k && !_k.done && (_c = _j.return)) _c.call(_j);
|
|
}
|
|
finally { if (e_4) throw e_4.error; }
|
|
}
|
|
if (this.state.tree && this.state.tree.done) {
|
|
// get donedata
|
|
var doneData = this.state.tree.getDoneData(this.state.context, actions_1.toEventObject(event));
|
|
try {
|
|
for (var _l = __values(this.doneListeners), _m = _l.next(); !_m.done; _m = _l.next()) {
|
|
var listener = _m.value;
|
|
listener(actions_1.doneInvoke(this.id, doneData));
|
|
}
|
|
}
|
|
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
finally {
|
|
try {
|
|
if (_m && !_m.done && (_d = _l.return)) _d.call(_l);
|
|
}
|
|
finally { if (e_5) throw e_5.error; }
|
|
}
|
|
this.stop();
|
|
}
|
|
};
|
|
/*
|
|
* Adds a listener that is notified whenever a state transition happens. The listener is called with
|
|
* the next state and the event object that caused the state transition.
|
|
*
|
|
* @param listener The state listener
|
|
*/
|
|
Interpreter.prototype.onTransition = function (listener) {
|
|
this.listeners.add(listener);
|
|
return this;
|
|
};
|
|
Interpreter.prototype.subscribe = function (nextListener,
|
|
// @ts-ignore
|
|
errorListener, completeListener) {
|
|
var _this = this;
|
|
if (nextListener) {
|
|
this.onTransition(nextListener);
|
|
}
|
|
if (completeListener) {
|
|
this.onDone(completeListener);
|
|
}
|
|
return {
|
|
unsubscribe: function () {
|
|
nextListener && _this.listeners.delete(nextListener);
|
|
completeListener && _this.doneListeners.delete(completeListener);
|
|
}
|
|
};
|
|
};
|
|
/**
|
|
* Adds an event listener that is notified whenever an event is sent to the running interpreter.
|
|
* @param listener The event listener
|
|
*/
|
|
Interpreter.prototype.onEvent = function (listener) {
|
|
this.eventListeners.add(listener);
|
|
return this;
|
|
};
|
|
/**
|
|
* Adds an event listener that is notified whenever a `send` event occurs.
|
|
* @param listener The event listener
|
|
*/
|
|
Interpreter.prototype.onSend = function (listener) {
|
|
this.sendListeners.add(listener);
|
|
return this;
|
|
};
|
|
/**
|
|
* Adds a context listener that is notified whenever the state context changes.
|
|
* @param listener The context listener
|
|
*/
|
|
Interpreter.prototype.onChange = function (listener) {
|
|
this.contextListeners.add(listener);
|
|
return this;
|
|
};
|
|
/**
|
|
* Adds a listener that is notified when the machine is stopped.
|
|
* @param listener The listener
|
|
*/
|
|
Interpreter.prototype.onStop = function (listener) {
|
|
this.stopListeners.add(listener);
|
|
return this;
|
|
};
|
|
/**
|
|
* Adds a state listener that is notified when the statechart has reached its final state.
|
|
* @param listener The state listener
|
|
*/
|
|
Interpreter.prototype.onDone = function (listener) {
|
|
this.doneListeners.add(listener);
|
|
return this;
|
|
};
|
|
/**
|
|
* Removes a listener.
|
|
* @param listener The listener to remove
|
|
*/
|
|
Interpreter.prototype.off = function (listener) {
|
|
this.listeners.delete(listener);
|
|
this.eventListeners.delete(listener);
|
|
this.sendListeners.delete(listener);
|
|
this.stopListeners.delete(listener);
|
|
this.doneListeners.delete(listener);
|
|
this.contextListeners.delete(listener);
|
|
return this;
|
|
};
|
|
/**
|
|
* Starts the interpreter from the given state, or the initial state.
|
|
* @param initialState The state to start the statechart from
|
|
*/
|
|
Interpreter.prototype.start = function (initialState) {
|
|
var _this = this;
|
|
if (this.initialized) {
|
|
// Do not restart the service if it is already started
|
|
return this;
|
|
}
|
|
this.initialized = true;
|
|
var resolvedState = withServiceScope(this, function () {
|
|
return initialState === undefined
|
|
? _this.machine.initialState
|
|
: initialState instanceof State_1.State
|
|
? _this.machine.resolveState(initialState)
|
|
: _this.machine.resolveState(State_1.State.from(initialState));
|
|
});
|
|
if (this.options.devTools) {
|
|
this.attachDev();
|
|
}
|
|
this.scheduler.initialize(function () {
|
|
_this.update(resolvedState, { type: actionTypes.init });
|
|
});
|
|
return this;
|
|
};
|
|
/**
|
|
* Stops the interpreter and unsubscribe all listeners.
|
|
*
|
|
* This will also notify the `onStop` listeners.
|
|
*/
|
|
Interpreter.prototype.stop = function () {
|
|
var e_6, _a, e_7, _b, e_8, _c, e_9, _d, e_10, _e;
|
|
try {
|
|
for (var _f = __values(this.listeners), _g = _f.next(); !_g.done; _g = _f.next()) {
|
|
var listener = _g.value;
|
|
this.listeners.delete(listener);
|
|
}
|
|
}
|
|
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
finally {
|
|
try {
|
|
if (_g && !_g.done && (_a = _f.return)) _a.call(_f);
|
|
}
|
|
finally { if (e_6) throw e_6.error; }
|
|
}
|
|
try {
|
|
for (var _h = __values(this.stopListeners), _j = _h.next(); !_j.done; _j = _h.next()) {
|
|
var listener = _j.value;
|
|
// call listener, then remove
|
|
listener();
|
|
this.stopListeners.delete(listener);
|
|
}
|
|
}
|
|
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
finally {
|
|
try {
|
|
if (_j && !_j.done && (_b = _h.return)) _b.call(_h);
|
|
}
|
|
finally { if (e_7) throw e_7.error; }
|
|
}
|
|
try {
|
|
for (var _k = __values(this.contextListeners), _l = _k.next(); !_l.done; _l = _k.next()) {
|
|
var listener = _l.value;
|
|
this.contextListeners.delete(listener);
|
|
}
|
|
}
|
|
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
finally {
|
|
try {
|
|
if (_l && !_l.done && (_c = _k.return)) _c.call(_k);
|
|
}
|
|
finally { if (e_8) throw e_8.error; }
|
|
}
|
|
try {
|
|
for (var _m = __values(this.doneListeners), _o = _m.next(); !_o.done; _o = _m.next()) {
|
|
var listener = _o.value;
|
|
this.doneListeners.delete(listener);
|
|
}
|
|
}
|
|
catch (e_9_1) { e_9 = { error: e_9_1 }; }
|
|
finally {
|
|
try {
|
|
if (_o && !_o.done && (_d = _m.return)) _d.call(_m);
|
|
}
|
|
finally { if (e_9) throw e_9.error; }
|
|
}
|
|
// Stop all children
|
|
this.children.forEach(function (child) {
|
|
if (utils_1.isFunction(child.stop)) {
|
|
child.stop();
|
|
}
|
|
});
|
|
try {
|
|
// Cancel all delayed events
|
|
for (var _p = __values(utils_1.keys(this.delayedEventsMap)), _q = _p.next(); !_q.done; _q = _p.next()) {
|
|
var key = _q.value;
|
|
this.clock.clearTimeout(this.delayedEventsMap[key]);
|
|
}
|
|
}
|
|
catch (e_10_1) { e_10 = { error: e_10_1 }; }
|
|
finally {
|
|
try {
|
|
if (_q && !_q.done && (_e = _p.return)) _e.call(_p);
|
|
}
|
|
finally { if (e_10) throw e_10.error; }
|
|
}
|
|
this.initialized = false;
|
|
return this;
|
|
};
|
|
Interpreter.prototype.batch = function (events) {
|
|
var _this = this;
|
|
if (!this.initialized && this.options.deferEvents) {
|
|
// tslint:disable-next-line:no-console
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(false, events.length + " event(s) were sent to uninitialized service \"" + this.machine.id + "\" and are deferred. Make sure .start() is called for this service.\nEvent: " + JSON.stringify(event));
|
|
}
|
|
}
|
|
else if (!this.initialized) {
|
|
throw new Error(
|
|
// tslint:disable-next-line:max-line-length
|
|
events.length + " event(s) were sent to uninitialized service \"" + this.machine.id + "\". Make sure .start() is called for this service, or set { deferEvents: true } in the service options.");
|
|
}
|
|
this.scheduler.schedule(function () {
|
|
var e_11, _a, _b;
|
|
var nextState = _this.state;
|
|
try {
|
|
for (var events_1 = __values(events), events_1_1 = events_1.next(); !events_1_1.done; events_1_1 = events_1.next()) {
|
|
var event_1 = events_1_1.value;
|
|
var changed = nextState.changed;
|
|
var eventObject = actions_1.toEventObject(event_1);
|
|
var actions = nextState.actions.map(function (a) {
|
|
return utils_1.bindActionToState(a, nextState);
|
|
});
|
|
nextState = _this.machine.transition(nextState, eventObject);
|
|
(_b = nextState.actions).unshift.apply(_b, __spread(actions));
|
|
nextState.changed = nextState.changed || !!changed;
|
|
_this.forward(eventObject);
|
|
}
|
|
}
|
|
catch (e_11_1) { e_11 = { error: e_11_1 }; }
|
|
finally {
|
|
try {
|
|
if (events_1_1 && !events_1_1.done && (_a = events_1.return)) _a.call(events_1);
|
|
}
|
|
finally { if (e_11) throw e_11.error; }
|
|
}
|
|
_this.update(nextState, actions_1.toEventObject(events[events.length - 1]));
|
|
});
|
|
};
|
|
/**
|
|
* Returns a send function bound to this interpreter instance.
|
|
*
|
|
* @param event The event to be sent by the sender.
|
|
*/
|
|
Interpreter.prototype.sender = function (event) {
|
|
return this.send.bind(this, event);
|
|
};
|
|
/**
|
|
* Returns the next state given the interpreter's current state and the event.
|
|
*
|
|
* This is a pure method that does _not_ update the interpreter's state.
|
|
*
|
|
* @param event The event to determine the next state
|
|
*/
|
|
Interpreter.prototype.nextState = function (event) {
|
|
var _this = this;
|
|
var eventObject = actions_1.toEventObject(event);
|
|
if (eventObject.type.indexOf(actionTypes.errorPlatform) === 0 &&
|
|
!this.state.nextEvents.some(function (nextEvent) { return nextEvent.indexOf(actionTypes.errorPlatform) === 0; })) {
|
|
throw eventObject.data;
|
|
}
|
|
var nextState = withServiceScope(this, function () {
|
|
return _this.machine.transition(_this.state, eventObject, _this.state.context);
|
|
});
|
|
return nextState;
|
|
};
|
|
Interpreter.prototype.forward = function (event) {
|
|
var e_12, _a;
|
|
try {
|
|
for (var _b = __values(this.forwardTo), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
var id = _c.value;
|
|
var child = this.children.get(id);
|
|
if (!child) {
|
|
throw new Error("Unable to forward event '" + event + "' from interpreter '" + this.id + "' to nonexistant child '" + id + "'.");
|
|
}
|
|
child.send(event);
|
|
}
|
|
}
|
|
catch (e_12_1) { e_12 = { error: e_12_1 }; }
|
|
finally {
|
|
try {
|
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
}
|
|
finally { if (e_12) throw e_12.error; }
|
|
}
|
|
};
|
|
Interpreter.prototype.defer = function (sendAction) {
|
|
var _this = this;
|
|
var delay = sendAction.delay;
|
|
if (utils_1.isString(delay)) {
|
|
if (!this.machine.options.delays ||
|
|
this.machine.options.delays[delay] === undefined) {
|
|
// tslint:disable-next-line:no-console
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(false,
|
|
// tslint:disable-next-line:max-line-length
|
|
"No delay reference for delay expression '" + delay + "' was found on machine '" + this.machine.id + "' on service '" + this.id + "'.");
|
|
}
|
|
// Do not send anything
|
|
return;
|
|
}
|
|
else {
|
|
var delayExpr = this.machine.options.delays[delay];
|
|
delay =
|
|
typeof delayExpr === 'number'
|
|
? delayExpr
|
|
: delayExpr(this.state.context, this.state.event);
|
|
}
|
|
}
|
|
this.delayedEventsMap[sendAction.id] = this.clock.setTimeout(function () {
|
|
if (sendAction.to) {
|
|
_this.sendTo(sendAction.event, sendAction.to);
|
|
}
|
|
else {
|
|
_this.send(sendAction.event);
|
|
}
|
|
}, delay || 0);
|
|
};
|
|
Interpreter.prototype.cancel = function (sendId) {
|
|
this.clock.clearTimeout(this.delayedEventsMap[sendId]);
|
|
delete this.delayedEventsMap[sendId];
|
|
};
|
|
Interpreter.prototype.exec = function (action, context, event, actionFunctionMap) {
|
|
var actionOrExec = actions_1.getActionFunction(action.type, actionFunctionMap) || action.exec;
|
|
var exec = utils_1.isFunction(actionOrExec)
|
|
? actionOrExec
|
|
: actionOrExec
|
|
? actionOrExec.exec
|
|
: action.exec;
|
|
if (exec) {
|
|
// @ts-ignore (TODO: fix for TypeDoc)
|
|
return exec(context, event, { action: action, state: this.state });
|
|
}
|
|
switch (action.type) {
|
|
case actionTypes.send:
|
|
var sendAction = action;
|
|
if (sendAction.delay) {
|
|
this.defer(sendAction);
|
|
return;
|
|
}
|
|
else {
|
|
if (sendAction.to) {
|
|
this.sendTo(sendAction.event, sendAction.to);
|
|
}
|
|
else {
|
|
this.send(sendAction.event);
|
|
}
|
|
}
|
|
break;
|
|
case actionTypes.cancel:
|
|
this.cancel(action.sendId);
|
|
break;
|
|
case actionTypes.start: {
|
|
var activity = action
|
|
.activity;
|
|
// If the activity will be stopped right after it's started
|
|
// (such as in transient states)
|
|
// don't bother starting the activity.
|
|
if (!this.state.activities[activity.type]) {
|
|
break;
|
|
}
|
|
// Invoked services
|
|
if (activity.type === types_1.ActionTypes.Invoke) {
|
|
var serviceCreator = this
|
|
.machine.options.services
|
|
? this.machine.options.services[activity.src]
|
|
: undefined;
|
|
var id = activity.id, data = activity.data;
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(!('forward' in activity),
|
|
// tslint:disable-next-line:max-line-length
|
|
"`forward` property is deprecated (found in invocation of '" + activity.src + "' in in machine '" + this.machine.id + "'). " +
|
|
"Please use `autoForward` instead.");
|
|
}
|
|
var autoForward = 'autoForward' in activity
|
|
? activity.autoForward
|
|
: !!activity.forward;
|
|
if (!serviceCreator) {
|
|
// tslint:disable-next-line:no-console
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(false, "No service found for invocation '" + activity.src + "' in machine '" + this.machine.id + "'.");
|
|
}
|
|
return;
|
|
}
|
|
var source = utils_1.isFunction(serviceCreator)
|
|
? serviceCreator(context, event)
|
|
: serviceCreator;
|
|
if (utils_1.isPromiseLike(source)) {
|
|
this.spawnPromise(Promise.resolve(source), id);
|
|
}
|
|
else if (utils_1.isFunction(source)) {
|
|
this.spawnCallback(source, id);
|
|
}
|
|
else if (utils_1.isObservable(source)) {
|
|
this.spawnObservable(source, id);
|
|
}
|
|
else if (utils_1.isMachine(source)) {
|
|
// TODO: try/catch here
|
|
this.spawnMachine(data
|
|
? source.withContext(utils_1.mapContext(data, context, event))
|
|
: source, {
|
|
id: id,
|
|
autoForward: autoForward
|
|
});
|
|
}
|
|
else {
|
|
// service is string
|
|
}
|
|
}
|
|
else {
|
|
this.spawnActivity(activity);
|
|
}
|
|
break;
|
|
}
|
|
case actionTypes.stop: {
|
|
this.stopChild(action.activity.id);
|
|
break;
|
|
}
|
|
case actionTypes.log:
|
|
var expr = action.expr ? action.expr(context, event) : undefined;
|
|
if (action.label) {
|
|
this.logger(action.label, expr);
|
|
}
|
|
else {
|
|
this.logger(expr);
|
|
}
|
|
break;
|
|
default:
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(false, "No implementation found for action type '" + action.type + "'");
|
|
}
|
|
break;
|
|
}
|
|
return undefined;
|
|
};
|
|
Interpreter.prototype.stopChild = function (childId) {
|
|
var child = this.children.get(childId);
|
|
if (!child) {
|
|
return;
|
|
}
|
|
this.children.delete(childId);
|
|
this.forwardTo.delete(childId);
|
|
if (utils_1.isFunction(child.stop)) {
|
|
child.stop();
|
|
}
|
|
};
|
|
Interpreter.prototype.spawn = function (entity, name, options) {
|
|
if (utils_1.isPromiseLike(entity)) {
|
|
return this.spawnPromise(Promise.resolve(entity), name);
|
|
}
|
|
else if (utils_1.isFunction(entity)) {
|
|
return this.spawnCallback(entity, name);
|
|
}
|
|
else if (utils_1.isObservable(entity)) {
|
|
return this.spawnObservable(entity, name);
|
|
}
|
|
else if (utils_1.isMachine(entity)) {
|
|
return this.spawnMachine(entity, __assign({}, options, { id: name }));
|
|
}
|
|
else {
|
|
throw new Error("Unable to spawn entity \"" + name + "\" of type \"" + typeof entity + "\".");
|
|
}
|
|
};
|
|
Interpreter.prototype.spawnMachine = function (machine, options) {
|
|
var _this = this;
|
|
if (options === void 0) { options = {}; }
|
|
var childService = new Interpreter(machine, __assign({}, this.options, { parent: this, id: options.id || machine.id }));
|
|
var resolvedOptions = __assign({}, DEFAULT_SPAWN_OPTIONS, options);
|
|
if (resolvedOptions.sync) {
|
|
childService.onTransition(function (state) {
|
|
_this.send(actionTypes.update, { state: state, id: childService.id });
|
|
});
|
|
}
|
|
childService
|
|
.onDone(function (doneEvent) {
|
|
_this.send(doneEvent);
|
|
})
|
|
.start();
|
|
var actor = childService;
|
|
// const actor = {
|
|
// id: childService.id,
|
|
// send: childService.send,
|
|
// state: childService.state,
|
|
// subscribe: childService.subscribe,
|
|
// toJSON() {
|
|
// return { id: childService.id };
|
|
// }
|
|
// } as Actor<State<TChildContext, TChildEvents>>;
|
|
this.children.set(childService.id, actor);
|
|
if (resolvedOptions.autoForward) {
|
|
this.forwardTo.add(childService.id);
|
|
}
|
|
return actor;
|
|
};
|
|
Interpreter.prototype.spawnPromise = function (promise, id) {
|
|
var _this = this;
|
|
var canceled = false;
|
|
promise.then(function (response) {
|
|
if (!canceled) {
|
|
_this.send(actions_1.doneInvoke(id, response));
|
|
}
|
|
}, function (errorData) {
|
|
if (!canceled) {
|
|
var errorEvent = actions_1.error(id, errorData);
|
|
try {
|
|
// Send "error.execution" to this (parent).
|
|
_this.send(errorEvent);
|
|
}
|
|
catch (error) {
|
|
_this.reportUnhandledExceptionOnInvocation(errorData, error, id);
|
|
if (_this.devTools) {
|
|
_this.devTools.send(errorEvent, _this.state);
|
|
}
|
|
if (_this.machine.strict) {
|
|
// it would be better to always stop the state machine if unhandled
|
|
// exception/promise rejection happens but because we don't want to
|
|
// break existing code so enforce it on strict mode only especially so
|
|
// because documentation says that onError is optional
|
|
_this.stop();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
var actor = {
|
|
id: id,
|
|
send: function () { return void 0; },
|
|
subscribe: function (next, handleError, complete) {
|
|
var unsubscribed = false;
|
|
promise.then(function (response) {
|
|
if (unsubscribed) {
|
|
return;
|
|
}
|
|
next && next(response);
|
|
if (unsubscribed) {
|
|
return;
|
|
}
|
|
complete && complete();
|
|
}, function (err) {
|
|
if (unsubscribed) {
|
|
return;
|
|
}
|
|
handleError(err);
|
|
});
|
|
return {
|
|
unsubscribe: function () { return (unsubscribed = true); }
|
|
};
|
|
},
|
|
stop: function () {
|
|
canceled = true;
|
|
},
|
|
toJSON: function () {
|
|
return { id: id };
|
|
}
|
|
};
|
|
this.children.set(id, actor);
|
|
return actor;
|
|
};
|
|
Interpreter.prototype.spawnCallback = function (callback, id) {
|
|
var _this = this;
|
|
var canceled = false;
|
|
var receive = function (e) {
|
|
if (canceled) {
|
|
return;
|
|
}
|
|
_this.send(e);
|
|
};
|
|
var listeners = new Set();
|
|
var callbackStop;
|
|
try {
|
|
callbackStop = callback(receive, function (newListener) {
|
|
listeners.add(newListener);
|
|
});
|
|
}
|
|
catch (err) {
|
|
this.send(actions_1.error(id, err));
|
|
}
|
|
if (utils_1.isPromiseLike(callbackStop)) {
|
|
// it turned out to be an async function, can't reliably check this before calling `callback`
|
|
// because transpiled async functions are not recognizable
|
|
return this.spawnPromise(callbackStop, id);
|
|
}
|
|
var actor = {
|
|
id: id,
|
|
send: function (event) { return listeners.forEach(function (listener) { return listener(event); }); },
|
|
subscribe: function (next) {
|
|
listeners.add(next);
|
|
return {
|
|
unsubscribe: function () {
|
|
listeners.delete(next);
|
|
}
|
|
};
|
|
},
|
|
stop: function () {
|
|
canceled = true;
|
|
if (utils_1.isFunction(callbackStop)) {
|
|
callbackStop();
|
|
}
|
|
},
|
|
toJSON: function () {
|
|
return { id: id };
|
|
}
|
|
};
|
|
this.children.set(id, actor);
|
|
return actor;
|
|
};
|
|
Interpreter.prototype.spawnObservable = function (source, id) {
|
|
var _this = this;
|
|
var subscription = source.subscribe(function (value) {
|
|
_this.send(value);
|
|
}, function (err) {
|
|
_this.send(actions_1.error(id, err));
|
|
}, function () {
|
|
_this.send(actions_1.doneInvoke(id));
|
|
});
|
|
var actor = {
|
|
id: id,
|
|
send: function () { return void 0; },
|
|
subscribe: function (next, handleError, complete) {
|
|
return source.subscribe(next, handleError, complete);
|
|
},
|
|
stop: function () { return subscription.unsubscribe(); },
|
|
toJSON: function () {
|
|
return { id: id };
|
|
}
|
|
};
|
|
this.children.set(id, actor);
|
|
return actor;
|
|
};
|
|
Interpreter.prototype.spawnActivity = function (activity) {
|
|
var implementation = this.machine.options && this.machine.options.activities
|
|
? this.machine.options.activities[activity.type]
|
|
: undefined;
|
|
if (!implementation) {
|
|
// tslint:disable-next-line:no-console
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(false, "No implementation found for activity '" + activity.type + "'");
|
|
}
|
|
return;
|
|
}
|
|
// Start implementation
|
|
var dispose = implementation(this.state.context, activity);
|
|
this.spawnEffect(activity.id, dispose);
|
|
};
|
|
Interpreter.prototype.spawnEffect = function (id, dispose) {
|
|
this.children.set(id, {
|
|
id: id,
|
|
send: function () { return void 0; },
|
|
subscribe: function () {
|
|
return { unsubscribe: function () { return void 0; } };
|
|
},
|
|
stop: dispose || undefined,
|
|
toJSON: function () {
|
|
return { id: id };
|
|
}
|
|
});
|
|
};
|
|
Interpreter.prototype.reportUnhandledExceptionOnInvocation = function (originalError, currentError, id) {
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
var originalStackTrace = originalError.stack
|
|
? " Stacktrace was '" + originalError.stack + "'"
|
|
: '';
|
|
if (originalError === currentError) {
|
|
// tslint:disable-next-line:no-console
|
|
console.error("Missing onError handler for invocation '" + id + "', error was '" + originalError + "'." + originalStackTrace);
|
|
}
|
|
else {
|
|
var stackTrace = currentError.stack
|
|
? " Stacktrace was '" + currentError.stack + "'"
|
|
: '';
|
|
// tslint:disable-next-line:no-console
|
|
console.error("Missing onError handler and/or unhandled exception/promise rejection for invocation '" + id + "'. " +
|
|
("Original error: '" + originalError + "'. " + originalStackTrace + " Current error is '" + currentError + "'." + stackTrace));
|
|
}
|
|
}
|
|
};
|
|
Interpreter.prototype.attachDev = function () {
|
|
if (this.options.devTools &&
|
|
typeof window !== 'undefined' &&
|
|
window.__REDUX_DEVTOOLS_EXTENSION__) {
|
|
var devToolsOptions = typeof this.options.devTools === 'object'
|
|
? this.options.devTools
|
|
: undefined;
|
|
this.devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect(__assign({ name: this.id, autoPause: true, stateSanitizer: function (state) {
|
|
return {
|
|
value: state.value,
|
|
context: state.context,
|
|
actions: state.actions
|
|
};
|
|
} }, devToolsOptions, { features: __assign({ jump: false, skip: false }, (devToolsOptions ? devToolsOptions.features : undefined)) }));
|
|
this.devTools.init(this.state);
|
|
}
|
|
};
|
|
Interpreter.prototype.toJSON = function () {
|
|
return {
|
|
id: this.id
|
|
};
|
|
};
|
|
/**
|
|
* The default interpreter options:
|
|
*
|
|
* - `clock` uses the global `setTimeout` and `clearTimeout` functions
|
|
* - `logger` uses the global `console.log()` method
|
|
*/
|
|
Interpreter.defaultOptions = (function (global) { return ({
|
|
execute: true,
|
|
deferEvents: true,
|
|
clock: {
|
|
setTimeout: function (fn, ms) {
|
|
return global.setTimeout.call(null, fn, ms);
|
|
},
|
|
clearTimeout: function (id) {
|
|
return global.clearTimeout.call(null, id);
|
|
}
|
|
},
|
|
logger: global.console.log.bind(console),
|
|
devTools: false
|
|
}); })(typeof window === 'undefined' ? global : window);
|
|
Interpreter.interpret = interpret;
|
|
return Interpreter;
|
|
}());
|
|
exports.Interpreter = Interpreter;
|
|
var createNullActor = function (name) {
|
|
if (name === void 0) { name = 'null'; }
|
|
return ({
|
|
id: name,
|
|
send: function () { return void 0; },
|
|
subscribe: function () {
|
|
// tslint:disable-next-line:no-empty
|
|
return { unsubscribe: function () { } };
|
|
},
|
|
toJSON: function () { return ({ id: name }); }
|
|
});
|
|
};
|
|
var resolveSpawnOptions = function (nameOrOptions) {
|
|
if (utils_1.isString(nameOrOptions)) {
|
|
return __assign({}, DEFAULT_SPAWN_OPTIONS, { name: nameOrOptions });
|
|
}
|
|
return __assign({}, DEFAULT_SPAWN_OPTIONS, { name: utils_1.uniqueId() }, nameOrOptions);
|
|
};
|
|
function spawn(entity, nameOrOptions) {
|
|
var resolvedOptions = resolveSpawnOptions(nameOrOptions);
|
|
return withServiceScope(undefined, function (service) {
|
|
if (!environment_1.IS_PRODUCTION) {
|
|
utils_1.warn(!!service, "Attempted to spawn an Actor (ID: \"" + (utils_1.isMachine(entity) ? entity.id : 'undefined') + "\") outside of a service. This will have no effect.");
|
|
}
|
|
if (service) {
|
|
return service.spawn(entity, resolvedOptions.name, resolvedOptions);
|
|
}
|
|
else {
|
|
return createNullActor(resolvedOptions.name);
|
|
}
|
|
});
|
|
}
|
|
exports.spawn = spawn;
|
|
/**
|
|
* Creates a new Interpreter instance for the given machine with the provided options, if any.
|
|
*
|
|
* @param machine The machine to interpret
|
|
* @param options Interpreter options
|
|
*/
|
|
function interpret(machine, options) {
|
|
var interpreter = new Interpreter(machine, options);
|
|
return interpreter;
|
|
}
|
|
exports.interpret = interpret;
|
|
//# sourceMappingURL=interpreter.js.map
|