import { __values, __spreadArray, __read, __assign } from './_virtual/_tslib.js'; import { SpecialTargets, ActionTypes } from './types.js'; import { isStateConfig, State, bindActionToState } from './State.js'; import { errorPlatform, update, error as error$1, log, stop, start, cancel, send, raise } from './actionTypes.js'; import { initEvent, doneInvoke, toActionObjects, resolveActions, error, getActionFunction } from './actions.js'; import { IS_PRODUCTION } from './environment.js'; import { warn, mapContext, toObserver, isFunction, toSCXMLEvent, flatten, isRaisableAction, isPromiseLike, isObservable, isMachine, isBehavior, reportUnhandledExceptionOnInvocation, symbolObservable, isArray, toEventObject, isString, isActor, toInvokeSource, uniqueId } from './utils.js'; import { Scheduler } from './scheduler.js'; import { createDeferredActor, isSpawnedActor } from './Actor.js'; import { registry } from './registry.js'; import { getGlobal, registerService } from './devTools.js'; import { provide, consume } from './serviceScope.js'; import { spawnBehavior } from './behaviors.js'; var DEFAULT_SPAWN_OPTIONS = { sync: false, autoForward: false }; var InterpreterStatus; (function (InterpreterStatus) { InterpreterStatus[InterpreterStatus["NotStarted"] = 0] = "NotStarted"; InterpreterStatus[InterpreterStatus["Running"] = 1] = "Running"; InterpreterStatus[InterpreterStatus["Stopped"] = 2] = "Stopped"; })(InterpreterStatus || (InterpreterStatus = {})); var Interpreter = /*#__PURE__*/ /** @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) { if (options === void 0) { options = Interpreter.defaultOptions; } var _this = this; this.machine = machine; 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.status = InterpreterStatus.NotStarted; this.children = new Map(); this.forwardTo = new Set(); this._outgoingQueue = []; /** * 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 (isArray(event)) { _this.batch(event); return _this.state; } var _event = toSCXMLEvent(toEventObject(event, payload)); if (_this.status === InterpreterStatus.Stopped) { // do nothing if (!IS_PRODUCTION) { warn(false, "Event \"".concat(_event.name, "\" was sent to stopped service \"").concat(_this.machine.id, "\". This service has already reached its final state, and will not transition.\nEvent: ").concat(JSON.stringify(_event.data))); } return _this.state; } if (_this.status !== InterpreterStatus.Running && !_this.options.deferEvents) { throw new Error("Event \"".concat(_event.name, "\" was sent to uninitialized service \"").concat(_this.machine.id // tslint:disable-next-line:max-line-length , "\". Make sure .start() is called for this service, or set { deferEvents: true } in the service options.\nEvent: ").concat(JSON.stringify(_event.data))); } _this.scheduler.schedule(function () { // Forward copy of event to child actors _this.forward(_event); var nextState = _this._nextState(_event); _this.update(nextState, _event); }); return _this._state; // TODO: deprecate (should return void) // tslint:disable-next-line:semicolon }; this.sendTo = function (event, to, immediate) { var isParent = _this.parent && (to === SpecialTargets.Parent || _this.parent.id === to); var target = isParent ? _this.parent : isString(to) ? to === SpecialTargets.Internal ? _this : _this.children.get(to) || registry.get(to) : isActor(to) ? to : undefined; if (!target) { if (!isParent) { throw new Error("Unable to send event to child '".concat(to, "' from service '").concat(_this.id, "'.")); } // tslint:disable-next-line:no-console if (!IS_PRODUCTION) { warn(false, "Service '".concat(_this.id, "' has no parent: unable to send event ").concat(event.type)); } return; } if ('machine' in target) { // perhaps those events should be rejected in the parent // but atm it doesn't have easy access to all of the information that is required to do it reliably if (_this.status !== InterpreterStatus.Stopped || _this.parent !== target || // we need to send events to the parent from exit handlers of a machine that reached its final state _this.state.done) { // Send SCXML events to machines var scxmlEvent = __assign(__assign({}, event), { name: event.name === error$1 ? "".concat(error(_this.id)) : event.name, origin: _this.sessionId }); if (!immediate && _this.machine.config.predictableActionArguments) { _this._outgoingQueue.push([target, scxmlEvent]); } else { target.send(scxmlEvent); } } } else { // Send normal events to other targets if (!immediate && _this.machine.config.predictableActionArguments) { _this._outgoingQueue.push([target, event.data]); } else { target.send(event.data); } } }; this._exec = function (action, context, _event, actionFunctionMap) { if (actionFunctionMap === void 0) { actionFunctionMap = _this.machine.options.actions; } var actionOrExec = action.exec || getActionFunction(action.type, actionFunctionMap); var exec = isFunction(actionOrExec) ? actionOrExec : actionOrExec ? actionOrExec.exec : action.exec; if (exec) { try { return exec(context, _event.data, !_this.machine.config.predictableActionArguments ? { action: action, state: _this.state, _event: _event } : { action: action, _event: _event }); } catch (err) { if (_this.parent) { _this.parent.send({ type: 'xstate.error', data: err }); } throw err; } } switch (action.type) { case raise: { // if raise action reached the interpreter then it's a delayed one var sendAction_1 = action; _this.defer(sendAction_1); break; } case send: var sendAction = action; if (typeof sendAction.delay === 'number') { _this.defer(sendAction); return; } else { if (sendAction.to) { _this.sendTo(sendAction._event, sendAction.to, _event === initEvent); } else { _this.send(sendAction._event); } } break; case cancel: _this.cancel(action.sendId); break; case start: { if (_this.status !== InterpreterStatus.Running) { return; } 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 ( // in v4 with `predictableActionArguments` invokes are called eagerly when the `this.state` still points to the previous state !_this.machine.config.predictableActionArguments && !_this.state.activities[activity.id || activity.type]) { break; } // Invoked services if (activity.type === ActionTypes.Invoke) { var invokeSource = toInvokeSource(activity.src); var serviceCreator = _this.machine.options.services ? _this.machine.options.services[invokeSource.type] : undefined; var id = activity.id, data = activity.data; if (!IS_PRODUCTION) { warn(!('forward' in activity), // tslint:disable-next-line:max-line-length "`forward` property is deprecated (found in invocation of '".concat(activity.src, "' in in machine '").concat(_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 (!IS_PRODUCTION) { warn(false, "No service found for invocation '".concat(activity.src, "' in machine '").concat(_this.machine.id, "'.")); } return; } var resolvedData = data ? mapContext(data, context, _event) : undefined; if (typeof serviceCreator === 'string') { // TODO: warn return; } var source = isFunction(serviceCreator) ? serviceCreator(context, _event.data, { data: resolvedData, src: invokeSource, meta: activity.meta }) : serviceCreator; if (!source) { // TODO: warn? return; } var options = void 0; if (isMachine(source)) { source = resolvedData ? source.withContext(resolvedData) : source; options = { autoForward: autoForward }; } _this.spawn(source, id, options); } else { _this.spawnActivity(activity); } break; } case stop: { _this.stopChild(action.activity.id); break; } case log: var _a = action, label = _a.label, value = _a.value; if (label) { _this.logger(label, value); } else { _this.logger(value); } break; default: if (!IS_PRODUCTION) { warn(false, "No implementation found for action type '".concat(action.type, "'")); } break; } }; var resolvedOptions = __assign(__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({ deferEvents: this.options.deferEvents }); this.sessionId = registry.bookId(); } Object.defineProperty(Interpreter.prototype, "initialState", { get: function () { var _this = this; if (this._initialState) { return this._initialState; } return provide(this, function () { _this._initialState = _this.machine.initialState; return _this._initialState; }); }, enumerable: false, configurable: true }); Object.defineProperty(Interpreter.prototype, "state", { /** * @deprecated Use `.getSnapshot()` instead. */ get: function () { if (!IS_PRODUCTION) { warn(this.status !== InterpreterStatus.NotStarted, "Attempted to read state from uninitialized service '".concat(this.id, "'. Make sure the service is started first.")); } return this._state; }, enumerable: false, configurable: true }); /** * 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, 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; var _this = this; // Attach session ID to state state._sessionid = this.sessionId; // Update state this._state = state; // Execute actions if ((!this.machine.config.predictableActionArguments || // this is currently required to execute initial actions as the `initialState` gets cached // we can't just recompute it (and execute actions while doing so) because we try to preserve identity of actors created within initial assigns _event === initEvent) && this.options.execute) { this.execute(this.state); } else { var item = void 0; while (item = this._outgoingQueue.shift()) { item[0].send(item[1]); } } // Update children this.children.forEach(function (child) { _this.state.children[child.id] = child; }); // Dev tools if (this.devTools) { this.devTools.send(_event.data, 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.done) { // get final child state node var finalChildStateNode = state.configuration.find(function (sn) { return sn.type === 'final' && sn.parent === _this.machine; }); var doneData = finalChildStateNode && finalChildStateNode.doneData ? mapContext(finalChildStateNode.doneData, state.context, _event) : undefined; this._doneEvent = doneInvoke(this.id, doneData); try { for (var _l = __values(this.doneListeners), _m = _l.next(); !_m.done; _m = _l.next()) { var listener = _m.value; listener(this._doneEvent); } } 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(); this._stopChildren(); registry.free(this.sessionId); } }; /* * 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); // Send current state to listener if (this.status === InterpreterStatus.Running) { listener(this.state, this.state.event); } return this; }; Interpreter.prototype.subscribe = function (nextListenerOrObserver, _, // TODO: error listener completeListener) { var _this = this; var observer = toObserver(nextListenerOrObserver, _, completeListener); this.listeners.add(observer.next); // Send current state to listener if (this.status !== InterpreterStatus.NotStarted) { observer.next(this.state); } var completeOnce = function () { _this.doneListeners.delete(completeOnce); _this.stopListeners.delete(completeOnce); observer.complete(); }; if (this.status === InterpreterStatus.Stopped) { observer.complete(); } else { this.onDone(completeOnce); this.onStop(completeOnce); } return { unsubscribe: function () { _this.listeners.delete(observer.next); _this.doneListeners.delete(completeOnce); _this.stopListeners.delete(completeOnce); } }; }; /** * 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) { if (this.status === InterpreterStatus.Stopped && this._doneEvent) { listener(this._doneEvent); } else { 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.status === InterpreterStatus.Running) { // Do not restart the service if it is already started return this; } // yes, it's a hack but we need the related cache to be populated for some things to work (like delayed transitions) // this is usually called by `machine.getInitialState` but if we rehydrate from a state we might bypass this call // we also don't want to call this method here as it resolves the full initial state which might involve calling assign actions // and that could potentially lead to some unwanted side-effects (even such as creating some rogue actors) this.machine._init(); registry.register(this.sessionId, this); this.initialized = true; this.status = InterpreterStatus.Running; var resolvedState = initialState === undefined ? this.initialState : provide(this, function () { return isStateConfig(initialState) ? _this.machine.resolveState(initialState) : _this.machine.resolveState(State.from(initialState, _this.machine.context)); }); if (this.options.devTools) { this.attachDev(); } this.scheduler.initialize(function () { _this.update(resolvedState, initEvent); }); return this; }; Interpreter.prototype._stopChildren = function () { // TODO: think about converting those to actions this.children.forEach(function (child) { if (isFunction(child.stop)) { child.stop(); } }); this.children.clear(); }; 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; } } if (!this.initialized) { // Interpreter already stopped; do nothing return this; } this.initialized = false; this.status = InterpreterStatus.Stopped; this._initialState = undefined; try { // we are going to stop within the current sync frame // so we can safely just cancel this here as nothing async should be fired anyway for (var _p = __values(Object.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; } } // clear everything that might be enqueued this.scheduler.clear(); this.scheduler = new Scheduler({ deferEvents: this.options.deferEvents }); }; /** * Stops the interpreter and unsubscribe all listeners. * * This will also notify the `onStop` listeners. */ Interpreter.prototype.stop = function () { // TODO: add warning for stopping non-root interpreters var _this = this; // grab the current scheduler as it will be replaced in _stop var scheduler = this.scheduler; this._stop(); // let what is currently processed to be finished scheduler.schedule(function () { var _a; if ((_a = _this._state) === null || _a === void 0 ? void 0 : _a.done) { return; } // it feels weird to handle this here but we need to handle this even slightly "out of band" var _event = toSCXMLEvent({ type: 'xstate.stop' }); var nextState = provide(_this, function () { var exitActions = flatten(__spreadArray([], __read(_this.state.configuration), false).sort(function (a, b) { return b.order - a.order; }).map(function (stateNode) { return toActionObjects(stateNode.onExit, _this.machine.options.actions); })); var _a = __read(resolveActions(_this.machine, _this.state, _this.state.context, _event, [{ type: 'exit', actions: exitActions }], _this.machine.config.predictableActionArguments ? _this._exec : undefined, _this.machine.config.predictableActionArguments || _this.machine.config.preserveActionOrder), 2), resolvedActions = _a[0], updatedContext = _a[1]; var newState = new State({ value: _this.state.value, context: updatedContext, _event: _event, _sessionid: _this.sessionId, historyValue: undefined, history: _this.state, actions: resolvedActions.filter(function (action) { return !isRaisableAction(action); }), activities: {}, events: [], configuration: [], transitions: [], children: {}, done: _this.state.done, tags: _this.state.tags, machine: _this.machine }); newState.changed = true; return newState; }); _this.update(nextState, _event); _this._stopChildren(); registry.free(_this.sessionId); }); return this; }; Interpreter.prototype.batch = function (events) { var _this = this; if (this.status === InterpreterStatus.NotStarted && this.options.deferEvents) { // tslint:disable-next-line:no-console if (!IS_PRODUCTION) { warn(false, "".concat(events.length, " event(s) were sent to uninitialized service \"").concat(this.machine.id, "\" and are deferred. Make sure .start() is called for this service.\nEvent: ").concat(JSON.stringify(event))); } } else if (this.status !== InterpreterStatus.Running) { throw new Error( // tslint:disable-next-line:max-line-length "".concat(events.length, " event(s) were sent to uninitialized service \"").concat(this.machine.id, "\". Make sure .start() is called for this service, or set { deferEvents: true } in the service options.")); } if (!events.length) { return; } var exec = !!this.machine.config.predictableActionArguments && this._exec; this.scheduler.schedule(function () { var e_11, _a; var nextState = _this.state; var batchChanged = false; var batchedActions = []; var _loop_1 = function (event_1) { var _event = toSCXMLEvent(event_1); _this.forward(_event); nextState = provide(_this, function () { return _this.machine.transition(nextState, _event, undefined, exec || undefined); }); batchedActions.push.apply(batchedActions, __spreadArray([], __read(_this.machine.config.predictableActionArguments ? nextState.actions : nextState.actions.map(function (a) { return bindActionToState(a, nextState); })), false)); batchChanged = batchChanged || !!nextState.changed; }; 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; _loop_1(event_1); } } 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; } } nextState.changed = batchChanged; nextState.actions = batchedActions; _this.update(nextState, toSCXMLEvent(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); }; Interpreter.prototype._nextState = function (event, exec) { var _this = this; if (exec === void 0) { exec = !!this.machine.config.predictableActionArguments && this._exec; } var _event = toSCXMLEvent(event); if (_event.name.indexOf(errorPlatform) === 0 && !this.state.nextEvents.some(function (nextEvent) { return nextEvent.indexOf(errorPlatform) === 0; })) { throw _event.data.data; } var nextState = provide(this, function () { return _this.machine.transition(_this.state, _event, undefined, exec || undefined); }); return nextState; }; /** * 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) { return this._nextState(event, false); }; 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 '".concat(event, "' from interpreter '").concat(this.id, "' to nonexistant child '").concat(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 timerId = this.clock.setTimeout(function () { if ('to' in sendAction && sendAction.to) { _this.sendTo(sendAction._event, sendAction.to, true); } else { _this.send(sendAction._event); } }, sendAction.delay); if (sendAction.id) { this.delayedEventsMap[sendAction.id] = timerId; } }; Interpreter.prototype.cancel = function (sendId) { this.clock.clearTimeout(this.delayedEventsMap[sendId]); delete this.delayedEventsMap[sendId]; }; Interpreter.prototype.exec = function (action, state, actionFunctionMap) { if (actionFunctionMap === void 0) { actionFunctionMap = this.machine.options.actions; } this._exec(action, state.context, state._event, actionFunctionMap); }; Interpreter.prototype.removeChild = function (childId) { var _a; this.children.delete(childId); this.forwardTo.delete(childId); // this.state might not exist at the time this is called, // such as when a child is added then removed while initializing the state (_a = this.state) === null || _a === void 0 ? true : delete _a.children[childId]; }; Interpreter.prototype.stopChild = function (childId) { var child = this.children.get(childId); if (!child) { return; } this.removeChild(childId); if (isFunction(child.stop)) { child.stop(); } }; Interpreter.prototype.spawn = function (entity, name, options) { if (this.status !== InterpreterStatus.Running) { return createDeferredActor(entity, name); } if (isPromiseLike(entity)) { return this.spawnPromise(Promise.resolve(entity), name); } else if (isFunction(entity)) { return this.spawnCallback(entity, name); } else if (isSpawnedActor(entity)) { return this.spawnActor(entity, name); } else if (isObservable(entity)) { return this.spawnObservable(entity, name); } else if (isMachine(entity)) { return this.spawnMachine(entity, __assign(__assign({}, options), { id: name })); } else if (isBehavior(entity)) { return this.spawnBehavior(entity, name); } else { throw new Error("Unable to spawn entity \"".concat(name, "\" of type \"").concat(typeof entity, "\".")); } }; Interpreter.prototype.spawnMachine = function (machine, options) { var _this = this; if (options === void 0) { options = {}; } var childService = new Interpreter(machine, __assign(__assign({}, this.options), { parent: this, id: options.id || machine.id })); var resolvedOptions = __assign(__assign({}, DEFAULT_SPAWN_OPTIONS), options); if (resolvedOptions.sync) { childService.onTransition(function (state) { _this.send(update, { state: state, id: childService.id }); }); } var actor = childService; this.children.set(childService.id, actor); if (resolvedOptions.autoForward) { this.forwardTo.add(childService.id); } childService.onDone(function (doneEvent) { _this.removeChild(childService.id); _this.send(toSCXMLEvent(doneEvent, { origin: childService.id })); }).start(); return actor; }; Interpreter.prototype.spawnBehavior = function (behavior, id) { var actorRef = spawnBehavior(behavior, { id: id, parent: this }); this.children.set(id, actorRef); return actorRef; }; Interpreter.prototype.spawnPromise = function (promise, id) { var _a; var _this = this; var canceled = false; var resolvedData; promise.then(function (response) { if (!canceled) { resolvedData = response; _this.removeChild(id); _this.send(toSCXMLEvent(doneInvoke(id, response), { origin: id })); } }, function (errorData) { if (!canceled) { _this.removeChild(id); var errorEvent = error(id, errorData); try { // Send "error.platform.id" to this (parent). _this.send(toSCXMLEvent(errorEvent, { origin: id })); } catch (error) { 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 = (_a = { id: id, send: function () { return void 0; }, subscribe: function (next, handleError, complete) { var observer = toObserver(next, handleError, complete); var unsubscribed = false; promise.then(function (response) { if (unsubscribed) { return; } observer.next(response); if (unsubscribed) { return; } observer.complete(); }, function (err) { if (unsubscribed) { return; } observer.error(err); }); return { unsubscribe: function () { return unsubscribed = true; } }; }, stop: function () { canceled = true; }, toJSON: function () { return { id: id }; }, getSnapshot: function () { return resolvedData; } }, _a[symbolObservable] = function () { return this; }, _a); this.children.set(id, actor); return actor; }; Interpreter.prototype.spawnCallback = function (callback, id) { var _a; var _this = this; var canceled = false; var receivers = new Set(); var listeners = new Set(); var emitted; var receive = function (e) { emitted = e; listeners.forEach(function (listener) { return listener(e); }); if (canceled) { return; } _this.send(toSCXMLEvent(e, { origin: id })); }; var callbackStop; try { callbackStop = callback(receive, function (newListener) { receivers.add(newListener); }); } catch (err) { this.send(error(id, err)); } if (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 = (_a = { id: id, send: function (event) { return receivers.forEach(function (receiver) { return receiver(event); }); }, subscribe: function (next) { var observer = toObserver(next); listeners.add(observer.next); return { unsubscribe: function () { listeners.delete(observer.next); } }; }, stop: function () { canceled = true; if (isFunction(callbackStop)) { callbackStop(); } }, toJSON: function () { return { id: id }; }, getSnapshot: function () { return emitted; } }, _a[symbolObservable] = function () { return this; }, _a); this.children.set(id, actor); return actor; }; Interpreter.prototype.spawnObservable = function (source, id) { var _a; var _this = this; var emitted; var subscription = source.subscribe(function (value) { emitted = value; _this.send(toSCXMLEvent(value, { origin: id })); }, function (err) { _this.removeChild(id); _this.send(toSCXMLEvent(error(id, err), { origin: id })); }, function () { _this.removeChild(id); _this.send(toSCXMLEvent(doneInvoke(id), { origin: id })); }); var actor = (_a = { id: id, send: function () { return void 0; }, subscribe: function (next, handleError, complete) { return source.subscribe(next, handleError, complete); }, stop: function () { return subscription.unsubscribe(); }, getSnapshot: function () { return emitted; }, toJSON: function () { return { id: id }; } }, _a[symbolObservable] = function () { return this; }, _a); this.children.set(id, actor); return actor; }; Interpreter.prototype.spawnActor = function (actor, name) { this.children.set(name, 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) { if (!IS_PRODUCTION) { warn(false, "No implementation found for activity '".concat(activity.type, "'")); } // tslint:disable-next-line:no-console return; } // Start implementation var dispose = implementation(this.state.context, activity); this.spawnEffect(activity.id, dispose); }; Interpreter.prototype.spawnEffect = function (id, dispose) { var _a; this.children.set(id, (_a = { id: id, send: function () { return void 0; }, subscribe: function () { return { unsubscribe: function () { return void 0; } }; }, stop: dispose || undefined, getSnapshot: function () { return undefined; }, toJSON: function () { return { id: id }; } }, _a[symbolObservable] = function () { return this; }, _a)); }; Interpreter.prototype.attachDev = function () { var global = getGlobal(); if (this.options.devTools && global) { if (global.__REDUX_DEVTOOLS_EXTENSION__) { var devToolsOptions = typeof this.options.devTools === 'object' ? this.options.devTools : undefined; this.devTools = global.__REDUX_DEVTOOLS_EXTENSION__.connect(__assign(__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.machine); this.devTools.init(this.state); } // add XState-specific dev tooling hook registerService(this); } }; Interpreter.prototype.toJSON = function () { return { id: this.id }; }; Interpreter.prototype[symbolObservable] = function () { return this; }; Interpreter.prototype.getSnapshot = function () { if (this.status === InterpreterStatus.NotStarted) { return this.initialState; } return this._state; }; /** * The default interpreter options: * * - `clock` uses the global `setTimeout` and `clearTimeout` functions * - `logger` uses the global `console.log()` method */ Interpreter.defaultOptions = { execute: true, deferEvents: true, clock: { setTimeout: function (fn, ms) { return setTimeout(fn, ms); }, clearTimeout: function (id) { return clearTimeout(id); } }, logger: /*#__PURE__*/console.log.bind(console), devTools: false }; Interpreter.interpret = interpret; return Interpreter; }(); var resolveSpawnOptions = function (nameOrOptions) { if (isString(nameOrOptions)) { return __assign(__assign({}, DEFAULT_SPAWN_OPTIONS), { name: nameOrOptions }); } return __assign(__assign(__assign({}, DEFAULT_SPAWN_OPTIONS), { name: uniqueId() }), nameOrOptions); }; function spawn(entity, nameOrOptions) { var resolvedOptions = resolveSpawnOptions(nameOrOptions); return consume(function (service) { if (!IS_PRODUCTION) { var isLazyEntity = isMachine(entity) || isFunction(entity); warn(!!service || isLazyEntity, "Attempted to spawn an Actor (ID: \"".concat(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 createDeferredActor(entity, resolvedOptions.name); } }); } /** * 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; } export { Interpreter, InterpreterStatus, interpret, spawn };