xstate
Version:
Finite State Machines and Statecharts for the Modern Web.
2,624 lines • 82.4 kB
JavaScript
'use strict';
var dev_dist_xstateDev = require('../dev/dist/xstate-dev.cjs.js');
class Mailbox {
constructor(_process) {
this._process = _process;
this._active = false;
this._current = null;
this._last = null;
}
start() {
this._active = true;
this.flush();
}
clear() {
// we can't set _current to null because we might be currently processing
// and enqueue following clear shouldnt start processing the enqueued item immediately
if (this._current) {
this._current.next = null;
this._last = this._current;
}
}
enqueue(event) {
const enqueued = {
value: event,
next: null
};
if (this._current) {
this._last.next = enqueued;
this._last = enqueued;
return;
}
this._current = enqueued;
this._last = enqueued;
if (this._active) {
this.flush();
}
}
flush() {
while (this._current) {
// atm the given _process is responsible for implementing proper try/catch handling
// we assume here that this won't throw in a way that can affect this mailbox
const consumed = this._current;
this._process(consumed.value);
this._current = consumed.next;
}
this._last = null;
}
}
const STATE_DELIMITER = '.';
const TARGETLESS_KEY = '';
const NULL_EVENT = '';
const STATE_IDENTIFIER = '#';
const WILDCARD = '*';
const XSTATE_INIT = 'xstate.init';
const XSTATE_ERROR = 'xstate.error';
const XSTATE_STOP = 'xstate.stop';
/**
* Returns an event 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 createAfterEvent(delayRef, id) {
return {
type: `xstate.after.${delayRef}.${id}`
};
}
/**
* 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 output The data to pass into the event
*/
function createDoneStateEvent(id, output) {
return {
type: `xstate.done.state.${id}`,
output
};
}
/**
* 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 invokeId The invoked service ID
* @param output The data to pass into the event
*/
function createDoneActorEvent(invokeId, output) {
return {
type: `xstate.done.actor.${invokeId}`,
output
};
}
function createErrorActorEvent(id, error) {
return {
type: `xstate.error.actor.${id}`,
error
};
}
function createInitEvent(input) {
return {
type: XSTATE_INIT,
input
};
}
/**
* This function makes sure that unhandled errors are thrown in a separate macrotask.
* It allows those errors to be detected by global error handlers and reported to bug tracking services
* without interrupting our own stack of execution.
*
* @param err error to be thrown
*/
function reportUnhandledError(err) {
setTimeout(() => {
throw err;
});
}
const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
function createScheduledEventId(actorRef, id) {
return `${actorRef.sessionId}.${id}`;
}
let idCounter = 0;
function createSystem(rootActor, options) {
const children = new Map();
const keyedActors = new Map();
const reverseKeyedActors = new WeakMap();
const inspectionObservers = new Set();
const timerMap = {};
const {
clock,
logger
} = options;
const scheduler = {
schedule: (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
const scheduledEvent = {
source,
target,
event,
delay,
id,
startedAt: Date.now()
};
const scheduledEventId = createScheduledEventId(source, id);
system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
const timeout = clock.setTimeout(() => {
delete timerMap[scheduledEventId];
delete system._snapshot._scheduledEvents[scheduledEventId];
system._relay(source, target, event);
}, delay);
timerMap[scheduledEventId] = timeout;
},
cancel: (source, id) => {
const scheduledEventId = createScheduledEventId(source, id);
const timeout = timerMap[scheduledEventId];
delete timerMap[scheduledEventId];
delete system._snapshot._scheduledEvents[scheduledEventId];
clock.clearTimeout(timeout);
},
cancelAll: actorRef => {
for (const scheduledEventId in system._snapshot._scheduledEvents) {
const scheduledEvent = system._snapshot._scheduledEvents[scheduledEventId];
if (scheduledEvent.source === actorRef) {
scheduler.cancel(actorRef, scheduledEvent.id);
}
}
}
};
const sendInspectionEvent = event => {
if (!inspectionObservers.size) {
return;
}
const resolvedInspectionEvent = {
...event,
rootId: rootActor.sessionId
};
inspectionObservers.forEach(observer => observer.next?.(resolvedInspectionEvent));
};
const system = {
_snapshot: {
_scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
},
_bookId: () => `x:${idCounter++}`,
_register: (sessionId, actorRef) => {
children.set(sessionId, actorRef);
return sessionId;
},
_unregister: actorRef => {
children.delete(actorRef.sessionId);
const systemId = reverseKeyedActors.get(actorRef);
if (systemId !== undefined) {
keyedActors.delete(systemId);
reverseKeyedActors.delete(actorRef);
}
},
get: systemId => {
return keyedActors.get(systemId);
},
_set: (systemId, actorRef) => {
const existing = keyedActors.get(systemId);
if (existing && existing !== actorRef) {
throw new Error(`Actor with system ID '${systemId}' already exists.`);
}
keyedActors.set(systemId, actorRef);
reverseKeyedActors.set(actorRef, systemId);
},
inspect: observer => {
inspectionObservers.add(observer);
},
_sendInspectionEvent: sendInspectionEvent,
_relay: (source, target, event) => {
system._sendInspectionEvent({
type: '@xstate.event',
sourceRef: source,
actorRef: target,
event
});
target._send(event);
},
scheduler,
getSnapshot: () => {
return {
_scheduledEvents: {
...system._snapshot._scheduledEvents
}
};
},
start: () => {
const scheduledEvents = system._snapshot._scheduledEvents;
system._snapshot._scheduledEvents = {};
for (const scheduledId in scheduledEvents) {
const {
source,
target,
event,
delay,
id
} = scheduledEvents[scheduledId];
scheduler.schedule(source, target, event, delay, id);
}
},
_clock: clock,
_logger: logger
};
return system;
}
function matchesState(parentStateId, childStateId) {
const parentStateValue = toStateValue(parentStateId);
const childStateValue = toStateValue(childStateId);
if (typeof childStateValue === 'string') {
if (typeof parentStateValue === 'string') {
return childStateValue === parentStateValue;
}
// Parent more specific than child
return false;
}
if (typeof parentStateValue === 'string') {
return parentStateValue in childStateValue;
}
return Object.keys(parentStateValue).every(key => {
if (!(key in childStateValue)) {
return false;
}
return matchesState(parentStateValue[key], childStateValue[key]);
});
}
function toStatePath(stateId) {
if (isArray(stateId)) {
return stateId;
}
let result = [];
let segment = '';
for (let i = 0; i < stateId.length; i++) {
const char = stateId.charCodeAt(i);
switch (char) {
// \
case 92:
// consume the next character
segment += stateId[i + 1];
// and skip over it
i++;
continue;
// .
case 46:
result.push(segment);
segment = '';
continue;
}
segment += stateId[i];
}
result.push(segment);
return result;
}
function toStateValue(stateValue) {
if (isMachineSnapshot(stateValue)) {
return stateValue.value;
}
if (typeof stateValue !== 'string') {
return stateValue;
}
const statePath = toStatePath(stateValue);
return pathToStateValue(statePath);
}
function pathToStateValue(statePath) {
if (statePath.length === 1) {
return statePath[0];
}
const value = {};
let marker = value;
for (let i = 0; i < statePath.length - 1; i++) {
if (i === statePath.length - 2) {
marker[statePath[i]] = statePath[i + 1];
} else {
const previous = marker;
marker = {};
previous[statePath[i]] = marker;
}
}
return value;
}
function mapValues(collection, iteratee) {
const result = {};
const collectionKeys = Object.keys(collection);
for (let i = 0; i < collectionKeys.length; i++) {
const key = collectionKeys[i];
result[key] = iteratee(collection[key], key, collection, i);
}
return result;
}
function toArrayStrict(value) {
if (isArray(value)) {
return value;
}
return [value];
}
function toArray(value) {
if (value === undefined) {
return [];
}
return toArrayStrict(value);
}
function resolveOutput(mapper, context, event, self) {
if (typeof mapper === 'function') {
return mapper({
context,
event,
self
});
}
return mapper;
}
function isArray(value) {
return Array.isArray(value);
}
function isErrorActorEvent(event) {
return event.type.startsWith('xstate.error.actor');
}
function toTransitionConfigArray(configLike) {
return toArrayStrict(configLike).map(transitionLike => {
if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
return {
target: transitionLike
};
}
return transitionLike;
});
}
function normalizeTarget(target) {
if (target === undefined || target === TARGETLESS_KEY) {
return undefined;
}
return toArray(target);
}
function toObserver(nextHandler, errorHandler, completionHandler) {
const isObserver = typeof nextHandler === 'object';
const self = isObserver ? nextHandler : undefined;
return {
next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
};
}
function createInvokeId(stateNodeId, index) {
return `${index}.${stateNodeId}`;
}
function resolveReferencedActor(machine, src) {
const match = src.match(/^xstate\.invoke\.(\d+)\.(.*)/);
if (!match) {
return machine.implementations.actors[src];
}
const [, indexStr, nodeId] = match;
const node = machine.getStateNodeById(nodeId);
const invokeConfig = node.config.invoke;
return (Array.isArray(invokeConfig) ? invokeConfig[indexStr] : invokeConfig).src;
}
function getAllOwnEventDescriptors(snapshot) {
return [...new Set([...snapshot._nodes.flatMap(sn => sn.ownEvents)])];
}
const $$ACTOR_TYPE = 1;
// those values are currently used by @xstate/react directly so it's important to keep the assigned values in sync
let ProcessingStatus = /*#__PURE__*/function (ProcessingStatus) {
ProcessingStatus[ProcessingStatus["NotStarted"] = 0] = "NotStarted";
ProcessingStatus[ProcessingStatus["Running"] = 1] = "Running";
ProcessingStatus[ProcessingStatus["Stopped"] = 2] = "Stopped";
return ProcessingStatus;
}({});
const defaultOptions = {
clock: {
setTimeout: (fn, ms) => {
return setTimeout(fn, ms);
},
clearTimeout: id => {
return clearTimeout(id);
}
},
logger: console.log.bind(console),
devTools: false
};
/**
* An Actor is a running process that can receive events, send events and change its behavior based on the events it receives, which can cause effects outside of the actor. When you run a state machine, it becomes an actor.
*/
class Actor {
/**
* Creates a new actor instance for the given logic with the provided options, if any.
*
* @param logic The logic to create an actor from
* @param options Actor options
*/
constructor(logic, options) {
this.logic = logic;
/**
* The current internal state of the actor.
*/
this._snapshot = void 0;
/**
* The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
*/
this.clock = void 0;
this.options = void 0;
/**
* The unique identifier for this actor relative to its parent.
*/
this.id = void 0;
this.mailbox = new Mailbox(this._process.bind(this));
this.observers = new Set();
this.eventListeners = new Map();
this.logger = void 0;
/** @internal */
this._processingStatus = ProcessingStatus.NotStarted;
// Actor Ref
this._parent = void 0;
/** @internal */
this._syncSnapshot = void 0;
this.ref = void 0;
// TODO: add typings for system
this._actorScope = void 0;
this._systemId = void 0;
/**
* The globally unique process ID for this invocation.
*/
this.sessionId = void 0;
/**
* The system to which this actor belongs.
*/
this.system = void 0;
this._doneEvent = void 0;
this.src = void 0;
// array of functions to defer
this._deferred = [];
const resolvedOptions = {
...defaultOptions,
...options
};
const {
clock,
logger,
parent,
syncSnapshot,
id,
systemId,
inspect
} = resolvedOptions;
this.system = parent ? parent.system : createSystem(this, {
clock,
logger
});
if (inspect && !parent) {
// Always inspect at the system-level
this.system.inspect(toObserver(inspect));
}
this.sessionId = this.system._bookId();
this.id = id ?? this.sessionId;
this.logger = options?.logger ?? this.system._logger;
this.clock = options?.clock ?? this.system._clock;
this._parent = parent;
this._syncSnapshot = syncSnapshot;
this.options = resolvedOptions;
this.src = resolvedOptions.src ?? logic;
this.ref = this;
this._actorScope = {
self: this,
id: this.id,
sessionId: this.sessionId,
logger: this.logger,
defer: fn => {
this._deferred.push(fn);
},
system: this.system,
stopChild: child => {
if (child._parent !== this) {
throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
}
child._stop();
},
emit: emittedEvent => {
const listeners = this.eventListeners.get(emittedEvent.type);
if (!listeners) {
return;
}
for (const handler of Array.from(listeners)) {
handler(emittedEvent);
}
}
};
// Ensure that the send method is bound to this Actor instance
// if destructured
this.send = this.send.bind(this);
this.system._sendInspectionEvent({
type: '@xstate.actor',
actorRef: this
});
if (systemId) {
this._systemId = systemId;
this.system._set(systemId, this);
}
this._initState(options?.snapshot ?? options?.state);
if (systemId && this._snapshot.status !== 'active') {
this.system._unregister(this);
}
}
_initState(persistedState) {
try {
this._snapshot = persistedState ? this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState : this.logic.getInitialSnapshot(this._actorScope, this.options?.input);
} catch (err) {
// if we get here then it means that we assign a value to this._snapshot that is not of the correct type
// we can't get the true `TSnapshot & { status: 'error'; }`, it's impossible
// so right now this is a lie of sorts
this._snapshot = {
status: 'error',
output: undefined,
error: err
};
}
}
update(snapshot, event) {
// Update state
this._snapshot = snapshot;
// Execute deferred effects
let deferredFn;
while (deferredFn = this._deferred.shift()) {
try {
deferredFn();
} catch (err) {
// this error can only be caught when executing *initial* actions
// it's the only time when we call actions provided by the user through those deferreds
// when the actor is already running we always execute them synchronously while transitioning
// no "builtin deferred" should actually throw an error since they are either safe
// or the control flow is passed through the mailbox and errors should be caught by the `_process` used by the mailbox
this._deferred.length = 0;
this._snapshot = {
...snapshot,
status: 'error',
error: err
};
}
}
switch (this._snapshot.status) {
case 'active':
for (const observer of this.observers) {
try {
observer.next?.(snapshot);
} catch (err) {
reportUnhandledError(err);
}
}
break;
case 'done':
// next observers are meant to be notified about done snapshots
// this can be seen as something that is different from how observable work
// but with observables `complete` callback is called without any arguments
// it's more ergonomic for XState to treat a done snapshot as a "next" value
// and the completion event as something that is separate,
// something that merely follows emitting that done snapshot
for (const observer of this.observers) {
try {
observer.next?.(snapshot);
} catch (err) {
reportUnhandledError(err);
}
}
this._stopProcedure();
this._complete();
this._doneEvent = createDoneActorEvent(this.id, this._snapshot.output);
if (this._parent) {
this.system._relay(this, this._parent, this._doneEvent);
}
break;
case 'error':
this._error(this._snapshot.error);
break;
}
this.system._sendInspectionEvent({
type: '@xstate.snapshot',
actorRef: this,
event,
snapshot
});
}
/**
* Subscribe an observer to an actor’s snapshot values.
*
* @remarks
* The observer will receive the actor’s snapshot value when it is emitted. The observer can be:
* - A plain function that receives the latest snapshot, or
* - An observer object whose `.next(snapshot)` method receives the latest snapshot
*
* @example
* ```ts
* // Observer as a plain function
* const subscription = actor.subscribe((snapshot) => {
* console.log(snapshot);
* });
* ```
*
* @example
* ```ts
* // Observer as an object
* const subscription = actor.subscribe({
* next(snapshot) {
* console.log(snapshot);
* },
* error(err) {
* // ...
* },
* complete() {
* // ...
* },
* });
* ```
*
* The return value of `actor.subscribe(observer)` is a subscription object that has an `.unsubscribe()` method. You can call `subscription.unsubscribe()` to unsubscribe the observer:
*
* @example
* ```ts
* const subscription = actor.subscribe((snapshot) => {
* // ...
* });
*
* // Unsubscribe the observer
* subscription.unsubscribe();
* ```
*
* When the actor is stopped, all of its observers will automatically be unsubscribed.
*
* @param observer - Either a plain function that receives the latest snapshot, or an observer object whose `.next(snapshot)` method receives the latest snapshot
*/
subscribe(nextListenerOrObserver, errorListener, completeListener) {
const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
if (this._processingStatus !== ProcessingStatus.Stopped) {
this.observers.add(observer);
} else {
switch (this._snapshot.status) {
case 'done':
try {
observer.complete?.();
} catch (err) {
reportUnhandledError(err);
}
break;
case 'error':
{
const err = this._snapshot.error;
if (!observer.error) {
reportUnhandledError(err);
} else {
try {
observer.error(err);
} catch (err) {
reportUnhandledError(err);
}
}
break;
}
}
}
return {
unsubscribe: () => {
this.observers.delete(observer);
}
};
}
on(type, handler) {
let listeners = this.eventListeners.get(type);
if (!listeners) {
listeners = new Set();
this.eventListeners.set(type, listeners);
}
const wrappedHandler = handler.bind(undefined);
listeners.add(wrappedHandler);
return {
unsubscribe: () => {
listeners.delete(wrappedHandler);
}
};
}
/**
* Starts the Actor from the initial state
*/
start() {
if (this._processingStatus === ProcessingStatus.Running) {
// Do not restart the service if it is already started
return this;
}
if (this._syncSnapshot) {
this.subscribe({
next: snapshot => {
if (snapshot.status === 'active') {
this.system._relay(this, this._parent, {
type: `xstate.snapshot.${this.id}`,
snapshot
});
}
},
error: () => {}
});
}
this.system._register(this.sessionId, this);
if (this._systemId) {
this.system._set(this._systemId, this);
}
this._processingStatus = ProcessingStatus.Running;
// TODO: this isn't correct when rehydrating
const initEvent = createInitEvent(this.options.input);
this.system._sendInspectionEvent({
type: '@xstate.event',
sourceRef: this._parent,
actorRef: this,
event: initEvent
});
const status = this._snapshot.status;
switch (status) {
case 'done':
// a state machine can be "done" upon initialization (it could reach a final state using initial microsteps)
// we still need to complete observers, flush deferreds etc
this.update(this._snapshot, initEvent);
// TODO: rethink cleanup of observers, mailbox, etc
return this;
case 'error':
this._error(this._snapshot.error);
return this;
}
if (!this._parent) {
this.system.start();
}
if (this.logic.start) {
try {
this.logic.start(this._snapshot, this._actorScope);
} catch (err) {
this._snapshot = {
...this._snapshot,
status: 'error',
error: err
};
this._error(err);
return this;
}
}
// TODO: this notifies all subscribers but usually this is redundant
// there is no real change happening here
// we need to rethink if this needs to be refactored
this.update(this._snapshot, initEvent);
if (this.options.devTools) {
this.attachDevTools();
}
this.mailbox.start();
return this;
}
_process(event) {
let nextState;
let caughtError;
try {
nextState = this.logic.transition(this._snapshot, event, this._actorScope);
} catch (err) {
// we wrap it in a box so we can rethrow it later even if falsy value gets caught here
caughtError = {
err
};
}
if (caughtError) {
const {
err
} = caughtError;
this._snapshot = {
...this._snapshot,
status: 'error',
error: err
};
this._error(err);
return;
}
this.update(nextState, event);
if (event.type === XSTATE_STOP) {
this._stopProcedure();
this._complete();
}
}
_stop() {
if (this._processingStatus === ProcessingStatus.Stopped) {
return this;
}
this.mailbox.clear();
if (this._processingStatus === ProcessingStatus.NotStarted) {
this._processingStatus = ProcessingStatus.Stopped;
return this;
}
this.mailbox.enqueue({
type: XSTATE_STOP
});
return this;
}
/**
* Stops the Actor and unsubscribe all listeners.
*/
stop() {
if (this._parent) {
throw new Error('A non-root actor cannot be stopped directly.');
}
return this._stop();
}
_complete() {
for (const observer of this.observers) {
try {
observer.complete?.();
} catch (err) {
reportUnhandledError(err);
}
}
this.observers.clear();
}
_reportError(err) {
if (!this.observers.size) {
if (!this._parent) {
reportUnhandledError(err);
}
return;
}
let reportError = false;
for (const observer of this.observers) {
const errorListener = observer.error;
reportError ||= !errorListener;
try {
errorListener?.(err);
} catch (err2) {
reportUnhandledError(err2);
}
}
this.observers.clear();
if (reportError) {
reportUnhandledError(err);
}
}
_error(err) {
this._stopProcedure();
this._reportError(err);
if (this._parent) {
this.system._relay(this, this._parent, createErrorActorEvent(this.id, err));
}
}
// TODO: atm children don't belong entirely to the actor so
// in a way - it's not even super aware of them
// so we can't stop them from here but we really should!
// right now, they are being stopped within the machine's transition
// but that could throw and leave us with "orphaned" active actors
_stopProcedure() {
if (this._processingStatus !== ProcessingStatus.Running) {
// Actor already stopped; do nothing
return this;
}
// Cancel all delayed events
this.system.scheduler.cancelAll(this);
// TODO: mailbox.reset
this.mailbox.clear();
// TODO: after `stop` we must prepare ourselves for receiving events again
// events sent *after* stop signal must be queued
// it seems like this should be the common behavior for all of our consumers
// so perhaps this should be unified somehow for all of them
this.mailbox = new Mailbox(this._process.bind(this));
this._processingStatus = ProcessingStatus.Stopped;
this.system._unregister(this);
return this;
}
/**
* @internal
*/
_send(event) {
if (this._processingStatus === ProcessingStatus.Stopped) {
return;
}
this.mailbox.enqueue(event);
}
/**
* Sends an event to the running Actor to trigger a transition.
*
* @param event The event to send
*/
send(event) {
this.system._relay(undefined, this, event);
}
attachDevTools() {
const {
devTools
} = this.options;
if (devTools) {
const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
resolvedDevToolsAdapter(this);
}
}
toJSON() {
return {
xstate$$type: $$ACTOR_TYPE,
id: this.id
};
}
/**
* Obtain the internal state of the actor, which can be persisted.
*
* @remarks
* The internal state can be persisted from any actor, not only machines.
*
* Note that the persisted state is not the same as the snapshot from {@link Actor.getSnapshot}. Persisted state represents the internal state of the actor, while snapshots represent the actor's last emitted value.
*
* Can be restored with {@link ActorOptions.state}
*
* @see https://stately.ai/docs/persistence
*/
getPersistedSnapshot(options) {
return this.logic.getPersistedSnapshot(this._snapshot, options);
}
[symbolObservable]() {
return this;
}
/**
* Read an actor’s snapshot synchronously.
*
* @remarks
* The snapshot represent an actor's last emitted value.
*
* When an actor receives an event, its internal state may change.
* An actor may emit a snapshot when a state transition occurs.
*
* Note that some actors, such as callback actors generated with `fromCallback`, will not emit snapshots.
*
* @see {@link Actor.subscribe} to subscribe to an actor’s snapshot values.
* @see {@link Actor.getPersistedSnapshot} to persist the internal state of an actor (which is more than just a snapshot).
*/
getSnapshot() {
return this._snapshot;
}
}
/**
* Creates a new actor instance for the given actor logic with the provided options, if any.
*
* @remarks
* When you create an actor from actor logic via `createActor(logic)`, you implicitly create an actor system where the created actor is the root actor.
* Any actors spawned from this root actor and its descendants are part of that actor system.
*
* @example
* ```ts
* import { createActor } from 'xstate';
* import { someActorLogic } from './someActorLogic.ts';
*
* // Creating the actor, which implicitly creates an actor system with itself as the root actor
* const actor = createActor(someActorLogic);
*
* actor.subscribe((snapshot) => {
* console.log(snapshot);
* });
*
* // Actors must be started by calling `actor.start()`, which will also start the actor system.
* actor.start();
*
* // Actors can receive events
* actor.send({ type: 'someEvent' });
*
* // You can stop root actors by calling `actor.stop()`, which will also stop the actor system and all actors in that system.
* actor.stop();
* ```
*
* @param logic - The actor logic to create an actor from. For a state machine actor logic creator, see {@link createMachine}. Other actor logic creators include {@link fromCallback}, {@link fromEventObservable}, {@link fromObservable}, {@link fromPromise}, and {@link fromTransition}.
* @param options - Actor options
*/
function createActor(logic, ...[options]) {
return new Actor(logic, options);
}
/**
* Creates a new Interpreter instance for the given machine with the provided options, if any.
*
* @deprecated Use `createActor` instead
*/
const interpret = createActor;
/**
* @deprecated Use `Actor` instead.
*/
function resolveCancel(_, snapshot, actionArgs, actionParams, {
sendId
}) {
const resolvedSendId = typeof sendId === 'function' ? sendId(actionArgs, actionParams) : sendId;
return [snapshot, resolvedSendId];
}
function executeCancel(actorScope, resolvedSendId) {
actorScope.defer(() => {
actorScope.system.scheduler.cancel(actorScope.self, resolvedSendId);
});
}
/**
* Cancels a delayed `sendTo(...)` action that is waiting to be executed. The canceled `sendTo(...)` action
* will not send its event or execute, unless the `delay` has already elapsed before `cancel(...)` is called.
*
* @param sendId The `id` of the `sendTo(...)` action to cancel.
*
* @example
```ts
import { createMachine, sendTo, cancel } from 'xstate';
const machine = createMachine({
// ...
on: {
sendEvent: {
actions: sendTo('some-actor', { type: 'someEvent' }, {
id: 'some-id',
delay: 1000
})
},
cancelEvent: {
actions: cancel('some-id')
}
}
});
```
*/
function cancel(sendId) {
function cancel(args, params) {
}
cancel.type = 'xstate.cancel';
cancel.sendId = sendId;
cancel.resolve = resolveCancel;
cancel.execute = executeCancel;
return cancel;
}
function resolveSpawn(actorScope, snapshot, actionArgs, _actionParams, {
id,
systemId,
src,
input,
syncSnapshot
}) {
const logic = typeof src === 'string' ? resolveReferencedActor(snapshot.machine, src) : src;
const resolvedId = typeof id === 'function' ? id(actionArgs) : id;
let actorRef;
if (logic) {
actorRef = createActor(logic, {
id: resolvedId,
src,
parent: actorScope.self,
syncSnapshot,
systemId,
input: typeof input === 'function' ? input({
context: snapshot.context,
event: actionArgs.event,
self: actorScope.self
}) : input
});
}
return [cloneMachineSnapshot(snapshot, {
children: {
...snapshot.children,
[resolvedId]: actorRef
}
}), {
id,
actorRef
}];
}
function executeSpawn(actorScope, {
id,
actorRef
}) {
if (!actorRef) {
return;
}
actorScope.defer(() => {
if (actorRef._processingStatus === ProcessingStatus.Stopped) {
return;
}
actorRef.start();
});
}
function spawnChild(...[src, {
id,
systemId,
input,
syncSnapshot = false
} = {}]) {
function spawnChild(args, params) {
}
spawnChild.type = 'snapshot.spawnChild';
spawnChild.id = id;
spawnChild.systemId = systemId;
spawnChild.src = src;
spawnChild.input = input;
spawnChild.syncSnapshot = syncSnapshot;
spawnChild.resolve = resolveSpawn;
spawnChild.execute = executeSpawn;
return spawnChild;
}
function resolveStop(_, snapshot, args, actionParams, {
actorRef
}) {
const actorRefOrString = typeof actorRef === 'function' ? actorRef(args, actionParams) : actorRef;
const resolvedActorRef = typeof actorRefOrString === 'string' ? snapshot.children[actorRefOrString] : actorRefOrString;
let children = snapshot.children;
if (resolvedActorRef) {
children = {
...children
};
delete children[resolvedActorRef.id];
}
return [cloneMachineSnapshot(snapshot, {
children
}), resolvedActorRef];
}
function executeStop(actorScope, actorRef) {
if (!actorRef) {
return;
}
// we need to eagerly unregister it here so a new actor with the same systemId can be registered immediately
// since we defer actual stopping of the actor but we don't defer actor creations (and we can't do that)
// this could throw on `systemId` collision, for example, when dealing with reentering transitions
actorScope.system._unregister(actorRef);
// this allows us to prevent an actor from being started if it gets stopped within the same macrostep
// this can happen, for example, when the invoking state is being exited immediately by an always transition
if (actorRef._processingStatus !== ProcessingStatus.Running) {
actorScope.stopChild(actorRef);
return;
}
// stopping a child enqueues a stop event in the child actor's mailbox
// we need for all of the already enqueued events to be processed before we stop the child
// the parent itself might want to send some events to a child (for example from exit actions on the invoking state)
// and we don't want to ignore those events
actorScope.defer(() => {
actorScope.stopChild(actorRef);
});
}
/**
* Stops a child actor.
*
* @param actorRef The actor to stop.
*/
function stopChild(actorRef) {
function stop(args, params) {
}
stop.type = 'xstate.stopChild';
stop.actorRef = actorRef;
stop.resolve = resolveStop;
stop.execute = executeStop;
return stop;
}
/**
* Stops a child actor.
*
* @deprecated Use `stopChild(...)` instead
*/
const stop = stopChild;
function checkStateIn(snapshot, _, {
stateValue
}) {
if (typeof stateValue === 'string' && isStateId(stateValue)) {
const target = snapshot.machine.getStateNodeById(stateValue);
return snapshot._nodes.some(sn => sn === target);
}
return snapshot.matches(stateValue);
}
function stateIn(stateValue) {
function stateIn(args, params) {
return false;
}
stateIn.check = checkStateIn;
stateIn.stateValue = stateValue;
return stateIn;
}
function checkNot(snapshot, {
context,
event
}, {
guards
}) {
return !evaluateGuard(guards[0], context, event, snapshot);
}
/**
* Higher-order guard that evaluates to `true` if the `guard` passed to it evaluates to `false`.
*
* @category Guards
* @example
```ts
import { setup, not } from 'xstate';
const machine = setup({
guards: {
someNamedGuard: () => false
}
}).createMachine({
on: {
someEvent: {
guard: not('someNamedGuard'),
actions: () => {
// will be executed if guard in `not(...)`
// evaluates to `false`
}
}
}
});
```
* @returns A guard
*/
function not(guard) {
function not(args, params) {
return false;
}
not.check = checkNot;
not.guards = [guard];
return not;
}
function checkAnd(snapshot, {
context,
event
}, {
guards
}) {
return guards.every(guard => evaluateGuard(guard, context, event, snapshot));
}
/**
* Higher-order guard that evaluates to `true` if all `guards` passed to it
* evaluate to `true`.
*
* @category Guards
* @example
```ts
import { setup, and } from 'xstate';
const machine = setup({
guards: {
someNamedGuard: () => true
}
}).createMachine({
on: {
someEvent: {
guard: and([
({ context }) => context.value > 0,
'someNamedGuard'
]),
actions: () => {
// will be executed if all guards in `and(...)`
// evaluate to true
}
}
}
});
```
* @returns A guard action object
*/
function and(guards) {
function and(args, params) {
return false;
}
and.check = checkAnd;
and.guards = guards;
return and;
}
function checkOr(snapshot, {
context,
event
}, {
guards
}) {
return guards.some(guard => evaluateGuard(guard, context, event, snapshot));
}
/**
* Higher-order guard that evaluates to `true` if any of the `guards` passed to it
* evaluate to `true`.
*
* @category Guards
* @example
```ts
import { setup, or } from 'xstate';
const machine = setup({
guards: {
someNamedGuard: () => true
}
}).createMachine({
on: {
someEvent: {
guard: or([
({ context }) => context.value > 0,
'someNamedGuard'
]),
actions: () => {
// will be executed if any of the guards in `or(...)`
// evaluate to true
}
}
}
});
```
* @returns A guard action object
*/
function or(guards) {
function or(args, params) {
return false;
}
or.check = checkOr;
or.guards = guards;
return or;
}
// TODO: throw on cycles (depth check should be enough)
function evaluateGuard(guard, context, event, snapshot) {
const {
machine
} = snapshot;
const isInline = typeof guard === 'function';
const resolved = isInline ? guard : machine.implementations.guards[typeof guard === 'string' ? guard : guard.type];
if (!isInline && !resolved) {
throw new Error(`Guard '${typeof guard === 'string' ? guard : guard.type}' is not implemented.'.`);
}
if (typeof resolved !== 'function') {
return evaluateGuard(resolved, context, event, snapshot);
}
const guardArgs = {
context,
event
};
const guardParams = isInline || typeof guard === 'string' ? undefined : 'params' in guard ? typeof guard.params === 'function' ? guard.params({
context,
event
}) : guard.params : undefined;
if (!('check' in resolved)) {
// the existing type of `.guards` assumes non-nullable `TExpressionGuard`
// inline guards expect `TExpressionGuard` to be set to `undefined`
// it's fine to cast this here, our logic makes sure that we call those 2 "variants" correctly
return resolved(guardArgs, guardParams);
}
const builtinGuard = resolved;
return builtinGuard.check(snapshot, guardArgs, resolved // this holds all params
);
}
const isAtomicStateNode = stateNode => stateNode.type === 'atomic' || stateNode.type === 'final';
function getChildren(stateNode) {
return Object.values(stateNode.states).filter(sn => sn.type !== 'history');
}
function getProperAncestors(stateNode, toStateNode) {
const ancestors = [];
if (toStateNode === stateNode) {
return ancestors;
}
// add all ancestors
let m = stateNode.parent;
while (m && m !== toStateNode) {
ancestors.push(m);
m = m.parent;
}
return ancestors;
}
function getAllStateNodes(stateNodes) {
const nodeSet = new Set(stateNodes);
const adjList = getAdjList(nodeSet);
// add descendants
for (const s of nodeSet) {
// if previously active, add existing child nodes
if (s.type === 'compound' && (!adjList.get(s) || !adjList.get(s).length)) {
getInitialStateNodesWithTheirAncestors(s).forEach(sn => nodeSet.add(sn));
} else {
if (s.type === 'parallel') {
for (const child of getChildren(s)) {
if (child.type === 'history') {
continue;
}
if (!nodeSet.has(child)) {
const initialStates = getInitialStateNodesWithTheirAncestors(child);
for (const initialStateNode of initialStates) {
nodeSet.add(initialStateNode);
}
}
}
}
}
}
// add all ancestors
for (const s of nodeSet) {
let m = s.parent;
while (m) {
nodeSet.add(m);
m = m.parent;
}
}
return nodeSet;
}
function getValueFromAdj(baseNode, adjList) {
const childStateNodes = adjList.get(baseNode);
if (!childStateNodes) {
return {}; // todo: fix?
}
if (baseNode.type === 'compound') {
const childStateNode = childStateNodes[0];
if (childStateNode) {
if (isAtomicStateNode(childStateNode)) {
return childStateNode.key;
}
} else {
return {};
}
}
const stateValue = {};
for (const childStateNode of childStateNodes) {
stateValue[childStateNode.key] = getValueFromAdj(childStateNode, adjList);
}
return stateValue;
}
function getAdjList(stateNodes) {
const adjList = new Map();
for (const s of stateNodes) {
if (!adjList.has(s)) {
adjList.set(s, []);
}
if (s.parent) {
if (!adjList.has(s.parent)) {
adjList.set(s.parent, []);
}
adjList.get(s.parent).push(s);
}
}
return adjList;
}
function getStateValue(rootNode, stateNodes) {
const config = getAllStateNodes(stateNodes);
return getValueFromAdj(rootNode, getAdjList(config));
}
function isInFinalState(stateNodeSet, stateNode) {
if (stateNode.type === 'compound') {
return getChildren(stateNode).some(s => s.type === 'final' && stateNodeSet.has(s));
}
if (stateNode.type === 'parallel') {
return getChildren(stateNode).every(sn => isInFinalState(stateNodeSet, sn));
}
return stateNode.type === 'final';
}
const isStateId = str => str[0] === STATE_IDENTIFIER;
function getCandidates(stateNode, receivedEventType) {
const candidates = stateNode.transitions.get(receivedEventType) || [...stateNode.transitions.keys()].filter(eventDescriptor => {
// check if transition is a wildcard transition,
// which matches any non-transient events
if (eventDescriptor === WILDCARD) {
return true;
}
if (!eventDescriptor.endsWith('.*')) {
return false;
}
const partialEventTokens = eventDescriptor.split('.');
const eventTokens = receivedEventType.split('.');
for (let tokenIndex = 0; tokenIndex < partialEventTokens.length; tokenIndex++) {
const partialEventToken = partialEventTokens[tokenIndex];
const eventToken = eventTokens[tokenIndex];
if (partialEventToken === '*') {
const isLastToken = tokenIndex === partialEventTokens.length - 1;
return isLastToken;
}
if (partialEventToken !== eventToken) {
return false;
}
}
return true;
}).sort((a, b) => b.length - a.length).flatMap(key => stateNode.transitions.get(key));
return candidates;
}
/**
* All delayed transitions from the config.
*/
function getDelayedTransitions(stateNode) {
const afterConfig = stateNode.config.after;
if (!afterConfig) {
return [];
}
const mutateEntryExit = (delay, i) => {
const afterEvent = createAfterEvent(delay, stateNode.id);
const eventType = afterEvent.type;
stateNode.entry.push(raise(afterEvent, {
id: eventType,
delay
}));
stateNode.exit.push(cancel(eventType));
return eventType;
};
const delayedTransitions = Object.keys(afterConfig).flatMap((delay, i) => {
const configTransition = afterConfig[delay];
const resolvedTransition = typeof configTransition === 'string' ? {
target: configTransition
} : configTransition;
const resolvedDelay = Number.isNaN(+delay) ? delay : +delay;
const eventType = mutateEntryExit(resolvedDelay);
return toArray(resolvedTransition).map(transition => ({
...transition,
event: eventType,
delay: resolvedDelay
}));
});
return delayedTransitions.map(delayedTransition => {
const {
delay
} = delayedTransition;
return {
...formatTransition(stateNode, delayedTransition.event, delayedTransition),
delay
};
});
}
function formatTransition(stateNode, descriptor, transitionConfig) {
const normalizedTarget = normalizeTarget(transitionConfig.target);
const reenter = transitionConfig.reenter ?? false;
const target = resolveTarget(stateNode, normalizedTarget);
const transition = {
...transitionConfig,
actions: toArray(transitionConfig.actions),
guard: transitionConfig.guard,
target,
source: stateNode,
reenter,
eventType: descriptor,
toJSON: () => ({
...transition,
source: `#${stateNode.id}`,
target: target ? target.map(t => `#${t.id}`) : undefined
})
};
return transition;
}
function formatTransitions(stateNode) {
const transitions = new Map();
if (stateNode.config.on) {
for (const descriptor of Object.keys(stateNode.config.on)) {
if (descriptor === NULL_EVENT) {
throw new Error('Null events ("") cannot be specified as a transition key. Use `always: { ... }` instead.');
}
const transitionsConfig = stateNode.config.on[descriptor];
transitions.set(descriptor, toTransitionConfigArray(transitionsConfig).map(t => formatTransition(stateNode, descriptor, t)));
}
}
if (stateNode.config.onDone) {
const descriptor = `xstate.done.state.${stateNode.id}`;
transitions.set(descriptor, toTransitionConfigArray(stateNode.config.onDone).map(t => formatTransition(stateNode, descriptor, t)));
}
for (const invokeDef of stateNode.invoke) {
if (invokeDef.onDone) {
const descriptor = `xstate.done.actor.${invokeDef.id}`;
transitions.set(descriptor, toTransitionConfigArray(invokeDef.onDone).map(t => formatTransition(stateNode, descriptor, t)));
}
if (invokeDef.onError) {
const descriptor = `xstate.error.actor.${invokeDef.id}`;
transitions.set(descriptor, toTransitionConfigArray(invokeDef.onError).map(t => formatTransition(stateNode, descriptor, t)));
}
if (invokeDef.onSnapshot) {
const descriptor = `xstate.snapshot.${invokeDef.id}`;
transitions.set(descriptor, toTransitionConfigArray(invokeDef.onSnapshot).map(t => formatTransition(stateNode, descriptor, t)));
}
}
for (const delayedTransition of stateNode.after) {
let existing = transitions.get(delayedTransition.eventType);
if (!existing) {
existing = [];
transitions.set(delayedTransition.eventType, existing);
}
existing.push(delayedTransition);
}
return transitions;
}
function formatInitialTransition(stateNode, _target) {
const resolvedTarget = typeof _target === 'string' ? stateNode.states[_target] : _target ? stateNode.states[_target.target] : undefined;
if (!resolvedTarget && _target) {
throw new Error(`Initial state node "${_target}" not found on parent state node #${stateNode.id}`);
}
const transition = {
source: stateNode,
actions: !_target || typeof _target === 'string' ? [] : toArray(_target.actions),
eventType: null,
reenter: false,
target: resolvedTarget ? [resolvedTarget] : [],
toJSON: () => ({
...transition,
source: `#${stateNode.id}`,
target: resolvedTarget ? [`#${resolvedTarget.id}`] : []
})
};
return transition;
}
function resolveTarget(stateNode, targets) {
if (targets === undefined) {
// an undefined target signals that the state node should not transition from that state when receiving that event
return undefined;
}
return targets.map(target => {
if (typeof target !== 'string') {
return target;
}
if (isStateId(target)) {
return stateNode.machine.getStateNodeById(target);
}
const isInternalTarget = target[0] === STATE_DELIMITER;
// If internal target is defined on machine,
// do not include machine key on target
if (isInternalTarget && !stateNode.parent) {
return getStateNodeByPath(stateNode, target.slice(1));
}
const resolvedTarget = isInternalTarget ? stateNode.key + target : target;
if (stateNode.parent) {
try {
const targetStateNode = getStateNodeByPath(stateNode.parent, resolvedTarget);
return targetStateNode;
} catch (err) {
throw new Error(`Invalid transition definition for state node '${stateNode.id}':\n${err.message}`);
}
} else {
throw new Error(`Invalid target: "${target}" is not a valid target from the root node. Did you mean ".${target}"?`);
}
});
}
function resolveHistoryDefaultTransition(stateNode) {
const normalizedTarget = normalizeTarget(stateNode.config.target);
if (!normalizedTarget) {
return stateNode.parent.initial;
}
return {
target: normalizedTarget.map(t => typeof t === 'string' ? getStateNodeByPath(stateNode.parent, t) : t)
};
}
function isHistoryNode(stateNode) {
return stateNode.type === 'history';
}
function getInitialStateNodesWithTheirAncestors(stateNode) {
const states = getInitialStateNodes(stateNode);
for (const initialState of states) {
for (const ancestor of getProperAncestors(initialState, stateNode)) {
states.add(ancestor);
}
}
return states;
}
function getInitialStateNodes(stateNode) {
const set = new Set();
function iter(descStateNode) {
if (set.has(descStateNode)) {
return;
}
set.add(descStateNode);
if (descStateNode.type === 'compound') {
iter(descStateNode.initial.target[0]);
} else if (descStateNode.type === 'parallel') {
for (const child of getChildren(descStateNode)) {
iter(child);
}
}
}
iter(stateNode);
return set;
}
/**
* Returns the child state node from its relative `stateKey`, or throws.
*/
function getStateNode(stateNode, stateKey) {
if (isStateId(stateKey)) {
return stateNode.machine.getStateNodeById(stateKey);
}
if (!stateNode.states) {
throw new Error(`Unable to retrieve child state '${stateKey}' from '${stateNode.id}'; no child states exist.`);
}
const result = stateNode.states[stateKey];
if (!result) {
throw new Error(`Child state '${stateKey}' does not exist on '${stateNode.id}'`);
}
return result;
}
/**
* Returns the relative state node from the given `statePath`, or throws.
*
* @param statePath The string or string array relative path to the state node.
*/
function getStateNodeByPath(stateNode, statePath) {
if (typeof statePath === 'string' && isStateId(statePath)) {
try {
return stateNode.machine.getStateNodeById(statePath);
} catch (e) {
// try individual paths
// throw e;
}
}
const arrayStatePath = toStatePath(statePath).slice();
let currentStateNode = stateNode;
while (arrayStatePath.length) {
const key = arrayStatePath.shift();
if (!key.length) {
break;
}
currentStateNode = getStateNode(currentStateNode, key);
}
return currentStateNode;
}
/**
* Returns the state nodes represented by the current state value.
*
* @param stateValue The state value or State instance
*/
function getStateNodes(stateNode, stateValue) {
if (typeof stateValue === 'string') {
const childStateNode = stateNode.states[stateValue];
if (!childStateNode) {
throw new Error(`State '${stateValue}' does not exist on '${stateNode.id}'`);
}
return [stateNode, childStateNode];
}
const childStateKeys = Object.keys(stateValue);
const childStateNodes = childStateKeys.map(subStateKey => getStateNode(stateNode, subStateKey)).filter(Boolean);
return [stateNode.machine.root, stateNode].concat(childStateNodes, childStateKeys.reduce((allSubStateNodes, subStateKey) => {
const subStateNode = getStateNode(stateNode, subStateKey);
if (!subStateNode) {
return allSubStateNodes;
}
const subStateNodes = getStateNodes(subStateNode, stateValue[subStateKey]);
return allSubStateNodes.concat(subStateNodes);
}, []));
}
function transitionAtomicNode(stateNode, stateValue, snapshot, event) {
const childStateNode = getStateNode(stateNode, stateValue);
const next = childStateNode.next(snapshot, event);
if (!next || !next.length) {
return stateNode.next(snapshot, event);
}
return next;
}
function transitionCompoundNode(stateNode, stateValue, snapshot, event) {
const subStateKeys = Object.keys(stateValue);
const childStateNode = getStateNode(stateNode, subStateKeys[0]);
const next = transitionNode(childStateNode, stateValue[subStateKeys[0]], snapshot, event);
if (!next || !next.length) {
return stateNode.next(snapshot, event);
}
return next;
}
function transitionParallelNode(stateNode, stateValue, snapshot, event) {
const allInnerTransitions = [];
for (const subStateKey of Object.keys(stateValue)) {
const subStateValue = stateValue[subStateKey];
if (!subStateValue) {
continue;
}
const subStateNode = getStateNode(stateNode, subStateKey);
const innerTransitions = transitionNode(subStateNode, subStateValue, snapshot, event);
if (innerTransitions) {
allInnerTransitions.push(...innerTransitions);
}
}
if (!allInnerTransitions.length) {
return stateNode.next(snapshot, event);
}
return allInnerTransitions;
}
function transitionNode(stateNode, stateValue, snapshot, event) {
// leaf node
if (typeof stateValue === 'string') {
return transitionAtomicNode(stateNode, stateValue, snapshot, event);
}
// compound node
if (Object.keys(stateValue).length === 1) {
return transitionCompoundNode(stateNode, stateValue, snapshot, event);
}
// parallel node
return transitionParallelNode(stateNode, stateValue, snapshot, event);
}
function getHistoryNodes(stateNode) {
return Object.keys(stateNode.states).map(key => stateNode.states[key]).filter(sn => sn.type === 'history');
}
function isDescendant(childStateNode, parentStateNode) {
let marker = childStateNode;
while (marker.parent && marker.parent !== parentStateNode) {
marker = marker.parent;
}
return marker.parent === parentStateNode;
}
function hasIntersection(s1, s2) {
const set1 = new Set(s1);
const set2 = new Set(s2);
for (const item of set1) {
if (set2.has(item)) {
return true;
}
}
for (const item of set2) {
if (set1.has(item)) {
return true;
}
}
return false;
}
function removeConflictingTransitions(enabledTransitions, stateNodeSet, historyValue) {
const filteredTransitions = new Set();
for (const t1 of enabledTransitions) {
let t1Preempted = false;
const transitionsToRemove = new Set();
for (const t2 of filteredTransitions) {
if (hasIntersection(computeExitSet([t1], stateNodeSet, historyValue), computeExitSet([t2], stateNodeSet, historyValue))) {
if (isDescendant(t1.source, t2.source)) {
transitionsToRemove.add(t2);
} else {
t1Preempted = true;
break;
}
}
}
if (!t1Preempted) {
for (const t3 of transitionsToRemove) {
filteredTransitions.delete(t3);
}
filteredTransitions.add(t1);
}
}
return Array.from(filteredTransitions);
}
function findLeastCommonAncestor(stateNodes) {
const [head, ...tail] = stateNodes;
for (const ancestor of getProperAncestors(head, undefined)) {
if (tail.every(sn => isDescendant(sn, ancestor))) {
return ancestor;
}
}
}
function getEffectiveTargetStates(transition, historyValue) {
if (!transition.target) {
return [];
}
const targets = new Set();
for (const targetNode of transition.target) {
if (isHistoryNode(targetNode)) {
if (historyValue[targetNode.id]) {
for (const node of historyValue[targetNode.id]) {
targets.add(node);
}
} else {
for (const node of getEffectiveTargetStates(resolveHistoryDefaultTransition(targetNode), historyValue)) {
targets.add(node);
}
}
} else {
targets.add(targetNode);
}
}
return [...targets];
}
function getTransitionDomain(transition, historyValue) {
const targetStates = getEffectiveTargetStates(transition, historyValue);
if (!targetStates) {
return;
}
if (!transition.reenter && targetStates.every(target => target === transition.source || isDescendant(target, transition.source))) {
return transition.source;
}
const lca = findLeastCommonAncestor(targetStates.concat(transition.source));
if (lca) {
return lca;
}
// at this point we know that it's a root transition since LCA couldn't be found
if (transition.reenter) {
return;
}
return transition.source.machine.root;
}
function computeExitSet(transitions, stateNodeSet, historyValue) {
const statesToExit = new Set();
for (const t of transitions) {
if (t.target?.length) {
const domain = getTransitionDomain(t, historyValue);
if (t.reenter && t.source === domain) {
statesToExit.add(domain);
}
for (const stateNode of stateNodeSet) {
if (isDescendant(stateNode, domain)) {
statesToExit.add(stateNode);
}
}
}
}
return [...statesToExit];
}
function areStateNodeCollectionsEqual(prevStateNodes, nextStateNodeSet) {
if (prevStateNodes.length !== nextStateNodeSet.size) {
return false;
}
for (const node of prevStateNodes) {
if (!nextStateNodeSet.has(node)) {
return false;
}
}
return true;
}
/**
* https://www.w3.org/TR/scxml/#microstepProcedure
*/
function microstep(transitions, currentSnapshot, actorScope, event, isInitial, internalQueue) {
if (!transitions.length) {
return currentSnapshot;
}
const mutStateNodeSet = new Set(currentSnapshot._nodes);
let historyValue = currentSnapshot.historyValue;
const filteredTransitions = removeConflictingTransitions(transitions, mutStateNodeSet, historyValue);
let nextState = currentSnapshot;
// Exit states
if (!isInitial) {
[nextState, historyValue] = exitStates(nextState, event, actorScope, filteredTransitions, mutStateNodeSet, historyValue, internalQueue);
}
// Execute transition content
nextState = resolveActionsAndContext(nextState, event, actorScope, filteredTransitions.flatMap(t => t.actions), internalQueue);
// Enter states
nextState = enterStates(nextState, event, actorScope, filteredTransitions, mutStateNodeSet, internalQueue, historyValue, isInitial);
const nextStateNodes = [...mutStateNodeSet];
if (nextState.status === 'done') {
nextState = resolveActionsAndContext(nextState, event, actorScope, nextStateNodes.sort((a, b) => b.order - a.order).flatMap(state => state.exit), internalQueue);
}
try {
if (historyValue === currentSnapshot.historyValue && areStateNodeCollectionsEqual(currentSnapshot._nodes, mutStateNodeSet)) {
return nextState;
}
return cloneMachineSnapshot(nextState, {
_nodes: nextStateNodes,
historyValue
});
} catch (e) {
// TODO: Refactor this once proper error handling is implemented.
// See https://github.com/statelyai/rfcs/pull/4
throw e;
}
}
function getMachineOutput(snapshot, event, actorScope, rootNode, rootCompletionNode) {
if (rootNode.output === undefined) {
return;
}
const doneStateEvent = createDoneStateEvent(rootCompletionNode.id, rootCompletionNode.output !== undefined && rootCompletionNode.parent ? resolveOutput(rootCompletionNode.output, snapshot.context, event, actorScope.self) : undefined);
return resolveOutput(rootNode.output, snapshot.context, doneStateEvent, actorScope.self);
}
function enterStates(currentSnapshot, event, actorScope, filteredTransitions, mutStateNodeSet, internalQueue, historyValue, isInitial) {
let nextSnapshot = currentSnapshot;
const statesToEnter = new Set();
// those are states that were directly targeted or indirectly targeted by the explicit target
// in other words, those are states for which initial actions should be executed
// when we target `#deep_child` initial actions of its ancestors shouldn't be executed
const statesForDefaultEntry = new Set();
computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
// In the initial state, the root state node is "entered".
if (isInitial) {
statesForDefaultEntry.add(currentSnapshot.machine.root);
}
const completedNodes = new Set();
for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
mutStateNodeSet.add(stateNodeToEnter);
const actions = [];
// Add entry actions
actions.push(...stateNodeToEnter.entry);
for (const invokeDef of stateNodeToEnter.invoke) {
actions.push(spawnChild(invokeDef.src, {
...invokeDef,
syncSnapshot: !!invokeDef.onSnapshot
}));
}
if (statesForDefaultEntry.has(stateNodeToEnter)) {
const initialActions = stateNodeToEnter.initial.actions;
actions.push(...initialActions);
}
nextSnapshot = resolveActionsAndContext(nextSnapshot, event, actorScope, actions, internalQueue, stateNodeToEnter.invoke.map(invokeDef => invokeDef.id));
if (stateNodeToEnter.type === 'final') {
const parent = stateNodeToEnter.parent;
let ancestorMarker = parent?.type === 'parallel' ? parent : parent?.parent;
let rootCompletionNode = ancestorMarker || stateNodeToEnter;
if (parent?.type === 'compound') {
internalQueue.push(createDoneStateEvent(parent.id, stateNodeToEnter.output !== undefined ? resolveOutput(stateNodeToEnter.output, nextSnapshot.context, event, actorScope.self) : undefined));
}
while (ancestorMarker?.type === 'parallel' && !completedNodes.has(ancestorMarker) && isInFinalState(mutStateNodeSet, ancestorMarker)) {
completedNodes.add(ancestorMarker);
internalQueue.push(createDoneStateEvent(ancestorMarker.id));
rootCompletionNode = ancestorMarker;
ancestorMarker = ancestorMarker.parent;
}
if (ancestorMarker) {
continue;
}
nextSnapshot = cloneMachineSnapshot(nextSnapshot, {
status: 'done',
output: getMachineOutput(nextSnapshot, event, actorScope, nextSnapshot.machine.root, rootCompletionNode)
});
}
}
return nextSnapshot;
}
function computeEntrySet(transitions, historyValue, statesForDefaultEntry, statesToEnter) {
for (const t of transitions) {
const domain = getTransitionDomain(t, historyValue);
for (const s of t.target || []) {
if (!isHistoryNode(s) && (
// if the target is different than the source then it will *definitely* be entered
t.source !== s ||
// we know that the domain can't lie within the source
// if it's different than the source then it's outside of it and it means that the target has to be entered as well
t.source !== domain ||
// reentering transitions always enter the target, even if it's the source itself
t.reenter)) {
statesToEnter.add(s);
statesForDefaultEntry.add(s);
}
addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
}
const targetStates = getEffectiveTargetStates(t, historyValue);
for (const s of targetStates) {
const ancestors = getProperAncestors(s, domain);
if (domain?.type === 'parallel') {
ancestors.push(domain);
}
addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, ancestors, !t.source.parent && t.reenter ? undefined : domain);
}
}
}
function addDescendantStatesToEnter(stateNode, historyValue, statesForDefaultEntry, statesToEnter) {
if (isHistoryNode(stateNode)) {
if (historyValue[stateNode.id]) {
const historyStateNodes = historyValue[stateNode.id];
for (const s of historyStateNodes) {
statesToEnter.add(s);
addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
}
for (const s of historyStateNodes) {
addProperAncestorStatesToEnter(s, stateNode.parent, statesToEnter, historyValue, statesForDefaultEntry);
}
} else {
const historyDefaultTransition = resolveHistoryDefaultTransition(stateNode);
for (const s of historyDefaultTransition.target) {
statesToEnter.add(s);
if (historyDefaultTransition === stateNode.parent?.initial) {
statesForDefaultEntry.add(stateNode.parent);
}
addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
}
for (const s of historyDefaultTransition.target) {
addProperAncestorStatesToEnter(s, stateNode.parent, statesToEnter, historyValue, statesForDefaultEntry);
}
}
} else {
if (stateNode.type === 'compound') {
const [initialState] = stateNode.initial.target;
if (!isHistoryNode(initialState)) {
statesToEnter.add(initialState);
statesForDefaultEntry.add(initialState);
}
addDescendantStatesToEnter(initialState, historyValue, statesForDefaultEntry, statesToEnter);
addProperAncestorStatesToEnter(initialState, stateNode, statesToEnter, historyValue, statesForDefaultEntry);
} else {
if (stateNode.type === 'parallel') {
for (const child of getChildren(stateNode).filter(sn => !isHistoryNode(sn))) {
if (![...statesToEnter].some(s => isDescendant(s, child))) {
if (!isHistoryNode(child)) {
statesToEnter.add(child);
statesForDefaultEntry.add(child);
}
addDescendantStatesToEnter(child, historyValue, statesForDefaultEntry, statesToEnter);
}
}
}
}
}
}
function addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, ancestors, reentrancyDomain) {
for (const anc of ancestors) {
if (!reentrancyDomain || isDescendant(anc, reentrancyDomain)) {
statesToEnter.add(anc);
}
if (anc.type === 'parallel') {
for (const child of getChildren(anc).filter(sn => !isHistoryNode(sn))) {
if (![...statesToEnter].some(s => isDescendant(s, child))) {
statesToEnter.add(child);
addDescendantStatesToEnter(child, historyValue, statesForDefaultEntry, statesToEnter);
}
}
}
}
}
function addProperAncestorStatesToEnter(stateNode, toStateNode, statesToEnter, historyValue, statesForDefaultEntry) {
addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, getProperAncestors(stateNode, toStateNode));
}
function exitStates(currentSnapshot, event, actorScope, transitions, mutStateNodeSet, historyValue, internalQueue) {
let nextSnapshot = currentSnapshot;
const statesToExit = computeExitSet(transitions, mutStateNodeSet, historyValue);
statesToExit.sort((a, b) => b.order - a.order);
let changedHistory;
// From SCXML algorithm: https://www.w3.org/TR/scxml/#exitStates
for (const exitStateNode of statesToExit) {
for (const historyNode of getHistoryNodes(exitStateNode)) {
let predicate;
if (historyNode.history === 'deep') {
predicate = sn => isAtomicStateNode(sn) && isDescendant(sn, exitStateNode);
} else {
predicate = sn => {
return sn.parent === exitStateNode;
};
}
changedHistory ??= {
...historyValue
};
changedHistory[historyNode.id] = Array.from(mutStateNodeSet).filter(predicate);
}
}
for (const s of statesToExit) {
nextSnapshot = resolveActionsAndContext(nextSnapshot, event, actorScope, [...s.exit, ...s.invoke.map(def => stopChild(def.id))], internalQueue);
mutStateNodeSet.delete(s);
}
return [nextSnapshot, changedHistory || historyValue];
}
function resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope, actions, extra, retries) {
const {
machine
} = currentSnapshot;
let intermediateSnapshot = currentSnapshot;
for (const action of actions) {
const isInline = typeof action === 'function';
const resolvedAction = isInline ? action :
// the existing type of `.actions` assumes non-nullable `TExpressionAction`
// it's fine to cast this here to get a common type and lack of errors in the rest of the code
// our logic below makes sure that we call those 2 "variants" correctly
machine.implementations.actions[typeof action === 'string' ? action : action.type];
if (!resolvedAction) {
continue;
}
const actionArgs = {
context: intermediateSnapshot.context,
event,
self: actorScope.self,
system: actorScope.system
};
const actionParams = isInline || typeof action === 'string' ? undefined : 'params' in action ? typeof action.params === 'function' ? action.params({
context: intermediateSnapshot.context,
event
}) : action.params : undefined;
function executeAction() {
actorScope.system._sendInspectionEvent({
type: '@xstate.action',
actorRef: actorScope.self,
action: {
type: typeof action === 'string' ? action : typeof action === 'object' ? action.type : action.name || '(anonymous)',
params: actionParams
}
});
resolvedAction(actionArgs, actionParams);
}
if (!('resolve' in resolvedAction)) {
if (actorScope.self._processingStatus === ProcessingStatus.Running) {
executeAction();
} else {
actorScope.defer(() => {
executeAction();
});
}
continue;
}
const builtinAction = resolvedAction;
const [nextState, params, actions] = builtinAction.resolve(actorScope, intermediateSnapshot, actionArgs, actionParams, resolvedAction,
// this holds all params
extra);
intermediateSnapshot = nextState;
if ('retryResolve' in builtinAction) {
retries?.push([builtinAction, params]);
}
if ('execute' in builtinAction) {
if (actorScope.self._processingStatus === ProcessingStatus.Running) {
builtinAction.execute(actorScope, params);
} else {
actorScope.defer(builtinAction.execute.bind(null, actorScope, params));
}
}
if (actions) {
intermediateSnapshot = resolveAndExecuteActionsWithContext(intermediateSnapshot, event, actorScope, actions, extra, retries);
}
}
return intermediateSnapshot;
}
function resolveActionsAndContext(currentSnapshot, event, actorScope, actions, internalQueue, deferredActorIds) {
const retries = deferredActorIds ? [] : undefined;
const nextState = resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope, actions, {
internalQueue,
deferredActorIds
}, retries);
retries?.forEach(([builtinAction, params]) => {
builtinAction.retryResolve(actorScope, nextState, params);
});
return nextState;
}
function macrostep(snapshot, event, actorScope, internalQueue = []) {
let nextSnapshot = snapshot;
const microstates = [];
function addMicrostate(microstate, event, transitions) {
actorScope.system._sendInspectionEvent({
type: '@xstate.microstep',
actorRef: actorScope.self,
event,
snapshot: microstate,
_transitions: transitions
});
microstates.push(microstate);
}
// Handle stop event
if (event.type === XSTATE_STOP) {
nextSnapshot = cloneMachineSnapshot(stopChildren(nextSnapshot, event, actorScope), {
status: 'stopped'
});
addMicrostate(nextSnapshot, event, []);
return {
snapshot: nextSnapshot,
microstates
};
}
let nextEvent = event;
// Assume the state is at rest (no raised events)
// Determine the next state based on the next microstep
if (nextEvent.type !== XSTATE_INIT) {
const currentEvent = nextEvent;
const isErr = isErrorActorEvent(currentEvent);
const transitions = selectTransitions(currentEvent, nextSnapshot);
if (isErr && !transitions.length) {
// TODO: we should likely only allow transitions selected by very explicit descriptors
// `*` shouldn't be matched, likely `xstate.error.*` shouldnt be either
// similarly `xstate.error.actor.*` and `xstate.error.actor.todo.*` have to be considered too
nextSnapshot = cloneMachineSnapshot(snapshot, {
status: 'error',
error: currentEvent.error
});
addMicrostate(nextSnapshot, currentEvent, []);
return {
snapshot: nextSnapshot,
microstates
};
}
nextSnapshot = microstep(transitions, snapshot, actorScope, nextEvent, false,
// isInitial
internalQueue);
addMicrostate(nextSnapshot, currentEvent, transitions);
}
let shouldSelectEventlessTransitions = true;
while (nextSnapshot.status === 'active') {
let enabledTransitions = shouldSelectEventlessTransitions ? selectEventlessTransitions(nextSnapshot, nextEvent) : [];
// eventless transitions should always be selected after selecting *regular* transitions
// by assigning `undefined` to `previousState` we ensure that `shouldSelectEventlessTransitions` gets always computed to true in such a case
const previousState = enabledTransitions.length ? nextSnapshot : undefined;
if (!enabledTransitions.length) {
if (!internalQueue.length) {
break;
}
nextEvent = internalQueue.shift();
enabledTransitions = selectTransitions(nextEvent, nextSnapshot);
}
nextSnapshot = microstep(enabledTransitions, nextSnapshot, actorScope, nextEvent, false, internalQueue);
shouldSelectEventlessTransitions = nextSnapshot !== previousState;
addMicrostate(nextSnapshot, nextEvent, enabledTransitions);
}
if (nextSnapshot.status !== 'active') {
stopChildren(nextSnapshot, nextEvent, actorScope);
}
return {
snapshot: nextSnapshot,
microstates
};
}
function stopChildren(nextState, event, actorScope) {
return resolveActionsAndContext(nextState, event, actorScope, Object.values(nextState.children).map(child => stopChild(child)), []);
}
function selectTransitions(event, nextState) {
return nextState.machine.getTransitionData(nextState, event);
}
function selectEventlessTransitions(nextState, event) {
const enabledTransitionSet = new Set();
const atomicStates = nextState._nodes.filter(isAtomicStateNode);
for (const stateNode of atomicStates) {
loop: for (const s of [stateNode].concat(getProperAncestors(stateNode, undefined))) {
if (!s.always) {
continue;
}
for (const transition of s.always) {
if (transition.guard === undefined || evaluateGuard(transition.guard, nextState.context, event, nextState)) {
enabledTransitionSet.add(transition);
break loop;
}
}
}
}
return removeConflictingTransitions(Array.from(enabledTransitionSet), new Set(nextState._nodes), nextState.historyValue);
}
/**
* Resolves a partial state value with its full representation in the state node's machine.
*
* @param stateValue The partial state value to resolve.
*/
function resolveStateValue(rootNode, stateValue) {
const allStateNodes = getAllStateNodes(getStateNodes(rootNode, stateValue));
return getStateValue(rootNode, [...allStateNodes]);
}
function isMachineSnapshot(value) {
return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
}
const machineSnapshotMatches = function matches(testValue) {
return matchesState(testValue, this.value);
};
const machineSnapshotHasTag = function hasTag(tag) {
return this.tags.has(tag);
};
const machineSnapshotCan = function can(event) {
const transitionData = this.machine.getTransitionData(this, event);
return !!transitionData?.length &&
// Check that at least one transition is not forbidden
transitionData.some(t => t.target !== undefined || t.actions.length);
};
const machineSnapshotToJSON = function toJSON() {
const {
_nodes: nodes,
tags,
machine,
getMeta,
toJSON,
can,
hasTag,
matches,
...jsonValues
} = this;
return {
...jsonValues,
tags: Array.from(tags)
};
};
const machineSnapshotGetMeta = function getMeta() {
return this._nodes.reduce((acc, stateNode) => {
if (stateNode.meta !== undefined) {
acc[stateNode.id] = stateNode.meta;
}
return acc;
}, {});
};
function createMachineSnapshot(config, machine) {
return {
status: config.status,
output: config.output,
error: config.error,
machine,
context: config.context,
_nodes: config._nodes,
value: getStateValue(machine.root, config._nodes),
tags: new Set(config._nodes.flatMap(sn => sn.tags)),
children: config.children,
historyValue: config.historyValue || {},
matches: machineSnapshotMatches,
hasTag: machineSnapshotHasTag,
can: machineSnapshotCan,
getMeta: machineSnapshotGetMeta,
toJSON: machineSnapshotToJSON
};
}
function cloneMachineSnapshot(snapshot, config = {}) {
return createMachineSnapshot({
...snapshot,
...config
}, snapshot.machine);
}
function getPersistedSnapshot(snapshot, options) {
const {
_nodes: nodes,
tags,
machine,
children,
context,
can,
hasTag,
matches,
getMeta,
toJSON,
...jsonValues
} = snapshot;
const childrenJson = {};
for (const id in children) {
const child = children[id];
childrenJson[id] = {
snapshot: child.getPersistedSnapshot(options),
src: child.src,
systemId: child._systemId,
syncSnapshot: child._syncSnapshot
};
}
const persisted = {
...jsonValues,
context: persistContext(context),
children: childrenJson
};
return persisted;
}
function persistContext(contextPart) {
let copy;
for (const key in contextPart) {
const value = contextPart[key];
if (value && typeof value === 'object') {
if ('sessionId' in value && 'send' in value && 'ref' in value) {
copy ??= Array.isArray(contextPart) ? contextPart.slice() : {
...contextPart
};
copy[key] = {
xstate$$type: $$ACTOR_TYPE,
id: value.id
};
} else {
const result = persistContext(value);
if (result !== value) {
copy ??= Array.isArray(contextPart) ? contextPart.slice() : {
...contextPart
};
copy[key] = result;
}
}
}
}
return copy ?? contextPart;
}
function resolveRaise(_, snapshot, args, actionParams, {
event: eventOrExpr,
id,
delay
}, {
internalQueue
}) {
const delaysMap = snapshot.machine.implementations.delays;
if (typeof eventOrExpr === 'string') {
throw new Error(`Only event objects may be used with raise; use raise({ type: "${eventOrExpr}" }) instead`);
}
const resolvedEvent = typeof eventOrExpr === 'function' ? eventOrExpr(args, actionParams) : eventOrExpr;
let resolvedDelay;
if (typeof delay === 'string') {
const configDelay = delaysMap && delaysMap[delay];
resolvedDelay = typeof configDelay === 'function' ? configDelay(args, actionParams) : configDelay;
} else {
resolvedDelay = typeof delay === 'function' ? delay(args, actionParams) : delay;
}
if (typeof resolvedDelay !== 'number') {
internalQueue.push(resolvedEvent);
}
return [snapshot, {
event: resolvedEvent,
id,
delay: resolvedDelay
}];
}
function executeRaise(actorScope, params) {
const {
event,
delay,
id
} = params;
if (typeof delay === 'number') {
actorScope.defer(() => {
const self = actorScope.self;
actorScope.system.scheduler.schedule(self, self, event, delay, id);
});
return;
}
}
/**
* 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(eventOrExpr, options) {
function raise(args, params) {
}
raise.type = 'xstate.raise';
raise.event = eventOrExpr;
raise.id = options?.id;
raise.delay = options?.delay;
raise.resolve = resolveRaise;
raise.execute = executeRaise;
return raise;
}
exports.$$ACTOR_TYPE = $$ACTOR_TYPE;
exports.Actor = Actor;
exports.NULL_EVENT = NULL_EVENT;
exports.ProcessingStatus = ProcessingStatus;
exports.STATE_DELIMITER = STATE_DELIMITER;
exports.XSTATE_ERROR = XSTATE_ERROR;
exports.XSTATE_STOP = XSTATE_STOP;
exports.and = and;
exports.cancel = cancel;
exports.cloneMachineSnapshot = cloneMachineSnapshot;
exports.createActor = createActor;
exports.createErrorActorEvent = createErrorActorEvent;
exports.createInitEvent = createInitEvent;
exports.createInvokeId = createInvokeId;
exports.createMachineSnapshot = createMachineSnapshot;
exports.evaluateGuard = evaluateGuard;
exports.formatInitialTransition = formatInitialTransition;
exports.formatTransition = formatTransition;
exports.formatTransitions = formatTransitions;
exports.getAllOwnEventDescriptors = getAllOwnEventDescriptors;
exports.getAllStateNodes = getAllStateNodes;
exports.getCandidates = getCandidates;
exports.getDelayedTransitions = getDelayedTransitions;
exports.getInitialStateNodes = getInitialStateNodes;
exports.getPersistedSnapshot = getPersistedSnapshot;
exports.getStateNodeByPath = getStateNodeByPath;
exports.getStateNodes = getStateNodes;
exports.interpret = interpret;
exports.isInFinalState = isInFinalState;
exports.isMachineSnapshot = isMachineSnapshot;
exports.isStateId = isStateId;
exports.macrostep = macrostep;
exports.mapValues = mapValues;
exports.matchesState = matchesState;
exports.microstep = microstep;
exports.not = not;
exports.or = or;
exports.pathToStateValue = pathToStateValue;
exports.raise = raise;
exports.resolveActionsAndContext = resolveActionsAndContext;
exports.resolveReferencedActor = resolveReferencedActor;
exports.resolveStateValue = resolveStateValue;
exports.spawnChild = spawnChild;
exports.stateIn = stateIn;
exports.stop = stop;
exports.stopChild = stopChild;
exports.toArray = toArray;
exports.toObserver = toObserver;
exports.toStatePath = toStatePath;
exports.toTransitionConfigArray = toTransitionConfigArray;
exports.transitionNode = transitionNode;