xstate 6.0.0-alpha.2 → 6.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +19 -17
  2. package/bin/xstate.js +36 -0
  3. package/dist/{StateMachine-64813f46.cjs.js → StateMachine-1b155670.cjs.js} +182 -40
  4. package/dist/{StateMachine-2dcdb1f5.esm.js → StateMachine-3c17fe3e.esm.js} +182 -40
  5. package/dist/{StateMachine-8d22a79c.development.cjs.js → StateMachine-550ff7f2.development.cjs.js} +193 -36
  6. package/dist/{StateMachine-2ea0a7fa.development.esm.js → StateMachine-e25dd61f.development.esm.js} +193 -36
  7. package/dist/declarations/src/State.d.ts +13 -2
  8. package/dist/declarations/src/StateMachine.d.ts +23 -13
  9. package/dist/declarations/src/actions.d.ts +2 -1
  10. package/dist/declarations/src/actors/promise.d.ts +4 -2
  11. package/dist/declarations/src/createActor.d.ts +9 -11
  12. package/dist/declarations/src/createMachine.d.ts +14 -11
  13. package/dist/declarations/src/createMachineFromConfig.d.ts +61 -28
  14. package/dist/declarations/src/fsm.d.ts +74 -0
  15. package/dist/declarations/src/graph/graph.d.ts +3 -3
  16. package/dist/declarations/src/graph/shortestPaths.d.ts +2 -2
  17. package/dist/declarations/src/graph/simplePaths.d.ts +2 -2
  18. package/dist/declarations/src/index.d.ts +6 -5
  19. package/dist/declarations/src/serialize.d.ts +10 -18
  20. package/dist/declarations/src/setup.d.ts +277 -65
  21. package/dist/declarations/src/spawn.d.ts +3 -3
  22. package/dist/declarations/src/stateUtils.d.ts +4 -1
  23. package/dist/declarations/src/system.d.ts +1 -1
  24. package/dist/declarations/src/transition.d.ts +8 -5
  25. package/dist/declarations/src/transitionActions.d.ts +12 -0
  26. package/dist/declarations/src/types.d.ts +237 -102
  27. package/dist/declarations/src/types.v6.d.ts +132 -58
  28. package/dist/declarations/src/utils.d.ts +4 -3
  29. package/dist/{index-93edd3bd.esm.js → index-3f87408c.esm.js} +936 -1389
  30. package/dist/{index-e61170ba.development.esm.js → index-d39fb138.development.esm.js} +968 -1421
  31. package/dist/{index-9cb3b3a0.cjs.js → index-e694228c.cjs.js} +945 -1394
  32. package/dist/{index-f6ef20d1.development.cjs.js → index-ec7c7a2d.development.cjs.js} +977 -1426
  33. package/dist/xstate-actors.cjs.js +1 -1
  34. package/dist/xstate-actors.development.cjs.js +1 -1
  35. package/dist/xstate-actors.development.esm.js +1 -1
  36. package/dist/xstate-actors.esm.js +1 -1
  37. package/dist/xstate-actors.umd.min.js +1 -1
  38. package/dist/xstate-actors.umd.min.js.map +1 -1
  39. package/dist/xstate-graph.cjs.js +2 -2
  40. package/dist/xstate-graph.development.cjs.js +2 -2
  41. package/dist/xstate-graph.development.esm.js +2 -2
  42. package/dist/xstate-graph.esm.js +2 -2
  43. package/dist/xstate-graph.umd.min.js +1 -1
  44. package/dist/xstate-graph.umd.min.js.map +1 -1
  45. package/dist/xstate.cjs.js +1100 -182
  46. package/dist/xstate.cjs.mjs +3 -5
  47. package/dist/xstate.development.cjs.js +1100 -182
  48. package/dist/xstate.development.cjs.mjs +3 -5
  49. package/dist/xstate.development.esm.js +1101 -181
  50. package/dist/xstate.esm.js +1101 -181
  51. package/dist/xstate.umd.min.js +1 -1
  52. package/dist/xstate.umd.min.js.map +1 -1
  53. package/package.json +6 -2
  54. package/dist/declarations/src/atom.d.ts +0 -81
@@ -1,18 +1,5 @@
1
1
  'use strict';
2
2
 
