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

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