UNPKG

26.8 kBJavaScriptView Raw
1export { createEmptyActor, fromCallback, fromEventObservable, fromObservable, fromPromise, fromTransition } from '../actors/dist/xstate-actors.development.esm.js';
2import { S as STATE_DELIMITER, m as mapValues, t as toArray, f as formatTransitions, a as toTransitionConfigArray, b as formatTransition, N as NULL_EVENT, e as evaluateGuard, c as createInvokeId, g as getDelayedTransitions, d as formatInitialTransition, h as getCandidates, r as resolveStateValue, i as getAllStateNodes, j as getStateNodes, k as createMachineSnapshot, l as isInFinalState, n as macrostep, o as transitionNode, p as resolveActionsAndContext, q as createInitEvent, s as microstep, u as getInitialStateNodes, v as toStatePath, w as isStateId, x as getStateNodeByPath, y as getPersistedSnapshot, z as resolveReferencedActor, A as createActor, $ as $$ACTOR_TYPE } from './raise-03a786f4.development.esm.js';
3export { B as Actor, I as __unsafe_getAllOwnEventDescriptors, E as and, M as cancel, A as createActor, j as getStateNodes, C as interpret, D as isMachineSnapshot, J as matchesState, F as not, G as or, K as pathToStateValue, O as raise, P as spawnChild, H as stateIn, Q as stop, R as stopChild, L as toObserver } from './raise-03a786f4.development.esm.js';
4import { a as assign } from './log-f64de12b.development.esm.js';
5export { S as SpecialTargets, a as assign, e as emit, b as enqueueActions, f as forwardTo, l as log, s as sendParent, c as sendTo } from './log-f64de12b.development.esm.js';
6import '../dev/dist/xstate-dev.development.esm.js';
7
8class SimulatedClock {
9 constructor() {
10 this.timeouts = new Map();
11 this._now = 0;
12 this._id = 0;
13 this._flushing = false;
14 this._flushingInvalidated = false;
15 }
16 now() {
17 return this._now;
18 }
19 getId() {
20 return this._id++;
21 }
22 setTimeout(fn, timeout) {
23 this._flushingInvalidated = this._flushing;
24 const id = this.getId();
25 this.timeouts.set(id, {
26 start: this.now(),
27 timeout,
28 fn
29 });
30 return id;
31 }
32 clearTimeout(id) {
33 this._flushingInvalidated = this._flushing;
34 this.timeouts.delete(id);
35 }
36 set(time) {
37 if (this._now > time) {
38 throw new Error('Unable to travel back in time');
39 }
40 this._now = time;
41 this.flushTimeouts();
42 }
43 flushTimeouts() {
44 if (this._flushing) {
45 this._flushingInvalidated = true;
46 return;
47 }
48 this._flushing = true;
49 const sorted = [...this.timeouts].sort(([_idA, timeoutA], [_idB, timeoutB]) => {
50 const endA = timeoutA.start + timeoutA.timeout;
51 const endB = timeoutB.start + timeoutB.timeout;
52 return endB > endA ? -1 : 1;
53 });
54 for (const [id, timeout] of sorted) {
55 if (this._flushingInvalidated) {
56 this._flushingInvalidated = false;
57 this._flushing = false;
58 this.flushTimeouts();
59 return;
60 }
61 if (this.now() - timeout.start >= timeout.timeout) {
62 this.timeouts.delete(id);
63 timeout.fn.call(null);
64 }
65 }
66 this._flushing = false;
67 }
68 increment(ms) {
69 this._now += ms;
70 this.flushTimeouts();
71 }
72}
73
74const cache = new WeakMap();
75function memo(object, key, fn) {
76 let memoizedData = cache.get(object);
77 if (!memoizedData) {
78 memoizedData = {
79 [key]: fn()
80 };
81 cache.set(object, memoizedData);
82 } else if (!(key in memoizedData)) {
83 memoizedData[key] = fn();
84 }
85 return memoizedData[key];
86}
87
88const EMPTY_OBJECT = {};
89const toSerializableAction = action => {
90 if (typeof action === 'string') {
91 return {
92 type: action
93 };
94 }
95 if (typeof action === 'function') {
96 if ('resolve' in action) {
97 return {
98 type: action.type
99 };
100 }
101 return {
102 type: action.name
103 };
104 }
105 return action;
106};
107class StateNode {
108 constructor(
109 /**
110 * The raw config used to create the machine.
111 */
112 config, options) {
113 this.config = config;
114 /**
115 * The relative key of the state node, which represents its location in the overall state value.
116 */
117 this.key = void 0;
118 /**
119 * The unique ID of the state node.
120 */
121 this.id = void 0;
122 /**
123 * The type of this state node:
124 *
125 * - `'atomic'` - no child state nodes
126 * - `'compound'` - nested child state nodes (XOR)
127 * - `'parallel'` - orthogonal nested child state nodes (AND)
128 * - `'history'` - history state node
129 * - `'final'` - final state node
130 */
131 this.type = void 0;
132 /**
133 * The string path from the root machine node to this node.
134 */
135 this.path = void 0;
136 /**
137 * The child state nodes.
138 */
139 this.states = void 0;
140 /**
141 * The type of history on this state node. Can be:
142 *
143 * - `'shallow'` - recalls only top-level historical state value
144 * - `'deep'` - recalls historical state value at all levels
145 */
146 this.history = void 0;
147 /**
148 * The action(s) to be executed upon entering the state node.
149 */
150 this.entry = void 0;
151 /**
152 * The action(s) to be executed upon exiting the state node.
153 */
154 this.exit = void 0;
155 /**
156 * The parent state node.
157 */
158 this.parent = void 0;
159 /**
160 * The root machine node.
161 */
162 this.machine = void 0;
163 /**
164 * The meta data associated with this state node, which will be returned in State instances.
165 */
166 this.meta = void 0;
167 /**
168 * The output data sent with the "xstate.done.state._id_" event if this is a final state node.
169 */
170 this.output = void 0;
171 /**
172 * The order this state node appears. Corresponds to the implicit document order.
173 */
174 this.order = -1;
175 this.description = void 0;
176 this.tags = [];
177 this.transitions = void 0;
178 this.always = void 0;
179 this.parent = options._parent;
180 this.key = options._key;
181 this.machine = options._machine;
182 this.path = this.parent ? this.parent.path.concat(this.key) : [];
183 this.id = this.config.id || [this.machine.id, ...this.path].join(STATE_DELIMITER);
184 this.type = this.config.type || (this.config.states && Object.keys(this.config.states).length ? 'compound' : this.config.history ? 'history' : 'atomic');
185 this.description = this.config.description;
186 this.order = this.machine.idMap.size;
187 this.machine.idMap.set(this.id, this);
188 this.states = this.config.states ? mapValues(this.config.states, (stateConfig, key) => {
189 const stateNode = new StateNode(stateConfig, {
190 _parent: this,
191 _key: key,
192 _machine: this.machine
193 });
194 return stateNode;
195 }) : EMPTY_OBJECT;
196 if (this.type === 'compound' && !this.config.initial) {
197 throw new Error(`No initial state specified for compound state node "#${this.id}". Try adding { initial: "${Object.keys(this.states)[0]}" } to the state config.`);
198 }
199
200 // History config
201 this.history = this.config.history === true ? 'shallow' : this.config.history || false;
202 this.entry = toArray(this.config.entry).slice();
203 this.exit = toArray(this.config.exit).slice();
204 this.meta = this.config.meta;
205 this.output = this.type === 'final' || !this.parent ? this.config.output : undefined;
206 this.tags = toArray(config.tags).slice();
207 }
208
209 /** @internal */
210 _initialize() {
211 this.transitions = formatTransitions(this);
212 if (this.config.always) {
213 this.always = toTransitionConfigArray(this.config.always).map(t => formatTransition(this, NULL_EVENT, t));
214 }
215 Object.keys(this.states).forEach(key => {
216 this.states[key]._initialize();
217 });
218 }
219
220 /**
221 * The well-structured state node definition.
222 */
223 get definition() {
224 return {
225 id: this.id,
226 key: this.key,
227 version: this.machine.version,
228 type: this.type,
229 initial: this.initial ? {
230 target: this.initial.target,
231 source: this,
232 actions: this.initial.actions.map(toSerializableAction),
233 eventType: null,
234 reenter: false,
235 toJSON: () => ({
236 target: this.initial.target.map(t => `#${t.id}`),
237 source: `#${this.id}`,
238 actions: this.initial.actions.map(toSerializableAction),
239 eventType: null
240 })
241 } : undefined,
242 history: this.history,
243 states: mapValues(this.states, state => {
244 return state.definition;
245 }),
246 on: this.on,
247 transitions: [...this.transitions.values()].flat().map(t => ({
248 ...t,
249 actions: t.actions.map(toSerializableAction)
250 })),
251 entry: this.entry.map(toSerializableAction),
252 exit: this.exit.map(toSerializableAction),
253 meta: this.meta,
254 order: this.order || -1,
255 output: this.output,
256 invoke: this.invoke,
257 description: this.description,
258 tags: this.tags
259 };
260 }
261
262 /** @internal */
263 toJSON() {
264 return this.definition;
265 }
266
267 /**
268 * The logic invoked as actors by this state node.
269 */
270 get invoke() {
271 return memo(this, 'invoke', () => toArray(this.config.invoke).map((invokeConfig, i) => {
272 const {
273 src,
274 systemId
275 } = invokeConfig;
276 const resolvedId = invokeConfig.id ?? createInvokeId(this.id, i);
277 const resolvedSrc = typeof src === 'string' ? src : `xstate.invoke.${createInvokeId(this.id, i)}`;
278 return {
279 ...invokeConfig,
280 src: resolvedSrc,
281 id: resolvedId,
282 systemId: systemId,
283 toJSON() {
284 const {
285 onDone,
286 onError,
287 ...invokeDefValues
288 } = invokeConfig;
289 return {
290 ...invokeDefValues,
291 type: 'xstate.invoke',
292 src: resolvedSrc,
293 id: resolvedId
294 };
295 }
296 };
297 }));
298 }
299
300 /**
301 * The mapping of events to transitions.
302 */
303 get on() {
304 return memo(this, 'on', () => {
305 const transitions = this.transitions;
306 return [...transitions].flatMap(([descriptor, t]) => t.map(t => [descriptor, t])).reduce((map, [descriptor, transition]) => {
307 map[descriptor] = map[descriptor] || [];
308 map[descriptor].push(transition);
309 return map;
310 }, {});
311 });
312 }
313 get after() {
314 return memo(this, 'delayedTransitions', () => getDelayedTransitions(this));
315 }
316 get initial() {
317 return memo(this, 'initial', () => formatInitialTransition(this, this.config.initial));
318 }
319
320 /** @internal */
321 next(snapshot, event) {
322 const eventType = event.type;
323 const actions = [];
324 let selectedTransition;
325 const candidates = memo(this, `candidates-${eventType}`, () => getCandidates(this, eventType));
326 for (const candidate of candidates) {
327 const {
328 guard
329 } = candidate;
330 const resolvedContext = snapshot.context;
331 let guardPassed = false;
332 try {
333 guardPassed = !guard || evaluateGuard(guard, resolvedContext, event, snapshot);
334 } catch (err) {
335 const guardType = typeof guard === 'string' ? guard : typeof guard === 'object' ? guard.type : undefined;
336 throw new Error(`Unable to evaluate guard ${guardType ? `'${guardType}' ` : ''}in transition for event '${eventType}' in state node '${this.id}':\n${err.message}`);
337 }
338 if (guardPassed) {
339 actions.push(...candidate.actions);
340 selectedTransition = candidate;
341 break;
342 }
343 }
344 return selectedTransition ? [selectedTransition] : undefined;
345 }
346
347 /**
348 * All the event types accepted by this state node and its descendants.
349 */
350 get events() {
351 return memo(this, 'events', () => {
352 const {
353 states
354 } = this;
355 const events = new Set(this.ownEvents);
356 if (states) {
357 for (const stateId of Object.keys(states)) {
358 const state = states[stateId];
359 if (state.states) {
360 for (const event of state.events) {
361 events.add(`${event}`);
362 }
363 }
364 }
365 }
366 return Array.from(events);
367 });
368 }
369
370 /**
371 * All the events that have transitions directly from this state node.
372 *
373 * Excludes any inert events.
374 */
375 get ownEvents() {
376 const events = new Set([...this.transitions.keys()].filter(descriptor => {
377 return this.transitions.get(descriptor).some(transition => !(!transition.target && !transition.actions.length && !transition.reenter));
378 }));
379 return Array.from(events);
380 }
381}
382
383const STATE_IDENTIFIER = '#';
384class StateMachine {
385 constructor(
386 /**
387 * The raw config used to create the machine.
388 */
389 config, implementations) {
390 this.config = config;
391 /**
392 * The machine's own version.
393 */
394 this.version = void 0;
395 this.schemas = void 0;
396 this.implementations = void 0;
397 /** @internal */
398 this.__xstatenode = true;
399 /** @internal */
400 this.idMap = new Map();
401 this.root = void 0;
402 this.id = void 0;
403 this.states = void 0;
404 this.events = void 0;
405 /**
406 * @deprecated an internal property that was acting as a "phantom" type, it's not used by anything right now but it's kept around for compatibility reasons
407 **/
408 this.__TResolvedTypesMeta = void 0;
409 this.id = config.id || '(machine)';
410 this.implementations = {
411 actors: implementations?.actors ?? {},
412 actions: implementations?.actions ?? {},
413 delays: implementations?.delays ?? {},
414 guards: implementations?.guards ?? {}
415 };
416 this.version = this.config.version;
417 this.schemas = this.config.schemas;
418 this.transition = this.transition.bind(this);
419 this.getInitialSnapshot = this.getInitialSnapshot.bind(this);
420 this.getPersistedSnapshot = this.getPersistedSnapshot.bind(this);
421 this.restoreSnapshot = this.restoreSnapshot.bind(this);
422 this.start = this.start.bind(this);
423 this.root = new StateNode(config, {
424 _key: this.id,
425 _machine: this
426 });
427 this.root._initialize();
428 this.states = this.root.states; // TODO: remove!
429 this.events = this.root.events;
430 if (!('output' in this.root) && Object.values(this.states).some(state => state.type === 'final' && 'output' in state)) {
431 console.warn('Missing `machine.output` declaration (top-level final state with output detected)');
432 }
433 }
434
435 /**
436 * Clones this state machine with the provided implementations
437 * and merges the `context` (if provided).
438 *
439 * @param implementations Options (`actions`, `guards`, `actors`, `delays`, `context`)
440 * to recursively merge with the existing options.
441 *
442 * @returns A new `StateMachine` instance with the provided implementations.
443 */
444 provide(implementations) {
445 const {
446 actions,
447 guards,
448 actors,
449 delays
450 } = this.implementations;
451 return new StateMachine(this.config, {
452 actions: {
453 ...actions,
454 ...implementations.actions
455 },
456 guards: {
457 ...guards,
458 ...implementations.guards
459 },
460 actors: {
461 ...actors,
462 ...implementations.actors
463 },
464 delays: {
465 ...delays,
466 ...implementations.delays
467 }
468 });
469 }
470 resolveState(config) {
471 const resolvedStateValue = resolveStateValue(this.root, config.value);
472 const nodeSet = getAllStateNodes(getStateNodes(this.root, resolvedStateValue));
473 return createMachineSnapshot({
474 _nodes: [...nodeSet],
475 context: config.context || {},
476 children: {},
477 status: isInFinalState(nodeSet, this.root) ? 'done' : config.status || 'active',
478 output: config.output,
479 error: config.error,
480 historyValue: config.historyValue
481 }, this);
482 }
483
484 /**
485 * Determines the next snapshot given the current `snapshot` and received `event`.
486 * Calculates a full macrostep from all microsteps.
487 *
488 * @param snapshot The current snapshot
489 * @param event The received event
490 */
491 transition(snapshot, event, actorScope) {
492 return macrostep(snapshot, event, actorScope).snapshot;
493 }
494
495 /**
496 * Determines the next state given the current `state` and `event`.
497 * Calculates a microstep.
498 *
499 * @param state The current state
500 * @param event The received event
501 */
502 microstep(snapshot, event, actorScope) {
503 return macrostep(snapshot, event, actorScope).microstates;
504 }
505 getTransitionData(snapshot, event) {
506 return transitionNode(this.root, snapshot.value, snapshot, event) || [];
507 }
508
509 /**
510 * The initial state _before_ evaluating any microsteps.
511 * This "pre-initial" state is provided to initial actions executed in the initial state.
512 */
513 getPreInitialState(actorScope, initEvent, internalQueue) {
514 const {
515 context
516 } = this.config;
517 const preInitial = createMachineSnapshot({
518 context: typeof context !== 'function' && context ? context : {},
519 _nodes: [this.root],
520 children: {},
521 status: 'active'
522 }, this);
523 if (typeof context === 'function') {
524 const assignment = ({
525 spawn,
526 event,
527 self
528 }) => context({
529 spawn,
530 input: event.input,
531 self
532 });
533 return resolveActionsAndContext(preInitial, initEvent, actorScope, [assign(assignment)], internalQueue);
534 }
535 return preInitial;
536 }
537
538 /**
539 * Returns the initial `State` instance, with reference to `self` as an `ActorRef`.
540 */
541 getInitialSnapshot(actorScope, input) {
542 const initEvent = createInitEvent(input); // TODO: fix;
543 const internalQueue = [];
544 const preInitialState = this.getPreInitialState(actorScope, initEvent, internalQueue);
545 const nextState = microstep([{
546 target: [...getInitialStateNodes(this.root)],
547 source: this.root,
548 reenter: true,
549 actions: [],
550 eventType: null,
551 toJSON: null // TODO: fix
552 }], preInitialState, actorScope, initEvent, true, internalQueue);
553 const {
554 snapshot: macroState
555 } = macrostep(nextState, initEvent, actorScope, internalQueue);
556 return macroState;
557 }
558 start(snapshot) {
559 Object.values(snapshot.children).forEach(child => {
560 if (child.getSnapshot().status === 'active') {
561 child.start();
562 }
563 });
564 }
565 getStateNodeById(stateId) {
566 const fullPath = toStatePath(stateId);
567 const relativePath = fullPath.slice(1);
568 const resolvedStateId = isStateId(fullPath[0]) ? fullPath[0].slice(STATE_IDENTIFIER.length) : fullPath[0];
569 const stateNode = this.idMap.get(resolvedStateId);
570 if (!stateNode) {
571 throw new Error(`Child state node '#${resolvedStateId}' does not exist on machine '${this.id}'`);
572 }
573 return getStateNodeByPath(stateNode, relativePath);
574 }
575 get definition() {
576 return this.root.definition;
577 }
578 toJSON() {
579 return this.definition;
580 }
581 getPersistedSnapshot(snapshot, options) {
582 return getPersistedSnapshot(snapshot, options);
583 }
584 restoreSnapshot(snapshot, _actorScope) {
585 const children = {};
586 const snapshotChildren = snapshot.children;
587 Object.keys(snapshotChildren).forEach(actorId => {
588 const actorData = snapshotChildren[actorId];
589 const childState = actorData.snapshot;
590 const src = actorData.src;
591 const logic = typeof src === 'string' ? resolveReferencedActor(this, src) : src;
592 if (!logic) {
593 return;
594 }
595 const actorRef = createActor(logic, {
596 id: actorId,
597 parent: _actorScope.self,
598 syncSnapshot: actorData.syncSnapshot,
599 snapshot: childState,
600 src,
601 systemId: actorData.systemId
602 });
603 children[actorId] = actorRef;
604 });
605 const restoredSnapshot = createMachineSnapshot({
606 ...snapshot,
607 children,
608 _nodes: Array.from(getAllStateNodes(getStateNodes(this.root, snapshot.value)))
609 }, this);
610 let seen = new Set();
611 function reviveContext(contextPart, children) {
612 if (seen.has(contextPart)) {
613 return;
614 }
615 seen.add(contextPart);
616 for (let key in contextPart) {
617 const value = contextPart[key];
618 if (value && typeof value === 'object') {
619 if ('xstate$$type' in value && value.xstate$$type === $$ACTOR_TYPE) {
620 contextPart[key] = children[value.id];
621 continue;
622 }
623 reviveContext(value, children);
624 }
625 }
626 }
627 reviveContext(restoredSnapshot.context, children);
628 return restoredSnapshot;
629 }
630}
631
632const defaultWaitForOptions = {
633 timeout: Infinity // much more than 10 seconds
634};
635
636/**
637 * Subscribes to an actor ref and waits for its emitted value to satisfy
638 * a predicate, and then resolves with that value.
639 * Will throw if the desired state is not reached after an optional timeout.
640 * (defaults to Infinity).
641 *
642 * @example
643 * ```js
644 * const state = await waitFor(someService, state => {
645 * return state.hasTag('loaded');
646 * });
647 *
648 * state.hasTag('loaded'); // true
649 * ```
650 *
651 * @param actorRef The actor ref to subscribe to
652 * @param predicate Determines if a value matches the condition to wait for
653 * @param options
654 * @returns A promise that eventually resolves to the emitted value
655 * that matches the condition
656 */
657function waitFor(actorRef, predicate, options) {
658 const resolvedOptions = {
659 ...defaultWaitForOptions,
660 ...options
661 };
662 return new Promise((res, rej) => {
663 let done = false;
664 if (resolvedOptions.timeout < 0) {
665 console.error('`timeout` passed to `waitFor` is negative and it will reject its internal promise immediately.');
666 }
667 const handle = resolvedOptions.timeout === Infinity ? undefined : setTimeout(() => {
668 sub.unsubscribe();
669 rej(new Error(`Timeout of ${resolvedOptions.timeout} ms exceeded`));
670 }, resolvedOptions.timeout);
671 const dispose = () => {
672 clearTimeout(handle);
673 done = true;
674 sub?.unsubscribe();
675 };
676 function checkEmitted(emitted) {
677 if (predicate(emitted)) {
678 dispose();
679 res(emitted);
680 }
681 }
682 let sub; // avoid TDZ when disposing synchronously
683
684 // See if the current snapshot already matches the predicate
685 checkEmitted(actorRef.getSnapshot());
686 if (done) {
687 return;
688 }
689 sub = actorRef.subscribe({
690 next: checkEmitted,
691 error: err => {
692 dispose();
693 rej(err);
694 },
695 complete: () => {
696 dispose();
697 rej(new Error(`Actor terminated without satisfying predicate`));
698 }
699 });
700 if (done) {
701 sub.unsubscribe();
702 }
703 });
704}
705
706// this is not 100% accurate since we can't make parallel regions required in the result
707// `TTestValue` doesn't encode this information anyhow for us to be able to do that
708// this is fine for most practical use cases anyway though
709/**
710 * Creates a state machine (statechart) with the given configuration.
711 *
712 * The state machine represents the pure logic of a state machine actor.
713 *
714 * @param config The state machine configuration.
715 * @param options DEPRECATED: use `setup({ ... })` or `machine.provide({ ... })` to provide machine implementations instead.
716 *
717 * @example
718 ```ts
719 import { createMachine } from 'xstate';
720
721 const lightMachine = createMachine({
722 id: 'light',
723 initial: 'green',
724 states: {
725 green: {
726 on: {
727 TIMER: { target: 'yellow' }
728 }
729 },
730 yellow: {
731 on: {
732 TIMER: { target: 'red' }
733 }
734 },
735 red: {
736 on: {
737 TIMER: { target: 'green' }
738 }
739 }
740 }
741 });
742
743 const lightActor = createActor(lightMachine);
744 lightActor.start();
745
746 lightActor.send({ type: 'TIMER' });
747 ```
748 */
749function createMachine(config, implementations) {
750 return new StateMachine(config, implementations);
751}
752
753/** @internal */
754function createInertActorScope(actorLogic) {
755 const self = createActor(actorLogic);
756 const inertActorScope = {
757 self,
758 defer: () => {},
759 id: '',
760 logger: () => {},
761 sessionId: '',
762 stopChild: () => {},
763 system: self.system,
764 emit: () => {}
765 };
766 return inertActorScope;
767}
768function getInitialSnapshot(actorLogic, ...[input]) {
769 const actorScope = createInertActorScope(actorLogic);
770 return actorLogic.getInitialSnapshot(actorScope, input);
771}
772
773/**
774 * Determines the next snapshot for the given `actorLogic` based on
775 * the given `snapshot` and `event`.
776 *
777 * If the `snapshot` is `undefined`, the initial snapshot of the
778 * `actorLogic` is used.
779 *
780 * @example
781 ```ts
782 import { getNextSnapshot } from 'xstate';
783 import { trafficLightMachine } from './trafficLightMachine.ts';
784
785 const nextSnapshot = getNextSnapshot(
786 trafficLightMachine, // actor logic
787 undefined, // snapshot (or initial state if undefined)
788 { type: 'TIMER' }); // event object
789
790 console.log(nextSnapshot.value);
791 // => 'yellow'
792
793 const nextSnapshot2 = getNextSnapshot(
794 trafficLightMachine, // actor logic
795 nextSnapshot, // snapshot
796 { type: 'TIMER' }); // event object
797
798 console.log(nextSnapshot2.value);
799 // =>'red'
800 ```
801 */
802function getNextSnapshot(actorLogic, snapshot, event) {
803 const inertActorScope = createInertActorScope(actorLogic);
804 inertActorScope.self._snapshot = snapshot;
805 return actorLogic.transition(snapshot, event, inertActorScope);
806}
807
808// at the moment we allow extra actors - ones that are not specified by `children`
809// this could be reconsidered in the future
810function setup({
811 schemas,
812 actors,
813 actions,
814 guards,
815 delays
816}) {
817 return {
818 createMachine: config => createMachine({
819 ...config,
820 schemas
821 }, {
822 actors,
823 actions,
824 guards,
825 delays
826 })
827 };
828}
829
830/**
831 * Returns a promise that resolves to the `output` of the actor when it is done.
832 *
833 * @example
834 * ```ts
835 * const machine = createMachine({
836 * // ...
837 * output: {
838 * count: 42
839 * }
840 * });
841 *
842 * const actor = createActor(machine);
843 *
844 * actor.start();
845 *
846 * const output = await toPromise(actor);
847 *
848 * console.log(output);
849 * // logs { count: 42 }
850 * ```
851 */
852function toPromise(actor) {
853 return new Promise((resolve, reject) => {
854 actor.subscribe({
855 complete: () => {
856 resolve(actor.getSnapshot().output);
857 },
858 error: reject
859 });
860 });
861}
862
863/**
864 * Asserts that the given event object is of the specified type or types.
865 * Throws an error if the event object is not of the specified types.
866 @example
867
868 ```ts
869 // ...
870 entry: ({ event }) => {
871 assertEvent(event, 'doNothing');
872 // event is { type: 'doNothing' }
873 },
874 // ...
875 exit: ({ event }) => {
876 assertEvent(event, 'greet');
877 // event is { type: 'greet'; message: string }
878
879 assertEvent(event, ['greet', 'notify']);
880 // event is { type: 'greet'; message: string }
881 // or { type: 'notify'; message: string; level: 'info' | 'error' }
882 },
883 ```
884 */
885function assertEvent(event, type) {
886 const types = toArray(type);
887 if (!types.includes(event.type)) {
888 const typesText = types.length === 1 ? `type "${types[0]}"` : `one of types "${types.join('", "')}"`;
889 throw new Error(`Expected event ${JSON.stringify(event)} to have ${typesText}`);
890 }
891}
892
893export { SimulatedClock, StateMachine, StateNode, assertEvent, createMachine, getInitialSnapshot, getNextSnapshot, setup, toPromise, waitFor };