3
- /**
4
- * Reactivity interop between actors and atoms.
5
- *
6
- * `Actor#get()` calls this hook (when installed) so that computed atoms can
7
- * track actor reads as dependencies. The hook is installed by `atom.ts` on
8
- * module evaluation, which only happens when atoms are actually used — apps
9
- * that never import atoms don't bundle the reactive system.
10
- */
11
- let onActorRead;
12
- function installActorReadHook(hook) {
13
- onActorRead = hook;
14
- }
15
-
16
3
  class Mailbox {
17
4
  constructor(_process) {
18
5
  this._process = _process;
@@ -66,6 +53,8 @@ const NULL_EVENT = '';
66
53
  const STATE_IDENTIFIER = '#';
67
54
  const WILDCARD = '*';
68
55
  const XSTATE_INIT = '@xstate.init';
56
+ const XSTATE_START = '@xstate.start';
57
+ const XSTATE_SPAWN = '@xstate.spawn';
69
58
  const XSTATE_STOP = '@xstate.stop';
70
59
 
71
60
  /**
@@ -142,6 +131,12 @@ function createErrorActorEvent(id, error) {
142
131
  actorId: id
143
132
  };
144
133
  }
134
+ function createErrorPlatformEvent(kind, error) {
135
+ return {
136
+ type: `xstate.error.${kind}`,
137
+ error
138
+ };
139
+ }
145
140
  function createInitEvent(input) {
146
141
  return {
147
142
  type: XSTATE_INIT,
@@ -166,6 +161,9 @@ function reportUnhandledError(err) {
166
161
  const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
167
162
 
168
163
  const builtInActions = {
164
+ // No-op: creation/registration are eager and pure; the start is a separate
165
+ // deferred `@xstate.start` effect (see `deriveDeferredStarts`).
166
+ ['@xstate.spawn']: _actor => {},
169
167
  ['@xstate.start']: actor => {
170
168
  actor.start();
171
169
  },
@@ -187,7 +185,7 @@ const builtInActions = {
187
185
  ['@xstate.cancel']: (actorScope, sendId) => {
188
186
  actorScope.system.scheduler.cancel(actorScope.self, sendId);
189
187
  },
190
- ['@xstate.stopChild']: (actorScope, actor) => {
188
+ ['@xstate.stop']: (actorScope, actor) => {
191
189
  actorScope.stopChild(actor);
192
190
  }
193
191
  };
@@ -199,15 +197,20 @@ const subscriptions = /* #__PURE__ */new WeakMap();
199
197
  * (`enq.listen()` / `enq.subscribeTo()`) and detaches when stopped.
200
198
  */
201
199
  function createAttachedLogic(attach) {
200
+ const initialTransition = (input, _) => [{
201
+ status: 'active',
202
+ output: undefined,
203
+ error: undefined,
204
+ input
205
+ }, []];
202
206
  return {
203
207
  start: (state, {
204
208
  self,
205
209
  system
206
210
  }) => {
207
- // Don't attach if the target doesn't exist, or (for actors) is stopped.
208
- // Atoms have no `getSnapshot` / lifecycle, so they always attach.
211
+ // Don't attach if the target doesn't exist or is stopped.
209
212
  const target = state.input.actor;
210
- if (!target || typeof target.getSnapshot === 'function' && target.getSnapshot().status === 'stopped') {
213
+ if (!target || target.getSnapshot().status === 'stopped') {
211
214
  return;
212
215
  }
213
216
  const subscription = attach(state.input, {
@@ -250,20 +253,16 @@ function createAttachedLogic(attach) {
250
253
  if (event.type === XSTATE_STOP) {
251
254
  subscriptions.get(self)?.unsubscribe();
252
255
  subscriptions.delete(self);
253
- return {
256
+ return [{
254
257
  ...state,
255
258
  status: 'stopped',
256
259
  error: undefined
257
- };
260
+ }, []];
258
261
  }
259
- return state;
262
+ return [state, []];
260
263
  },
261
- getInitialSnapshot: (_, input) => ({
262
- status: 'active',
263
- output: undefined,
264
- error: undefined,
265
- input
266
- }),
264
+ initialTransition,
265
+ getInitialSnapshot: (actorScope, input) => initialTransition(input)[0],
267
266
  getPersistedSnapshot: snapshot => snapshot,
268
267
  restoreSnapshot: snapshot => snapshot
269
268
  };
@@ -309,620 +308,6 @@ function createListenerLogic() {
309
308
  // Singleton logic instance
310
309
  const listenerLogic = /* #__PURE__ */createListenerLogic();
311
310
 
312
- /* eslint-disable */
313
- // Adapted from Alien Signals
314
- // https://github.com/stackblitz/alien-signals/
315
-
316
- let ReactiveFlags = /*#__PURE__*/function (ReactiveFlags) {
317
- ReactiveFlags[ReactiveFlags["None"] = 0] = "None";
318
- ReactiveFlags[ReactiveFlags["Mutable"] = 1] = "Mutable";
319
- ReactiveFlags[ReactiveFlags["Watching"] = 2] = "Watching";
320
- ReactiveFlags[ReactiveFlags["RecursedCheck"] = 4] = "RecursedCheck";
321
- ReactiveFlags[ReactiveFlags["Recursed"] = 8] = "Recursed";
322
- ReactiveFlags[ReactiveFlags["Dirty"] = 16] = "Dirty";
323
- ReactiveFlags[ReactiveFlags["Pending"] = 32] = "Pending";
324
- return ReactiveFlags;
325
- }({});
326
- function createReactiveSystem({
327
- update,
328
- notify,
329
- unwatched
330
- }) {
331
- return {
332
- link,
333
- unlink,
334
- propagate,
335
- checkDirty,
336
- shallowPropagate
337
- };
338
- function link(dep, sub, version) {
339
- const prevDep = sub.depsTail;
340
- if (prevDep !== undefined && prevDep.dep === dep) {
341
- return;
342
- }
343
- const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps;
344
- if (nextDep !== undefined && nextDep.dep === dep) {
345
- nextDep.version = version;
346
- sub.depsTail = nextDep;
347
- return;
348
- }
349
- const prevSub = dep.subsTail;
350
- if (prevSub !== undefined && prevSub.version === version && prevSub.sub === sub) {
351
- return;
352
- }
353
- const newLink = sub.depsTail = dep.subsTail = {
354
- version,
355
- dep,
356
- sub,
357
- prevDep,
358
- nextDep,
359
- prevSub,
360
- nextSub: undefined
361
- };
362
- if (nextDep !== undefined) {
363
- nextDep.prevDep = newLink;
364
- }
365
- if (prevDep !== undefined) {
366
- prevDep.nextDep = newLink;
367
- } else {
368
- sub.deps = newLink;
369
- }
370
- if (prevSub !== undefined) {
371
- prevSub.nextSub = newLink;
372
- } else {
373
- dep.subs = newLink;
374
- }
375
- }
376
- function unlink(link, sub = link.sub) {
377
- const dep = link.dep;
378
- const prevDep = link.prevDep;
379
- const nextDep = link.nextDep;
380
- const nextSub = link.nextSub;
381
- const prevSub = link.prevSub;
382
- if (nextDep !== undefined) {
383
- nextDep.prevDep = prevDep;
384
- } else {
385
- sub.depsTail = prevDep;
386
- }
387
- if (prevDep !== undefined) {
388
- prevDep.nextDep = nextDep;
389
- } else {
390
- sub.deps = nextDep;
391
- }
392
- if (nextSub !== undefined) {
393
- nextSub.prevSub = prevSub;
394
- } else {
395
- dep.subsTail = prevSub;
396
- }
397
- if (prevSub !== undefined) {
398
- prevSub.nextSub = nextSub;
399
- } else if ((dep.subs = nextSub) === undefined) {
400
- unwatched(dep);
401
- }
402
- return nextDep;
403
- }
404
- function propagate(link) {
405
- let next = link.nextSub;
406
- let stack;
407
- top: do {
408
- const sub = link.sub;
409
- let flags = sub.flags;
410
- if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending))) {
411
- sub.flags = flags | ReactiveFlags.Pending;
412
- } else if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))) {
413
- flags = ReactiveFlags.None;
414
- } else if (!(flags & ReactiveFlags.RecursedCheck)) {
415
- sub.flags = flags & ~ReactiveFlags.Recursed | ReactiveFlags.Pending;
416
- } else if (!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) && isValidLink(link, sub)) {
417
- sub.flags = flags | (ReactiveFlags.Recursed | ReactiveFlags.Pending);
418
- flags &= ReactiveFlags.Mutable;
419
- } else {
420
- flags = ReactiveFlags.None;
421
- }
422
- if (flags & ReactiveFlags.Watching) {
423
- notify(sub);
424
- }
425
- if (flags & ReactiveFlags.Mutable) {
426
- const subSubs = sub.subs;
427
- if (subSubs !== undefined) {
428
- const nextSub = (link = subSubs).nextSub;
429
- if (nextSub !== undefined) {
430
- stack = {
431
- value: next,
432
- prev: stack
433
- };
434
- next = nextSub;
435
- }
436
- continue;
437
- }
438
- }
439
- if ((link = next) !== undefined) {
440
- next = link.nextSub;
441
- continue;
442
- }
443
- while (stack !== undefined) {
444
- link = stack.value;
445
- stack = stack.prev;
446
- if (link !== undefined) {
447
- next = link.nextSub;
448
- continue top;
449
- }
450
- }
451
- break;
452
- } while (true);
453
- }
454
- function checkDirty(link, sub) {
455
- let stack;
456
- let checkDepth = 0;
457
- let dirty = false;
458
- top: do {
459
- const dep = link.dep;
460
- const flags = dep.flags;
461
- if (sub.flags & ReactiveFlags.Dirty) {
462
- dirty = true;
463
- } else if ((flags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) === (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) {
464
- if (update(dep)) {
465
- const subs = dep.subs;
466
- if (subs.nextSub !== undefined) {
467
- shallowPropagate(subs);
468
- }
469
- dirty = true;
470
- }
471
- } else if ((flags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) === (ReactiveFlags.Mutable | ReactiveFlags.Pending)) {
472
- if (link.nextSub !== undefined || link.prevSub !== undefined) {
473
- stack = {
474
- value: link,
475
- prev: stack
476
- };
477
- }
478
- link = dep.deps;
479
- sub = dep;
480
- ++checkDepth;
481
- continue;
482
- }
483
- if (!dirty) {
484
- const nextDep = link.nextDep;
485
- if (nextDep !== undefined) {
486
- link = nextDep;
487
- continue;
488
- }
489
- }
490
- while (checkDepth--) {
491
- const firstSub = sub.subs;
492
- const hasMultipleSubs = firstSub.nextSub !== undefined;
493
- if (hasMultipleSubs) {
494
- link = stack.value;
495
- stack = stack.prev;
496
- } else {
497
- link = firstSub;
498
- }
499
- if (dirty) {
500
- if (update(sub)) {
501
- if (hasMultipleSubs) {
502
- shallowPropagate(firstSub);
503
- }
504
- sub = link.sub;
505
- continue;
506
- }
507
- dirty = false;
508
- } else {
509
- sub.flags &= ~ReactiveFlags.Pending;
510
- }
511
- sub = link.sub;
512
- const nextDep = link.nextDep;
513
- if (nextDep !== undefined) {
514
- link = nextDep;
515
- continue top;
516
- }
517
- }
518
- return dirty;
519
- } while (true);
520
- }
521
- function shallowPropagate(link) {
522
- do {
523
- const sub = link.sub;
524
- const flags = sub.flags;
525
- if ((flags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) === ReactiveFlags.Pending) {
526
- sub.flags = flags | ReactiveFlags.Dirty;
527
- if ((flags & (ReactiveFlags.Watching | ReactiveFlags.RecursedCheck)) === ReactiveFlags.Watching) {
528
- notify(sub);
529
- }
530
- }
531
- } while ((link = link.nextSub) !== undefined);
532
- }
533
- function isValidLink(checkLink, sub) {
534
- let link = sub.depsTail;
535
- while (link !== undefined) {
536
- if (link === checkLink) {
537
- return true;
538
- }
539
- link = link.prevDep;
540
- }
541
- return false;
542
- }
543
- }
544
-
545
- /**
546
- * Nominal brand (phantom, compile-time only) marking a value as an XState atom.
547
- * Real atoms get the runtime brand ({@link $$atom}) in `createAtom`; this type
548
- * brand keeps atom-only APIs (e.g. `enq.subscribeTo`) from structurally
549
- * accepting plain readables like `actor.select(...)` that would be dispatched
550
- * down the non-atom path at runtime.
551
- */
552
-
553
- /** Brand marking an object as an atom, so it can be detected structurally. */
554
- const $$atom = /* #__PURE__ */Symbol.for('xstate.atom');
555
-
556
- /**
557
- * Returns `true` if the value is an atom (writable, computed, async, or
558
- * reducer). Lets actor-consuming APIs (e.g. `enq.subscribeTo`) accept atoms
559
- * directly.
560
- */
561
- function isAtom(value) {
562
- return !!value && typeof value === 'object' && $$atom in value;
563
- }
564
- const queuedEffects = [];
565
- let cycle = 0;
566
- // Initialized lazily on first atom creation (instead of at module evaluation)
567
- // so the reactive system stays tree-shakeable for apps that never use atoms —
568
-
569
- // would be retained by consumer bundlers even when atoms are unused.
570
- let link;
571
- let unlink;
572
- let propagate;
573
- let checkDirty;
574
- let shallowPropagate;
575
- let reactiveSystemInstalled = false;
576
- function ensureReactiveSystem() {
577
- if (reactiveSystemInstalled) {
578
- return;
579
- }
580
- reactiveSystemInstalled = true;
581
- ({
582
- link,
583
- unlink,
584
- propagate,
585
- checkDirty,
586
- shallowPropagate
587
- } = createReactiveSystem({
588
- update(atom) {
589
- return atom._update();
590
- },
591
- notify(effect) {
592
- queuedEffects[queuedEffectsLength++] = effect;
593
- effect.flags &= ~ReactiveFlags.Watching;
594
- },
595
- unwatched(atom) {
596
- if (atom.depsTail !== undefined) {
597
- atom.depsTail = undefined;
598
- atom.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty;
599
- purgeDeps(atom);
600
- }
601
- }
602
- }));
603
- }
604
- let notifyIndex = 0;
605
- let queuedEffectsLength = 0;
606
- let activeSub;
607
- function purgeDeps(sub) {
608
- const depsTail = sub.depsTail;
609
- let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps;
610
- while (dep !== undefined) {
611
- dep = unlink(dep, sub);
612
- }
613
- }
614
- function flush() {
615
- while (notifyIndex < queuedEffectsLength) {
616
- const effect = queuedEffects[notifyIndex];
617
- queuedEffects[notifyIndex++] = undefined;
618
- effect.notify();
619
- }
620
- notifyIndex = 0;
621
- queuedEffectsLength = 0;
622
- }
623
-
624
- /** The current state of an async atom. */
625
-
626
- /** Options passed to an async atom getter. */
627
-
628
- function compareAsyncAtomState(a, b) {
629
- if (a.status !== b.status) {
630
- return false;
631
- }
632
- if (a.status === 'done' && b.status === 'done') {
633
- return Object.is(a.data, b.data);
634
- }
635
- if (a.status === 'error' && b.status === 'error') {
636
- return Object.is(a.error, b.error);
637
- }
638
- // both 'pending'
639
- return true;
640
- }
641
- function updateAsyncAtom(atom, nextValue) {
642
- if (atom._update(nextValue)) {
643
- const subs = atom.subs;
644
- if (subs !== undefined) {
645
- propagate(subs);
646
- shallowPropagate(subs);
647
- flush();
648
- }
649
- }
650
- }
651
-
652
- /**
653
- * Creates a read-only atom whose value is loaded from an async getter.
654
- *
655
- * The getter receives an `AbortSignal`; when the async atom recomputes, the
656
- * previous signal is aborted and stale resolutions are ignored.
657
- */
658
- function createAsyncAtom(getValue, options) {
659
- const ref = {};
660
- let currentController;
661
- let currentRunId = 0;
662
- const atom = createAtom(() => {
663
- currentController?.abort();
664
- const controller = new AbortController();
665
- const runId = ++currentRunId;
666
- currentController = controller;
667
- getValue({
668
- signal: controller.signal
669
- }).then(data => {
670
- if (runId !== currentRunId || controller.signal.aborted) {
671
- return;
672
- }
673
- updateAsyncAtom(ref.current, {
674
- status: 'done',
675
- data
676
- });
677
- }, error => {
678
- if (runId !== currentRunId || controller.signal.aborted) {
679
- return;
680
- }
681
- updateAsyncAtom(ref.current, {
682
- status: 'error',
683
- error
684
- });
685
- });
686
- return {
687
- status: 'pending'
688
- };
689
- }, {
690
- compare: compareAsyncAtomState,
691
- ...options
692
- });
693
- ref.current = atom;
694
- return atom;
695
- }
696
-
697
- /**
698
- * Mirror atoms that bump a version whenever a tracked actor emits a snapshot,
699
- * letting computed atoms depend on `actor.get()` reads.
700
- *
701
- * The hook is installed lazily on first atom creation (not at module
702
- * evaluation) so that it remains tree-shakeable for apps that never use atoms —
703
- * any tracked read necessarily happens inside an atom computation, so first-use
704
- * installation is always early enough.
705
- */
706
- let actorInteropInstalled = false;
707
- function ensureActorInterop() {
708
- if (actorInteropInstalled) {
709
- return;
710
- }
711
- actorInteropInstalled = true;
712
- const actorVersions = new WeakMap();
713
- installActorReadHook(actorRef => {
714
- if (activeSub === undefined) {
715
- return;
716
- }
717
- let version = actorVersions.get(actorRef);
718
- if (version === undefined) {
719
- version = createAtom(0);
720
- actorVersions.set(actorRef, version);
721
- actorRef.subscribe({
722
- next: () => version.set(v => v + 1),
723
- error: () => {}
724
- });
725
- }
726
- version.get();
727
- });
728
- }
729
-
730
- /**
731
- * Creates an atom.
732
- *
733
- * Pass a value for a writable atom or a getter for a computed read-only atom.
734
- */
735
-
736
- function createAtom(valueOrFn, optionsOrInput) {
737
- ensureReactiveSystem();
738
- ensureActorInterop();
739
- const isComputed = typeof valueOrFn === 'function';
740
- const getter = valueOrFn;
741
-
742
- // Create plain object atom
743
- const atom = {
744
- _snapshot: isComputed ? undefined : valueOrFn,
745
- subs: undefined,
746
- subsTail: undefined,
747
- deps: undefined,
748
- depsTail: undefined,
749
- flags: isComputed ? ReactiveFlags.None : ReactiveFlags.Mutable,
750
- get() {
751
- if (activeSub !== undefined) {
752
- link(atom, activeSub, cycle);
753
- }
754
- return atom._snapshot;
755
- },
756
- subscribe(observerOrFn) {
757
- const observer = typeof observerOrFn === 'function' ? {
758
- next: observerOrFn
759
- } : observerOrFn;
760
- const observed = {
761
- current: false
762
- };
763
- const e = effect(() => {
764
- atom.get();
765
- if (!observed.current) {
766
- observed.current = true;
767
- } else {
768
- const prevSub = activeSub;
769
- activeSub = undefined;
770
- try {
771
- observer.next?.(atom._snapshot);
772
- } catch (err) {
773
- reportUnhandledError(err);
774
- } finally {
775
- activeSub = prevSub;
776
- }
777
-
778
- // If the observer synchronously updates any of our deps we'll be
779
- // marked as dirty preventing this effect from re-running. Request
780
- // the value again to reconcile any dirty deps.
781
- atom.get();
782
- }
783
- });
784
- return {
785
- unsubscribe: () => {
786
- e.stop();
787
- }
788
- };
789
- },
790
- _update(getValue) {
791
- const prevSub = activeSub;
792
- const compare = optionsOrInput?.compare ?? Object.is;
793
- activeSub = atom;
794
- ++cycle;
795
- atom.depsTail = undefined;
796
- if (isComputed) {
797
- atom.flags = ReactiveFlags.Mutable | ReactiveFlags.RecursedCheck;
798
- }
799
- try {
800
- const oldValue = atom._snapshot;
801
- const newValue = typeof getValue === 'function' ? getValue(oldValue) : getValue === undefined && isComputed ? getter(oldValue) : getValue;
802
- if (oldValue === undefined || !compare(oldValue, newValue)) {
803
- atom._snapshot = newValue;
804
- return true;
805
- }
806
- return false;
807
- } finally {
808
- activeSub = prevSub;
809
- if (isComputed) {
810
- atom.flags &= ~ReactiveFlags.RecursedCheck;
811
- }
812
- purgeDeps(atom);
813
- }
814
- }
815
- };
816
- if (isComputed) {
817
- atom.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty;
818
- atom.get = function () {
819
- const flags = atom.flags;
820
- if (flags & ReactiveFlags.Dirty || flags & ReactiveFlags.Pending && checkDirty(atom.deps, atom)) {
821
- if (atom._update()) {
822
- const subs = atom.subs;
823
- if (subs !== undefined) {
824
- shallowPropagate(subs);
825
- }
826
- }
827
- } else if (flags & ReactiveFlags.Pending) {
828
- atom.flags = flags & ~ReactiveFlags.Pending;
829
- }
830
- if (activeSub !== undefined) {
831
- link(atom, activeSub, cycle);
832
- }
833
- return atom._snapshot;
834
- };
835
- } else {
836
- atom.set = function (valueOrFn) {
837
- if (atom._update(valueOrFn)) {
838
- const subs = atom.subs;
839
- if (subs !== undefined) {
840
- propagate(subs);
841
- shallowPropagate(subs);
842
- flush();
843
- }
844
- }
845
- };
846
- }
847
- atom[$$atom] = true;
848
- return atom;
849
- }
850
-
851
- /**
852
- * Creates an inert atom config that can be instantiated later.
853
- *
854
- * Useful for framework hooks that need to create a stable local atom from
855
- * component input.
856
- */
857
-
858
- function createAtomConfig(initialValueOrFn, options) {
859
- return {
860
- createAtom(input) {
861
- const initialValue = typeof initialValueOrFn === 'function' ? initialValueOrFn(input) : initialValueOrFn;
862
- return createAtom(initialValue, options);
863
- }
864
- };
865
- }
866
-
867
- /** Creates an atom whose updates are handled by a reducer function. */
868
- function createReducerAtom(initialValue, reducer, options) {
869
- const atom = createAtom(initialValue, options);
870
- return {
871
- [$$atom]: true,
872
- get: atom.get.bind(atom),
873
- subscribe: atom.subscribe.bind(atom),
874
- send(event) {
875
- const prevSub = activeSub;
876
- activeSub = undefined;
877
- let nextState;
878
- try {
879
- nextState = reducer(atom.get(), event);
880
- } finally {
881
- activeSub = prevSub;
882
- }
883
- atom.set(nextState);
884
- }
885
- };
886
- }
887
- function effect(fn) {
888
- const run = () => {
889
- const prevSub = activeSub;
890
- activeSub = effectObj;
891
- ++cycle;
892
- effectObj.depsTail = undefined;
893
- effectObj.flags = ReactiveFlags.Watching | ReactiveFlags.RecursedCheck;
894
- try {
895
- return fn();
896
- } finally {
897
- activeSub = prevSub;
898
- effectObj.flags &= ~ReactiveFlags.RecursedCheck;
899
- purgeDeps(effectObj);
900
- }
901
- };
902
- const effectObj = {
903
- deps: undefined,
904
- depsTail: undefined,
905
- subs: undefined,
906
- subsTail: undefined,
907
- flags: ReactiveFlags.Watching | ReactiveFlags.RecursedCheck,
908
- notify() {
909
- const flags = this.flags;
910
- if (flags & ReactiveFlags.Dirty || flags & ReactiveFlags.Pending && checkDirty(this.deps, this)) {
911
- run();
912
- } else {
913
- this.flags = ReactiveFlags.Watching;
914
- }
915
- },
916
- stop() {
917
- this.flags = ReactiveFlags.None;
918
- this.depsTail = undefined;
919
- purgeDeps(this);
920
- }
921
- };
922
- run();
923
- return effectObj;
924
- }
925
-
926
311
  /**
927
312
  * Creates actor logic for subscribing to lifecycle events (done/error/snapshot)
928
313
  * from another actor. Used internally by `enq.subscribeTo()`.
@@ -940,15 +325,6 @@ function createSubscriptionLogic() {
940
325
  error,
941
326
  snapshot: onSnapshot
942
327
  } = mappers;
943
-
944
- // Atoms emit a raw value (no `status`) and have no done/error lifecycle —
945
- // pass the value straight to the `snapshot` mapper.
946
- if (isAtom(actor)) {
947
- if (!onSnapshot) {
948
- return;
949
- }
950
- return actor.subscribe(value => relayMappedToParent(self, system, () => onSnapshot(value)));
951
- }
952
328
  return actor.subscribe({
953
329
  next: snapshot => {
954
330
  if (snapshot.status === 'done' && done) {
@@ -974,6 +350,19 @@ function createSubscriptionLogic() {
974
350
  // Singleton logic instance
975
351
  const subscriptionLogic = /* #__PURE__ */createSubscriptionLogic();
976
352
 
353
+ const builtInExecutableAction = Symbol('xstate.builtInExecutableAction');
354
+ function brandBuiltInExecutableAction(action) {
355
+ Object.defineProperty(action, builtInExecutableAction, {
356
+ value: true
357
+ });
358
+ return action;
359
+ }
360
+ function mergeContextPatch(context, patch) {
361
+ return {
362
+ ...context,
363
+ ...patch
364
+ };
365
+ }
977
366
  function pushBuiltInAction(actions, action, ...args) {
978
367
  actions.push({
979
368
  action,
@@ -1009,11 +398,11 @@ function unregisterChild(actor) {
1009
398
  _special: true
1010
399
  });
1011
400
  }
1012
- function pushStartedChild(actions, actor, id) {
1013
- pushBuiltInAction(actions, builtInActions['@xstate.start'], actor);
401
+ function pushSpawnedChild(actions, actor, id) {
402
+ pushBuiltInAction(actions, builtInActions['@xstate.spawn'], actor);
1014
403
  actions.push(registerSpawnedChild(actor, id ?? actor.id));
1015
404
  }
1016
- function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubscriptions = false) {
405
+ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubscriptions = false, createActors = true) {
1017
406
  const props = {
1018
407
  cancel: id => {
1019
408
  pushBuiltInAction(actions, builtInActions['@xstate.cancel'], actorScope, id);
@@ -1035,21 +424,32 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
1035
424
  }
1036
425
  },
1037
426
  spawn: (logic, options) => {
427
+ if (!createActors) {
428
+ // TODO: replace this speculative placeholder with a typed inert actor ref.
429
+ return {
430
+ id: options?.id ?? options?.registryKey ?? logic.id
431
+ };
432
+ }
1038
433
  const actor = createActor(logic, {
1039
434
  ...options,
1040
435
  parent: actorScope.self
1041
436
  });
1042
- pushStartedChild(actions, actor, options?.id);
437
+ pushSpawnedChild(actions, actor, options?.id);
1043
438
  return actor;
1044
439
  },
1045
440
  sendTo: (actor, event, options) => {
1046
- if (actor) {
1047
- pushBuiltInAction(actions, builtInActions['@xstate.sendTo'], actorScope, actor, event, options);
441
+ if (!actor) {
442
+ internalEvents.push(createErrorPlatformEvent('communication', {
443
+ message: 'Unable to send event to an undefined actor',
444
+ event
445
+ }));
446
+ return;
1048
447
  }
448
+ pushBuiltInAction(actions, builtInActions['@xstate.sendTo'], actorScope, actor, event, options);
1049
449
  },
1050
450
  stop: actor => {
1051
451
  if (actor) {
1052
- pushBuiltInAction(actions, builtInActions['@xstate.stopChild'], actorScope, actor);
452
+ pushBuiltInAction(actions, builtInActions['@xstate.stop'], actorScope, actor);
1053
453
  actions.push(unregisterChild(actor));
1054
454
  }
1055
455
  }
@@ -1066,7 +466,7 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
1066
466
  input,
1067
467
  parent: actorScope.self
1068
468
  });
1069
- pushBuiltInAction(actions, builtInActions['@xstate.start'], listenerActor);
469
+ pushBuiltInAction(actions, builtInActions['@xstate.spawn'], listenerActor);
1070
470
  return listenerActor;
1071
471
  },
1072
472
  subscribeTo: (actor, mappers) => {
@@ -1081,34 +481,125 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
1081
481
  input,
1082
482
  parent: actorScope.self
1083
483
  });
1084
- pushBuiltInAction(actions, builtInActions['@xstate.start'], subscriptionActor);
484
+ pushBuiltInAction(actions, builtInActions['@xstate.spawn'], subscriptionActor);
1085
485
  return subscriptionActor;
1086
486
  }
1087
487
  });
1088
488
  }
