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,16 +1,3 @@
1
- /**
2
- * Reactivity interop between actors and atoms.
3
- *
4
- * `Actor#get()` calls this hook (when installed) so that computed atoms can
5
- * track actor reads as dependencies. The hook is installed by `atom.ts` on
6
- * module evaluation, which only happens when atoms are actually used — apps
7
- * that never import atoms don't bundle the reactive system.
8
- */
9
- let onActorRead;
10
- function installActorReadHook(hook) {
11
- onActorRead = hook;
12
- }
13
-
14
1
  class Mailbox {
15
2
  constructor(_process) {
16
3
  this._process = _process;
@@ -64,6 +51,8 @@ const NULL_EVENT = '';
64
51
  const STATE_IDENTIFIER = '#';
65
52
  const WILDCARD = '*';
66
53
  const XSTATE_INIT = '@xstate.init';
54
+ const XSTATE_START = '@xstate.start';
55
+ const XSTATE_SPAWN = '@xstate.spawn';
67
56
  const XSTATE_STOP = '@xstate.stop';
68
57
 
69
58
  /**
@@ -140,6 +129,12 @@ function createErrorActorEvent(id, error) {
140
129
  actorId: id
141
130
  };
142
131
  }
132
+ function createErrorPlatformEvent(kind, error) {
133
+ return {
134
+ type: `xstate.error.${kind}`,
135
+ error
136
+ };
137
+ }
143
138
  function createInitEvent(input) {
144
139
  return {
145
140
  type: XSTATE_INIT,
@@ -164,6 +159,9 @@ function reportUnhandledError(err) {
164
159
  const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
165
160
 
166
161
  const builtInActions = {
162
+ // No-op: creation/registration are eager and pure; the start is a separate
163
+ // deferred `@xstate.start` effect (see `deriveDeferredStarts`).
164
+ ['@xstate.spawn']: _actor => {},
167
165
  ['@xstate.start']: actor => {
168
166
  actor.start();
169
167
  },
@@ -186,7 +184,7 @@ const builtInActions = {
186
184
  ['@xstate.cancel']: (actorScope, sendId) => {
187
185
  actorScope.system.scheduler.cancel(actorScope.self, sendId);
188
186
  },
189
- ['@xstate.stopChild']: (actorScope, actor) => {
187
+ ['@xstate.stop']: (actorScope, actor) => {
190
188
  actorScope.stopChild(actor);
191
189
  }
192
190
  };
@@ -198,15 +196,20 @@ const subscriptions = /* #__PURE__ */new WeakMap();
198
196
  * (`enq.listen()` / `enq.subscribeTo()`) and detaches when stopped.
199
197
  */
200
198
  function createAttachedLogic(attach) {
199
+ const initialTransition = (input, _) => [{
200
+ status: 'active',
201
+ output: undefined,
202
+ error: undefined,
203
+ input
204
+ }, []];
201
205
  return {
202
206
  start: (state, {
203
207
  self,
204
208
  system
205
209
  }) => {
206
- // Don't attach if the target doesn't exist, or (for actors) is stopped.
207
- // Atoms have no `getSnapshot` / lifecycle, so they always attach.
210
+ // Don't attach if the target doesn't exist or is stopped.
208
211
  const target = state.input.actor;
209
- if (!target || typeof target.getSnapshot === 'function' && target.getSnapshot().status === 'stopped') {
212
+ if (!target || target.getSnapshot().status === 'stopped') {
210
213
  return;
211
214
  }
212
215
  const subscription = attach(state.input, {
@@ -249,20 +252,16 @@ function createAttachedLogic(attach) {
249
252
  if (event.type === XSTATE_STOP) {
250
253
  subscriptions.get(self)?.unsubscribe();
251
254
  subscriptions.delete(self);
252
- return {
255
+ return [{
253
256
  ...state,
254
257
  status: 'stopped',
255
258
  error: undefined
256
- };
259
+ }, []];
257
260
  }
258
- return state;
261
+ return [state, []];
259
262
  },
260
- getInitialSnapshot: (_, input) => ({
261
- status: 'active',
262
- output: undefined,
263
- error: undefined,
264
- input
265
- }),
263
+ initialTransition,
264
+ getInitialSnapshot: (actorScope, input) => initialTransition(input)[0],
266
265
  getPersistedSnapshot: snapshot => snapshot,
267
266
  restoreSnapshot: snapshot => snapshot
268
267
  };
@@ -308,620 +307,6 @@ function createListenerLogic() {
308
307
  // Singleton logic instance
309
308
  const listenerLogic = /* #__PURE__ */createListenerLogic();
310
309
 
311
- /* eslint-disable */
312
- // Adapted from Alien Signals
313
- // https://github.com/stackblitz/alien-signals/
314
-
315
- let ReactiveFlags = /*#__PURE__*/function (ReactiveFlags) {
316
- ReactiveFlags[ReactiveFlags["None"] = 0] = "None";
317
- ReactiveFlags[ReactiveFlags["Mutable"] = 1] = "Mutable";
318
- ReactiveFlags[ReactiveFlags["Watching"] = 2] = "Watching";
319
- ReactiveFlags[ReactiveFlags["RecursedCheck"] = 4] = "RecursedCheck";
320
- ReactiveFlags[ReactiveFlags["Recursed"] = 8] = "Recursed";
321
- ReactiveFlags[ReactiveFlags["Dirty"] = 16] = "Dirty";
322
- ReactiveFlags[ReactiveFlags["Pending"] = 32] = "Pending";
323
- return ReactiveFlags;
324
- }({});
325
- function createReactiveSystem({
326
- update,
327
- notify,
328
- unwatched
329
- }) {
330
- return {
331
- link,
332
- unlink,
333
- propagate,
334
- checkDirty,
335
- shallowPropagate
336
- };
337
- function link(dep, sub, version) {
338
- const prevDep = sub.depsTail;
339
- if (prevDep !== undefined && prevDep.dep === dep) {
340
- return;
341
- }
342
- const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps;
343
- if (nextDep !== undefined && nextDep.dep === dep) {
344
- nextDep.version = version;
345
- sub.depsTail = nextDep;
346
- return;
347
- }
348
- const prevSub = dep.subsTail;
349
- if (prevSub !== undefined && prevSub.version === version && prevSub.sub === sub) {
350
- return;
351
- }
352
- const newLink = sub.depsTail = dep.subsTail = {
353
- version,
354
- dep,
355
- sub,
356
- prevDep,
357
- nextDep,
358
- prevSub,
359
- nextSub: undefined
360
- };
361
- if (nextDep !== undefined) {
362
- nextDep.prevDep = newLink;
363
- }
364
- if (prevDep !== undefined) {
365
- prevDep.nextDep = newLink;
366
- } else {
367
- sub.deps = newLink;
368
- }
369
- if (prevSub !== undefined) {
370
- prevSub.nextSub = newLink;
371
- } else {
372
- dep.subs = newLink;
373
- }
374
- }
375
- function unlink(link, sub = link.sub) {
376
- const dep = link.dep;
377
- const prevDep = link.prevDep;
378
- const nextDep = link.nextDep;
379
- const nextSub = link.nextSub;
380
- const prevSub = link.prevSub;
381
- if (nextDep !== undefined) {
382
- nextDep.prevDep = prevDep;
383
- } else {
384
- sub.depsTail = prevDep;
385
- }
386
- if (prevDep !== undefined) {
387
- prevDep.nextDep = nextDep;
388
- } else {
389
- sub.deps = nextDep;
390
- }
391
- if (nextSub !== undefined) {
392
- nextSub.prevSub = prevSub;
393
- } else {
394
- dep.subsTail = prevSub;
395
- }
396
- if (prevSub !== undefined) {
397
- prevSub.nextSub = nextSub;
398
- } else if ((dep.subs = nextSub) === undefined) {
399
- unwatched(dep);
400
- }
401
- return nextDep;
402
- }
403
- function propagate(link) {
404
- let next = link.nextSub;
405
- let stack;
406
- top: do {
407
- const sub = link.sub;
408
- let flags = sub.flags;
409
- if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending))) {
410
- sub.flags = flags | ReactiveFlags.Pending;
411
- } else if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))) {
412
- flags = ReactiveFlags.None;
413
- } else if (!(flags & ReactiveFlags.RecursedCheck)) {
414
- sub.flags = flags & ~ReactiveFlags.Recursed | ReactiveFlags.Pending;
415
- } else if (!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) && isValidLink(link, sub)) {
416
- sub.flags = flags | (ReactiveFlags.Recursed | ReactiveFlags.Pending);
417
- flags &= ReactiveFlags.Mutable;
418
- } else {
419
- flags = ReactiveFlags.None;
420
- }
421
- if (flags & ReactiveFlags.Watching) {
422
- notify(sub);
423
- }
424
- if (flags & ReactiveFlags.Mutable) {
425
- const subSubs = sub.subs;
426
- if (subSubs !== undefined) {
427
- const nextSub = (link = subSubs).nextSub;
428
- if (nextSub !== undefined) {
429
- stack = {
430
- value: next,
431
- prev: stack
432
- };
433
- next = nextSub;
434
- }
435
- continue;
436
- }
437
- }
438
- if ((link = next) !== undefined) {
439
- next = link.nextSub;
440
- continue;
441
- }
442
- while (stack !== undefined) {
443
- link = stack.value;
444
- stack = stack.prev;
445
- if (link !== undefined) {
446
- next = link.nextSub;
447
- continue top;
448
- }
449
- }
450
- break;
451
- } while (true);
452
- }
453
- function checkDirty(link, sub) {
454
- let stack;
455
- let checkDepth = 0;
456
- let dirty = false;
457
- top: do {
458
- const dep = link.dep;
459
- const flags = dep.flags;
460
- if (sub.flags & ReactiveFlags.Dirty) {
461
- dirty = true;
462
- } else if ((flags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) === (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) {
463
- if (update(dep)) {
464
- const subs = dep.subs;
465
- if (subs.nextSub !== undefined) {
466
- shallowPropagate(subs);
467
- }
468
- dirty = true;
469
- }
470
- } else if ((flags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) === (ReactiveFlags.Mutable | ReactiveFlags.Pending)) {
471
- if (link.nextSub !== undefined || link.prevSub !== undefined) {
472
- stack = {
473
- value: link,
474
- prev: stack
475
- };
476
- }
477
- link = dep.deps;
478
- sub = dep;
479
- ++checkDepth;
480
- continue;
481
- }
482
- if (!dirty) {
483
- const nextDep = link.nextDep;
484
- if (nextDep !== undefined) {
485
- link = nextDep;
486
- continue;
487
- }
488
- }
489
- while (checkDepth--) {
490
- const firstSub = sub.subs;
491
- const hasMultipleSubs = firstSub.nextSub !== undefined;
492
- if (hasMultipleSubs) {
493
- link = stack.value;
494
- stack = stack.prev;
495
- } else {
496
- link = firstSub;
497
- }
498
- if (dirty) {
499
- if (update(sub)) {
500
- if (hasMultipleSubs) {
501
- shallowPropagate(firstSub);
502
- }
503
- sub = link.sub;
504
- continue;
505
- }
506
- dirty = false;
507
- } else {
508
- sub.flags &= ~ReactiveFlags.Pending;
509
- }
510
- sub = link.sub;
511
- const nextDep = link.nextDep;
512
- if (nextDep !== undefined) {
513
- link = nextDep;
514
- continue top;
515
- }
516
- }
517
- return dirty;
518
- } while (true);
519
- }
520
- function shallowPropagate(link) {
521
- do {
522
- const sub = link.sub;
523
- const flags = sub.flags;
524
- if ((flags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) === ReactiveFlags.Pending) {
525
- sub.flags = flags | ReactiveFlags.Dirty;
526
- if ((flags & (ReactiveFlags.Watching | ReactiveFlags.RecursedCheck)) === ReactiveFlags.Watching) {
527
- notify(sub);
528
- }
529
- }
530
- } while ((link = link.nextSub) !== undefined);
531
- }
532
- function isValidLink(checkLink, sub) {
533
- let link = sub.depsTail;
534
- while (link !== undefined) {
535
- if (link === checkLink) {
536
- return true;
537
- }
538
- link = link.prevDep;
539
- }
540
- return false;
541
- }
542
- }
543
-
544
- /**
545
- * Nominal brand (phantom, compile-time only) marking a value as an XState atom.
546
- * Real atoms get the runtime brand ({@link $$atom}) in `createAtom`; this type
547
- * brand keeps atom-only APIs (e.g. `enq.subscribeTo`) from structurally
548
- * accepting plain readables like `actor.select(...)` that would be dispatched
549
- * down the non-atom path at runtime.
550
- */
551
-
552
- /** Brand marking an object as an atom, so it can be detected structurally. */
553
- const $$atom = /* #__PURE__ */Symbol.for('xstate.atom');
554
-
555
- /**
556
- * Returns `true` if the value is an atom (writable, computed, async, or
557
- * reducer). Lets actor-consuming APIs (e.g. `enq.subscribeTo`) accept atoms
558
- * directly.
559
- */
560
- function isAtom(value) {
561
- return !!value && typeof value === 'object' && $$atom in value;
562
- }
563
- const queuedEffects = [];
564
- let cycle = 0;
565
- // Initialized lazily on first atom creation (instead of at module evaluation)
566
- // so the reactive system stays tree-shakeable for apps that never use atoms —
567
-
568
- // would be retained by consumer bundlers even when atoms are unused.
569
- let link;
570
- let unlink;
571
- let propagate;
572
- let checkDirty;
573
- let shallowPropagate;
574
- let reactiveSystemInstalled = false;
575
- function ensureReactiveSystem() {
576
- if (reactiveSystemInstalled) {
577
- return;
578
- }
579
- reactiveSystemInstalled = true;
580
- ({
581
- link,
582
- unlink,
583
- propagate,
584
- checkDirty,
585
- shallowPropagate
586
- } = createReactiveSystem({
587
- update(atom) {
588
- return atom._update();
589
- },
590
- notify(effect) {
591
- queuedEffects[queuedEffectsLength++] = effect;
592
- effect.flags &= ~ReactiveFlags.Watching;
593
- },
594
- unwatched(atom) {
595
- if (atom.depsTail !== undefined) {
596
- atom.depsTail = undefined;
597
- atom.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty;
598
- purgeDeps(atom);
599
- }
600
- }
601
- }));
602
- }
603
- let notifyIndex = 0;
604
- let queuedEffectsLength = 0;
605
- let activeSub;
606
- function purgeDeps(sub) {
607
- const depsTail = sub.depsTail;
608
- let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps;
609
- while (dep !== undefined) {
610
- dep = unlink(dep, sub);
611
- }
612
- }
613
- function flush() {
614
- while (notifyIndex < queuedEffectsLength) {
615
- const effect = queuedEffects[notifyIndex];
616
- queuedEffects[notifyIndex++] = undefined;
617
- effect.notify();
618
- }
619
- notifyIndex = 0;
620
- queuedEffectsLength = 0;
621
- }
622
-
623
- /** The current state of an async atom. */
624
-
625
- /** Options passed to an async atom getter. */
626
-
627
- function compareAsyncAtomState(a, b) {
628
- if (a.status !== b.status) {
629
- return false;
630
- }
631
- if (a.status === 'done' && b.status === 'done') {
632
- return Object.is(a.data, b.data);
633
- }
634
- if (a.status === 'error' && b.status === 'error') {
635
- return Object.is(a.error, b.error);
636
- }
637
- // both 'pending'
638
- return true;
639
- }
640
- function updateAsyncAtom(atom, nextValue) {
641
- if (atom._update(nextValue)) {
642
- const subs = atom.subs;
643
- if (subs !== undefined) {
644
- propagate(subs);
645
- shallowPropagate(subs);
646
- flush();
647
- }
648
- }
649
- }
650
-
651
- /**
652
- * Creates a read-only atom whose value is loaded from an async getter.
653
- *
654
- * The getter receives an `AbortSignal`; when the async atom recomputes, the
655
- * previous signal is aborted and stale resolutions are ignored.
656
- */
657
- function createAsyncAtom(getValue, options) {
658
- const ref = {};
659
- let currentController;
660
- let currentRunId = 0;
661
- const atom = createAtom(() => {
662
- currentController?.abort();
663
- const controller = new AbortController();
664
- const runId = ++currentRunId;
665
- currentController = controller;
666
- getValue({
667
- signal: controller.signal
668
- }).then(data => {
669
- if (runId !== currentRunId || controller.signal.aborted) {
670
- return;
671
- }
672
- updateAsyncAtom(ref.current, {
673
- status: 'done',
674
- data
675
- });
676
- }, error => {
677
- if (runId !== currentRunId || controller.signal.aborted) {
678
- return;
679
- }
680
- updateAsyncAtom(ref.current, {
681
- status: 'error',
682
- error
683
- });
684
- });
685
- return {
686
- status: 'pending'
687
- };
688
- }, {
689
- compare: compareAsyncAtomState,
690
- ...options
691
- });
692
- ref.current = atom;
693
- return atom;
694
- }
695
-
696
- /**
697
- * Mirror atoms that bump a version whenever a tracked actor emits a snapshot,
698
- * letting computed atoms depend on `actor.get()` reads.
699
- *
700
- * The hook is installed lazily on first atom creation (not at module
701
- * evaluation) so that it remains tree-shakeable for apps that never use atoms —
702
- * any tracked read necessarily happens inside an atom computation, so first-use
703
- * installation is always early enough.
704
- */
705
- let actorInteropInstalled = false;
706
- function ensureActorInterop() {
707
- if (actorInteropInstalled) {
708
- return;
709
- }
710
- actorInteropInstalled = true;
711
- const actorVersions = new WeakMap();
712
- installActorReadHook(actorRef => {
713
- if (activeSub === undefined) {
714
- return;
715
- }
716
- let version = actorVersions.get(actorRef);
717
- if (version === undefined) {
718
- version = createAtom(0);
719
- actorVersions.set(actorRef, version);
720
- actorRef.subscribe({
721
- next: () => version.set(v => v + 1),
722
- error: () => {}
723
- });
724
- }
725
- version.get();
726
- });
727
- }
728
-
729
- /**
730
- * Creates an atom.
731
- *
732
- * Pass a value for a writable atom or a getter for a computed read-only atom.
733
- */
734
-
735
- function createAtom(valueOrFn, optionsOrInput) {
736
- ensureReactiveSystem();
737
- ensureActorInterop();
738
- const isComputed = typeof valueOrFn === 'function';
739
- const getter = valueOrFn;
740
-
741
- // Create plain object atom
742
- const atom = {
743
- _snapshot: isComputed ? undefined : valueOrFn,
744
- subs: undefined,
745
- subsTail: undefined,
746
- deps: undefined,
747
- depsTail: undefined,
748
- flags: isComputed ? ReactiveFlags.None : ReactiveFlags.Mutable,
749
- get() {
750
- if (activeSub !== undefined) {
751
- link(atom, activeSub, cycle);
752
- }
753
- return atom._snapshot;
754
- },
755
- subscribe(observerOrFn) {
756
- const observer = typeof observerOrFn === 'function' ? {
757
- next: observerOrFn
758
- } : observerOrFn;
759
- const observed = {
760
- current: false
761
- };
762
- const e = effect(() => {
763
- atom.get();
764
- if (!observed.current) {
765
- observed.current = true;
766
- } else {
767
- const prevSub = activeSub;
768
- activeSub = undefined;
769
- try {
770
- observer.next?.(atom._snapshot);
771
- } catch (err) {
772
- reportUnhandledError(err);
773
- } finally {
774
- activeSub = prevSub;
775
- }
776
-
777
- // If the observer synchronously updates any of our deps we'll be
778
- // marked as dirty preventing this effect from re-running. Request
779
- // the value again to reconcile any dirty deps.
780
- atom.get();
781
- }
782
- });
783
- return {
784
- unsubscribe: () => {
785
- e.stop();
786
- }
787
- };
788
- },
789
- _update(getValue) {
790
- const prevSub = activeSub;
791
- const compare = optionsOrInput?.compare ?? Object.is;
792
- activeSub = atom;
793
- ++cycle;
794
- atom.depsTail = undefined;
795
- if (isComputed) {
796
- atom.flags = ReactiveFlags.Mutable | ReactiveFlags.RecursedCheck;
797
- }
798
- try {
799
- const oldValue = atom._snapshot;
800
- const newValue = typeof getValue === 'function' ? getValue(oldValue) : getValue === undefined && isComputed ? getter(oldValue) : getValue;
801
- if (oldValue === undefined || !compare(oldValue, newValue)) {
802
- atom._snapshot = newValue;
803
- return true;
804
- }
805
- return false;
806
- } finally {
807
- activeSub = prevSub;
808
- if (isComputed) {
809
- atom.flags &= ~ReactiveFlags.RecursedCheck;
810
- }
811
- purgeDeps(atom);
812
- }
813
- }
814
- };
815
- if (isComputed) {
816
- atom.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty;
817
- atom.get = function () {
818
- const flags = atom.flags;
819
- if (flags & ReactiveFlags.Dirty || flags & ReactiveFlags.Pending && checkDirty(atom.deps, atom)) {
820
- if (atom._update()) {
821
- const subs = atom.subs;
822
- if (subs !== undefined) {
823
- shallowPropagate(subs);
824
- }
825
- }
826
- } else if (flags & ReactiveFlags.Pending) {
827
- atom.flags = flags & ~ReactiveFlags.Pending;
828
- }
829
- if (activeSub !== undefined) {
830
- link(atom, activeSub, cycle);
831
- }
832
- return atom._snapshot;
833
- };
834
- } else {
835
- atom.set = function (valueOrFn) {
836
- if (atom._update(valueOrFn)) {
837
- const subs = atom.subs;
838
- if (subs !== undefined) {
839
- propagate(subs);
840
- shallowPropagate(subs);
841
- flush();
842
- }
843
- }
844
- };
845
- }
846
- atom[$$atom] = true;
847
- return atom;
848
- }
849
-
850
- /**
851
- * Creates an inert atom config that can be instantiated later.
852
- *
853
- * Useful for framework hooks that need to create a stable local atom from
854
- * component input.
855
- */
856
-
857
- function createAtomConfig(initialValueOrFn, options) {
858
- return {
859
- createAtom(input) {
860
- const initialValue = typeof initialValueOrFn === 'function' ? initialValueOrFn(input) : initialValueOrFn;
861
- return createAtom(initialValue, options);
862
- }
863
- };
864
- }
865
-
866
- /** Creates an atom whose updates are handled by a reducer function. */
867
- function createReducerAtom(initialValue, reducer, options) {
868
- const atom = createAtom(initialValue, options);
869
- return {
870
- [$$atom]: true,
871
- get: atom.get.bind(atom),
872
- subscribe: atom.subscribe.bind(atom),
873
- send(event) {
874
- const prevSub = activeSub;
875
- activeSub = undefined;
876
- let nextState;
877
- try {
878
- nextState = reducer(atom.get(), event);
879
- } finally {
880
- activeSub = prevSub;
881
- }
882
- atom.set(nextState);
883
- }
884
- };
885
- }
886
- function effect(fn) {
887
- const run = () => {
888
- const prevSub = activeSub;
889
- activeSub = effectObj;
890
- ++cycle;
891
- effectObj.depsTail = undefined;
892
- effectObj.flags = ReactiveFlags.Watching | ReactiveFlags.RecursedCheck;
893
- try {
894
- return fn();
895
- } finally {
896
- activeSub = prevSub;
897
- effectObj.flags &= ~ReactiveFlags.RecursedCheck;
898
- purgeDeps(effectObj);
899
- }
900
- };
901
- const effectObj = {
902
- deps: undefined,
903
- depsTail: undefined,
904
- subs: undefined,
905
- subsTail: undefined,
906
- flags: ReactiveFlags.Watching | ReactiveFlags.RecursedCheck,
907
- notify() {
908
- const flags = this.flags;
909
- if (flags & ReactiveFlags.Dirty || flags & ReactiveFlags.Pending && checkDirty(this.deps, this)) {
910
- run();
911
- } else {
912
- this.flags = ReactiveFlags.Watching;
913
- }
914
- },
915
- stop() {
916
- this.flags = ReactiveFlags.None;
917
- this.depsTail = undefined;
918
- purgeDeps(this);
919
- }
920
- };
921
- run();
922
- return effectObj;
923
- }
924
-
925
310
  /**
926
311
  * Creates actor logic for subscribing to lifecycle events (done/error/snapshot)
927
312
  * from another actor. Used internally by `enq.subscribeTo()`.
@@ -939,15 +324,6 @@ function createSubscriptionLogic() {
939
324
  error,
940
325
  snapshot: onSnapshot
941
326
  } = mappers;
942
-
943
- // Atoms emit a raw value (no `status`) and have no done/error lifecycle —
944
- // pass the value straight to the `snapshot` mapper.
945
- if (isAtom(actor)) {
946
- if (!onSnapshot) {
947
- return;
948
- }
949
- return actor.subscribe(value => relayMappedToParent(self, system, () => onSnapshot(value)));
950
- }
951
327
  return actor.subscribe({
952
328
  next: snapshot => {
953
329
  if (snapshot.status === 'done' && done) {
@@ -973,6 +349,19 @@ function createSubscriptionLogic() {
973
349
  // Singleton logic instance
974
350
  const subscriptionLogic = /* #__PURE__ */createSubscriptionLogic();
975
351
 
352
+ const builtInExecutableAction = Symbol('xstate.builtInExecutableAction');
353
+ function brandBuiltInExecutableAction(action) {
354
+ Object.defineProperty(action, builtInExecutableAction, {
355
+ value: true
356
+ });
357
+ return action;
358
+ }
359
+ function mergeContextPatch(context, patch) {
360
+ return {
361
+ ...context,
362
+ ...patch
363
+ };
364
+ }
976
365
  function pushBuiltInAction(actions, action, ...args) {
977
366
  actions.push({
978
367
  action,
@@ -1008,11 +397,11 @@ function unregisterChild(actor) {
1008
397
  _special: true
1009
398
  });
1010
399
  }
1011
- function pushStartedChild(actions, actor, id) {
1012
- pushBuiltInAction(actions, builtInActions['@xstate.start'], actor);
400
+ function pushSpawnedChild(actions, actor, id) {
401
+ pushBuiltInAction(actions, builtInActions['@xstate.spawn'], actor);
1013
402
  actions.push(registerSpawnedChild(actor, id ?? actor.id));
1014
403
  }
1015
- function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubscriptions = false) {
404
+ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubscriptions = false, createActors = true) {
1016
405
  const props = {
1017
406
  cancel: id => {
1018
407
  pushBuiltInAction(actions, builtInActions['@xstate.cancel'], actorScope, id);
@@ -1034,21 +423,32 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
1034
423
  }
1035
424
  },
1036
425
  spawn: (logic, options) => {
426
+ if (!createActors) {
427
+ // TODO: replace this speculative placeholder with a typed inert actor ref.
428
+ return {
429
+ id: options?.id ?? options?.registryKey ?? logic.id
430
+ };
431
+ }
1037
432
  const actor = createActor(logic, {
1038
433
  ...options,
1039
434
  parent: actorScope.self
1040
435
  });
1041
- pushStartedChild(actions, actor, options?.id);
436
+ pushSpawnedChild(actions, actor, options?.id);
1042
437
  return actor;
1043
438
  },
1044
439
  sendTo: (actor, event, options) => {
1045
- if (actor) {
1046
- pushBuiltInAction(actions, builtInActions['@xstate.sendTo'], actorScope, actor, event, options);
440
+ if (!actor) {
441
+ internalEvents.push(createErrorPlatformEvent('communication', {
442
+ message: 'Unable to send event to an undefined actor',
443
+ event
444
+ }));
445
+ return;
1047
446
  }
447
+ pushBuiltInAction(actions, builtInActions['@xstate.sendTo'], actorScope, actor, event, options);
1048
448
  },
1049
449
  stop: actor => {
1050
450
  if (actor) {
1051
- pushBuiltInAction(actions, builtInActions['@xstate.stopChild'], actorScope, actor);
451
+ pushBuiltInAction(actions, builtInActions['@xstate.stop'], actorScope, actor);
1052
452
  actions.push(unregisterChild(actor));
1053
453
  }
1054
454
  }
@@ -1065,7 +465,7 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
1065
465
  input,
1066
466
  parent: actorScope.self
1067
467
  });
1068
- pushBuiltInAction(actions, builtInActions['@xstate.start'], listenerActor);
468
+ pushBuiltInAction(actions, builtInActions['@xstate.spawn'], listenerActor);
1069
469
  return listenerActor;
1070
470
  },
1071
471
  subscribeTo: (actor, mappers) => {
@@ -1080,77 +480,175 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
1080
480
  input,
1081
481
  parent: actorScope.self
1082
482
  });
1083
- pushBuiltInAction(actions, builtInActions['@xstate.start'], subscriptionActor);
483
+ pushBuiltInAction(actions, builtInActions['@xstate.spawn'], subscriptionActor);
1084
484
  return subscriptionActor;
1085
485
  }
