xstate 6.0.0-alpha.21 → 6.0.0-alpha.22

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 (40) hide show
  1. package/dist/{StateMachine-550ff7f2.development.cjs.js → StateMachine-1888d034.development.cjs.js} +178 -27
  2. package/dist/{StateMachine-1b155670.cjs.js → StateMachine-610fe929.cjs.js} +178 -27
  3. package/dist/{StateMachine-e25dd61f.development.esm.js → StateMachine-9fad7a2b.development.esm.js} +174 -28
  4. package/dist/{StateMachine-3c17fe3e.esm.js → StateMachine-ddb5d007.esm.js} +174 -28
  5. package/dist/declarations/src/State.d.ts +4 -2
  6. package/dist/declarations/src/StateMachine.d.ts +5 -4
  7. package/dist/declarations/src/actions.d.ts +10 -8
  8. package/dist/declarations/src/actors/logic.d.ts +5 -21
  9. package/dist/declarations/src/createActor.d.ts +3 -1
  10. package/dist/declarations/src/fsm.d.ts +3 -1
  11. package/dist/declarations/src/index.d.ts +2 -2
  12. package/dist/declarations/src/stateUtils.d.ts +1 -1
  13. package/dist/declarations/src/system.d.ts +34 -9
  14. package/dist/declarations/src/transitionActions.d.ts +5 -1
  15. package/dist/declarations/src/types.d.ts +70 -51
  16. package/dist/{index-3f87408c.esm.js → index-2fe11873.esm.js} +782 -389
  17. package/dist/{index-ec7c7a2d.development.cjs.js → index-41c0375c.development.cjs.js} +790 -390
  18. package/dist/{index-e694228c.cjs.js → index-434fac23.cjs.js} +789 -389
  19. package/dist/{index-d39fb138.development.esm.js → index-44d8c2d1.development.esm.js} +783 -390
  20. package/dist/xstate-actors.cjs.js +1 -1
  21. package/dist/xstate-actors.development.cjs.js +1 -1
  22. package/dist/xstate-actors.development.esm.js +1 -1
  23. package/dist/xstate-actors.esm.js +1 -1
  24. package/dist/xstate-actors.umd.min.js +1 -1
  25. package/dist/xstate-actors.umd.min.js.map +1 -1
  26. package/dist/xstate-graph.cjs.js +2 -2
  27. package/dist/xstate-graph.development.cjs.js +2 -2
  28. package/dist/xstate-graph.development.esm.js +2 -2
  29. package/dist/xstate-graph.esm.js +2 -2
  30. package/dist/xstate-graph.umd.min.js +1 -1
  31. package/dist/xstate-graph.umd.min.js.map +1 -1
  32. package/dist/xstate.cjs.js +82 -101
  33. package/dist/xstate.cjs.mjs +1 -0
  34. package/dist/xstate.development.cjs.js +82 -101
  35. package/dist/xstate.development.cjs.mjs +1 -0
  36. package/dist/xstate.development.esm.js +80 -100
  37. package/dist/xstate.esm.js +80 -100
  38. package/dist/xstate.umd.min.js +1 -1
  39. package/dist/xstate.umd.min.js.map +1 -1
  40. package/package.json +1 -1
@@ -54,6 +54,8 @@ const XSTATE_INIT = '@xstate.init';
54
54
  const XSTATE_START = '@xstate.start';
55
55
  const XSTATE_SPAWN = '@xstate.spawn';
56
56
  const XSTATE_STOP = '@xstate.stop';
57
+ const XSTATE_TERMINATE = '@xstate.terminate';
58
+ const XSTATE_TIMER = 'xstate.timer';
57
59
 
58
60
  /**
59
61
  * Returns an event that represents an implicit event that is sent after the
@@ -158,33 +160,39 @@ function reportUnhandledError(err) {
158
160
 
159
161
  const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
160
162
 
163
+ function assertSendToEvent(event) {
164
+ if (typeof event === 'string') {
165
+ throw new Error(`Only event objects may be used with sendTo`);
166
+ }
167
+ }
161
168
  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 => {},
169
+ // Actor refs are created while resolving the next snapshot. Starting is a
170
+ // separate deferred effect so the returned list remains execution-complete.
171
+ ['@xstate.spawn']: actor => {
172
+ return actor.system.spawnActor(actor._parent, actor);
173
+ },
165
174
  ['@xstate.start']: actor => {
166
- actor.start();
175
+ return actor.system.startActor(actor);
167
176
  },
168
- ['@xstate.raise']: (actorScope, event, options) => {
169
- actorScope.system.scheduler.schedule(actorScope.self, actorScope.self, event, options?.delay ?? 0, options?.id);
177
+ ['@xstate.raise']: (actorScope, _event, options) => {
178
+ return actorScope.system.scheduleTimer(actorScope.self, options.id, options?.delay ?? 0);
170
179
  },
171
180
  ['@xstate.sendTo']: (actorScope, actor, event, options) => {
172
- if (typeof event === 'string') {
173
- throw new Error(`Only event objects may be used with sendTo`);
174
- }
181
+ assertSendToEvent(event);
175
182
  if (options?.delay !== undefined) {
176
- actorScope.system.scheduler.schedule(actorScope.self, actor, event, options?.delay ?? 0, options?.id);
183
+ return actorScope.system.scheduleTimer(actorScope.self, options.id, options?.delay ?? 0);
177
184
  } else {
178
- actorScope.defer(() => {
179
- actorScope.system._relay(actorScope.self, actor, event);
180
- });
185
+ return actorScope.system._relay(actorScope.self, actor, event);
181
186
  }
182
187
  },
183
188
  ['@xstate.cancel']: (actorScope, sendId) => {
184
- actorScope.system.scheduler.cancel(actorScope.self, sendId);
189
+ return actorScope.system.cancelTimer(actorScope.self, sendId);
185
190
  },
186
191
  ['@xstate.stop']: (actorScope, actor) => {
187
- actorScope.stopChild(actor);
192
+ return actorScope.system.stopActor(actor);
193
+ },
194
+ ['@xstate.terminate']: (actor, termination) => {
195
+ return actor.system.terminateActor(actor, termination);
188
196
  }
189
197
  };
190
198
 
@@ -348,12 +356,109 @@ function createSubscriptionLogic() {
348
356
  // Singleton logic instance
349
357
  const subscriptionLogic = /* #__PURE__ */createSubscriptionLogic();
350
358
 
