UNPKG

xstate

Version:

Finite State Machines and Statecharts for the Modern Web.

410 lines (391 loc) 11.3 kB
export { createEmptyActor, fromCallback, fromEventObservable, fromObservable, fromPromise, fromTransition } from '../actors/dist/xstate-actors.esm.js'; import { t as toArray, c as createActor } from './raise-b0a4e862.esm.js'; export { A as Actor, d as __unsafe_getAllOwnEventDescriptors, a as and, f as cancel, c as createActor, g as getStateNodes, i as interpret, b as isMachineSnapshot, m as matchesState, n as not, o as or, p as pathToStateValue, r as raise, h as spawnChild, s as stateIn, j as stop, k as stopChild, e as toObserver } from './raise-b0a4e862.esm.js'; import { S as StateMachine } from './StateMachine-c88ea5dd.esm.js'; export { S as StateMachine, a as StateNode } from './StateMachine-c88ea5dd.esm.js'; export { S as SpecialTargets, e as emit, a as enqueueActions, f as forwardTo, l as log, s as sendParent, b as sendTo } from './log-1c257a58.esm.js'; export { a as assign } from './assign-c3259787.esm.js'; import '../dev/dist/xstate-dev.esm.js'; /** * Asserts that the given event object is of the specified type or types. Throws * an error if the event object is not of the specified types. * * @example * * ```ts * // ... * entry: ({ event }) => { * assertEvent(event, 'doNothing'); * // event is { type: 'doNothing' } * }, * // ... * exit: ({ event }) => { * assertEvent(event, 'greet'); * // event is { type: 'greet'; message: string } * * assertEvent(event, ['greet', 'notify']); * // event is { type: 'greet'; message: string } * // or { type: 'notify'; message: string; level: 'info' | 'error' } * }, * ``` */ function assertEvent(event, type) { const types = toArray(type); if (!types.includes(event.type)) { const typesText = types.length === 1 ? `type "${types[0]}"` : `one of types "${types.join('", "')}"`; throw new Error(`Expected event ${JSON.stringify(event)} to have ${typesText}`); } } /** * Creates a state machine (statechart) with the given configuration. * * The state machine represents the pure logic of a state machine actor. * * @example * * ```ts * import { createMachine } from 'xstate'; * * const lightMachine = createMachine({ * id: 'light', * initial: 'green', * states: { * green: { * on: { * TIMER: { target: 'yellow' } * } * }, * yellow: { * on: { * TIMER: { target: 'red' } * } * }, * red: { * on: { * TIMER: { target: 'green' } * } * } * } * }); * * const lightActor = createActor(lightMachine); * lightActor.start(); * * lightActor.send({ type: 'TIMER' }); * ``` * * @param config The state machine configuration. * @param options DEPRECATED: use `setup({ ... })` or `machine.provide({ ... })` * to provide machine implementations instead. */ function createMachine(config, implementations) { return new StateMachine(config, implementations); } /** @internal */ function createInertActorScope(actorLogic) { const self = createActor(actorLogic); const inertActorScope = { self, defer: () => {}, id: '', logger: () => {}, sessionId: '', stopChild: () => {}, system: self.system, emit: () => {}, actionExecutor: () => {} }; return inertActorScope; } /** @deprecated Use `initialTransition(…)` instead. */ function getInitialSnapshot(actorLogic, ...[input]) { const actorScope = createInertActorScope(actorLogic); return actorLogic.getInitialSnapshot(actorScope, input); } /** * Determines the next snapshot for the given `actorLogic` based on the given * `snapshot` and `event`. * * If the `snapshot` is `undefined`, the initial snapshot of the `actorLogic` is * used. * * @deprecated Use `transition(…)` instead. * @example * * ```ts * import { getNextSnapshot } from 'xstate'; * import { trafficLightMachine } from './trafficLightMachine.ts'; * * const nextSnapshot = getNextSnapshot( * trafficLightMachine, // actor logic * undefined, // snapshot (or initial state if undefined) * { type: 'TIMER' } * ); // event object * * console.log(nextSnapshot.value); * // => 'yellow' * * const nextSnapshot2 = getNextSnapshot( * trafficLightMachine, // actor logic * nextSnapshot, // snapshot * { type: 'TIMER' } * ); // event object * * console.log(nextSnapshot2.value); * // =>'red' * ``` */ function getNextSnapshot(actorLogic, snapshot, event) { const inertActorScope = createInertActorScope(actorLogic); inertActorScope.self._snapshot = snapshot; return actorLogic.transition(snapshot, event, inertActorScope); } // at the moment we allow extra actors - ones that are not specified by `children` // this could be reconsidered in the future function setup({ schemas, actors, actions, guards, delays }) { return { createMachine: config => createMachine({ ...config, schemas }, { actors, actions, guards, delays }) }; } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class SimulatedClock { constructor() { this.timeouts = new Map(); this._now = 0; this._id = 0; this._flushing = false; this._flushingInvalidated = false; } now() { return this._now; } getId() { return this._id++; } setTimeout(fn, timeout) { this._flushingInvalidated = this._flushing; const id = this.getId(); this.timeouts.set(id, { start: this.now(), timeout, fn }); return id; } clearTimeout(id) { this._flushingInvalidated = this._flushing; this.timeouts.delete(id); } set(time) { if (this._now > time) { throw new Error('Unable to travel back in time'); } this._now = time; this.flushTimeouts(); } flushTimeouts() { if (this._flushing) { this._flushingInvalidated = true; return; } this._flushing = true; const sorted = [...this.timeouts].sort(([_idA, timeoutA], [_idB, timeoutB]) => { const endA = timeoutA.start + timeoutA.timeout; const endB = timeoutB.start + timeoutB.timeout; return endB > endA ? -1 : 1; }); for (const [id, timeout] of sorted) { if (this._flushingInvalidated) { this._flushingInvalidated = false; this._flushing = false; this.flushTimeouts(); return; } if (this.now() - timeout.start >= timeout.timeout) { this.timeouts.delete(id); timeout.fn.call(null); } } this._flushing = false; } increment(ms) { this._now += ms; this.flushTimeouts(); } } /** * Returns a promise that resolves to the `output` of the actor when it is done. * * @example * * ```ts * const machine = createMachine({ * // ... * output: { * count: 42 * } * }); * * const actor = createActor(machine); * * actor.start(); * * const output = await toPromise(actor); * * console.log(output); * // logs { count: 42 } * ``` */ function toPromise(actor) { return new Promise((resolve, reject) => { actor.subscribe({ complete: () => { resolve(actor.getSnapshot().output); }, error: reject }); }); } /** * Given actor `logic`, a `snapshot`, and an `event`, returns a tuple of the * `nextSnapshot` and `actions` to execute. * * This is a pure function that does not execute `actions`. */ function transition(logic, snapshot, event) { const executableActions = []; const actorScope = createInertActorScope(logic); actorScope.actionExecutor = action => { executableActions.push(action); }; const nextSnapshot = logic.transition(snapshot, event, actorScope); return [nextSnapshot, executableActions]; } /** * Given actor `logic` and optional `input`, returns a tuple of the * `nextSnapshot` and `actions` to execute from the initial transition (no * previous state). * * This is a pure function that does not execute `actions`. */ function initialTransition(logic, ...[input]) { const executableActions = []; const actorScope = createInertActorScope(logic); actorScope.actionExecutor = action => { executableActions.push(action); }; const nextSnapshot = logic.getInitialSnapshot(actorScope, input); return [nextSnapshot, executableActions]; } const defaultWaitForOptions = { timeout: Infinity // much more than 10 seconds }; /** * Subscribes to an actor ref and waits for its emitted value to satisfy a * predicate, and then resolves with that value. Will throw if the desired state * is not reached after an optional timeout. (defaults to Infinity). * * @example * * ```js * const state = await waitFor(someService, (state) => { * return state.hasTag('loaded'); * }); * * state.hasTag('loaded'); // true * ``` * * @param actorRef The actor ref to subscribe to * @param predicate Determines if a value matches the condition to wait for * @param options * @returns A promise that eventually resolves to the emitted value that matches * the condition */ function waitFor(actorRef, predicate, options) { const resolvedOptions = { ...defaultWaitForOptions, ...options }; return new Promise((res, rej) => { const { signal } = resolvedOptions; if (signal?.aborted) { // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors rej(signal.reason); return; } let done = false; const handle = resolvedOptions.timeout === Infinity ? undefined : setTimeout(() => { dispose(); rej(new Error(`Timeout of ${resolvedOptions.timeout} ms exceeded`)); }, resolvedOptions.timeout); const dispose = () => { clearTimeout(handle); done = true; sub?.unsubscribe(); if (abortListener) { signal.removeEventListener('abort', abortListener); } }; function checkEmitted(emitted) { if (predicate(emitted)) { dispose(); res(emitted); } } /** * If the `signal` option is provided, this will be the listener for its * `abort` event */ let abortListener; // eslint-disable-next-line prefer-const let sub; // avoid TDZ when disposing synchronously // See if the current snapshot already matches the predicate checkEmitted(actorRef.getSnapshot()); if (done) { return; } // only define the `abortListener` if the `signal` option is provided if (signal) { abortListener = () => { dispose(); // XState does not "own" the signal, so we should reject with its reason (if any) // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors rej(signal.reason); }; signal.addEventListener('abort', abortListener); } sub = actorRef.subscribe({ next: checkEmitted, error: err => { dispose(); // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors rej(err); }, complete: () => { dispose(); rej(new Error(`Actor terminated without satisfying predicate`)); } }); if (done) { sub.unsubscribe(); } }); } export { SimulatedClock, assertEvent, createMachine, getInitialSnapshot, getNextSnapshot, initialTransition, setup, toPromise, transition, waitFor };