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

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