351
- const builtInExecutableAction = Symbol('xstate.builtInExecutableAction');
352
- function brandBuiltInExecutableAction(action) {
353
- Object.defineProperty(action, builtInExecutableAction, {
354
- value: true
355
- });
356
- return action;
359
+ function execCustomEffect() {
360
+ return this.action?.(...this.args);
361
+ }
362
+ function execEmitEffect(runtime = this.source.system) {
363
+ return runtime.emitEvent(this.source, this.event);
364
+ }
365
+
366
+ /** @internal Creates an emitted-event effect. */
367
+ function createEmitEffect(actorScope, event) {
368
+ return {
369
+ kind: 'emit',
370
+ exec: execEmitEffect,
371
+ type: event.type,
372
+ source: actorScope.self,
373
+ event,
374
+ params: undefined,
375
+ args: []
376
+ };
377
+ }
378
+
379
+ /** @internal Creates a directly executable user effect. */
380
+ function createCustomEffect(type, action, params) {
381
+ return {
382
+ kind: 'action',
383
+ exec: action,
384
+ type,
385
+ action: action,
386
+ params,
387
+ args: []
388
+ };
389
+ }
390
+ function execSpawnEffect(runtime = this.actor.system) {
391
+ return runtime.spawnActor(this.source, this.actor);
392
+ }
393
+ function execStartEffect(runtime = this.actor.system) {
394
+ return runtime.startActor(this.actor);
395
+ }
396
+ function execStopEffect(runtime = this.source.system) {
397
+ return runtime.stopActor(this.actor);
398
+ }
399
+ function execTerminateEffect(runtime = this.actor.system) {
400
+ const termination = this.status === 'done' ? {
401
+ status: 'done',
402
+ output: this.output,
403
+ error: undefined
404
+ } : {
405
+ status: 'error',
406
+ output: undefined,
407
+ error: this.error
408
+ };
409
+ return runtime.terminateActor(this.actor, termination);
410
+ }
411
+ function execRaiseEffect(runtime = this.source.system) {
412
+ return runtime.scheduleTimer(this.source, this.id, this.delay ?? 0);
413
+ }
414
+ function execSendToEffect(runtime = this.source.system) {
415
+ assertSendToEvent(this.event);
416
+ if (this.delay !== undefined) {
417
+ return runtime.scheduleTimer(this.source, this.id, this.delay);
418
+ }
419
+ return runtime.sendEvent(this.source, this.target, this.event);
420
+ }
421
+ function execCancelEffect(runtime = this.source.system) {
422
+ return runtime.cancelTimer(this.source, this.id);
423
+ }
424
+ function updateLogicalTimers(snapshot, effect, actorScope) {
425
+ if (!isBuiltInExecutableAction(effect)) {
426
+ return snapshot;
427
+ }
428
+ if (effect.type === '@xstate.cancel') {
429
+ if (!snapshot.timers?.[effect.id]) {
430
+ return snapshot;
431
+ }
432
+ const timers = {
433
+ ...snapshot.timers
434
+ };
435
+ delete timers[effect.id];
436
+ return {
437
+ ...snapshot,
438
+ timers
439
+ };
440
+ }
441
+ if (effect.type !== '@xstate.raise' && effect.type !== '@xstate.sendTo' || effect.delay === undefined) {
442
+ return snapshot;
443
+ }
444
+ let nextTimerId = snapshot._nextTimerId ?? 0;
445
+ const id = effect.id ?? `xstate.timer.auto.${nextTimerId++}`;
446
+ effect.id = id;
447
+ const target = effect.type === '@xstate.raise' || effect.target === actorScope.self ? 'self' : effect.target;
448
+ return {
449
+ ...snapshot,
450
+ timers: {
451
+ ...snapshot.timers,
452
+ [id]: {
453
+ id,
454
+ delay: effect.delay,
455
+ type: effect.type,
456
+ event: effect.event,
457
+ target
458
+ }
459
+ },
460
+ _nextTimerId: nextTimerId
461
+ };
357
462
  }
358
463
  function mergeContextPatch(context, patch) {
359
464
  return {
@@ -362,43 +467,55 @@ function mergeContextPatch(context, patch) {
362
467
  };
363
468
  }
364
469
  function pushBuiltInAction(actions, action, ...args) {
365
- actions.push({
470
+ const actionRecord = {
366
471
  action,
367
472
  args
368
- });
473
+ };
474
+ actions.push(actionRecord);
475
+ return actionRecord;
369
476
  }
370
- function registerSpawnedChild(actor, id) {
371
- return Object.assign(function registerChild(args) {
477
+ function applyChildUpdate(snapshot, update, actorScope) {
478
+ if (update.type === 'add') {
372
479
  return {
480
+ ...snapshot,
373
481
  children: {
374
- ...args.children,
375
- [id]: actor
482
+ ...snapshot.children,
483
+ [update.id]: update.actor
376
484
  }
377
485
  };
378
- }, {
379
- _special: true
380
- });
381
- }
382
- function unregisterChild(actor) {
383
- return Object.assign(function removeChild(args) {
384
- const children = {
385
- ...args.children
386
- };
387
- for (const key of Object.keys(children)) {
388
- if (children[key] === actor) {
389
- delete children[key];
390
- }
486
+ }
487
+ const children = {
488
+ ...snapshot.children
489
+ };
490
+ let owned = update.actor._parent === actorScope.self;
491
+ for (const key of Object.keys(children)) {
492
+ if (children[key] === update.actor) {
493
+ owned = true;
494
+ delete children[key];
391
495
  }
392
- return {
393
- children
394
- };
395
- }, {
396
- _special: true
397
- });
496
+ }
497
+ if (!owned) {
498
+ throw new Error(`Cannot stop non-child actor ${update.actor.id}`);
499
+ }
500
+ actorScope.system._unregister(update.actor);
501
+ return {
502
+ ...snapshot,
503
+ children
504
+ };
505
+ }
506
+ function getTransitionActionRecord(action) {
507
+ if (typeof action === 'object' && action !== null && 'action' in action && typeof action.action === 'function') {
508
+ return action;
509
+ }
510
+ return undefined;
398
511
  }
399
512
  function pushSpawnedChild(actions, actor, id) {
400
- pushBuiltInAction(actions, builtInActions['@xstate.spawn'], actor);
401
- actions.push(registerSpawnedChild(actor, id ?? actor.id));
513
+ const action = pushBuiltInAction(actions, builtInActions['@xstate.spawn'], actor);
514
+ action.childUpdate = {
515
+ type: 'add',
516
+ actor,
517
+ id: id ?? actor.id
518
+ };
402
519
  }
403
520
  function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubscriptions = false, createActors = true) {
404
521
  const props = {
@@ -428,7 +545,7 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
428
545
  id: options?.id ?? options?.registryKey ?? logic.id
429
546
  };
430
547
  }
431
- const actor = createActor(logic, {
548
+ const actor = actorScope.system.createActorRef(logic, {
432
549
  ...options,
433
550
  parent: actorScope.self
434
551
  });
@@ -447,8 +564,11 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
447
564
  },
448
565
  stop: actor => {
449
566
  if (actor) {
450
- pushBuiltInAction(actions, builtInActions['@xstate.stop'], actorScope, actor);
451
- actions.push(unregisterChild(actor));
567
+ const action = pushBuiltInAction(actions, builtInActions['@xstate.stop'], actorScope, actor);
568
+ action.childUpdate = {
569
+ type: 'remove',
570
+ actor
571
+ };
452
572
  }
453
573
  }
454
574
  };
@@ -460,7 +580,7 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
460
580
  eventType,
461
581
  mapper
462
582
  };
463
- const listenerActor = createActor(listenerLogic, {
583
+ const listenerActor = actorScope.system.createActorRef(listenerLogic, {
464
584
  input,
465
585
  parent: actorScope.self
466
586
  });
@@ -475,7 +595,7 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
475
595
  actor,
476
596
  mappers: normalizedMappers
477
597
  };
478
- const subscriptionActor = createActor(subscriptionLogic, {
598
+ const subscriptionActor = actorScope.system.createActorRef(subscriptionLogic, {
479
599
  input,
480
600
  parent: actorScope.self
481
601
  });
@@ -494,6 +614,9 @@ function getBuiltInActionFields(action, args) {
494
614
  {
495
615
  const [actor] = args;
496
616
  return {
617
+ kind: 'builtin',
618
+ exec: execSpawnEffect,
619
+ source: actor._parent,
497
620
  actor,
498
621
  id: actor.id,
499
622
  logic: actor.logic,
@@ -505,6 +628,9 @@ function getBuiltInActionFields(action, args) {
505
628
  {
506
629
  const [, event, options] = args;
507
630
  return {
631
+ kind: 'builtin',
632
+ exec: execRaiseEffect,
633
+ source: args[0].self,
508
634
  event,
509
635
  id: options?.id,
510
636
  delay: options?.delay
@@ -514,6 +640,9 @@ function getBuiltInActionFields(action, args) {
514
640
  {
515
641
  const [, target, event, options] = args;
516
642
  return {
643
+ kind: 'builtin',
644
+ exec: execSendToEffect,
645
+ source: args[0].self,
517
646
  target,
518
647
  event,
519
648
  id: options?.id,
@@ -524,6 +653,9 @@ function getBuiltInActionFields(action, args) {
524
653
  {
525
654
  const [, id] = args;
526
655
  return {
656
+ kind: 'builtin',
657
+ exec: execCancelEffect,
658
+ source: args[0].self,
527
659
  id
528
660
  };
529
661
  }
@@ -531,7 +663,11 @@ function getBuiltInActionFields(action, args) {
531
663
  {
532
664
  const [, actor] = args;
533
665
  return {
534
- actor
666
+ kind: 'builtin',
667
+ exec: execStopEffect,
668
+ source: args[0].self,
669
+ actor,
670
+ id: actor.id
535
671
  };
536
672
  }
537
673
  default:
@@ -539,19 +675,91 @@ function getBuiltInActionFields(action, args) {
539
675
  }
540
676
  }
541
677
  function createStartEffect(actor) {
542
- const action = builtInActions['@xstate.start'];
543
678
  const args = [actor];
544
- return brandBuiltInExecutableAction({
679
+ return {
680
+ kind: 'builtin',
681
+ exec: execStartEffect,
545
682
  type: XSTATE_START,
546
- params: {
547
- action,
548
- args
549
- },
683
+ source: actor._parent,
684
+ params: undefined,
550
685
  args,
551
- exec: action.bind(null, actor),
552
686
  actor,
553
687
  id: actor.id
554
- });
688
+ };
689
+ }
690
+ function createSpawnEffect(actor) {
691
+ const args = [actor];
692
+ return {
693
+ kind: 'builtin',
694
+ exec: execSpawnEffect,
695
+ type: XSTATE_SPAWN,
696
+ source: actor._parent,
697
+ params: undefined,
698
+ args,
699
+ actor,
700
+ id: actor.id,
701
+ logic: actor.logic,
702
+ src: actor.src,
703
+ input: actor.options?.input
704
+ };
705
+ }
706
+
707
+ /** @internal Creates an immediate actor-to-actor delivery effect. */
708
+ function createSendToEffect(actorScope, target, event) {
709
+ const args = [actorScope, target, event, {}];
710
+ return {
711
+ kind: 'builtin',
712
+ exec: execSendToEffect,
713
+ type: '@xstate.sendTo',
714
+ source: actorScope.self,
715
+ target,
716
+ event,
717
+ id: undefined,
718
+ delay: undefined,
719
+ params: undefined,
720
+ args
721
+ };
722
+ }
723
+
724
+ /** @internal Creates the terminal lifecycle effect for an actor. */
725
+ function createTerminationEffect(actorScope, snapshot) {
726
+ if (snapshot.status !== 'done' && snapshot.status !== 'error') {
727
+ throw new Error('Cannot terminate an active or stopped actor');
728
+ }
729
+ const termination = snapshot.status === 'done' ? {
730
+ status: 'done',
731
+ output: snapshot.output,
732
+ error: undefined
733
+ } : {
734
+ status: 'error',
735
+ output: undefined,
736
+ error: snapshot.error
737
+ };
738
+ const args = [actorScope.self, termination];
739
+ return {
740
+ kind: 'builtin',
741
+ exec: execTerminateEffect,
742
+ type: XSTATE_TERMINATE,
743
+ source: actorScope.self,
744
+ actor: actorScope.self,
745
+ id: actorScope.self.id,
746
+ ...termination,
747
+ params: undefined,
748
+ args
749
+ };
750
+ }
751
+
752
+ /**
753
+ * Ensures that a newly terminal snapshot has exactly one ordered actor
754
+ * termination effect.
755
+ *
756
+ * @internal
757
+ */
758
+ function finalizeTransitionResult(actorScope, previousSnapshot, [nextSnapshot, effects]) {
759
+ const becameTerminal = nextSnapshot.status === 'done' || nextSnapshot.status === 'error';
760
+ const wasTerminal = previousSnapshot?.status === 'done' || previousSnapshot?.status === 'error';
761
+ const hasTerminationEffect = effects.some(effect => typeof effect === 'object' && effect !== null && 'type' in effect && effect.type === XSTATE_TERMINATE);
762
+ return becameTerminal && !wasTerminal && !hasTerminationEffect ? [nextSnapshot, [...effects, createTerminationEffect(actorScope, nextSnapshot)]] : [nextSnapshot, effects];
555
763
  }
556
764
 
557
765
  /**
@@ -580,14 +788,19 @@ function deriveDeferredStarts(effects) {
580
788
  return [...attachedStarts, ...childStarts];
581
789
  }
582
790
  function isBuiltInExecutableAction(action) {
583
- return action[builtInExecutableAction] === true;
791
+ return action.kind === 'builtin';
792
+ }
793
+
794
+ /** Executes transition effects sequentially, awaiting each runtime operation. */
795
+ async function executeEffects(effects, runtime) {
796
+ for (const effect of effects) {
797
+ await effect.exec(runtime);
798
+ }
584
799
  }
585
800
  function resolveActionsWithContext(currentSnapshot, event, actorScope, actions) {
586
801
  let intermediateSnapshot = currentSnapshot;
587
802
  const executableActions = [];
588
803
  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;
591
804
  const actionArgs = {
592
805
  context: intermediateSnapshot.context,
593
806
  event,
@@ -599,20 +812,29 @@ function resolveActionsWithContext(currentSnapshot, event, actorScope, actions)
599
812
  actions: currentSnapshot.machine.implementations.actions,
600
813
  actorSources: currentSnapshot.machine.implementations.actorSources
601
814
  };
815
+ const isInline = typeof action === 'function';
816
+ const actionRecord = getTransitionActionRecord(action);
817
+ const resolvedAction = isInline ? action : actionRecord ? actionRecord.action.bind(null, ...actionRecord.args) : false;
602
818
  let actionParams = undefined;
603
819
  if (typeof action === 'object' && action !== null) {
604
820
  const {
605
821
  type: _,
822
+ childUpdate: _childUpdate,
606
823
  ...emittedEventParams
607
824
  } = action;
608
825
  actionParams = emittedEventParams;
609
826
  }
827
+ if (actionRecord?.childUpdate) {
828
+ intermediateSnapshot = applyChildUpdate(intermediateSnapshot, actionRecord.childUpdate, actorScope);
829
+ }
610
830
  if (resolvedAction && '_special' in resolvedAction) {
611
831
  executableActions.push({
832
+ kind: 'action',
833
+ exec: execCustomEffect,
612
834
  type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type ?? '(anonymous)' : action.name || '(anonymous)',
613
835
  params: actionParams,
614
836
  args: [],
615
- exec: undefined
837
+ action: undefined
616
838
  });
617
839
  const specialAction = resolvedAction;
618
840
  const res = specialAction(actionArgs, emptyEnqueueObject);
@@ -634,14 +856,26 @@ function resolveActionsWithContext(currentSnapshot, event, actorScope, actions)
634
856
  }
635
857
  if (!resolvedAction || !('resolve' in resolvedAction)) {
636
858
  const builtInFields = typeof action === 'object' && action !== null && 'action' in action && typeof action.action === 'function' ? getBuiltInActionFields(action.action, action.args) : undefined;
859
+ const isEmittedEvent = typeof action === 'object' && action !== null && !actionRecord;
637
860
  const executableAction = {
861
+ kind: builtInFields ? 'builtin' : isEmittedEvent ? 'emit' : 'action',
638
862
  type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type : action.name || '(anonymous)',
639
- params: actionParams,
863
+ params: builtInFields ? undefined : actionParams,
640
864
  args: typeof action === 'object' && 'action' in action ? action.args : [],
641
- exec: resolvedAction || (typeof action === 'object' && action !== null ? () => actorScope.defer(() => actorScope.emit(action)) : undefined),
865
+ ...(builtInFields ? {} : isEmittedEvent ? {
866
+ source: actorScope.self,
867
+ event: action
868
+ } : {
869
+ action: actionRecord?.action ?? (isInline ? action : undefined)
870
+ }),
871
+ ...(!builtInFields ? {
872
+ exec: isEmittedEvent ? execEmitEffect : execCustomEffect
873
+ } : {}),
642
874
  ...builtInFields
643
875
  };
644
- executableActions.push(builtInFields ? brandBuiltInExecutableAction(executableAction) : executableAction);
876
+ const typedExecutableAction = executableAction;
877
+ intermediateSnapshot = updateLogicalTimers(intermediateSnapshot, typedExecutableAction, actorScope);
878
+ executableActions.push(typedExecutableAction);
645
879
  continue;
646
880
  }
647
881
  }
@@ -1421,6 +1655,17 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1421
1655
  return [[wrappedAction], undefined, undefined];
1422
1656
  };
1423
1657
  let nextState = currentSnapshot;
1658
+ const getInvokeStopActions = (stateNode, children) => {
1659
+ const actions = [];
1660
+ const enqueue = createTransitionEnqueue(actorScope, actions, []);
1661
+ for (const invokeDef of stateNode.invoke) {
1662
+ const child = children[invokeDef.id];
1663
+ if (child) {
1664
+ enqueue.stop(child);
1665
+ }
1666
+ }
1667
+ return actions;
1668
+ };
1424
1669
  const exitStates = () => {
1425
1670
  const statesToExit = computeExitSet(filteredTransitions, mutStateNodeSet, currentSnapshot, event, actorScope);
1426
1671
  statesToExit.sort((a, b) => b.order - a.order);
@@ -1442,7 +1687,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1442
1687
  }
1443
1688
  for (const exitStateNode of statesToExit) {
1444
1689
  const stateInput = getStateInput(currentSnapshot, exitStateNode.id);
1445
- const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, currentSnapshot.children, stateInput) : [[]];
1690
+ const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, currentSnapshot.children, stateInput) : [[], undefined, undefined];
1446
1691
  if (internalEvents?.length) {
1447
1692
  internalQueue.push(...internalEvents);
1448
1693
  }
@@ -1454,12 +1699,11 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1454
1699
  const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, exitActions);
1455
1700
  nextState = resolvedState;
1456
1701
  executableActions.push(...resolvedActions);
1457
- for (const def of exitStateNode.invoke) {
1458
- const childActor = nextState.children[def.id];
1459
- if (childActor && !childActor._isExternal) {
1460
- actorScope.stopChild(childActor);
1461
- }
1462
- delete nextState.children[def.id];
1702
+ const invokeStopActions = getInvokeStopActions(exitStateNode, nextState.children);
1703
+ if (invokeStopActions.length) {
1704
+ const [stoppedState, stopEffects] = resolveActionsWithContext(nextState, event, actorScope, invokeStopActions);
1705
+ nextState = stoppedState;
1706
+ executableActions.push(...stopEffects);
1463
1707
  }
1464
1708
  mutStateNodeSet.delete(exitStateNode);
1465
1709
  }
@@ -1630,7 +1874,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1630
1874
  }
1631
1875
  const completedNodes = new Set();
1632
1876
  const children = {
1633
- ...currentSnapshot.children
1877
+ ...nextState.children
1634
1878
  };
1635
1879
  for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
1636
1880
  mutStateNodeSet.add(stateNodeToEnter);
@@ -1657,7 +1901,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1657
1901
  event,
1658
1902
  output: getEventOutput(event)
1659
1903
  }) : invokeDef.input;
1660
- const actor = createActor(logic, {
1904
+ const actor = actorScope.system.createActorRef(logic, {
1661
1905
  ...invokeDef,
1662
1906
  input,
1663
1907
  parent: actorScope.self,
@@ -1786,6 +2030,12 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1786
2030
  const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, allExitActions);
1787
2031
  nextState = resolvedState;
1788
2032
  executableActions.push(...resolvedActions);
2033
+ const [stoppedState, stopEffects] = stopChildren(nextState, event, actorScope);
2034
+ nextState = stoppedState;
2035
+ executableActions.push(...stopEffects);
2036
+ const [withoutTimers, cancelEffects] = cancelTimers(nextState, event, actorScope);
2037
+ nextState = withoutTimers;
2038
+ executableActions.push(...cancelEffects);
1789
2039
  }
1790
2040
  if (historyValue === currentSnapshot.historyValue && currentSnapshot._nodes.length === mutStateNodeSet.size && currentSnapshot._nodes.every(node => mutStateNodeSet.has(node))) {
1791
2041
  // If context was changed (e.g. by entry actions during self-transition),
@@ -1859,9 +2109,27 @@ function getTransitionResult(transition, snapshot, event, actorScope, options) {
1859
2109
  input: resolvedInput
1860
2110
  };
1861
2111
  }
1862
- function macrostep(snapshot, event, actorScope, internalQueue) {
2112
+ function macrostep(snapshot, event, actorScope, internalQueue, initialMicrosteps = []) {
1863
2113
  let nextSnapshot = snapshot;
1864
- const microsteps = [];
2114
+ const microsteps = initialMicrosteps.slice();
2115
+ function completeMacrostep() {
2116
+ const effects = microsteps.flatMap(([, actions]) => actions);
2117
+ const starts = deriveDeferredStarts(effects);
2118
+ const shouldTerminate = (snapshot.status === 'active' || initialMicrosteps.length > 0) && (nextSnapshot.status === 'done' || nextSnapshot.status === 'error');
2119
+ if (starts.length || shouldTerminate) {
2120
+ const terminalEffects = shouldTerminate ? [...starts, createTerminationEffect(actorScope, nextSnapshot)] : starts;
2121
+ if (microsteps.length) {
2122
+ const [lastSnapshot, lastEffects] = microsteps.at(-1);
2123
+ microsteps[microsteps.length - 1] = [lastSnapshot, [...lastEffects, ...terminalEffects]];
2124
+ } else {
2125
+ microsteps.push([nextSnapshot, terminalEffects]);
2126
+ }
2127
+ }
2128
+ return {
2129
+ snapshot: nextSnapshot,
2130
+ microsteps
2131
+ };
2132
+ }
1865
2133
  function addMicrostep(step, transitions) {
1866
2134
  // collect microsteps; surfaced on the enclosing '@xstate.transition' event
1867
2135
  // via its `microsteps[]` facet (there is no standalone microstep event)
@@ -1873,20 +2141,39 @@ function macrostep(snapshot, event, actorScope, internalQueue) {
1873
2141
 
1874
2142
  // Handle stop event
1875
2143
  if (event.type === XSTATE_STOP) {
1876
- nextSnapshot = cloneMachineSnapshot(stopChildren(nextSnapshot, actorScope), {
2144
+ const [stoppedChildrenSnapshot, stopEffects] = stopChildren(nextSnapshot, event, actorScope);
2145
+ const [withoutTimers, cancelEffects] = cancelTimers(stoppedChildrenSnapshot, event, actorScope);
2146
+ nextSnapshot = cloneMachineSnapshot(withoutTimers, {
1877
2147
  status: 'stopped'
1878
2148
  });
1879
- addMicrostep([nextSnapshot, []], []);
1880
- return {
1881
- snapshot: nextSnapshot,
1882
- microsteps
1883
- };
2149
+ addMicrostep([nextSnapshot, [...stopEffects, ...cancelEffects]], []);
2150
+ return completeMacrostep();
1884
2151
  }
1885
2152
  let nextEvent = event;
2153
+ if (event.type === XSTATE_TIMER) {
2154
+ const timer = nextSnapshot.timers[event.id];
2155
+ if (!timer) {
2156
+ return completeMacrostep();
2157
+ }
2158
+ const timers = {
2159
+ ...nextSnapshot.timers
2160
+ };
2161
+ delete timers[timer.id];
2162
+ nextSnapshot = cloneMachineSnapshot(nextSnapshot, {
2163
+ timers
2164
+ });
2165
+ if (timer.type === '@xstate.raise') {
2166
+ internalQueue.push(timer.event);
2167
+ addMicrostep([nextSnapshot, []], []);
2168
+ } else {
2169
+ const target = timer.target === 'self' ? actorScope.self : timer.target;
2170
+ addMicrostep([nextSnapshot, [createSendToEffect(actorScope, target, timer.event)]], []);
2171
+ }
2172
+ }
1886
2173
 
1887
2174
  // Assume the state is at rest (no raised events)
1888
2175
  // Determine the next state based on the next microstep
1889
- if (nextEvent.type !== XSTATE_INIT) {
2176
+ if (nextEvent.type !== XSTATE_INIT && nextEvent.type !== XSTATE_TIMER) {
1890
2177
  const currentEvent = nextEvent;
1891
2178
  const isErr = isErrorEvent(currentEvent);
1892
2179
  const transitions = nextSnapshot.machine.getTransitionData(nextSnapshot, currentEvent, actorScope.self);
@@ -1899,10 +2186,7 @@ function macrostep(snapshot, event, actorScope, internalQueue) {
1899
2186
  error: currentEvent.error
1900
2187
  });
1901
2188
  addMicrostep([nextSnapshot, []], []);
1902
- return {
1903
- snapshot: nextSnapshot,
1904
- microsteps
1905
- };
2189
+ return completeMacrostep();
1906
2190
  }
1907
2191
  const step = microstep(transitions, snapshot, actorScope, nextEvent, false,
1908
2192
  // isInitial
@@ -1940,13 +2224,7 @@ function macrostep(snapshot, event, actorScope, internalQueue) {
1940
2224
  shouldSelectEventlessTransitions = nextSnapshot !== previousState;
1941
2225
  addMicrostep(step, enabledTransitions);
1942
2226
  }
1943
- if (nextSnapshot.status !== 'active' && nextSnapshot.children) {
1944
- stopChildren(nextSnapshot, actorScope);
1945
- }
1946
- return {
1947
- snapshot: nextSnapshot,
1948
- microsteps
1949
- };
2227
+ return completeMacrostep();
1950
2228
  }
1951
2229
 
1952
2230
  /**
@@ -2006,17 +2284,29 @@ function transitionToHasEffect(transitionTo, context, event, snapshot, self, imp
2006
2284
  }
2007
2285
  return res !== undefined;
2008
2286
  }
2009
- function stopChildren(snapshot, actorScope) {
2287
+ function stopChildren(snapshot, event, actorScope) {
2010
2288
  let children;
2011
2289
  if (!snapshot.children || (children = Object.values(snapshot.children).filter(Boolean)).length === 0) {
2012
- return snapshot;
2290
+ return [snapshot, []];
2013
2291
  }
2292
+ const actions = [];
2293
+ const enqueue = createTransitionEnqueue(actorScope, actions, []);
2014
2294
  for (const child of children) {
2015
- actorScope.stopChild(child);
2295
+ enqueue.stop(child);
2016
2296
  }
2017
- return cloneMachineSnapshot(snapshot, {
2018
- children: {}
2019
- });
2297
+ return resolveActionsWithContext(snapshot, event, actorScope, actions);
2298
+ }
2299
+ function cancelTimers(snapshot, event, actorScope) {
2300
+ const timerIds = Object.keys(snapshot.timers);
2301
+ if (!timerIds.length) {
2302
+ return [snapshot, []];
2303
+ }
2304
+ const actions = [];
2305
+ const enqueue = createTransitionEnqueue(actorScope, actions, []);
2306
+ for (const id of timerIds) {
2307
+ enqueue.cancel(id);
2308
+ }
2309
+ return resolveActionsWithContext(snapshot, event, actorScope, actions);
2020
2310
  }
2021
2311
  function selectEventlessTransitions(snapshot, event, actorScope) {
2022
2312
  const enabledTransitionSet = new Set();
@@ -2061,6 +2351,129 @@ function evaluateCandidate(candidate, event, snapshot, stateNode, self) {
2061
2351
  return true;
2062
2352
  }
2063
2353
 
2354
+ /** @internal */
2355
+ const snapshotActorRef = Symbol('xstate.snapshotActorRef');
2356
+
2357
+ /** Snapshot-scoped actor identity and system view. @internal */
2358
+
2359
+ function getSnapshotChildren(snapshot) {
2360
+ return 'children' in snapshot && snapshot.children ? snapshot.children : {};
2361
+ }
2362
+
2363
+ /**
2364
+ * Creates the isolated receptionist/id view used by a pure branch. Scheduling,
2365
+ * relaying and clocks remain delegated to the snapshot's execution system.
2366
+ *
2367
+ * @internal
2368
+ */
2369
+ function createSnapshotSystem(baseSystem, self, children, baseState) {
2370
+ const registeredActors = new Map(baseState?.children ?? baseSystem.children);
2371
+ const keyedActors = new Map(baseState?.keyedActors ?? Object.entries(baseSystem.getAll()).filter(entry => !!entry[1]));
2372
+ const reverseKeyedActors = new WeakMap();
2373
+ let booksSelf = true;
2374
+ const system = {
2375
+ ...baseSystem,
2376
+ children: registeredActors,
2377
+ keyedActors,
2378
+ reverseKeyedActors,
2379
+ _snapshot: {
2380
+ ...(baseState?.snapshot ?? baseSystem._snapshot)
2381
+ },
2382
+ _bookId: () => {
2383
+ if (booksSelf) {
2384
+ booksSelf = false;
2385
+ return self.sessionId ?? self.id;
2386
+ }
2387
+ return `x:${system._snapshot._nextActorId++}`;
2388
+ },
2389
+ _register: (sessionId, actor) => {
2390
+ registeredActors.set(sessionId, actor);
2391
+ return sessionId;
2392
+ },
2393
+ _unregister: actor => {
2394
+ if (actor.sessionId) {
2395
+ registeredActors.delete(actor.sessionId);
2396
+ }
2397
+ const registryKey = reverseKeyedActors.get(actor);
2398
+ if (registryKey !== undefined && keyedActors.get(registryKey) === actor) {
2399
+ keyedActors.delete(registryKey);
2400
+ }
2401
+ reverseKeyedActors.delete(actor);
2402
+ },
2403
+ _set: (registryKey, actor) => {
2404
+ const existing = keyedActors.get(registryKey);
2405
+ if (existing && existing !== actor) {
2406
+ throw new Error(`Actor with registry key '${String(registryKey)}' already exists.`);
2407
+ }
2408
+ keyedActors.set(registryKey, actor);
2409
+ reverseKeyedActors.set(actor, registryKey);
2410
+ },
2411
+ get: registryKey => keyedActors.get(registryKey),
2412
+ getAll: () => Object.fromEntries(keyedActors),
2413
+ // Pure transition resolution must not leak topology inspection events into
2414
+ // a live runtime system.
2415
+ _sendInspectionEvent: () => {}
2416
+ };
2417
+ for (const [registryKey, actor] of keyedActors) {
2418
+ reverseKeyedActors.set(actor, registryKey);
2419
+ }
2420
+ for (const actor of Object.values(children)) {
2421
+ if (!actor) {
2422
+ continue;
2423
+ }
2424
+ if (actor.sessionId) {
2425
+ registeredActors.set(actor.sessionId, actor);
2426
+ }
2427
+ const registryKey = actor.registryKey;
2428
+ if (registryKey) {
2429
+ keyedActors.set(registryKey, actor);
2430
+ reverseKeyedActors.set(actor, registryKey);
2431
+ }
2432
+ }
2433
+ return system;
2434
+ }
2435
+
2436
+ /** Returns the actor identity associated with a transition snapshot. @internal */
2437
+ function getSnapshotActorRef(snapshot) {
2438
+ return snapshot[snapshotActorRef];
2439
+ }
2440
+
2441
+ /**
2442
+ * Copies an internal actor association without rebuilding its system view.
2443
+ *
2444
+ * @internal
2445
+ */
2446
+ function copySnapshotActorRef(source, target) {
2447
+ const ref = getSnapshotActorRef(source);
2448
+ if (ref) {
2449
+ Object.defineProperty(target, snapshotActorRef, {
2450
+ configurable: true,
2451
+ value: ref
2452
+ });
2453
+ }
2454
+ }
2455
+
2456
+ /**
2457
+ * Associates a transition snapshot with an actor and an immutable system view.
2458
+ * The internal association is excluded from enumeration and persistence.
2459
+ *
2460
+ * @internal
2461
+ */
2462
+ function setSnapshotActorRef(snapshot, actor, baseSystem = actor.system) {
2463
+ const system = createSnapshotSystem(baseSystem, actor, getSnapshotChildren(snapshot));
2464
+ Object.defineProperty(snapshot, snapshotActorRef, {
2465
+ configurable: true,
2466
+ value: {
2467
+ actor,
2468
+ systemState: {
2469
+ children: system.children,
2470
+ keyedActors: system.keyedActors,
2471
+ snapshot: system._snapshot
2472
+ }
2473
+ }
2474
+ });
2475
+ }
2476
+
2064
2477
  function isMachineSnapshot(value) {
2065
2478
  return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
2066
2479
  }
@@ -2080,6 +2493,7 @@ const machineSnapshotToJSON = function toJSON() {
2080
2493
  const {
2081
2494
  _nodes: nodes,
2082
2495
  _stateInputs,
2496
+ [snapshotActorRef]: _actorRef,
2083
2497
  tags,
2084
2498
  machine,
2085
2499
  getMeta,
@@ -2116,8 +2530,8 @@ function collectTags(stateNodes) {
2116
2530
  }
2117
2531
  return tags;
2118
2532
  }
2119
- function createMachineSnapshot(config, machine) {
2120
- return {
2533
+ function createMachineSnapshot(config, machine, actorRef) {
2534
+ const snapshot = {
2121
2535
  status: config.status,
2122
2536
  output: config.output,
2123
2537
  error: config.error,
@@ -2127,8 +2541,10 @@ function createMachineSnapshot(config, machine) {
2127
2541
  value: config.value ?? getStateValue(machine.root, config._nodes),
2128
2542
  tags: collectTags(config._nodes),
2129
2543
  children: config.children,
2544
+ timers: config.timers ?? {},
2130
2545
  historyValue: config.historyValue || {},
2131
2546
  _stateInputs: config._stateInputs || {},
2547
+ _nextTimerId: config._nextTimerId ?? 0,
2132
2548
  matches: machineSnapshotMatches,
2133
2549
  hasTag: machineSnapshotHasTag,
2134
2550
  can: machineSnapshotCan,
@@ -2136,6 +2552,10 @@ function createMachineSnapshot(config, machine) {
2136
2552
  getInputs: machineSnapshotGetInputs,
2137
2553
  toJSON: machineSnapshotToJSON
2138
2554
  };
2555
+ if (actorRef) {
2556
+ setSnapshotActorRef(snapshot, actorRef);
2557
+ }
2558
+ return snapshot;
2139
2559
  }
2140
2560
  function cloneMachineSnapshot(snapshot, config = {}) {
2141
2561
  const configWithSnapshot = {
@@ -2143,7 +2563,7 @@ function cloneMachineSnapshot(snapshot, config = {}) {
2143
2563
  ...config
2144
2564
  };
2145
2565
  if ((config._nodes ?? snapshot._nodes) === snapshot._nodes) {
2146
- return {
2566
+ const clonedSnapshot = {
2147
2567
  status: configWithSnapshot.status,
2148
2568
  output: configWithSnapshot.output,
2149
2569
  error: configWithSnapshot.error,
@@ -2153,8 +2573,10 @@ function cloneMachineSnapshot(snapshot, config = {}) {
2153
2573
  value: snapshot.value,
2154
2574
  tags: snapshot.tags,
2155
2575
  children: configWithSnapshot.children,
2576
+ timers: configWithSnapshot.timers ?? {},
2156
2577
  historyValue: configWithSnapshot.historyValue || {},
2157
2578
  _stateInputs: configWithSnapshot._stateInputs || {},
2579
+ _nextTimerId: configWithSnapshot._nextTimerId ?? 0,
2158
2580
  matches: machineSnapshotMatches,
2159
2581
  hasTag: machineSnapshotHasTag,
2160
2582
  can: machineSnapshotCan,
@@ -2162,11 +2584,15 @@ function cloneMachineSnapshot(snapshot, config = {}) {
2162
2584
  getInputs: machineSnapshotGetInputs,
2163
2585
  toJSON: machineSnapshotToJSON
2164
2586
  };
2587
+ copySnapshotActorRef(snapshot, clonedSnapshot);
2588
+ return clonedSnapshot;
2165
2589
  }
2166
- return createMachineSnapshot({
2590
+ const clonedSnapshot = createMachineSnapshot({
2167
2591
  ...configWithSnapshot,
2168
2592
  value: undefined
2169
2593
  }, snapshot.machine);
2594
+ copySnapshotActorRef(snapshot, clonedSnapshot);
2595
+ return clonedSnapshot;
2170
2596
  }
2171
2597
  function serializeHistoryValue(historyValue) {
2172
2598
  const result = {};
@@ -2187,6 +2613,7 @@ function getPersistedSnapshot(snapshot, options) {
2187
2613
  tags,
2188
2614
  machine,
2189
2615
  children,
2616
+ timers,
2190
2617
  context,
2191
2618
  can,
2192
2619
  hasTag,
@@ -2197,6 +2624,7 @@ function getPersistedSnapshot(snapshot, options) {
2197
2624
  ...jsonValues
2198
2625
  } = snapshot;
2199
2626
  const childrenJson = {};
2627
+ const timersJson = {};
2200
2628
  for (const id in children) {
2201
2629
  const child = children[id];
2202
2630
  childrenJson[id] = {
@@ -2206,10 +2634,36 @@ function getPersistedSnapshot(snapshot, options) {
2206
2634
  syncSnapshot: child._syncSnapshot
2207
2635
  };
2208
2636
  }
2637
+ for (const id in timers) {
2638
+ const timer = timers[id];
2639
+ let target;
2640
+ if (timer.target === 'self') {
2641
+ target = 'self';
2642
+ } else {
2643
+ const childId = Object.entries(children).find(([, child]) => child === timer.target)?.[0];
2644
+ if (childId) {
2645
+ target = childId;
2646
+ } else if (timer.target === getSnapshotActorRef(snapshot)?.actor._parent) {
2647
+ target = {
2648
+ type: 'parent'
2649
+ };
2650
+ } else {
2651
+ throw new Error(`Unable to persist timer '${id}': target actor '${timer.target.id}' is no longer addressable from this snapshot.`);
2652
+ }
2653
+ }
2654
+ timersJson[id] = {
2655
+ id: timer.id,
2656
+ delay: timer.delay,
2657
+ type: timer.type,
2658
+ event: timer.event,
2659
+ target
2660
+ };
2661
+ }
2209
2662
  const persisted = {
2210
2663
  ...jsonValues,
2211
2664
  context: persistContext(context),
2212
2665
  children: childrenJson,
2666
+ timers: timersJson,
2213
2667
  historyValue: serializeHistoryValue(jsonValues.historyValue)
2214
2668
  };
2215
2669
  if (_stateInputs && Object.keys(_stateInputs).length > 0) {
@@ -2467,11 +2921,17 @@ function matchesEventDescriptor(eventType, descriptor) {
2467
2921
  return true;
2468
2922
  }
2469
2923
 
2470
- function createScheduledEventId(actor, id) {
2924
+ /**
2925
+ * Runtime operations used to execute effects.
2926
+ *
2927
+ * An external interpreter can override these operations while the default actor
2928
+ * system provides the local in-memory implementation.
2929
+ */
2930
+
2931
+ function createScheduledTimerId(actor, id) {
2471
2932
  return `${actor.sessionId}.${id}`;
2472
2933
  }
2473
2934
  function createRuntimeSystem(rootActor, options) {
2474
- let idCounter = 0;
2475
2935
  const children = new Map();
2476
2936
  const keyedActors = new Map();
2477
2937
  const reverseKeyedActors = new WeakMap();
@@ -2499,42 +2959,50 @@ function createRuntimeSystem(rootActor, options) {
2499
2959
  });
2500
2960
  };
2501
2961
  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 = {
2962
+ schedule: (source, id, delay) => {
2963
+ const existingId = createScheduledTimerId(source, id);
2964
+ if (timerMap[existingId] !== undefined) {
2965
+ scheduler.cancel(source, id);
2966
+ }
2967
+ const timer = source.getSnapshot()?.timers?.[id];
2968
+ if (timer) {
2969
+ const target = timer.target === 'self' ? source : timer.target;
2970
+ recordSent(source, target, timer.event, delay, id);
2971
+ }
2972
+ const scheduledAt = Date.now();
2973
+ const scheduledTimer = {
2505
2974
  source,
2506
- target,
2507
- event,
2508
2975
  delay,
2509
2976
  id,
2510
- startedAt: Date.now()
2977
+ scheduledAt,
2978
+ dueAt: scheduledAt + delay
2511
2979
  };
2512
- const scheduledEventId = createScheduledEventId(source, id);
2513
- system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
2980
+ const scheduledTimerId = createScheduledTimerId(source, id);
2981
+ system._snapshot._scheduledTimers[scheduledTimerId] = scheduledTimer;
2514
2982
  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);
2983
+ delete timerMap[scheduledTimerId];
2984
+ delete system._snapshot._scheduledTimers[scheduledTimerId];
2985
+ deliver(source, source, {
2986
+ type: XSTATE_TIMER,
2987
+ id
2988
+ });
2521
2989
  }, delay);
2522
- timerMap[scheduledEventId] = timeout;
2990
+ timerMap[scheduledTimerId] = timeout;
2523
2991
  },
2524
2992
  cancel: (source, id) => {
2525
- const scheduledEventId = createScheduledEventId(source, id);
2526
- const timeout = timerMap[scheduledEventId];
2527
- delete timerMap[scheduledEventId];
2528
- delete system._snapshot._scheduledEvents[scheduledEventId];
2993
+ const scheduledTimerId = createScheduledTimerId(source, id);
2994
+ const timeout = timerMap[scheduledTimerId];
2995
+ delete timerMap[scheduledTimerId];
2996
+ delete system._snapshot._scheduledTimers[scheduledTimerId];
2529
2997
  if (timeout !== undefined) {
2530
2998
  clock.clearTimeout(timeout);
2531
2999
  }
2532
3000
  },
2533
3001
  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);
3002
+ for (const scheduledTimerId in system._snapshot._scheduledTimers) {
3003
+ const scheduledTimer = system._snapshot._scheduledTimers[scheduledTimerId];
3004
+ if (scheduledTimer.source === actor) {
3005
+ scheduler.cancel(actor, scheduledTimer.id);
2538
3006
  }
2539
3007
  }
2540
3008
  }
@@ -2567,9 +3035,10 @@ function createRuntimeSystem(rootActor, options) {
2567
3035
  reverseKeyedActors,
2568
3036
  keyedActors,
2569
3037
  _snapshot: {
2570
- _scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
3038
+ _scheduledTimers: (options?.snapshot && options.snapshot.scheduler) ?? {},
3039
+ _nextActorId: 0
2571
3040
  },
2572
- _bookId: () => `x:${idCounter++}`,
3041
+ _bookId: () => `x:${system._snapshot._nextActorId++}`,
2573
3042
  _register: (sessionId, actor) => {
2574
3043
  children.set(sessionId, actor);
2575
3044
  return sessionId;
@@ -2578,7 +3047,9 @@ function createRuntimeSystem(rootActor, options) {
2578
3047
  children.delete(actor.sessionId);
2579
3048
  const registryKey = reverseKeyedActors.get(actor);
2580
3049
  if (registryKey !== undefined) {
2581
- keyedActors.delete(registryKey);
3050
+ if (keyedActors.get(registryKey) === actor) {
3051
+ keyedActors.delete(registryKey);
3052
+ }
2582
3053
  reverseKeyedActors.delete(actor);
2583
3054
  }
2584
3055
  },
@@ -2606,35 +3077,48 @@ function createRuntimeSystem(rootActor, options) {
2606
3077
  };
2607
3078
  },
2608
3079
  _sendInspectionEvent: sendInspectionEvent,
2609
- _relay: (source, target, event) => {
3080
+ createActorRef: options.createActorRef,
3081
+ spawnActor: () => {},
3082
+ startActor: actor => {
3083
+ actor.start();
3084
+ },
3085
+ stopActor: actor => {
3086
+ actor._stop();
3087
+ },
3088
+ terminateActor: (actor, termination) => {
3089
+ actor._terminate(termination);
3090
+ },
3091
+ sendEvent: (source, target, event) => {
2610
3092
  recordSent(source, target, event);
2611
3093
  deliver(source, target, event);
2612
3094
  },
3095
+ emitEvent: (source, event) => source._emit(event),
3096
+ scheduleTimer: (source, id, delay) => scheduler.schedule(source, id, delay),
3097
+ cancelTimer: (source, id) => scheduler.cancel(source, id),
3098
+ cancelAllTimers: source => scheduler.cancelAll(source),
3099
+ _relay: (source, target, event) => system.sendEvent(source, target, event),
2613
3100
  scheduler,
2614
3101
  getSnapshot: () => {
2615
3102
  return {
2616
- _scheduledEvents: {
2617
- ...system._snapshot._scheduledEvents
3103
+ _scheduledTimers: {
3104
+ ...system._snapshot._scheduledTimers
2618
3105
  }
2619
3106
  };
2620
3107
  },
2621
3108
  start: () => {
2622
- const scheduledEvents = system._snapshot._scheduledEvents;
2623
- system._snapshot._scheduledEvents = {};
2624
- for (const scheduledId in scheduledEvents) {
3109
+ const scheduledTimers = system._snapshot._scheduledTimers;
3110
+ system._snapshot._scheduledTimers = {};
3111
+ for (const scheduledId in scheduledTimers) {
2625
3112
  const {
2626
3113
  source,
2627
- target,
2628
- event,
2629
- delay,
3114
+ dueAt,
2630
3115
  id
2631
- } = scheduledEvents[scheduledId];
2632
- scheduler.schedule(source, target, event, delay, id);
3116
+ } = scheduledTimers[scheduledId];
3117
+ system.scheduleTimer(source, id, Math.max(0, dueAt - Date.now()));
2633
3118
  }
2634
3119
  },
2635
3120
  _clock: clock,
2636
- _logger: logger,
2637
- _timerStrategy: options.timers
3121
+ _logger: logger
2638
3122
  };
2639
3123
  return system;
2640
3124
  }
@@ -2669,22 +3153,13 @@ function safeCall(fn, arg) {
2669
3153
  reportUnhandledError(err);
2670
3154
  }
2671
3155
  }
2672
- function isExecutableActionObject(effect) {
2673
- return typeof effect === 'object' && effect !== null && 'args' in effect && 'exec' in effect;
2674
- }
2675
3156
  function executeExecutableEffects(effects, actorScope) {
2676
3157
  if (!effects?.length) {
2677
- return [];
3158
+ return;
2678
3159
  }
2679
- const logicEffects = [];
2680
3160
  for (const effect of effects) {
2681
- if (isExecutableActionObject(effect)) {
2682
- actorScope.actionExecutor(effect);
2683
- } else {
2684
- logicEffects.push(effect);
2685
- }
3161
+ actorScope.actionExecutor(effect);
2686
3162
  }
2687
- return logicEffects;
2688
3163
  }
2689
3164
 
2690
3165
  /**
@@ -2750,8 +3225,7 @@ class Actor {
2750
3225
  this.src = void 0;
2751
3226
  // array of functions to defer
2752
3227
  this._deferred = [];
2753
- // pending effects (timers) from a persisted snapshot, rescheduled on `start()`
2754
- this._pendingEffects = void 0;
3228
+ this._restored = false;
2755
3229
  const resolvedOptions = {
2756
3230
  ...defaultOptions,
2757
3231
  ...options
@@ -2768,7 +3242,7 @@ class Actor {
2768
3242
  this.system = parent ? parent.system : resolvedOptions._systemRef?.current ?? createRuntimeSystem(this, {
2769
3243
  clock,
2770
3244
  logger,
2771
- timers: resolvedOptions.timers
3245
+ createActorRef: (childLogic, childOptions) => createActor(childLogic, childOptions)
2772
3246
  });
2773
3247
  if (!parent && resolvedOptions._systemRef && !resolvedOptions._systemRef.current) {
2774
3248
  resolvedOptions._systemRef.current = this.system;
@@ -2801,15 +3275,7 @@ class Actor {
2801
3275
  }
2802
3276
  child._stop();
2803
3277
  },
2804
- emit: emittedEvent => {
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);
2809
- }
2810
- }
2811
- }
2812
- },
3278
+ emit: emittedEvent => this.system.emitEvent(this, emittedEvent),
2813
3279
  actionExecutor: action => {
2814
3280
  const exec = () => {
2815
3281
  // Record every executed action for the '@xstate.transition' inspection
@@ -2818,13 +3284,10 @@ class Actor {
2818
3284
  type: action.type,
2819
3285
  params: action.params
2820
3286
  });
2821
- if (!action.exec) {
2822
- return;
2823
- }
2824
3287
  const saveExecutingCustomAction = executingCustomAction;
2825
3288
  try {
2826
3289
  executingCustomAction = true;
2827
- action.exec();
3290
+ void action.exec();
2828
3291
  } finally {
2829
3292
  executingCustomAction = saveExecutingCustomAction;
2830
3293
  }
@@ -2857,38 +3320,29 @@ class Actor {
2857
3320
 
2858
3321
  // prepare to collect initial microsteps during initialTransition
2859
3322
  this._collectedMicrosteps = [];
2860
- let persistedState = options?.snapshot ?? options?.state;
2861
- if (persistedState && typeof persistedState === 'object' && '_pendingEffects' in persistedState) {
2862
- // Pending effects are restored by this actor on `start()`; the logic
2863
- // only sees the rest of the persisted snapshot.
2864
- const {
2865
- _pendingEffects,
2866
- ...rest
2867
- } = persistedState;
2868
- this._pendingEffects = _pendingEffects;
2869
- persistedState = rest;
2870
- }
3323
+ const persistedState = options?.snapshot ?? options?.state;
3324
+ this._restored = persistedState !== undefined;
2871
3325
  try {
2872
3326
  if (persistedState) {
2873
- this._snapshot = this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState;
3327
+ this._setSnapshot(this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState);
2874
3328
  } else if (options?._inert) {
2875
3329
  // Inert actors (createInertActorScope) only anchor a scope for pure
2876
3330
  // transition functions; computing an initial snapshot here would run
2877
3331
  // init-time side effects (context factories, entry) a second time.
2878
3332
  } else {
2879
- const [snapshot, effects] = this.logic.initialTransition(this.options?.input, this._actorScope);
2880
- this._snapshot = snapshot;
3333
+ const [snapshot, effects] = finalizeTransitionResult(this._actorScope, undefined, this.logic.initialTransition(this.options?.input, this._actorScope));
3334
+ this._setSnapshot(snapshot);
2881
3335
  this._initialEffects = effects;
2882
3336
  }
2883
3337
  } catch (err) {
2884
3338
  // if we get here then it means that we assign a value to this._snapshot that is not of the correct type
2885
3339
  // we can't get the true `TSnapshot & { status: 'error'; }`, it's impossible
2886
3340
  // so right now this is a lie of sorts
2887
- this._snapshot = {
3341
+ this._setSnapshot({
2888
3342
  status: 'error',
2889
3343
  output: undefined,
2890
3344
  error: err
2891
- };
3345
+ });
2892
3346
  // discard any functions deferred during the failed initial snapshot
2893
3347
  // computation so they can't run against an inconsistent actor
2894
3348
  this._deferred.length = 0;
@@ -2909,12 +3363,17 @@ class Actor {
2909
3363
  snapshot: this._snapshot
2910
3364
  });
2911
3365
  }
3366
+ /** Associates each live snapshot with this actor for later pure transitions. */
3367
+ _setSnapshot(snapshot) {
3368
+ this._snapshot = snapshot;
3369
+ setSnapshotActorRef(snapshot, this);
3370
+ }
2912
3371
  _setErrorSnapshot(err, snapshot = this._snapshot) {
2913
- this._snapshot = {
3372
+ this._setSnapshot({
2914
3373
  ...snapshot,
2915
3374
  status: 'error',
2916
3375
  error: err
2917
- };
3376
+ });
2918
3377
  }
2919
3378
 
2920
3379
  /** Recover via the logic's error event if possible; otherwise error out. */
@@ -2932,11 +3391,10 @@ class Actor {
2932
3391
  return false;
2933
3392
  }
2934
3393
  try {
2935
- const [nextSnapshot, effects] = this.logic.transition(snapshot, errorEvent, this._actorScope);
2936
- this._snapshot = nextSnapshot;
2937
- const logicEffects = executeExecutableEffects(effects, this._actorScope);
3394
+ const [nextSnapshot, effects] = finalizeTransitionResult(this._actorScope, snapshot, this.logic.transition(snapshot, errorEvent, this._actorScope));
3395
+ this._setSnapshot(nextSnapshot);
3396
+ executeExecutableEffects(effects, this._actorScope);
2938
3397
  this.update(nextSnapshot, errorEvent);
2939
- this.logic.executeEffects?.(logicEffects, this._actorScope);
2940
3398
  return true;
2941
3399
  } catch {
2942
3400
  return false;
@@ -2949,7 +3407,7 @@ class Actor {
2949
3407
  }
2950
3408
  update(snapshot, event) {
2951
3409
  // Update state
2952
- this._snapshot = snapshot;
3410
+ this._setSnapshot(snapshot);
2953
3411
 
2954
3412
  // Execute deferred effects
2955
3413
  for (let i = 0; i < this._deferred.length; i++) {
@@ -2967,6 +3425,7 @@ class Actor {
2967
3425
  return;
2968
3426
  }
2969
3427
  this._setErrorSnapshot(err, snapshot);
3428
+ this._error(err);
2970
3429
  break;
2971
3430
  }
2972
3431
  }
@@ -2975,26 +3434,6 @@ class Actor {
2975
3434
  case 'active':
2976
3435
  this._next(snapshot);
2977
3436
  break;
2978
- case 'done':
2979
- {
2980
- // next observers are meant to be notified about done snapshots
2981
- // this can be seen as something that is different from how observable work
2982
- // but with observables `complete` callback is called without any arguments
2983
- // it's more ergonomic for XState to treat a done snapshot as a "next" value
2984
- // and the completion event as something that is separate,
2985
- // something that merely follows emitting that done snapshot
2986
- this._next(snapshot);
2987
- this._stopProcedure();
2988
- this._complete();
2989
- const doneEvent = createDoneActorEvent(this.id, this._snapshot.output);
2990
- if (this._parent) {
2991
- this.system._relay(this, this._parent, doneEvent);
2992
- }
2993
- break;
2994
- }
2995
- case 'error':
2996
- this._error(this._snapshot.error);
2997
- break;
2998
3437
  }
2999
3438
  this.system._sendInspectionEvent({
3000
3439
  type: '@xstate.transition',
@@ -3019,8 +3458,7 @@ class Actor {
3019
3458
  }
3020
3459
  this._forceDeferredActions = true;
3021
3460
  try {
3022
- const logicEffects = executeExecutableEffects(this._initialEffects, this._actorScope);
3023
- this.logic.executeEffects?.(logicEffects, this._actorScope);
3461
+ executeExecutableEffects(this._initialEffects, this._actorScope);
3024
3462
  this._initialEffects = undefined;
3025
3463
  return true;
3026
3464
  } catch (err) {
@@ -3191,6 +3629,14 @@ class Actor {
3191
3629
  case 'done':
3192
3630
  // a state machine can be "done" upon initialization (it could reach a final state using initial microsteps)
3193
3631
  // we still need to complete observers, flush deferreds etc
3632
+ if (this._restored) {
3633
+ // Restoration does not replay the transition that originally
3634
+ // terminated the actor, and must not notify the parent again.
3635
+ this._next(this._snapshot);
3636
+ this._stopProcedure();
3637
+ this._complete();
3638
+ return this;
3639
+ }
3194
3640
  if (!this._flushInitialEffects()) {
3195
3641
  return this;
3196
3642
  }
@@ -3204,33 +3650,31 @@ class Actor {
3204
3650
  if (!this._parent) {
3205
3651
  this.system.start();
3206
3652
  }
3653
+ if (!this._flushInitialEffects()) {
3654
+ return this;
3655
+ }
3207
3656
  if (this.logic.start) {
3208
3657
  try {
3209
- this.logic.start(this._snapshot, this._actorScope);
3658
+ this.logic.start(this._snapshot, this._actorScope, {
3659
+ restored: this._restored
3660
+ });
3210
3661
  } catch (err) {
3211
3662
  this._setErrorSnapshot(err);
3212
3663
  this._error(err);
3213
3664
  return this;
3214
3665
  }
3215
3666
  }
3216
- if (!this._flushInitialEffects()) {
3217
- return this;
3218
- }
3219
3667
 
3220
3668
  // TODO: this notifies all subscribers but usually this is redundant
3221
3669
  // there is no real change happening here
3222
3670
  // we need to rethink if this needs to be refactored
3223
3671
  this.update(this._snapshot, initEvent);
3224
- if (this._pendingEffects) {
3225
- const strategy = this.options.timers ?? this.system._timerStrategy ?? 'resume';
3226
- for (const effect of this._pendingEffects) {
3227
- // Re-execute the pending effect with its remaining delay. Only
3228
- // self-targeted raises exist today; other effect types are ignored.
3229
- if (effect.type === '@xstate.raise') {
3230
- this.system.scheduler.schedule(this, this, effect.event, resolveRestoredTimerDelay(strategy, effect), effect.id);
3231
- }
3672
+ if (this._restored) {
3673
+ const timers = this._snapshot.timers ?? {};
3674
+ for (const timer of Object.values(timers)) {
3675
+ this.system.scheduleTimer(this, timer.id, timer.delay);
3232
3676
  }
3233
- this._pendingEffects = undefined;
3677
+ this._restored = false;
3234
3678
  }
3235
3679
  this.mailbox.start();
3236
3680
  return this;
@@ -3239,7 +3683,7 @@ class Actor {
3239
3683
  let nextState;
3240
3684
  let caughtError;
3241
3685
  try {
3242
- nextState = this.logic.transition(this._snapshot, event, this._actorScope);
3686
+ nextState = finalizeTransitionResult(this._actorScope, this._snapshot, this.logic.transition(this._snapshot, event, this._actorScope));
3243
3687
  } catch (err) {
3244
3688
  // we wrap it in a box so we can rethrow it later even if falsy value gets caught here
3245
3689
  caughtError = {
@@ -3254,10 +3698,9 @@ class Actor {
3254
3698
  try {
3255
3699
  const [nextSnapshot, effects] = nextState;
3256
3700
  snapshot = nextSnapshot;
3257
- this._snapshot = snapshot;
3258
- const logicEffects = executeExecutableEffects(effects, this._actorScope);
3701
+ this._setSnapshot(snapshot);
3702
+ executeExecutableEffects(effects, this._actorScope);
3259
3703
  this.update(snapshot, event);
3260
- this.logic.executeEffects?.(logicEffects, this._actorScope);
3261
3704
  } catch (err) {
3262
3705
  this._recoverOrError(err, snapshot);
3263
3706
  return;
@@ -3268,6 +3711,35 @@ class Actor {
3268
3711
  }
3269
3712
  }
3270
3713
 
3714
+ /** @internal */
3715
+ _emit(event) {
3716
+ for (const listeners of [this.eventListeners.get(event.type), this.eventListeners.get('*')]) {
3717
+ if (listeners) {
3718
+ for (const handler of listeners) {
3719
+ safeCall(handler, event);
3720
+ }
3721
+ }
3722
+ }
3723
+ }
3724
+
3725
+ /** @internal */
3726
+ _terminate(termination) {
3727
+ if (termination.status === 'done') {
3728
+ // The terminal snapshot is published before observer completion and the
3729
+ // parent notification, matching actor lifecycle order.
3730
+ this._next(this._snapshot);
3731
+ this._stopProcedure();
3732
+ this._complete();
3733
+ if (this._parent) {
3734
+ this.system._relay(this, this._parent, createDoneActorEvent(this.id, termination.output));
3735
+ }
3736
+ return;
3737
+ }
3738
+ if (termination.status === 'error') {
3739
+ this._error(termination.error);
3740
+ }
3741
+ }
3742
+
3271
3743
  /** @internal */
3272
3744
  _stop() {
3273
3745
  if (this._processingStatus === ProcessingStatus.Stopped) {
@@ -3276,6 +3748,7 @@ class Actor {
3276
3748
  this.mailbox.clear();
3277
3749
  if (this._processingStatus === ProcessingStatus.NotStarted) {
3278
3750
  this._processingStatus = ProcessingStatus.Stopped;
3751
+ this.system._unregister(this);
3279
3752
  return this;
3280
3753
  }
3281
3754
  this.mailbox.enqueue({
@@ -3334,7 +3807,7 @@ class Actor {
3334
3807
  }
3335
3808
 
3336
3809
  // Cancel all delayed events
3337
- this.system.scheduler.cancelAll(this);
3810
+ this.system.cancelAllTimers(this);
3338
3811
 
3339
3812
  // TODO: mailbox.reset
3340
3813
  this.mailbox.clear();
@@ -3385,35 +3858,7 @@ class Actor {
3385
3858
  */
3386
3859
 
3387
3860
  getPersistedSnapshot(options) {
3388
- const persisted = this.logic.getPersistedSnapshot(this._snapshot, options);
3389
-
3390
- // Capture this actor's pending self-targeted effects (delayed
3391
- // events/transitions) as serialized action descriptors plus their
3392
- // runtime progress, so they can be restored on `start()`.
3393
- const scheduledEvents = this.system._snapshot._scheduledEvents;
3394
- let pendingEffects;
3395
- const now = Date.now();
3396
- for (const key in scheduledEvents) {
3397
- const scheduled = scheduledEvents[key];
3398
- if (scheduled.source === this && scheduled.target === this) {
3399
- (pendingEffects ??= []).push({
3400
- type: '@xstate.raise',
3401
- event: scheduled.event,
3402
- id: scheduled.id,
3403
- delay: scheduled.delay,
3404
- startedAt: scheduled.startedAt,
3405
- elapsed: Math.max(0, now - scheduled.startedAt)
3406
- });
3407
- }
3408
- }
3409
-
3410
- // If this actor was restored but not yet started, its pending effects
3411
- // are not in the scheduler yet — carry them through as-is.
3412
- pendingEffects ??= this._pendingEffects;
3413
- return pendingEffects ? {
3414
- ...persisted,
3415
- _pendingEffects: pendingEffects
3416
- } : persisted;
3861
+ return this.logic.getPersistedSnapshot(this._snapshot, options);
3417
3862
  }
3418
3863
  [symbolObservable]() {
3419
3864
  return this;
@@ -3437,20 +3882,6 @@ class Actor {
3437
3882
  return this._snapshot;
3438
3883
  }
3439
3884
  }
3440
- function resolveRestoredTimerDelay(strategy, effect) {
3441
- if (typeof strategy === 'function') {
3442
- return strategy(effect);
3443
- }
3444
- switch (strategy) {
3445
- case 'restart':
3446
- return effect.delay;
3447
- case 'absolute':
3448
- return Math.max(0, effect.startedAt + effect.delay - Date.now());
3449
- case 'resume':
3450
- default:
3451
- return Math.max(0, effect.delay - effect.elapsed);
3452
- }
3453
- }
3454
3885
  /**
3455
3886
  * Creates a new actor instance for the given actor logic with the provided
3456
3887
  * options, if any.
@@ -3506,63 +3937,27 @@ function getEffectState(self) {
3506
3937
  }
3507
3938
  return state;
3508
3939
  }
3509
- function executeLogicEffects(effects, actorScope) {
3510
- if (!effects?.length) {
3940
+ function executeLogicEffect(self, key, effect) {
3941
+ const state = getEffectState(self);
3942
+ if (key !== undefined && state.has(key)) {
3511
3943
  return;
3512
3944
  }
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
- }
3945
+ const cleanup = effect();
3946
+ state.set(key ?? Symbol(), {
3947
+ cleanup: typeof cleanup === 'function' ? cleanup : undefined
3948
+ });
3949
+ }
3950
+ function cleanupLogicEffects(self) {
3951
+ const state = effectStates.get(self);
3952
+ if (!state) {
3953
+ return;
3954
+ }
3955
+ for (const {
3956
+ cleanup
3957
+ } of state.values()) {
3958
+ cleanup?.();
3565
3959
  }
3960
+ effectStates.delete(self);
3566
3961
  }
3567
3962
  function resolveContext(context, input) {
3568
3963
  return typeof context === 'function' ? context({
@@ -3579,9 +3974,7 @@ function createLogic(config) {
3579
3974
  ...snapshot,
3580
3975
  status: 'stopped',
3581
3976
  input: undefined
3582
- }, [{
3583
- type: 'cleanupEffects'
3584
- }]];
3977
+ }, [createCustomEffect('xstate.logic.cleanup', () => cleanupLogicEffects(actorScope.self))]];
3585
3978
  }
3586
3979
  if (event.type === XSTATE_LOGIC_EFFECT_START) {
3587
3980
  return [{
@@ -3624,43 +4017,33 @@ function createLogic(config) {
3624
4017
  if (snapshot.effects?.[key]) {
3625
4018
  return;
3626
4019
  }
3627
- effects.push({
3628
- type: 'effect',
3629
- key,
3630
- exec
3631
- });
4020
+ effects.push(createCustomEffect('xstate.logic.effect', (runtime = actorScope.self.system) => executeLogicEffect(actorScope.self, key, () => exec(runtime)), {
4021
+ key
4022
+ }));
3632
4023
  trackedEffects[key] = {
3633
4024
  status: 'active'
3634
4025
  };
3635
4026
  };
3636
4027
  const enq = {
3637
4028
  emit: emitted => {
3638
- effects.push({
3639
- type: 'emit',
3640
- event: emitted
3641
- });
4029
+ effects.push(createEmitEffect(actorScope, emitted));
3642
4030
  },
3643
4031
  sendBack: sentEvent => {
3644
- effects.push({
3645
- type: 'sendBack',
3646
- event: sentEvent
3647
- });
4032
+ const parent = actorScope.self._parent;
4033
+ if (parent) {
4034
+ effects.push(createSendToEffect(actorScope, parent, sentEvent));
4035
+ }
3648
4036
  },
3649
4037
  raise: raisedEvent => {
3650
- effects.push({
3651
- type: 'raise',
3652
- event: raisedEvent
3653
- });
4038
+ effects.push(createSendToEffect(actorScope, actorScope.self, raisedEvent));
3654
4039
  },
3655
4040
  effect: (keyOrExec, maybeExec) => {
3656
4041
  if (typeof keyOrExec === 'string') {
3657
4042
  enqueueEffect(keyOrExec, maybeExec);
3658
4043
  return;
3659
4044
  }
3660
- effects.push({
3661
- type: 'effect',
3662
- exec: keyOrExec
3663
- });
4045
+ const exec = keyOrExec;
4046
+ effects.push(createCustomEffect('xstate.logic.effect', (runtime = actorScope.self.system) => executeLogicEffect(actorScope.self, undefined, () => exec(runtime))));
3664
4047
  }
3665
4048
  };
3666
4049
  const patch = config.run({
@@ -3685,18 +4068,23 @@ function createLogic(config) {
3685
4068
  ...trackedEffects
3686
4069
  };
3687
4070
  }
3688
- return [nextSnapshot, effects];
4071
+ return finalizeTransitionResult(actorScope, snapshot, [nextSnapshot, effects]);
3689
4072
  };
3690
4073
  const logic = {
3691
4074
  id: config.id,
3692
4075
  config,
3693
4076
  transition,
3694
- start: (snapshot, actorScope) => {
4077
+ start: (snapshot, actorScope, options) => {
4078
+ if (!options?.restored) {
4079
+ return;
4080
+ }
3695
4081
  const [nextSnapshot, effects] = transition(snapshot, createInitEvent(snapshot.input), actorScope);
3696
4082
  Object.assign(snapshot, nextSnapshot);
3697
- executeLogicEffects(effects, actorScope);
4083
+ for (const effect of effects) {
4084
+ actorScope.actionExecutor(effect);
4085
+ }
3698
4086
  },
3699
- initialTransition: (input, _) => {
4087
+ initialTransition: (input, actorScope) => {
3700
4088
  const context = resolveContext(config.context, input);
3701
4089
  const snapshot = {
3702
4090
  status: 'active',
@@ -3707,14 +4095,13 @@ function createLogic(config) {
3707
4095
  if (context !== undefined) {
3708
4096
  snapshot.context = context;
3709
4097
  }
3710
- return [{
4098
+ return transition({
3711
4099
  ...snapshot
3712
- }, []];
4100
+ }, createInitEvent(input), actorScope);
3713
4101
  },
3714
4102
  getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
3715
4103
  getPersistedSnapshot: snapshot => snapshot,
3716
- restoreSnapshot: snapshot => snapshot,
3717
- executeEffects: executeLogicEffects
4104
+ restoreSnapshot: snapshot => snapshot
3718
4105
  };
3719
4106
  return logic;
3720
4107
  }
@@ -3974,7 +4361,6 @@ function createObservableLogic(observableCreatorOrConfig) {
3974
4361
  self,
3975
4362
  system
3976
4363
  } = args;
3977
- const emit = args.emit;
3978
4364
  switch (event.type) {
3979
4365
  case XSTATE_OBSERVABLE_NEXT:
3980
4366
  {
@@ -4005,28 +4391,32 @@ function createObservableLogic(observableCreatorOrConfig) {
4005
4391
  }
4006
4392
  };
4007
4393
  }
4008
- enq.effect('observable', () => {
4394
+ if (event.type !== XSTATE_INIT) {
4395
+ return;
4396
+ }
4397
+ enq.effect((runtime = system) => {
4009
4398
  const actorSelf = self;
4399
+ const sendSelf = event => void runtime.sendEvent(actorSelf, actorSelf, event);
4010
4400
  const subscription = observableCreator({
4011
4401
  input,
4012
4402
  system,
4013
4403
  self: self,
4014
- emit: emit
4404
+ emit: event => void runtime.emitEvent(actorSelf, event)
4015
4405
  }).subscribe({
4016
4406
  next: value => {
4017
- system._relay(actorSelf, actorSelf, {
4407
+ sendSelf({
4018
4408
  type: XSTATE_OBSERVABLE_NEXT,
4019
4409
  data: value
4020
4410
  });
4021
4411
  },
4022
4412
  error: err => {
4023
- system._relay(actorSelf, actorSelf, {
4413
+ sendSelf({
4024
4414
  type: XSTATE_OBSERVABLE_ERROR,
4025
4415
  data: err
4026
4416
  });
4027
4417
  },
4028
4418
  complete: () => {
4029
- system._relay(actorSelf, actorSelf, {
4419
+ sendSelf({
4030
4420
  type: XSTATE_OBSERVABLE_COMPLETE
4031
4421
  });
4032
4422
  }
@@ -4095,26 +4485,26 @@ function createEventObservableLogic(lazyObservableOrConfig) {
4095
4485
  // TODO: event types
4096
4486
  const logic = {
4097
4487
  config: lazyObservable,
4098
- transition: (state, event) => {
4488
+ transition: (state, event, actorScope) => {
4099
4489
  if (state.status !== 'active') {
4100
4490
  return [state, []];
4101
4491
  }
4102
4492
  switch (event.type) {
4103
4493
  case XSTATE_OBSERVABLE_ERROR:
4104
- return [{
4494
+ return finalizeTransitionResult(actorScope, state, [{
4105
4495
  ...state,
4106
4496
  status: 'error',
4107
4497
  error: event.data,
4108
4498
  input: undefined,
4109
4499
  _subscription: undefined
4110
- }, []];
4500
+ }, []]);
4111
4501
  case XSTATE_OBSERVABLE_COMPLETE:
4112
- return [{
4502
+ return finalizeTransitionResult(actorScope, state, [{
4113
4503
  ...state,
4114
4504
  status: 'done',
4115
4505
  input: undefined,
4116
4506
  _subscription: undefined
4117
- }, []];
4507
+ }, []]);
4118
4508
  case XSTATE_STOP:
4119
4509
  state._subscription.unsubscribe();
4120
4510
  return [{
@@ -4149,7 +4539,9 @@ function createEventObservableLogic(lazyObservableOrConfig) {
4149
4539
  input: state.input,
4150
4540
  system,
4151
4541
  self,
4152
- emit
4542
+ emit: event => {
4543
+ void emit(event);
4544
+ }
4153
4545
  }).subscribe({
4154
4546
  next: value => {
4155
4547
  if (self._parent) {
@@ -4350,8 +4742,9 @@ function createAsyncLogic(asyncLogic) {
4350
4742
  if (event.type !== XSTATE_INIT) {
4351
4743
  return;
4352
4744
  }
4353
- enq.effect(() => {
4745
+ enq.effect((runtime = system) => {
4354
4746
  const actorSelf = self;
4747
+ const sendSelf = event => void runtime.sendEvent(actorSelf, actorSelf, event);
4355
4748
  const controller = new AbortController();
4356
4749
  const timeout = config.timeout;
4357
4750
  const timeoutMs = parseDelayToMilliseconds(timeout);
@@ -4360,7 +4753,7 @@ function createAsyncLogic(asyncLogic) {
4360
4753
  return;
4361
4754
  }
4362
4755
  controller.abort();
4363
- system._relay(actorSelf, actorSelf, {
4756
+ sendSelf({
4364
4757
  type: XSTATE_ASYNC_REJECT,
4365
4758
  data: new TimeoutError(timeout)
4366
4759
  });
@@ -4376,7 +4769,7 @@ function createAsyncLogic(asyncLogic) {
4376
4769
  self: self,
4377
4770
  signal: controller.signal
4378
4771
  }, {
4379
- emit: enq.emit,
4772
+ emit: event => void runtime.emitEvent(actorSelf, event),
4380
4773
  step: async (key, exec) => {
4381
4774
  const effect = self.getSnapshot().effects?.[key];
4382
4775
  if (effect?.status === 'done') {
@@ -4388,20 +4781,20 @@ function createAsyncLogic(asyncLogic) {
4388
4781
  if (effect?.status === 'active') {
4389
4782
  return waitForEffect(self, key);
4390
4783
  }
4391
- system._relay(actorSelf, actorSelf, {
4784
+ sendSelf({
4392
4785
  type: XSTATE_LOGIC_EFFECT_START,
4393
4786
  key
4394
4787
  });
4395
4788
  try {
4396
4789
  const output = await exec();
4397
- system._relay(actorSelf, actorSelf, {
4790
+ sendSelf({
4398
4791
  type: XSTATE_LOGIC_EFFECT_RESOLVE,
4399
4792
  key,
4400
4793
  output
4401
4794
  });
4402
4795
  return output;
4403
4796
  } catch (error) {
4404
- system._relay(actorSelf, actorSelf, {
4797
+ sendSelf({
4405
4798
  type: XSTATE_LOGIC_EFFECT_REJECT,
4406
4799
  key,
4407
4800
  error
@@ -4415,7 +4808,7 @@ function createAsyncLogic(asyncLogic) {
4415
4808
  if (self.getSnapshot().status !== 'active') {
4416
4809
  return;
4417
4810
  }
4418
- system._relay(actorSelf, actorSelf, {
4811
+ sendSelf({
4419
4812
  type: XSTATE_ASYNC_RESOLVE,
4420
4813
  data: response
4421
4814
  });
@@ -4424,7 +4817,7 @@ function createAsyncLogic(asyncLogic) {
4424
4817
  if (self.getSnapshot().status !== 'active') {
4425
4818
  return;
4426
4819
  }
4427
- system._relay(actorSelf, actorSelf, {
4820
+ sendSelf({
4428
4821
  type: XSTATE_ASYNC_REJECT,
4429
4822
  data: errorData
4430
4823
  });
@@ -4453,4 +4846,4 @@ function createEmptyActor() {
4453
4846
  return createActor(emptyLogic);
4454
4847
  }
4455
4848
 
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 };
4849
+ export { getCandidates as $, Actor as A, isBuiltInExecutableAction as B, createEmptyActor as C, createCallbackLogic as D, createObservableLogic as E, createEventObservableLogic as F, createLogic as G, createAsyncLogic as H, createListenerLogic as I, listenerLogic as J, createSubscriptionLogic as K, subscriptionLogic as L, resolveReferencedActor as M, setSnapshotActorRef as N, getSnapshotActorRef as O, createSnapshotSystem as P, mapValues as Q, createInvokeId as R, STATE_DELIMITER as S, TimeoutError as T, getDelayedTransitions as U, evaluateCandidate as V, formatTransition as W, XSTATE_INIT as X, NULL_EVENT as Y, toTransitionConfigArray as Z, createInvokeTimeoutEvent as _, XSTATE_STOP as a, formatRouteTransitions as a0, resolveStateValue as a1, getAllStateNodes as a2, createMachineSnapshot as a3, isInFinalState as a4, cloneMachineSnapshot as a5, transitionNode as a6, getTransitionResult as a7, hasEffect as a8, createErrorPlatformEvent as a9, toStatePath as aa, isStateId as ab, getStateNodeByPath as ac, getPersistedSnapshot as ad, $$ACTOR_TYPE as ae, builtInActions as b, createTransitionEnqueue as c, deriveDeferredStarts as d, XSTATE_TIMER as e, finalizeTransitionResult as f, createSendToEffect as g, createActor as h, isAtomicStateNode as i, macrostep as j, createInitEvent as k, createSpawnEffect as l, matchesEventDescriptor as m, initialMicrostep as n, getProperAncestors as o, parseDelayToMilliseconds as p, isMachineSnapshot as q, resolveActionsWithContext as r, getStateNodes as s, toArray as t, getAllOwnEventDescriptors as u, matchesState as v, checkStateIn as w, pathToStateValue as x, toObserver as y, executeEffects as z };