UNPKG

82.4 kBJavaScriptView Raw
1'use strict';
2
3var dev_dist_xstateDev = require('../dev/dist/xstate-dev.cjs.js');
4
5class Mailbox {
6 constructor(_process) {
7 this._process = _process;
8 this._active = false;
9 this._current = null;
10 this._last = null;
11 }
12 start() {
13 this._active = true;
14 this.flush();
15 }
16 clear() {
17 // we can't set _current to null because we might be currently processing
18 // and enqueue following clear shouldnt start processing the enqueued item immediately
19 if (this._current) {
20 this._current.next = null;
21 this._last = this._current;
22 }
23 }
24 enqueue(event) {
25 const enqueued = {
26 value: event,
27 next: null
28 };
29 if (this._current) {
30 this._last.next = enqueued;
31 this._last = enqueued;
32 return;
33 }
34 this._current = enqueued;
35 this._last = enqueued;
36 if (this._active) {
37 this.flush();
38 }
39 }
40 flush() {
41 while (this._current) {
42 // atm the given _process is responsible for implementing proper try/catch handling
43 // we assume here that this won't throw in a way that can affect this mailbox
44 const consumed = this._current;
45 this._process(consumed.value);
46 this._current = consumed.next;
47 }
48 this._last = null;
49 }
50}
51
52const STATE_DELIMITER = '.';
53const TARGETLESS_KEY = '';
54const NULL_EVENT = '';
55const STATE_IDENTIFIER = '#';
56const WILDCARD = '*';
57const XSTATE_INIT = 'xstate.init';
58const XSTATE_ERROR = 'xstate.error';
59const XSTATE_STOP = 'xstate.stop';
60
61/**
62 * Returns an event that represents an implicit event that
63 * is sent after the specified `delay`.
64 *
65 * @param delayRef The delay in milliseconds
66 * @param id The state node ID where this event is handled
67 */
68function createAfterEvent(delayRef, id) {
69 return {
70 type: `xstate.after.${delayRef}.${id}`
71 };
72}
73
74/**
75 * Returns an event that represents that a final state node
76 * has been reached in the parent state node.
77 *
78 * @param id The final state node's parent state node `id`
79 * @param output The data to pass into the event
80 */
81function createDoneStateEvent(id, output) {
82 return {
83 type: `xstate.done.state.${id}`,
84 output
85 };
86}
87
88/**
89 * Returns an event that represents that an invoked service has terminated.
90 *
91 * An invoked service is terminated when it has reached a top-level final state node,
92 * but not when it is canceled.
93 *
94 * @param invokeId The invoked service ID
95 * @param output The data to pass into the event
96 */
97function createDoneActorEvent(invokeId, output) {
98 return {
99 type: `xstate.done.actor.${invokeId}`,
100 output
101 };
102}
103function createErrorActorEvent(id, error) {
104 return {
105 type: `xstate.error.actor.${id}`,
106 error
107 };
108}
109function createInitEvent(input) {
110 return {
111 type: XSTATE_INIT,
112 input
113 };
114}
115
116/**
117 * This function makes sure that unhandled errors are thrown in a separate macrotask.
118 * It allows those errors to be detected by global error handlers and reported to bug tracking services
119 * without interrupting our own stack of execution.
120 *
121 * @param err error to be thrown
122 */
123function reportUnhandledError(err) {
124 setTimeout(() => {
125 throw err;
126 });
127}
128
129const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
130
131function createScheduledEventId(actorRef, id) {
132 return `${actorRef.sessionId}.${id}`;
133}
134let idCounter = 0;
135function createSystem(rootActor, options) {
136 const children = new Map();
137 const keyedActors = new Map();
138 const reverseKeyedActors = new WeakMap();
139 const inspectionObservers = new Set();
140 const timerMap = {};
141 const {
142 clock,
143 logger
144 } = options;
145 const scheduler = {
146 schedule: (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
147 const scheduledEvent = {
148 source,
149 target,
150 event,
151 delay,
152 id,
153 startedAt: Date.now()
154 };
155 const scheduledEventId = createScheduledEventId(source, id);
156 system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
157 const timeout = clock.setTimeout(() => {
158 delete timerMap[scheduledEventId];
159 delete system._snapshot._scheduledEvents[scheduledEventId];
160 system._relay(source, target, event);
161 }, delay);
162 timerMap[scheduledEventId] = timeout;
163 },
164 cancel: (source, id) => {
165 const scheduledEventId = createScheduledEventId(source, id);
166 const timeout = timerMap[scheduledEventId];
167 delete timerMap[scheduledEventId];
168 delete system._snapshot._scheduledEvents[scheduledEventId];
169 clock.clearTimeout(timeout);
170 },
171 cancelAll: actorRef => {
172 for (const scheduledEventId in system._snapshot._scheduledEvents) {
173 const scheduledEvent = system._snapshot._scheduledEvents[scheduledEventId];
174 if (scheduledEvent.source === actorRef) {
175 scheduler.cancel(actorRef, scheduledEvent.id);
176 }
177 }
178 }
179 };
180 const sendInspectionEvent = event => {
181 if (!inspectionObservers.size) {
182 return;
183 }
184 const resolvedInspectionEvent = {
185 ...event,
186 rootId: rootActor.sessionId
187 };
188 inspectionObservers.forEach(observer => observer.next?.(resolvedInspectionEvent));
189 };
190 const system = {
191 _snapshot: {
192 _scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
193 },
194 _bookId: () => `x:${idCounter++}`,
195 _register: (sessionId, actorRef) => {
196 children.set(sessionId, actorRef);
197 return sessionId;
198 },
199 _unregister: actorRef => {
200 children.delete(actorRef.sessionId);
201 const systemId = reverseKeyedActors.get(actorRef);
202 if (systemId !== undefined) {
203 keyedActors.delete(systemId);
204 reverseKeyedActors.delete(actorRef);
205 }
206 },
207 get: systemId => {
208 return keyedActors.get(systemId);
209 },
210 _set: (systemId, actorRef) => {
211 const existing = keyedActors.get(systemId);
212 if (existing && existing !== actorRef) {
213 throw new Error(`Actor with system ID '${systemId}' already exists.`);
214 }
215 keyedActors.set(systemId, actorRef);
216 reverseKeyedActors.set(actorRef, systemId);
217 },
218 inspect: observer => {
219 inspectionObservers.add(observer);
220 },
221 _sendInspectionEvent: sendInspectionEvent,
222 _relay: (source, target, event) => {
223 system._sendInspectionEvent({
224 type: '@xstate.event',
225 sourceRef: source,
226 actorRef: target,
227 event
228 });
229 target._send(event);
230 },
231 scheduler,
232 getSnapshot: () => {
233 return {
234 _scheduledEvents: {
235 ...system._snapshot._scheduledEvents
236 }
237 };
238 },
239 start: () => {
240 const scheduledEvents = system._snapshot._scheduledEvents;
241 system._snapshot._scheduledEvents = {};
242 for (const scheduledId in scheduledEvents) {
243 const {
244 source,
245 target,
246 event,
247 delay,
248 id
249 } = scheduledEvents[scheduledId];
250 scheduler.schedule(source, target, event, delay, id);
251 }
252 },
253 _clock: clock,
254 _logger: logger
255 };
256 return system;
257}
258
259function matchesState(parentStateId, childStateId) {
260 const parentStateValue = toStateValue(parentStateId);
261 const childStateValue = toStateValue(childStateId);
262 if (typeof childStateValue === 'string') {
263 if (typeof parentStateValue === 'string') {
264 return childStateValue === parentStateValue;
265 }
266
267 // Parent more specific than child
268 return false;
269 }
270 if (typeof parentStateValue === 'string') {
271 return parentStateValue in childStateValue;
272 }
273 return Object.keys(parentStateValue).every(key => {
274 if (!(key in childStateValue)) {
275 return false;
276 }
277 return matchesState(parentStateValue[key], childStateValue[key]);
278 });
279}
280function toStatePath(stateId) {
281 if (isArray(stateId)) {
282 return stateId;
283 }
284 let result = [];
285 let segment = '';
286 for (let i = 0; i < stateId.length; i++) {
287 const char = stateId.charCodeAt(i);
288 switch (char) {
289 // \
290 case 92:
291 // consume the next character
292 segment += stateId[i + 1];
293 // and skip over it
294 i++;
295 continue;
296 // .
297 case 46:
298 result.push(segment);
299 segment = '';
300 continue;
301 }
302 segment += stateId[i];
303 }
304 result.push(segment);
305 return result;
306}
307function toStateValue(stateValue) {
308 if (isMachineSnapshot(stateValue)) {
309 return stateValue.value;
310 }
311 if (typeof stateValue !== 'string') {
312 return stateValue;
313 }
314 const statePath = toStatePath(stateValue);
315 return pathToStateValue(statePath);
316}
317function pathToStateValue(statePath) {
318 if (statePath.length === 1) {
319 return statePath[0];
320 }
321 const value = {};
322 let marker = value;
323 for (let i = 0; i < statePath.length - 1; i++) {
324 if (i === statePath.length - 2) {
325 marker[statePath[i]] = statePath[i + 1];
326 } else {
327 const previous = marker;
328 marker = {};
329 previous[statePath[i]] = marker;
330 }
331 }
332 return value;
333}
334function mapValues(collection, iteratee) {
335 const result = {};
336 const collectionKeys = Object.keys(collection);
337 for (let i = 0; i < collectionKeys.length; i++) {
338 const key = collectionKeys[i];
339 result[key] = iteratee(collection[key], key, collection, i);
340 }
341 return result;
342}
343function toArrayStrict(value) {
344 if (isArray(value)) {
345 return value;
346 }
347 return [value];
348}
349function toArray(value) {
350 if (value === undefined) {
351 return [];
352 }
353 return toArrayStrict(value);
354}
355function resolveOutput(mapper, context, event, self) {
356 if (typeof mapper === 'function') {
357 return mapper({
358 context,
359 event,
360 self
361 });
362 }
363 return mapper;
364}
365function isArray(value) {
366 return Array.isArray(value);
367}
368function isErrorActorEvent(event) {
369 return event.type.startsWith('xstate.error.actor');
370}
371function toTransitionConfigArray(configLike) {
372 return toArrayStrict(configLike).map(transitionLike => {
373 if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
374 return {
375 target: transitionLike
376 };
377 }
378 return transitionLike;
379 });
380}
381function normalizeTarget(target) {
382 if (target === undefined || target === TARGETLESS_KEY) {
383 return undefined;
384 }
385 return toArray(target);
386}
387function toObserver(nextHandler, errorHandler, completionHandler) {
388 const isObserver = typeof nextHandler === 'object';
389 const self = isObserver ? nextHandler : undefined;
390 return {
391 next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
392 error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
393 complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
394 };
395}
396function createInvokeId(stateNodeId, index) {
397 return `${index}.${stateNodeId}`;
398}
399function resolveReferencedActor(machine, src) {
400 const match = src.match(/^xstate\.invoke\.(\d+)\.(.*)/);
401 if (!match) {
402 return machine.implementations.actors[src];
403 }
404 const [, indexStr, nodeId] = match;
405 const node = machine.getStateNodeById(nodeId);
406 const invokeConfig = node.config.invoke;
407 return (Array.isArray(invokeConfig) ? invokeConfig[indexStr] : invokeConfig).src;
408}
409function getAllOwnEventDescriptors(snapshot) {
410 return [...new Set([...snapshot._nodes.flatMap(sn => sn.ownEvents)])];
411}
412
413const $$ACTOR_TYPE = 1;
414// those values are currently used by @xstate/react directly so it's important to keep the assigned values in sync
415let ProcessingStatus = /*#__PURE__*/function (ProcessingStatus) {
416 ProcessingStatus[ProcessingStatus["NotStarted"] = 0] = "NotStarted";
417 ProcessingStatus[ProcessingStatus["Running"] = 1] = "Running";
418 ProcessingStatus[ProcessingStatus["Stopped"] = 2] = "Stopped";
419 return ProcessingStatus;
420}({});
421const defaultOptions = {
422 clock: {
423 setTimeout: (fn, ms) => {
424 return setTimeout(fn, ms);
425 },
426 clearTimeout: id => {
427 return clearTimeout(id);
428 }
429 },
430 logger: console.log.bind(console),
431 devTools: false
432};
433
434/**
435 * 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.
436 */
437class Actor {
438 /**
439 * Creates a new actor instance for the given logic with the provided options, if any.
440 *
441 * @param logic The logic to create an actor from
442 * @param options Actor options
443 */
444 constructor(logic, options) {
445 this.logic = logic;
446 /**
447 * The current internal state of the actor.
448 */
449 this._snapshot = void 0;
450 /**
451 * The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
452 */
453 this.clock = void 0;
454 this.options = void 0;
455 /**
456 * The unique identifier for this actor relative to its parent.
457 */
458 this.id = void 0;
459 this.mailbox = new Mailbox(this._process.bind(this));
460 this.observers = new Set();
461 this.eventListeners = new Map();
462 this.logger = void 0;
463 /** @internal */
464 this._processingStatus = ProcessingStatus.NotStarted;
465 // Actor Ref
466 this._parent = void 0;
467 /** @internal */
468 this._syncSnapshot = void 0;
469 this.ref = void 0;
470 // TODO: add typings for system
471 this._actorScope = void 0;
472 this._systemId = void 0;
473 /**
474 * The globally unique process ID for this invocation.
475 */
476 this.sessionId = void 0;
477 /**
478 * The system to which this actor belongs.
479 */
480 this.system = void 0;
481 this._doneEvent = void 0;
482 this.src = void 0;
483 // array of functions to defer
484 this._deferred = [];
485 const resolvedOptions = {
486 ...defaultOptions,
487 ...options
488 };
489 const {
490 clock,
491 logger,
492 parent,
493 syncSnapshot,
494 id,
495 systemId,
496 inspect
497 } = resolvedOptions;
498 this.system = parent ? parent.system : createSystem(this, {
499 clock,
500 logger
501 });
502 if (inspect && !parent) {
503 // Always inspect at the system-level
504 this.system.inspect(toObserver(inspect));
505 }
506 this.sessionId = this.system._bookId();
507 this.id = id ?? this.sessionId;
508 this.logger = options?.logger ?? this.system._logger;
509 this.clock = options?.clock ?? this.system._clock;
510 this._parent = parent;
511 this._syncSnapshot = syncSnapshot;
512 this.options = resolvedOptions;
513 this.src = resolvedOptions.src ?? logic;
514 this.ref = this;
515 this._actorScope = {
516 self: this,
517 id: this.id,
518 sessionId: this.sessionId,
519 logger: this.logger,
520 defer: fn => {
521 this._deferred.push(fn);
522 },
523 system: this.system,
524 stopChild: child => {
525 if (child._parent !== this) {
526 throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
527 }
528 child._stop();
529 },
530 emit: emittedEvent => {
531 const listeners = this.eventListeners.get(emittedEvent.type);
532 if (!listeners) {
533 return;
534 }
535 for (const handler of Array.from(listeners)) {
536 handler(emittedEvent);
537 }
538 }
539 };
540
541 // Ensure that the send method is bound to this Actor instance
542 // if destructured
543 this.send = this.send.bind(this);
544 this.system._sendInspectionEvent({
545 type: '@xstate.actor',
546 actorRef: this
547 });
548 if (systemId) {
549 this._systemId = systemId;
550 this.system._set(systemId, this);
551 }
552 this._initState(options?.snapshot ?? options?.state);
553 if (systemId && this._snapshot.status !== 'active') {
554 this.system._unregister(this);
555 }
556 }
557 _initState(persistedState) {
558 try {
559 this._snapshot = persistedState ? this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState : this.logic.getInitialSnapshot(this._actorScope, this.options?.input);
560 } catch (err) {
561 // if we get here then it means that we assign a value to this._snapshot that is not of the correct type
562 // we can't get the true `TSnapshot & { status: 'error'; }`, it's impossible
563 // so right now this is a lie of sorts
564 this._snapshot = {
565 status: 'error',
566 output: undefined,
567 error: err
568 };
569 }
570 }
571 update(snapshot, event) {
572 // Update state
573 this._snapshot = snapshot;
574
575 // Execute deferred effects
576 let deferredFn;
577 while (deferredFn = this._deferred.shift()) {
578 try {
579 deferredFn();
580 } catch (err) {
581 // this error can only be caught when executing *initial* actions
582 // it's the only time when we call actions provided by the user through those deferreds
583 // when the actor is already running we always execute them synchronously while transitioning
584 // no "builtin deferred" should actually throw an error since they are either safe
585 // or the control flow is passed through the mailbox and errors should be caught by the `_process` used by the mailbox
586 this._deferred.length = 0;
587 this._snapshot = {
588 ...snapshot,
589 status: 'error',
590 error: err
591 };
592 }
593 }
594 switch (this._snapshot.status) {
595 case 'active':
596 for (const observer of this.observers) {
597 try {
598 observer.next?.(snapshot);
599 } catch (err) {
600 reportUnhandledError(err);
601 }
602 }
603 break;
604 case 'done':
605 // next observers are meant to be notified about done snapshots
606 // this can be seen as something that is different from how observable work
607 // but with observables `complete` callback is called without any arguments
608 // it's more ergonomic for XState to treat a done snapshot as a "next" value
609 // and the completion event as something that is separate,
610 // something that merely follows emitting that done snapshot
611 for (const observer of this.observers) {
612 try {
613 observer.next?.(snapshot);
614 } catch (err) {
615 reportUnhandledError(err);
616 }
617 }
618 this._stopProcedure();
619 this._complete();
620 this._doneEvent = createDoneActorEvent(this.id, this._snapshot.output);
621 if (this._parent) {
622 this.system._relay(this, this._parent, this._doneEvent);
623 }
624 break;
625 case 'error':
626 this._error(this._snapshot.error);
627 break;
628 }
629 this.system._sendInspectionEvent({
630 type: '@xstate.snapshot',
631 actorRef: this,
632 event,
633 snapshot
634 });
635 }
636
637 /**
638 * Subscribe an observer to an actor’s snapshot values.
639 *
640 * @remarks
641 * The observer will receive the actor’s snapshot value when it is emitted. The observer can be:
642 * - A plain function that receives the latest snapshot, or
643 * - An observer object whose `.next(snapshot)` method receives the latest snapshot
644 *
645 * @example
646 * ```ts
647 * // Observer as a plain function
648 * const subscription = actor.subscribe((snapshot) => {
649 * console.log(snapshot);
650 * });
651 * ```
652 *
653 * @example
654 * ```ts
655 * // Observer as an object
656 * const subscription = actor.subscribe({
657 * next(snapshot) {
658 * console.log(snapshot);
659 * },
660 * error(err) {
661 * // ...
662 * },
663 * complete() {
664 * // ...
665 * },
666 * });
667 * ```
668 *
669 * 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:
670 *
671 * @example
672 * ```ts
673 * const subscription = actor.subscribe((snapshot) => {
674 * // ...
675 * });
676 *
677 * // Unsubscribe the observer
678 * subscription.unsubscribe();
679 * ```
680 *
681 * When the actor is stopped, all of its observers will automatically be unsubscribed.
682 *
683 * @param observer - Either a plain function that receives the latest snapshot, or an observer object whose `.next(snapshot)` method receives the latest snapshot
684 */
685
686 subscribe(nextListenerOrObserver, errorListener, completeListener) {
687 const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
688 if (this._processingStatus !== ProcessingStatus.Stopped) {
689 this.observers.add(observer);
690 } else {
691 switch (this._snapshot.status) {
692 case 'done':
693 try {
694 observer.complete?.();
695 } catch (err) {
696 reportUnhandledError(err);
697 }
698 break;
699 case 'error':
700 {
701 const err = this._snapshot.error;
702 if (!observer.error) {
703 reportUnhandledError(err);
704 } else {
705 try {
706 observer.error(err);
707 } catch (err) {
708 reportUnhandledError(err);
709 }
710 }
711 break;
712 }
713 }
714 }
715 return {
716 unsubscribe: () => {
717 this.observers.delete(observer);
718 }
719 };
720 }
721 on(type, handler) {
722 let listeners = this.eventListeners.get(type);
723 if (!listeners) {
724 listeners = new Set();
725 this.eventListeners.set(type, listeners);
726 }
727 const wrappedHandler = handler.bind(undefined);
728 listeners.add(wrappedHandler);
729 return {
730 unsubscribe: () => {
731 listeners.delete(wrappedHandler);
732 }
733 };
734 }
735
736 /**
737 * Starts the Actor from the initial state
738 */
739 start() {
740 if (this._processingStatus === ProcessingStatus.Running) {
741 // Do not restart the service if it is already started
742 return this;
743 }
744 if (this._syncSnapshot) {
745 this.subscribe({
746 next: snapshot => {
747 if (snapshot.status === 'active') {
748 this.system._relay(this, this._parent, {
749 type: `xstate.snapshot.${this.id}`,
750 snapshot
751 });
752 }
753 },
754 error: () => {}
755 });
756 }
757 this.system._register(this.sessionId, this);
758 if (this._systemId) {
759 this.system._set(this._systemId, this);
760 }
761 this._processingStatus = ProcessingStatus.Running;
762
763 // TODO: this isn't correct when rehydrating
764 const initEvent = createInitEvent(this.options.input);
765 this.system._sendInspectionEvent({
766 type: '@xstate.event',
767 sourceRef: this._parent,
768 actorRef: this,
769 event: initEvent
770 });
771 const status = this._snapshot.status;
772 switch (status) {
773 case 'done':
774 // a state machine can be "done" upon initialization (it could reach a final state using initial microsteps)
775 // we still need to complete observers, flush deferreds etc
776 this.update(this._snapshot, initEvent);
777 // TODO: rethink cleanup of observers, mailbox, etc
778 return this;
779 case 'error':
780 this._error(this._snapshot.error);
781 return this;
782 }
783 if (!this._parent) {
784 this.system.start();
785 }
786 if (this.logic.start) {
787 try {
788 this.logic.start(this._snapshot, this._actorScope);
789 } catch (err) {
790 this._snapshot = {
791 ...this._snapshot,
792 status: 'error',
793 error: err
794 };
795 this._error(err);
796 return this;
797 }
798 }
799
800 // TODO: this notifies all subscribers but usually this is redundant
801 // there is no real change happening here
802 // we need to rethink if this needs to be refactored
803 this.update(this._snapshot, initEvent);
804 if (this.options.devTools) {
805 this.attachDevTools();
806 }
807 this.mailbox.start();
808 return this;
809 }
810 _process(event) {
811 let nextState;
812 let caughtError;
813 try {
814 nextState = this.logic.transition(this._snapshot, event, this._actorScope);
815 } catch (err) {
816 // we wrap it in a box so we can rethrow it later even if falsy value gets caught here
817 caughtError = {
818 err
819 };
820 }
821 if (caughtError) {
822 const {
823 err
824 } = caughtError;
825 this._snapshot = {
826 ...this._snapshot,
827 status: 'error',
828 error: err
829 };
830 this._error(err);
831 return;
832 }
833 this.update(nextState, event);
834 if (event.type === XSTATE_STOP) {
835 this._stopProcedure();
836 this._complete();
837 }
838 }
839 _stop() {
840 if (this._processingStatus === ProcessingStatus.Stopped) {
841 return this;
842 }
843 this.mailbox.clear();
844 if (this._processingStatus === ProcessingStatus.NotStarted) {
845 this._processingStatus = ProcessingStatus.Stopped;
846 return this;
847 }
848 this.mailbox.enqueue({
849 type: XSTATE_STOP
850 });
851 return this;
852 }
853
854 /**
855 * Stops the Actor and unsubscribe all listeners.
856 */
857 stop() {
858 if (this._parent) {
859 throw new Error('A non-root actor cannot be stopped directly.');
860 }
861 return this._stop();
862 }
863 _complete() {
864 for (const observer of this.observers) {
865 try {
866 observer.complete?.();
867 } catch (err) {
868 reportUnhandledError(err);
869 }
870 }
871 this.observers.clear();
872 }
873 _reportError(err) {
874 if (!this.observers.size) {
875 if (!this._parent) {
876 reportUnhandledError(err);
877 }
878 return;
879 }
880 let reportError = false;
881 for (const observer of this.observers) {
882 const errorListener = observer.error;
883 reportError ||= !errorListener;
884 try {
885 errorListener?.(err);
886 } catch (err2) {
887 reportUnhandledError(err2);
888 }
889 }
890 this.observers.clear();
891 if (reportError) {
892 reportUnhandledError(err);
893 }
894 }
895 _error(err) {
896 this._stopProcedure();
897 this._reportError(err);
898 if (this._parent) {
899 this.system._relay(this, this._parent, createErrorActorEvent(this.id, err));
900 }
901 }
902 // TODO: atm children don't belong entirely to the actor so
903 // in a way - it's not even super aware of them
904 // so we can't stop them from here but we really should!
905 // right now, they are being stopped within the machine's transition
906 // but that could throw and leave us with "orphaned" active actors
907 _stopProcedure() {
908 if (this._processingStatus !== ProcessingStatus.Running) {
909 // Actor already stopped; do nothing
910 return this;
911 }
912
913 // Cancel all delayed events
914 this.system.scheduler.cancelAll(this);
915
916 // TODO: mailbox.reset
917 this.mailbox.clear();
918 // TODO: after `stop` we must prepare ourselves for receiving events again
919 // events sent *after* stop signal must be queued
920 // it seems like this should be the common behavior for all of our consumers
921 // so perhaps this should be unified somehow for all of them
922 this.mailbox = new Mailbox(this._process.bind(this));
923 this._processingStatus = ProcessingStatus.Stopped;
924 this.system._unregister(this);
925 return this;
926 }
927
928 /**
929 * @internal
930 */
931 _send(event) {
932 if (this._processingStatus === ProcessingStatus.Stopped) {
933 return;
934 }
935 this.mailbox.enqueue(event);
936 }
937
938 /**
939 * Sends an event to the running Actor to trigger a transition.
940 *
941 * @param event The event to send
942 */
943 send(event) {
944 this.system._relay(undefined, this, event);
945 }
946 attachDevTools() {
947 const {
948 devTools
949 } = this.options;
950 if (devTools) {
951 const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
952 resolvedDevToolsAdapter(this);
953 }
954 }
955 toJSON() {
956 return {
957 xstate$$type: $$ACTOR_TYPE,
958 id: this.id
959 };
960 }
961
962 /**
963 * Obtain the internal state of the actor, which can be persisted.
964 *
965 * @remarks
966 * The internal state can be persisted from any actor, not only machines.
967 *
968 * 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.
969 *
970 * Can be restored with {@link ActorOptions.state}
971 *
972 * @see https://stately.ai/docs/persistence
973 */
974
975 getPersistedSnapshot(options) {
976 return this.logic.getPersistedSnapshot(this._snapshot, options);
977 }
978 [symbolObservable]() {
979 return this;
980 }
981
982 /**
983 * Read an actor’s snapshot synchronously.
984 *
985 * @remarks
986 * The snapshot represent an actor's last emitted value.
987 *
988 * When an actor receives an event, its internal state may change.
989 * An actor may emit a snapshot when a state transition occurs.
990 *
991 * Note that some actors, such as callback actors generated with `fromCallback`, will not emit snapshots.
992 *
993 * @see {@link Actor.subscribe} to subscribe to an actor’s snapshot values.
994 * @see {@link Actor.getPersistedSnapshot} to persist the internal state of an actor (which is more than just a snapshot).
995 */
996 getSnapshot() {
997 return this._snapshot;
998 }
999}
1000/**
1001 * Creates a new actor instance for the given actor logic with the provided options, if any.
1002 *
1003 * @remarks
1004 * 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.
1005 * Any actors spawned from this root actor and its descendants are part of that actor system.
1006 *
1007 * @example
1008 * ```ts
1009 * import { createActor } from 'xstate';
1010 * import { someActorLogic } from './someActorLogic.ts';
1011 *
1012 * // Creating the actor, which implicitly creates an actor system with itself as the root actor
1013 * const actor = createActor(someActorLogic);
1014 *
1015 * actor.subscribe((snapshot) => {
1016 * console.log(snapshot);
1017 * });
1018 *
1019 * // Actors must be started by calling `actor.start()`, which will also start the actor system.
1020 * actor.start();
1021 *
1022 * // Actors can receive events
1023 * actor.send({ type: 'someEvent' });
1024 *
1025 * // You can stop root actors by calling `actor.stop()`, which will also stop the actor system and all actors in that system.
1026 * actor.stop();
1027 * ```
1028 *
1029 * @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}.
1030 * @param options - Actor options
1031 */
1032function createActor(logic, ...[options]) {
1033 return new Actor(logic, options);
1034}
1035
1036/**
1037 * Creates a new Interpreter instance for the given machine with the provided options, if any.
1038 *
1039 * @deprecated Use `createActor` instead
1040 */
1041const interpret = createActor;
1042
1043/**
1044 * @deprecated Use `Actor` instead.
1045 */
1046
1047function resolveCancel(_, snapshot, actionArgs, actionParams, {
1048 sendId
1049}) {
1050 const resolvedSendId = typeof sendId === 'function' ? sendId(actionArgs, actionParams) : sendId;
1051 return [snapshot, resolvedSendId];
1052}
1053function executeCancel(actorScope, resolvedSendId) {
1054 actorScope.defer(() => {
1055 actorScope.system.scheduler.cancel(actorScope.self, resolvedSendId);
1056 });
1057}
1058/**
1059 * Cancels a delayed `sendTo(...)` action that is waiting to be executed. The canceled `sendTo(...)` action
1060 * will not send its event or execute, unless the `delay` has already elapsed before `cancel(...)` is called.
1061 *
1062 * @param sendId The `id` of the `sendTo(...)` action to cancel.
1063 *
1064 * @example
1065 ```ts
1066 import { createMachine, sendTo, cancel } from 'xstate';
1067
1068 const machine = createMachine({
1069 // ...
1070 on: {
1071 sendEvent: {
1072 actions: sendTo('some-actor', { type: 'someEvent' }, {
1073 id: 'some-id',
1074 delay: 1000
1075 })
1076 },
1077 cancelEvent: {
1078 actions: cancel('some-id')
1079 }
1080 }
1081 });
1082 ```
1083 */
1084function cancel(sendId) {
1085 function cancel(args, params) {
1086 }
1087 cancel.type = 'xstate.cancel';
1088 cancel.sendId = sendId;
1089 cancel.resolve = resolveCancel;
1090 cancel.execute = executeCancel;
1091 return cancel;
1092}
1093
1094function resolveSpawn(actorScope, snapshot, actionArgs, _actionParams, {
1095 id,
1096 systemId,
1097 src,
1098 input,
1099 syncSnapshot
1100}) {
1101 const logic = typeof src === 'string' ? resolveReferencedActor(snapshot.machine, src) : src;
1102 const resolvedId = typeof id === 'function' ? id(actionArgs) : id;
1103 let actorRef;
1104 if (logic) {
1105 actorRef = createActor(logic, {
1106 id: resolvedId,
1107 src,
1108 parent: actorScope.self,
1109 syncSnapshot,
1110 systemId,
1111 input: typeof input === 'function' ? input({
1112 context: snapshot.context,
1113 event: actionArgs.event,
1114 self: actorScope.self
1115 }) : input
1116 });
1117 }
1118 return [cloneMachineSnapshot(snapshot, {
1119 children: {
1120 ...snapshot.children,
1121 [resolvedId]: actorRef
1122 }
1123 }), {
1124 id,
1125 actorRef
1126 }];
1127}
1128function executeSpawn(actorScope, {
1129 id,
1130 actorRef
1131}) {
1132 if (!actorRef) {
1133 return;
1134 }
1135 actorScope.defer(() => {
1136 if (actorRef._processingStatus === ProcessingStatus.Stopped) {
1137 return;
1138 }
1139 actorRef.start();
1140 });
1141}
1142function spawnChild(...[src, {
1143 id,
1144 systemId,
1145 input,
1146 syncSnapshot = false
1147} = {}]) {
1148 function spawnChild(args, params) {
1149 }
1150 spawnChild.type = 'snapshot.spawnChild';
1151 spawnChild.id = id;
1152 spawnChild.systemId = systemId;
1153 spawnChild.src = src;
1154 spawnChild.input = input;
1155 spawnChild.syncSnapshot = syncSnapshot;
1156 spawnChild.resolve = resolveSpawn;
1157 spawnChild.execute = executeSpawn;
1158 return spawnChild;
1159}
1160
1161function resolveStop(_, snapshot, args, actionParams, {
1162 actorRef
1163}) {
1164 const actorRefOrString = typeof actorRef === 'function' ? actorRef(args, actionParams) : actorRef;
1165 const resolvedActorRef = typeof actorRefOrString === 'string' ? snapshot.children[actorRefOrString] : actorRefOrString;
1166 let children = snapshot.children;
1167 if (resolvedActorRef) {
1168 children = {
1169 ...children
1170 };
1171 delete children[resolvedActorRef.id];
1172 }
1173 return [cloneMachineSnapshot(snapshot, {
1174 children
1175 }), resolvedActorRef];
1176}
1177function executeStop(actorScope, actorRef) {
1178 if (!actorRef) {
1179 return;
1180 }
1181
1182 // we need to eagerly unregister it here so a new actor with the same systemId can be registered immediately
1183 // since we defer actual stopping of the actor but we don't defer actor creations (and we can't do that)
1184 // this could throw on `systemId` collision, for example, when dealing with reentering transitions
1185 actorScope.system._unregister(actorRef);
1186
1187 // this allows us to prevent an actor from being started if it gets stopped within the same macrostep
1188 // this can happen, for example, when the invoking state is being exited immediately by an always transition
1189 if (actorRef._processingStatus !== ProcessingStatus.Running) {
1190 actorScope.stopChild(actorRef);
1191 return;
1192 }
1193 // stopping a child enqueues a stop event in the child actor's mailbox
1194 // we need for all of the already enqueued events to be processed before we stop the child
1195 // the parent itself might want to send some events to a child (for example from exit actions on the invoking state)
1196 // and we don't want to ignore those events
1197 actorScope.defer(() => {
1198 actorScope.stopChild(actorRef);
1199 });
1200}
1201/**
1202 * Stops a child actor.
1203 *
1204 * @param actorRef The actor to stop.
1205 */
1206function stopChild(actorRef) {
1207 function stop(args, params) {
1208 }
1209 stop.type = 'xstate.stopChild';
1210 stop.actorRef = actorRef;
1211 stop.resolve = resolveStop;
1212 stop.execute = executeStop;
1213 return stop;
1214}
1215
1216/**
1217 * Stops a child actor.
1218 *
1219 * @deprecated Use `stopChild(...)` instead
1220 */
1221const stop = stopChild;
1222
1223function checkStateIn(snapshot, _, {
1224 stateValue
1225}) {
1226 if (typeof stateValue === 'string' && isStateId(stateValue)) {
1227 const target = snapshot.machine.getStateNodeById(stateValue);
1228 return snapshot._nodes.some(sn => sn === target);
1229 }
1230 return snapshot.matches(stateValue);
1231}
1232function stateIn(stateValue) {
1233 function stateIn(args, params) {
1234 return false;
1235 }
1236 stateIn.check = checkStateIn;
1237 stateIn.stateValue = stateValue;
1238 return stateIn;
1239}
1240function checkNot(snapshot, {
1241 context,
1242 event
1243}, {
1244 guards
1245}) {
1246 return !evaluateGuard(guards[0], context, event, snapshot);
1247}
1248
1249/**
1250 * Higher-order guard that evaluates to `true` if the `guard` passed to it evaluates to `false`.
1251 *
1252 * @category Guards
1253 * @example
1254 ```ts
1255 import { setup, not } from 'xstate';
1256
1257 const machine = setup({
1258 guards: {
1259 someNamedGuard: () => false
1260 }
1261 }).createMachine({
1262 on: {
1263 someEvent: {
1264 guard: not('someNamedGuard'),
1265 actions: () => {
1266 // will be executed if guard in `not(...)`
1267 // evaluates to `false`
1268 }
1269 }
1270 }
1271 });
1272 ```
1273 * @returns A guard
1274 */
1275function not(guard) {
1276 function not(args, params) {
1277 return false;
1278 }
1279 not.check = checkNot;
1280 not.guards = [guard];
1281 return not;
1282}
1283function checkAnd(snapshot, {
1284 context,
1285 event
1286}, {
1287 guards
1288}) {
1289 return guards.every(guard => evaluateGuard(guard, context, event, snapshot));
1290}
1291
1292/**
1293 * Higher-order guard that evaluates to `true` if all `guards` passed to it
1294 * evaluate to `true`.
1295 *
1296 * @category Guards
1297 * @example
1298 ```ts
1299 import { setup, and } from 'xstate';
1300
1301 const machine = setup({
1302 guards: {
1303 someNamedGuard: () => true
1304 }
1305 }).createMachine({
1306 on: {
1307 someEvent: {
1308 guard: and([
1309 ({ context }) => context.value > 0,
1310 'someNamedGuard'
1311 ]),
1312 actions: () => {
1313 // will be executed if all guards in `and(...)`
1314 // evaluate to true
1315 }
1316 }
1317 }
1318 });
1319 ```
1320 * @returns A guard action object
1321 */
1322function and(guards) {
1323 function and(args, params) {
1324 return false;
1325 }
1326 and.check = checkAnd;
1327 and.guards = guards;
1328 return and;
1329}
1330function checkOr(snapshot, {
1331 context,
1332 event
1333}, {
1334 guards
1335}) {
1336 return guards.some(guard => evaluateGuard(guard, context, event, snapshot));
1337}
1338
1339/**
1340 * Higher-order guard that evaluates to `true` if any of the `guards` passed to it
1341 * evaluate to `true`.
1342 *
1343 * @category Guards
1344 * @example
1345 ```ts
1346 import { setup, or } from 'xstate';
1347
1348 const machine = setup({
1349 guards: {
1350 someNamedGuard: () => true
1351 }
1352 }).createMachine({
1353 on: {
1354 someEvent: {
1355 guard: or([
1356 ({ context }) => context.value > 0,
1357 'someNamedGuard'
1358 ]),
1359 actions: () => {
1360 // will be executed if any of the guards in `or(...)`
1361 // evaluate to true
1362 }
1363 }
1364 }
1365 });
1366 ```
1367 * @returns A guard action object
1368 */
1369function or(guards) {
1370 function or(args, params) {
1371 return false;
1372 }
1373 or.check = checkOr;
1374 or.guards = guards;
1375 return or;
1376}
1377
1378// TODO: throw on cycles (depth check should be enough)
1379function evaluateGuard(guard, context, event, snapshot) {
1380 const {
1381 machine
1382 } = snapshot;
1383 const isInline = typeof guard === 'function';
1384 const resolved = isInline ? guard : machine.implementations.guards[typeof guard === 'string' ? guard : guard.type];
1385 if (!isInline && !resolved) {
1386 throw new Error(`Guard '${typeof guard === 'string' ? guard : guard.type}' is not implemented.'.`);
1387 }
1388 if (typeof resolved !== 'function') {
1389 return evaluateGuard(resolved, context, event, snapshot);
1390 }
1391 const guardArgs = {
1392 context,
1393 event
1394 };
1395 const guardParams = isInline || typeof guard === 'string' ? undefined : 'params' in guard ? typeof guard.params === 'function' ? guard.params({
1396 context,
1397 event
1398 }) : guard.params : undefined;
1399 if (!('check' in resolved)) {
1400 // the existing type of `.guards` assumes non-nullable `TExpressionGuard`
1401 // inline guards expect `TExpressionGuard` to be set to `undefined`
1402 // it's fine to cast this here, our logic makes sure that we call those 2 "variants" correctly
1403 return resolved(guardArgs, guardParams);
1404 }
1405 const builtinGuard = resolved;
1406 return builtinGuard.check(snapshot, guardArgs, resolved // this holds all params
1407 );
1408}
1409
1410const isAtomicStateNode = stateNode => stateNode.type === 'atomic' || stateNode.type === 'final';
1411function getChildren(stateNode) {
1412 return Object.values(stateNode.states).filter(sn => sn.type !== 'history');
1413}
1414function getProperAncestors(stateNode, toStateNode) {
1415 const ancestors = [];
1416 if (toStateNode === stateNode) {
1417 return ancestors;
1418 }
1419
1420 // add all ancestors
1421 let m = stateNode.parent;
1422 while (m && m !== toStateNode) {
1423 ancestors.push(m);
1424 m = m.parent;
1425 }
1426 return ancestors;
1427}
1428function getAllStateNodes(stateNodes) {
1429 const nodeSet = new Set(stateNodes);
1430 const adjList = getAdjList(nodeSet);
1431
1432 // add descendants
1433 for (const s of nodeSet) {
1434 // if previously active, add existing child nodes
1435 if (s.type === 'compound' && (!adjList.get(s) || !adjList.get(s).length)) {
1436 getInitialStateNodesWithTheirAncestors(s).forEach(sn => nodeSet.add(sn));
1437 } else {
1438 if (s.type === 'parallel') {
1439 for (const child of getChildren(s)) {
1440 if (child.type === 'history') {
1441 continue;
1442 }
1443 if (!nodeSet.has(child)) {
1444 const initialStates = getInitialStateNodesWithTheirAncestors(child);
1445 for (const initialStateNode of initialStates) {
1446 nodeSet.add(initialStateNode);
1447 }
1448 }
1449 }
1450 }
1451 }
1452 }
1453
1454 // add all ancestors
1455 for (const s of nodeSet) {
1456 let m = s.parent;
1457 while (m) {
1458 nodeSet.add(m);
1459 m = m.parent;
1460 }
1461 }
1462 return nodeSet;
1463}
1464function getValueFromAdj(baseNode, adjList) {
1465 const childStateNodes = adjList.get(baseNode);
1466 if (!childStateNodes) {
1467 return {}; // todo: fix?
1468 }
1469
1470 if (baseNode.type === 'compound') {
1471 const childStateNode = childStateNodes[0];
1472 if (childStateNode) {
1473 if (isAtomicStateNode(childStateNode)) {
1474 return childStateNode.key;
1475 }
1476 } else {
1477 return {};
1478 }
1479 }
1480 const stateValue = {};
1481 for (const childStateNode of childStateNodes) {
1482 stateValue[childStateNode.key] = getValueFromAdj(childStateNode, adjList);
1483 }
1484 return stateValue;
1485}
1486function getAdjList(stateNodes) {
1487 const adjList = new Map();
1488 for (const s of stateNodes) {
1489 if (!adjList.has(s)) {
1490 adjList.set(s, []);
1491 }
1492 if (s.parent) {
1493 if (!adjList.has(s.parent)) {
1494 adjList.set(s.parent, []);
1495 }
1496 adjList.get(s.parent).push(s);
1497 }
1498 }
1499 return adjList;
1500}
1501function getStateValue(rootNode, stateNodes) {
1502 const config = getAllStateNodes(stateNodes);
1503 return getValueFromAdj(rootNode, getAdjList(config));
1504}
1505function isInFinalState(stateNodeSet, stateNode) {
1506 if (stateNode.type === 'compound') {
1507 return getChildren(stateNode).some(s => s.type === 'final' && stateNodeSet.has(s));
1508 }
1509 if (stateNode.type === 'parallel') {
1510 return getChildren(stateNode).every(sn => isInFinalState(stateNodeSet, sn));
1511 }
1512 return stateNode.type === 'final';
1513}
1514const isStateId = str => str[0] === STATE_IDENTIFIER;
1515function getCandidates(stateNode, receivedEventType) {
1516 const candidates = stateNode.transitions.get(receivedEventType) || [...stateNode.transitions.keys()].filter(eventDescriptor => {
1517 // check if transition is a wildcard transition,
1518 // which matches any non-transient events
1519 if (eventDescriptor === WILDCARD) {
1520 return true;
1521 }
1522 if (!eventDescriptor.endsWith('.*')) {
1523 return false;
1524 }
1525 const partialEventTokens = eventDescriptor.split('.');
1526 const eventTokens = receivedEventType.split('.');
1527 for (let tokenIndex = 0; tokenIndex < partialEventTokens.length; tokenIndex++) {
1528 const partialEventToken = partialEventTokens[tokenIndex];
1529 const eventToken = eventTokens[tokenIndex];
1530 if (partialEventToken === '*') {
1531 const isLastToken = tokenIndex === partialEventTokens.length - 1;
1532 return isLastToken;
1533 }
1534 if (partialEventToken !== eventToken) {
1535 return false;
1536 }
1537 }
1538 return true;
1539 }).sort((a, b) => b.length - a.length).flatMap(key => stateNode.transitions.get(key));
1540 return candidates;
1541}
1542
1543/**
1544 * All delayed transitions from the config.
1545 */
1546function getDelayedTransitions(stateNode) {
1547 const afterConfig = stateNode.config.after;
1548 if (!afterConfig) {
1549 return [];
1550 }
1551 const mutateEntryExit = (delay, i) => {
1552 const afterEvent = createAfterEvent(delay, stateNode.id);
1553 const eventType = afterEvent.type;
1554 stateNode.entry.push(raise(afterEvent, {
1555 id: eventType,
1556 delay
1557 }));
1558 stateNode.exit.push(cancel(eventType));
1559 return eventType;
1560 };
1561 const delayedTransitions = Object.keys(afterConfig).flatMap((delay, i) => {
1562 const configTransition = afterConfig[delay];
1563 const resolvedTransition = typeof configTransition === 'string' ? {
1564 target: configTransition
1565 } : configTransition;
1566 const resolvedDelay = Number.isNaN(+delay) ? delay : +delay;
1567 const eventType = mutateEntryExit(resolvedDelay);
1568 return toArray(resolvedTransition).map(transition => ({
1569 ...transition,
1570 event: eventType,
1571 delay: resolvedDelay
1572 }));
1573 });
1574 return delayedTransitions.map(delayedTransition => {
1575 const {
1576 delay
1577 } = delayedTransition;
1578 return {
1579 ...formatTransition(stateNode, delayedTransition.event, delayedTransition),
1580 delay
1581 };
1582 });
1583}
1584function formatTransition(stateNode, descriptor, transitionConfig) {
1585 const normalizedTarget = normalizeTarget(transitionConfig.target);
1586 const reenter = transitionConfig.reenter ?? false;
1587 const target = resolveTarget(stateNode, normalizedTarget);
1588 const transition = {
1589 ...transitionConfig,
1590 actions: toArray(transitionConfig.actions),
1591 guard: transitionConfig.guard,
1592 target,
1593 source: stateNode,
1594 reenter,
1595 eventType: descriptor,
1596 toJSON: () => ({
1597 ...transition,
1598 source: `#${stateNode.id}`,
1599 target: target ? target.map(t => `#${t.id}`) : undefined
1600 })
1601 };
1602 return transition;
1603}
1604function formatTransitions(stateNode) {
1605 const transitions = new Map();
1606 if (stateNode.config.on) {
1607 for (const descriptor of Object.keys(stateNode.config.on)) {
1608 if (descriptor === NULL_EVENT) {
1609 throw new Error('Null events ("") cannot be specified as a transition key. Use `always: { ... }` instead.');
1610 }
1611 const transitionsConfig = stateNode.config.on[descriptor];
1612 transitions.set(descriptor, toTransitionConfigArray(transitionsConfig).map(t => formatTransition(stateNode, descriptor, t)));
1613 }
1614 }
1615 if (stateNode.config.onDone) {
1616 const descriptor = `xstate.done.state.${stateNode.id}`;
1617 transitions.set(descriptor, toTransitionConfigArray(stateNode.config.onDone).map(t => formatTransition(stateNode, descriptor, t)));
1618 }
1619 for (const invokeDef of stateNode.invoke) {
1620 if (invokeDef.onDone) {
1621 const descriptor = `xstate.done.actor.${invokeDef.id}`;
1622 transitions.set(descriptor, toTransitionConfigArray(invokeDef.onDone).map(t => formatTransition(stateNode, descriptor, t)));
1623 }
1624 if (invokeDef.onError) {
1625 const descriptor = `xstate.error.actor.${invokeDef.id}`;
1626 transitions.set(descriptor, toTransitionConfigArray(invokeDef.onError).map(t => formatTransition(stateNode, descriptor, t)));
1627 }
1628 if (invokeDef.onSnapshot) {
1629 const descriptor = `xstate.snapshot.${invokeDef.id}`;
1630 transitions.set(descriptor, toTransitionConfigArray(invokeDef.onSnapshot).map(t => formatTransition(stateNode, descriptor, t)));
1631 }
1632 }
1633 for (const delayedTransition of stateNode.after) {
1634 let existing = transitions.get(delayedTransition.eventType);
1635 if (!existing) {
1636 existing = [];
1637 transitions.set(delayedTransition.eventType, existing);
1638 }
1639 existing.push(delayedTransition);
1640 }
1641 return transitions;
1642}
1643function formatInitialTransition(stateNode, _target) {
1644 const resolvedTarget = typeof _target === 'string' ? stateNode.states[_target] : _target ? stateNode.states[_target.target] : undefined;
1645 if (!resolvedTarget && _target) {
1646 throw new Error(`Initial state node "${_target}" not found on parent state node #${stateNode.id}`);
1647 }
1648 const transition = {
1649 source: stateNode,
1650 actions: !_target || typeof _target === 'string' ? [] : toArray(_target.actions),
1651 eventType: null,
1652 reenter: false,
1653 target: resolvedTarget ? [resolvedTarget] : [],
1654 toJSON: () => ({
1655 ...transition,
1656 source: `#${stateNode.id}`,
1657 target: resolvedTarget ? [`#${resolvedTarget.id}`] : []
1658 })
1659 };
1660 return transition;
1661}
1662function resolveTarget(stateNode, targets) {
1663 if (targets === undefined) {
1664 // an undefined target signals that the state node should not transition from that state when receiving that event
1665 return undefined;
1666 }
1667 return targets.map(target => {
1668 if (typeof target !== 'string') {
1669 return target;
1670 }
1671 if (isStateId(target)) {
1672 return stateNode.machine.getStateNodeById(target);
1673 }
1674 const isInternalTarget = target[0] === STATE_DELIMITER;
1675 // If internal target is defined on machine,
1676 // do not include machine key on target
1677 if (isInternalTarget && !stateNode.parent) {
1678 return getStateNodeByPath(stateNode, target.slice(1));
1679 }
1680 const resolvedTarget = isInternalTarget ? stateNode.key + target : target;
1681 if (stateNode.parent) {
1682 try {
1683 const targetStateNode = getStateNodeByPath(stateNode.parent, resolvedTarget);
1684 return targetStateNode;
1685 } catch (err) {
1686 throw new Error(`Invalid transition definition for state node '${stateNode.id}':\n${err.message}`);
1687 }
1688 } else {
1689 throw new Error(`Invalid target: "${target}" is not a valid target from the root node. Did you mean ".${target}"?`);
1690 }
1691 });
1692}
1693function resolveHistoryDefaultTransition(stateNode) {
1694 const normalizedTarget = normalizeTarget(stateNode.config.target);
1695 if (!normalizedTarget) {
1696 return stateNode.parent.initial;
1697 }
1698 return {
1699 target: normalizedTarget.map(t => typeof t === 'string' ? getStateNodeByPath(stateNode.parent, t) : t)
1700 };
1701}
1702function isHistoryNode(stateNode) {
1703 return stateNode.type === 'history';
1704}
1705function getInitialStateNodesWithTheirAncestors(stateNode) {
1706 const states = getInitialStateNodes(stateNode);
1707 for (const initialState of states) {
1708 for (const ancestor of getProperAncestors(initialState, stateNode)) {
1709 states.add(ancestor);
1710 }
1711 }
1712 return states;
1713}
1714function getInitialStateNodes(stateNode) {
1715 const set = new Set();
1716 function iter(descStateNode) {
1717 if (set.has(descStateNode)) {
1718 return;
1719 }
1720 set.add(descStateNode);
1721 if (descStateNode.type === 'compound') {
1722 iter(descStateNode.initial.target[0]);
1723 } else if (descStateNode.type === 'parallel') {
1724 for (const child of getChildren(descStateNode)) {
1725 iter(child);
1726 }
1727 }
1728 }
1729 iter(stateNode);
1730 return set;
1731}
1732/**
1733 * Returns the child state node from its relative `stateKey`, or throws.
1734 */
1735function getStateNode(stateNode, stateKey) {
1736 if (isStateId(stateKey)) {
1737 return stateNode.machine.getStateNodeById(stateKey);
1738 }
1739 if (!stateNode.states) {
1740 throw new Error(`Unable to retrieve child state '${stateKey}' from '${stateNode.id}'; no child states exist.`);
1741 }
1742 const result = stateNode.states[stateKey];
1743 if (!result) {
1744 throw new Error(`Child state '${stateKey}' does not exist on '${stateNode.id}'`);
1745 }
1746 return result;
1747}
1748
1749/**
1750 * Returns the relative state node from the given `statePath`, or throws.
1751 *
1752 * @param statePath The string or string array relative path to the state node.
1753 */
1754function getStateNodeByPath(stateNode, statePath) {
1755 if (typeof statePath === 'string' && isStateId(statePath)) {
1756 try {
1757 return stateNode.machine.getStateNodeById(statePath);
1758 } catch (e) {
1759 // try individual paths
1760 // throw e;
1761 }
1762 }
1763 const arrayStatePath = toStatePath(statePath).slice();
1764 let currentStateNode = stateNode;
1765 while (arrayStatePath.length) {
1766 const key = arrayStatePath.shift();
1767 if (!key.length) {
1768 break;
1769 }
1770 currentStateNode = getStateNode(currentStateNode, key);
1771 }
1772 return currentStateNode;
1773}
1774
1775/**
1776 * Returns the state nodes represented by the current state value.
1777 *
1778 * @param stateValue The state value or State instance
1779 */
1780function getStateNodes(stateNode, stateValue) {
1781 if (typeof stateValue === 'string') {
1782 const childStateNode = stateNode.states[stateValue];
1783 if (!childStateNode) {
1784 throw new Error(`State '${stateValue}' does not exist on '${stateNode.id}'`);
1785 }
1786 return [stateNode, childStateNode];
1787 }
1788 const childStateKeys = Object.keys(stateValue);
1789 const childStateNodes = childStateKeys.map(subStateKey => getStateNode(stateNode, subStateKey)).filter(Boolean);
1790 return [stateNode.machine.root, stateNode].concat(childStateNodes, childStateKeys.reduce((allSubStateNodes, subStateKey) => {
1791 const subStateNode = getStateNode(stateNode, subStateKey);
1792 if (!subStateNode) {
1793 return allSubStateNodes;
1794 }
1795 const subStateNodes = getStateNodes(subStateNode, stateValue[subStateKey]);
1796 return allSubStateNodes.concat(subStateNodes);
1797 }, []));
1798}
1799function transitionAtomicNode(stateNode, stateValue, snapshot, event) {
1800 const childStateNode = getStateNode(stateNode, stateValue);
1801 const next = childStateNode.next(snapshot, event);
1802 if (!next || !next.length) {
1803 return stateNode.next(snapshot, event);
1804 }
1805 return next;
1806}
1807function transitionCompoundNode(stateNode, stateValue, snapshot, event) {
1808 const subStateKeys = Object.keys(stateValue);
1809 const childStateNode = getStateNode(stateNode, subStateKeys[0]);
1810 const next = transitionNode(childStateNode, stateValue[subStateKeys[0]], snapshot, event);
1811 if (!next || !next.length) {
1812 return stateNode.next(snapshot, event);
1813 }
1814 return next;
1815}
1816function transitionParallelNode(stateNode, stateValue, snapshot, event) {
1817 const allInnerTransitions = [];
1818 for (const subStateKey of Object.keys(stateValue)) {
1819 const subStateValue = stateValue[subStateKey];
1820 if (!subStateValue) {
1821 continue;
1822 }
1823 const subStateNode = getStateNode(stateNode, subStateKey);
1824 const innerTransitions = transitionNode(subStateNode, subStateValue, snapshot, event);
1825 if (innerTransitions) {
1826 allInnerTransitions.push(...innerTransitions);
1827 }
1828 }
1829 if (!allInnerTransitions.length) {
1830 return stateNode.next(snapshot, event);
1831 }
1832 return allInnerTransitions;
1833}
1834function transitionNode(stateNode, stateValue, snapshot, event) {
1835 // leaf node
1836 if (typeof stateValue === 'string') {
1837 return transitionAtomicNode(stateNode, stateValue, snapshot, event);
1838 }
1839
1840 // compound node
1841 if (Object.keys(stateValue).length === 1) {
1842 return transitionCompoundNode(stateNode, stateValue, snapshot, event);
1843 }
1844
1845 // parallel node
1846 return transitionParallelNode(stateNode, stateValue, snapshot, event);
1847}
1848function getHistoryNodes(stateNode) {
1849 return Object.keys(stateNode.states).map(key => stateNode.states[key]).filter(sn => sn.type === 'history');
1850}
1851function isDescendant(childStateNode, parentStateNode) {
1852 let marker = childStateNode;
1853 while (marker.parent && marker.parent !== parentStateNode) {
1854 marker = marker.parent;
1855 }
1856 return marker.parent === parentStateNode;
1857}
1858function hasIntersection(s1, s2) {
1859 const set1 = new Set(s1);
1860 const set2 = new Set(s2);
1861 for (const item of set1) {
1862 if (set2.has(item)) {
1863 return true;
1864 }
1865 }
1866 for (const item of set2) {
1867 if (set1.has(item)) {
1868 return true;
1869 }
1870 }
1871 return false;
1872}
1873function removeConflictingTransitions(enabledTransitions, stateNodeSet, historyValue) {
1874 const filteredTransitions = new Set();
1875 for (const t1 of enabledTransitions) {
1876 let t1Preempted = false;
1877 const transitionsToRemove = new Set();
1878 for (const t2 of filteredTransitions) {
1879 if (hasIntersection(computeExitSet([t1], stateNodeSet, historyValue), computeExitSet([t2], stateNodeSet, historyValue))) {
1880 if (isDescendant(t1.source, t2.source)) {
1881 transitionsToRemove.add(t2);
1882 } else {
1883 t1Preempted = true;
1884 break;
1885 }
1886 }
1887 }
1888 if (!t1Preempted) {
1889 for (const t3 of transitionsToRemove) {
1890 filteredTransitions.delete(t3);
1891 }
1892 filteredTransitions.add(t1);
1893 }
1894 }
1895 return Array.from(filteredTransitions);
1896}
1897function findLeastCommonAncestor(stateNodes) {
1898 const [head, ...tail] = stateNodes;
1899 for (const ancestor of getProperAncestors(head, undefined)) {
1900 if (tail.every(sn => isDescendant(sn, ancestor))) {
1901 return ancestor;
1902 }
1903 }
1904}
1905function getEffectiveTargetStates(transition, historyValue) {
1906 if (!transition.target) {
1907 return [];
1908 }
1909 const targets = new Set();
1910 for (const targetNode of transition.target) {
1911 if (isHistoryNode(targetNode)) {
1912 if (historyValue[targetNode.id]) {
1913 for (const node of historyValue[targetNode.id]) {
1914 targets.add(node);
1915 }
1916 } else {
1917 for (const node of getEffectiveTargetStates(resolveHistoryDefaultTransition(targetNode), historyValue)) {
1918 targets.add(node);
1919 }
1920 }
1921 } else {
1922 targets.add(targetNode);
1923 }
1924 }
1925 return [...targets];
1926}
1927function getTransitionDomain(transition, historyValue) {
1928 const targetStates = getEffectiveTargetStates(transition, historyValue);
1929 if (!targetStates) {
1930 return;
1931 }
1932 if (!transition.reenter && targetStates.every(target => target === transition.source || isDescendant(target, transition.source))) {
1933 return transition.source;
1934 }
1935 const lca = findLeastCommonAncestor(targetStates.concat(transition.source));
1936 if (lca) {
1937 return lca;
1938 }
1939
1940 // at this point we know that it's a root transition since LCA couldn't be found
1941 if (transition.reenter) {
1942 return;
1943 }
1944 return transition.source.machine.root;
1945}
1946function computeExitSet(transitions, stateNodeSet, historyValue) {
1947 const statesToExit = new Set();
1948 for (const t of transitions) {
1949 if (t.target?.length) {
1950 const domain = getTransitionDomain(t, historyValue);
1951 if (t.reenter && t.source === domain) {
1952 statesToExit.add(domain);
1953 }
1954 for (const stateNode of stateNodeSet) {
1955 if (isDescendant(stateNode, domain)) {
1956 statesToExit.add(stateNode);
1957 }
1958 }
1959 }
1960 }
1961 return [...statesToExit];
1962}
1963function areStateNodeCollectionsEqual(prevStateNodes, nextStateNodeSet) {
1964 if (prevStateNodes.length !== nextStateNodeSet.size) {
1965 return false;
1966 }
1967 for (const node of prevStateNodes) {
1968 if (!nextStateNodeSet.has(node)) {
1969 return false;
1970 }
1971 }
1972 return true;
1973}
1974
1975/**
1976 * https://www.w3.org/TR/scxml/#microstepProcedure
1977 */
1978function microstep(transitions, currentSnapshot, actorScope, event, isInitial, internalQueue) {
1979 if (!transitions.length) {
1980 return currentSnapshot;
1981 }
1982 const mutStateNodeSet = new Set(currentSnapshot._nodes);
1983 let historyValue = currentSnapshot.historyValue;
1984 const filteredTransitions = removeConflictingTransitions(transitions, mutStateNodeSet, historyValue);
1985 let nextState = currentSnapshot;
1986
1987 // Exit states
1988 if (!isInitial) {
1989 [nextState, historyValue] = exitStates(nextState, event, actorScope, filteredTransitions, mutStateNodeSet, historyValue, internalQueue);
1990 }
1991
1992 // Execute transition content
1993 nextState = resolveActionsAndContext(nextState, event, actorScope, filteredTransitions.flatMap(t => t.actions), internalQueue);
1994
1995 // Enter states
1996 nextState = enterStates(nextState, event, actorScope, filteredTransitions, mutStateNodeSet, internalQueue, historyValue, isInitial);
1997 const nextStateNodes = [...mutStateNodeSet];
1998 if (nextState.status === 'done') {
1999 nextState = resolveActionsAndContext(nextState, event, actorScope, nextStateNodes.sort((a, b) => b.order - a.order).flatMap(state => state.exit), internalQueue);
2000 }
2001 try {
2002 if (historyValue === currentSnapshot.historyValue && areStateNodeCollectionsEqual(currentSnapshot._nodes, mutStateNodeSet)) {
2003 return nextState;
2004 }
2005 return cloneMachineSnapshot(nextState, {
2006 _nodes: nextStateNodes,
2007 historyValue
2008 });
2009 } catch (e) {
2010 // TODO: Refactor this once proper error handling is implemented.
2011 // See https://github.com/statelyai/rfcs/pull/4
2012 throw e;
2013 }
2014}
2015function getMachineOutput(snapshot, event, actorScope, rootNode, rootCompletionNode) {
2016 if (rootNode.output === undefined) {
2017 return;
2018 }
2019 const doneStateEvent = createDoneStateEvent(rootCompletionNode.id, rootCompletionNode.output !== undefined && rootCompletionNode.parent ? resolveOutput(rootCompletionNode.output, snapshot.context, event, actorScope.self) : undefined);
2020 return resolveOutput(rootNode.output, snapshot.context, doneStateEvent, actorScope.self);
2021}
2022function enterStates(currentSnapshot, event, actorScope, filteredTransitions, mutStateNodeSet, internalQueue, historyValue, isInitial) {
2023 let nextSnapshot = currentSnapshot;
2024 const statesToEnter = new Set();
2025 // those are states that were directly targeted or indirectly targeted by the explicit target
2026 // in other words, those are states for which initial actions should be executed
2027 // when we target `#deep_child` initial actions of its ancestors shouldn't be executed
2028 const statesForDefaultEntry = new Set();
2029 computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
2030
2031 // In the initial state, the root state node is "entered".
2032 if (isInitial) {
2033 statesForDefaultEntry.add(currentSnapshot.machine.root);
2034 }
2035 const completedNodes = new Set();
2036 for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
2037 mutStateNodeSet.add(stateNodeToEnter);
2038 const actions = [];
2039
2040 // Add entry actions
2041 actions.push(...stateNodeToEnter.entry);
2042 for (const invokeDef of stateNodeToEnter.invoke) {
2043 actions.push(spawnChild(invokeDef.src, {
2044 ...invokeDef,
2045 syncSnapshot: !!invokeDef.onSnapshot
2046 }));
2047 }
2048 if (statesForDefaultEntry.has(stateNodeToEnter)) {
2049 const initialActions = stateNodeToEnter.initial.actions;
2050 actions.push(...initialActions);
2051 }
2052 nextSnapshot = resolveActionsAndContext(nextSnapshot, event, actorScope, actions, internalQueue, stateNodeToEnter.invoke.map(invokeDef => invokeDef.id));
2053 if (stateNodeToEnter.type === 'final') {
2054 const parent = stateNodeToEnter.parent;
2055 let ancestorMarker = parent?.type === 'parallel' ? parent : parent?.parent;
2056 let rootCompletionNode = ancestorMarker || stateNodeToEnter;
2057 if (parent?.type === 'compound') {
2058 internalQueue.push(createDoneStateEvent(parent.id, stateNodeToEnter.output !== undefined ? resolveOutput(stateNodeToEnter.output, nextSnapshot.context, event, actorScope.self) : undefined));
2059 }
2060 while (ancestorMarker?.type === 'parallel' && !completedNodes.has(ancestorMarker) && isInFinalState(mutStateNodeSet, ancestorMarker)) {
2061 completedNodes.add(ancestorMarker);
2062 internalQueue.push(createDoneStateEvent(ancestorMarker.id));
2063 rootCompletionNode = ancestorMarker;
2064 ancestorMarker = ancestorMarker.parent;
2065 }
2066 if (ancestorMarker) {
2067 continue;
2068 }
2069 nextSnapshot = cloneMachineSnapshot(nextSnapshot, {
2070 status: 'done',
2071 output: getMachineOutput(nextSnapshot, event, actorScope, nextSnapshot.machine.root, rootCompletionNode)
2072 });
2073 }
2074 }
2075 return nextSnapshot;
2076}
2077function computeEntrySet(transitions, historyValue, statesForDefaultEntry, statesToEnter) {
2078 for (const t of transitions) {
2079 const domain = getTransitionDomain(t, historyValue);
2080 for (const s of t.target || []) {
2081 if (!isHistoryNode(s) && (
2082 // if the target is different than the source then it will *definitely* be entered
2083 t.source !== s ||
2084 // we know that the domain can't lie within the source
2085 // 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
2086 t.source !== domain ||
2087 // reentering transitions always enter the target, even if it's the source itself
2088 t.reenter)) {
2089 statesToEnter.add(s);
2090 statesForDefaultEntry.add(s);
2091 }
2092 addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
2093 }
2094 const targetStates = getEffectiveTargetStates(t, historyValue);
2095 for (const s of targetStates) {
2096 const ancestors = getProperAncestors(s, domain);
2097 if (domain?.type === 'parallel') {
2098 ancestors.push(domain);
2099 }
2100 addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, ancestors, !t.source.parent && t.reenter ? undefined : domain);
2101 }
2102 }
2103}
2104function addDescendantStatesToEnter(stateNode, historyValue, statesForDefaultEntry, statesToEnter) {
2105 if (isHistoryNode(stateNode)) {
2106 if (historyValue[stateNode.id]) {
2107 const historyStateNodes = historyValue[stateNode.id];
2108 for (const s of historyStateNodes) {
2109 statesToEnter.add(s);
2110 addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
2111 }
2112 for (const s of historyStateNodes) {
2113 addProperAncestorStatesToEnter(s, stateNode.parent, statesToEnter, historyValue, statesForDefaultEntry);
2114 }
2115 } else {
2116 const historyDefaultTransition = resolveHistoryDefaultTransition(stateNode);
2117 for (const s of historyDefaultTransition.target) {
2118 statesToEnter.add(s);
2119 if (historyDefaultTransition === stateNode.parent?.initial) {
2120 statesForDefaultEntry.add(stateNode.parent);
2121 }
2122 addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
2123 }
2124 for (const s of historyDefaultTransition.target) {
2125 addProperAncestorStatesToEnter(s, stateNode.parent, statesToEnter, historyValue, statesForDefaultEntry);
2126 }
2127 }
2128 } else {
2129 if (stateNode.type === 'compound') {
2130 const [initialState] = stateNode.initial.target;
2131 if (!isHistoryNode(initialState)) {
2132 statesToEnter.add(initialState);
2133 statesForDefaultEntry.add(initialState);
2134 }
2135 addDescendantStatesToEnter(initialState, historyValue, statesForDefaultEntry, statesToEnter);
2136 addProperAncestorStatesToEnter(initialState, stateNode, statesToEnter, historyValue, statesForDefaultEntry);
2137 } else {
2138 if (stateNode.type === 'parallel') {
2139 for (const child of getChildren(stateNode).filter(sn => !isHistoryNode(sn))) {
2140 if (![...statesToEnter].some(s => isDescendant(s, child))) {
2141 if (!isHistoryNode(child)) {
2142 statesToEnter.add(child);
2143 statesForDefaultEntry.add(child);
2144 }
2145 addDescendantStatesToEnter(child, historyValue, statesForDefaultEntry, statesToEnter);
2146 }
2147 }
2148 }
2149 }
2150 }
2151}
2152function addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, ancestors, reentrancyDomain) {
2153 for (const anc of ancestors) {
2154 if (!reentrancyDomain || isDescendant(anc, reentrancyDomain)) {
2155 statesToEnter.add(anc);
2156 }
2157 if (anc.type === 'parallel') {
2158 for (const child of getChildren(anc).filter(sn => !isHistoryNode(sn))) {
2159 if (![...statesToEnter].some(s => isDescendant(s, child))) {
2160 statesToEnter.add(child);
2161 addDescendantStatesToEnter(child, historyValue, statesForDefaultEntry, statesToEnter);
2162 }
2163 }
2164 }
2165 }
2166}
2167function addProperAncestorStatesToEnter(stateNode, toStateNode, statesToEnter, historyValue, statesForDefaultEntry) {
2168 addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, getProperAncestors(stateNode, toStateNode));
2169}
2170function exitStates(currentSnapshot, event, actorScope, transitions, mutStateNodeSet, historyValue, internalQueue) {
2171 let nextSnapshot = currentSnapshot;
2172 const statesToExit = computeExitSet(transitions, mutStateNodeSet, historyValue);
2173 statesToExit.sort((a, b) => b.order - a.order);
2174 let changedHistory;
2175
2176 // From SCXML algorithm: https://www.w3.org/TR/scxml/#exitStates
2177 for (const exitStateNode of statesToExit) {
2178 for (const historyNode of getHistoryNodes(exitStateNode)) {
2179 let predicate;
2180 if (historyNode.history === 'deep') {
2181 predicate = sn => isAtomicStateNode(sn) && isDescendant(sn, exitStateNode);
2182 } else {
2183 predicate = sn => {
2184 return sn.parent === exitStateNode;
2185 };
2186 }
2187 changedHistory ??= {
2188 ...historyValue
2189 };
2190 changedHistory[historyNode.id] = Array.from(mutStateNodeSet).filter(predicate);
2191 }
2192 }
2193 for (const s of statesToExit) {
2194 nextSnapshot = resolveActionsAndContext(nextSnapshot, event, actorScope, [...s.exit, ...s.invoke.map(def => stopChild(def.id))], internalQueue);
2195 mutStateNodeSet.delete(s);
2196 }
2197 return [nextSnapshot, changedHistory || historyValue];
2198}
2199function resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope, actions, extra, retries) {
2200 const {
2201 machine
2202 } = currentSnapshot;
2203 let intermediateSnapshot = currentSnapshot;
2204 for (const action of actions) {
2205 const isInline = typeof action === 'function';
2206 const resolvedAction = isInline ? action :
2207 // the existing type of `.actions` assumes non-nullable `TExpressionAction`
2208 // it's fine to cast this here to get a common type and lack of errors in the rest of the code
2209 // our logic below makes sure that we call those 2 "variants" correctly
2210 machine.implementations.actions[typeof action === 'string' ? action : action.type];
2211 if (!resolvedAction) {
2212 continue;
2213 }
2214 const actionArgs = {
2215 context: intermediateSnapshot.context,
2216 event,
2217 self: actorScope.self,
2218 system: actorScope.system
2219 };
2220 const actionParams = isInline || typeof action === 'string' ? undefined : 'params' in action ? typeof action.params === 'function' ? action.params({
2221 context: intermediateSnapshot.context,
2222 event
2223 }) : action.params : undefined;
2224 function executeAction() {
2225 actorScope.system._sendInspectionEvent({
2226 type: '@xstate.action',
2227 actorRef: actorScope.self,
2228 action: {
2229 type: typeof action === 'string' ? action : typeof action === 'object' ? action.type : action.name || '(anonymous)',
2230 params: actionParams
2231 }
2232 });
2233 resolvedAction(actionArgs, actionParams);
2234 }
2235 if (!('resolve' in resolvedAction)) {
2236 if (actorScope.self._processingStatus === ProcessingStatus.Running) {
2237 executeAction();
2238 } else {
2239 actorScope.defer(() => {
2240 executeAction();
2241 });
2242 }
2243 continue;
2244 }
2245 const builtinAction = resolvedAction;
2246 const [nextState, params, actions] = builtinAction.resolve(actorScope, intermediateSnapshot, actionArgs, actionParams, resolvedAction,
2247 // this holds all params
2248 extra);
2249 intermediateSnapshot = nextState;
2250 if ('retryResolve' in builtinAction) {
2251 retries?.push([builtinAction, params]);
2252 }
2253 if ('execute' in builtinAction) {
2254 if (actorScope.self._processingStatus === ProcessingStatus.Running) {
2255 builtinAction.execute(actorScope, params);
2256 } else {
2257 actorScope.defer(builtinAction.execute.bind(null, actorScope, params));
2258 }
2259 }
2260 if (actions) {
2261 intermediateSnapshot = resolveAndExecuteActionsWithContext(intermediateSnapshot, event, actorScope, actions, extra, retries);
2262 }
2263 }
2264 return intermediateSnapshot;
2265}
2266function resolveActionsAndContext(currentSnapshot, event, actorScope, actions, internalQueue, deferredActorIds) {
2267 const retries = deferredActorIds ? [] : undefined;
2268 const nextState = resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope, actions, {
2269 internalQueue,
2270 deferredActorIds
2271 }, retries);
2272 retries?.forEach(([builtinAction, params]) => {
2273 builtinAction.retryResolve(actorScope, nextState, params);
2274 });
2275 return nextState;
2276}
2277function macrostep(snapshot, event, actorScope, internalQueue = []) {
2278 let nextSnapshot = snapshot;
2279 const microstates = [];
2280 function addMicrostate(microstate, event, transitions) {
2281 actorScope.system._sendInspectionEvent({
2282 type: '@xstate.microstep',
2283 actorRef: actorScope.self,
2284 event,
2285 snapshot: microstate,
2286 _transitions: transitions
2287 });
2288 microstates.push(microstate);
2289 }
2290
2291 // Handle stop event
2292 if (event.type === XSTATE_STOP) {
2293 nextSnapshot = cloneMachineSnapshot(stopChildren(nextSnapshot, event, actorScope), {
2294 status: 'stopped'
2295 });
2296 addMicrostate(nextSnapshot, event, []);
2297 return {
2298 snapshot: nextSnapshot,
2299 microstates
2300 };
2301 }
2302 let nextEvent = event;
2303
2304 // Assume the state is at rest (no raised events)
2305 // Determine the next state based on the next microstep
2306 if (nextEvent.type !== XSTATE_INIT) {
2307 const currentEvent = nextEvent;
2308 const isErr = isErrorActorEvent(currentEvent);
2309 const transitions = selectTransitions(currentEvent, nextSnapshot);
2310 if (isErr && !transitions.length) {
2311 // TODO: we should likely only allow transitions selected by very explicit descriptors
2312 // `*` shouldn't be matched, likely `xstate.error.*` shouldnt be either
2313 // similarly `xstate.error.actor.*` and `xstate.error.actor.todo.*` have to be considered too
2314 nextSnapshot = cloneMachineSnapshot(snapshot, {
2315 status: 'error',
2316 error: currentEvent.error
2317 });
2318 addMicrostate(nextSnapshot, currentEvent, []);
2319 return {
2320 snapshot: nextSnapshot,
2321 microstates
2322 };
2323 }
2324 nextSnapshot = microstep(transitions, snapshot, actorScope, nextEvent, false,
2325 // isInitial
2326 internalQueue);
2327 addMicrostate(nextSnapshot, currentEvent, transitions);
2328 }
2329 let shouldSelectEventlessTransitions = true;
2330 while (nextSnapshot.status === 'active') {
2331 let enabledTransitions = shouldSelectEventlessTransitions ? selectEventlessTransitions(nextSnapshot, nextEvent) : [];
2332
2333 // eventless transitions should always be selected after selecting *regular* transitions
2334 // by assigning `undefined` to `previousState` we ensure that `shouldSelectEventlessTransitions` gets always computed to true in such a case
2335 const previousState = enabledTransitions.length ? nextSnapshot : undefined;
2336 if (!enabledTransitions.length) {
2337 if (!internalQueue.length) {
2338 break;
2339 }
2340 nextEvent = internalQueue.shift();
2341 enabledTransitions = selectTransitions(nextEvent, nextSnapshot);
2342 }
2343 nextSnapshot = microstep(enabledTransitions, nextSnapshot, actorScope, nextEvent, false, internalQueue);
2344 shouldSelectEventlessTransitions = nextSnapshot !== previousState;
2345 addMicrostate(nextSnapshot, nextEvent, enabledTransitions);
2346 }
2347 if (nextSnapshot.status !== 'active') {
2348 stopChildren(nextSnapshot, nextEvent, actorScope);
2349 }
2350 return {
2351 snapshot: nextSnapshot,
2352 microstates
2353 };
2354}
2355function stopChildren(nextState, event, actorScope) {
2356 return resolveActionsAndContext(nextState, event, actorScope, Object.values(nextState.children).map(child => stopChild(child)), []);
2357}
2358function selectTransitions(event, nextState) {
2359 return nextState.machine.getTransitionData(nextState, event);
2360}
2361function selectEventlessTransitions(nextState, event) {
2362 const enabledTransitionSet = new Set();
2363 const atomicStates = nextState._nodes.filter(isAtomicStateNode);
2364 for (const stateNode of atomicStates) {
2365 loop: for (const s of [stateNode].concat(getProperAncestors(stateNode, undefined))) {
2366 if (!s.always) {
2367 continue;
2368 }
2369 for (const transition of s.always) {
2370 if (transition.guard === undefined || evaluateGuard(transition.guard, nextState.context, event, nextState)) {
2371 enabledTransitionSet.add(transition);
2372 break loop;
2373 }
2374 }
2375 }
2376 }
2377 return removeConflictingTransitions(Array.from(enabledTransitionSet), new Set(nextState._nodes), nextState.historyValue);
2378}
2379
2380/**
2381 * Resolves a partial state value with its full representation in the state node's machine.
2382 *
2383 * @param stateValue The partial state value to resolve.
2384 */
2385function resolveStateValue(rootNode, stateValue) {
2386 const allStateNodes = getAllStateNodes(getStateNodes(rootNode, stateValue));
2387 return getStateValue(rootNode, [...allStateNodes]);
2388}
2389
2390function isMachineSnapshot(value) {
2391 return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
2392}
2393const machineSnapshotMatches = function matches(testValue) {
2394 return matchesState(testValue, this.value);
2395};
2396const machineSnapshotHasTag = function hasTag(tag) {
2397 return this.tags.has(tag);
2398};
2399const machineSnapshotCan = function can(event) {
2400 const transitionData = this.machine.getTransitionData(this, event);
2401 return !!transitionData?.length &&
2402 // Check that at least one transition is not forbidden
2403 transitionData.some(t => t.target !== undefined || t.actions.length);
2404};
2405const machineSnapshotToJSON = function toJSON() {
2406 const {
2407 _nodes: nodes,
2408 tags,
2409 machine,
2410 getMeta,
2411 toJSON,
2412 can,
2413 hasTag,
2414 matches,
2415 ...jsonValues
2416 } = this;
2417 return {
2418 ...jsonValues,
2419 tags: Array.from(tags)
2420 };
2421};
2422const machineSnapshotGetMeta = function getMeta() {
2423 return this._nodes.reduce((acc, stateNode) => {
2424 if (stateNode.meta !== undefined) {
2425 acc[stateNode.id] = stateNode.meta;
2426 }
2427 return acc;
2428 }, {});
2429};
2430function createMachineSnapshot(config, machine) {
2431 return {
2432 status: config.status,
2433 output: config.output,
2434 error: config.error,
2435 machine,
2436 context: config.context,
2437 _nodes: config._nodes,
2438 value: getStateValue(machine.root, config._nodes),
2439 tags: new Set(config._nodes.flatMap(sn => sn.tags)),
2440 children: config.children,
2441 historyValue: config.historyValue || {},
2442 matches: machineSnapshotMatches,
2443 hasTag: machineSnapshotHasTag,
2444 can: machineSnapshotCan,
2445 getMeta: machineSnapshotGetMeta,
2446 toJSON: machineSnapshotToJSON
2447 };
2448}
2449function cloneMachineSnapshot(snapshot, config = {}) {
2450 return createMachineSnapshot({
2451 ...snapshot,
2452 ...config
2453 }, snapshot.machine);
2454}
2455function getPersistedSnapshot(snapshot, options) {
2456 const {
2457 _nodes: nodes,
2458 tags,
2459 machine,
2460 children,
2461 context,
2462 can,
2463 hasTag,
2464 matches,
2465 getMeta,
2466 toJSON,
2467 ...jsonValues
2468 } = snapshot;
2469 const childrenJson = {};
2470 for (const id in children) {
2471 const child = children[id];
2472 childrenJson[id] = {
2473 snapshot: child.getPersistedSnapshot(options),
2474 src: child.src,
2475 systemId: child._systemId,
2476 syncSnapshot: child._syncSnapshot
2477 };
2478 }
2479 const persisted = {
2480 ...jsonValues,
2481 context: persistContext(context),
2482 children: childrenJson
2483 };
2484 return persisted;
2485}
2486function persistContext(contextPart) {
2487 let copy;
2488 for (const key in contextPart) {
2489 const value = contextPart[key];
2490 if (value && typeof value === 'object') {
2491 if ('sessionId' in value && 'send' in value && 'ref' in value) {
2492 copy ??= Array.isArray(contextPart) ? contextPart.slice() : {
2493 ...contextPart
2494 };
2495 copy[key] = {
2496 xstate$$type: $$ACTOR_TYPE,
2497 id: value.id
2498 };
2499 } else {
2500 const result = persistContext(value);
2501 if (result !== value) {
2502 copy ??= Array.isArray(contextPart) ? contextPart.slice() : {
2503 ...contextPart
2504 };
2505 copy[key] = result;
2506 }
2507 }
2508 }
2509 }
2510 return copy ?? contextPart;
2511}
2512
2513function resolveRaise(_, snapshot, args, actionParams, {
2514 event: eventOrExpr,
2515 id,
2516 delay
2517}, {
2518 internalQueue
2519}) {
2520 const delaysMap = snapshot.machine.implementations.delays;
2521 if (typeof eventOrExpr === 'string') {
2522 throw new Error(`Only event objects may be used with raise; use raise({ type: "${eventOrExpr}" }) instead`);
2523 }
2524 const resolvedEvent = typeof eventOrExpr === 'function' ? eventOrExpr(args, actionParams) : eventOrExpr;
2525 let resolvedDelay;
2526 if (typeof delay === 'string') {
2527 const configDelay = delaysMap && delaysMap[delay];
2528 resolvedDelay = typeof configDelay === 'function' ? configDelay(args, actionParams) : configDelay;
2529 } else {
2530 resolvedDelay = typeof delay === 'function' ? delay(args, actionParams) : delay;
2531 }
2532 if (typeof resolvedDelay !== 'number') {
2533 internalQueue.push(resolvedEvent);
2534 }
2535 return [snapshot, {
2536 event: resolvedEvent,
2537 id,
2538 delay: resolvedDelay
2539 }];
2540}
2541function executeRaise(actorScope, params) {
2542 const {
2543 event,
2544 delay,
2545 id
2546 } = params;
2547 if (typeof delay === 'number') {
2548 actorScope.defer(() => {
2549 const self = actorScope.self;
2550 actorScope.system.scheduler.schedule(self, self, event, delay, id);
2551 });
2552 return;
2553 }
2554}
2555/**
2556 * Raises an event. This places the event in the internal event queue, so that
2557 * the event is immediately consumed by the machine in the current step.
2558 *
2559 * @param eventType The event to raise.
2560 */
2561function raise(eventOrExpr, options) {
2562 function raise(args, params) {
2563 }
2564 raise.type = 'xstate.raise';
2565 raise.event = eventOrExpr;
2566 raise.id = options?.id;
2567 raise.delay = options?.delay;
2568 raise.resolve = resolveRaise;
2569 raise.execute = executeRaise;
2570 return raise;
2571}
2572
2573exports.$$ACTOR_TYPE = $$ACTOR_TYPE;
2574exports.Actor = Actor;
2575exports.NULL_EVENT = NULL_EVENT;
2576exports.ProcessingStatus = ProcessingStatus;
2577exports.STATE_DELIMITER = STATE_DELIMITER;
2578exports.XSTATE_ERROR = XSTATE_ERROR;
2579exports.XSTATE_STOP = XSTATE_STOP;
2580exports.and = and;
2581exports.cancel = cancel;
2582exports.cloneMachineSnapshot = cloneMachineSnapshot;
2583exports.createActor = createActor;
2584exports.createErrorActorEvent = createErrorActorEvent;
2585exports.createInitEvent = createInitEvent;
2586exports.createInvokeId = createInvokeId;
2587exports.createMachineSnapshot = createMachineSnapshot;
2588exports.evaluateGuard = evaluateGuard;
2589exports.formatInitialTransition = formatInitialTransition;
2590exports.formatTransition = formatTransition;
2591exports.formatTransitions = formatTransitions;
2592exports.getAllOwnEventDescriptors = getAllOwnEventDescriptors;
2593exports.getAllStateNodes = getAllStateNodes;
2594exports.getCandidates = getCandidates;
2595exports.getDelayedTransitions = getDelayedTransitions;
2596exports.getInitialStateNodes = getInitialStateNodes;
2597exports.getPersistedSnapshot = getPersistedSnapshot;
2598exports.getStateNodeByPath = getStateNodeByPath;
2599exports.getStateNodes = getStateNodes;
2600exports.interpret = interpret;
2601exports.isInFinalState = isInFinalState;
2602exports.isMachineSnapshot = isMachineSnapshot;
2603exports.isStateId = isStateId;
2604exports.macrostep = macrostep;
2605exports.mapValues = mapValues;
2606exports.matchesState = matchesState;
2607exports.microstep = microstep;
2608exports.not = not;
2609exports.or = or;
2610exports.pathToStateValue = pathToStateValue;
2611exports.raise = raise;
2612exports.resolveActionsAndContext = resolveActionsAndContext;
2613exports.resolveReferencedActor = resolveReferencedActor;
2614exports.resolveStateValue = resolveStateValue;
2615exports.spawnChild = spawnChild;
2616exports.stateIn = stateIn;
2617exports.stop = stop;
2618exports.stopChild = stopChild;
2619exports.toArray = toArray;
2620exports.toObserver = toObserver;
2621exports.toStatePath = toStatePath;
2622exports.toTransitionConfigArray = toTransitionConfigArray;
2623exports.transitionNode = transitionNode;