UNPKG

xstate

Version:

Finite State Machines and Statecharts for the Modern Web.

492 lines (467 loc) 14.3 kB
import { T as ProcessingStatus, z as resolveReferencedActor, A as createActor, U as cloneMachineSnapshot, V as XSTATE_ERROR, W as createErrorActorEvent, e as evaluateGuard, M as cancel, O as raise, P as spawnChild, R as stopChild } from './raise-040ba012.esm.js'; // it's likely-ish that `(TActor & { src: TSrc })['logic']` would be faster // but it's only possible to do it since https://github.com/microsoft/TypeScript/pull/53098 (TS 5.1) // and we strive to support TS 5.0 whenever possible function createSpawner(actorScope, { machine, context }, event, spawnedChildren) { const spawn = (src, options = {}) => { const { systemId, input } = options; if (typeof src === 'string') { const logic = resolveReferencedActor(machine, src); if (!logic) { throw new Error(`Actor logic '${src}' not implemented in machine '${machine.id}'`); } const actorRef = createActor(logic, { id: options.id, parent: actorScope.self, syncSnapshot: options.syncSnapshot, input: typeof input === 'function' ? input({ context, event, self: actorScope.self }) : input, src, systemId }); spawnedChildren[actorRef.id] = actorRef; return actorRef; } else { const actorRef = createActor(src, { id: options.id, parent: actorScope.self, syncSnapshot: options.syncSnapshot, input: options.input, src, systemId }); return actorRef; } }; return (src, options) => { const actorRef = spawn(src, options); // TODO: fix types spawnedChildren[actorRef.id] = actorRef; actorScope.defer(() => { if (actorRef._processingStatus === ProcessingStatus.Stopped) { return; } actorRef.start(); }); return actorRef; }; } function resolveAssign(actorScope, snapshot, actionArgs, actionParams, { assignment }) { if (!snapshot.context) { throw new Error('Cannot assign to undefined `context`. Ensure that `context` is defined in the machine config.'); } const spawnedChildren = {}; const assignArgs = { context: snapshot.context, event: actionArgs.event, spawn: createSpawner(actorScope, snapshot, actionArgs.event, spawnedChildren), self: actorScope.self, system: actorScope.system }; let partialUpdate = {}; if (typeof assignment === 'function') { partialUpdate = assignment(assignArgs, actionParams); } else { for (const key of Object.keys(assignment)) { const propAssignment = assignment[key]; partialUpdate[key] = typeof propAssignment === 'function' ? propAssignment(assignArgs, actionParams) : propAssignment; } } const updatedContext = Object.assign({}, snapshot.context, partialUpdate); return [cloneMachineSnapshot(snapshot, { context: updatedContext, children: Object.keys(spawnedChildren).length ? { ...snapshot.children, ...spawnedChildren } : snapshot.children })]; } /** * Updates the current context of the machine. * * @param assignment An object that represents the partial context to update, or a * function that returns an object that represents the partial context to update. * * @example ```ts import { createMachine, assign } from 'xstate'; const countMachine = createMachine({ context: { count: 0, message: '' }, on: { inc: { actions: assign({ count: ({ context }) => context.count + 1 }) }, updateMessage: { actions: assign(({ context, event }) => { return { message: event.message.trim() } }) } } }); ``` */ function assign(assignment) { function assign(args, params) { } assign.type = 'xstate.assign'; assign.assignment = assignment; assign.resolve = resolveAssign; return assign; } function resolveEmit(_, snapshot, args, actionParams, { event: eventOrExpr }) { const resolvedEvent = typeof eventOrExpr === 'function' ? eventOrExpr(args, actionParams) : eventOrExpr; return [snapshot, { event: resolvedEvent }]; } function executeEmit(actorScope, { event }) { actorScope.defer(() => actorScope.emit(event)); } /** * Emits an event to event handlers registered on the actor via `actor.on(event, handler)`. * * @example ```ts import { emit } from 'xstate'; const machine = createMachine({ // ... on: { something: { actions: emit({ type: 'emitted', some: 'data' }) } } // ... }); const actor = createActor(machine).start(); actor.on('emitted', (event) => { console.log(event); }); actor.send({ type: 'something' }); // logs: // { // type: 'emitted', // some: 'data' // } ``` */ function emit( /** * The event to emit, or an expression that returns an event to emit. */ eventOrExpr) { function emit(args, params) { } emit.type = 'xstate.emit'; emit.event = eventOrExpr; emit.resolve = resolveEmit; emit.execute = executeEmit; return emit; } /** * * @remarks * * `T | unknown` reduces to `unknown` and that can be problematic when it comes to contextual typing. * It especially is a problem when the union has a function member, like here: * * ```ts * declare function test(cbOrVal: ((arg: number) => unknown) | unknown): void; * test((arg) => {}) // oops, implicit any * ``` * * This type can be used to avoid this problem. This union represents the same value space as `unknown`. */ // https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887 // @TODO: Replace with native `NoInfer` when TS issue gets fixed: // https://github.com/microsoft/TypeScript/pull/57673 /** * @deprecated Use the built-in `NoInfer` type instead */ /** * The full definition of an event, with a string `type`. */ /** * The string or object representing the state value relative to the parent state node. * * @remarks * * - For a child atomic state node, this is a string, e.g., `"pending"`. * * - For complex state nodes, this is an object, e.g., `{ success: "someChildState" }`. */ // TODO: remove once TS fixes this type-widening issue /** @deprecated use `AnyMachineSnapshot` instead */ // TODO: possibly refactor this somehow, use even a simpler type, and maybe even make `machine.options` private or something /** * @hidden */ let SpecialTargets = /*#__PURE__*/function (SpecialTargets) { SpecialTargets["Parent"] = "#_parent"; SpecialTargets["Internal"] = "#_internal"; return SpecialTargets; }({}); /** * @deprecated Use `AnyActor` instead. */ // Based on RxJS types /** * @deprecated Use `Actor<T>` instead. */ // only meant to be used internally for debugging purposes /** * Represents logic which can be used by an actor. * * @template TSnapshot - The type of the snapshot. * @template TEvent - The type of the event object. * @template TInput - The type of the input. * @template TSystem - The type of the actor system. */ function resolveSendTo(actorScope, snapshot, args, actionParams, { to, event: eventOrExpr, id, delay }, extra) { const delaysMap = snapshot.machine.implementations.delays; if (typeof eventOrExpr === 'string') { throw new Error(`Only event objects may be used with sendTo; use sendTo({ 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; } const resolvedTarget = typeof to === 'function' ? to(args, actionParams) : to; let targetActorRef; if (typeof resolvedTarget === 'string') { if (resolvedTarget === SpecialTargets.Parent) { targetActorRef = actorScope.self._parent; } else if (resolvedTarget === SpecialTargets.Internal) { targetActorRef = actorScope.self; } else if (resolvedTarget.startsWith('#_')) { // SCXML compatibility: https://www.w3.org/TR/scxml/#SCXMLEventProcessor // #_invokeid. If the target is the special term '#_invokeid', where invokeid is the invokeid of an SCXML session that the sending session has created by <invoke>, the Processor must add the event to the external queue of that session. targetActorRef = snapshot.children[resolvedTarget.slice(2)]; } else { targetActorRef = extra.deferredActorIds?.includes(resolvedTarget) ? resolvedTarget : snapshot.children[resolvedTarget]; } if (!targetActorRef) { throw new Error(`Unable to send event to actor '${resolvedTarget}' from machine '${snapshot.machine.id}'.`); } } else { targetActorRef = resolvedTarget || actorScope.self; } return [snapshot, { to: targetActorRef, event: resolvedEvent, id, delay: resolvedDelay }]; } function retryResolveSendTo(_, snapshot, params) { if (typeof params.to === 'string') { params.to = snapshot.children[params.to]; } } function executeSendTo(actorScope, params) { // this forms an outgoing events queue // thanks to that the recipient actors are able to read the *updated* snapshot value of the sender actorScope.defer(() => { const { to, event, delay, id } = params; if (typeof delay === 'number') { actorScope.system.scheduler.schedule(actorScope.self, to, event, delay, id); return; } actorScope.system._relay(actorScope.self, // at this point, in a deferred task, it should already be mutated by retryResolveSendTo // if it initially started as a string to, event.type === XSTATE_ERROR ? createErrorActorEvent(actorScope.self.id, event.data) : event); }); } /** * 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 * - `id` - The unique send event identifier (used with `cancel()`). * - `delay` - The number of milliseconds to delay the sending of the event. */ function sendTo(to, eventOrExpr, options) { function sendTo(args, params) { } sendTo.type = 'xsnapshot.sendTo'; sendTo.to = to; sendTo.event = eventOrExpr; sendTo.id = options?.id; sendTo.delay = options?.delay; sendTo.resolve = resolveSendTo; sendTo.retryResolve = retryResolveSendTo; sendTo.execute = executeSendTo; return sendTo; } /** * 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 sendTo(SpecialTargets.Parent, event, options); } /** * Forwards (sends) an event to the `target` actor. * * @param target The target actor to forward the event to. * @param options Options to pass into the send action creator. */ function forwardTo(target, options) { return sendTo(target, ({ event }) => event, options); } function resolveEnqueueActions(actorScope, snapshot, args, actionParams, { collect }) { const actions = []; const enqueue = function enqueue(action) { actions.push(action); }; enqueue.assign = (...args) => { actions.push(assign(...args)); }; enqueue.cancel = (...args) => { actions.push(cancel(...args)); }; enqueue.raise = (...args) => { // for some reason it fails to infer `TDelay` from `...args` here and picks its default (`never`) // then it fails to typecheck that because `...args` use `string` in place of `TDelay` actions.push(raise(...args)); }; enqueue.sendTo = (...args) => { // for some reason it fails to infer `TDelay` from `...args` here and picks its default (`never`) // then it fails to typecheck that because `...args` use `string` in place of `TDelay actions.push(sendTo(...args)); }; enqueue.spawnChild = (...args) => { actions.push(spawnChild(...args)); }; enqueue.stopChild = (...args) => { actions.push(stopChild(...args)); }; enqueue.emit = (...args) => { actions.push(emit(...args)); }; collect({ context: args.context, event: args.event, enqueue, check: guard => evaluateGuard(guard, snapshot.context, args.event, snapshot), self: actorScope.self, system: actorScope.system }, actionParams); return [snapshot, undefined, actions]; } /** * Creates an action object that will execute actions that are queued by the `enqueue(action)` function. * * @example ```ts import { createMachine, enqueueActions } from 'xstate'; const machine = createMachine({ entry: enqueueActions(({ enqueue, check }) => { enqueue.assign({ count: 0 }); if (check('someGuard')) { enqueue.assign({ count: 1 }); } enqueue('someAction'); }) }) ``` */ function enqueueActions(collect) { function enqueueActions(args, params) { } enqueueActions.type = 'xstate.enqueueActions'; enqueueActions.collect = collect; enqueueActions.resolve = resolveEnqueueActions; return enqueueActions; } function resolveLog(_, snapshot, actionArgs, actionParams, { value, label }) { return [snapshot, { value: typeof value === 'function' ? value(actionArgs, actionParams) : value, label }]; } function executeLog({ logger }, { value, label }) { if (label) { logger(label, value); } else { logger(value); } } /** * * @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(value = ({ context, event }) => ({ context, event }), label) { function log(args, params) { } log.type = 'xstate.log'; log.value = value; log.label = label; log.resolve = resolveLog; log.execute = executeLog; return log; } export { SpecialTargets as S, assign as a, enqueueActions as b, sendTo as c, emit as e, forwardTo as f, log as l, sendParent as s };