1086
486
  });
1087
487
  }
1088
- return createEnqueueObject$1(props, (action, ...args) => {
488
+ return createEnqueueObject(props, (action, ...args) => {
1089
489
  pushBuiltInAction(actions, action, ...args);
1090
490
  });
1091
491
  }
1092
- function resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope, actions) {
1093
- let intermediateSnapshot = currentSnapshot;
1094
- for (const action of actions) {
1095
- const isInline = typeof action === 'function';
1096
- const resolvedAction = isInline ? action : typeof action === 'object' && 'action' in action && typeof action.action === 'function' ? action.action.bind(null, ...action.args) : false;
1097
- if (!resolvedAction && typeof action === 'object' && action !== null) {
1098
- actorScope.defer(() => {
1099
- actorScope.emit(action);
1100
- });
1101
- }
1102
- const actionArgs = {
1103
- context: intermediateSnapshot.context,
1104
- event,
1105
- self: actorScope.self,
1106
- system: actorScope.system,
1107
- children: intermediateSnapshot.children,
1108
- parent: actorScope.self._parent,
1109
- actions: currentSnapshot.machine.implementations.actions,
1110
- actors: currentSnapshot.machine.implementations.actors
1111
- };
1112
- let actionParams = undefined;
1113
- if (typeof action === 'object' && action !== null) {
1114
- const {
1115
- type: _,
1116
- ...emittedEventParams
1117
- } = action;
1118
- actionParams = emittedEventParams;
1119
- }
1120
- if (resolvedAction && '_special' in resolvedAction) {
1121
- actorScope.actionExecutor({
1122
- type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type ?? '(anonymous)' : action.name || '(anonymous)',
1123
- params: actionParams,
1124
- args: [],
1125
- exec: undefined
1126
- });
1127
- const specialAction = resolvedAction;
1128
- const res = specialAction(actionArgs, emptyEnqueueObject);
1129
- if (res && ('context' in res || 'children' in res)) {
1130
- intermediateSnapshot = cloneMachineSnapshot(intermediateSnapshot, {
1131
- ...('context' in res ? {
1132
- context: res.context
1133
- } : {}),
1134
- ...('children' in res ? {
1135
- children: res.children
1136
- } : {})
1137
- });
492
+ function getBuiltInActionFields(action, args) {
493
+ switch (action) {
494
+ case builtInActions['@xstate.spawn']:
495
+ {
496
+ const [actor] = args;
497
+ return {
498
+ actor,
499
+ id: actor.id,
500
+ logic: actor.logic,
501
+ src: actor.src,
502
+ input: actor.options?.input
503
+ };
504
+ }
505
+ case builtInActions['@xstate.raise']:
506
+ {
507
+ const [, event, options] = args;
508
+ return {
509
+ event,
510
+ id: options?.id,
511
+ delay: options?.delay
512
+ };
513
+ }
514
+ case builtInActions['@xstate.sendTo']:
515
+ {
516
+ const [, target, event, options] = args;
517
+ return {
518
+ target,
519
+ event,
520
+ id: options?.id,
521
+ delay: options?.delay
522
+ };
523
+ }
524
+ case builtInActions['@xstate.cancel']:
525
+ {
526
+ const [, id] = args;
527
+ return {
528
+ id
529
+ };
530
+ }
531
+ case builtInActions['@xstate.stop']:
532
+ {
533
+ const [, actor] = args;
534
+ return {
535
+ actor
536
+ };
537
+ }
538
+ default:
539
+ return undefined;
540
+ }
541
+ }
542
+ function createStartEffect(actor) {
543
+ const action = builtInActions['@xstate.start'];
544
+ const args = [actor];
545
+ return brandBuiltInExecutableAction({
546
+ type: XSTATE_START,
547
+ params: {
548
+ action,
549
+ args
550
+ },
551
+ args,
552
+ exec: action.bind(null, actor),
553
+ actor,
554
+ id: actor.id
555
+ });
556
+ }
557
+
558
+ /**
559
+ * Attached (listener/subscription) starts are ordered before child starts so
560
+ * that a listener/subscription captures events emitted synchronously as its
561
+ * target actor starts.
562
+ */
563
+ function deriveDeferredStarts(effects) {
564
+ const attachedStarts = [];
565
+ const childStarts = [];
566
+ for (const effect of effects) {
567
+ if (!isBuiltInExecutableAction(effect) || effect.type !== XSTATE_SPAWN) {
568
+ continue;
569
+ }
570
+ const {
571
+ actor,
572
+ logic
573
+ } = effect;
574
+ const start = createStartEffect(actor);
575
+ if (logic === listenerLogic || logic === subscriptionLogic) {
576
+ attachedStarts.push(start);
577
+ } else {
578
+ childStarts.push(start);
579
+ }
580
+ }
581
+ return [...attachedStarts, ...childStarts];
582
+ }
583
+ function isBuiltInExecutableAction(action) {
584
+ return action[builtInExecutableAction] === true;
585
+ }
586
+ function resolveActionsWithContext(currentSnapshot, event, actorScope, actions) {
587
+ let intermediateSnapshot = currentSnapshot;
588
+ const executableActions = [];
589
+ for (const action of actions) {
590
+ const isInline = typeof action === 'function';
591
+ const resolvedAction = isInline ? action : typeof action === 'object' && 'action' in action && typeof action.action === 'function' ? action.action.bind(null, ...action.args) : false;
592
+ const actionArgs = {
593
+ context: intermediateSnapshot.context,
594
+ event,
595
+ output: getEventOutput(event),
596
+ self: actorScope.self,
597
+ system: actorScope.system,
598
+ children: intermediateSnapshot.children,
599
+ parent: actorScope.self._parent,
600
+ actions: currentSnapshot.machine.implementations.actions,
601
+ actorSources: currentSnapshot.machine.implementations.actorSources
602
+ };
603
+ let actionParams = undefined;
604
+ if (typeof action === 'object' && action !== null) {
605
+ const {
606
+ type: _,
607
+ ...emittedEventParams
608
+ } = action;
609
+ actionParams = emittedEventParams;
610
+ }
611
+ if (resolvedAction && '_special' in resolvedAction) {
612
+ executableActions.push({
613
+ type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type ?? '(anonymous)' : action.name || '(anonymous)',
614
+ params: actionParams,
615
+ args: [],
616
+ exec: undefined
617
+ });
618
+ const specialAction = resolvedAction;
619
+ const res = specialAction(actionArgs, emptyEnqueueObject);
620
+ if (res && ('context' in res || 'children' in res)) {
621
+ // Special-action patches never change `_nodes`, so a shallow clone is
622
+ // equivalent to `cloneMachineSnapshot` — and keeps this module (and
623
+ // non-machine logic like `createFSM`) independent of State.ts.
624
+ intermediateSnapshot = {
625
+ ...intermediateSnapshot,
626
+ ...(res.context !== undefined ? {
627
+ context: mergeContextPatch(intermediateSnapshot.context, res.context)
628
+ } : {}),
629
+ ...('children' in res ? {
630
+ children: res.children
631
+ } : {})
632
+ };
1138
633
  }
1139
634
  continue;
1140
635
  }
1141
636
  if (!resolvedAction || !('resolve' in resolvedAction)) {
1142
- actorScope.actionExecutor({
637
+ const builtInFields = typeof action === 'object' && action !== null && 'action' in action && typeof action.action === 'function' ? getBuiltInActionFields(action.action, action.args) : undefined;
638
+ const executableAction = {
1143
639
  type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type : action.name || '(anonymous)',
1144
640
  params: actionParams,
1145
641
  args: typeof action === 'object' && 'action' in action ? action.args : [],
1146
- exec: resolvedAction
1147
- });
642
+ exec: resolvedAction || (typeof action === 'object' && action !== null ? () => actorScope.defer(() => actorScope.emit(action)) : undefined),
643
+ ...builtInFields
644
+ };
645
+ executableActions.push(builtInFields ? brandBuiltInExecutableAction(executableAction) : executableAction);
1148
646
  continue;
1149
647
  }
1150
648
  }
1151
- return intermediateSnapshot;
649
+ return [intermediateSnapshot, executableActions];
1152
650
  }
1153
- function createEnqueueObject$1(props, action) {
651
+ function createEnqueueObject(props, action) {
1154
652
  const enqueueFn = (fn, ...args) => {
1155
653
  action(fn, ...args);
1156
654
  };
@@ -1168,7 +666,7 @@ function createEnqueueObject$1(props, action) {
1168
666
  });
1169
667
  return enqueueFn;
1170
668
  }
1171
- const emptyEnqueueObject = createEnqueueObject$1({}, () => {});
669
+ const emptyEnqueueObject = createEnqueueObject({}, () => {});
1172
670
 
1173
671
  function parseDurationToMilliseconds(duration) {
1174
672
  const normalizedDuration = duration.trim();
@@ -1230,6 +728,9 @@ function resolveDelay(delay, delaySource, args) {
1230
728
  }
1231
729
  return configuredDelay;
1232
730
  }
731
+ function getStateInput(snapshot, stateNodeId) {
732
+ return snapshot._stateInputs?.[stateNodeId];
733
+ }
1233
734
  function isAtomicStateNode(stateNode) {
1234
735
  return stateNode.type === 'atomic' || stateNode.type === 'final' || stateNode.type === 'choice';
1235
736
  }
@@ -1342,22 +843,9 @@ function isInFinalState(stateNodeSet, stateNode) {
1342
843
  }
1343
844
  const isStateId = str => str[0] === STATE_IDENTIFIER;
1344
845
  function getCandidates(stateNode, receivedEventType) {
1345
- const candidates = stateNode.transitions.get(receivedEventType);
1346
- if (candidates) {
1347
- return candidates;
1348
- }
1349
- const matchingDescriptors = [];
1350
- for (const eventDescriptor of stateNode.transitions.keys()) {
1351
- if (matchesEventDescriptor(receivedEventType, eventDescriptor)) {
1352
- matchingDescriptors.push(eventDescriptor);
1353
- }
1354
- }
1355
- matchingDescriptors.sort((a, b) => b.length - a.length);
1356
- const wildcardCandidates = [];
1357
- for (const descriptor of matchingDescriptors) {
1358
- wildcardCandidates.push(...stateNode.transitions.get(descriptor));
1359
- }
1360
- return wildcardCandidates;
846
+ const exactMatch = stateNode.transitions.get(receivedEventType);
847
+ 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));
848
+ return exactMatch ? [...exactMatch, ...wildcardCandidates] : wildcardCandidates;
1361
849
  }
1362
850
  function scheduleDelayedEvent(stateNode, event, resolveScheduledDelay) {
1363
851
  const eventType = event.type;
@@ -1396,61 +884,69 @@ function getDelayedTransitions(stateNode) {
1396
884
  }
1397
885
  }
1398
886
  }
1399
- const mutateEntryExitWithDelay = delay => {
1400
- const afterEvent = createAfterEvent(delay, stateNode.id);
1401
- return scheduleDelayedEvent(stateNode, afterEvent, x => resolveDelay(delay, x.delays, {
1402
- context: x.context,
1403
- event: x.event,
1404
- stateNode
1405
- }));
1406
- };
1407
- const delayedTransitions = [];
1408
- const addDelayedTransitions = (transitionsConfig, event, delay) => {
1409
- for (const transition of toTransitionConfigArray(transitionsConfig)) {
1410
- delayedTransitions.push({
1411
- ...transition,
1412
- event,
1413
- delay
1414
- });
1415
- }
1416
- };
887
+
888
+ // Every delayed transition — `after`, state-level `timeout`/`onTimeout`, and
889
+ // invoke-level `timeout`/`onTimeout` — has the same shape: an event raised on
890
+ // entry (and cancelled on exit) plus a transition that catches it. They
891
+ // differ only in the event raised, the transition(s) caught, and whether the
892
+ // delay resolves against the machine's `delays` map (invoke timeouts do not).
893
+ //
894
+ // `xstate.timeout.<id>` is a dedicated event so a state-level `timeout` cannot
895
+ // collide with an explicit `after` entry on the same state. Invoke timeouts
896
+ // are scheduled on the enclosing state; completion transitions cancel the
897
+ // timer separately, so it clears even when the parent state stays active.
898
+ const sources = [];
1417
899
  if (afterConfig) {
1418
- for (const delay of Object.keys(afterConfig)) {
1419
- const configTransition = afterConfig[delay];
1420
- const parsedDelay = Number.isNaN(+delay) ? delay : +delay;
1421
- const eventType = mutateEntryExitWithDelay(parsedDelay);
1422
- const resolvedDelay = getConfiguredDelayValue(parsedDelay, stateNode.machine.implementations.delays);
1423
- addDelayedTransitions(configTransition, eventType, resolvedDelay);
900
+ for (const key of Object.keys(afterConfig)) {
901
+ const delay = Number.isNaN(+key) ? key : +key;
902
+ sources.push({
903
+ event: createAfterEvent(delay, stateNode.id),
904
+ delay,
905
+ transitions: afterConfig[key],
906
+ fromDelaysMap: true
907
+ });
1424
908
  }
1425
909
  }
1426
-
1427
- // Desugar state-level `timeout` + `onTimeout` into a delayed transition.
1428
- // Uses a dedicated `xstate.timeout.<id>` event so it cannot collide with
1429
- // explicit `after` entries on the same state.
1430
910
  if (timeoutConfig !== undefined && onTimeoutConfig) {
1431
- const timeoutEvent = createTimeoutEvent(stateNode.id);
1432
- const timeoutEventType = scheduleDelayedEvent(stateNode, timeoutEvent, x => resolveDelay(timeoutConfig, x.delays, {
1433
- context: x.context,
1434
- event: x.event,
1435
- stateNode
1436
- }));
1437
- const resolvedDelay = typeof timeoutConfig === 'function' ? timeoutConfig : getConfiguredDelayValue(timeoutConfig, stateNode.machine.implementations.delays);
1438
- addDelayedTransitions(onTimeoutConfig, timeoutEventType, resolvedDelay);
911
+ sources.push({
912
+ event: createTimeoutEvent(stateNode.id),
913
+ delay: timeoutConfig,
914
+ transitions: onTimeoutConfig,
915
+ fromDelaysMap: true
916
+ });
1439
917
  }
1440
-
1441
- // Desugar invoke-level `timeout` + `onTimeout` into a delayed transition on
1442
- // the enclosing state. Completion transitions cancel this timer separately,
1443
- // so the timeout is cleared even when the parent state stays active.
1444
918
  for (const invokeDef of invokeDefs) {
1445
- const invokeTimeoutEvent = createInvokeTimeoutEvent(invokeDef.id);
1446
- const invokeTimeout = invokeDef.timeout;
1447
- const invokeTimeoutEventType = scheduleDelayedEvent(stateNode, invokeTimeoutEvent, x => resolveDelay(invokeTimeout, {}, {
919
+ sources.push({
920
+ event: createInvokeTimeoutEvent(invokeDef.id),
921
+ delay: invokeDef.timeout,
922
+ transitions: invokeDef.onTimeout,
923
+ fromDelaysMap: false
924
+ });
925
+ }
926
+
927
+ // `delay` here is the raw config value, retained only as metadata on the
928
+ // transition definition — the live delay is resolved at runtime in the
929
+ // scheduled entry action, so no eager resolution is needed.
930
+ const delayedTransitions = [];
931
+ for (const {
932
+ event,
933
+ delay,
934
+ transitions,
935
+ fromDelaysMap
936
+ } of sources) {
937
+ const eventType = scheduleDelayedEvent(stateNode, event, x => resolveDelay(delay, fromDelaysMap ? x.delays : {}, {
1448
938
  context: x.context,
1449
939
  event: x.event,
1450
- stateNode
940
+ stateNode,
941
+ input: x.input
1451
942
  }));
1452
- const invokeOnTimeout = invokeDef.onTimeout;
1453
- addDelayedTransitions(invokeOnTimeout, invokeTimeoutEventType, invokeTimeout);
943
+ for (const transition of toTransitionConfigArray(transitions)) {
944
+ delayedTransitions.push({
945
+ ...transition,
946
+ event: eventType,
947
+ delay
948
+ });
949
+ }
1454
950
  }
1455
951
  return delayedTransitions.map(delayedTransition => ({
1456
952
  ...formatTransition(stateNode, delayedTransition.event, delayedTransition),
@@ -1512,30 +1008,13 @@ function formatRouteTransitions(rootStateNode) {
1512
1008
  }
1513
1009
  return;
1514
1010
  }
1515
- if ('$unserializable' in routeConfig) {
1516
- throw new Error(`State "${sn.id}" has a route that is not serializable. Re-provide the route function when reviving this machine.`);
1517
- }
1518
-
1519
- // Object-form route. A `guard` (string or function) on the object is
1520
- // only produced by the JSON layer (createMachineFromConfig) —
1521
- // authoring uses the function form above.
1522
- const userGuard = routeConfig.guard;
1011
+ const {
1012
+ guard: _guard,
1013
+ ...routeOptions
1014
+ } = routeConfig;
1523
1015
  const transition = {
1524
- ...routeConfig,
1525
- guard: userGuard ? args => {
1526
- if (!routeMatches(args)) return false;
1527
- if (typeof userGuard === 'function') return userGuard(args);
1528
- if (typeof userGuard === 'string') {
1529
- const guardImpl = args.guards?.[userGuard];
1530
- if (!guardImpl) {
1531
- // A typo'd guard name must fail loudly — silently passing
1532
- // would allow the route and corrupt machine behavior.
1533
- throw new Error(`Guard '${userGuard}' is not implemented in machine '${rootStateNode.machine.id}'. Available guards: ${Object.keys(args.guards ?? {}).map(key => `'${key}'`).join(', ') || '(none)'}.` );
1534
- }
1535
- return guardImpl(args);
1536
- }
1537
- return true;
1538
- } : routeMatches,
1016
+ ...routeOptions,
1017
+ guard: routeMatches,
1539
1018
  target: `#${routeId}`
1540
1019
  };
1541
1020
  routeTransitions.push(formatTransition(rootStateNode, 'xstate.route', transition));
@@ -1574,7 +1053,7 @@ function resolveTarget(stateNode, targets) {
1574
1053
  const targetStateNode = getStateNodeByPath(stateNode.parent, resolvedTarget);
1575
1054
  return targetStateNode;
1576
1055
  } catch (err) {
1577
- throw new Error(`Invalid transition definition for state node '${stateNode.id}':\n${err.message}`);
1056
+ throw new Error(`Invalid transition definition for state node '${stateNode.id}':\n${err.message}` );
1578
1057
  }
1579
1058
  } else {
1580
1059
  throw new Error(`Invalid target: "${target}" is not a valid target from the root node. Did you mean ".${target}"?` );
@@ -1584,6 +1063,14 @@ function resolveTarget(stateNode, targets) {
1584
1063
  function resolveHistoryDefaultTransition(stateNode) {
1585
1064
  const normalizedTarget = normalizeTarget(stateNode.config.target);
1586
1065
  if (!normalizedTarget) {
1066
+ if (stateNode.parent.type === 'parallel') {
1067
+ return {
1068
+ target: [stateNode.parent],
1069
+ source: stateNode,
1070
+ reenter: false,
1071
+ eventType: ''
1072
+ };
1073
+ }
1587
1074
  return stateNode.parent.initial;
1588
1075
  }
1589
1076
  return {
@@ -1625,12 +1112,9 @@ function getStateNode(stateNode, stateKey) {
1625
1112
  if (isStateId(stateKey)) {
1626
1113
  return stateNode.machine.getStateNodeById(stateKey);
1627
1114
  }
1628
- if (!stateNode.states) {
1629
- throw new Error(`Unable to retrieve child state '${stateKey}' from '${stateNode.id}'; no child states exist.`);
1630
- }
1631
- const result = stateNode.states[stateKey];
1115
+ const result = stateNode.states?.[stateKey];
1632
1116
  if (!result) {
1633
- throw new Error(`Child state '${stateKey}' does not exist on '${stateNode.id}'`);
1117
+ throw new Error(!stateNode.states ? `Unable to retrieve child state '${stateKey}' from '${stateNode.id}'; no child states exist.` : `Child state '${stateKey}' does not exist on '${stateNode.id}'`);
1634
1118
  }
1635
1119
  return result;
1636
1120
  }
@@ -1771,6 +1255,11 @@ function removeConflictingTransitions(enabledTransitions, stateNodeSet, snapshot
1771
1255
  if (hasIntersection(getExitSet(t1), getExitSet(t2))) {
1772
1256
  if (isDescendant(t1.source, t2.source)) {
1773
1257
  transitionsToRemove.add(t2);
1258
+ } else if (t2.source.type === 'final' && t1.source.type !== 'final') {
1259
+ // A transition sourced in a final state yields to a conflicting
1260
+ // transition from a live state, so a done region doesn't keep
1261
+ // consuming events its siblings can still handle.
1262
+ transitionsToRemove.add(t2);
1774
1263
  } else {
1775
1264
  t1Preempted = true;
1776
1265
  break;
@@ -1790,7 +1279,9 @@ function getEffectiveTargetStates(transition, snapshot, event, actorScope) {
1790
1279
  const historyValue = snapshot.historyValue;
1791
1280
  const {
1792
1281
  targets
1793
- } = getTransitionResult(transition, snapshot, event, actorScope);
1282
+ } = getTransitionResult(transition, snapshot, event, actorScope, {
1283
+ resolveActions: false
1284
+ });
1794
1285
  if (!targets) {
1795
1286
  return [];
1796
1287
  }
@@ -1812,19 +1303,44 @@ function getEffectiveTargetStates(transition, snapshot, event, actorScope) {
1812
1303
  }
1813
1304
  return [...targetSet];
1814
1305
  }
1306
+
1307
+ /**
1308
+ * Narrows a parallel domain to the single child region that contains every
1309
+ * effective target, so that a transition confined to one region does not exit
1310
+ * (and reset) its sibling regions.
1311
+ */
1312
+ function narrowParallelDomain(domain, targetStates) {
1313
+ let narrowed = domain;
1314
+ while (narrowed.type === 'parallel') {
1315
+ const region = getChildren(narrowed).find(child => targetStates.every(target => target === child || isDescendant(target, child)));
1316
+ if (!region) {
1317
+ break;
1318
+ }
1319
+ narrowed = region;
1320
+ }
1321
+ return narrowed;
1322
+ }
1815
1323
  function getTransitionDomain(transition, snapshot, event, actorScope) {
1816
1324
  const targetStates = getEffectiveTargetStates(transition, snapshot, event, actorScope);
1817
1325
  const {
1818
1326
  reenter
1819
- } = getTransitionResult(transition, snapshot, event, actorScope);
1820
- if (!reenter && targetStates.every(target => target === transition.source || isDescendant(target, transition.source))) {
1821
- return transition.source;
1327
+ } = getTransitionResult(transition, snapshot, event, actorScope, {
1328
+ resolveActions: false
1329
+ });
1330
+ if (targetStates.every(target => target === transition.source || isDescendant(target, transition.source))) {
1331
+ // Targets are contained within the source. A reentering transition
1332
+ // exits and reenters the source itself; otherwise the domain narrows
1333
+ // past parallel nodes so sibling regions are left untouched.
1334
+ return reenter ? transition.source : narrowParallelDomain(transition.source, targetStates);
1822
1335
  }
1823
1336
  const [head, ...tail] = targetStates.concat(transition.source);
1824
1337
  // Find the least common ancestor (LCA) of the source and effective targets.
1825
1338
  for (const ancestor of getProperAncestors(head, undefined)) {
1826
1339
  if (tail.every(sn => isDescendant(sn, ancestor))) {
1827
- return ancestor;
1340
+ // A cross-region transition (source in one parallel region, targets in
1341
+ // another) only exits the region containing its targets; the source
1342
+ // region and other sibling regions stay put unless it reenters.
1343
+ return reenter ? ancestor : narrowParallelDomain(ancestor, targetStates);
1828
1344
  }
1829
1345
  }
1830
1346
 
@@ -1832,17 +1348,20 @@ function getTransitionDomain(transition, snapshot, event, actorScope) {
1832
1348
  if (reenter) {
1833
1349
  return;
1834
1350
  }
1835
- return transition.source.machine.root;
1351
+ return narrowParallelDomain(transition.source.machine.root, targetStates);
1836
1352
  }
1837
1353
  function computeExitSet(transitions, stateNodeSet, snapshot, event, actorScope) {
1838
1354
  const statesToExit = new Set();
1839
1355
  for (const transition of transitions) {
1840
1356
  const {
1841
- targets
1842
- } = getTransitionResult(transition, snapshot, event, actorScope);
1357
+ targets,
1358
+ reenter
1359
+ } = getTransitionResult(transition, snapshot, event, actorScope, {
1360
+ resolveActions: false
1361
+ });
1843
1362
  if (targets?.length) {
1844
1363
  const domain = getTransitionDomain(transition, snapshot, event, actorScope);
1845
- if (transition.reenter && transition.source === domain) {
1364
+ if (reenter && transition.source === domain) {
1846
1365
  statesToExit.add(domain);
1847
1366
  }
1848
1367
  for (const stateNode of stateNodeSet) {
@@ -1866,16 +1385,11 @@ function initialMicrostep(root, preInitialState, actorScope, initEvent, internal
1866
1385
 
1867
1386
  /** https://www.w3.org/TR/scxml/#microstepProcedure */
1868
1387
  function microstep(transitions, currentSnapshot, actorScope, event, isInitial, internalQueue) {
1869
- const actions = [];
1388
+ const executableActions = [];
1870
1389
  if (!transitions.length) {
1871
- return [currentSnapshot, actions];
1390
+ return [currentSnapshot, executableActions];
1872
1391
  }
1873
- const originalExecutor = actorScope.actionExecutor;
1874
- actorScope.actionExecutor = action => {
1875
- actions.push(action);
1876
- originalExecutor(action);
1877
- };
1878
- try {
1392
+ {
1879
1393
  const mutStateNodeSet = new Set(currentSnapshot._nodes);
1880
1394
  let historyValue = currentSnapshot.historyValue;
1881
1395
  const originalContext = currentSnapshot.context;
@@ -1896,13 +1410,13 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1896
1410
  children,
1897
1411
  system: actorScope.system,
1898
1412
  actions: currentSnapshot.machine.implementations.actions,
1899
- actors: currentSnapshot.machine.implementations.actors,
1413
+ actorSources: currentSnapshot.machine.implementations.actorSources,
1900
1414
  guards: currentSnapshot.machine.implementations.guards,
1901
1415
  delays: currentSnapshot.machine.implementations.delays,
1902
1416
  input
1903
1417
  }, enqueue);
1904
- if (res?.context) {
1905
- updatedContext = res.context;
1418
+ if (res?.context !== undefined) {
1419
+ updatedContext = mergeContextPatch(context, res.context);
1906
1420
  }
1907
1421
  return [actions, updatedContext, internalEvents];
1908
1422
  }
@@ -1938,7 +1452,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1938
1452
  }
1939
1453
  }
1940
1454
  for (const exitStateNode of statesToExit) {
1941
- const stateInput = currentSnapshot._stateInputs?.[exitStateNode.id];
1455
+ const stateInput = getStateInput(currentSnapshot, exitStateNode.id);
1942
1456
  const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, currentSnapshot.children, stateInput) : [[]];
1943
1457
  if (internalEvents?.length) {
1944
1458
  internalQueue.push(...internalEvents);
@@ -1948,7 +1462,9 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1948
1462
  context: nextContext
1949
1463
  });
1950
1464
  }
1951
- nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, exitActions);
1465
+ const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, exitActions);
1466
+ nextState = resolvedState;
1467
+ executableActions.push(...resolvedActions);
1952
1468
  for (const def of exitStateNode.invoke) {
1953
1469
  const childActor = nextState.children[def.id];
1954
1470
  if (childActor && !childActor._isExternal) {
@@ -1973,8 +1489,8 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1973
1489
  transitionActions.push(...toArray(t.actions));
1974
1490
  }
1975
1491
  const res = getCurrentTransitionResult(t);
1976
- if (res.context) {
1977
- context = res.context;
1492
+ if (res.context !== undefined) {
1493
+ context = mergeContextPatch(context, res.context);
1978
1494
  }
1979
1495
  if (res.actions) {
1980
1496
  transitionActions.push(...res.actions);
@@ -1989,17 +1505,18 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1989
1505
  const enterStates = () => {
1990
1506
  const getMachineOutput = rootCompletionNode => {
1991
1507
  const rootNode = nextState.machine.root;
1992
- if (rootNode.output === undefined) {
1993
- return;
1994
- }
1995
1508
  let completionOutput;
1996
1509
  if (rootCompletionNode.output !== undefined && rootCompletionNode.parent) {
1997
- completionOutput = resolveOutput(rootCompletionNode.output, nextState.context, event, actorScope.self);
1510
+ const rootDoneEvent = internalQueue.find(e => e.type === `xstate.done.state.${rootNode.id}`);
1511
+ completionOutput = rootDoneEvent && rootCompletionNode.parent === rootNode ? rootDoneEvent.output : resolveOutput(rootCompletionNode.output, nextState.context, event, actorScope.self, stateInputMap[rootCompletionNode.id]);
1998
1512
  } else if (rootCompletionNode.type === 'parallel') {
1999
1513
  const parallelDoneType = `xstate.done.state.${rootCompletionNode.id}`;
2000
1514
  const parallelDoneEvent = internalQueue.find(e => e.type === parallelDoneType);
2001
1515
  completionOutput = parallelDoneEvent?.output;
2002
1516
  }
1517
+ if (rootNode.output === undefined) {
1518
+ return rootCompletionNode.parent === rootNode ? completionOutput : undefined;
1519
+ }
2003
1520
  return resolveOutput(rootNode.output, nextState.context, createDoneStateEvent(rootCompletionNode.id, completionOutput), actorScope.self);
2004
1521
  };
2005
1522
  const statesToEnter = new Set();
@@ -2076,7 +1593,9 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2076
1593
  const {
2077
1594
  targets,
2078
1595
  reenter
2079
- } = getCurrentTransitionResult(transition);
1596
+ } = getTransitionResult(transition, currentSnapshot, event, actorScope, {
1597
+ resolveActions: false
1598
+ });
2080
1599
  for (const targetNode of targets ?? []) {
2081
1600
  if (!isHistoryNode(targetNode) && (transition.source !== targetNode || transition.source !== domain || reenter)) {
2082
1601
  statesToEnter.add(targetNode);
@@ -2092,6 +1611,12 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2092
1611
  }
2093
1612
  addAncestorStatesToEnter(ancestors, !transition.source.parent && reenter ? undefined : domain);
2094
1613
  }
1614
+ if (reenter && domain === transition.source) {
1615
+ // A reentering transition whose domain is its own source exits and
1616
+ // reenters the source, so the source must be entered explicitly —
1617
+ // it has no ancestor inside the domain to add it.
1618
+ statesToEnter.add(transition.source);
1619
+ }
2095
1620
  }
2096
1621
  if (isInitial) {
2097
1622
  statesForDefaultEntry.add(currentSnapshot.machine.root);
@@ -2099,14 +1624,18 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2099
1624
  const stateInputMap = {
2100
1625
  ...currentSnapshot._stateInputs
2101
1626
  };
1627
+ let stateInputsChanged = false;
2102
1628
  for (const transition of filteredTransitions) {
2103
1629
  const {
2104
1630
  targets,
2105
1631
  input
2106
- } = getCurrentTransitionResult(transition);
1632
+ } = getTransitionResult(transition, currentSnapshot, event, actorScope, {
1633
+ resolveActions: false
1634
+ });
2107
1635
  if (input && targets) {
2108
1636
  for (const targetNode of targets) {
2109
1637
  stateInputMap[targetNode.id] = input;
1638
+ stateInputsChanged = true;
2110
1639
  }
2111
1640
  }
2112
1641
  }
@@ -2123,7 +1652,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2123
1652
  let src = invokeDef.logic;
2124
1653
  if (typeof src === 'function') {
2125
1654
  src = src({
2126
- actors: currentSnapshot.machine.implementations.actors,
1655
+ actorSources: currentSnapshot.machine.implementations.actorSources,
2127
1656
  context: nextState.context,
2128
1657
  event,
2129
1658
  self: actorScope.self
@@ -2131,12 +1660,13 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2131
1660
  }
2132
1661
  const logic = typeof src === 'string' ? resolveReferencedActor(currentSnapshot.machine, src) : src;
2133
1662
  if (!logic) {
2134
- throw new Error(`Actor logic '${typeof src === 'string' ? src : 'inline'}' not implemented in machine '${currentSnapshot.machine.id}'`);
1663
+ throw new Error(`Actor logic '${typeof src === 'string' ? src : 'inline'}' not implemented in machine '${currentSnapshot.machine.id}'` );
2135
1664
  }
2136
1665
  const input = typeof invokeDef.input === 'function' ? invokeDef.input({
2137
1666
  self: actorScope.self,
2138
1667
  context: nextState.context,
2139
- event
1668
+ event,
1669
+ output: getEventOutput(event)
2140
1670
  }) : invokeDef.input;
2141
1671
  const actor = createActor(logic, {
2142
1672
  ...invokeDef,
@@ -2145,7 +1675,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2145
1675
  syncSnapshot: !!invokeDef.onSnapshot
2146
1676
  });
2147
1677
  actions.push({
2148
- action: builtInActions['@xstate.start'],
1678
+ action: builtInActions['@xstate.spawn'],
2149
1679
  args: [actor]
2150
1680
  });
2151
1681
  if (invokeDef.id) {
@@ -2157,18 +1687,23 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2157
1687
  children
2158
1688
  });
2159
1689
  }
2160
- let context;
1690
+ let context = nextState.context;
1691
+ let contextChangedByEntry = false;
2161
1692
  const stateInput = stateInputMap[stateNodeToEnter.id];
2162
1693
  if (stateNodeToEnter.entry) {
2163
- const [resultActions, nextContext, nextInternalEvents] = getStateActionsAndContext(stateNodeToEnter.entry, nextState.context, children, stateInput);
1694
+ const [resultActions, nextContext, nextInternalEvents] = getStateActionsAndContext(stateNodeToEnter.entry, context, children, stateInput);
2164
1695
  actions.push(...resultActions);
2165
1696
  if (nextInternalEvents?.length) {
2166
1697
  internalQueue.push(...nextInternalEvents);
2167
1698
  }
2168
1699
  if (nextContext) {
2169
1700
  context = nextContext;
1701
+ contextChangedByEntry = true;
2170
1702
  }
2171
1703
  }
1704
+ if (contextChangedByEntry) {
1705
+ nextState.context = context;
1706
+ }
2172
1707
  if (statesForDefaultEntry.has(stateNodeToEnter)) {
2173
1708
  const {
2174
1709
  actions: initialActions,
@@ -2180,13 +1715,14 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2180
1715
  if (initialInput && stateNodeToEnter.initial?.target) {
2181
1716
  for (const targetNode of stateNodeToEnter.initial.target) {
2182
1717
  stateInputMap[targetNode.id] = initialInput;
1718
+ stateInputsChanged = true;
2183
1719
  }
2184
1720
  }
2185
1721
  }
2186
- nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, actions);
2187
- if (context) {
2188
- nextState.context = context;
2189
- }
1722
+ const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, actions);
1723
+ nextState = resolvedState;
1724
+ actions.length = 0;
1725
+ executableActions.push(...resolvedActions);
2190
1726
  if (stateNodeToEnter.type !== 'final') {
2191
1727
  continue;
2192
1728
  }
@@ -2194,14 +1730,14 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2194
1730
  let ancestorMarker = parent?.type === 'parallel' ? parent : parent?.parent;
2195
1731
  let rootCompletionNode = ancestorMarker || stateNodeToEnter;
2196
1732
  if (parent?.type === 'compound') {
2197
- internalQueue.push(createDoneStateEvent(parent.id, stateNodeToEnter.output !== undefined ? resolveOutput(stateNodeToEnter.output, nextState.context, event, actorScope.self) : undefined));
1733
+ internalQueue.push(createDoneStateEvent(parent.id, stateNodeToEnter.output !== undefined ? resolveOutput(stateNodeToEnter.output, nextState.context, event, actorScope.self, stateInput) : undefined));
2198
1734
  }
2199
1735
  while (ancestorMarker?.type === 'parallel' && !completedNodes.has(ancestorMarker) && isInFinalState(mutStateNodeSet, ancestorMarker)) {
2200
1736
  completedNodes.add(ancestorMarker);
2201
1737
  const regionOutput = {};
2202
1738
  for (const region of getChildren(ancestorMarker)) {
2203
1739
  if (region.type === 'final') {
2204
- regionOutput[region.key] = region.output !== undefined ? resolveOutput(region.output, nextState.context, event, actorScope.self) : undefined;
1740
+ regionOutput[region.key] = region.output !== undefined ? resolveOutput(region.output, nextState.context, event, actorScope.self, stateInputMap[region.id]) : undefined;
2205
1741
  continue;
2206
1742
  }
2207
1743
  if (region.type === 'parallel') {
@@ -2211,7 +1747,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2211
1747
  continue;
2212
1748
  }
2213
1749
  const finalChild = getChildren(region).find(s => s.type === 'final' && mutStateNodeSet.has(s));
2214
- regionOutput[region.key] = finalChild?.output !== undefined ? resolveOutput(finalChild.output, nextState.context, event, actorScope.self) : undefined;
1750
+ regionOutput[region.key] = finalChild?.output !== undefined ? resolveOutput(finalChild.output, nextState.context, event, actorScope.self, stateInputMap[finalChild.id]) : undefined;
2215
1751
  }
2216
1752
  internalQueue.push(createDoneStateEvent(ancestorMarker.id, regionOutput));
2217
1753
  rootCompletionNode = ancestorMarker;
@@ -2225,8 +1761,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2225
1761
  output: getMachineOutput(rootCompletionNode)
2226
1762
  });
2227
1763
  }
2228
- const inputChanged = JSON.stringify(stateInputMap) !== JSON.stringify(currentSnapshot._stateInputs || {});
2229
- if (inputChanged) {
1764
+ if (stateInputsChanged) {
2230
1765
  nextState = cloneMachineSnapshot(nextState, {
2231
1766
  _stateInputs: stateInputMap
2232
1767
  });
@@ -2234,7 +1769,9 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2234
1769
  };
2235
1770
 
2236
1771
  // Execute transition content
2237
- nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, transitionActions);
1772
+ const [resolvedTransitionState, transitionExecutableActions] = resolveActionsWithContext(nextState, event, actorScope, transitionActions);
1773
+ nextState = resolvedTransitionState;
1774
+ executableActions.push(...transitionExecutableActions);
2238
1775
  if (context && context !== currentSnapshot.context) {
2239
1776
  nextState = cloneMachineSnapshot(nextState, {
2240
1777
  context
@@ -2249,7 +1786,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2249
1786
  const nextStateNodesToExit = nextStateNodes.sort((a, b) => b.order - a.order);
2250
1787
  nextStateNodesToExit.forEach(stateNode => {
2251
1788
  if (stateNode.exit) {
2252
- const stateInput = nextState._stateInputs?.[stateNode.id];
1789
+ const stateInput = getStateInput(nextState, stateNode.id);
2253
1790
  const [exitActions,, nextInternalEvents] = getStateActionsAndContext(stateNode.exit, nextState.context, nextState.children, stateInput);
2254
1791
  allExitActions.push(...exitActions);
2255
1792
  if (nextInternalEvents?.length) {
@@ -2257,22 +1794,22 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2257
1794
  }
2258
1795
  }
2259
1796
  });
2260
- nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, allExitActions);
1797
+ const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, allExitActions);
1798
+ nextState = resolvedState;
1799
+ executableActions.push(...resolvedActions);
2261
1800
  }
2262
1801
  if (historyValue === currentSnapshot.historyValue && currentSnapshot._nodes.length === mutStateNodeSet.size && currentSnapshot._nodes.every(node => mutStateNodeSet.has(node))) {
2263
1802
  // If context was changed (e.g. by entry actions during self-transition),
2264
1803
  // clone to ensure reference inequality for eventless transition re-evaluation
2265
1804
  if (nextState.context !== originalContext) {
2266
- return [cloneMachineSnapshot(nextState), actions];
1805
+ return [cloneMachineSnapshot(nextState), executableActions];
2267
1806
  }
2268
- return [nextState, actions];
1807
+ return [nextState, executableActions];
2269
1808
  }
2270
1809
  return [cloneMachineSnapshot(nextState, {
2271
1810
  _nodes: nextStateNodes,
2272
1811
  historyValue
2273
- }), actions];
2274
- } finally {
2275
- actorScope.actionExecutor = originalExecutor;
1812
+ }), executableActions];
2276
1813
  }
2277
1814
  }
2278
1815
 
@@ -2280,29 +1817,33 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2280
1817
  * Gets the transition result for a given transition without executing the
2281
1818
  * transition.
2282
1819
  */
2283
- function getTransitionResult(transition, snapshot, event, actorScope) {
1820
+ function getTransitionResult(transition, snapshot, event, actorScope, options) {
1821
+ const transitionArgs = {
1822
+ context: snapshot.context,
1823
+ event,
1824
+ output: getEventOutput(event),
1825
+ value: snapshot.value,
1826
+ children: snapshot.children,
1827
+ system: actorScope.system,
1828
+ parent: actorScope.self._parent,
1829
+ self: actorScope.self,
1830
+ actions: snapshot.machine.implementations.actions,
1831
+ actorSources: snapshot.machine.implementations.actorSources,
1832
+ guards: snapshot.machine.implementations.guards,
1833
+ delays: snapshot.machine.implementations.delays,
1834
+ input: getStateInput(snapshot, transition.source.id)
1835
+ };
2284
1836
  if (transition.to) {
2285
1837
  const actions = [];
2286
1838
  const internalEvents = [];
2287
- const enqueue = createTransitionEnqueue(actorScope, actions, internalEvents);
2288
- const res = transition.to({
2289
- context: snapshot.context,
2290
- event,
2291
- value: snapshot.value,
2292
- children: snapshot.children,
2293
- parent: actorScope.self._parent,
2294
- self: actorScope.self,
2295
- actions: snapshot.machine.implementations.actions,
2296
- actors: snapshot.machine.implementations.actors,
2297
- guards: snapshot.machine.implementations.guards,
2298
- delays: snapshot.machine.implementations.delays
2299
- }, enqueue);
1839
+ const enqueue = createTransitionEnqueue(actorScope, actions, internalEvents, false, options?.resolveActions ?? true);
1840
+ const res = transition.to(transitionArgs, enqueue);
2300
1841
  const targets = res?.target ? resolveTarget(transition.source, toArray(res.target)) : undefined;
2301
- // Resolve input for .to transitions
2302
- const resolvedInput = typeof transition.input === 'function' ? transition.input({
1842
+ const resolvedInput = res?.input ?? (typeof transition.input === 'function' ? transition.input({
2303
1843
  context: snapshot.context,
2304
- event
2305
- }) : transition.input;
1844
+ event,
1845
+ output: getEventOutput(event)
1846
+ }) : transition.input);
2306
1847
  return {
2307
1848
  targets: targets,
2308
1849
  context: res?.context,
@@ -2316,11 +1857,13 @@ function getTransitionResult(transition, snapshot, event, actorScope) {
2316
1857
  // Resolve input for regular transitions
2317
1858
  const resolvedInput = typeof transition.input === 'function' ? transition.input({
2318
1859
  context: snapshot.context,
2319
- event
1860
+ event,
1861
+ output: getEventOutput(event)
2320
1862
  }) : transition.input;
1863
+ const resolvedContext = typeof transition.context === 'function' ? transition.context(transitionArgs) : transition.context;
2321
1864
  return {
2322
1865
  targets: transition.target,
2323
- context: undefined,
1866
+ context: resolvedContext,
2324
1867
  reenter: transition.reenter,
2325
1868
  actions: undefined,
2326
1869
  internalEvents: undefined,
@@ -2356,7 +1899,7 @@ function macrostep(snapshot, event, actorScope, internalQueue) {
2356
1899
  // Determine the next state based on the next microstep
2357
1900
  if (nextEvent.type !== XSTATE_INIT) {
2358
1901
  const currentEvent = nextEvent;
2359
- const isErr = isErrorActorEvent(currentEvent);
1902
+ const isErr = isErrorEvent(currentEvent);
2360
1903
  const transitions = nextSnapshot.machine.getTransitionData(nextSnapshot, currentEvent, actorScope.self);
2361
1904
  if (isErr && !transitions.length) {
2362
1905
  // TODO: we should likely only allow transitions selected by very explicit descriptors
@@ -2427,31 +1970,13 @@ function resolveStateValue(rootNode, stateValue) {
2427
1970
  const allStateNodes = getAllStateNodes(getStateNodes(rootNode, stateValue));
2428
1971
  return getStateValue(rootNode, allStateNodes);
2429
1972
  }
2430
- function createEnqueueObject(props, action) {
2431
- const enqueueFn = (fn, ...args) => {
2432
- action(fn, ...args);
2433
- };
2434
- Object.assign(enqueueFn, {
2435
- cancel: () => {},
2436
- emit: () => {},
2437
- log: () => {},
2438
- raise: () => {},
2439
- spawn: () => ({}),
2440
- sendTo: () => {},
2441
- stop: () => {},
2442
- listen: () => ({}),
2443
- subscribeTo: () => ({}),
2444
- ...props
2445
- });
2446
- return enqueueFn;
2447
- }
2448
1973
  function hasEffect(transition, context, event, snapshot, self) {
2449
1974
  if (transition.to) {
2450
- return transitionToHasEffect(transition.to, context, event, snapshot, self, snapshot.machine.implementations);
1975
+ return transitionToHasEffect(transition.to, context, event, snapshot, self, snapshot.machine.implementations, transition.source.id);
2451
1976
  }
2452
1977
  return false;
2453
1978
  }
2454
- function transitionToHasEffect(transitionTo, context, event, snapshot, self, implementations) {
1979
+ function transitionToHasEffect(transitionTo, context, event, snapshot, self, implementations, sourceId) {
2455
1980
  let hasEffect = false;
2456
1981
  let res;
2457
1982
  try {
@@ -2462,16 +1987,19 @@ function transitionToHasEffect(transitionTo, context, event, snapshot, self, imp
2462
1987
  res = transitionTo({
2463
1988
  context,
2464
1989
  event,
1990
+ output: getEventOutput(event),
2465
1991
  self,
1992
+ system: self.system,
2466
1993
  value: snapshot.value,
2467
1994
  children: snapshot.children,
2468
1995
  parent: {
2469
1996
  send: triggerEffect
2470
1997
  },
2471
1998
  actions: implementations.actions,
2472
- actors: implementations.actors,
1999
+ actorSources: implementations.actorSources,
2473
2000
  guards: implementations.guards,
2474
- delays: implementations.delays
2001
+ delays: implementations.delays,
2002
+ input: getStateInput(snapshot, sourceId)
2475
2003
  }, createEnqueueObject({
2476
2004
  emit: triggerEffect,
2477
2005
  cancel: triggerEffect,
@@ -2524,38 +2052,22 @@ function evaluateCandidate(candidate, event, snapshot, stateNode, self) {
2524
2052
  const guardArgs = {
2525
2053
  context: snapshot.context,
2526
2054
  event,
2055
+ output: getEventOutput(event),
2527
2056
  self,
2528
2057
  parent: self._parent,
2529
2058
  children: snapshot.children,
2530
2059
  actions: stateNode.machine.implementations.actions,
2531
- actors: stateNode.machine.implementations.actors,
2060
+ actorSources: stateNode.machine.implementations.actorSources,
2532
2061
  guards: stateNode.machine.implementations.guards,
2533
2062
  delays: stateNode.machine.implementations.delays,
2534
2063
  _snapshot: snapshot
2535
2064
  };
2536
- const guardConfig = candidate.guard;
2537
- const guardParams = typeof guardConfig?.params === 'function' ? guardConfig.params({
2538
- context: snapshot.context,
2539
- event
2540
- }) : guardConfig?.params;
2541
- let guardPassed = true;
2542
- if (typeof guardConfig === 'function') {
2543
- guardPassed = guardConfig(guardArgs, guardParams);
2544
- } else if (typeof guardConfig?.type === 'string') {
2545
- const guardImpl = stateNode.machine.implementations.guards[guardConfig.type];
2546
- if (!guardImpl) {
2547
- // A typo'd guard name must fail loudly — silently passing would
2548
- // take the guarded transition and corrupt machine behavior.
2549
- throw new Error(`Guard '${guardConfig.type}' is not implemented in machine '${stateNode.machine.id}'. Available guards: ${Object.keys(stateNode.machine.implementations.guards).map(key => `'${key}'`).join(', ') || '(none)'}.` );
2550
- }
2551
- guardPassed = guardImpl(guardArgs, guardParams);
2552
- }
2553
- if (!guardPassed) {
2065
+ if (!candidate.guard(guardArgs)) {
2554
2066
  return false;
2555
2067
  }
2556
2068
  }
2557
2069
  if (candidate.to) {
2558
- return transitionToHasEffect(candidate.to, snapshot.context, event, snapshot, self, stateNode.machine.implementations);
2070
+ return transitionToHasEffect(candidate.to, snapshot.context, event, snapshot, self, stateNode.machine.implementations, candidate.source.id);
2559
2071
  }
2560
2072
  return true;
2561
2073
  }
@@ -2563,29 +2075,6 @@ function evaluateCandidate(candidate, event, snapshot, stateNode, self) {
2563
2075
  function isMachineSnapshot(value) {
2564
2076
  return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
2565
2077
  }
2566
- let emptyCanActor;
2567
- let emptyCanActorScope;
2568
- function getEmptyCanActor() {
2569
- return emptyCanActor ??= createEmptyActor();
2570
- }
2571
- function getEmptyCanActorScope() {
2572
- if (emptyCanActorScope) {
2573
- return emptyCanActorScope;
2574
- }
2575
- const actor = getEmptyCanActor();
2576
- emptyCanActorScope = {
2577
- self: actor,
2578
- logger: () => {},
2579
- id: '',
2580
- sessionId: '',
2581
- defer: () => {},
2582
- system: actor.system,
2583
- stopChild: () => {},
2584
- emit: () => {},
2585
- actionExecutor: () => {}
2586
- };
2587
- return emptyCanActorScope;
2588
- }
2589
2078
  const machineSnapshotMatches = function matches(testValue) {
2590
2079
  return matchesState(testValue, this.value);
2591
2080
  };
@@ -2596,24 +2085,10 @@ const machineSnapshotCan = function can(event) {
2596
2085
  if (!this.machine) {
2597
2086
  console.warn(`state.can(...) used outside of a machine-created State object; this will always return false.`);
2598
2087
  }
2599
- const emptyActor = getEmptyCanActor();
2600
- const emptyActorScope = getEmptyCanActorScope();
2601
- const transitionData = this.machine.getTransitionData(this, event, emptyActor);
2602
- if (!transitionData?.length) {
2603
- return false;
2604
- }
2605
2088
 
2606
- // Check that at least one transition is not forbidden
2607
- for (const transition of transitionData) {
2608
- if (transition.target !== undefined) {
2609
- return true;
2610
- }
2611
- const res = getTransitionResult(transition, this, event, emptyActorScope);
2612
- if (res.targets?.length || res.context || hasEffect(transition, this.context, event, this, emptyActor)) {
2613
- return true;
2614
- }
2615
- }
2616
- return false;
2089
+ // The dry-run logic lives on the machine so that non-machine bundles
2090
+ // (e.g. `createFSM`) don't pay for the transition-resolution machinery.
2091
+ return this.machine?._canTransition(this, event) ?? false;
2617
2092
  };
2618
2093
  const machineSnapshotToJSON = function toJSON() {
2619
2094
  const {
@@ -2744,7 +2219,7 @@ function getPersistedSnapshot(snapshot, options) {
2744
2219
  childrenJson[id] = {
2745
2220
  snapshot: child.getPersistedSnapshot(options),
2746
2221
  src: child.src,
2747
- systemId: child.systemId,
2222
+ registryKey: child.registryKey,
2748
2223
  syncSnapshot: child._syncSnapshot
2749
2224
  };
2750
2225
  }
@@ -2754,6 +2229,9 @@ function getPersistedSnapshot(snapshot, options) {
2754
2229
  children: childrenJson,
2755
2230
  historyValue: serializeHistoryValue(jsonValues.historyValue)
2756
2231
  };
2232
+ if (_stateInputs && Object.keys(_stateInputs).length > 0) {
2233
+ persisted.stateInputs = _stateInputs;
2234
+ }
2757
2235
  if (machine.version !== undefined) {
2758
2236
  persisted.version = machine.version;
2759
2237
  }
@@ -2889,24 +2367,38 @@ function toArray(value) {
2889
2367
  }
2890
2368
  return toArrayStrict(value);
2891
2369
  }
2892
- function resolveOutput(mapper, context, event, self) {
2370
+ function resolveOutput(mapper, context, event, self, input) {
2893
2371
  if (typeof mapper === 'function') {
2894
- return mapper({
2372
+ const outputMapper = mapper;
2373
+ const args = {
2895
2374
  context,
2896
2375
  event,
2897
- self
2898
- });
2376
+ output: getEventOutput(event),
2377
+ self,
2378
+ input
2379
+ };
2380
+ return outputMapper(args);
2899
2381
  }
2900
2382
  if (!!mapper && typeof mapper === 'object' && Object.values(mapper).some(val => typeof val === 'function')) {
2901
2383
  console.warn(`Dynamically mapping values to individual properties is deprecated. Use a single function that returns the mapped object instead.\nFound object containing properties whose values are possibly mapping functions: ${Object.entries(mapper).filter(([, value]) => typeof value === 'function').map(([key, value]) => `\n - ${key}: ${value.toString().replace(/\n\s*/g, '')}`).join('')}`);
2902
2384
  }
2903
2385
  return mapper;
2904
2386
  }
2387
+ function getEventOutput(event) {
2388
+ if (isDoneEvent(event)) {
2389
+ const doneEvent = event;
2390
+ return doneEvent.output;
2391
+ }
2392
+ return undefined;
2393
+ }
2394
+ function isDoneEvent(event) {
2395
+ return event.type.startsWith('xstate.done.actor.') || event.type.startsWith('xstate.done.state.');
2396
+ }
2905
2397
  function isArray(value) {
2906
2398
  return Array.isArray(value);
2907
2399
  }
2908
- function isErrorActorEvent(event) {
2909
- return event.type.startsWith('xstate.error.actor');
2400
+ function isErrorEvent(event) {
2401
+ return event.type.startsWith('xstate.error.');
2910
2402
  }
2911
2403
  function toTransitionConfigArray(configLike) {
2912
2404
  return toArrayStrict(configLike).map(transitionLike => {
@@ -2944,14 +2436,14 @@ function createInvokeId(stateNodeId, index) {
2944
2436
  function resolveReferencedActor(machine, src) {
2945
2437
  const match = src.match(/^xstate\.invoke\.(\d+)\.(.*)/);
2946
2438
  if (!match) {
2947
- return machine.implementations.actors[src];
2439
+ return machine.implementations.actorSources[src];
2948
2440
  }
2949
2441
  const [, indexStr, nodeId] = match;
2950
2442
  const node = machine.getStateNodeById(nodeId);
2951
2443
  const invokeConfig = node.config.invoke;
2952
2444
  const configSrc = (Array.isArray(invokeConfig) ? invokeConfig[indexStr] : invokeConfig).src;
2953
2445
  // A referenced actor may itself be registered by name.
2954
- return typeof configSrc === 'string' ? machine.implementations.actors[configSrc] : configSrc;
2446
+ return typeof configSrc === 'string' ? machine.implementations.actorSources[configSrc] : configSrc;
2955
2447
  }
2956
2448
  function getAllOwnEventDescriptors(snapshot) {
2957
2449
  return [...new Set([...snapshot._nodes.flatMap(sn => sn.ownEvents)])];
@@ -3004,7 +2496,7 @@ function matchesEventDescriptor(eventType, descriptor) {
3004
2496
  function createScheduledEventId(actor, id) {
3005
2497
  return `${actor.sessionId}.${id}`;
3006
2498
  }
3007
- function createSystem(rootActor, options) {
2499
+ function createRuntimeSystem(rootActor, options) {
3008
2500
  let idCounter = 0;
3009
2501
  const children = new Map();
3010
2502
  const keyedActors = new Map();
@@ -3027,373 +2519,150 @@ function createSystem(rootActor, options) {
3027
2519
  collected.push({
3028
2520
  targetRef: target,
3029
2521
  targetId: target.id,
3030
- event,
3031
- delay,
3032
- id
3033
- });
3034
- };
3035
- const scheduler = {
3036
- schedule: (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
3037
- recordSent(source, target, event, delay, id);
3038
- const scheduledEvent = {
3039
- source,
3040
- target,
3041
- event,
3042
- delay,
3043
- id,
3044
- startedAt: Date.now()
3045
- };
3046
- const scheduledEventId = createScheduledEventId(source, id);
3047
- system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
3048
- const timeout = clock.setTimeout(() => {
3049
- delete timerMap[scheduledEventId];
3050
- delete system._snapshot._scheduledEvents[scheduledEventId];
3051
-
3052
- // The send was already recorded at schedule time on the sender's
3053
- // transition, so deliver without re-recording.
3054
- deliver(source, target, event);
3055
- }, delay);
3056
- timerMap[scheduledEventId] = timeout;
3057
- },
3058
- cancel: (source, id) => {
3059
- const scheduledEventId = createScheduledEventId(source, id);
3060
- const timeout = timerMap[scheduledEventId];
3061
- delete timerMap[scheduledEventId];
3062
- delete system._snapshot._scheduledEvents[scheduledEventId];
3063
- if (timeout !== undefined) {
3064
- clock.clearTimeout(timeout);
3065
- }
3066
- },
3067
- cancelAll: actor => {
3068
- for (const scheduledEventId in system._snapshot._scheduledEvents) {
3069
- const scheduledEvent = system._snapshot._scheduledEvents[scheduledEventId];
3070
- if (scheduledEvent.source === actor) {
3071
- scheduler.cancel(actor, scheduledEvent.id);
3072
- }
3073
- }
3074
- }
3075
- };
3076
- // Delivers an event to the target actor. Used by both `_relay` (which also
3077
- // records the send) and the scheduler's timer (which already recorded it).
3078
- const deliver = (source, target, event) => {
3079
- const targetMachine = target.logic;
3080
- const isInternalEvent = typeof targetMachine?.isInternalEventType === 'function' && targetMachine.isInternalEventType(event.type);
3081
- if (isInternalEvent && source !== target) {
3082
- throw new Error(`Internal event "${event.type}" cannot be sent to actor "${target.id}" from outside.`);
3083
- }
3084
-
3085
- // remember the last source for unified transition inspect event
3086
- target._lastSourceRef = source;
3087
- target._send(event);
3088
- };
3089
- const sendInspectionEvent = event => {
3090
- if (!inspectionObservers.size) {
3091
- return;
3092
- }
3093
- const resolvedInspectionEvent = {
3094
- ...event,
3095
- rootId: rootActor.sessionId
3096
- };
3097
- inspectionObservers.forEach(observer => observer.next?.(resolvedInspectionEvent));
3098
- };
3099
- const system = {
3100
- children,
3101
- reverseKeyedActors,
3102
- keyedActors,
3103
- _snapshot: {
3104
- _scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
3105
- },
3106
- _bookId: () => `x:${idCounter++}`,
3107
- _register: (sessionId, actor) => {
3108
- children.set(sessionId, actor);
3109
- return sessionId;
3110
- },
3111
- _unregister: actor => {
3112
- children.delete(actor.sessionId);
3113
- const systemId = reverseKeyedActors.get(actor);
3114
- if (systemId !== undefined) {
3115
- keyedActors.delete(systemId);
3116
- reverseKeyedActors.delete(actor);
3117
- }
3118
- },
3119
- get: systemId => {
3120
- return keyedActors.get(systemId);
3121
- },
3122
- getAll: () => {
3123
- return Object.fromEntries(keyedActors.entries());
3124
- },
3125
- _set: (systemId, actor) => {
3126
- const existing = keyedActors.get(systemId);
3127
- if (existing && existing !== actor) {
3128
- throw new Error(`Actor with system ID '${systemId}' already exists.`);
3129
- }
3130
- keyedActors.set(systemId, actor);
3131
- reverseKeyedActors.set(actor, systemId);
3132
- },
3133
- inspect: observerOrFn => {
3134
- const observer = toObserver(observerOrFn);
3135
- inspectionObservers.add(observer);
3136
- return {
3137
- unsubscribe() {
3138
- inspectionObservers.delete(observer);
3139
- }
3140
- };
3141
- },
3142
- _sendInspectionEvent: sendInspectionEvent,
3143
- _relay: (source, target, event) => {
3144
- recordSent(source, target, event);
3145
- deliver(source, target, event);
3146
- },
3147
- scheduler,
3148
- getSnapshot: () => {
3149
- return {
3150
- _scheduledEvents: {
3151
- ...system._snapshot._scheduledEvents
3152
- }
3153
- };
3154
- },
3155
- start: () => {
3156
- const scheduledEvents = system._snapshot._scheduledEvents;
3157
- system._snapshot._scheduledEvents = {};
3158
- for (const scheduledId in scheduledEvents) {
3159
- const {
3160
- source,
3161
- target,
3162
- event,
3163
- delay,
3164
- id
3165
- } = scheduledEvents[scheduledId];
3166
- scheduler.schedule(source, target, event, delay, id);
3167
- }
3168
- },
3169
- _clock: clock,
3170
- _logger: logger,
3171
- _timerStrategy: options.timers
3172
- };
3173
- return system;
3174
- }
3175
-
3176
- const effectStates = new WeakMap();
3177
- const XSTATE_LOGIC_EFFECT_RESOLVE = 'xstate.logic.effect.resolve';
3178
- const XSTATE_LOGIC_EFFECT_REJECT = 'xstate.logic.effect.reject';
3179
- const XSTATE_LOGIC_EFFECT_START = 'xstate.logic.effect.start';
3180
- function getEffectState(self) {
3181
- let state = effectStates.get(self);
3182
- if (!state) {
3183
- state = new Map();
3184
- effectStates.set(self, state);
3185
- }
3186
- return state;
3187
- }
3188
- function executeLogicEffects(effects, actorScope) {
3189
- if (!effects?.length) {
3190
- return;
3191
- }
3192
- for (const effect of effects) {
3193
- switch (effect.type) {
3194
- case 'emit':
3195
- actorScope.emit(effect.event);
3196
- break;
3197
- case 'sendBack':
3198
- {
3199
- const parent = actorScope.self._parent;
3200
- if (parent) {
3201
- actorScope.system._relay(actorScope.self, parent, effect.event);
3202
- }
3203
- break;
3204
- }
3205
- case 'raise':
3206
- actorScope.system._relay(actorScope.self, actorScope.self, effect.event);
3207
- break;
3208
- case 'effect':
3209
- {
3210
- if (!effect.key) {
3211
- const cleanup = effect.exec();
3212
- if (typeof cleanup === 'function') {
3213
- getEffectState(actorScope.self).set(Symbol(), {
3214
- cleanup
3215
- });
3216
- }
3217
- break;
3218
- }
3219
- const state = getEffectState(actorScope.self);
3220
- if (state.has(effect.key)) {
3221
- break;
3222
- }
3223
- const cleanup = effect.exec();
3224
- state.set(effect.key, {
3225
- cleanup: typeof cleanup === 'function' ? cleanup : undefined
3226
- });
3227
- break;
3228
- }
3229
- case 'cleanupEffects':
3230
- {
3231
- const state = effectStates.get(actorScope.self);
3232
- if (!state) {
3233
- break;
3234
- }
3235
- for (const {
3236
- cleanup
3237
- } of state.values()) {
3238
- cleanup?.();
3239
- }
3240
- effectStates.delete(actorScope.self);
3241
- break;
3242
- }
3243
- }
3244
- }
3245
- }
3246
- function resolveContext(context, input) {
3247
- return typeof context === 'function' ? context({
3248
- input
3249
- }) : context;
3250
- }
3251
- function createLogic(config) {
3252
- const transition = (snapshot, event, actorScope) => {
3253
- if (snapshot.status !== 'active') {
3254
- return [snapshot, []];
3255
- }
3256
- if (event.type === XSTATE_STOP) {
3257
- return [{
3258
- ...snapshot,
3259
- status: 'stopped',
3260
- input: undefined
3261
- }, [{
3262
- type: 'cleanupEffects'
3263
- }]];
3264
- }
3265
- if (event.type === XSTATE_LOGIC_EFFECT_START) {
3266
- return [{
3267
- ...snapshot,
3268
- effects: {
3269
- ...snapshot.effects,
3270
- [event.key]: {
3271
- status: 'active'
3272
- }
3273
- }
3274
- }, []];
3275
- }
3276
- if (event.type === XSTATE_LOGIC_EFFECT_RESOLVE) {
3277
- return [{
3278
- ...snapshot,
3279
- effects: {
3280
- ...snapshot.effects,
3281
- [event.key]: {
3282
- status: 'done',
3283
- output: event.output
3284
- }
3285
- }
3286
- }, []];
3287
- }
3288
- if (event.type === XSTATE_LOGIC_EFFECT_REJECT) {
3289
- return [{
3290
- ...snapshot,
3291
- effects: {
3292
- ...snapshot.effects,
3293
- [event.key]: {
3294
- status: 'error',
3295
- error: event.error
3296
- }
3297
- }
3298
- }, []];
3299
- }
3300
- const effects = [];
3301
- const trackedEffects = {};
3302
- const enqueueEffect = (key, exec) => {
3303
- if (snapshot.effects?.[key]) {
3304
- return;
3305
- }
3306
- effects.push({
3307
- type: 'effect',
3308
- key,
3309
- exec
3310
- });
3311
- trackedEffects[key] = {
3312
- status: 'active'
2522
+ event,
2523
+ delay,
2524
+ id
2525
+ });
2526
+ };
2527
+ const scheduler = {
2528
+ schedule: (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
2529
+ recordSent(source, target, event, delay, id);
2530
+ const scheduledEvent = {
2531
+ source,
2532
+ target,
2533
+ event,
2534
+ delay,
2535
+ id,
2536
+ startedAt: Date.now()
3313
2537
  };
3314
- };
3315
- const enq = {
3316
- emit: emitted => {
3317
- effects.push({
3318
- type: 'emit',
3319
- event: emitted
3320
- });
3321
- },
3322
- sendBack: sentEvent => {
3323
- effects.push({
3324
- type: 'sendBack',
3325
- event: sentEvent
3326
- });
3327
- },
3328
- raise: raisedEvent => {
3329
- effects.push({
3330
- type: 'raise',
3331
- event: raisedEvent
3332
- });
3333
- },
3334
- effect: (keyOrExec, maybeExec) => {
3335
- if (typeof keyOrExec === 'string') {
3336
- enqueueEffect(keyOrExec, maybeExec);
3337
- return;
2538
+ const scheduledEventId = createScheduledEventId(source, id);
2539
+ system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
2540
+ const timeout = clock.setTimeout(() => {
2541
+ delete timerMap[scheduledEventId];
2542
+ delete system._snapshot._scheduledEvents[scheduledEventId];
2543
+
2544
+ // The send was already recorded at schedule time on the sender's
2545
+ // transition, so deliver without re-recording.
2546
+ deliver(source, target, event);
2547
+ }, delay);
2548
+ timerMap[scheduledEventId] = timeout;
2549
+ },
2550
+ cancel: (source, id) => {
2551
+ const scheduledEventId = createScheduledEventId(source, id);
2552
+ const timeout = timerMap[scheduledEventId];
2553
+ delete timerMap[scheduledEventId];
2554
+ delete system._snapshot._scheduledEvents[scheduledEventId];
2555
+ if (timeout !== undefined) {
2556
+ clock.clearTimeout(timeout);
2557
+ }
2558
+ },
2559
+ cancelAll: actor => {
2560
+ for (const scheduledEventId in system._snapshot._scheduledEvents) {
2561
+ const scheduledEvent = system._snapshot._scheduledEvents[scheduledEventId];
2562
+ if (scheduledEvent.source === actor) {
2563
+ scheduler.cancel(actor, scheduledEvent.id);
3338
2564
  }
3339
- effects.push({
3340
- type: 'effect',
3341
- exec: keyOrExec
3342
- });
3343
2565
  }
3344
- };
3345
- const patch = config.run({
3346
- context: snapshot.context,
3347
- event,
3348
- input: snapshot.input,
3349
- system: actorScope.system,
3350
- self: actorScope.self,
3351
- emit: actorScope.emit
3352
- }, enq);
3353
- const nextSnapshot = {
3354
- ...snapshot,
3355
- ...(patch || {})
3356
- };
3357
- if ('context' in snapshot || patch?.context !== undefined) {
3358
- nextSnapshot.context = patch?.context ?? snapshot.context;
3359
2566
  }
3360
- if (patch?.effects || Object.keys(trackedEffects).length) {
3361
- nextSnapshot.effects = {
3362
- ...snapshot.effects,
3363
- ...patch?.effects,
3364
- ...trackedEffects
3365
- };
2567
+ };
2568
+ // Delivers an event to the target actor. Used by both `_relay` (which also
2569
+ // records the send) and the scheduler's timer (which already recorded it).
2570
+ const deliver = (source, target, event) => {
2571
+ const targetMachine = target.logic;
2572
+ const isInternalEvent = typeof targetMachine?.isInternalEventType === 'function' && targetMachine.isInternalEventType(event.type);
2573
+ if (isInternalEvent && source !== target) {
2574
+ throw new Error(`Internal event "${event.type}" cannot be sent to actor "${target.id}" from outside.`);
3366
2575
  }
3367
- return [nextSnapshot, effects];
2576
+
2577
+ // remember the last source for unified transition inspect event
2578
+ target._lastSourceRef = source;
2579
+ target._send(event);
3368
2580
  };
3369
- const logic = {
3370
- id: config.id,
3371
- config,
3372
- transition,
3373
- start: (snapshot, actorScope) => {
3374
- const [nextSnapshot, effects] = transition(snapshot, createInitEvent(snapshot.input), actorScope);
3375
- Object.assign(snapshot, nextSnapshot);
3376
- executeLogicEffects(effects, actorScope);
2581
+ const sendInspectionEvent = event => {
2582
+ if (!inspectionObservers.size) {
2583
+ return;
2584
+ }
2585
+ const resolvedInspectionEvent = {
2586
+ ...event,
2587
+ rootId: rootActor.sessionId
2588
+ };
2589
+ inspectionObservers.forEach(observer => observer.next?.(resolvedInspectionEvent));
2590
+ };
2591
+ const system = {
2592
+ children,
2593
+ reverseKeyedActors,
2594
+ keyedActors,
2595
+ _snapshot: {
2596
+ _scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
3377
2597
  },
3378
- getInitialSnapshot: (_, input) => {
3379
- const context = resolveContext(config.context, input);
3380
- const snapshot = {
3381
- status: 'active',
3382
- output: undefined,
3383
- error: undefined,
3384
- input
3385
- };
3386
- if (context !== undefined) {
3387
- snapshot.context = context;
2598
+ _bookId: () => `x:${idCounter++}`,
2599
+ _register: (sessionId, actor) => {
2600
+ children.set(sessionId, actor);
2601
+ return sessionId;
2602
+ },
2603
+ _unregister: actor => {
2604
+ children.delete(actor.sessionId);
2605
+ const registryKey = reverseKeyedActors.get(actor);
2606
+ if (registryKey !== undefined) {
2607
+ keyedActors.delete(registryKey);
2608
+ reverseKeyedActors.delete(actor);
3388
2609
  }
2610
+ },
2611
+ get: registryKey => {
2612
+ return keyedActors.get(registryKey);
2613
+ },
2614
+ getAll: () => {
2615
+ return Object.fromEntries(keyedActors.entries());
2616
+ },
2617
+ _set: (registryKey, actor) => {
2618
+ const existing = keyedActors.get(registryKey);
2619
+ if (existing && existing !== actor) {
2620
+ throw new Error(`Actor with registry key '${registryKey}' already exists.`);
2621
+ }
2622
+ keyedActors.set(registryKey, actor);
2623
+ reverseKeyedActors.set(actor, registryKey);
2624
+ },
2625
+ inspect: observerOrFn => {
2626
+ const observer = toObserver(observerOrFn);
2627
+ inspectionObservers.add(observer);
3389
2628
  return {
3390
- ...snapshot
2629
+ unsubscribe() {
2630
+ inspectionObservers.delete(observer);
2631
+ }
3391
2632
  };
3392
2633
  },
3393
- getPersistedSnapshot: snapshot => snapshot,
3394
- restoreSnapshot: snapshot => snapshot
2634
+ _sendInspectionEvent: sendInspectionEvent,
2635
+ _relay: (source, target, event) => {
2636
+ recordSent(source, target, event);
2637
+ deliver(source, target, event);
2638
+ },
2639
+ scheduler,
2640
+ getSnapshot: () => {
2641
+ return {
2642
+ _scheduledEvents: {
2643
+ ...system._snapshot._scheduledEvents
2644
+ }
2645
+ };
2646
+ },
2647
+ start: () => {
2648
+ const scheduledEvents = system._snapshot._scheduledEvents;
2649
+ system._snapshot._scheduledEvents = {};
2650
+ for (const scheduledId in scheduledEvents) {
2651
+ const {
2652
+ source,
2653
+ target,
2654
+ event,
2655
+ delay,
2656
+ id
2657
+ } = scheduledEvents[scheduledId];
2658
+ scheduler.schedule(source, target, event, delay, id);
2659
+ }
2660
+ },
2661
+ _clock: clock,
2662
+ _logger: logger,
2663
+ _timerStrategy: options.timers
3395
2664
  };
3396
- return logic;
2665
+ return system;
3397
2666
  }
3398
2667
 
3399
2668
  // those are needed to make JSDoc `@link` work properly
@@ -3419,6 +2688,30 @@ const defaultOptions = {
3419
2688
  },
3420
2689
  logger: console.log.bind(console)
3421
2690
  };
2691
+ function safeCall(fn, arg) {
2692
+ try {
2693
+ fn?.(arg);
2694
+ } catch (err) {
2695
+ reportUnhandledError(err);
2696
+ }
2697
+ }
2698
+ function isExecutableActionObject(effect) {
2699
+ return typeof effect === 'object' && effect !== null && 'args' in effect && 'exec' in effect;
2700
+ }
2701
+ function executeExecutableEffects(effects, actorScope) {
2702
+ if (!effects?.length) {
2703
+ return [];
2704
+ }
2705
+ const logicEffects = [];
2706
+ for (const effect of effects) {
2707
+ if (isExecutableActionObject(effect)) {
2708
+ actorScope.actionExecutor(effect);
2709
+ } else {
2710
+ logicEffects.push(effect);
2711
+ }
2712
+ }
2713
+ return logicEffects;
2714
+ }
3422
2715
 
3423
2716
  /**
3424
2717
  * An Actor is a running process that can receive events, send events and change
@@ -3457,6 +2750,7 @@ class Actor {
3457
2750
  this.logger = void 0;
3458
2751
  /** @internal */
3459
2752
  this._processingStatus = ProcessingStatus.NotStarted;
2753
+ this._forceDeferredActions = false;
3460
2754
  // Actor Ref
3461
2755
  this._parent = void 0;
3462
2756
  /** @internal */
@@ -3472,7 +2766,8 @@ class Actor {
3472
2766
  this._collectedActions = [];
3473
2767
  /** @internal Events relayed to other actors during the in-flight transition. */
3474
2768
  this._collectedSent = [];
3475
- this.systemId = void 0;
2769
+ this._initialEffects = void 0;
2770
+ this.registryKey = void 0;
3476
2771
  /** The globally unique process ID for this invocation. */
3477
2772
  this.sessionId = void 0;
3478
2773
  /** The system to which this actor belongs. */
@@ -3493,14 +2788,17 @@ class Actor {
3493
2788
  parent,
3494
2789
  syncSnapshot,
3495
2790
  id,
3496
- systemId,
2791
+ registryKey,
3497
2792
  inspect
3498
2793
  } = resolvedOptions;
3499
- this.system = parent ? parent.system : createSystem(this, {
2794
+ this.system = parent ? parent.system : resolvedOptions._systemRef?.current ?? createRuntimeSystem(this, {
3500
2795
  clock,
3501
2796
  logger,
3502
2797
  timers: resolvedOptions.timers
3503
2798
  });
2799
+ if (!parent && resolvedOptions._systemRef && !resolvedOptions._systemRef.current) {
2800
+ resolvedOptions._systemRef.current = this.system;
2801
+ }
3504
2802
  if (inspect && !parent) {
3505
2803
  // Always inspect at the system-level
3506
2804
  this.system.inspect(toObserver(inspect));
@@ -3525,35 +2823,18 @@ class Actor {
3525
2823
  system: this.system,
3526
2824
  stopChild: child => {
3527
2825
  if (child._parent !== this) {
3528
- throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
2826
+ throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child` );
3529
2827
  }
3530
2828
  child._stop();
3531
2829
  },
3532
2830
  emit: emittedEvent => {
3533
- const listeners = this.eventListeners.get(emittedEvent.type);
3534
- const wildcardListener = this.eventListeners.get('*');
3535
- if (!listeners && !wildcardListener) {
3536
- return;
3537
- }
3538
- if (listeners) {
3539
- for (const handler of listeners) {
3540
- try {
3541
- handler(emittedEvent);
3542
- } catch (err) {
3543
- reportUnhandledError(err);
2831
+ for (const listeners of [this.eventListeners.get(emittedEvent.type), this.eventListeners.get('*')]) {
2832
+ if (listeners) {
2833
+ for (const handler of listeners) {
2834
+ safeCall(handler, emittedEvent);
3544
2835
  }
3545
2836
  }
3546
2837
  }
3547
- if (!wildcardListener) {
3548
- return;
3549
- }
3550
- for (const handler of wildcardListener) {
3551
- try {
3552
- handler(emittedEvent);
3553
- } catch (err) {
3554
- reportUnhandledError(err);
3555
- }
3556
- }
3557
2838
  },
3558
2839
  actionExecutor: action => {
3559
2840
  const exec = () => {
@@ -3574,7 +2855,7 @@ class Actor {
3574
2855
  executingCustomAction = saveExecutingCustomAction;
3575
2856
  }
3576
2857
  };
3577
- if (this._processingStatus === ProcessingStatus.Running) {
2858
+ if (this._processingStatus === ProcessingStatus.Running && !this._forceDeferredActions) {
3578
2859
  exec();
3579
2860
  } else {
3580
2861
  this._deferred.push(exec);
@@ -3595,15 +2876,12 @@ class Actor {
3595
2876
  };
3596
2877
  }
3597
2878
  });
3598
-
3599
- // unified '@xstate.transition' event replaces '@xstate.actor'
3600
-
3601
- if (systemId) {
3602
- this.systemId = systemId;
3603
- this.system._set(systemId, this);
2879
+ if (registryKey) {
2880
+ this.registryKey = registryKey;
2881
+ this.system._set(registryKey, this);
3604
2882
  }
3605
2883
 
3606
- // prepare to collect initial microsteps during getInitialSnapshot
2884
+ // prepare to collect initial microsteps during initialTransition
3607
2885
  this._collectedMicrosteps = [];
3608
2886
  let persistedState = options?.snapshot ?? options?.state;
3609
2887
  if (persistedState && typeof persistedState === 'object' && '_pendingEffects' in persistedState) {
@@ -3617,7 +2895,17 @@ class Actor {
3617
2895
  persistedState = rest;
3618
2896
  }
3619
2897
  try {
3620
- this._snapshot = persistedState ? this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState : this.logic.getInitialSnapshot(this._actorScope, this.options?.input);
2898
+ if (persistedState) {
2899
+ this._snapshot = this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState;
2900
+ } else if (options?._inert) {
2901
+ // Inert actors (createInertActorScope) only anchor a scope for pure
2902
+ // transition functions; computing an initial snapshot here would run
2903
+ // init-time side effects (context factories, entry) a second time.
2904
+ } else {
2905
+ const [snapshot, effects] = this.logic.initialTransition(this.options?.input, this._actorScope);
2906
+ this._snapshot = snapshot;
2907
+ this._initialEffects = effects;
2908
+ }
3621
2909
  } catch (err) {
3622
2910
  // if we get here then it means that we assign a value to this._snapshot that is not of the correct type
3623
2911
  // we can't get the true `TSnapshot & { status: 'error'; }`, it's impossible
@@ -3631,7 +2919,7 @@ class Actor {
3631
2919
  // computation so they can't run against an inconsistent actor
3632
2920
  this._deferred.length = 0;
3633
2921
  }
3634
- if (systemId && this._snapshot.status !== 'active') {
2922
+ if (registryKey && this._snapshot.status !== 'active') {
3635
2923
  this.system._unregister(this);
3636
2924
  }
3637
2925
 
@@ -3654,13 +2942,35 @@ class Actor {
3654
2942
  error: err
3655
2943
  };
3656
2944
  }
2945
+
2946
+ /** Recover via the logic's error event if possible; otherwise error out. */
2947
+ _recoverOrError(err, snapshot) {
2948
+ if (!this._tryHandleExecutionError(err, snapshot)) {
2949
+ this._setErrorSnapshot(err);
2950
+ this._error(err);
2951
+ }
2952
+ }
2953
+ _tryHandleExecutionError(err, snapshot = this._snapshot) {
2954
+ // Machine logic can recover from execution errors via `onError`
2955
+ // transitions; the logic decides (so the actor stays logic-agnostic).
2956
+ const errorEvent = this.logic.getExecutionErrorEvent?.(snapshot, err);
2957
+ if (!errorEvent) {
2958
+ return false;
2959
+ }
2960
+ try {
2961
+ const [nextSnapshot, effects] = this.logic.transition(snapshot, errorEvent, this._actorScope);
2962
+ this._snapshot = nextSnapshot;
2963
+ const logicEffects = executeExecutableEffects(effects, this._actorScope);
2964
+ this.update(nextSnapshot, errorEvent);
2965
+ this.logic.executeEffects?.(logicEffects, this._actorScope);
2966
+ return true;
2967
+ } catch {
2968
+ return false;
2969
+ }
2970
+ }
3657
2971
  _next(snapshot) {
3658
2972
  for (const observer of this.observers) {
3659
- try {
3660
- observer.next?.(snapshot);
3661
- } catch (err) {
3662
- reportUnhandledError(err);
3663
- }
2973
+ safeCall(observer.next, snapshot);
3664
2974
  }
3665
2975
  }
3666
2976
  update(snapshot, event) {
@@ -3679,6 +2989,9 @@ class Actor {
3679
2989
  // no "builtin deferred" should actually throw an error since they are either safe
3680
2990
  // or the control flow is passed through the mailbox and errors should be caught by the `_process` used by the mailbox
3681
2991
  this._deferred.length = 0;
2992
+ if (this._tryHandleExecutionError(err, snapshot)) {
2993
+ return;
2994
+ }
3682
2995
  this._setErrorSnapshot(err, snapshot);
3683
2996
  break;
3684
2997
  }
@@ -3726,6 +3039,25 @@ class Actor {
3726
3039
  this._collectedActions = [];
3727
3040
  this._collectedSent = [];
3728
3041
  }
3042
+ _flushInitialEffects() {
3043
+ if (!this._initialEffects) {
3044
+ return true;
3045
+ }
3046
+ this._forceDeferredActions = true;
3047
+ try {
3048
+ const logicEffects = executeExecutableEffects(this._initialEffects, this._actorScope);
3049
+ this.logic.executeEffects?.(logicEffects, this._actorScope);
3050
+ this._initialEffects = undefined;
3051
+ return true;
3052
+ } catch (err) {
3053
+ this._initialEffects = undefined;
3054
+ this._deferred.length = 0;
3055
+ this._recoverOrError(err);
3056
+ return this._snapshot.status === 'active';
3057
+ } finally {
3058
+ this._forceDeferredActions = false;
3059
+ }
3060
+ }
3729
3061
 
3730
3062
  /**
3731
3063
  * Subscribe an observer to an actor’s snapshot values.
@@ -3794,11 +3126,7 @@ class Actor {
3794
3126
  } else {
3795
3127
  switch (this._snapshot.status) {
3796
3128
  case 'done':
3797
- try {
3798
- observer.complete?.();
3799
- } catch (err) {
3800
- reportUnhandledError(err);
3801
- }
3129
+ safeCall(observer.complete);
3802
3130
  break;
3803
3131
  case 'error':
3804
3132
  {
@@ -3806,11 +3134,7 @@ class Actor {
3806
3134
  if (!observer.error) {
3807
3135
  reportUnhandledError(err);
3808
3136
  } else {
3809
- try {
3810
- observer.error(err);
3811
- } catch (err) {
3812
- reportUnhandledError(err);
3813
- }
3137
+ safeCall(observer.error, err);
3814
3138
  }
3815
3139
  break;
3816
3140
  }
@@ -3852,7 +3176,7 @@ class Actor {
3852
3176
  complete: observer.complete
3853
3177
  });
3854
3178
  },
3855
- get: () => selector(this.get())
3179
+ get: () => selector(this.getSnapshot())
3856
3180
  };
3857
3181
  }
3858
3182
 
@@ -3862,6 +3186,9 @@ class Actor {
3862
3186
  // Do not restart the service if it is already started
3863
3187
  return this;
3864
3188
  }
3189
+ if (this._processingStatus === ProcessingStatus.Stopped) {
3190
+ return this;
3191
+ }
3865
3192
  if (this._syncSnapshot) {
3866
3193
  this.subscribe({
3867
3194
  next: snapshot => {
@@ -3876,8 +3203,8 @@ class Actor {
3876
3203
  });
3877
3204
  }
3878
3205
  this.system._register(this.sessionId, this);
3879
- if (this.systemId) {
3880
- this.system._set(this.systemId, this);
3206
+ if (this.registryKey) {
3207
+ this.system._set(this.registryKey, this);
3881
3208
  }
3882
3209
  this._processingStatus = ProcessingStatus.Running;
3883
3210
 
@@ -3890,6 +3217,9 @@ class Actor {
3890
3217
  case 'done':
3891
3218
  // a state machine can be "done" upon initialization (it could reach a final state using initial microsteps)
3892
3219
  // we still need to complete observers, flush deferreds etc
3220
+ if (!this._flushInitialEffects()) {
3221
+ return this;
3222
+ }
3893
3223
  this.update(this._snapshot, initEvent);
3894
3224
  // TODO: rethink cleanup of observers, mailbox, etc
3895
3225
  return this;
@@ -3909,6 +3239,9 @@ class Actor {
3909
3239
  return this;
3910
3240
  }
3911
3241
  }
3242
+ if (!this._flushInitialEffects()) {
3243
+ return this;
3244
+ }
3912
3245
 
3913
3246
  // TODO: this notifies all subscribers but usually this is redundant
3914
3247
  // there is no real change happening here
@@ -3940,16 +3273,21 @@ class Actor {
3940
3273
  };
3941
3274
  }
3942
3275
  if (caughtError) {
3943
- const {
3944
- err
3945
- } = caughtError;
3946
- this._setErrorSnapshot(err);
3947
- this._error(err);
3276
+ this._recoverOrError(caughtError.err);
3277
+ return;
3278
+ }
3279
+ let snapshot = this._snapshot;
3280
+ try {
3281
+ const [nextSnapshot, effects] = nextState;
3282
+ snapshot = nextSnapshot;
3283
+ this._snapshot = snapshot;
3284
+ const logicEffects = executeExecutableEffects(effects, this._actorScope);
3285
+ this.update(snapshot, event);
3286
+ this.logic.executeEffects?.(logicEffects, this._actorScope);
3287
+ } catch (err) {
3288
+ this._recoverOrError(err, snapshot);
3948
3289
  return;
3949
3290
  }
3950
- const [snapshot, effects] = Array.isArray(nextState) ? nextState : [nextState, undefined];
3951
- this.update(snapshot, event);
3952
- executeLogicEffects(effects, this._actorScope);
3953
3291
  if (event.type === XSTATE_STOP) {
3954
3292
  this._stopProcedure();
3955
3293
  this._complete();
@@ -3982,11 +3320,7 @@ class Actor {
3982
3320
  }
3983
3321
  _complete() {
3984
3322
  for (const observer of this.observers) {
3985
- try {
3986
- observer.complete?.();
3987
- } catch (err) {
3988
- reportUnhandledError(err);
3989
- }
3323
+ safeCall(observer.complete);
3990
3324
  }
3991
3325
  this.observers.clear();
3992
3326
  this.eventListeners.clear();
@@ -3997,24 +3331,19 @@ class Actor {
3997
3331
  if (!this._parent) {
3998
3332
  reportUnhandledError(err);
3999
3333
  }
4000
- this.eventListeners.clear();
4001
3334
  } else {
4002
3335
  let reportError = false;
4003
3336
  for (const observer of this.observers) {
4004
3337
  const errorListener = observer.error;
4005
3338
  reportError ||= !errorListener;
4006
- try {
4007
- errorListener?.(err);
4008
- } catch (err2) {
4009
- reportUnhandledError(err2);
4010
- }
3339
+ safeCall(errorListener, err);
4011
3340
  }
4012
3341
  this.observers.clear();
4013
- this.eventListeners.clear();
4014
3342
  if (reportError) {
4015
3343
  reportUnhandledError(err);
4016
3344
  }
4017
3345
  }
3346
+ this.eventListeners.clear();
4018
3347
  if (this._parent) {
4019
3348
  this.system._relay(this, this._parent, createErrorActorEvent(this.id, err));
4020
3349
  }
@@ -4141,15 +3470,9 @@ class Actor {
4141
3470
  * @see {@link Actor.getPersistedSnapshot} to persist the internal state of an actor (which is more than just a snapshot).
4142
3471
  */
4143
3472
  getSnapshot() {
4144
- return this.get();
4145
- }
4146
-
4147
- /** Read the actor's current snapshot as an atom value. */
4148
- get() {
4149
3473
  if (!this._snapshot) {
4150
3474
  throw new Error(`Snapshot can't be read while the actor initializes itself`);
4151
3475
  }
4152
- onActorRead?.(this);
4153
3476
  return this._snapshot;
4154
3477
  }
4155
3478
  }
@@ -4210,6 +3533,231 @@ function createActor(logic, options) {
4210
3533
  return new Actor(logic, options);
4211
3534
  }
4212
3535
 
3536
+ const effectStates = new WeakMap();
3537
+ const XSTATE_LOGIC_EFFECT_RESOLVE = 'xstate.logic.effect.resolve';
3538
+ const XSTATE_LOGIC_EFFECT_REJECT = 'xstate.logic.effect.reject';
3539
+ const XSTATE_LOGIC_EFFECT_START = 'xstate.logic.effect.start';
3540
+ function getEffectState(self) {
3541
+ let state = effectStates.get(self);
3542
+ if (!state) {
3543
+ state = new Map();
3544
+ effectStates.set(self, state);
3545
+ }
3546
+ return state;
3547
+ }
3548
+ function executeLogicEffects(effects, actorScope) {
3549
+ if (!effects?.length) {
3550
+ return;
3551
+ }
3552
+ for (const effect of effects) {
3553
+ switch (effect.type) {
3554
+ case 'emit':
3555
+ actorScope.emit(effect.event);
3556
+ break;
3557
+ case 'sendBack':
3558
+ {
3559
+ const parent = actorScope.self._parent;
3560
+ if (parent) {
3561
+ actorScope.system._relay(actorScope.self, parent, effect.event);
3562
+ }
3563
+ break;
3564
+ }
3565
+ case 'raise':
3566
+ actorScope.system._relay(actorScope.self, actorScope.self, effect.event);
3567
+ break;
3568
+ case 'effect':
3569
+ {
3570
+ if (!effect.key) {
3571
+ const cleanup = effect.exec();
3572
+ if (typeof cleanup === 'function') {
3573
+ getEffectState(actorScope.self).set(Symbol(), {
3574
+ cleanup
3575
+ });
3576
+ }
3577
+ break;
3578
+ }
3579
+ const state = getEffectState(actorScope.self);
3580
+ if (state.has(effect.key)) {
3581
+ break;
3582
+ }
3583
+ const cleanup = effect.exec();
3584
+ state.set(effect.key, {
3585
+ cleanup: typeof cleanup === 'function' ? cleanup : undefined
3586
+ });
3587
+ break;
3588
+ }
3589
+ case 'cleanupEffects':
3590
+ {
3591
+ const state = effectStates.get(actorScope.self);
3592
+ if (!state) {
3593
+ break;
3594
+ }
3595
+ for (const {
3596
+ cleanup
3597
+ } of state.values()) {
3598
+ cleanup?.();
3599
+ }
3600
+ effectStates.delete(actorScope.self);
3601
+ break;
3602
+ }
3603
+ }
3604
+ }
3605
+ }
3606
+ function resolveContext(context, input) {
3607
+ return typeof context === 'function' ? context({
3608
+ input
3609
+ }) : context;
3610
+ }
3611
+ function createLogic(config) {
3612
+ const transition = (snapshot, event, actorScope) => {
3613
+ if (snapshot.status !== 'active') {
3614
+ return [snapshot, []];
3615
+ }
3616
+ if (event.type === XSTATE_STOP) {
3617
+ return [{
3618
+ ...snapshot,
3619
+ status: 'stopped',
3620
+ input: undefined
3621
+ }, [{
3622
+ type: 'cleanupEffects'
3623
+ }]];
3624
+ }
3625
+ if (event.type === XSTATE_LOGIC_EFFECT_START) {
3626
+ return [{
3627
+ ...snapshot,
3628
+ effects: {
3629
+ ...snapshot.effects,
3630
+ [event.key]: {
3631
+ status: 'active'
3632
+ }
3633
+ }
3634
+ }, []];
3635
+ }
3636
+ if (event.type === XSTATE_LOGIC_EFFECT_RESOLVE) {
3637
+ return [{
3638
+ ...snapshot,
3639
+ effects: {
3640
+ ...snapshot.effects,
3641
+ [event.key]: {
3642
+ status: 'done',
3643
+ output: event.output
3644
+ }
3645
+ }
3646
+ }, []];
3647
+ }
3648
+ if (event.type === XSTATE_LOGIC_EFFECT_REJECT) {
3649
+ return [{
3650
+ ...snapshot,
3651
+ effects: {
3652
+ ...snapshot.effects,
3653
+ [event.key]: {
3654
+ status: 'error',
3655
+ error: event.error
3656
+ }
3657
+ }
3658
+ }, []];
3659
+ }
3660
+ const effects = [];
3661
+ const trackedEffects = {};
3662
+ const enqueueEffect = (key, exec) => {
3663
+ if (snapshot.effects?.[key]) {
3664
+ return;
3665
+ }
3666
+ effects.push({
3667
+ type: 'effect',
3668
+ key,
3669
+ exec
3670
+ });
3671
+ trackedEffects[key] = {
3672
+ status: 'active'
3673
+ };
3674
+ };
3675
+ const enq = {
3676
+ emit: emitted => {
3677
+ effects.push({
3678
+ type: 'emit',
3679
+ event: emitted
3680
+ });
3681
+ },
3682
+ sendBack: sentEvent => {
3683
+ effects.push({
3684
+ type: 'sendBack',
3685
+ event: sentEvent
3686
+ });
3687
+ },
3688
+ raise: raisedEvent => {
3689
+ effects.push({
3690
+ type: 'raise',
3691
+ event: raisedEvent
3692
+ });
3693
+ },
3694
+ effect: (keyOrExec, maybeExec) => {
3695
+ if (typeof keyOrExec === 'string') {
3696
+ enqueueEffect(keyOrExec, maybeExec);
3697
+ return;
3698
+ }
3699
+ effects.push({
3700
+ type: 'effect',
3701
+ exec: keyOrExec
3702
+ });
3703
+ }
3704
+ };
3705
+ const patch = config.run({
3706
+ context: snapshot.context,
3707
+ event,
3708
+ input: snapshot.input,
3709
+ system: actorScope.system,
3710
+ self: actorScope.self,
3711
+ emit: actorScope.emit
3712
+ }, enq);
3713
+ const nextSnapshot = {
3714
+ ...snapshot,
3715
+ ...(patch || {})
3716
+ };
3717
+ if ('context' in snapshot || patch?.context !== undefined) {
3718
+ nextSnapshot.context = patch?.context ?? snapshot.context;
3719
+ }
3720
+ if (patch?.effects || Object.keys(trackedEffects).length) {
3721
+ nextSnapshot.effects = {
3722
+ ...snapshot.effects,
3723
+ ...patch?.effects,
3724
+ ...trackedEffects
3725
+ };
3726
+ }
3727
+ return [nextSnapshot, effects];
3728
+ };
3729
+ const logic = {
3730
+ id: config.id,
3731
+ config,
3732
+ transition,
3733
+ start: (snapshot, actorScope) => {
3734
+ const [nextSnapshot, effects] = transition(snapshot, createInitEvent(snapshot.input), actorScope);
3735
+ Object.assign(snapshot, nextSnapshot);
3736
+ executeLogicEffects(effects, actorScope);
3737
+ },
3738
+ initialTransition: (input, _) => {
3739
+ const context = resolveContext(config.context, input);
3740
+ const snapshot = {
3741
+ status: 'active',
3742
+ output: undefined,
3743
+ error: undefined,
3744
+ input
3745
+ };
3746
+ if (context !== undefined) {
3747
+ snapshot.context = context;
3748
+ }
3749
+ return [{
3750
+ ...snapshot
3751
+ }, []];
3752
+ },
3753
+ getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
3754
+ getPersistedSnapshot: snapshot => snapshot,
3755
+ restoreSnapshot: snapshot => snapshot,
3756
+ executeEffects: executeLogicEffects
3757
+ };
3758
+ return logic;
3759
+ }
3760
+
4213
3761
  const instanceStates = /* #__PURE__ */new WeakMap();
4214
3762
 
4215
3763
  /**
@@ -4588,46 +4136,45 @@ function createEventObservableLogic(lazyObservableOrConfig) {
4588
4136
  config: lazyObservable,
4589
4137
  transition: (state, event) => {
4590
4138
  if (state.status !== 'active') {
4591
- return state;
4139
+ return [state, []];
4592
4140
  }
4593
4141
  switch (event.type) {
4594
4142
  case XSTATE_OBSERVABLE_ERROR:
4595
- return {
4143
+ return [{
4596
4144
  ...state,
4597
4145
  status: 'error',
4598
4146
  error: event.data,
4599
4147
  input: undefined,
4600
4148
  _subscription: undefined
4601
- };
4149
+ }, []];
4602
4150
  case XSTATE_OBSERVABLE_COMPLETE:
4603
- return {
4151
+ return [{
4604
4152
  ...state,
4605
4153
  status: 'done',
4606
4154
  input: undefined,
4607
4155
  _subscription: undefined
4608
- };
4156
+ }, []];
4609
4157
  case XSTATE_STOP:
4610
4158
  state._subscription.unsubscribe();
4611
- return {
4159
+ return [{
4612
4160
  ...state,
4613
4161
  status: 'stopped',
4614
4162
  input: undefined,
4615
4163
  _subscription: undefined
4616
- };
4164
+ }, []];
4617
4165
  default:
4618
- return state;
4166
+ return [state, []];
4619
4167
  }
4620
4168
  },
4621
- getInitialSnapshot: (_, input) => {
4622
- return {
4623
- status: 'active',
4624
- output: undefined,
4625
- error: undefined,
4626
- context: undefined,
4627
- input,
4628
- _subscription: undefined
4629
- };
4630
- },
4169
+ initialTransition: (input, _) => [{
4170
+ status: 'active',
4171
+ output: undefined,
4172
+ error: undefined,
4173
+ context: undefined,
4174
+ input,
4175
+ _subscription: undefined
4176
+ }, []],
4177
+ getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
4631
4178
  start: (state, {
4632
4179
  self,
4633
4180
  system,
@@ -4945,4 +4492,4 @@ function createEmptyActor() {
4945
4492
  return createActor(emptyLogic);
4946
4493
  }
4947
4494
 
4948
- export { isStateId as $, Actor as A, createAsyncLogic as B, createListenerLogic as C, listenerLogic as D, createSubscriptionLogic as E, subscriptionLogic as F, resolveReferencedActor as G, mapValues as H, createInvokeId as I, getDelayedTransitions as J, evaluateCandidate as K, formatTransition as L, toTransitionConfigArray as M, NULL_EVENT as N, createInvokeTimeoutEvent as O, ProcessingStatus as P, getCandidates as Q, formatRouteTransitions as R, STATE_DELIMITER as S, TimeoutError as T, resolveStateValue as U, getAllStateNodes as V, createMachineSnapshot as W, isInFinalState as X, transitionNode as Y, resolveAndExecuteActionsWithContext as Z, toStatePath as _, macrostep as a, getStateNodeByPath as a0, getPersistedSnapshot as a1, $$ACTOR_TYPE as a2, XSTATE_INIT as a3, createInitEvent as b, createActor as c, initialMicrostep as d, createAtom as e, createAtomConfig as f, getProperAncestors as g, createAsyncAtom as h, isAtomicStateNode as i, createReducerAtom as j, isAtom as k, isMachineSnapshot as l, matchesEventDescriptor as m, getStateNodes as n, getAllOwnEventDescriptors as o, parseDelayToMilliseconds as p, matchesState as q, checkStateIn as r, pathToStateValue as s, toArray as t, toObserver as u, createEmptyActor as v, createCallbackLogic as w, createObservableLogic as x, createEventObservableLogic as y, createLogic as z };
4495
+ export { transitionNode as $, Actor as A, createLogic as B, createAsyncLogic as C, createListenerLogic as D, listenerLogic as E, createSubscriptionLogic as F, subscriptionLogic as G, resolveReferencedActor as H, mapValues as I, createInvokeId as J, getDelayedTransitions as K, evaluateCandidate as L, formatTransition as M, NULL_EVENT as N, toTransitionConfigArray as O, ProcessingStatus as P, createInvokeTimeoutEvent as Q, getCandidates as R, STATE_DELIMITER as S, TimeoutError as T, formatRouteTransitions as U, resolveStateValue as V, getAllStateNodes as W, XSTATE_INIT as X, createMachineSnapshot as Y, isInFinalState as Z, cloneMachineSnapshot as _, XSTATE_STOP as a, getTransitionResult as a0, hasEffect as a1, createErrorPlatformEvent as a2, toStatePath as a3, isStateId as a4, getStateNodeByPath as a5, getPersistedSnapshot as a6, $$ACTOR_TYPE as a7, builtInActions as b, createTransitionEnqueue as c, deriveDeferredStarts as d, createActor as e, macrostep as f, createInitEvent as g, initialMicrostep as h, isAtomicStateNode as i, getProperAncestors as j, isMachineSnapshot as k, getStateNodes as l, matchesEventDescriptor as m, getAllOwnEventDescriptors as n, matchesState as o, parseDelayToMilliseconds as p, checkStateIn as q, resolveActionsWithContext as r, pathToStateValue as s, toArray as t, toObserver as u, isBuiltInExecutableAction as v, createEmptyActor as w, createCallbackLogic as x, createObservableLogic as y, createEventObservableLogic as z };