xstate 6.0.0-alpha.21 → 6.0.0-alpha.23

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