Files
30-seconds-of-code/node_modules/xstate/lib/StateNode.js
2019-08-20 15:52:05 +02:00

1471 lines
63 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 __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
};
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;
};
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 };
}
};
};
Object.defineProperty(exports, "__esModule", { value: true });
var utils_1 = require("./utils");
var types_1 = require("./types");
var utils_2 = require("./utils");
var State_1 = require("./State");
var actionTypes = require("./actionTypes");
var actions_1 = require("./actions");
var StateTree_1 = require("./StateTree");
var environment_1 = require("./environment");
var constants_1 = require("./constants");
var stateUtils_1 = require("./stateUtils");
var STATE_DELIMITER = '.';
var NULL_EVENT = '';
var STATE_IDENTIFIER = '#';
var TARGETLESS_KEY = '';
var EMPTY_OBJECT = {};
var isStateId = function (str) { return str[0] === STATE_IDENTIFIER; };
var createDefaultOptions = function () { return ({
actions: {},
guards: {},
services: {},
activities: {},
delays: {},
updater: utils_1.updateContext
}); };
var StateNode = /** @class */ (function () {
function StateNode(_config, options,
/**
* The initial extended state
*/
context) {
var _this = this;
this.context = context;
this.__xstatenode = true;
this.__cache = {
events: undefined,
relativeValue: new Map(),
initialStateValue: undefined,
initialState: undefined,
transitions: undefined
};
this.idMap = {};
var parent = _config.parent, config = __rest(_config, ["parent"]);
this.config = config;
this.parent = parent;
this.options = __assign({}, createDefaultOptions(), options);
this.key = _config.key || _config.id || '(machine)';
this.machine = this.parent ? this.parent.machine : this;
this.path = this.parent ? this.parent.path.concat(this.key) : [];
this.delimiter =
_config.delimiter ||
(this.parent ? this.parent.delimiter : STATE_DELIMITER);
this.id =
_config.id ||
(this.machine
? __spread([this.machine.key], this.path).join(this.delimiter)
: this.key);
this.version = this.parent
? this.parent.version
: _config.version;
this.type =
_config.type ||
(_config.parallel
? 'parallel'
: _config.states && utils_1.keys(_config.states).length
? 'compound'
: _config.history
? 'history'
: 'atomic');
if (!environment_1.IS_PRODUCTION) {
utils_1.warn(!('parallel' in _config), "The \"parallel\" property is deprecated and will be removed in version 4.1. " + (_config.parallel
? "Replace with `type: 'parallel'`"
: "Use `type: '" + this.type + "'`") + " in the config for state node '" + this.id + "' instead.");
}
this.initial = _config.initial;
this.order = _config.order || -1;
this.states = (_config.states
? utils_1.mapValues(_config.states, function (stateConfig, key, _, i) {
var _a;
var stateNode = new StateNode(__assign({}, stateConfig, { key: key, order: stateConfig.order === undefined ? i : stateConfig.order, parent: _this }));
Object.assign(_this.idMap, __assign((_a = {}, _a[stateNode.id] = stateNode, _a), stateNode.idMap));
return stateNode;
})
: EMPTY_OBJECT);
// History config
this.history =
_config.history === true ? 'shallow' : _config.history || false;
this._transient = !!(_config.on && _config.on[NULL_EVENT]);
this.strict = !!_config.strict;
// TODO: deprecate (entry)
this.onEntry = utils_1.toArray(_config.entry || _config.onEntry).map(function (action) {
return actions_1.toActionObject(action);
});
// TODO: deprecate (exit)
this.onExit = utils_1.toArray(_config.exit || _config.onExit).map(function (action) {
return actions_1.toActionObject(action);
});
this.meta = _config.meta;
this.data =
this.type === 'final'
? _config.data
: undefined;
this.invoke = utils_1.toArray(_config.invoke).map(function (invokeConfig, i) {
var _a, _b;
if (utils_1.isMachine(invokeConfig)) {
(_this.parent || _this).options.services = __assign((_a = {}, _a[invokeConfig.id] = invokeConfig, _a), (_this.parent || _this).options.services);
return {
type: actionTypes.invoke,
src: invokeConfig.id,
id: invokeConfig.id
};
}
else if (typeof invokeConfig.src !== 'string') {
var invokeSrc = _this.id + ":invocation[" + i + "]"; // TODO: util function
_this.machine.options.services = __assign((_b = {}, _b[invokeSrc] = invokeConfig.src, _b), _this.machine.options.services);
return __assign({ type: actionTypes.invoke, id: invokeSrc }, invokeConfig, { src: invokeSrc });
}
else {
return __assign({}, invokeConfig, { type: actionTypes.invoke, id: invokeConfig.id || invokeConfig.src, src: invokeConfig.src });
}
});
this.activities = utils_1.toArray(_config.activities)
.concat(this.invoke)
.map(function (activity) { return actions_1.toActivityDefinition(activity); });
this.after = this.getDelayedTransitions();
}
/**
* Clones this state machine with custom options and context.
*
* @param options Options (actions, guards, activities, services) to recursively merge with the existing options.
* @param context Custom context (will override predefined context)
*/
StateNode.prototype.withConfig = function (options, context) {
if (context === void 0) { context = this.context; }
var _a = this.options, actions = _a.actions, activities = _a.activities, guards = _a.guards, services = _a.services, delays = _a.delays;
return new StateNode(this.config, {
actions: __assign({}, actions, options.actions),
activities: __assign({}, activities, options.activities),
guards: __assign({}, guards, options.guards),
services: __assign({}, services, options.services),
delays: __assign({}, delays, options.delays)
}, context);
};
/**
* Clones this state machine with custom context.
*
* @param context Custom context (will override predefined context, not recursive)
*/
StateNode.prototype.withContext = function (context) {
return new StateNode(this.config, this.options, context);
};
Object.defineProperty(StateNode.prototype, "definition", {
/**
* The well-structured state node definition.
*/
get: function () {
return {
id: this.id,
key: this.key,
version: this.version,
type: this.type,
initial: this.initial,
history: this.history,
states: utils_1.mapValues(this.states, function (state) { return state.definition; }),
on: this.on,
onEntry: this.onEntry,
onExit: this.onExit,
activities: this.activities || [],
meta: this.meta,
order: this.order || -1,
data: this.data,
invoke: this.invoke
};
},
enumerable: true,
configurable: true
});
StateNode.prototype.toJSON = function () {
return this.definition;
};
Object.defineProperty(StateNode.prototype, "on", {
/**
* The mapping of events to transitions.
*/
get: function () {
return (this.__cache.transitions ||
((this.__cache.transitions = this.formatTransitions()),
this.__cache.transitions));
},
enumerable: true,
configurable: true
});
Object.defineProperty(StateNode.prototype, "transitions", {
/**
* All the transitions that can be taken from this state node.
*/
get: function () {
var _this = this;
return utils_1.flatten(utils_1.keys(this.on).map(function (event) { return _this.on[event]; }));
},
enumerable: true,
configurable: true
});
/**
* All delayed transitions from the config.
*/
StateNode.prototype.getDelayedTransitions = function () {
var _this = this;
if (this.after) {
return this.after;
}
var afterConfig = this.config.after;
var guards = this.machine.options.guards;
if (!afterConfig) {
return [];
}
if (utils_1.isArray(afterConfig)) {
return afterConfig.map(function (delayedTransition, i) {
var delay = delayedTransition.delay, target = delayedTransition.target;
var delayRef;
if (utils_1.isFunction(delay)) {
delayRef = _this.id + ":delay[" + i + "]";
_this.options.delays[delayRef] = delay; // TODO: util function
}
else {
delayRef = delay;
}
var event = actions_1.after(delayRef, _this.id);
_this.onEntry.push(actions_1.send(event, { delay: delay }));
_this.onExit.push(actions_1.cancel(event));
return __assign({ event: event }, delayedTransition, { source: _this, target: target === undefined ? undefined : utils_1.toArray(target), cond: utils_1.toGuard(delayedTransition.cond, guards), actions: utils_1.toArray(delayedTransition.actions).map(function (action) {
return actions_1.toActionObject(action);
}) });
});
}
var allDelayedTransitions = utils_1.flatten(utils_1.keys(afterConfig).map(function (delayKey) {
var delayedTransition = afterConfig[delayKey];
var delay = isNaN(+delayKey) ? delayKey : +delayKey;
var event = actions_1.after(delay, _this.id);
_this.onEntry.push(actions_1.send(event, { delay: delay }));
_this.onExit.push(actions_1.cancel(event));
if (utils_1.isString(delayedTransition)) {
return [
{
source: _this,
target: [delayedTransition],
delay: delay,
event: event,
actions: []
}
];
}
var delayedTransitions = utils_1.toArray(delayedTransition);
return delayedTransitions.map(function (transition) { return (__assign({ event: event,
delay: delay }, transition, { source: _this, target: transition.target === undefined
? transition.target
: utils_1.toArray(transition.target), cond: utils_1.toGuard(transition.cond, guards), actions: utils_1.toArray(transition.actions).map(function (action) {
return actions_1.toActionObject(action);
}) })); });
}));
allDelayedTransitions.sort(function (a, b) {
return utils_1.isString(a) || utils_1.isString(b) ? 0 : +a.delay - +b.delay;
});
return allDelayedTransitions;
};
/**
* Returns the state nodes represented by the current state value.
*
* @param state The state value or State instance
*/
StateNode.prototype.getStateNodes = function (state) {
var _this = this;
var _a;
if (!state) {
return [];
}
var stateValue = state instanceof State_1.State
? state.value
: utils_1.toStateValue(state, this.delimiter);
if (utils_1.isString(stateValue)) {
var initialStateValue = this.getStateNode(stateValue).initial;
return initialStateValue !== undefined
? this.getStateNodes((_a = {}, _a[stateValue] = initialStateValue, _a))
: [this.states[stateValue]];
}
var subStateKeys = utils_1.keys(stateValue);
var subStateNodes = subStateKeys.map(function (subStateKey) { return _this.getStateNode(subStateKey); });
return subStateNodes.concat(subStateKeys.reduce(function (allSubStateNodes, subStateKey) {
var subStateNode = _this.getStateNode(subStateKey).getStateNodes(stateValue[subStateKey]);
return allSubStateNodes.concat(subStateNode);
}, []));
};
/**
* Returns `true` if this state node explicitly handles the given event.
*
* @param event The event in question
*/
StateNode.prototype.handles = function (event) {
var eventType = utils_1.getEventType(event);
return this.events.indexOf(eventType) !== -1;
};
/**
* Resolves the given `state` to a new `State` instance relative to this machine.
*
* This ensures that `.events` and `.nextEvents` represent the correct values.
*
* @param state The state to resolve
*/
StateNode.prototype.resolveState = function (state) {
return new State_1.State(__assign({}, state, { value: this.resolve(state.value), tree: this.getStateTree(state.value) }));
};
StateNode.prototype.transitionLeafNode = function (stateValue, state, eventObject) {
var stateNode = this.getStateNode(stateValue);
var next = stateNode.next(state, eventObject);
if (!next.tree) {
var _a = this.next(state, eventObject), actions = _a.actions, tree = _a.tree, transitions = _a.transitions, configuration = _a.configuration;
return {
tree: tree,
transitions: transitions,
configuration: configuration,
source: state,
actions: actions
};
}
return next;
};
StateNode.prototype.transitionCompoundNode = function (stateValue, state, eventObject) {
var subStateKeys = utils_1.keys(stateValue);
var stateNode = this.getStateNode(subStateKeys[0]);
var next = stateNode._transition(stateValue[subStateKeys[0]], state, eventObject);
if (!next.tree) {
var _a = this.next(state, eventObject), actions = _a.actions, tree = _a.tree, transitions = _a.transitions, configuration = _a.configuration;
return {
tree: tree,
transitions: transitions,
configuration: configuration,
source: state,
actions: actions
};
}
return next;
};
StateNode.prototype.transitionParallelNode = function (stateValue, state, eventObject) {
var e_1, _a;
var noTransitionKeys = [];
var transitionMap = {};
try {
for (var _b = __values(utils_1.keys(stateValue)), _c = _b.next(); !_c.done; _c = _b.next()) {
var subStateKey = _c.value;
var subStateValue = stateValue[subStateKey];
if (!subStateValue) {
continue;
}
var subStateNode = this.getStateNode(subStateKey);
var next = subStateNode._transition(subStateValue, state, eventObject);
if (!next.tree) {
noTransitionKeys.push(subStateKey);
}
transitionMap[subStateKey] = next;
}
}
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; }
}
var stateTransitions = utils_1.keys(transitionMap).map(function (key) { return transitionMap[key]; });
var enabledTransitions = utils_1.flatten(stateTransitions.map(function (st) { return st.transitions; }));
var willTransition = stateTransitions.some(function (transition) { return transition.tree !== undefined; });
if (!willTransition) {
var _d = this.next(state, eventObject), actions = _d.actions, tree = _d.tree, transitions = _d.transitions, _configuration = _d.configuration;
return {
tree: tree,
transitions: transitions,
configuration: _configuration,
source: state,
actions: actions
};
}
var targetNodes = utils_1.flatten(stateTransitions.map(function (st) { return st.configuration; }));
var prevNodes = this.getStateNodes(stateValue);
// console.log(targetNodes.map(t => t.id));
// console.log([...getConfiguration(prevNodes, targetNodes)].map(c => c.id));
var stateValueFromConfiguration = stateUtils_1.getValue(this.machine, stateUtils_1.getConfiguration(prevNodes, targetNodes));
// console.log(sv);
var combinedTree = new StateTree_1.StateTree(this.machine, stateValueFromConfiguration);
// const allTrees = keys(transitionMap)
// .map(key => transitionMap[key].tree)
// .filter(t => t !== undefined) as StateTree[];
// const combinedTree = allTrees.reduce((acc, t) => {
// return acc.combine(t);
// });
var allPaths = combinedTree.paths;
var configuration = utils_1.flatten(utils_1.keys(transitionMap).map(function (key) { return transitionMap[key].configuration; }));
// External transition that escapes orthogonal region
if (allPaths.length === 1 &&
!utils_2.matchesState(utils_1.toStateValue(this.path, this.delimiter), combinedTree.value)) {
return {
tree: combinedTree,
transitions: enabledTransitions,
configuration: configuration,
source: state,
actions: utils_1.flatten(utils_1.keys(transitionMap).map(function (key) {
return transitionMap[key].actions;
}))
};
}
// const allResolvedTrees = keys(transitionMap).map(key => {
// const { tree } = transitionMap[key];
// if (tree) {
// return tree;
// }
// const subValue = path(this.path)(state.value)[key];
// return new StateTree(this.getStateNode(key), subValue).absolute;
// });
// const finalCombinedTree = allResolvedTrees.reduce((acc, t) => {
// return acc.combine(t);
// });
return {
tree: combinedTree,
transitions: enabledTransitions,
configuration: configuration,
source: state,
actions: utils_1.flatten(utils_1.keys(transitionMap).map(function (key) {
return transitionMap[key].actions;
}))
};
};
StateNode.prototype._transition = function (stateValue, state, event) {
// leaf node
if (utils_1.isString(stateValue)) {
return this.transitionLeafNode(stateValue, state, event);
}
// hierarchical node
if (utils_1.keys(stateValue).length === 1) {
return this.transitionCompoundNode(stateValue, state, event);
}
// orthogonal node
return this.transitionParallelNode(stateValue, state, event);
};
StateNode.prototype.next = function (state, eventObject) {
var _this = this;
var e_2, _a;
var eventType = eventObject.type;
var candidates = this.on[eventType];
if (!candidates || !candidates.length) {
return {
tree: undefined,
transitions: [],
configuration: [],
source: state,
actions: []
};
}
var actions = this._transient
? [{ type: actionTypes.nullEvent }]
: [];
var nextStateStrings = [];
var selectedTransition;
try {
for (var candidates_1 = __values(candidates), candidates_1_1 = candidates_1.next(); !candidates_1_1.done; candidates_1_1 = candidates_1.next()) {
var candidate = candidates_1_1.value;
var cond = candidate.cond, stateIn = candidate.in;
var resolvedContext = state.context;
var isInState = stateIn
? utils_1.isString(stateIn) && isStateId(stateIn)
? // Check if in state by ID
state.matches(utils_1.toStateValue(this.getStateNodeById(stateIn).path, this.delimiter))
: // Check if in state by relative grandparent
utils_2.matchesState(utils_1.toStateValue(stateIn, this.delimiter), utils_1.path(this.path.slice(0, -2))(state.value))
: true;
var guardPassed = false;
try {
guardPassed =
!cond ||
this.evaluateGuard(cond, resolvedContext, eventObject, state);
}
catch (err) {
throw new Error("Unable to evaluate guard '" + (cond.name ||
cond
.type) + "' in transition for event '" + eventType + "' in state node '" + this.id + "':\n" + err.message);
}
if (guardPassed && isInState) {
if (candidate.target !== undefined) {
nextStateStrings = candidate.target;
}
actions.push.apply(actions, __spread(utils_1.toArray(candidate.actions)));
selectedTransition = candidate;
break;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (candidates_1_1 && !candidates_1_1.done && (_a = candidates_1.return)) _a.call(candidates_1);
}
finally { if (e_2) throw e_2.error; }
}
if (!nextStateStrings.length) {
return {
tree: selectedTransition && state.value // targetless transition
? new StateTree_1.StateTree(this, utils_1.path(this.path)(state.value)).absolute
: undefined,
transitions: [selectedTransition],
configuration: selectedTransition && state.value ? [this] : [],
source: state,
actions: actions
};
}
var nextStateNodes = utils_1.flatten(nextStateStrings.map(function (str) {
if (str instanceof StateNode) {
return str; // TODO: fix anys
}
return _this.getRelativeStateNodes(str, state.historyValue);
}));
var isInternal = !!selectedTransition.internal;
var reentryNodes = isInternal
? []
: utils_1.flatten(nextStateNodes.map(function (n) { return _this.nodesFromChild(n); }));
var trees = nextStateNodes.map(function (stateNode) { return stateNode.tree; });
var combinedTree = trees.reduce(function (acc, t) {
return acc.combine(t);
});
reentryNodes.forEach(function (reentryNode) {
return combinedTree.addReentryNode(reentryNode);
});
return {
tree: combinedTree,
transitions: [selectedTransition],
configuration: nextStateNodes,
source: state,
actions: actions
};
};
Object.defineProperty(StateNode.prototype, "tree", {
/**
* The state tree represented by this state node.
*/
get: function () {
var stateValue = utils_1.toStateValue(this.path, this.delimiter);
return new StateTree_1.StateTree(this.machine, stateValue);
},
enumerable: true,
configurable: true
});
StateNode.prototype.nodesFromChild = function (childStateNode) {
if (childStateNode.escapes(this)) {
return [];
}
var nodes = [];
var marker = childStateNode;
while (marker && marker !== this) {
nodes.push(marker);
marker = marker.parent;
}
nodes.push(this); // inclusive
return nodes;
};
StateNode.prototype.getStateTree = function (stateValue) {
return new StateTree_1.StateTree(this, stateValue);
};
/**
* Whether the given state node "escapes" this state node. If the `stateNode` is equal to or the parent of
* this state node, it does not escape.
*/
StateNode.prototype.escapes = function (stateNode) {
if (this === stateNode) {
return false;
}
var parent = this.parent;
while (parent) {
if (parent === stateNode) {
return false;
}
parent = parent.parent;
}
return true;
};
StateNode.prototype.evaluateGuard = function (guard, context, eventObject, state) {
var condFn;
var guards = this.machine.options.guards;
var guardMeta = {
state: state,
cond: guard
};
// TODO: do not hardcode!
if (guard.type === constants_1.DEFAULT_GUARD_TYPE) {
return guard.predicate(context, eventObject, guardMeta);
}
if (!guards[guard.type]) {
throw new Error("Guard '" + guard.type + "' is not implemented on machine '" + this.machine.id + "'.");
}
condFn = guards[guard.type];
return condFn(context, eventObject, guardMeta);
};
StateNode.prototype.getActions = function (transition, prevState) {
var entryExitStates = transition.tree
? transition.tree.resolved.getEntryExitStates(prevState ? this.getStateTree(prevState.value) : undefined)
: { entry: [], exit: [] };
var doneEvents = transition.tree
? transition.tree.getDoneEvents(new Set(entryExitStates.entry))
: [];
if (!transition.source) {
entryExitStates.exit = [];
// Ensure that root StateNode (machine) is entered
entryExitStates.entry.unshift(this);
}
var entryStates = new Set(entryExitStates.entry);
var exitStates = new Set(entryExitStates.exit);
var _a = __read([
utils_1.flatten(Array.from(entryStates).map(function (stateNode) {
return __spread(stateNode.activities.map(function (activity) { return actions_1.start(activity); }), stateNode.onEntry);
})).concat(doneEvents.map(actions_1.raise)),
utils_1.flatten(Array.from(exitStates).map(function (stateNode) { return __spread(stateNode.onExit, stateNode.activities.map(function (activity) { return actions_1.stop(activity); })); }))
], 2), entryActions = _a[0], exitActions = _a[1];
var actions = actions_1.toActionObjects(exitActions.concat(transition.actions).concat(entryActions), this.machine.options.actions);
return actions;
};
/**
* Determines the next state given the current `state` and sent `event`.
*
* @param state The current State instance or state value
* @param event The event that was sent at the current state
* @param context The current context (extended state) of the current state
*/
StateNode.prototype.transition = function (state, event, context) {
var currentState;
if (state instanceof State_1.State) {
currentState =
context === undefined
? state
: this.resolveState(State_1.State.from(state, context));
}
else {
var resolvedStateValue = utils_1.isString(state)
? this.resolve(utils_1.pathToStateValue(this.getResolvedPath(state)))
: this.resolve(state);
var resolvedContext = context ? context : this.machine.context;
currentState = this.resolveState(State_1.State.from(resolvedStateValue, resolvedContext));
}
var eventObject = actions_1.toEventObject(event);
var eventType = eventObject.type;
if (this.strict) {
if (this.events.indexOf(eventType) === -1 && !utils_1.isBuiltInEvent(eventType)) {
throw new Error("Machine '" + this.id + "' does not accept event '" + eventType + "'");
}
}
var stateTransition = this._transition(currentState.value, currentState, eventObject);
if (stateTransition.tree) {
stateTransition.tree = stateTransition.tree.resolved;
}
// const prevConfig = this.machine.getStateNodes(currentState.value);
// const cv = getValue(
// this.machine,
// getConfiguration(prevConfig, stateTransition.configuration)
// );
// if (stateTransition.tree) {
// const eq = stateValuesEqual(cv, stateTransition.tree.value);
// console.log(eq);
// }
// if (!eq) {
// console.log('prevConfig', prevConfig.map(c => c.id));
// console.log('config', [...stateTransition.configuration].map(c => c.id));
// console.log(cv, stateTransition.tree!.value);
// }
return this.resolveTransition(stateTransition, currentState, eventObject);
};
StateNode.prototype.resolveTransition = function (stateTransition, currentState, _eventObject) {
var _this = this;
var e_3, _a, _b;
var resolvedStateValue = stateTransition.tree
? stateTransition.tree.value
: undefined;
var historyValue = currentState
? currentState.historyValue
? currentState.historyValue
: stateTransition.source
? this.machine.historyValue(currentState.value)
: undefined
: undefined;
var currentContext = currentState
? currentState.context
: stateTransition.context || this.machine.context;
var eventObject = _eventObject || { type: types_1.ActionTypes.Init };
if (!environment_1.IS_PRODUCTION && stateTransition.tree) {
try {
this.ensureValidPaths(stateTransition.tree.paths); // TODO: ensure code coverage for this
}
catch (e) {
throw new Error("Event '" + (eventObject ? eventObject.type : 'none') + "' leads to an invalid configuration: " + e.message);
}
}
var actions = this.getActions(stateTransition, currentState);
var activities = currentState ? __assign({}, currentState.activities) : {};
try {
for (var actions_2 = __values(actions), actions_2_1 = actions_2.next(); !actions_2_1.done; actions_2_1 = actions_2.next()) {
var action = actions_2_1.value;
if (action.type === actionTypes.start) {
activities[action.activity.type] = action;
}
else if (action.type === actionTypes.stop) {
activities[action.activity.type] = false;
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (actions_2_1 && !actions_2_1.done && (_a = actions_2.return)) _a.call(actions_2);
}
finally { if (e_3) throw e_3.error; }
}
var _c = __read(utils_1.partition(actions, function (action) {
return action.type === actionTypes.raise ||
action.type === actionTypes.nullEvent;
}), 2), raisedEvents = _c[0], otherActions = _c[1];
var _d = __read(utils_1.partition(otherActions, function (action) {
return action.type === actionTypes.assign;
}), 2), assignActions = _d[0], nonEventActions = _d[1];
var updatedContext = assignActions.length
? this.options.updater(currentContext, eventObject, assignActions)
: currentContext;
var resolvedActions = utils_1.flatten(nonEventActions.map(function (actionObject) {
if (actionObject.type === actionTypes.send) {
var sendAction = actions_1.resolveSend(actionObject, updatedContext, eventObject || { type: types_1.ActionTypes.Init }); // TODO: fix ActionTypes.Init
if (utils_1.isString(sendAction.delay)) {
if (!_this.machine.options.delays ||
_this.machine.options.delays[sendAction.delay] === undefined) {
if (!environment_1.IS_PRODUCTION) {
utils_1.warn(false,
// tslint:disable-next-line:max-line-length
"No delay reference for delay expression '" + sendAction.delay + "' was found on machine '" + _this.machine.id + "'");
}
// Do not send anything
return sendAction;
}
var delayExpr = _this.machine.options.delays[sendAction.delay];
sendAction.delay =
typeof delayExpr === 'number'
? delayExpr
: delayExpr(updatedContext, eventObject || { type: types_1.ActionTypes.Init });
}
return sendAction;
}
if (actionObject.type === types_1.ActionTypes.Pure) {
return (actionObject.get(updatedContext, eventObject) || []);
}
return actions_1.toActionObject(actionObject, _this.options.actions);
}));
var stateNodes = resolvedStateValue
? this.getStateNodes(resolvedStateValue)
: [];
var isTransient = stateNodes.some(function (stateNode) { return stateNode._transient; });
if (isTransient) {
raisedEvents.push({ type: actionTypes.nullEvent });
}
var meta = __spread([this], stateNodes).reduce(function (acc, stateNode) {
if (stateNode.meta !== undefined) {
acc[stateNode.id] = stateNode.meta;
}
return acc;
}, {});
var nextState = new State_1.State({
value: resolvedStateValue || currentState.value,
context: updatedContext,
event: eventObject || actions_1.initEvent,
historyValue: resolvedStateValue
? historyValue
? utils_1.updateHistoryValue(historyValue, resolvedStateValue)
: undefined
: currentState
? currentState.historyValue
: undefined,
history: !resolvedStateValue || stateTransition.source
? currentState
: undefined,
actions: resolvedStateValue ? resolvedActions : [],
activities: resolvedStateValue
? activities
: currentState
? currentState.activities
: {},
meta: resolvedStateValue
? meta
: currentState
? currentState.meta
: undefined,
events: resolvedStateValue ? raisedEvents : [],
tree: resolvedStateValue
? stateTransition.tree
: currentState
? currentState.tree
: undefined
});
nextState.changed =
eventObject.type === actionTypes.update || !!assignActions.length;
// Dispose of penultimate histories to prevent memory leaks
var history = nextState.history;
if (history) {
delete history.history;
}
if (!resolvedStateValue) {
return nextState;
}
var maybeNextState = nextState;
while (raisedEvents.length) {
var currentActions = maybeNextState.actions;
var raisedEvent = raisedEvents.shift();
maybeNextState = this.transition(maybeNextState, raisedEvent.type === actionTypes.nullEvent
? NULL_EVENT
: raisedEvent.event, maybeNextState.context);
// Save original event to state
maybeNextState.event = eventObject;
(_b = maybeNextState.actions).unshift.apply(_b, __spread(currentActions));
}
// Detect if state changed
var changed = maybeNextState.changed ||
(history
? !!maybeNextState.actions.length ||
!!assignActions.length ||
typeof history.value !== typeof maybeNextState.value ||
!State_1.stateValuesEqual(maybeNextState.value, history.value)
: undefined);
maybeNextState.changed = changed;
// Preserve original history after raised events
maybeNextState.historyValue = nextState.historyValue;
maybeNextState.history = history;
return maybeNextState;
};
StateNode.prototype.ensureValidPaths = function (paths) {
var _this = this;
var e_4, _a;
if (!environment_1.IS_PRODUCTION) {
var visitedParents = new Map();
var stateNodes = utils_1.flatten(paths.map(function (_path) { return _this.getRelativeStateNodes(_path); }));
try {
outer: for (var stateNodes_1 = __values(stateNodes), stateNodes_1_1 = stateNodes_1.next(); !stateNodes_1_1.done; stateNodes_1_1 = stateNodes_1.next()) {
var stateNode = stateNodes_1_1.value;
var marker = stateNode;
while (marker.parent) {
if (visitedParents.has(marker.parent)) {
if (marker.parent.type === 'parallel') {
continue outer;
}
throw new Error("State node '" + stateNode.id + "' shares parent '" + marker.parent.id + "' with state node '" + visitedParents
.get(marker.parent)
.map(function (a) { return a.id; }) + "'");
}
if (!visitedParents.get(marker.parent)) {
visitedParents.set(marker.parent, [stateNode]);
}
else {
visitedParents.get(marker.parent).push(stateNode);
}
marker = marker.parent;
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (stateNodes_1_1 && !stateNodes_1_1.done && (_a = stateNodes_1.return)) _a.call(stateNodes_1);
}
finally { if (e_4) throw e_4.error; }
}
}
else {
return;
}
};
/**
* Returns the child state node from its relative `stateKey`, or throws.
*/
StateNode.prototype.getStateNode = function (stateKey) {
if (isStateId(stateKey)) {
return this.machine.getStateNodeById(stateKey);
}
if (!this.states) {
throw new Error("Unable to retrieve child state '" + stateKey + "' from '" + this.id + "'; no child states exist.");
}
var result = this.states[stateKey];
if (!result) {
throw new Error("Child state '" + stateKey + "' does not exist on '" + this.id + "'");
}
return result;
};
/**
* Returns the state node with the given `stateId`, or throws.
*
* @param stateId The state ID. The prefix "#" is removed.
*/
StateNode.prototype.getStateNodeById = function (stateId) {
var resolvedStateId = isStateId(stateId)
? stateId.slice(STATE_IDENTIFIER.length)
: stateId;
if (resolvedStateId === this.id) {
return this;
}
var stateNode = this.machine.idMap[resolvedStateId];
if (!stateNode) {
throw new Error("Child state node '#" + resolvedStateId + "' does not exist on machine '" + this.id + "'");
}
return stateNode;
};
/**
* Returns the relative state node from the given `statePath`, or throws.
*
* @param statePath The string or string array relative path to the state node.
*/
StateNode.prototype.getStateNodeByPath = function (statePath) {
if (typeof statePath === 'string' && isStateId(statePath)) {
try {
return this.getStateNodeById(statePath.slice(1));
}
catch (e) {
// try individual paths
// throw e;
}
}
var arrayStatePath = utils_1.toStatePath(statePath, this.delimiter).slice();
var currentStateNode = this;
while (arrayStatePath.length) {
var key = arrayStatePath.shift();
if (!key.length) {
break;
}
currentStateNode = currentStateNode.getStateNode(key);
}
return currentStateNode;
};
/**
* Resolves a partial state value with its full representation in this machine.
*
* @param stateValue The partial state value to resolve.
*/
StateNode.prototype.resolve = function (stateValue) {
var _this = this;
var _a;
if (!stateValue) {
return this.initialStateValue || EMPTY_OBJECT; // TODO: type-specific properties
}
switch (this.type) {
case 'parallel':
return utils_1.mapValues(this.initialStateValue, function (subStateValue, subStateKey) {
return subStateValue
? _this.getStateNode(subStateKey).resolve(stateValue[subStateKey] || subStateValue)
: EMPTY_OBJECT;
});
case 'compound':
if (utils_1.isString(stateValue)) {
var subStateNode = this.getStateNode(stateValue);
if (subStateNode.type === 'parallel' ||
subStateNode.type === 'compound') {
return _a = {}, _a[stateValue] = subStateNode.initialStateValue, _a;
}
return stateValue;
}
if (!utils_1.keys(stateValue).length) {
return this.initialStateValue || {};
}
return utils_1.mapValues(stateValue, function (subStateValue, subStateKey) {
return subStateValue
? _this.getStateNode(subStateKey).resolve(subStateValue)
: EMPTY_OBJECT;
});
default:
return stateValue || EMPTY_OBJECT;
}
};
Object.defineProperty(StateNode.prototype, "resolvedStateValue", {
get: function () {
var _a, _b;
var key = this.key;
if (this.type === 'parallel') {
return _a = {},
_a[key] = utils_1.mapFilterValues(this.states, function (stateNode) { return stateNode.resolvedStateValue[stateNode.key]; }, function (stateNode) { return !(stateNode.type === 'history'); }),
_a;
}
if (this.initial === undefined) {
// If leaf node, value is just the state node's key
return key;
}
if (!this.states[this.initial]) {
throw new Error("Initial state '" + this.initial + "' not found on '" + key + "'");
}
return _b = {},
_b[key] = this.states[this.initial].resolvedStateValue,
_b;
},
enumerable: true,
configurable: true
});
StateNode.prototype.getResolvedPath = function (stateIdentifier) {
if (isStateId(stateIdentifier)) {
var stateNode = this.machine.idMap[stateIdentifier.slice(STATE_IDENTIFIER.length)];
if (!stateNode) {
throw new Error("Unable to find state node '" + stateIdentifier + "'");
}
return stateNode.path;
}
return utils_1.toStatePath(stateIdentifier, this.delimiter);
};
Object.defineProperty(StateNode.prototype, "initialStateValue", {
get: function () {
if (this.__cache.initialStateValue) {
return this.__cache.initialStateValue;
}
var initialStateValue = (this.type === 'parallel'
? utils_1.mapFilterValues(this.states, function (state) { return state.initialStateValue || EMPTY_OBJECT; }, function (stateNode) { return !(stateNode.type === 'history'); })
: utils_1.isString(this.resolvedStateValue)
? undefined
: this.resolvedStateValue[this.key]);
this.__cache.initialStateValue = initialStateValue;
return this.__cache.initialStateValue;
},
enumerable: true,
configurable: true
});
StateNode.prototype.getInitialState = function (stateValue, context) {
if (context === void 0) { context = this.machine.context; }
var tree = this.getStateTree(stateValue);
var configuration = this.getStateNodes(stateValue);
configuration.forEach(function (stateNode) {
tree.addReentryNode(stateNode);
});
return this.resolveTransition({
tree: tree,
configuration: configuration,
transitions: [],
source: undefined,
actions: [],
context: context
});
};
Object.defineProperty(StateNode.prototype, "initialState", {
/**
* The initial State instance, which includes all actions to be executed from
* entering the initial state.
*/
get: function () {
if (this.__cache.initialState) {
return this.__cache.initialState;
}
var initialStateValue = this.initialStateValue;
if (!initialStateValue) {
throw new Error("Cannot retrieve initial state from simple state '" + this.id + "'.");
}
this.__cache.initialState = this.getInitialState(initialStateValue);
return this.__cache.initialState;
},
enumerable: true,
configurable: true
});
Object.defineProperty(StateNode.prototype, "target", {
/**
* The target state value of the history state node, if it exists. This represents the
* default state value to transition to if no history value exists yet.
*/
get: function () {
var target;
if (this.type === 'history') {
var historyConfig = this.config;
if (historyConfig.target && utils_1.isString(historyConfig.target)) {
target = isStateId(historyConfig.target)
? utils_1.pathToStateValue(this.machine
.getStateNodeById(historyConfig.target)
.path.slice(this.path.length - 1))
: historyConfig.target;
}
else {
target = historyConfig.target;
}
}
return target;
},
enumerable: true,
configurable: true
});
StateNode.prototype.getStates = function (stateValue) {
var e_5, _a;
if (utils_1.isString(stateValue)) {
return [this.states[stateValue]];
}
var stateNodes = [];
try {
for (var _b = __values(utils_1.keys(stateValue)), _c = _b.next(); !_c.done; _c = _b.next()) {
var key = _c.value;
stateNodes.push.apply(stateNodes, __spread(this.states[key].getStates(stateValue[key])));
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_5) throw e_5.error; }
}
return stateNodes;
};
/**
* Returns the leaf nodes from a state path relative to this state node.
*
* @param relativeStateId The relative state path to retrieve the state nodes
* @param history The previous state to retrieve history
* @param resolve Whether state nodes should resolve to initial child state nodes
*/
StateNode.prototype.getRelativeStateNodes = function (relativeStateId, historyValue, resolve) {
if (resolve === void 0) { resolve = true; }
if (utils_1.isString(relativeStateId) && isStateId(relativeStateId)) {
var unresolvedStateNode = this.getStateNodeById(relativeStateId);
return resolve
? unresolvedStateNode.type === 'history'
? unresolvedStateNode.resolveHistory(historyValue)
: unresolvedStateNode.initialStateNodes
: [unresolvedStateNode];
}
var statePath = utils_1.toStatePath(relativeStateId, this.delimiter);
var rootStateNode = this.parent || this;
var unresolvedStateNodes = rootStateNode.getFromRelativePath(statePath, historyValue);
if (!resolve) {
return unresolvedStateNodes;
}
return utils_1.flatten(unresolvedStateNodes.map(function (stateNode) { return stateNode.initialStateNodes; }));
};
Object.defineProperty(StateNode.prototype, "initialStateNodes", {
get: function () {
var _this = this;
if (this.type === 'atomic' || this.type === 'final') {
return [this];
}
// Case when state node is compound but no initial state is defined
if (this.type === 'compound' && !this.initial) {
if (!environment_1.IS_PRODUCTION) {
utils_1.warn(false, "Compound state node '" + this.id + "' has no initial state.");
}
return [this];
}
var initialStateNodePaths = utils_1.toStatePaths(this.initialStateValue);
return utils_1.flatten(initialStateNodePaths.map(function (initialPath) {
return _this.getFromRelativePath(initialPath);
}));
},
enumerable: true,
configurable: true
});
/**
* Retrieves state nodes from a relative path to this state node.
*
* @param relativePath The relative path from this state node
* @param historyValue
*/
StateNode.prototype.getFromRelativePath = function (relativePath, historyValue) {
if (!relativePath.length) {
return [this];
}
var _a = __read(relativePath), stateKey = _a[0], childStatePath = _a.slice(1);
if (!this.states) {
throw new Error("Cannot retrieve subPath '" + stateKey + "' from node with no states");
}
var childStateNode = this.getStateNode(stateKey);
if (childStateNode.type === 'history') {
return childStateNode.resolveHistory(historyValue);
}
if (!this.states[stateKey]) {
throw new Error("Child state '" + stateKey + "' does not exist on '" + this.id + "'");
}
return this.states[stateKey].getFromRelativePath(childStatePath, historyValue);
};
StateNode.prototype.historyValue = function (relativeStateValue) {
if (!utils_1.keys(this.states).length) {
return undefined;
}
return {
current: relativeStateValue || this.initialStateValue,
states: utils_1.mapFilterValues(this.states, function (stateNode, key) {
if (!relativeStateValue) {
return stateNode.historyValue();
}
var subStateValue = utils_1.isString(relativeStateValue)
? undefined
: relativeStateValue[key];
return stateNode.historyValue(subStateValue || stateNode.initialStateValue);
}, function (stateNode) { return !stateNode.history; })
};
};
/**
* Resolves to the historical value(s) of the parent state node,
* represented by state nodes.
*
* @param historyValue
*/
StateNode.prototype.resolveHistory = function (historyValue) {
var _this = this;
if (this.type !== 'history') {
return [this];
}
var parent = this.parent;
if (!historyValue) {
return this.target
? utils_1.flatten(utils_1.toStatePaths(this.target).map(function (relativeChildPath) {
return parent.getFromRelativePath(relativeChildPath);
}))
: parent.initialStateNodes;
}
var subHistoryValue = utils_1.nestedPath(parent.path, 'states')(historyValue).current;
if (utils_1.isString(subHistoryValue)) {
return [parent.getStateNode(subHistoryValue)];
}
return utils_1.flatten(utils_1.toStatePaths(subHistoryValue).map(function (subStatePath) {
return _this.history === 'deep'
? parent.getFromRelativePath(subStatePath)
: [parent.states[subStatePath[0]]];
}));
};
Object.defineProperty(StateNode.prototype, "stateIds", {
/**
* All the state node IDs of this state node and its descendant state nodes.
*/
get: function () {
var _this = this;
var childStateIds = utils_1.flatten(utils_1.keys(this.states).map(function (stateKey) {
return _this.states[stateKey].stateIds;
}));
return [this.id].concat(childStateIds);
},
enumerable: true,
configurable: true
});
Object.defineProperty(StateNode.prototype, "events", {
/**
* All the event types accepted by this state node and its descendants.
*/
get: function () {
var e_6, _a, e_7, _b;
if (this.__cache.events) {
return this.__cache.events;
}
var states = this.states;
var events = new Set(this.ownEvents);
if (states) {
try {
for (var _c = __values(utils_1.keys(states)), _d = _c.next(); !_d.done; _d = _c.next()) {
var stateId = _d.value;
var state = states[stateId];
if (state.states) {
try {
for (var _e = __values(state.events), _f = _e.next(); !_f.done; _f = _e.next()) {
var event_1 = _f.value;
events.add("" + event_1);
}
}
catch (e_7_1) { e_7 = { error: e_7_1 }; }
finally {
try {
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
}
finally { if (e_7) throw e_7.error; }
}
}
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
}
finally { if (e_6) throw e_6.error; }
}
}
return (this.__cache.events = Array.from(events));
},
enumerable: true,
configurable: true
});
Object.defineProperty(StateNode.prototype, "ownEvents", {
/**
* All the events that have transitions directly from this state node.
*
* Excludes any inert events.
*/
get: function () {
var _this = this;
var events = new Set(utils_1.keys(this.on).filter(function (key) {
var transitions = _this.on[key];
return transitions.some(function (transition) {
return !(!transition.target &&
!transition.actions.length &&
transition.internal);
});
}));
return Array.from(events);
},
enumerable: true,
configurable: true
});
StateNode.prototype.formatTransition = function (target, transitionConfig, event) {
var _this = this;
var internal = transitionConfig ? transitionConfig.internal : undefined;
var targets = utils_1.toArray(target);
var guards = this.machine.options.guards;
// Format targets to their full string path
var formattedTargets = targets.map(function (_target) {
if (!utils_1.isString(_target)) {
return "#" + _target.id;
}
var isInternalTarget = _target[0] === _this.delimiter;
internal = internal === undefined ? isInternalTarget : internal;
// If internal target is defined on machine,
// do not include machine key on target
if (isInternalTarget && !_this.parent) {
return "#" + _this.getStateNodeByPath(_target.slice(1)).id;
}
var resolvedTarget = isInternalTarget
? _this.key + _target
: "" + _target;
if (_this.parent) {
try {
var targetStateNode = _this.parent.getStateNodeByPath(resolvedTarget);
return "#" + targetStateNode.id;
}
catch (err) {
throw new Error("Invalid transition for state node '" + _this.id + "' on event '" + event + "':\n" + err.message);
}
}
else {
return "#" + _this.getStateNodeByPath(resolvedTarget).id;
}
});
if (transitionConfig === undefined) {
return {
target: target === undefined ? undefined : formattedTargets,
source: this,
actions: [],
internal: target === undefined || internal,
event: event
};
}
// Check if there is no target (targetless)
// An undefined transition signals that the state node should not transition from that event.
var isTargetless = target === undefined || target === TARGETLESS_KEY;
return __assign({}, transitionConfig, { actions: actions_1.toActionObjects(utils_1.toArray(transitionConfig.actions)), cond: utils_1.toGuard(transitionConfig.cond, guards), target: isTargetless ? undefined : formattedTargets, source: this, internal: (isTargetless && internal === undefined) || internal, event: event });
};
StateNode.prototype.formatTransitions = function () {
var _this = this;
var _a, e_8, _b;
var onConfig = this.config.on || EMPTY_OBJECT;
var doneConfig = this.config.onDone
? (_a = {},
_a["" + actions_1.done(this.id)] = this.config.onDone,
_a) : undefined;
var invokeConfig = this.invoke.reduce(function (acc, invokeDef) {
if (invokeDef.onDone) {
acc[actions_1.doneInvoke(invokeDef.id)] = invokeDef.onDone;
}
if (invokeDef.onError) {
acc[actions_1.error(invokeDef.id)] = invokeDef.onError;
}
return acc;
}, {});
var delayedTransitions = this.after;
var formattedTransitions = utils_1.mapValues(__assign({}, onConfig, doneConfig, invokeConfig), function (value, event) {
var e_9, _a;
if (value === undefined) {
return [{ target: undefined, event: event, actions: [], internal: true }];
}
if (utils_1.isArray(value)) {
return value.map(function (targetTransitionConfig) {
return _this.formatTransition(targetTransitionConfig.target, targetTransitionConfig, event);
});
}
if (utils_1.isString(value) || utils_1.isMachine(value)) {
return [_this.formatTransition([value], undefined, event)];
}
if (!environment_1.IS_PRODUCTION) {
try {
for (var _b = __values(utils_1.keys(value)), _c = _b.next(); !_c.done; _c = _b.next()) {
var key = _c.value;
if (['target', 'actions', 'internal', 'in', 'cond', 'event'].indexOf(key) === -1) {
throw new Error(
// tslint:disable-next-line:max-line-length
"State object mapping of transitions is deprecated. Check the config for event '" + event + "' on state '" + _this.id + "'.");
}
}
}
catch (e_9_1) { e_9 = { error: e_9_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_9) throw e_9.error; }
}
}
return [
_this.formatTransition(value.target, value, event)
];
});
try {
for (var delayedTransitions_1 = __values(delayedTransitions), delayedTransitions_1_1 = delayedTransitions_1.next(); !delayedTransitions_1_1.done; delayedTransitions_1_1 = delayedTransitions_1.next()) {
var delayedTransition = delayedTransitions_1_1.value;
formattedTransitions[delayedTransition.event] =
formattedTransitions[delayedTransition.event] || [];
formattedTransitions[delayedTransition.event].push(delayedTransition);
}
}
catch (e_8_1) { e_8 = { error: e_8_1 }; }
finally {
try {
if (delayedTransitions_1_1 && !delayedTransitions_1_1.done && (_b = delayedTransitions_1.return)) _b.call(delayedTransitions_1);
}
finally { if (e_8) throw e_8.error; }
}
return formattedTransitions;
};
return StateNode;
}());
exports.StateNode = StateNode;
//# sourceMappingURL=StateNode.js.map