xstate
Version:
Finite State Machines and Statecharts for the Modern Web.
655 lines (570 loc) • 19.8 kB
JavaScript
import { __assign, __spreadArray, __read, __values } from './_virtual/_tslib.js';
import { SpecialTargets, ActionTypes } from './types.js';
import { init, raise as raise$1, send as send$1, update, log as log$1, cancel as cancel$1, assign as assign$1, error as error$1, stop as stop$1, pure as pure$1, choose as choose$1 } from './actionTypes.js';
import * as actionTypes from './actionTypes.js';
export { actionTypes };
import { toSCXMLEvent, isString, isFunction, toEventObject, getEventType, updateContext, flatten, isArray, toArray, toGuard, evaluateGuard, warn } from './utils.js';
import { IS_PRODUCTION } from './environment.js';
var initEvent = /*#__PURE__*/toSCXMLEvent({
type: init
});
function getActionFunction(actionType, actionFunctionMap) {
return actionFunctionMap ? actionFunctionMap[actionType] || undefined : undefined;
}
function toActionObject(action, actionFunctionMap) {
var actionObject;
if (isString(action) || typeof action === 'number') {
var exec = getActionFunction(action, actionFunctionMap);
if (isFunction(exec)) {
actionObject = {
type: action,
exec: exec
};
} else if (exec) {
actionObject = exec;
} else {
actionObject = {
type: action,
exec: undefined
};
}
} else if (isFunction(action)) {
actionObject = {
// Convert action to string if unnamed
type: action.name || action.toString(),
exec: action
};
} else {
var exec = getActionFunction(action.type, actionFunctionMap);
if (isFunction(exec)) {
actionObject = __assign(__assign({}, action), {
exec: exec
});
} else if (exec) {
var actionType = exec.type || action.type;
actionObject = __assign(__assign(__assign({}, exec), action), {
type: actionType
});
} else {
actionObject = action;
}
}
return actionObject;
}
var toActionObjects = function (action, actionFunctionMap) {
if (!action) {
return [];
}
var actions = isArray(action) ? action : [action];
return actions.map(function (subAction) {
return toActionObject(subAction, actionFunctionMap);
});
};
function toActivityDefinition(action) {
var actionObject = toActionObject(action);
return __assign(__assign({
id: isString(action) ? action : actionObject.id
}, actionObject), {
type: actionObject.type
});
}
/**
* Raises an event. This places the event in the internal event queue, so that
* the event is immediately consumed by the machine in the current step.
*
* @param eventType The event to raise.
*/
function raise(event, options) {
return {
type: raise$1,
event: typeof event === 'function' ? event : toEventObject(event),
delay: options ? options.delay : undefined,
id: options === null || options === void 0 ? void 0 : options.id
};
}
function resolveRaise(action, ctx, _event, delaysMap) {
var meta = {
_event: _event
};
var resolvedEvent = toSCXMLEvent(isFunction(action.event) ? action.event(ctx, _event.data, meta) : action.event);
var resolvedDelay;
if (isString(action.delay)) {
var configDelay = delaysMap && delaysMap[action.delay];
resolvedDelay = isFunction(configDelay) ? configDelay(ctx, _event.data, meta) : configDelay;
} else {
resolvedDelay = isFunction(action.delay) ? action.delay(ctx, _event.data, meta) : action.delay;
}
return __assign(__assign({}, action), {
type: raise$1,
_event: resolvedEvent,
delay: resolvedDelay
});
}
/**
* Sends an event. This returns an action that will be read by an interpreter to
* send the event in the next step, after the current step is finished executing.
*
* @deprecated Use the `sendTo(...)` action creator instead.
*
* @param event The event to send.
* @param options Options to pass into the send event:
* - `id` - The unique send event identifier (used with `cancel()`).
* - `delay` - The number of milliseconds to delay the sending of the event.
* - `to` - The target of this event (by default, the machine the event was sent from).
*/
function send(event, options) {
return {
to: options ? options.to : undefined,
type: send$1,
event: isFunction(event) ? event : toEventObject(event),
delay: options ? options.delay : undefined,
// TODO: don't auto-generate IDs here like that
// there is too big chance of the ID collision
id: options && options.id !== undefined ? options.id : isFunction(event) ? event.name : getEventType(event)
};
}
function resolveSend(action, ctx, _event, delaysMap) {
var meta = {
_event: _event
}; // TODO: helper function for resolving Expr
var resolvedEvent = toSCXMLEvent(isFunction(action.event) ? action.event(ctx, _event.data, meta) : action.event);
var resolvedDelay;
if (isString(action.delay)) {
var configDelay = delaysMap && delaysMap[action.delay];
resolvedDelay = isFunction(configDelay) ? configDelay(ctx, _event.data, meta) : configDelay;
} else {
resolvedDelay = isFunction(action.delay) ? action.delay(ctx, _event.data, meta) : action.delay;
}
var resolvedTarget = isFunction(action.to) ? action.to(ctx, _event.data, meta) : action.to;
return __assign(__assign({}, action), {
to: resolvedTarget,
_event: resolvedEvent,
event: resolvedEvent.data,
delay: resolvedDelay
});
}
/**
* Sends an event to this machine's parent.
*
* @param event The event to send to the parent machine.
* @param options Options to pass into the send event.
*/
function sendParent(event, options) {
return send(event, __assign(__assign({}, options), {
to: SpecialTargets.Parent
}));
}
/**
* Sends an event to an actor.
*
* @param actor The `ActorRef` to send the event to.
* @param event The event to send, or an expression that evaluates to the event to send
* @param options Send action options
* @returns An XState send action object
*/
function sendTo(actor, event, options) {
return send(event, __assign(__assign({}, options), {
to: actor
}));
}
/**
* Sends an update event to this machine's parent.
*/
function sendUpdate() {
return sendParent(update);
}
/**
* Sends an event back to the sender of the original event.
*
* @param event The event to send back to the sender
* @param options Options to pass into the send event
*/
function respond(event, options) {
return send(event, __assign(__assign({}, options), {
to: function (_, __, _a) {
var _event = _a._event;
return _event.origin; // TODO: handle when _event.origin is undefined
}
}));
}
var defaultLogExpr = function (context, event) {
return {
context: context,
event: event
};
};
/**
*
* @param expr The expression function to evaluate which will be logged.
* Takes in 2 arguments:
* - `ctx` - the current state context
* - `event` - the event that caused this action to be executed.
* @param label The label to give to the logged expression.
*/
function log(expr, label) {
if (expr === void 0) {
expr = defaultLogExpr;
}
return {
type: log$1,
label: label,
expr: expr
};
}
var resolveLog = function (action, ctx, _event) {
return __assign(__assign({}, action), {
value: isString(action.expr) ? action.expr : action.expr(ctx, _event.data, {
_event: _event
})
});
};
/**
* Cancels an in-flight `send(...)` action. A canceled sent action will not
* be executed, nor will its event be sent, unless it has already been sent
* (e.g., if `cancel(...)` is called after the `send(...)` action's `delay`).
*
* @param sendId The `id` of the `send(...)` action to cancel.
*/
var cancel = function (sendId) {
return {
type: cancel$1,
sendId: sendId
};
};
/**
* Starts an activity.
*
* @param activity The activity to start.
*/
function start(activity) {
var activityDef = toActivityDefinition(activity);
return {
type: ActionTypes.Start,
activity: activityDef,
exec: undefined
};
}
/**
* Stops an activity.
*
* @param actorRef The activity to stop.
*/
function stop(actorRef) {
var activity = isFunction(actorRef) ? actorRef : toActivityDefinition(actorRef);
return {
type: ActionTypes.Stop,
activity: activity,
exec: undefined
};
}
function resolveStop(action, context, _event) {
var actorRefOrString = isFunction(action.activity) ? action.activity(context, _event.data) : action.activity;
var resolvedActorRef = typeof actorRefOrString === 'string' ? {
id: actorRefOrString
} : actorRefOrString;
var actionObject = {
type: ActionTypes.Stop,
activity: resolvedActorRef
};
return actionObject;
}
/**
* Updates the current context of the machine.
*
* @param assignment An object that represents the partial context to update.
*/
var assign = function (assignment) {
return {
type: assign$1,
assignment: assignment
};
};
function isActionObject(action) {
return typeof action === 'object' && 'type' in action;
}
/**
* Returns an event type that represents an implicit event that
* is sent after the specified `delay`.
*
* @param delayRef The delay in milliseconds
* @param id The state node ID where this event is handled
*/
function after(delayRef, id) {
var idSuffix = id ? "#".concat(id) : '';
return "".concat(ActionTypes.After, "(").concat(delayRef, ")").concat(idSuffix);
}
/**
* Returns an event that represents that a final state node
* has been reached in the parent state node.
*
* @param id The final state node's parent state node `id`
* @param data The data to pass into the event
*/
function done(id, data) {
var type = "".concat(ActionTypes.DoneState, ".").concat(id);
var eventObject = {
type: type,
data: data
};
eventObject.toString = function () {
return type;
};
return eventObject;
}
/**
* Returns an event that represents that an invoked service has terminated.
*
* An invoked service is terminated when it has reached a top-level final state node,
* but not when it is canceled.
*
* @param id The final state node ID
* @param data The data to pass into the event
*/
function doneInvoke(id, data) {
var type = "".concat(ActionTypes.DoneInvoke, ".").concat(id);
var eventObject = {
type: type,
data: data
};
eventObject.toString = function () {
return type;
};
return eventObject;
}
function error(id, data) {
var type = "".concat(ActionTypes.ErrorPlatform, ".").concat(id);
var eventObject = {
type: type,
data: data
};
eventObject.toString = function () {
return type;
};
return eventObject;
}
function pure(getActions) {
return {
type: ActionTypes.Pure,
get: getActions
};
}
/**
* Forwards (sends) an event to a specified service.
*
* @param target The target service to forward the event to.
* @param options Options to pass into the send action creator.
*/
function forwardTo(target, options) {
if (!IS_PRODUCTION && (!target || typeof target === 'function')) {
var originalTarget_1 = target;
target = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var resolvedTarget = typeof originalTarget_1 === 'function' ? originalTarget_1.apply(void 0, __spreadArray([], __read(args), false)) : originalTarget_1;
if (!resolvedTarget) {
throw new Error("Attempted to forward event to undefined actor. This risks an infinite loop in the sender.");
}
return resolvedTarget;
};
}
return send(function (_, event) {
return event;
}, __assign(__assign({}, options), {
to: target
}));
}
/**
* Escalates an error by sending it as an event to this machine's parent.
*
* @param errorData The error data to send, or the expression function that
* takes in the `context`, `event`, and `meta`, and returns the error data to send.
* @param options Options to pass into the send action creator.
*/
function escalate(errorData, options) {
return sendParent(function (context, event, meta) {
return {
type: error$1,
data: isFunction(errorData) ? errorData(context, event, meta) : errorData
};
}, __assign(__assign({}, options), {
to: SpecialTargets.Parent
}));
}
function choose(conds) {
return {
type: ActionTypes.Choose,
conds: conds
};
}
var pluckAssigns = function (actionBlocks) {
var e_1, _a;
var assignActions = [];
try {
for (var actionBlocks_1 = __values(actionBlocks), actionBlocks_1_1 = actionBlocks_1.next(); !actionBlocks_1_1.done; actionBlocks_1_1 = actionBlocks_1.next()) {
var block = actionBlocks_1_1.value;
var i = 0;
while (i < block.actions.length) {
if (block.actions[i].type === assign$1) {
assignActions.push(block.actions[i]);
block.actions.splice(i, 1);
continue;
}
i++;
}
}
} catch (e_1_1) {
e_1 = {
error: e_1_1
};
} finally {
try {
if (actionBlocks_1_1 && !actionBlocks_1_1.done && (_a = actionBlocks_1.return)) _a.call(actionBlocks_1);
} finally {
if (e_1) throw e_1.error;
}
}
return assignActions;
};
function resolveActions(machine, currentState, currentContext, _event, actionBlocks, predictableExec, preserveActionOrder) {
if (preserveActionOrder === void 0) {
preserveActionOrder = false;
}
var assignActions = preserveActionOrder ? [] : pluckAssigns(actionBlocks);
var updatedContext = assignActions.length ? updateContext(currentContext, _event, assignActions, currentState) : currentContext;
var preservedContexts = preserveActionOrder ? [currentContext] : undefined;
var deferredToBlockEnd = [];
function handleAction(blockType, actionObject) {
var _a;
switch (actionObject.type) {
case raise$1:
{
var raisedAction = resolveRaise(actionObject, updatedContext, _event, machine.options.delays);
if (predictableExec && typeof raisedAction.delay === 'number') {
predictableExec(raisedAction, updatedContext, _event);
}
return raisedAction;
}
case send$1:
var sendAction = resolveSend(actionObject, updatedContext, _event, machine.options.delays); // TODO: fix ActionTypes.Init
if (!IS_PRODUCTION) {
var configuredDelay = actionObject.delay; // warn after resolving as we can create better contextual message here
warn(!isString(configuredDelay) || typeof sendAction.delay === 'number', // tslint:disable-next-line:max-line-length
"No delay reference for delay expression '".concat(configuredDelay, "' was found on machine '").concat(machine.id, "'"));
}
if (predictableExec && sendAction.to !== SpecialTargets.Internal) {
if (blockType === 'entry') {
deferredToBlockEnd.push(sendAction);
} else {
predictableExec(sendAction, updatedContext, _event);
}
}
return sendAction;
case log$1:
{
var resolved = resolveLog(actionObject, updatedContext, _event);
predictableExec === null || predictableExec === void 0 ? void 0 : predictableExec(resolved, updatedContext, _event);
return resolved;
}
case choose$1:
{
var chooseAction = actionObject;
var matchedActions = (_a = chooseAction.conds.find(function (condition) {
var guard = toGuard(condition.cond, machine.options.guards);
return !guard || evaluateGuard(machine, guard, updatedContext, _event, !predictableExec ? currentState : undefined);
})) === null || _a === void 0 ? void 0 : _a.actions;
if (!matchedActions) {
return [];
}
var _b = __read(resolveActions(machine, currentState, updatedContext, _event, [{
type: blockType,
actions: toActionObjects(toArray(matchedActions), machine.options.actions)
}], predictableExec, preserveActionOrder), 2),
resolvedActionsFromChoose = _b[0],
resolvedContextFromChoose = _b[1];
updatedContext = resolvedContextFromChoose;
preservedContexts === null || preservedContexts === void 0 ? void 0 : preservedContexts.push(updatedContext);
return resolvedActionsFromChoose;
}
case pure$1:
{
var matchedActions = actionObject.get(updatedContext, _event.data);
if (!matchedActions) {
return [];
}
var _c = __read(resolveActions(machine, currentState, updatedContext, _event, [{
type: blockType,
actions: toActionObjects(toArray(matchedActions), machine.options.actions)
}], predictableExec, preserveActionOrder), 2),
resolvedActionsFromPure = _c[0],
resolvedContext = _c[1];
updatedContext = resolvedContext;
preservedContexts === null || preservedContexts === void 0 ? void 0 : preservedContexts.push(updatedContext);
return resolvedActionsFromPure;
}
case stop$1:
{
var resolved = resolveStop(actionObject, updatedContext, _event);
predictableExec === null || predictableExec === void 0 ? void 0 : predictableExec(resolved, currentContext, _event);
return resolved;
}
case assign$1:
{
updatedContext = updateContext(updatedContext, _event, [actionObject], !predictableExec ? currentState : undefined);
preservedContexts === null || preservedContexts === void 0 ? void 0 : preservedContexts.push(updatedContext);
break;
}
default:
var resolvedActionObject = toActionObject(actionObject, machine.options.actions);
var exec_1 = resolvedActionObject.exec;
if (predictableExec) {
predictableExec(resolvedActionObject, updatedContext, _event);
} else if (exec_1 && preservedContexts) {
var contextIndex_1 = preservedContexts.length - 1;
var wrapped = __assign(__assign({}, resolvedActionObject), {
exec: function (_ctx) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
exec_1.apply(void 0, __spreadArray([preservedContexts[contextIndex_1]], __read(args), false));
}
});
resolvedActionObject = wrapped;
}
return resolvedActionObject;
}
}
function processBlock(block) {
var e_2, _a;
var resolvedActions = [];
try {
for (var _b = __values(block.actions), _c = _b.next(); !_c.done; _c = _b.next()) {
var action = _c.value;
var resolved = handleAction(block.type, action);
if (resolved) {
resolvedActions = resolvedActions.concat(resolved);
}
}
} catch (e_2_1) {
e_2 = {
error: e_2_1
};
} finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
} finally {
if (e_2) throw e_2.error;
}
}
deferredToBlockEnd.forEach(function (action) {
predictableExec(action, updatedContext, _event);
});
deferredToBlockEnd.length = 0;
return resolvedActions;
}
var resolvedActions = flatten(actionBlocks.map(processBlock));
return [resolvedActions, updatedContext];
}
export { after, assign, cancel, choose, done, doneInvoke, error, escalate, forwardTo, getActionFunction, initEvent, isActionObject, log, pure, raise, resolveActions, resolveLog, resolveRaise, resolveSend, resolveStop, respond, send, sendParent, sendTo, sendUpdate, start, stop, toActionObject, toActionObjects, toActivityDefinition };