1089
- return createEnqueueObject$1(props, (action, ...args) => {
489
+ return createEnqueueObject(props, (action, ...args) => {
1090
490
  pushBuiltInAction(actions, action, ...args);
1091
491
  });
1092
492
  }
1093
- function resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope, actions) {
1094
- let intermediateSnapshot = currentSnapshot;
1095
- for (const action of actions) {
1096
- const isInline = typeof action === 'function';
1097
- const resolvedAction = isInline ? action : typeof action === 'object' && 'action' in action && typeof action.action === 'function' ? action.action.bind(null, ...action.args) : false;
1098
- if (!resolvedAction && typeof action === 'object' && action !== null) {
1099
- actorScope.defer(() => {
1100
- actorScope.emit(action);
1101
- });
493
+ function getBuiltInActionFields(action, args) {
494
+ switch (action) {
495
+ case builtInActions['@xstate.spawn']:
496
+ {
497
+ const [actor] = args;
498
+ return {
499
+ actor,
500
+ id: actor.id,
501
+ logic: actor.logic,
502
+ src: actor.src,
503
+ input: actor.options?.input
504
+ };
505
+ }
506
+ case builtInActions['@xstate.raise']:
507
+ {
508
+ const [, event, options] = args;
509
+ return {
510
+ event,
511
+ id: options?.id,
512
+ delay: options?.delay
513
+ };
514
+ }
515
+ case builtInActions['@xstate.sendTo']:
516
+ {
517
+ const [, target, event, options] = args;
518
+ return {
519
+ target,
520
+ event,
521
+ id: options?.id,
522
+ delay: options?.delay
523
+ };
524
+ }
525
+ case builtInActions['@xstate.cancel']:
526
+ {
527
+ const [, id] = args;
528
+ return {
529
+ id
530
+ };
531
+ }
532
+ case builtInActions['@xstate.stop']:
533
+ {
534
+ const [, actor] = args;
535
+ return {
536
+ actor
537
+ };
538
+ }
539
+ default:
540
+ return undefined;
541
+ }
542
+ }
543
+ function createStartEffect(actor) {
544
+ const action = builtInActions['@xstate.start'];
545
+ const args = [actor];
546
+ return brandBuiltInExecutableAction({
547
+ type: XSTATE_START,
548
+ params: {
549
+ action,
550
+ args
551
+ },
552
+ args,
553
+ exec: action.bind(null, actor),
554
+ actor,
555
+ id: actor.id
556
+ });
557
+ }
558
+
559
+ /**
560
+ * Attached (listener/subscription) starts are ordered before child starts so
561
+ * that a listener/subscription captures events emitted synchronously as its
562
+ * target actor starts.
563
+ */
564
+ function deriveDeferredStarts(effects) {
565
+ const attachedStarts = [];
566
+ const childStarts = [];
567
+ for (const effect of effects) {
568
+ if (!isBuiltInExecutableAction(effect) || effect.type !== XSTATE_SPAWN) {
569
+ continue;
1102
570
  }
571
+ const {
572
+ actor,
573
+ logic
574
+ } = effect;
575
+ const start = createStartEffect(actor);
576
+ if (logic === listenerLogic || logic === subscriptionLogic) {
577
+ attachedStarts.push(start);
578
+ } else {
579
+ childStarts.push(start);
580
+ }
581
+ }
582
+ return [...attachedStarts, ...childStarts];
583
+ }
584
+ function isBuiltInExecutableAction(action) {
585
+ return action[builtInExecutableAction] === true;
586
+ }
587
+ function resolveActionsWithContext(currentSnapshot, event, actorScope, actions) {
588
+ let intermediateSnapshot = currentSnapshot;
589
+ const executableActions = [];
590
+ for (const action of actions) {
591
+ const isInline = typeof action === 'function';
592
+ const resolvedAction = isInline ? action : typeof action === 'object' && 'action' in action && typeof action.action === 'function' ? action.action.bind(null, ...action.args) : false;
1103
593
  const actionArgs = {
1104
594
  context: intermediateSnapshot.context,
1105
595
  event,
596
+ output: getEventOutput(event),
1106
597
  self: actorScope.self,
1107
598
  system: actorScope.system,
1108
599
  children: intermediateSnapshot.children,
1109
600
  parent: actorScope.self._parent,
1110
601
  actions: currentSnapshot.machine.implementations.actions,
1111
- actors: currentSnapshot.machine.implementations.actors
602
+ actorSources: currentSnapshot.machine.implementations.actorSources
1112
603
  };
1113
604
  let actionParams = undefined;
1114
605
  if (typeof action === 'object' && action !== null) {
@@ -1119,7 +610,7 @@ function resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope,
1119
610
  actionParams = emittedEventParams;
1120
611
  }
1121
612
  if (resolvedAction && '_special' in resolvedAction) {
1122
- actorScope.actionExecutor({
613
+ executableActions.push({
1123
614
  type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type ?? '(anonymous)' : action.name || '(anonymous)',
1124
615
  params: actionParams,
1125
616
  args: [],
@@ -1128,30 +619,37 @@ function resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope,
1128
619
  const specialAction = resolvedAction;
1129
620
  const res = specialAction(actionArgs, emptyEnqueueObject);
1130
621
  if (res && ('context' in res || 'children' in res)) {
1131
- intermediateSnapshot = cloneMachineSnapshot(intermediateSnapshot, {
1132
- ...('context' in res ? {
1133
- context: res.context
622
+ // Special-action patches never change `_nodes`, so a shallow clone is
623
+ // equivalent to `cloneMachineSnapshot` — and keeps this module (and
624
+ // non-machine logic like `createFSM`) independent of State.ts.
625
+ intermediateSnapshot = {
626
+ ...intermediateSnapshot,
627
+ ...(res.context !== undefined ? {
628
+ context: mergeContextPatch(intermediateSnapshot.context, res.context)
1134
629
  } : {}),
1135
630
  ...('children' in res ? {
1136
631
  children: res.children
1137
632
  } : {})
1138
- });
633
+ };
1139
634
  }
1140
635
  continue;
1141
636
  }
1142
637
  if (!resolvedAction || !('resolve' in resolvedAction)) {
1143
- actorScope.actionExecutor({
638
+ const builtInFields = typeof action === 'object' && action !== null && 'action' in action && typeof action.action === 'function' ? getBuiltInActionFields(action.action, action.args) : undefined;
639
+ const executableAction = {
1144
640
  type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type : action.name || '(anonymous)',
1145
641
  params: actionParams,
1146
642
  args: typeof action === 'object' && 'action' in action ? action.args : [],
1147
- exec: resolvedAction
1148
- });
643
+ exec: resolvedAction || (typeof action === 'object' && action !== null ? () => actorScope.defer(() => actorScope.emit(action)) : undefined),
644
+ ...builtInFields
645
+ };
646
+ executableActions.push(builtInFields ? brandBuiltInExecutableAction(executableAction) : executableAction);
1149
647
  continue;
1150
648
  }
1151
649
  }
1152
- return intermediateSnapshot;
650
+ return [intermediateSnapshot, executableActions];
1153
651
  }
1154
- function createEnqueueObject$1(props, action) {
652
+ function createEnqueueObject(props, action) {
1155
653
  const enqueueFn = (fn, ...args) => {
1156
654
  action(fn, ...args);
1157
655
  };
@@ -1169,7 +667,7 @@ function createEnqueueObject$1(props, action) {
1169
667
  });
1170
668
  return enqueueFn;
1171
669
  }
1172
- const emptyEnqueueObject = createEnqueueObject$1({}, () => {});
670
+ const emptyEnqueueObject = createEnqueueObject({}, () => {});
1173
671
 
1174
672
  function parseDurationToMilliseconds(duration) {
1175
673
  const normalizedDuration = duration.trim();
@@ -1231,6 +729,9 @@ function resolveDelay(delay, delaySource, args) {
1231
729
  }
1232
730
  return configuredDelay;
1233
731
  }
732
+ function getStateInput(snapshot, stateNodeId) {
733
+ return snapshot._stateInputs?.[stateNodeId];
734
+ }
1234
735
  function isAtomicStateNode(stateNode) {
1235
736
  return stateNode.type === 'atomic' || stateNode.type === 'final' || stateNode.type === 'choice';
1236
737
  }
@@ -1343,22 +844,9 @@ function isInFinalState(stateNodeSet, stateNode) {
1343
844
  }
1344
845
  const isStateId = str => str[0] === STATE_IDENTIFIER;
1345
846
  function getCandidates(stateNode, receivedEventType) {
1346
- const candidates = stateNode.transitions.get(receivedEventType);
1347
- if (candidates) {
1348
- return candidates;
1349
- }
1350
- const matchingDescriptors = [];
1351
- for (const eventDescriptor of stateNode.transitions.keys()) {
1352
- if (matchesEventDescriptor(receivedEventType, eventDescriptor)) {
1353
- matchingDescriptors.push(eventDescriptor);
1354
- }
1355
- }
1356
- matchingDescriptors.sort((a, b) => b.length - a.length);
1357
- const wildcardCandidates = [];
1358
- for (const descriptor of matchingDescriptors) {
1359
- wildcardCandidates.push(...stateNode.transitions.get(descriptor));
1360
- }
1361
- return wildcardCandidates;
847
+ const exactMatch = stateNode.transitions.get(receivedEventType);
848
+ const wildcardCandidates = [...stateNode.transitions.keys()].filter(eventDescriptor => eventDescriptor !== receivedEventType && matchesEventDescriptor(receivedEventType, eventDescriptor)).sort((a, b) => b.length - a.length).flatMap(key => stateNode.transitions.get(key));
849
+ return exactMatch ? [...exactMatch, ...wildcardCandidates] : wildcardCandidates;
1362
850
  }
1363
851
  function scheduleDelayedEvent(stateNode, event, resolveScheduledDelay) {
1364
852
  const eventType = event.type;
@@ -1387,61 +875,69 @@ function getDelayedTransitions(stateNode) {
1387
875
  if (!afterConfig && timeoutConfig === undefined && invokeDefs.length === 0) {
1388
876
  return [];
1389
877
  }
1390
- const mutateEntryExitWithDelay = delay => {
1391
- const afterEvent = createAfterEvent(delay, stateNode.id);
1392
- return scheduleDelayedEvent(stateNode, afterEvent, x => resolveDelay(delay, x.delays, {
1393
- context: x.context,
1394
- event: x.event,
1395
- stateNode
1396
- }));
1397
- };
1398
- const delayedTransitions = [];
1399
- const addDelayedTransitions = (transitionsConfig, event, delay) => {
1400
- for (const transition of toTransitionConfigArray(transitionsConfig)) {
1401
- delayedTransitions.push({
1402
- ...transition,
1403
- event,
1404
- delay
1405
- });
1406
- }
1407
- };
878
+
879
+ // Every delayed transition — `after`, state-level `timeout`/`onTimeout`, and
880
+ // invoke-level `timeout`/`onTimeout` — has the same shape: an event raised on
881
+ // entry (and cancelled on exit) plus a transition that catches it. They
882
+ // differ only in the event raised, the transition(s) caught, and whether the
883
+ // delay resolves against the machine's `delays` map (invoke timeouts do not).
884
+ //
885
+ // `xstate.timeout.<id>` is a dedicated event so a state-level `timeout` cannot
886
+ // collide with an explicit `after` entry on the same state. Invoke timeouts
887
+ // are scheduled on the enclosing state; completion transitions cancel the
888
+ // timer separately, so it clears even when the parent state stays active.
889
+ const sources = [];
1408
890
  if (afterConfig) {
1409
- for (const delay of Object.keys(afterConfig)) {
1410
- const configTransition = afterConfig[delay];
1411
- const parsedDelay = Number.isNaN(+delay) ? delay : +delay;
1412
- const eventType = mutateEntryExitWithDelay(parsedDelay);
1413
- const resolvedDelay = getConfiguredDelayValue(parsedDelay, stateNode.machine.implementations.delays);
1414
- addDelayedTransitions(configTransition, eventType, resolvedDelay);
891
+ for (const key of Object.keys(afterConfig)) {
892
+ const delay = Number.isNaN(+key) ? key : +key;
893
+ sources.push({
894
+ event: createAfterEvent(delay, stateNode.id),
895
+ delay,
896
+ transitions: afterConfig[key],
897
+ fromDelaysMap: true
898
+ });
1415
899
  }
1416
900
  }
1417
-
1418
- // Desugar state-level `timeout` + `onTimeout` into a delayed transition.
1419
- // Uses a dedicated `xstate.timeout.<id>` event so it cannot collide with
1420
- // explicit `after` entries on the same state.
1421
901
  if (timeoutConfig !== undefined && onTimeoutConfig) {
1422
- const timeoutEvent = createTimeoutEvent(stateNode.id);
1423
- const timeoutEventType = scheduleDelayedEvent(stateNode, timeoutEvent, x => resolveDelay(timeoutConfig, x.delays, {
1424
- context: x.context,
1425
- event: x.event,
1426
- stateNode
1427
- }));
1428
- const resolvedDelay = typeof timeoutConfig === 'function' ? timeoutConfig : getConfiguredDelayValue(timeoutConfig, stateNode.machine.implementations.delays);
1429
- addDelayedTransitions(onTimeoutConfig, timeoutEventType, resolvedDelay);
902
+ sources.push({
903
+ event: createTimeoutEvent(stateNode.id),
904
+ delay: timeoutConfig,
905
+ transitions: onTimeoutConfig,
906
+ fromDelaysMap: true
907
+ });
1430
908
  }
1431
-
1432
- // Desugar invoke-level `timeout` + `onTimeout` into a delayed transition on
1433
- // the enclosing state. Completion transitions cancel this timer separately,
1434
- // so the timeout is cleared even when the parent state stays active.
1435
909
  for (const invokeDef of invokeDefs) {
1436
- const invokeTimeoutEvent = createInvokeTimeoutEvent(invokeDef.id);
1437
- const invokeTimeout = invokeDef.timeout;
1438
- const invokeTimeoutEventType = scheduleDelayedEvent(stateNode, invokeTimeoutEvent, x => resolveDelay(invokeTimeout, {}, {
910
+ sources.push({
911
+ event: createInvokeTimeoutEvent(invokeDef.id),
912
+ delay: invokeDef.timeout,
913
+ transitions: invokeDef.onTimeout,
914
+ fromDelaysMap: false
915
+ });
916
+ }
917
+
918
+ // `delay` here is the raw config value, retained only as metadata on the
919
+ // transition definition — the live delay is resolved at runtime in the
920
+ // scheduled entry action, so no eager resolution is needed.
921
+ const delayedTransitions = [];
922
+ for (const {
923
+ event,
924
+ delay,
925
+ transitions,
926
+ fromDelaysMap
927
+ } of sources) {
928
+ const eventType = scheduleDelayedEvent(stateNode, event, x => resolveDelay(delay, fromDelaysMap ? x.delays : {}, {
1439
929
  context: x.context,
1440
930
  event: x.event,
1441
- stateNode
931
+ stateNode,
932
+ input: x.input
1442
933
  }));
1443
- const invokeOnTimeout = invokeDef.onTimeout;
1444
- addDelayedTransitions(invokeOnTimeout, invokeTimeoutEventType, invokeTimeout);
934
+ for (const transition of toTransitionConfigArray(transitions)) {
935
+ delayedTransitions.push({
936
+ ...transition,
937
+ event: eventType,
938
+ delay
939
+ });
940
+ }
1445
941
  }
1446
942
  return delayedTransitions.map(delayedTransition => ({
1447
943
  ...formatTransition(stateNode, delayedTransition.event, delayedTransition),
@@ -1503,30 +999,13 @@ function formatRouteTransitions(rootStateNode) {
1503
999
  }
1504
1000
  return;
1505
1001
  }
1506
- if ('$unserializable' in routeConfig) {
1507
- throw new Error(`State "${sn.id}" has a route that is not serializable. Re-provide the route function when reviving this machine.`);
1508
- }
1509
-
1510
- // Object-form route. A `guard` (string or function) on the object is
1511
- // only produced by the JSON layer (createMachineFromConfig) —
1512
- // authoring uses the function form above.
1513
- const userGuard = routeConfig.guard;
1002
+ const {
1003
+ guard: _guard,
1004
+ ...routeOptions
1005
+ } = routeConfig;
1514
1006
  const transition = {
1515
- ...routeConfig,
1516
- guard: userGuard ? args => {
1517
- if (!routeMatches(args)) return false;
1518
- if (typeof userGuard === 'function') return userGuard(args);
1519
- if (typeof userGuard === 'string') {
1520
- const guardImpl = args.guards?.[userGuard];
1521
- if (!guardImpl) {
1522
- // A typo'd guard name must fail loudly — silently passing
1523
- // would allow the route and corrupt machine behavior.
1524
- throw new Error(`Guard '${userGuard}' is not implemented in machine '${rootStateNode.machine.id}'.`);
1525
- }
1526
- return guardImpl(args);
1527
- }
1528
- return true;
1529
- } : routeMatches,
1007
+ ...routeOptions,
1008
+ guard: routeMatches,
1530
1009
  target: `#${routeId}`
1531
1010
  };
1532
1011
  routeTransitions.push(formatTransition(rootStateNode, 'xstate.route', transition));
@@ -1565,7 +1044,7 @@ function resolveTarget(stateNode, targets) {
1565
1044
  const targetStateNode = getStateNodeByPath(stateNode.parent, resolvedTarget);
1566
1045
  return targetStateNode;
1567
1046
  } catch (err) {
1568
- throw new Error(`Invalid transition definition for state node '${stateNode.id}':\n${err.message}`);
1047
+ throw new Error(`Invalid transition for '${stateNode.id}': ${err.message}`);
1569
1048
  }
1570
1049
  } else {
1571
1050
  throw new Error(`Invalid target: "${target}"`);
@@ -1575,6 +1054,14 @@ function resolveTarget(stateNode, targets) {
1575
1054
  function resolveHistoryDefaultTransition(stateNode) {
1576
1055
  const normalizedTarget = normalizeTarget(stateNode.config.target);
1577
1056
  if (!normalizedTarget) {
1057
+ if (stateNode.parent.type === 'parallel') {
1058
+ return {
1059
+ target: [stateNode.parent],
1060
+ source: stateNode,
1061
+ reenter: false,
1062
+ eventType: ''
1063
+ };
1064
+ }
1578
1065
  return stateNode.parent.initial;
1579
1066
  }
1580
1067
  return {
@@ -1616,10 +1103,7 @@ function getStateNode(stateNode, stateKey) {
1616
1103
  if (isStateId(stateKey)) {
1617
1104
  return stateNode.machine.getStateNodeById(stateKey);
1618
1105
  }
1619
- if (!stateNode.states) {
1620
- throw new Error(`Unable to retrieve child state '${stateKey}' from '${stateNode.id}'; no child states exist.`);
1621
- }
1622
- const result = stateNode.states[stateKey];
1106
+ const result = stateNode.states?.[stateKey];
1623
1107
  if (!result) {
1624
1108
  throw new Error(`Child state '${stateKey}' does not exist on '${stateNode.id}'`);
1625
1109
  }
@@ -1762,6 +1246,11 @@ function removeConflictingTransitions(enabledTransitions, stateNodeSet, snapshot
1762
1246
  if (hasIntersection(getExitSet(t1), getExitSet(t2))) {
1763
1247
  if (isDescendant(t1.source, t2.source)) {
1764
1248
  transitionsToRemove.add(t2);
1249
+ } else if (t2.source.type === 'final' && t1.source.type !== 'final') {
1250
+ // A transition sourced in a final state yields to a conflicting
1251
+ // transition from a live state, so a done region doesn't keep
1252
+ // consuming events its siblings can still handle.
1253
+ transitionsToRemove.add(t2);
1765
1254
  } else {
1766
1255
  t1Preempted = true;
1767
1256
  break;
@@ -1781,7 +1270,9 @@ function getEffectiveTargetStates(transition, snapshot, event, actorScope) {
1781
1270
  const historyValue = snapshot.historyValue;
1782
1271
  const {
1783
1272
  targets
1784
- } = getTransitionResult(transition, snapshot, event, actorScope);
1273
+ } = getTransitionResult(transition, snapshot, event, actorScope, {
1274
+ resolveActions: false
1275
+ });
1785
1276
  if (!targets) {
1786
1277
  return [];
1787
1278
  }
@@ -1803,19 +1294,44 @@ function getEffectiveTargetStates(transition, snapshot, event, actorScope) {
1803
1294
  }
1804
1295
  return [...targetSet];
1805
1296
  }
1297
+
1298
+ /**
1299
+ * Narrows a parallel domain to the single child region that contains every
1300
+ * effective target, so that a transition confined to one region does not exit
1301
+ * (and reset) its sibling regions.
1302
+ */
1303
+ function narrowParallelDomain(domain, targetStates) {
1304
+ let narrowed = domain;
1305
+ while (narrowed.type === 'parallel') {
1306
+ const region = getChildren(narrowed).find(child => targetStates.every(target => target === child || isDescendant(target, child)));
1307
+ if (!region) {
1308
+ break;
1309
+ }
1310
+ narrowed = region;
1311
+ }
1312
+ return narrowed;
1313
+ }
1806
1314
  function getTransitionDomain(transition, snapshot, event, actorScope) {
1807
1315
  const targetStates = getEffectiveTargetStates(transition, snapshot, event, actorScope);
1808
1316
  const {
1809
1317
  reenter
1810
- } = getTransitionResult(transition, snapshot, event, actorScope);
1811
- if (!reenter && targetStates.every(target => target === transition.source || isDescendant(target, transition.source))) {
1812
- return transition.source;
1318
+ } = getTransitionResult(transition, snapshot, event, actorScope, {
1319
+ resolveActions: false
1320
+ });
1321
+ if (targetStates.every(target => target === transition.source || isDescendant(target, transition.source))) {
1322
+ // Targets are contained within the source. A reentering transition
1323
+ // exits and reenters the source itself; otherwise the domain narrows
1324
+ // past parallel nodes so sibling regions are left untouched.
1325
+ return reenter ? transition.source : narrowParallelDomain(transition.source, targetStates);
1813
1326
  }
1814
1327
  const [head, ...tail] = targetStates.concat(transition.source);
1815
1328
  // Find the least common ancestor (LCA) of the source and effective targets.
1816
1329
  for (const ancestor of getProperAncestors(head, undefined)) {
1817
1330
  if (tail.every(sn => isDescendant(sn, ancestor))) {
1818
- return ancestor;
1331
+ // A cross-region transition (source in one parallel region, targets in
1332
+ // another) only exits the region containing its targets; the source
1333
+ // region and other sibling regions stay put unless it reenters.
1334
+ return reenter ? ancestor : narrowParallelDomain(ancestor, targetStates);
1819
1335
  }
1820
1336
  }
1821
1337
 
@@ -1823,17 +1339,20 @@ function getTransitionDomain(transition, snapshot, event, actorScope) {
1823
1339
  if (reenter) {
1824
1340
  return;
1825
1341
  }
1826
- return transition.source.machine.root;
1342
+ return narrowParallelDomain(transition.source.machine.root, targetStates);
1827
1343
  }
1828
1344
  function computeExitSet(transitions, stateNodeSet, snapshot, event, actorScope) {
1829
1345
  const statesToExit = new Set();
1830
1346
  for (const transition of transitions) {
1831
1347
  const {
1832
- targets
1833
- } = getTransitionResult(transition, snapshot, event, actorScope);
1348
+ targets,
1349
+ reenter
1350
+ } = getTransitionResult(transition, snapshot, event, actorScope, {
1351
+ resolveActions: false
1352
+ });
1834
1353
  if (targets?.length) {
1835
1354
  const domain = getTransitionDomain(transition, snapshot, event, actorScope);
1836
- if (transition.reenter && transition.source === domain) {
1355
+ if (reenter && transition.source === domain) {
1837
1356
  statesToExit.add(domain);
1838
1357
  }
1839
1358
  for (const stateNode of stateNodeSet) {
@@ -1857,16 +1376,11 @@ function initialMicrostep(root, preInitialState, actorScope, initEvent, internal
1857
1376
 
1858
1377
  /** https://www.w3.org/TR/scxml/#microstepProcedure */
1859
1378
  function microstep(transitions, currentSnapshot, actorScope, event, isInitial, internalQueue) {
1860
- const actions = [];
1379
+ const executableActions = [];
1861
1380
  if (!transitions.length) {
1862
- return [currentSnapshot, actions];
1381
+ return [currentSnapshot, executableActions];
1863
1382
  }
1864
- const originalExecutor = actorScope.actionExecutor;
1865
- actorScope.actionExecutor = action => {
1866
- actions.push(action);
1867
- originalExecutor(action);
1868
- };
1869
- try {
1383
+ {
1870
1384
  const mutStateNodeSet = new Set(currentSnapshot._nodes);
1871
1385
  let historyValue = currentSnapshot.historyValue;
1872
1386
  const originalContext = currentSnapshot.context;
@@ -1887,13 +1401,13 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1887
1401
  children,
1888
1402
  system: actorScope.system,
1889
1403
  actions: currentSnapshot.machine.implementations.actions,
1890
- actors: currentSnapshot.machine.implementations.actors,
1404
+ actorSources: currentSnapshot.machine.implementations.actorSources,
1891
1405
  guards: currentSnapshot.machine.implementations.guards,
1892
1406
  delays: currentSnapshot.machine.implementations.delays,
1893
1407
  input
1894
1408
  }, enqueue);
1895
- if (res?.context) {
1896
- updatedContext = res.context;
1409
+ if (res?.context !== undefined) {
1410
+ updatedContext = mergeContextPatch(context, res.context);
1897
1411
  }
1898
1412
  return [actions, updatedContext, internalEvents];
1899
1413
  }
@@ -1929,7 +1443,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1929
1443
  }
1930
1444
  }
1931
1445
  for (const exitStateNode of statesToExit) {
1932
- const stateInput = currentSnapshot._stateInputs?.[exitStateNode.id];
1446
+ const stateInput = getStateInput(currentSnapshot, exitStateNode.id);
1933
1447
  const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, currentSnapshot.children, stateInput) : [[]];
1934
1448
  if (internalEvents?.length) {
1935
1449
  internalQueue.push(...internalEvents);
@@ -1939,7 +1453,9 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1939
1453
  context: nextContext
1940
1454
  });
1941
1455
  }
1942
- nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, exitActions);
1456
+ const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, exitActions);
1457
+ nextState = resolvedState;
1458
+ executableActions.push(...resolvedActions);
1943
1459
  for (const def of exitStateNode.invoke) {
1944
1460
  const childActor = nextState.children[def.id];
1945
1461
  if (childActor && !childActor._isExternal) {
@@ -1964,8 +1480,8 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1964
1480
  transitionActions.push(...toArray(t.actions));
1965
1481
  }
1966
1482
  const res = getCurrentTransitionResult(t);
1967
- if (res.context) {
1968
- context = res.context;
1483
+ if (res.context !== undefined) {
1484
+ context = mergeContextPatch(context, res.context);
1969
1485
  }
1970
1486
  if (res.actions) {
1971
1487
  transitionActions.push(...res.actions);
@@ -1980,17 +1496,18 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1980
1496
  const enterStates = () => {
1981
1497
  const getMachineOutput = rootCompletionNode => {
1982
1498
  const rootNode = nextState.machine.root;
1983
- if (rootNode.output === undefined) {
1984
- return;
1985
- }
1986
1499
  let completionOutput;
1987
1500
  if (rootCompletionNode.output !== undefined && rootCompletionNode.parent) {
1988
- completionOutput = resolveOutput(rootCompletionNode.output, nextState.context, event, actorScope.self);
1501
+ const rootDoneEvent = internalQueue.find(e => e.type === `xstate.done.state.${rootNode.id}`);
1502
+ completionOutput = rootDoneEvent && rootCompletionNode.parent === rootNode ? rootDoneEvent.output : resolveOutput(rootCompletionNode.output, nextState.context, event, actorScope.self, stateInputMap[rootCompletionNode.id]);
1989
1503
  } else if (rootCompletionNode.type === 'parallel') {
1990
1504
  const parallelDoneType = `xstate.done.state.${rootCompletionNode.id}`;
1991
1505
  const parallelDoneEvent = internalQueue.find(e => e.type === parallelDoneType);
1992
1506
  completionOutput = parallelDoneEvent?.output;
1993
1507
  }
1508
+ if (rootNode.output === undefined) {
1509
+ return rootCompletionNode.parent === rootNode ? completionOutput : undefined;
1510
+ }
1994
1511
  return resolveOutput(rootNode.output, nextState.context, createDoneStateEvent(rootCompletionNode.id, completionOutput), actorScope.self);
1995
1512
  };
1996
1513
  const statesToEnter = new Set();
@@ -2067,7 +1584,9 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2067
1584
  const {
2068
1585
  targets,
2069
1586
  reenter
2070
- } = getCurrentTransitionResult(transition);
1587
+ } = getTransitionResult(transition, currentSnapshot, event, actorScope, {
1588
+ resolveActions: false
1589
+ });
2071
1590
  for (const targetNode of targets ?? []) {
2072
1591
  if (!isHistoryNode(targetNode) && (transition.source !== targetNode || transition.source !== domain || reenter)) {
2073
1592
  statesToEnter.add(targetNode);
@@ -2083,6 +1602,12 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2083
1602
  }
2084
1603
  addAncestorStatesToEnter(ancestors, !transition.source.parent && reenter ? undefined : domain);
2085
1604
  }
1605
+ if (reenter && domain === transition.source) {
1606
+ // A reentering transition whose domain is its own source exits and
1607
+ // reenters the source, so the source must be entered explicitly —
1608
+ // it has no ancestor inside the domain to add it.
1609
+ statesToEnter.add(transition.source);
1610
+ }
2086
1611
  }
2087
1612
  if (isInitial) {
2088
1613
  statesForDefaultEntry.add(currentSnapshot.machine.root);
@@ -2090,14 +1615,18 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2090
1615
  const stateInputMap = {
2091
1616
  ...currentSnapshot._stateInputs
2092
1617
  };
1618
+ let stateInputsChanged = false;
2093
1619
  for (const transition of filteredTransitions) {
2094
1620
  const {
2095
1621
  targets,
2096
1622
  input
2097
- } = getCurrentTransitionResult(transition);
1623
+ } = getTransitionResult(transition, currentSnapshot, event, actorScope, {
1624
+ resolveActions: false
1625
+ });
2098
1626
  if (input && targets) {
2099
1627
  for (const targetNode of targets) {
2100
1628
  stateInputMap[targetNode.id] = input;
1629
+ stateInputsChanged = true;
2101
1630
  }
2102
1631
  }
2103
1632
  }
@@ -2114,7 +1643,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2114
1643
  let src = invokeDef.logic;
2115
1644
  if (typeof src === 'function') {
2116
1645
  src = src({
2117
- actors: currentSnapshot.machine.implementations.actors,
1646
+ actorSources: currentSnapshot.machine.implementations.actorSources,
2118
1647
  context: nextState.context,
2119
1648
  event,
2120
1649
  self: actorScope.self
@@ -2122,12 +1651,13 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2122
1651
  }
2123
1652
  const logic = typeof src === 'string' ? resolveReferencedActor(currentSnapshot.machine, src) : src;
2124
1653
  if (!logic) {
2125
- throw new Error(`Actor logic '${typeof src === 'string' ? src : 'inline'}' not implemented in machine '${currentSnapshot.machine.id}'`);
1654
+ throw new Error(`Actor logic '${typeof src === 'string' ? src : 'inline'}' not implemented`);
2126
1655
  }
2127
1656
  const input = typeof invokeDef.input === 'function' ? invokeDef.input({
2128
1657
  self: actorScope.self,
2129
1658
  context: nextState.context,
2130
- event
1659
+ event,
1660
+ output: getEventOutput(event)
2131
1661
  }) : invokeDef.input;
2132
1662
  const actor = createActor(logic, {
2133
1663
  ...invokeDef,
@@ -2136,7 +1666,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2136
1666
  syncSnapshot: !!invokeDef.onSnapshot
2137
1667
  });
2138
1668
  actions.push({
2139
- action: builtInActions['@xstate.start'],
1669
+ action: builtInActions['@xstate.spawn'],
2140
1670
  args: [actor]
2141
1671
  });
2142
1672
  if (invokeDef.id) {
@@ -2148,18 +1678,23 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2148
1678
  children
2149
1679
  });
2150
1680
  }
2151
- let context;
1681
+ let context = nextState.context;
1682
+ let contextChangedByEntry = false;
2152
1683
  const stateInput = stateInputMap[stateNodeToEnter.id];
2153
1684
  if (stateNodeToEnter.entry) {
2154
- const [resultActions, nextContext, nextInternalEvents] = getStateActionsAndContext(stateNodeToEnter.entry, nextState.context, children, stateInput);
1685
+ const [resultActions, nextContext, nextInternalEvents] = getStateActionsAndContext(stateNodeToEnter.entry, context, children, stateInput);
2155
1686
  actions.push(...resultActions);
2156
1687
  if (nextInternalEvents?.length) {
2157
1688
  internalQueue.push(...nextInternalEvents);
2158
1689
  }
2159
1690
  if (nextContext) {
2160
1691
  context = nextContext;
1692
+ contextChangedByEntry = true;
2161
1693
  }
2162
1694
  }
1695
+ if (contextChangedByEntry) {
1696
+ nextState.context = context;
1697
+ }
2163
1698
  if (statesForDefaultEntry.has(stateNodeToEnter)) {
2164
1699
  const {
2165
1700
  actions: initialActions,
@@ -2171,13 +1706,14 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2171
1706
  if (initialInput && stateNodeToEnter.initial?.target) {
2172
1707
  for (const targetNode of stateNodeToEnter.initial.target) {
2173
1708
  stateInputMap[targetNode.id] = initialInput;
1709
+ stateInputsChanged = true;
2174
1710
  }
2175
1711
  }
2176
1712
  }
2177
- nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, actions);
2178
- if (context) {
2179
- nextState.context = context;
2180
- }
1713
+ const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, actions);
1714
+ nextState = resolvedState;
1715
+ actions.length = 0;
1716
+ executableActions.push(...resolvedActions);
2181
1717
  if (stateNodeToEnter.type !== 'final') {
2182
1718
  continue;
2183
1719
  }
@@ -2185,14 +1721,14 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2185
1721
  let ancestorMarker = parent?.type === 'parallel' ? parent : parent?.parent;
2186
1722
  let rootCompletionNode = ancestorMarker || stateNodeToEnter;
2187
1723
  if (parent?.type === 'compound') {
2188
- internalQueue.push(createDoneStateEvent(parent.id, stateNodeToEnter.output !== undefined ? resolveOutput(stateNodeToEnter.output, nextState.context, event, actorScope.self) : undefined));
1724
+ internalQueue.push(createDoneStateEvent(parent.id, stateNodeToEnter.output !== undefined ? resolveOutput(stateNodeToEnter.output, nextState.context, event, actorScope.self, stateInput) : undefined));
2189
1725
  }
2190
1726
  while (ancestorMarker?.type === 'parallel' && !completedNodes.has(ancestorMarker) && isInFinalState(mutStateNodeSet, ancestorMarker)) {
2191
1727
  completedNodes.add(ancestorMarker);
2192
1728
  const regionOutput = {};
2193
1729
  for (const region of getChildren(ancestorMarker)) {
2194
1730
  if (region.type === 'final') {
2195
- regionOutput[region.key] = region.output !== undefined ? resolveOutput(region.output, nextState.context, event, actorScope.self) : undefined;
1731
+ regionOutput[region.key] = region.output !== undefined ? resolveOutput(region.output, nextState.context, event, actorScope.self, stateInputMap[region.id]) : undefined;
2196
1732
  continue;
2197
1733
  }
2198
1734
  if (region.type === 'parallel') {
@@ -2202,7 +1738,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2202
1738
  continue;
2203
1739
  }
2204
1740
  const finalChild = getChildren(region).find(s => s.type === 'final' && mutStateNodeSet.has(s));
2205
- regionOutput[region.key] = finalChild?.output !== undefined ? resolveOutput(finalChild.output, nextState.context, event, actorScope.self) : undefined;
1741
+ regionOutput[region.key] = finalChild?.output !== undefined ? resolveOutput(finalChild.output, nextState.context, event, actorScope.self, stateInputMap[finalChild.id]) : undefined;
2206
1742
  }
2207
1743
  internalQueue.push(createDoneStateEvent(ancestorMarker.id, regionOutput));
2208
1744
  rootCompletionNode = ancestorMarker;
@@ -2216,8 +1752,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2216
1752
  output: getMachineOutput(rootCompletionNode)
2217
1753
  });
2218
1754
  }
2219
- const inputChanged = JSON.stringify(stateInputMap) !== JSON.stringify(currentSnapshot._stateInputs || {});
2220
- if (inputChanged) {
1755
+ if (stateInputsChanged) {
2221
1756
  nextState = cloneMachineSnapshot(nextState, {
2222
1757
  _stateInputs: stateInputMap
2223
1758
  });
@@ -2225,7 +1760,9 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2225
1760
  };
2226
1761
 
2227
1762
  // Execute transition content
2228
- nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, transitionActions);
1763
+ const [resolvedTransitionState, transitionExecutableActions] = resolveActionsWithContext(nextState, event, actorScope, transitionActions);
1764
+ nextState = resolvedTransitionState;
1765
+ executableActions.push(...transitionExecutableActions);
2229
1766
  if (context && context !== currentSnapshot.context) {
2230
1767
  nextState = cloneMachineSnapshot(nextState, {
2231
1768
  context
@@ -2240,7 +1777,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2240
1777
  const nextStateNodesToExit = nextStateNodes.sort((a, b) => b.order - a.order);
2241
1778
  nextStateNodesToExit.forEach(stateNode => {
2242
1779
  if (stateNode.exit) {
2243
- const stateInput = nextState._stateInputs?.[stateNode.id];
1780
+ const stateInput = getStateInput(nextState, stateNode.id);
2244
1781
  const [exitActions,, nextInternalEvents] = getStateActionsAndContext(stateNode.exit, nextState.context, nextState.children, stateInput);
2245
1782
  allExitActions.push(...exitActions);
2246
1783
  if (nextInternalEvents?.length) {
@@ -2248,22 +1785,22 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2248
1785
  }
2249
1786
  }
2250
1787
  });
2251
- nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, allExitActions);
1788
+ const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, allExitActions);
1789
+ nextState = resolvedState;
1790
+ executableActions.push(...resolvedActions);
2252
1791
  }
2253
1792
  if (historyValue === currentSnapshot.historyValue && currentSnapshot._nodes.length === mutStateNodeSet.size && currentSnapshot._nodes.every(node => mutStateNodeSet.has(node))) {
2254
1793
  // If context was changed (e.g. by entry actions during self-transition),
2255
1794
  // clone to ensure reference inequality for eventless transition re-evaluation
2256
1795
  if (nextState.context !== originalContext) {
2257
- return [cloneMachineSnapshot(nextState), actions];
1796
+ return [cloneMachineSnapshot(nextState), executableActions];
2258
1797
  }
2259
- return [nextState, actions];
1798
+ return [nextState, executableActions];
2260
1799
  }
2261
1800
  return [cloneMachineSnapshot(nextState, {
2262
1801
  _nodes: nextStateNodes,
2263
1802
  historyValue
2264
- }), actions];
2265
- } finally {
2266
- actorScope.actionExecutor = originalExecutor;
1803
+ }), executableActions];
2267
1804
  }
2268
1805
  }
2269
1806
 
@@ -2271,29 +1808,33 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2271
1808
  * Gets the transition result for a given transition without executing the
2272
1809
  * transition.
2273
1810
  */
2274
- function getTransitionResult(transition, snapshot, event, actorScope) {
1811
+ function getTransitionResult(transition, snapshot, event, actorScope, options) {
1812
+ const transitionArgs = {
1813
+ context: snapshot.context,
1814
+ event,
1815
+ output: getEventOutput(event),
1816
+ value: snapshot.value,
1817
+ children: snapshot.children,
1818
+ system: actorScope.system,
1819
+ parent: actorScope.self._parent,
1820
+ self: actorScope.self,
1821
+ actions: snapshot.machine.implementations.actions,
1822
+ actorSources: snapshot.machine.implementations.actorSources,
1823
+ guards: snapshot.machine.implementations.guards,
1824
+ delays: snapshot.machine.implementations.delays,
1825
+ input: getStateInput(snapshot, transition.source.id)
1826
+ };
2275
1827
  if (transition.to) {
2276
1828
  const actions = [];
2277
1829
  const internalEvents = [];
2278
- const enqueue = createTransitionEnqueue(actorScope, actions, internalEvents);
2279
- const res = transition.to({
2280
- context: snapshot.context,
2281
- event,
2282
- value: snapshot.value,
2283
- children: snapshot.children,
2284
- parent: actorScope.self._parent,
2285
- self: actorScope.self,
2286
- actions: snapshot.machine.implementations.actions,
2287
- actors: snapshot.machine.implementations.actors,
2288
- guards: snapshot.machine.implementations.guards,
2289
- delays: snapshot.machine.implementations.delays
2290
- }, enqueue);
1830
+ const enqueue = createTransitionEnqueue(actorScope, actions, internalEvents, false, options?.resolveActions ?? true);
1831
+ const res = transition.to(transitionArgs, enqueue);
2291
1832
  const targets = res?.target ? resolveTarget(transition.source, toArray(res.target)) : undefined;
2292
- // Resolve input for .to transitions
2293
- const resolvedInput = typeof transition.input === 'function' ? transition.input({
1833
+ const resolvedInput = res?.input ?? (typeof transition.input === 'function' ? transition.input({
2294
1834
  context: snapshot.context,
2295
- event
2296
- }) : transition.input;
1835
+ event,
1836
+ output: getEventOutput(event)
1837
+ }) : transition.input);
2297
1838
  return {
2298
1839
  targets: targets,
2299
1840
  context: res?.context,
@@ -2307,11 +1848,13 @@ function getTransitionResult(transition, snapshot, event, actorScope) {
2307
1848
  // Resolve input for regular transitions
2308
1849
  const resolvedInput = typeof transition.input === 'function' ? transition.input({
2309
1850
  context: snapshot.context,
2310
- event
1851
+ event,
1852
+ output: getEventOutput(event)
2311
1853
  }) : transition.input;
1854
+ const resolvedContext = typeof transition.context === 'function' ? transition.context(transitionArgs) : transition.context;
2312
1855
  return {
2313
1856
  targets: transition.target,
2314
- context: undefined,
1857
+ context: resolvedContext,
2315
1858
  reenter: transition.reenter,
2316
1859
  actions: undefined,
2317
1860
  internalEvents: undefined,
@@ -2347,7 +1890,7 @@ function macrostep(snapshot, event, actorScope, internalQueue) {
2347
1890
  // Determine the next state based on the next microstep
2348
1891
  if (nextEvent.type !== XSTATE_INIT) {
2349
1892
  const currentEvent = nextEvent;
2350
- const isErr = isErrorActorEvent(currentEvent);
1893
+ const isErr = isErrorEvent(currentEvent);
2351
1894
  const transitions = nextSnapshot.machine.getTransitionData(nextSnapshot, currentEvent, actorScope.self);
2352
1895
  if (isErr && !transitions.length) {
2353
1896
  // TODO: we should likely only allow transitions selected by very explicit descriptors
@@ -2418,31 +1961,13 @@ function resolveStateValue(rootNode, stateValue) {
2418
1961
  const allStateNodes = getAllStateNodes(getStateNodes(rootNode, stateValue));
2419
1962
  return getStateValue(rootNode, allStateNodes);
2420
1963
  }
2421
- function createEnqueueObject(props, action) {
2422
- const enqueueFn = (fn, ...args) => {
2423
- action(fn, ...args);
2424
- };
2425
- Object.assign(enqueueFn, {
2426
- cancel: () => {},
2427
- emit: () => {},
2428
- log: () => {},
2429
- raise: () => {},
2430
- spawn: () => ({}),
2431
- sendTo: () => {},
2432
- stop: () => {},
2433
- listen: () => ({}),
2434
- subscribeTo: () => ({}),
2435
- ...props
2436
- });
2437
- return enqueueFn;
2438
- }
2439
1964
  function hasEffect(transition, context, event, snapshot, self) {
2440
1965
  if (transition.to) {
2441
- return transitionToHasEffect(transition.to, context, event, snapshot, self, snapshot.machine.implementations);
1966
+ return transitionToHasEffect(transition.to, context, event, snapshot, self, snapshot.machine.implementations, transition.source.id);
2442
1967
  }
2443
1968
  return false;
2444
1969
  }
2445
- function transitionToHasEffect(transitionTo, context, event, snapshot, self, implementations) {
1970
+ function transitionToHasEffect(transitionTo, context, event, snapshot, self, implementations, sourceId) {
2446
1971
  let hasEffect = false;
2447
1972
  let res;
2448
1973
  try {
@@ -2453,16 +1978,19 @@ function transitionToHasEffect(transitionTo, context, event, snapshot, self, imp
2453
1978
  res = transitionTo({
2454
1979
  context,
2455
1980
  event,
1981
+ output: getEventOutput(event),
2456
1982
  self,
1983
+ system: self.system,
2457
1984
  value: snapshot.value,
2458
1985
  children: snapshot.children,
2459
1986
  parent: {
2460
1987
  send: triggerEffect
2461
1988
  },
2462
1989
  actions: implementations.actions,
2463
- actors: implementations.actors,
1990
+ actorSources: implementations.actorSources,
2464
1991
  guards: implementations.guards,
2465
- delays: implementations.delays
1992
+ delays: implementations.delays,
1993
+ input: getStateInput(snapshot, sourceId)
2466
1994
  }, createEnqueueObject({
2467
1995
  emit: triggerEffect,
2468
1996
  cancel: triggerEffect,
@@ -2515,38 +2043,22 @@ function evaluateCandidate(candidate, event, snapshot, stateNode, self) {
2515
2043
  const guardArgs = {
2516
2044
  context: snapshot.context,
2517
2045
  event,
2046
+ output: getEventOutput(event),
2518
2047
  self,
2519
2048
  parent: self._parent,
2520
2049
  children: snapshot.children,
2521
2050
  actions: stateNode.machine.implementations.actions,
2522
- actors: stateNode.machine.implementations.actors,
2051
+ actorSources: stateNode.machine.implementations.actorSources,
2523
2052
  guards: stateNode.machine.implementations.guards,
2524
2053
  delays: stateNode.machine.implementations.delays,
2525
2054
  _snapshot: snapshot
2526
2055
  };
2527
- const guardConfig = candidate.guard;
2528
- const guardParams = typeof guardConfig?.params === 'function' ? guardConfig.params({
2529
- context: snapshot.context,
2530
- event
2531
- }) : guardConfig?.params;
2532
- let guardPassed = true;
2533
- if (typeof guardConfig === 'function') {
2534
- guardPassed = guardConfig(guardArgs, guardParams);
2535
- } else if (typeof guardConfig?.type === 'string') {
2536
- const guardImpl = stateNode.machine.implementations.guards[guardConfig.type];
2537
- if (!guardImpl) {
2538
- // A typo'd guard name must fail loudly — silently passing would
2539
- // take the guarded transition and corrupt machine behavior.
2540
- throw new Error(`Guard '${guardConfig.type}' is not implemented in machine '${stateNode.machine.id}'.`);
2541
- }
2542
- guardPassed = guardImpl(guardArgs, guardParams);
2543
- }
2544
- if (!guardPassed) {
2056
+ if (!candidate.guard(guardArgs)) {
2545
2057
  return false;
2546
2058
  }
2547
2059
  }
2548
2060
  if (candidate.to) {
2549
- return transitionToHasEffect(candidate.to, snapshot.context, event, snapshot, self, stateNode.machine.implementations);
2061
+ return transitionToHasEffect(candidate.to, snapshot.context, event, snapshot, self, stateNode.machine.implementations, candidate.source.id);
2550
2062
  }
2551
2063
  return true;
2552
2064
  }
@@ -2554,29 +2066,6 @@ function evaluateCandidate(candidate, event, snapshot, stateNode, self) {
2554
2066
  function isMachineSnapshot(value) {
2555
2067
  return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
2556
2068
  }
2557
- let emptyCanActor;
2558
- let emptyCanActorScope;
2559
- function getEmptyCanActor() {
2560
- return emptyCanActor ??= createEmptyActor();
2561
- }
2562
- function getEmptyCanActorScope() {
2563
- if (emptyCanActorScope) {
2564
- return emptyCanActorScope;
2565
- }
2566
- const actor = getEmptyCanActor();
2567
- emptyCanActorScope = {
2568
- self: actor,
2569
- logger: () => {},
2570
- id: '',
2571
- sessionId: '',
2572
- defer: () => {},
2573
- system: actor.system,
2574
- stopChild: () => {},
2575
- emit: () => {},
2576
- actionExecutor: () => {}
2577
- };
2578
- return emptyCanActorScope;
2579
- }
2580
2069
  const machineSnapshotMatches = function matches(testValue) {
2581
2070
  return matchesState(testValue, this.value);
2582
2071
  };
@@ -2584,24 +2073,10 @@ const machineSnapshotHasTag = function hasTag(tag) {
2584
2073
  return this.tags.has(tag);
2585
2074
  };
2586
2075
  const machineSnapshotCan = function can(event) {
2587
- const emptyActor = getEmptyCanActor();
2588
- const emptyActorScope = getEmptyCanActorScope();
2589
- const transitionData = this.machine.getTransitionData(this, event, emptyActor);
2590
- if (!transitionData?.length) {
2591
- return false;
2592
- }
2593
2076
 
2594
- // Check that at least one transition is not forbidden
2595
- for (const transition of transitionData) {
2596
- if (transition.target !== undefined) {
2597
- return true;
2598
- }
2599
- const res = getTransitionResult(transition, this, event, emptyActorScope);
2600
- if (res.targets?.length || res.context || hasEffect(transition, this.context, event, this, emptyActor)) {
2601
- return true;
2602
- }
2603
- }
2604
- return false;
2077
+ // The dry-run logic lives on the machine so that non-machine bundles
2078
+ // (e.g. `createFSM`) don't pay for the transition-resolution machinery.
2079
+ return this.machine?._canTransition(this, event) ?? false;
2605
2080
  };
2606
2081
  const machineSnapshotToJSON = function toJSON() {
2607
2082
  const {
@@ -2729,7 +2204,7 @@ function getPersistedSnapshot(snapshot, options) {
2729
2204
  childrenJson[id] = {
2730
2205
  snapshot: child.getPersistedSnapshot(options),
2731
2206
  src: child.src,
2732
- systemId: child.systemId,
2207
+ registryKey: child.registryKey,
2733
2208
  syncSnapshot: child._syncSnapshot
2734
2209
  };
2735
2210
  }
@@ -2739,6 +2214,9 @@ function getPersistedSnapshot(snapshot, options) {
2739
2214
  children: childrenJson,
2740
2215
  historyValue: serializeHistoryValue(jsonValues.historyValue)
2741
2216
  };
2217
+ if (_stateInputs && Object.keys(_stateInputs).length > 0) {
2218
+ persisted.stateInputs = _stateInputs;
2219
+ }
2742
2220
  if (machine.version !== undefined) {
2743
2221
  persisted.version = machine.version;
2744
2222
  }
@@ -2874,21 +2352,35 @@ function toArray(value) {
2874
2352
  }
2875
2353
  return toArrayStrict(value);
2876
2354
  }
2877
- function resolveOutput(mapper, context, event, self) {
2355
+ function resolveOutput(mapper, context, event, self, input) {
2878
2356
  if (typeof mapper === 'function') {
2879
- return mapper({
2357
+ const outputMapper = mapper;
2358
+ const args = {
2880
2359
  context,
2881
2360
  event,
2882
- self
2883
- });
2361
+ output: getEventOutput(event),
2362
+ self,
2363
+ input
2364
+ };
2365
+ return outputMapper(args);
2884
2366
  }
2885
2367
  return mapper;
2886
2368
  }
2369
+ function getEventOutput(event) {
2370
+ if (isDoneEvent(event)) {
2371
+ const doneEvent = event;
2372
+ return doneEvent.output;
2373
+ }
2374
+ return undefined;
2375
+ }
2376
+ function isDoneEvent(event) {
2377
+ return event.type.startsWith('xstate.done.actor.') || event.type.startsWith('xstate.done.state.');
2378
+ }
2887
2379
  function isArray(value) {
2888
2380
  return Array.isArray(value);
2889
2381
  }
2890
- function isErrorActorEvent(event) {
2891
- return event.type.startsWith('xstate.error.actor');
2382
+ function isErrorEvent(event) {
2383
+ return event.type.startsWith('xstate.error.');
2892
2384
  }
2893
2385
  function toTransitionConfigArray(configLike) {
2894
2386
  return toArrayStrict(configLike).map(transitionLike => {
@@ -2926,14 +2418,14 @@ function createInvokeId(stateNodeId, index) {
2926
2418
  function resolveReferencedActor(machine, src) {
2927
2419
  const match = src.match(/^xstate\.invoke\.(\d+)\.(.*)/);
2928
2420
  if (!match) {
2929
- return machine.implementations.actors[src];
2421
+ return machine.implementations.actorSources[src];
2930
2422
  }
2931
2423
  const [, indexStr, nodeId] = match;
2932
2424
  const node = machine.getStateNodeById(nodeId);
2933
2425
  const invokeConfig = node.config.invoke;
2934
2426
  const configSrc = (Array.isArray(invokeConfig) ? invokeConfig[indexStr] : invokeConfig).src;
2935
2427
  // A referenced actor may itself be registered by name.
2936
- return typeof configSrc === 'string' ? machine.implementations.actors[configSrc] : configSrc;
2428
+ return typeof configSrc === 'string' ? machine.implementations.actorSources[configSrc] : configSrc;
2937
2429
  }
2938
2430
  function getAllOwnEventDescriptors(snapshot) {
2939
2431
  return [...new Set([...snapshot._nodes.flatMap(sn => sn.ownEvents)])];
@@ -2980,7 +2472,7 @@ function matchesEventDescriptor(eventType, descriptor) {
2980
2472
  function createScheduledEventId(actor, id) {
2981
2473
  return `${actor.sessionId}.${id}`;
2982
2474
  }
2983
- function createSystem(rootActor, options) {
2475
+ function createRuntimeSystem(rootActor, options) {
2984
2476
  let idCounter = 0;
2985
2477
  const children = new Map();
2986
2478
  const keyedActors = new Map();
@@ -3003,373 +2495,150 @@ function createSystem(rootActor, options) {
3003
2495
  collected.push({
3004
2496
  targetRef: target,
3005
2497
  targetId: target.id,
3006
- event,
3007
- delay,
3008
- id
3009
- });
3010
- };
3011
- const scheduler = {
3012
- schedule: (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
3013
- recordSent(source, target, event, delay, id);
3014
- const scheduledEvent = {
3015
- source,
3016
- target,
3017
- event,
3018
- delay,
3019
- id,
3020
- startedAt: Date.now()
3021
- };
3022
- const scheduledEventId = createScheduledEventId(source, id);
3023
- system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
3024
- const timeout = clock.setTimeout(() => {
3025
- delete timerMap[scheduledEventId];
3026
- delete system._snapshot._scheduledEvents[scheduledEventId];
3027
-
3028
- // The send was already recorded at schedule time on the sender's
3029
- // transition, so deliver without re-recording.
3030
- deliver(source, target, event);
3031
- }, delay);
3032
- timerMap[scheduledEventId] = timeout;
3033
- },
3034
- cancel: (source, id) => {
3035
- const scheduledEventId = createScheduledEventId(source, id);
3036
- const timeout = timerMap[scheduledEventId];
3037
- delete timerMap[scheduledEventId];
3038
- delete system._snapshot._scheduledEvents[scheduledEventId];
3039
- if (timeout !== undefined) {
3040
- clock.clearTimeout(timeout);
3041
- }
3042
- },
3043
- cancelAll: actor => {
3044
- for (const scheduledEventId in system._snapshot._scheduledEvents) {
3045
- const scheduledEvent = system._snapshot._scheduledEvents[scheduledEventId];
3046
- if (scheduledEvent.source === actor) {
3047
- scheduler.cancel(actor, scheduledEvent.id);
3048
- }
3049
- }
3050
- }
3051
- };
3052
- // Delivers an event to the target actor. Used by both `_relay` (which also
3053
- // records the send) and the scheduler's timer (which already recorded it).
3054
- const deliver = (source, target, event) => {
3055
- const targetMachine = target.logic;
3056
- const isInternalEvent = typeof targetMachine?.isInternalEventType === 'function' && targetMachine.isInternalEventType(event.type);
3057
- if (isInternalEvent && source !== target) {
3058
- throw new Error(`Internal event "${event.type}" cannot be sent to actor "${target.id}" from outside.`);
3059
- }
3060
-
3061
- // remember the last source for unified transition inspect event
3062
- target._lastSourceRef = source;
3063
- target._send(event);
3064
- };
3065
- const sendInspectionEvent = event => {
3066
- if (!inspectionObservers.size) {
3067
- return;
3068
- }
3069
- const resolvedInspectionEvent = {
3070
- ...event,
3071
- rootId: rootActor.sessionId
3072
- };
3073
- inspectionObservers.forEach(observer => observer.next?.(resolvedInspectionEvent));
3074
- };
3075
- const system = {
3076
- children,
3077
- reverseKeyedActors,
3078
- keyedActors,
3079
- _snapshot: {
3080
- _scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
3081
- },
3082
- _bookId: () => `x:${idCounter++}`,
3083
- _register: (sessionId, actor) => {
3084
- children.set(sessionId, actor);
3085
- return sessionId;
3086
- },
3087
- _unregister: actor => {
3088
- children.delete(actor.sessionId);
3089
- const systemId = reverseKeyedActors.get(actor);
3090
- if (systemId !== undefined) {
3091
- keyedActors.delete(systemId);
3092
- reverseKeyedActors.delete(actor);
3093
- }
3094
- },
3095
- get: systemId => {
3096
- return keyedActors.get(systemId);
3097
- },
3098
- getAll: () => {
3099
- return Object.fromEntries(keyedActors.entries());
3100
- },
3101
- _set: (systemId, actor) => {
3102
- const existing = keyedActors.get(systemId);
3103
- if (existing && existing !== actor) {
3104
- throw new Error(`Actor with system ID '${systemId}' already exists.`);
3105
- }
3106
- keyedActors.set(systemId, actor);
3107
- reverseKeyedActors.set(actor, systemId);
3108
- },
3109
- inspect: observerOrFn => {
3110
- const observer = toObserver(observerOrFn);
3111
- inspectionObservers.add(observer);
3112
- return {
3113
- unsubscribe() {
3114
- inspectionObservers.delete(observer);
3115
- }
3116
- };
3117
- },
3118
- _sendInspectionEvent: sendInspectionEvent,
3119
- _relay: (source, target, event) => {
3120
- recordSent(source, target, event);
3121
- deliver(source, target, event);
3122
- },
3123
- scheduler,
3124
- getSnapshot: () => {
3125
- return {
3126
- _scheduledEvents: {
3127
- ...system._snapshot._scheduledEvents
3128
- }
3129
- };
3130
- },
3131
- start: () => {
3132
- const scheduledEvents = system._snapshot._scheduledEvents;
3133
- system._snapshot._scheduledEvents = {};
3134
- for (const scheduledId in scheduledEvents) {
3135
- const {
3136
- source,
3137
- target,
3138
- event,
3139
- delay,
3140
- id
3141
- } = scheduledEvents[scheduledId];
3142
- scheduler.schedule(source, target, event, delay, id);
3143
- }
3144
- },
3145
- _clock: clock,
3146
- _logger: logger,
3147
- _timerStrategy: options.timers
3148
- };
3149
- return system;
3150
- }
3151
-
3152
- const effectStates = new WeakMap();
3153
- const XSTATE_LOGIC_EFFECT_RESOLVE = 'xstate.logic.effect.resolve';
3154
- const XSTATE_LOGIC_EFFECT_REJECT = 'xstate.logic.effect.reject';
3155
- const XSTATE_LOGIC_EFFECT_START = 'xstate.logic.effect.start';
3156
- function getEffectState(self) {
3157
- let state = effectStates.get(self);
3158
- if (!state) {
3159
- state = new Map();
3160
- effectStates.set(self, state);
3161
- }
3162
- return state;
3163
- }
3164
- function executeLogicEffects(effects, actorScope) {
3165
- if (!effects?.length) {
3166
- return;
3167
- }
3168
- for (const effect of effects) {
3169
- switch (effect.type) {
3170
- case 'emit':
3171
- actorScope.emit(effect.event);
3172
- break;
3173
- case 'sendBack':
3174
- {
3175
- const parent = actorScope.self._parent;
3176
- if (parent) {
3177
- actorScope.system._relay(actorScope.self, parent, effect.event);
3178
- }
3179
- break;
3180
- }
3181
- case 'raise':
3182
- actorScope.system._relay(actorScope.self, actorScope.self, effect.event);
3183
- break;
3184
- case 'effect':
3185
- {
3186
- if (!effect.key) {
3187
- const cleanup = effect.exec();
3188
- if (typeof cleanup === 'function') {
3189
- getEffectState(actorScope.self).set(Symbol(), {
3190
- cleanup
3191
- });
3192
- }
3193
- break;
3194
- }
3195
- const state = getEffectState(actorScope.self);
3196
- if (state.has(effect.key)) {
3197
- break;
3198
- }
3199
- const cleanup = effect.exec();
3200
- state.set(effect.key, {
3201
- cleanup: typeof cleanup === 'function' ? cleanup : undefined
3202
- });
3203
- break;
3204
- }
3205
- case 'cleanupEffects':
3206
- {
3207
- const state = effectStates.get(actorScope.self);
3208
- if (!state) {
3209
- break;
3210
- }
3211
- for (const {
3212
- cleanup
3213
- } of state.values()) {
3214
- cleanup?.();
3215
- }
3216
- effectStates.delete(actorScope.self);
3217
- break;
3218
- }
3219
- }
3220
- }
3221
- }
3222
- function resolveContext(context, input) {
3223
- return typeof context === 'function' ? context({
3224
- input
3225
- }) : context;
3226
- }
3227
- function createLogic(config) {
3228
- const transition = (snapshot, event, actorScope) => {
3229
- if (snapshot.status !== 'active') {
3230
- return [snapshot, []];
3231
- }
3232
- if (event.type === XSTATE_STOP) {
3233
- return [{
3234
- ...snapshot,
3235
- status: 'stopped',
3236
- input: undefined
3237
- }, [{
3238
- type: 'cleanupEffects'
3239
- }]];
3240
- }
3241
- if (event.type === XSTATE_LOGIC_EFFECT_START) {
3242
- return [{
3243
- ...snapshot,
3244
- effects: {
3245
- ...snapshot.effects,
3246
- [event.key]: {
3247
- status: 'active'
3248
- }
3249
- }
3250
- }, []];
3251
- }
3252
- if (event.type === XSTATE_LOGIC_EFFECT_RESOLVE) {
3253
- return [{
3254
- ...snapshot,
3255
- effects: {
3256
- ...snapshot.effects,
3257
- [event.key]: {
3258
- status: 'done',
3259
- output: event.output
3260
- }
3261
- }
3262
- }, []];
3263
- }
3264
- if (event.type === XSTATE_LOGIC_EFFECT_REJECT) {
3265
- return [{
3266
- ...snapshot,
3267
- effects: {
3268
- ...snapshot.effects,
3269
- [event.key]: {
3270
- status: 'error',
3271
- error: event.error
3272
- }
3273
- }
3274
- }, []];
3275
- }
3276
- const effects = [];
3277
- const trackedEffects = {};
3278
- const enqueueEffect = (key, exec) => {
3279
- if (snapshot.effects?.[key]) {
3280
- return;
3281
- }
3282
- effects.push({
3283
- type: 'effect',
3284
- key,
3285
- exec
3286
- });
3287
- trackedEffects[key] = {
3288
- status: 'active'
2498
+ event,
2499
+ delay,
2500
+ id
2501
+ });
2502
+ };
2503
+ const scheduler = {
2504
+ schedule: (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
2505
+ recordSent(source, target, event, delay, id);
2506
+ const scheduledEvent = {
2507
+ source,
2508
+ target,
2509
+ event,
2510
+ delay,
2511
+ id,
2512
+ startedAt: Date.now()
3289
2513
  };
3290
- };
3291
- const enq = {
3292
- emit: emitted => {
3293
- effects.push({
3294
- type: 'emit',
3295
- event: emitted
3296
- });
3297
- },
3298
- sendBack: sentEvent => {
3299
- effects.push({
3300
- type: 'sendBack',
3301
- event: sentEvent
3302
- });
3303
- },
3304
- raise: raisedEvent => {
3305
- effects.push({
3306
- type: 'raise',
3307
- event: raisedEvent
3308
- });
3309
- },
3310
- effect: (keyOrExec, maybeExec) => {
3311
- if (typeof keyOrExec === 'string') {
3312
- enqueueEffect(keyOrExec, maybeExec);
3313
- return;
2514
+ const scheduledEventId = createScheduledEventId(source, id);
2515
+ system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
2516
+ const timeout = clock.setTimeout(() => {
2517
+ delete timerMap[scheduledEventId];
2518
+ delete system._snapshot._scheduledEvents[scheduledEventId];
2519
+
2520
+ // The send was already recorded at schedule time on the sender's
2521
+ // transition, so deliver without re-recording.
2522
+ deliver(source, target, event);
2523
+ }, delay);
2524
+ timerMap[scheduledEventId] = timeout;
2525
+ },
2526
+ cancel: (source, id) => {
2527
+ const scheduledEventId = createScheduledEventId(source, id);
2528
+ const timeout = timerMap[scheduledEventId];
2529
+ delete timerMap[scheduledEventId];
2530
+ delete system._snapshot._scheduledEvents[scheduledEventId];
2531
+ if (timeout !== undefined) {
2532
+ clock.clearTimeout(timeout);
2533
+ }
2534
+ },
2535
+ cancelAll: actor => {
2536
+ for (const scheduledEventId in system._snapshot._scheduledEvents) {
2537
+ const scheduledEvent = system._snapshot._scheduledEvents[scheduledEventId];
2538
+ if (scheduledEvent.source === actor) {
2539
+ scheduler.cancel(actor, scheduledEvent.id);
3314
2540
  }
3315
- effects.push({
3316
- type: 'effect',
3317
- exec: keyOrExec
3318
- });
3319
2541
  }
3320
- };
3321
- const patch = config.run({
3322
- context: snapshot.context,
3323
- event,
3324
- input: snapshot.input,
3325
- system: actorScope.system,
3326
- self: actorScope.self,
3327
- emit: actorScope.emit
3328
- }, enq);
3329
- const nextSnapshot = {
3330
- ...snapshot,
3331
- ...(patch || {})
3332
- };
3333
- if ('context' in snapshot || patch?.context !== undefined) {
3334
- nextSnapshot.context = patch?.context ?? snapshot.context;
3335
2542
  }
3336
- if (patch?.effects || Object.keys(trackedEffects).length) {
3337
- nextSnapshot.effects = {
3338
- ...snapshot.effects,
3339
- ...patch?.effects,
3340
- ...trackedEffects
3341
- };
2543
+ };
2544
+ // Delivers an event to the target actor. Used by both `_relay` (which also
2545
+ // records the send) and the scheduler's timer (which already recorded it).
2546
+ const deliver = (source, target, event) => {
2547
+ const targetMachine = target.logic;
2548
+ const isInternalEvent = typeof targetMachine?.isInternalEventType === 'function' && targetMachine.isInternalEventType(event.type);
2549
+ if (isInternalEvent && source !== target) {
2550
+ throw new Error(`Internal event "${event.type}" cannot be sent to actor "${target.id}" from outside.`);
3342
2551
  }
3343
- return [nextSnapshot, effects];
2552
+
2553
+ // remember the last source for unified transition inspect event
2554
+ target._lastSourceRef = source;
2555
+ target._send(event);
3344
2556
  };
3345
- const logic = {
3346
- id: config.id,
3347
- config,
3348
- transition,
3349
- start: (snapshot, actorScope) => {
3350
- const [nextSnapshot, effects] = transition(snapshot, createInitEvent(snapshot.input), actorScope);
3351
- Object.assign(snapshot, nextSnapshot);
3352
- executeLogicEffects(effects, actorScope);
2557
+ const sendInspectionEvent = event => {
2558
+ if (!inspectionObservers.size) {
2559
+ return;
2560
+ }
2561
+ const resolvedInspectionEvent = {
2562
+ ...event,
2563
+ rootId: rootActor.sessionId
2564
+ };
2565
+ inspectionObservers.forEach(observer => observer.next?.(resolvedInspectionEvent));
2566
+ };
2567
+ const system = {
2568
+ children,
2569
+ reverseKeyedActors,
2570
+ keyedActors,
2571
+ _snapshot: {
2572
+ _scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
3353
2573
  },
3354
- getInitialSnapshot: (_, input) => {
3355
- const context = resolveContext(config.context, input);
3356
- const snapshot = {
3357
- status: 'active',
3358
- output: undefined,
3359
- error: undefined,
3360
- input
3361
- };
3362
- if (context !== undefined) {
3363
- snapshot.context = context;
2574
+ _bookId: () => `x:${idCounter++}`,
2575
+ _register: (sessionId, actor) => {
2576
+ children.set(sessionId, actor);
2577
+ return sessionId;
2578
+ },
2579
+ _unregister: actor => {
2580
+ children.delete(actor.sessionId);
2581
+ const registryKey = reverseKeyedActors.get(actor);
2582
+ if (registryKey !== undefined) {
2583
+ keyedActors.delete(registryKey);
2584
+ reverseKeyedActors.delete(actor);
3364
2585
  }
2586
+ },
2587
+ get: registryKey => {
2588
+ return keyedActors.get(registryKey);
2589
+ },
2590
+ getAll: () => {
2591
+ return Object.fromEntries(keyedActors.entries());
2592
+ },
2593
+ _set: (registryKey, actor) => {
2594
+ const existing = keyedActors.get(registryKey);
2595
+ if (existing && existing !== actor) {
2596
+ throw new Error(`Actor with registry key '${registryKey}' already exists.`);
2597
+ }
2598
+ keyedActors.set(registryKey, actor);
2599
+ reverseKeyedActors.set(actor, registryKey);
2600
+ },
2601
+ inspect: observerOrFn => {
2602
+ const observer = toObserver(observerOrFn);
2603
+ inspectionObservers.add(observer);
3365
2604
  return {
3366
- ...snapshot
2605
+ unsubscribe() {
2606
+ inspectionObservers.delete(observer);
2607
+ }
3367
2608
  };
3368
2609
  },
3369
- getPersistedSnapshot: snapshot => snapshot,
3370
- restoreSnapshot: snapshot => snapshot
2610
+ _sendInspectionEvent: sendInspectionEvent,
2611
+ _relay: (source, target, event) => {
2612
+ recordSent(source, target, event);
2613
+ deliver(source, target, event);
2614
+ },
2615
+ scheduler,
2616
+ getSnapshot: () => {
2617
+ return {
2618
+ _scheduledEvents: {
2619
+ ...system._snapshot._scheduledEvents
2620
+ }
2621
+ };
2622
+ },
2623
+ start: () => {
2624
+ const scheduledEvents = system._snapshot._scheduledEvents;
2625
+ system._snapshot._scheduledEvents = {};
2626
+ for (const scheduledId in scheduledEvents) {
2627
+ const {
2628
+ source,
2629
+ target,
2630
+ event,
2631
+ delay,
2632
+ id
2633
+ } = scheduledEvents[scheduledId];
2634
+ scheduler.schedule(source, target, event, delay, id);
2635
+ }
2636
+ },
2637
+ _clock: clock,
2638
+ _logger: logger,
2639
+ _timerStrategy: options.timers
3371
2640
  };
3372
- return logic;
2641
+ return system;
3373
2642
  }
3374
2643
 
3375
2644
  // those are needed to make JSDoc `@link` work properly
@@ -3395,6 +2664,30 @@ const defaultOptions = {
3395
2664
  },
3396
2665
  logger: console.log.bind(console)
3397
2666
  };
2667
+ function safeCall(fn, arg) {
2668
+ try {
2669
+ fn?.(arg);
2670
+ } catch (err) {
2671
+ reportUnhandledError(err);
2672
+ }
2673
+ }
2674
+ function isExecutableActionObject(effect) {
2675
+ return typeof effect === 'object' && effect !== null && 'args' in effect && 'exec' in effect;
2676
+ }
2677
+ function executeExecutableEffects(effects, actorScope) {
2678
+ if (!effects?.length) {
2679
+ return [];
2680
+ }
2681
+ const logicEffects = [];
2682
+ for (const effect of effects) {
2683
+ if (isExecutableActionObject(effect)) {
2684
+ actorScope.actionExecutor(effect);
2685
+ } else {
2686
+ logicEffects.push(effect);
2687
+ }
2688
+ }
2689
+ return logicEffects;
2690
+ }
3398
2691
 
3399
2692
  /**
3400
2693
  * An Actor is a running process that can receive events, send events and change
@@ -3433,6 +2726,7 @@ class Actor {
3433
2726
  this.logger = void 0;
3434
2727
  /** @internal */
3435
2728
  this._processingStatus = ProcessingStatus.NotStarted;
2729
+ this._forceDeferredActions = false;
3436
2730
  // Actor Ref
3437
2731
  this._parent = void 0;
3438
2732
  /** @internal */
@@ -3448,7 +2742,8 @@ class Actor {
3448
2742
  this._collectedActions = [];
3449
2743
  /** @internal Events relayed to other actors during the in-flight transition. */
3450
2744
  this._collectedSent = [];
3451
- this.systemId = void 0;
2745
+ this._initialEffects = void 0;
2746
+ this.registryKey = void 0;
3452
2747
  /** The globally unique process ID for this invocation. */
3453
2748
  this.sessionId = void 0;
3454
2749
  /** The system to which this actor belongs. */
@@ -3469,14 +2764,17 @@ class Actor {
3469
2764
  parent,
3470
2765
  syncSnapshot,
3471
2766
  id,
3472
- systemId,
2767
+ registryKey,
3473
2768
  inspect
3474
2769
  } = resolvedOptions;
3475
- this.system = parent ? parent.system : createSystem(this, {
2770
+ this.system = parent ? parent.system : resolvedOptions._systemRef?.current ?? createRuntimeSystem(this, {
3476
2771
  clock,
3477
2772
  logger,
3478
2773
  timers: resolvedOptions.timers
3479
2774
  });
2775
+ if (!parent && resolvedOptions._systemRef && !resolvedOptions._systemRef.current) {
2776
+ resolvedOptions._systemRef.current = this.system;
2777
+ }
3480
2778
  if (inspect && !parent) {
3481
2779
  // Always inspect at the system-level
3482
2780
  this.system.inspect(toObserver(inspect));
@@ -3501,35 +2799,18 @@ class Actor {
3501
2799
  system: this.system,
3502
2800
  stopChild: child => {
3503
2801
  if (child._parent !== this) {
3504
- throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
2802
+ throw new Error(`Cannot stop non-child actor ${child.id}`);
3505
2803
  }
3506
2804
  child._stop();
3507
2805
  },
3508
2806
  emit: emittedEvent => {
3509
- const listeners = this.eventListeners.get(emittedEvent.type);
3510
- const wildcardListener = this.eventListeners.get('*');
3511
- if (!listeners && !wildcardListener) {
3512
- return;
3513
- }
3514
- if (listeners) {
3515
- for (const handler of listeners) {
3516
- try {
3517
- handler(emittedEvent);
3518
- } catch (err) {
3519
- reportUnhandledError(err);
2807
+ for (const listeners of [this.eventListeners.get(emittedEvent.type), this.eventListeners.get('*')]) {
2808
+ if (listeners) {
2809
+ for (const handler of listeners) {
2810
+ safeCall(handler, emittedEvent);
3520
2811
  }
3521
2812
  }
3522
2813
  }
3523
- if (!wildcardListener) {
3524
- return;
3525
- }
3526
- for (const handler of wildcardListener) {
3527
- try {
3528
- handler(emittedEvent);
3529
- } catch (err) {
3530
- reportUnhandledError(err);
3531
- }
3532
- }
3533
2814
  },
3534
2815
  actionExecutor: action => {
3535
2816
  const exec = () => {
@@ -3550,7 +2831,7 @@ class Actor {
3550
2831
  executingCustomAction = saveExecutingCustomAction;
3551
2832
  }
3552
2833
  };
3553
- if (this._processingStatus === ProcessingStatus.Running) {
2834
+ if (this._processingStatus === ProcessingStatus.Running && !this._forceDeferredActions) {
3554
2835
  exec();
3555
2836
  } else {
3556
2837
  this._deferred.push(exec);
@@ -3571,15 +2852,12 @@ class Actor {
3571
2852
  };
3572
2853
  }
3573
2854
  });
3574
-
3575
- // unified '@xstate.transition' event replaces '@xstate.actor'
3576
-
3577
- if (systemId) {
3578
- this.systemId = systemId;
3579
- this.system._set(systemId, this);
2855
+ if (registryKey) {
2856
+ this.registryKey = registryKey;
2857
+ this.system._set(registryKey, this);
3580
2858
  }
3581
2859
 
3582
- // prepare to collect initial microsteps during getInitialSnapshot
2860
+ // prepare to collect initial microsteps during initialTransition
3583
2861
  this._collectedMicrosteps = [];
3584
2862
  let persistedState = options?.snapshot ?? options?.state;
3585
2863
  if (persistedState && typeof persistedState === 'object' && '_pendingEffects' in persistedState) {
@@ -3593,7 +2871,17 @@ class Actor {
3593
2871
  persistedState = rest;
3594
2872
  }
3595
2873
  try {
3596
- this._snapshot = persistedState ? this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState : this.logic.getInitialSnapshot(this._actorScope, this.options?.input);
2874
+ if (persistedState) {
2875
+ this._snapshot = this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState;
2876
+ } else if (options?._inert) {
2877
+ // Inert actors (createInertActorScope) only anchor a scope for pure
2878
+ // transition functions; computing an initial snapshot here would run
2879
+ // init-time side effects (context factories, entry) a second time.
2880
+ } else {
2881
+ const [snapshot, effects] = this.logic.initialTransition(this.options?.input, this._actorScope);
2882
+ this._snapshot = snapshot;
2883
+ this._initialEffects = effects;
2884
+ }
3597
2885
  } catch (err) {
3598
2886
  // if we get here then it means that we assign a value to this._snapshot that is not of the correct type
3599
2887
  // we can't get the true `TSnapshot & { status: 'error'; }`, it's impossible
@@ -3607,7 +2895,7 @@ class Actor {
3607
2895
  // computation so they can't run against an inconsistent actor
3608
2896
  this._deferred.length = 0;
3609
2897
  }
3610
- if (systemId && this._snapshot.status !== 'active') {
2898
+ if (registryKey && this._snapshot.status !== 'active') {
3611
2899
  this.system._unregister(this);
3612
2900
  }
3613
2901
 
@@ -3630,13 +2918,35 @@ class Actor {
3630
2918
  error: err
3631
2919
  };
3632
2920
  }
2921
+
2922
+ /** Recover via the logic's error event if possible; otherwise error out. */
2923
+ _recoverOrError(err, snapshot) {
2924
+ if (!this._tryHandleExecutionError(err, snapshot)) {
2925
+ this._setErrorSnapshot(err);
2926
+ this._error(err);
2927
+ }
2928
+ }
2929
+ _tryHandleExecutionError(err, snapshot = this._snapshot) {
2930
+ // Machine logic can recover from execution errors via `onError`
2931
+ // transitions; the logic decides (so the actor stays logic-agnostic).
2932
+ const errorEvent = this.logic.getExecutionErrorEvent?.(snapshot, err);
2933
+ if (!errorEvent) {
2934
+ return false;
2935
+ }
2936
+ try {
2937
+ const [nextSnapshot, effects] = this.logic.transition(snapshot, errorEvent, this._actorScope);
2938
+ this._snapshot = nextSnapshot;
2939
+ const logicEffects = executeExecutableEffects(effects, this._actorScope);
2940
+ this.update(nextSnapshot, errorEvent);
2941
+ this.logic.executeEffects?.(logicEffects, this._actorScope);
2942
+ return true;
2943
+ } catch {
2944
+ return false;
2945
+ }
2946
+ }
3633
2947
  _next(snapshot) {
3634
2948
  for (const observer of this.observers) {
3635
- try {
3636
- observer.next?.(snapshot);
3637
- } catch (err) {
3638
- reportUnhandledError(err);
3639
- }
2949
+ safeCall(observer.next, snapshot);
3640
2950
  }
3641
2951
  }
3642
2952
  update(snapshot, event) {
@@ -3655,6 +2965,9 @@ class Actor {
3655
2965
  // no "builtin deferred" should actually throw an error since they are either safe
3656
2966
  // or the control flow is passed through the mailbox and errors should be caught by the `_process` used by the mailbox
3657
2967
  this._deferred.length = 0;
2968
+ if (this._tryHandleExecutionError(err, snapshot)) {
2969
+ return;
2970
+ }
3658
2971
  this._setErrorSnapshot(err, snapshot);
3659
2972
  break;
3660
2973
  }
@@ -3702,6 +3015,25 @@ class Actor {
3702
3015
  this._collectedActions = [];
3703
3016
  this._collectedSent = [];
3704
3017
  }
3018
+ _flushInitialEffects() {
3019
+ if (!this._initialEffects) {
3020
+ return true;
3021
+ }
3022
+ this._forceDeferredActions = true;
3023
+ try {
3024
+ const logicEffects = executeExecutableEffects(this._initialEffects, this._actorScope);
3025
+ this.logic.executeEffects?.(logicEffects, this._actorScope);
3026
+ this._initialEffects = undefined;
3027
+ return true;
3028
+ } catch (err) {
3029
+ this._initialEffects = undefined;
3030
+ this._deferred.length = 0;
3031
+ this._recoverOrError(err);
3032
+ return this._snapshot.status === 'active';
3033
+ } finally {
3034
+ this._forceDeferredActions = false;
3035
+ }
3036
+ }
3705
3037
 
3706
3038
  /**
3707
3039
  * Subscribe an observer to an actor’s snapshot values.
@@ -3770,11 +3102,7 @@ class Actor {
3770
3102
  } else {
3771
3103
  switch (this._snapshot.status) {
3772
3104
  case 'done':
3773
- try {
3774
- observer.complete?.();
3775
- } catch (err) {
3776
- reportUnhandledError(err);
3777
- }
3105
+ safeCall(observer.complete);
3778
3106
  break;
3779
3107
  case 'error':
3780
3108
  {
@@ -3782,11 +3110,7 @@ class Actor {
3782
3110
  if (!observer.error) {
3783
3111
  reportUnhandledError(err);
3784
3112
  } else {
3785
- try {
3786
- observer.error(err);
3787
- } catch (err) {
3788
- reportUnhandledError(err);
3789
- }
3113
+ safeCall(observer.error, err);
3790
3114
  }
3791
3115
  break;
3792
3116
  }
@@ -3828,7 +3152,7 @@ class Actor {
3828
3152
  complete: observer.complete
3829
3153
  });
3830
3154
  },
3831
- get: () => selector(this.get())
3155
+ get: () => selector(this.getSnapshot())
3832
3156
  };
3833
3157
  }
3834
3158
 
@@ -3838,6 +3162,9 @@ class Actor {
3838
3162
  // Do not restart the service if it is already started
3839
3163
  return this;
3840
3164
  }
3165
+ if (this._processingStatus === ProcessingStatus.Stopped) {
3166
+ return this;
3167
+ }
3841
3168
  if (this._syncSnapshot) {
3842
3169
  this.subscribe({
3843
3170
  next: snapshot => {
@@ -3852,8 +3179,8 @@ class Actor {
3852
3179
  });
3853
3180
  }
3854
3181
  this.system._register(this.sessionId, this);
3855
- if (this.systemId) {
3856
- this.system._set(this.systemId, this);
3182
+ if (this.registryKey) {
3183
+ this.system._set(this.registryKey, this);
3857
3184
  }
3858
3185
  this._processingStatus = ProcessingStatus.Running;
3859
3186
 
@@ -3866,6 +3193,9 @@ class Actor {
3866
3193
  case 'done':
3867
3194
  // a state machine can be "done" upon initialization (it could reach a final state using initial microsteps)
3868
3195
  // we still need to complete observers, flush deferreds etc
3196
+ if (!this._flushInitialEffects()) {
3197
+ return this;
3198
+ }
3869
3199
  this.update(this._snapshot, initEvent);
3870
3200
  // TODO: rethink cleanup of observers, mailbox, etc
3871
3201
  return this;
@@ -3885,6 +3215,9 @@ class Actor {
3885
3215
  return this;
3886
3216
  }
3887
3217
  }
3218
+ if (!this._flushInitialEffects()) {
3219
+ return this;
3220
+ }
3888
3221
 
3889
3222
  // TODO: this notifies all subscribers but usually this is redundant
3890
3223
  // there is no real change happening here
@@ -3916,16 +3249,21 @@ class Actor {
3916
3249
  };
3917
3250
  }
3918
3251
  if (caughtError) {
3919
- const {
3920
- err
3921
- } = caughtError;
3922
- this._setErrorSnapshot(err);
3923
- this._error(err);
3252
+ this._recoverOrError(caughtError.err);
3253
+ return;
3254
+ }
3255
+ let snapshot = this._snapshot;
3256
+ try {
3257
+ const [nextSnapshot, effects] = nextState;
3258
+ snapshot = nextSnapshot;
3259
+ this._snapshot = snapshot;
3260
+ const logicEffects = executeExecutableEffects(effects, this._actorScope);
3261
+ this.update(snapshot, event);
3262
+ this.logic.executeEffects?.(logicEffects, this._actorScope);
3263
+ } catch (err) {
3264
+ this._recoverOrError(err, snapshot);
3924
3265
  return;
3925
3266
  }
3926
- const [snapshot, effects] = Array.isArray(nextState) ? nextState : [nextState, undefined];
3927
- this.update(snapshot, event);
3928
- executeLogicEffects(effects, this._actorScope);
3929
3267
  if (event.type === XSTATE_STOP) {
3930
3268
  this._stopProcedure();
3931
3269
  this._complete();
@@ -3958,11 +3296,7 @@ class Actor {
3958
3296
  }
3959
3297
  _complete() {
3960
3298
  for (const observer of this.observers) {
3961
- try {
3962
- observer.complete?.();
3963
- } catch (err) {
3964
- reportUnhandledError(err);
3965
- }
3299
+ safeCall(observer.complete);
3966
3300
  }
3967
3301
  this.observers.clear();
3968
3302
  this.eventListeners.clear();
@@ -3973,24 +3307,19 @@ class Actor {
3973
3307
  if (!this._parent) {
3974
3308
  reportUnhandledError(err);
3975
3309
  }
3976
- this.eventListeners.clear();
3977
3310
  } else {
3978
3311
  let reportError = false;
3979
3312
  for (const observer of this.observers) {
3980
3313
  const errorListener = observer.error;
3981
3314
  reportError ||= !errorListener;
3982
- try {
3983
- errorListener?.(err);
3984
- } catch (err2) {
3985
- reportUnhandledError(err2);
3986
- }
3315
+ safeCall(errorListener, err);
3987
3316
  }
3988
3317
  this.observers.clear();
3989
- this.eventListeners.clear();
3990
3318
  if (reportError) {
3991
3319
  reportUnhandledError(err);
3992
3320
  }
3993
3321
  }
3322
+ this.eventListeners.clear();
3994
3323
  if (this._parent) {
3995
3324
  this.system._relay(this, this._parent, createErrorActorEvent(this.id, err));
3996
3325
  }
@@ -4107,12 +3436,6 @@ class Actor {
4107
3436
  * @see {@link Actor.getPersistedSnapshot} to persist the internal state of an actor (which is more than just a snapshot).
4108
3437
  */
4109
3438
  getSnapshot() {
4110
- return this.get();
4111
- }
4112
-
4113
- /** Read the actor's current snapshot as an atom value. */
4114
- get() {
4115
- onActorRead?.(this);
4116
3439
  return this._snapshot;
4117
3440
  }
4118
3441
  }
@@ -4173,6 +3496,231 @@ function createActor(logic, options) {
4173
3496
  return new Actor(logic, options);
4174
3497
  }
4175
3498
 
3499
+ const effectStates = new WeakMap();
3500
+ const XSTATE_LOGIC_EFFECT_RESOLVE = 'xstate.logic.effect.resolve';
3501
+ const XSTATE_LOGIC_EFFECT_REJECT = 'xstate.logic.effect.reject';
3502
+ const XSTATE_LOGIC_EFFECT_START = 'xstate.logic.effect.start';
3503
+ function getEffectState(self) {
3504
+ let state = effectStates.get(self);
3505
+ if (!state) {
3506
+ state = new Map();
3507
+ effectStates.set(self, state);
3508
+ }
3509
+ return state;
3510
+ }
3511
+ function executeLogicEffects(effects, actorScope) {
3512
+ if (!effects?.length) {
3513
+ return;
3514
+ }
3515
+ for (const effect of effects) {
3516
+ switch (effect.type) {
3517
+ case 'emit':
3518
+ actorScope.emit(effect.event);
3519
+ break;
3520
+ case 'sendBack':
3521
+ {
3522
+ const parent = actorScope.self._parent;
3523
+ if (parent) {
3524
+ actorScope.system._relay(actorScope.self, parent, effect.event);
3525
+ }
3526
+ break;
3527
+ }
3528
+ case 'raise':
3529
+ actorScope.system._relay(actorScope.self, actorScope.self, effect.event);
3530
+ break;
3531
+ case 'effect':
3532
+ {
3533
+ if (!effect.key) {
3534
+ const cleanup = effect.exec();
3535
+ if (typeof cleanup === 'function') {
3536
+ getEffectState(actorScope.self).set(Symbol(), {
3537
+ cleanup
3538
+ });
3539
+ }
3540
+ break;
3541
+ }
3542
+ const state = getEffectState(actorScope.self);
3543
+ if (state.has(effect.key)) {
3544
+ break;
3545
+ }
3546
+ const cleanup = effect.exec();
3547
+ state.set(effect.key, {
3548
+ cleanup: typeof cleanup === 'function' ? cleanup : undefined
3549
+ });
3550
+ break;
3551
+ }
3552
+ case 'cleanupEffects':
3553
+ {
3554
+ const state = effectStates.get(actorScope.self);
3555
+ if (!state) {
3556
+ break;
3557
+ }
3558
+ for (const {
3559
+ cleanup
3560
+ } of state.values()) {
3561
+ cleanup?.();
3562
+ }
3563
+ effectStates.delete(actorScope.self);
3564
+ break;
3565
+ }
3566
+ }
3567
+ }
3568
+ }
3569
+ function resolveContext(context, input) {
3570
+ return typeof context === 'function' ? context({
3571
+ input
3572
+ }) : context;
3573
+ }
3574
+ function createLogic(config) {
3575
+ const transition = (snapshot, event, actorScope) => {
3576
+ if (snapshot.status !== 'active') {
3577
+ return [snapshot, []];
3578
+ }
3579
+ if (event.type === XSTATE_STOP) {
3580
+ return [{
3581
+ ...snapshot,
3582
+ status: 'stopped',
3583
+ input: undefined
3584
+ }, [{
3585
+ type: 'cleanupEffects'
3586
+ }]];
3587
+ }
3588
+ if (event.type === XSTATE_LOGIC_EFFECT_START) {
3589
+ return [{
3590
+ ...snapshot,
3591
+ effects: {
3592
+ ...snapshot.effects,
3593
+ [event.key]: {
3594
+ status: 'active'
3595
+ }
3596
+ }
3597
+ }, []];
3598
+ }
3599
+ if (event.type === XSTATE_LOGIC_EFFECT_RESOLVE) {
3600
+ return [{
3601
+ ...snapshot,
3602
+ effects: {
3603
+ ...snapshot.effects,
3604
+ [event.key]: {
3605
+ status: 'done',
3606
+ output: event.output
3607
+ }
3608
+ }
3609
+ }, []];
3610
+ }
3611
+ if (event.type === XSTATE_LOGIC_EFFECT_REJECT) {
3612
+ return [{
3613
+ ...snapshot,
3614
+ effects: {
3615
+ ...snapshot.effects,
3616
+ [event.key]: {
3617
+ status: 'error',
3618
+ error: event.error
3619
+ }
3620
+ }
3621
+ }, []];
3622
+ }
3623
+ const effects = [];
3624
+ const trackedEffects = {};
3625
+ const enqueueEffect = (key, exec) => {
3626
+ if (snapshot.effects?.[key]) {
3627
+ return;
3628
+ }
3629
+ effects.push({
3630
+ type: 'effect',
3631
+ key,
3632
+ exec
3633
+ });
3634
+ trackedEffects[key] = {
3635
+ status: 'active'
3636
+ };
3637
+ };
3638
+ const enq = {
3639
+ emit: emitted => {
3640
+ effects.push({
3641
+ type: 'emit',
3642
+ event: emitted
3643
+ });
3644
+ },
3645
+ sendBack: sentEvent => {
3646
+ effects.push({
3647
+ type: 'sendBack',
3648
+ event: sentEvent
3649
+ });
3650
+ },
3651
+ raise: raisedEvent => {
3652
+ effects.push({
3653
+ type: 'raise',
3654
+ event: raisedEvent
3655
+ });
3656
+ },
3657
+ effect: (keyOrExec, maybeExec) => {
3658
+ if (typeof keyOrExec === 'string') {
3659
+ enqueueEffect(keyOrExec, maybeExec);
3660
+ return;
3661
+ }
3662
+ effects.push({
3663
+ type: 'effect',
3664
+ exec: keyOrExec
3665
+ });
3666
+ }
3667
+ };
3668
+ const patch = config.run({
3669
+ context: snapshot.context,
3670
+ event,
3671
+ input: snapshot.input,
3672
+ system: actorScope.system,
3673
+ self: actorScope.self,
3674
+ emit: actorScope.emit
3675
+ }, enq);
3676
+ const nextSnapshot = {
3677
+ ...snapshot,
3678
+ ...(patch || {})
3679
+ };
3680
+ if ('context' in snapshot || patch?.context !== undefined) {
3681
+ nextSnapshot.context = patch?.context ?? snapshot.context;
3682
+ }
3683
+ if (patch?.effects || Object.keys(trackedEffects).length) {
3684
+ nextSnapshot.effects = {
3685
+ ...snapshot.effects,
3686
+ ...patch?.effects,
3687
+ ...trackedEffects
3688
+ };
3689
+ }
3690
+ return [nextSnapshot, effects];
3691
+ };
3692
+ const logic = {
3693
+ id: config.id,
3694
+ config,
3695
+ transition,
3696
+ start: (snapshot, actorScope) => {
3697
+ const [nextSnapshot, effects] = transition(snapshot, createInitEvent(snapshot.input), actorScope);
3698
+ Object.assign(snapshot, nextSnapshot);
3699
+ executeLogicEffects(effects, actorScope);
3700
+ },
3701
+ initialTransition: (input, _) => {
3702
+ const context = resolveContext(config.context, input);
3703
+ const snapshot = {
3704
+ status: 'active',
3705
+ output: undefined,
3706
+ error: undefined,
3707
+ input
3708
+ };
3709
+ if (context !== undefined) {
3710
+ snapshot.context = context;
3711
+ }
3712
+ return [{
3713
+ ...snapshot
3714
+ }, []];
3715
+ },
3716
+ getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
3717
+ getPersistedSnapshot: snapshot => snapshot,
3718
+ restoreSnapshot: snapshot => snapshot,
3719
+ executeEffects: executeLogicEffects
3720
+ };
3721
+ return logic;
3722
+ }
3723
+
4176
3724
  const instanceStates = /* #__PURE__ */new WeakMap();
4177
3725
 
4178
3726
  /**
@@ -4551,46 +4099,45 @@ function createEventObservableLogic(lazyObservableOrConfig) {
4551
4099
  config: lazyObservable,
4552
4100
  transition: (state, event) => {
4553
4101
  if (state.status !== 'active') {
4554
- return state;
4102
+ return [state, []];
4555
4103
  }
4556
4104
  switch (event.type) {
4557
4105
  case XSTATE_OBSERVABLE_ERROR:
4558
- return {
4106
+ return [{
4559
4107
  ...state,
4560
4108
  status: 'error',
4561
4109
  error: event.data,
4562
4110
  input: undefined,
4563
4111
  _subscription: undefined
4564
- };
4112
+ }, []];
4565
4113
  case XSTATE_OBSERVABLE_COMPLETE:
4566
- return {
4114
+ return [{
4567
4115
  ...state,
4568
4116
  status: 'done',
4569
4117
  input: undefined,
4570
4118
  _subscription: undefined
4571
- };
4119
+ }, []];
4572
4120
  case XSTATE_STOP:
4573
4121
  state._subscription.unsubscribe();
4574
- return {
4122
+ return [{
4575
4123
  ...state,
4576
4124
  status: 'stopped',
4577
4125
  input: undefined,
4578
4126
  _subscription: undefined
4579
- };
4127
+ }, []];
4580
4128
  default:
4581
- return state;
4129
+ return [state, []];
4582
4130
  }
4583
4131
  },
4584
- getInitialSnapshot: (_, input) => {
4585
- return {
4586
- status: 'active',
4587
- output: undefined,
4588
- error: undefined,
4589
- context: undefined,
4590
- input,
4591
- _subscription: undefined
4592
- };
4593
- },
4132
+ initialTransition: (input, _) => [{
4133
+ status: 'active',
4134
+ output: undefined,
4135
+ error: undefined,
4136
+ context: undefined,
4137
+ input,
4138
+ _subscription: undefined
4139
+ }, []],
4140
+ getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
4594
4141
  start: (state, {
4595
4142
  self,
4596
4143
  system,
@@ -4915,14 +4462,15 @@ exports.ProcessingStatus = ProcessingStatus;
4915
4462
  exports.STATE_DELIMITER = STATE_DELIMITER;
4916
4463
  exports.TimeoutError = TimeoutError;
4917
4464
  exports.XSTATE_INIT = XSTATE_INIT;
4465
+ exports.XSTATE_STOP = XSTATE_STOP;
4466
+ exports.builtInActions = builtInActions;
4918
4467
  exports.checkStateIn = checkStateIn;
4468
+ exports.cloneMachineSnapshot = cloneMachineSnapshot;
4919
4469
  exports.createActor = createActor;
4920
- exports.createAsyncAtom = createAsyncAtom;
4921
4470
  exports.createAsyncLogic = createAsyncLogic;
4922
- exports.createAtom = createAtom;
4923
- exports.createAtomConfig = createAtomConfig;
4924
4471
  exports.createCallbackLogic = createCallbackLogic;
4925
4472
  exports.createEmptyActor = createEmptyActor;
4473
+ exports.createErrorPlatformEvent = createErrorPlatformEvent;
4926
4474
  exports.createEventObservableLogic = createEventObservableLogic;
4927
4475
  exports.createInitEvent = createInitEvent;
4928
4476
  exports.createInvokeId = createInvokeId;
@@ -4931,8 +4479,9 @@ exports.createListenerLogic = createListenerLogic;
4931
4479
  exports.createLogic = createLogic;
4932
4480
  exports.createMachineSnapshot = createMachineSnapshot;
4933
4481
  exports.createObservableLogic = createObservableLogic;
4934
- exports.createReducerAtom = createReducerAtom;
4935
4482
  exports.createSubscriptionLogic = createSubscriptionLogic;
4483
+ exports.createTransitionEnqueue = createTransitionEnqueue;
4484
+ exports.deriveDeferredStarts = deriveDeferredStarts;
4936
4485
  exports.evaluateCandidate = evaluateCandidate;
4937
4486
  exports.formatRouteTransitions = formatRouteTransitions;
4938
4487
  exports.formatTransition = formatTransition;
@@ -4944,9 +4493,11 @@ exports.getPersistedSnapshot = getPersistedSnapshot;
4944
4493
  exports.getProperAncestors = getProperAncestors;
4945
4494
  exports.getStateNodeByPath = getStateNodeByPath;
4946
4495
  exports.getStateNodes = getStateNodes;
4496
+ exports.getTransitionResult = getTransitionResult;
4497
+ exports.hasEffect = hasEffect;
4947
4498
  exports.initialMicrostep = initialMicrostep;
4948
- exports.isAtom = isAtom;
4949
4499
  exports.isAtomicStateNode = isAtomicStateNode;
4500
+ exports.isBuiltInExecutableAction = isBuiltInExecutableAction;
4950
4501
  exports.isInFinalState = isInFinalState;
4951
4502
  exports.isMachineSnapshot = isMachineSnapshot;
4952
4503
  exports.isStateId = isStateId;
@@ -4957,7 +4508,7 @@ exports.matchesEventDescriptor = matchesEventDescriptor;
4957
4508
  exports.matchesState = matchesState;
4958
4509
  exports.parseDelayToMilliseconds = parseDelayToMilliseconds;
4959
4510
  exports.pathToStateValue = pathToStateValue;
4960
- exports.resolveAndExecuteActionsWithContext = resolveAndExecuteActionsWithContext;
4511
+ exports.resolveActionsWithContext = resolveActionsWithContext;
4961
4512
  exports.resolveReferencedActor = resolveReferencedActor;
4962
4513
  exports.resolveStateValue = resolveStateValue;
4963
4514
  exports.subscriptionLogic = subscriptionLogic;