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
@@ -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,34 +162,40 @@ 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(// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
168
+ `Only event objects may be used with sendTo; use sendTo({ type: "${event}" }) instead` );
169
+ }
170
+ }
163
171
  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 => {},
172
+ // Actor refs are created while resolving the next snapshot. Starting is a
173
+ // separate deferred effect so the returned list remains execution-complete.
174
+ ['@xstate.spawn']: actor => {
175
+ return actor.system.spawnActor(actor._parent, actor);
176
+ },
167
177
  ['@xstate.start']: actor => {
168
- actor.start();
178
+ return actor.system.startActor(actor);
169
179
  },
170
- ['@xstate.raise']: (actorScope, event, options) => {
171
- actorScope.system.scheduler.schedule(actorScope.self, actorScope.self, event, options?.delay ?? 0, options?.id);
180
+ ['@xstate.raise']: (actorScope, _event, options) => {
181
+ return actorScope.system.scheduleTimer(actorScope.self, options.id, options?.delay ?? 0);
172
182
  },
173
183
  ['@xstate.sendTo']: (actorScope, actor, event, options) => {
174
- if (typeof event === 'string') {
175
- throw new Error(// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
176
- `Only event objects may be used with sendTo; use sendTo({ type: "${event}" }) instead` );
177
- }
184
+ assertSendToEvent(event);
178
185
  if (options?.delay !== undefined) {
179
- actorScope.system.scheduler.schedule(actorScope.self, actor, event, options?.delay ?? 0, options?.id);
186
+ return actorScope.system.scheduleTimer(actorScope.self, options.id, options?.delay ?? 0);
180
187
  } else {
181
- actorScope.defer(() => {
182
- actorScope.system._relay(actorScope.self, actor, event);
183
- });
188
+ return actorScope.system._relay(actorScope.self, actor, event);
184
189
  }
185
190
  },
186
191
  ['@xstate.cancel']: (actorScope, sendId) => {
187
- actorScope.system.scheduler.cancel(actorScope.self, sendId);
192
+ return actorScope.system.cancelTimer(actorScope.self, sendId);
188
193
  },
189
194
  ['@xstate.stop']: (actorScope, actor) => {
190
- actorScope.stopChild(actor);
195
+ return actorScope.system.stopActor(actor);
196
+ },
197
+ ['@xstate.terminate']: (actor, termination) => {
198
+ return actor.system.terminateActor(actor, termination);
191
199
  }
192
200
  };
193
201
 
@@ -351,12 +359,109 @@ function createSubscriptionLogic() {
351
359
  // Singleton logic instance
352
360
  const subscriptionLogic = /* #__PURE__ */createSubscriptionLogic();
353
361
 
354
- const builtInExecutableAction = Symbol('xstate.builtInExecutableAction');
355
- function brandBuiltInExecutableAction(action) {
356
- Object.defineProperty(action, builtInExecutableAction, {
357
- value: true
358
- });
359
- return action;
362
+ function execCustomEffect() {
363
+ return this.action?.(...this.args);
364
+ }
365
+ function execEmitEffect(runtime = this.source.system) {
366
+ return runtime.emitEvent(this.source, this.event);
367
+ }
368
+
369
+ /** @internal Creates an emitted-event effect. */
370
+ function createEmitEffect(actorScope, event) {
371
+ return {
372
+ kind: 'emit',
373
+ exec: execEmitEffect,
374
+ type: event.type,
375
+ source: actorScope.self,
376
+ event,
377
+ params: undefined,
378
+ args: []
379
+ };
380
+ }
381
+
382
+ /** @internal Creates a directly executable user effect. */
383
+ function createCustomEffect(type, action, params) {
384
+ return {
385
+ kind: 'action',
386
+ exec: action,
387
+ type,
388
+ action: action,
389
+ params,
390
+ args: []
391
+ };
392
+ }
393
+ function execSpawnEffect(runtime = this.actor.system) {
394
+ return runtime.spawnActor(this.source, this.actor);
395
+ }
396
+ function execStartEffect(runtime = this.actor.system) {
397
+ return runtime.startActor(this.actor);
398
+ }
399
+ function execStopEffect(runtime = this.source.system) {
400
+ return runtime.stopActor(this.actor);
401
+ }
402
+ function execTerminateEffect(runtime = this.actor.system) {
403
+ const termination = this.status === 'done' ? {
404
+ status: 'done',
405
+ output: this.output,
406
+ error: undefined
407
+ } : {
408
+ status: 'error',
409
+ output: undefined,
410
+ error: this.error
411
+ };
412
+ return runtime.terminateActor(this.actor, termination);
413
+ }
414
+ function execRaiseEffect(runtime = this.source.system) {
415
+ return runtime.scheduleTimer(this.source, this.id, this.delay ?? 0);
416
+ }
417
+ function execSendToEffect(runtime = this.source.system) {
418
+ assertSendToEvent(this.event);
419
+ if (this.delay !== undefined) {
420
+ return runtime.scheduleTimer(this.source, this.id, this.delay);
421
+ }
422
+ return runtime.sendEvent(this.source, this.target, this.event);
423
+ }
424
+ function execCancelEffect(runtime = this.source.system) {
425
+ return runtime.cancelTimer(this.source, this.id);
426
+ }
427
+ function updateLogicalTimers(snapshot, effect, actorScope) {
428
+ if (!isBuiltInExecutableAction(effect)) {
429
+ return snapshot;
430
+ }
431
+ if (effect.type === '@xstate.cancel') {
432
+ if (!snapshot.timers?.[effect.id]) {
433
+ return snapshot;
434
+ }
435
+ const timers = {
436
+ ...snapshot.timers
437
+ };
438
+ delete timers[effect.id];
439
+ return {
440
+ ...snapshot,
441
+ timers
442
+ };
443
+ }
444
+ if (effect.type !== '@xstate.raise' && effect.type !== '@xstate.sendTo' || effect.delay === undefined) {
445
+ return snapshot;
446
+ }
447
+ let nextTimerId = snapshot._nextTimerId ?? 0;
448
+ const id = effect.id ?? `xstate.timer.auto.${nextTimerId++}`;
449
+ effect.id = id;
450
+ const target = effect.type === '@xstate.raise' || effect.target === actorScope.self ? 'self' : effect.target;
451
+ return {
452
+ ...snapshot,
453
+ timers: {
454
+ ...snapshot.timers,
455
+ [id]: {
456
+ id,
457
+ delay: effect.delay,
458
+ type: effect.type,
459
+ event: effect.event,
460
+ target
461
+ }
462
+ },
463
+ _nextTimerId: nextTimerId
464
+ };
360
465
  }
361
466
  function mergeContextPatch(context, patch) {
362
467
  return {
@@ -365,43 +470,55 @@ function mergeContextPatch(context, patch) {
365
470
  };
366
471
  }
367
472
  function pushBuiltInAction(actions, action, ...args) {
368
- actions.push({
473
+ const actionRecord = {
369
474
  action,
370
475
  args
371
- });
476
+ };
477
+ actions.push(actionRecord);
478
+ return actionRecord;
372
479
  }
373
- function registerSpawnedChild(actor, id) {
374
- return Object.assign(function registerChild(args) {
480
+ function applyChildUpdate(snapshot, update, actorScope) {
481
+ if (update.type === 'add') {
375
482
  return {
483
+ ...snapshot,
376
484
  children: {
377
- ...args.children,
378
- [id]: actor
485
+ ...snapshot.children,
486
+ [update.id]: update.actor
379
487
  }
380
488
  };
381
- }, {
382
- _special: true
383
- });
384
- }
385
- function unregisterChild(actor) {
386
- return Object.assign(function removeChild(args) {
387
- const children = {
388
- ...args.children
389
- };
390
- for (const key of Object.keys(children)) {
391
- if (children[key] === actor) {
392
- delete children[key];
393
- }
489
+ }
490
+ const children = {
491
+ ...snapshot.children
492
+ };
493
+ let owned = update.actor._parent === actorScope.self;
494
+ for (const key of Object.keys(children)) {
495
+ if (children[key] === update.actor) {
496
+ owned = true;
497
+ delete children[key];
394
498
  }
395
- return {
396
- children
397
- };
398
- }, {
399
- _special: true
400
- });
499
+ }
500
+ if (!owned) {
501
+ throw new Error(`Cannot stop child actor ${update.actor.id} of ${actorScope.self.id} because it is not a child` );
502
+ }
503
+ actorScope.system._unregister(update.actor);
504
+ return {
505
+ ...snapshot,
506
+ children
507
+ };
508
+ }
509
+ function getTransitionActionRecord(action) {
510
+ if (typeof action === 'object' && action !== null && 'action' in action && typeof action.action === 'function') {
511
+ return action;
512
+ }
513
+ return undefined;
401
514
  }
402
515
  function pushSpawnedChild(actions, actor, id) {
403
- pushBuiltInAction(actions, builtInActions['@xstate.spawn'], actor);
404
- actions.push(registerSpawnedChild(actor, id ?? actor.id));
516
+ const action = pushBuiltInAction(actions, builtInActions['@xstate.spawn'], actor);
517
+ action.childUpdate = {
518
+ type: 'add',
519
+ actor,
520
+ id: id ?? actor.id
521
+ };
405
522
  }
406
523
  function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubscriptions = false, createActors = true) {
407
524
  const props = {
@@ -431,7 +548,7 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
431
548
  id: options?.id ?? options?.registryKey ?? logic.id
432
549
  };
433
550
  }
434
- const actor = createActor(logic, {
551
+ const actor = actorScope.system.createActorRef(logic, {
435
552
  ...options,
436
553
  parent: actorScope.self
437
554
  });
@@ -450,8 +567,11 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
450
567
  },
451
568
  stop: actor => {
452
569
  if (actor) {
453
- pushBuiltInAction(actions, builtInActions['@xstate.stop'], actorScope, actor);
454
- actions.push(unregisterChild(actor));
570
+ const action = pushBuiltInAction(actions, builtInActions['@xstate.stop'], actorScope, actor);
571
+ action.childUpdate = {
572
+ type: 'remove',
573
+ actor
574
+ };
455
575
  }
456
576
  }
457
577
  };
@@ -463,7 +583,7 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
463
583
  eventType,
464
584
  mapper
465
585
  };
466
- const listenerActor = createActor(listenerLogic, {
586
+ const listenerActor = actorScope.system.createActorRef(listenerLogic, {
467
587
  input,
468
588
  parent: actorScope.self
469
589
  });
@@ -478,7 +598,7 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
478
598
  actor,
479
599
  mappers: normalizedMappers
480
600
  };
481
- const subscriptionActor = createActor(subscriptionLogic, {
601
+ const subscriptionActor = actorScope.system.createActorRef(subscriptionLogic, {
482
602
  input,
483
603
  parent: actorScope.self
484
604
  });
@@ -497,6 +617,9 @@ function getBuiltInActionFields(action, args) {
497
617
  {
498
618
  const [actor] = args;
499
619
  return {
620
+ kind: 'builtin',
621
+ exec: execSpawnEffect,
622
+ source: actor._parent,
500
623
  actor,
501
624
  id: actor.id,
502
625
  logic: actor.logic,
@@ -508,6 +631,9 @@ function getBuiltInActionFields(action, args) {
508
631
  {
509
632
  const [, event, options] = args;
510
633
  return {
634
+ kind: 'builtin',
635
+ exec: execRaiseEffect,
636
+ source: args[0].self,
511
637
  event,
512
638
  id: options?.id,
513
639
  delay: options?.delay
@@ -517,6 +643,9 @@ function getBuiltInActionFields(action, args) {
517
643
  {
518
644
  const [, target, event, options] = args;
519
645
  return {
646
+ kind: 'builtin',
647
+ exec: execSendToEffect,
648
+ source: args[0].self,
520
649
  target,
521
650
  event,
522
651
  id: options?.id,
@@ -527,6 +656,9 @@ function getBuiltInActionFields(action, args) {
527
656
  {
528
657
  const [, id] = args;
529
658
  return {
659
+ kind: 'builtin',
660
+ exec: execCancelEffect,
661
+ source: args[0].self,
530
662
  id
531
663
  };
532
664
  }
@@ -534,7 +666,11 @@ function getBuiltInActionFields(action, args) {
534
666
  {
535
667
  const [, actor] = args;
536
668
  return {
537
- actor
669
+ kind: 'builtin',
670
+ exec: execStopEffect,
671
+ source: args[0].self,
672
+ actor,
673
+ id: actor.id
538
674
  };
539
675
  }
540
676
  default:
@@ -542,19 +678,91 @@ function getBuiltInActionFields(action, args) {
542
678
  }
543
679
  }
544
680
  function createStartEffect(actor) {
545
- const action = builtInActions['@xstate.start'];
546
681
  const args = [actor];
547
- return brandBuiltInExecutableAction({
682
+ return {
683
+ kind: 'builtin',
684
+ exec: execStartEffect,
548
685
  type: XSTATE_START,
549
- params: {
550
- action,
551
- args
552
- },
686
+ source: actor._parent,
687
+ params: undefined,
553
688
  args,
554
- exec: action.bind(null, actor),
555
689
  actor,
556
690
  id: actor.id
557
- });
691
+ };
692
+ }
693
+ function createSpawnEffect(actor) {
694
+ const args = [actor];
695
+ return {
696
+ kind: 'builtin',
697
+ exec: execSpawnEffect,
698
+ type: XSTATE_SPAWN,
699
+ source: actor._parent,
700
+ params: undefined,
701
+ args,
702
+ actor,
703
+ id: actor.id,
704
+ logic: actor.logic,
705
+ src: actor.src,
706
+ input: actor.options?.input
707
+ };
708
+ }
709
+
710
+ /** @internal Creates an immediate actor-to-actor delivery effect. */
711
+ function createSendToEffect(actorScope, target, event) {
712
+ const args = [actorScope, target, event, {}];
713
+ return {
714
+ kind: 'builtin',
715
+ exec: execSendToEffect,
716
+ type: '@xstate.sendTo',
717
+ source: actorScope.self,
718
+ target,
719
+ event,
720
+ id: undefined,
721
+ delay: undefined,
722
+ params: undefined,
723
+ args
724
+ };
725
+ }
726
+
727
+ /** @internal Creates the terminal lifecycle effect for an actor. */
728
+ function createTerminationEffect(actorScope, snapshot) {
729
+ if (snapshot.status !== 'done' && snapshot.status !== 'error') {
730
+ throw new Error('Cannot terminate an active or stopped actor');
731
+ }
732
+ const termination = snapshot.status === 'done' ? {
733
+ status: 'done',
734
+ output: snapshot.output,
735
+ error: undefined
736
+ } : {
737
+ status: 'error',
738
+ output: undefined,
739
+ error: snapshot.error
740
+ };
741
+ const args = [actorScope.self, termination];
742
+ return {
743
+ kind: 'builtin',
744
+ exec: execTerminateEffect,
745
+ type: XSTATE_TERMINATE,
746
+ source: actorScope.self,
747
+ actor: actorScope.self,
748
+ id: actorScope.self.id,
749
+ ...termination,
750
+ params: undefined,
751
+ args
752
+ };
753
+ }
754
+
755
+ /**
756
+ * Ensures that a newly terminal snapshot has exactly one ordered actor
757
+ * termination effect.
758
+ *
759
+ * @internal
760
+ */
761
+ function finalizeTransitionResult(actorScope, previousSnapshot, [nextSnapshot, effects]) {
762
+ const becameTerminal = nextSnapshot.status === 'done' || nextSnapshot.status === 'error';
763
+ const wasTerminal = previousSnapshot?.status === 'done' || previousSnapshot?.status === 'error';
764
+ const hasTerminationEffect = effects.some(effect => typeof effect === 'object' && effect !== null && 'type' in effect && effect.type === XSTATE_TERMINATE);
765
+ return becameTerminal && !wasTerminal && !hasTerminationEffect ? [nextSnapshot, [...effects, createTerminationEffect(actorScope, nextSnapshot)]] : [nextSnapshot, effects];
558
766
  }
559
767
 
560
768
  /**
@@ -583,14 +791,19 @@ function deriveDeferredStarts(effects) {
583
791
  return [...attachedStarts, ...childStarts];
584
792
  }
585
793
  function isBuiltInExecutableAction(action) {
586
- return action[builtInExecutableAction] === true;
794
+ return action.kind === 'builtin';
795
+ }
796
+
797
+ /** Executes transition effects sequentially, awaiting each runtime operation. */
798
+ async function executeEffects(effects, runtime) {
799
+ for (const effect of effects) {
800
+ await effect.exec(runtime);
801
+ }
587
802
  }
588
803
  function resolveActionsWithContext(currentSnapshot, event, actorScope, actions) {
589
804
  let intermediateSnapshot = currentSnapshot;
590
805
  const executableActions = [];
591
806
  for (const action of actions) {
592
- const isInline = typeof action === 'function';
593
- const resolvedAction = isInline ? action : typeof action === 'object' && 'action' in action && typeof action.action === 'function' ? action.action.bind(null, ...action.args) : false;
594
807
  const actionArgs = {
595
808
  context: intermediateSnapshot.context,
596
809
  event,
@@ -602,20 +815,29 @@ function resolveActionsWithContext(currentSnapshot, event, actorScope, actions)
602
815
  actions: currentSnapshot.machine.implementations.actions,
603
816
  actorSources: currentSnapshot.machine.implementations.actorSources
604
817
  };
818
+ const isInline = typeof action === 'function';
819
+ const actionRecord = getTransitionActionRecord(action);
820
+ const resolvedAction = isInline ? action : actionRecord ? actionRecord.action.bind(null, ...actionRecord.args) : false;
605
821
  let actionParams = undefined;
606
822
  if (typeof action === 'object' && action !== null) {
607
823
  const {
608
824
  type: _,
825
+ childUpdate: _childUpdate,
609
826
  ...emittedEventParams
610
827
  } = action;
611
828
  actionParams = emittedEventParams;
612
829
  }
830
+ if (actionRecord?.childUpdate) {
831
+ intermediateSnapshot = applyChildUpdate(intermediateSnapshot, actionRecord.childUpdate, actorScope);
832
+ }
613
833
  if (resolvedAction && '_special' in resolvedAction) {
614
834
  executableActions.push({
835
+ kind: 'action',
836
+ exec: execCustomEffect,
615
837
  type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type ?? '(anonymous)' : action.name || '(anonymous)',
616
838
  params: actionParams,
617
839
  args: [],
618
- exec: undefined
840
+ action: undefined
619
841
  });
620
842
  const specialAction = resolvedAction;
621
843
  const res = specialAction(actionArgs, emptyEnqueueObject);
@@ -637,14 +859,26 @@ function resolveActionsWithContext(currentSnapshot, event, actorScope, actions)
637
859
  }
638
860
  if (!resolvedAction || !('resolve' in resolvedAction)) {
639
861
  const builtInFields = typeof action === 'object' && action !== null && 'action' in action && typeof action.action === 'function' ? getBuiltInActionFields(action.action, action.args) : undefined;
862
+ const isEmittedEvent = typeof action === 'object' && action !== null && !actionRecord;
640
863
  const executableAction = {
864
+ kind: builtInFields ? 'builtin' : isEmittedEvent ? 'emit' : 'action',
641
865
  type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type : action.name || '(anonymous)',
642
- params: actionParams,
866
+ params: builtInFields ? undefined : actionParams,
643
867
  args: typeof action === 'object' && 'action' in action ? action.args : [],
644
- exec: resolvedAction || (typeof action === 'object' && action !== null ? () => actorScope.defer(() => actorScope.emit(action)) : undefined),
868
+ ...(builtInFields ? {} : isEmittedEvent ? {
869
+ source: actorScope.self,
870
+ event: action
871
+ } : {
872
+ action: actionRecord?.action ?? (isInline ? action : undefined)
873
+ }),
874
+ ...(!builtInFields ? {
875
+ exec: isEmittedEvent ? execEmitEffect : execCustomEffect
876
+ } : {}),
645
877
  ...builtInFields
646
878
  };
647
- executableActions.push(builtInFields ? brandBuiltInExecutableAction(executableAction) : executableAction);
879
+ const typedExecutableAction = executableAction;
880
+ intermediateSnapshot = updateLogicalTimers(intermediateSnapshot, typedExecutableAction, actorScope);
881
+ executableActions.push(typedExecutableAction);
648
882
  continue;
649
883
  }
650
884
  }
@@ -1434,6 +1668,17 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1434
1668
  return [[wrappedAction], undefined, undefined];
1435
1669
  };
1436
1670
  let nextState = currentSnapshot;
1671
+ const getInvokeStopActions = (stateNode, children) => {
1672
+ const actions = [];
1673
+ const enqueue = createTransitionEnqueue(actorScope, actions, []);
1674
+ for (const invokeDef of stateNode.invoke) {
1675
+ const child = children[invokeDef.id];
1676
+ if (child) {
1677
+ enqueue.stop(child);
1678
+ }
1679
+ }
1680
+ return actions;
1681
+ };
1437
1682
  const exitStates = () => {
1438
1683
  const statesToExit = computeExitSet(filteredTransitions, mutStateNodeSet, currentSnapshot, event, actorScope);
1439
1684
  statesToExit.sort((a, b) => b.order - a.order);
@@ -1455,7 +1700,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1455
1700
  }
1456
1701
  for (const exitStateNode of statesToExit) {
1457
1702
  const stateInput = getStateInput(currentSnapshot, exitStateNode.id);
1458
- const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, currentSnapshot.children, stateInput) : [[]];
1703
+ const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, currentSnapshot.children, stateInput) : [[], undefined, undefined];
1459
1704
  if (internalEvents?.length) {
1460
1705
  internalQueue.push(...internalEvents);
1461
1706
  }
@@ -1467,12 +1712,11 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1467
1712
  const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, exitActions);
1468
1713
  nextState = resolvedState;
1469
1714
  executableActions.push(...resolvedActions);
1470
- for (const def of exitStateNode.invoke) {
1471
- const childActor = nextState.children[def.id];
1472
- if (childActor && !childActor._isExternal) {
1473
- actorScope.stopChild(childActor);
1474
- }
1475
- delete nextState.children[def.id];
1715
+ const invokeStopActions = getInvokeStopActions(exitStateNode, nextState.children);
1716
+ if (invokeStopActions.length) {
1717
+ const [stoppedState, stopEffects] = resolveActionsWithContext(nextState, event, actorScope, invokeStopActions);
1718
+ nextState = stoppedState;
1719
+ executableActions.push(...stopEffects);
1476
1720
  }
1477
1721
  mutStateNodeSet.delete(exitStateNode);
1478
1722
  }
@@ -1643,7 +1887,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1643
1887
  }
1644
1888
  const completedNodes = new Set();
1645
1889
  const children = {
1646
- ...currentSnapshot.children
1890
+ ...nextState.children
1647
1891
  };
1648
1892
  for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
1649
1893
  mutStateNodeSet.add(stateNodeToEnter);
@@ -1670,7 +1914,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1670
1914
  event,
1671
1915
  output: getEventOutput(event)
1672
1916
  }) : invokeDef.input;
1673
- const actor = createActor(logic, {
1917
+ const actor = actorScope.system.createActorRef(logic, {
1674
1918
  ...invokeDef,
1675
1919
  input,
1676
1920
  parent: actorScope.self,
@@ -1799,6 +2043,12 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
1799
2043
  const [resolvedState, resolvedActions] = resolveActionsWithContext(nextState, event, actorScope, allExitActions);
1800
2044
  nextState = resolvedState;
1801
2045
  executableActions.push(...resolvedActions);
2046
+ const [stoppedState, stopEffects] = stopChildren(nextState, event, actorScope);
2047
+ nextState = stoppedState;
2048
+ executableActions.push(...stopEffects);
2049
+ const [withoutTimers, cancelEffects] = cancelTimers(nextState, event, actorScope);
2050
+ nextState = withoutTimers;
2051
+ executableActions.push(...cancelEffects);
1802
2052
  }
1803
2053
  if (historyValue === currentSnapshot.historyValue && currentSnapshot._nodes.length === mutStateNodeSet.size && currentSnapshot._nodes.every(node => mutStateNodeSet.has(node))) {
1804
2054
  // If context was changed (e.g. by entry actions during self-transition),
@@ -1872,9 +2122,27 @@ function getTransitionResult(transition, snapshot, event, actorScope, options) {
1872
2122
  input: resolvedInput
1873
2123
  };
1874
2124
  }
1875
- function macrostep(snapshot, event, actorScope, internalQueue) {
2125
+ function macrostep(snapshot, event, actorScope, internalQueue, initialMicrosteps = []) {
1876
2126
  let nextSnapshot = snapshot;
1877
- const microsteps = [];
2127
+ const microsteps = initialMicrosteps.slice();
2128
+ function completeMacrostep() {
2129
+ const effects = microsteps.flatMap(([, actions]) => actions);
2130
+ const starts = deriveDeferredStarts(effects);
2131
+ const shouldTerminate = (snapshot.status === 'active' || initialMicrosteps.length > 0) && (nextSnapshot.status === 'done' || nextSnapshot.status === 'error');
2132
+ if (starts.length || shouldTerminate) {
2133
+ const terminalEffects = shouldTerminate ? [...starts, createTerminationEffect(actorScope, nextSnapshot)] : starts;
2134
+ if (microsteps.length) {
2135
+ const [lastSnapshot, lastEffects] = microsteps.at(-1);
2136
+ microsteps[microsteps.length - 1] = [lastSnapshot, [...lastEffects, ...terminalEffects]];
2137
+ } else {
2138
+ microsteps.push([nextSnapshot, terminalEffects]);
2139
+ }
2140
+ }
2141
+ return {
2142
+ snapshot: nextSnapshot,
2143
+ microsteps
2144
+ };
2145
+ }
1878
2146
  function addMicrostep(step, transitions) {
1879
2147
  // collect microsteps; surfaced on the enclosing '@xstate.transition' event
1880
2148
  // via its `microsteps[]` facet (there is no standalone microstep event)
@@ -1886,20 +2154,39 @@ function macrostep(snapshot, event, actorScope, internalQueue) {
1886
2154
 
1887
2155
  // Handle stop event
1888
2156
  if (event.type === XSTATE_STOP) {
1889
- nextSnapshot = cloneMachineSnapshot(stopChildren(nextSnapshot, actorScope), {
2157
+ const [stoppedChildrenSnapshot, stopEffects] = stopChildren(nextSnapshot, event, actorScope);
2158
+ const [withoutTimers, cancelEffects] = cancelTimers(stoppedChildrenSnapshot, event, actorScope);
2159
+ nextSnapshot = cloneMachineSnapshot(withoutTimers, {
1890
2160
  status: 'stopped'
1891
2161
  });
1892
- addMicrostep([nextSnapshot, []], []);
1893
- return {
1894
- snapshot: nextSnapshot,
1895
- microsteps
1896
- };
2162
+ addMicrostep([nextSnapshot, [...stopEffects, ...cancelEffects]], []);
2163
+ return completeMacrostep();
1897
2164
  }
1898
2165
  let nextEvent = event;
2166
+ if (event.type === XSTATE_TIMER) {
2167
+ const timer = nextSnapshot.timers[event.id];
2168
+ if (!timer) {
2169
+ return completeMacrostep();
2170
+ }
2171
+ const timers = {
2172
+ ...nextSnapshot.timers
2173
+ };
2174
+ delete timers[timer.id];
2175
+ nextSnapshot = cloneMachineSnapshot(nextSnapshot, {
2176
+ timers
2177
+ });
2178
+ if (timer.type === '@xstate.raise') {
2179
+ internalQueue.push(timer.event);
2180
+ addMicrostep([nextSnapshot, []], []);
2181
+ } else {
2182
+ const target = timer.target === 'self' ? actorScope.self : timer.target;
2183
+ addMicrostep([nextSnapshot, [createSendToEffect(actorScope, target, timer.event)]], []);
2184
+ }
2185
+ }
1899
2186
 
1900
2187
  // Assume the state is at rest (no raised events)
1901
2188
  // Determine the next state based on the next microstep
1902
- if (nextEvent.type !== XSTATE_INIT) {
2189
+ if (nextEvent.type !== XSTATE_INIT && nextEvent.type !== XSTATE_TIMER) {
1903
2190
  const currentEvent = nextEvent;
1904
2191
  const isErr = isErrorEvent(currentEvent);
1905
2192
  const transitions = nextSnapshot.machine.getTransitionData(nextSnapshot, currentEvent, actorScope.self);
@@ -1912,10 +2199,7 @@ function macrostep(snapshot, event, actorScope, internalQueue) {
1912
2199
  error: currentEvent.error
1913
2200
  });
1914
2201
  addMicrostep([nextSnapshot, []], []);
1915
- return {
1916
- snapshot: nextSnapshot,
1917
- microsteps
1918
- };
2202
+ return completeMacrostep();
1919
2203
  }
1920
2204
  const step = microstep(transitions, snapshot, actorScope, nextEvent, false,
1921
2205
  // isInitial
@@ -1953,13 +2237,7 @@ function macrostep(snapshot, event, actorScope, internalQueue) {
1953
2237
  shouldSelectEventlessTransitions = nextSnapshot !== previousState;
1954
2238
  addMicrostep(step, enabledTransitions);
1955
2239
  }
1956
- if (nextSnapshot.status !== 'active' && nextSnapshot.children) {
1957
- stopChildren(nextSnapshot, actorScope);
1958
- }
1959
- return {
1960
- snapshot: nextSnapshot,
1961
- microsteps
1962
- };
2240
+ return completeMacrostep();
1963
2241
  }
1964
2242
 
1965
2243
  /**
@@ -2019,17 +2297,29 @@ function transitionToHasEffect(transitionTo, context, event, snapshot, self, imp
2019
2297
  }
2020
2298
  return res !== undefined;
2021
2299
  }
2022
- function stopChildren(snapshot, actorScope) {
2300
+ function stopChildren(snapshot, event, actorScope) {
2023
2301
  let children;
2024
2302
  if (!snapshot.children || (children = Object.values(snapshot.children).filter(Boolean)).length === 0) {
2025
- return snapshot;
2303
+ return [snapshot, []];
2026
2304
  }
2305
+ const actions = [];
2306
+ const enqueue = createTransitionEnqueue(actorScope, actions, []);
2027
2307
  for (const child of children) {
2028
- actorScope.stopChild(child);
2308
+ enqueue.stop(child);
2029
2309
  }
2030
- return cloneMachineSnapshot(snapshot, {
2031
- children: {}
2032
- });
2310
+ return resolveActionsWithContext(snapshot, event, actorScope, actions);
2311
+ }
2312
+ function cancelTimers(snapshot, event, actorScope) {
2313
+ const timerIds = Object.keys(snapshot.timers);
2314
+ if (!timerIds.length) {
2315
+ return [snapshot, []];
2316
+ }
2317
+ const actions = [];
2318
+ const enqueue = createTransitionEnqueue(actorScope, actions, []);
2319
+ for (const id of timerIds) {
2320
+ enqueue.cancel(id);
2321
+ }
2322
+ return resolveActionsWithContext(snapshot, event, actorScope, actions);
2033
2323
  }
2034
2324
  function selectEventlessTransitions(snapshot, event, actorScope) {
2035
2325
  const enabledTransitionSet = new Set();
@@ -2074,6 +2364,129 @@ function evaluateCandidate(candidate, event, snapshot, stateNode, self) {
2074
2364
  return true;
2075
2365
  }
2076
2366
 
2367
+ /** @internal */
2368
+ const snapshotActorRef = Symbol('xstate.snapshotActorRef');
2369
+
2370
+ /** Snapshot-scoped actor identity and system view. @internal */
2371
+
2372
+ function getSnapshotChildren(snapshot) {
2373
+ return 'children' in snapshot && snapshot.children ? snapshot.children : {};
2374
+ }
2375
+
2376
+ /**
2377
+ * Creates the isolated receptionist/id view used by a pure branch. Scheduling,
2378
+ * relaying and clocks remain delegated to the snapshot's execution system.
2379
+ *
2380
+ * @internal
2381
+ */
2382
+ function createSnapshotSystem(baseSystem, self, children, baseState) {
2383
+ const registeredActors = new Map(baseState?.children ?? baseSystem.children);
2384
+ const keyedActors = new Map(baseState?.keyedActors ?? Object.entries(baseSystem.getAll()).filter(entry => !!entry[1]));
2385
+ const reverseKeyedActors = new WeakMap();
2386
+ let booksSelf = true;
2387
+ const system = {
2388
+ ...baseSystem,
2389
+ children: registeredActors,
2390
+ keyedActors,
2391
+ reverseKeyedActors,
2392
+ _snapshot: {
2393
+ ...(baseState?.snapshot ?? baseSystem._snapshot)
2394
+ },
2395
+ _bookId: () => {
2396
+ if (booksSelf) {
2397
+ booksSelf = false;
2398
+ return self.sessionId ?? self.id;
2399
+ }
2400
+ return `x:${system._snapshot._nextActorId++}`;
2401
+ },
2402
+ _register: (sessionId, actor) => {
2403
+ registeredActors.set(sessionId, actor);
2404
+ return sessionId;
2405
+ },
2406
+ _unregister: actor => {
2407
+ if (actor.sessionId) {
2408
+ registeredActors.delete(actor.sessionId);
2409
+ }
2410
+ const registryKey = reverseKeyedActors.get(actor);
2411
+ if (registryKey !== undefined && keyedActors.get(registryKey) === actor) {
2412
+ keyedActors.delete(registryKey);
2413
+ }
2414
+ reverseKeyedActors.delete(actor);
2415
+ },
2416
+ _set: (registryKey, actor) => {
2417
+ const existing = keyedActors.get(registryKey);
2418
+ if (existing && existing !== actor) {
2419
+ throw new Error(`Actor with registry key '${String(registryKey)}' already exists.`);
2420
+ }
2421
+ keyedActors.set(registryKey, actor);
2422
+ reverseKeyedActors.set(actor, registryKey);
2423
+ },
2424
+ get: registryKey => keyedActors.get(registryKey),
2425
+ getAll: () => Object.fromEntries(keyedActors),
2426
+ // Pure transition resolution must not leak topology inspection events into
2427
+ // a live runtime system.
2428
+ _sendInspectionEvent: () => {}
2429
+ };
2430
+ for (const [registryKey, actor] of keyedActors) {
2431
+ reverseKeyedActors.set(actor, registryKey);
2432
+ }
2433
+ for (const actor of Object.values(children)) {
2434
+ if (!actor) {
2435
+ continue;
2436
+ }
2437
+ if (actor.sessionId) {
2438
+ registeredActors.set(actor.sessionId, actor);
2439
+ }
2440
+ const registryKey = actor.registryKey;
2441
+ if (registryKey) {
2442
+ keyedActors.set(registryKey, actor);
2443
+ reverseKeyedActors.set(actor, registryKey);
2444
+ }
2445
+ }
2446
+ return system;
2447
+ }
2448
+
2449
+ /** Returns the actor identity associated with a transition snapshot. @internal */
2450
+ function getSnapshotActorRef(snapshot) {
2451
+ return snapshot[snapshotActorRef];
2452
+ }
2453
+
2454
+ /**
2455
+ * Copies an internal actor association without rebuilding its system view.
2456
+ *
2457
+ * @internal
2458
+ */
2459
+ function copySnapshotActorRef(source, target) {
2460
+ const ref = getSnapshotActorRef(source);
2461
+ if (ref) {
2462
+ Object.defineProperty(target, snapshotActorRef, {
2463
+ configurable: true,
2464
+ value: ref
2465
+ });
2466
+ }
2467
+ }
2468
+
2469
+ /**
2470
+ * Associates a transition snapshot with an actor and an immutable system view.
2471
+ * The internal association is excluded from enumeration and persistence.
2472
+ *
2473
+ * @internal
2474
+ */
2475
+ function setSnapshotActorRef(snapshot, actor, baseSystem = actor.system) {
2476
+ const system = createSnapshotSystem(baseSystem, actor, getSnapshotChildren(snapshot));
2477
+ Object.defineProperty(snapshot, snapshotActorRef, {
2478
+ configurable: true,
2479
+ value: {
2480
+ actor,
2481
+ systemState: {
2482
+ children: system.children,
2483
+ keyedActors: system.keyedActors,
2484
+ snapshot: system._snapshot
2485
+ }
2486
+ }
2487
+ });
2488
+ }
2489
+
2077
2490
  function isMachineSnapshot(value) {
2078
2491
  return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
2079
2492
  }
@@ -2096,6 +2509,7 @@ const machineSnapshotToJSON = function toJSON() {
2096
2509
  const {
2097
2510
  _nodes: nodes,
2098
2511
  _stateInputs,
2512
+ [snapshotActorRef]: _actorRef,
2099
2513
  tags,
2100
2514
  machine,
2101
2515
  getMeta,
@@ -2132,8 +2546,8 @@ function collectTags(stateNodes) {
2132
2546
  }
2133
2547
  return tags;
2134
2548
  }
2135
- function createMachineSnapshot(config, machine) {
2136
- return {
2549
+ function createMachineSnapshot(config, machine, actorRef) {
2550
+ const snapshot = {
2137
2551
  status: config.status,
2138
2552
  output: config.output,
2139
2553
  error: config.error,
@@ -2143,8 +2557,10 @@ function createMachineSnapshot(config, machine) {
2143
2557
  value: config.value ?? getStateValue(machine.root, config._nodes),
2144
2558
  tags: collectTags(config._nodes),
2145
2559
  children: config.children,
2560
+ timers: config.timers ?? {},
2146
2561
  historyValue: config.historyValue || {},
2147
2562
  _stateInputs: config._stateInputs || {},
2563
+ _nextTimerId: config._nextTimerId ?? 0,
2148
2564
  matches: machineSnapshotMatches,
2149
2565
  hasTag: machineSnapshotHasTag,
2150
2566
  can: machineSnapshotCan,
@@ -2152,6 +2568,10 @@ function createMachineSnapshot(config, machine) {
2152
2568
  getInputs: machineSnapshotGetInputs,
2153
2569
  toJSON: machineSnapshotToJSON
2154
2570
  };
2571
+ if (actorRef) {
2572
+ setSnapshotActorRef(snapshot, actorRef);
2573
+ }
2574
+ return snapshot;
2155
2575
  }
2156
2576
  function cloneMachineSnapshot(snapshot, config = {}) {
2157
2577
  const configWithSnapshot = {
@@ -2159,7 +2579,7 @@ function cloneMachineSnapshot(snapshot, config = {}) {
2159
2579
  ...config
2160
2580
  };
2161
2581
  if ((config._nodes ?? snapshot._nodes) === snapshot._nodes) {
2162
- return {
2582
+ const clonedSnapshot = {
2163
2583
  status: configWithSnapshot.status,
2164
2584
  output: configWithSnapshot.output,
2165
2585
  error: configWithSnapshot.error,
@@ -2169,8 +2589,10 @@ function cloneMachineSnapshot(snapshot, config = {}) {
2169
2589
  value: snapshot.value,
2170
2590
  tags: snapshot.tags,
2171
2591
  children: configWithSnapshot.children,
2592
+ timers: configWithSnapshot.timers ?? {},
2172
2593
  historyValue: configWithSnapshot.historyValue || {},
2173
2594
  _stateInputs: configWithSnapshot._stateInputs || {},
2595
+ _nextTimerId: configWithSnapshot._nextTimerId ?? 0,
2174
2596
  matches: machineSnapshotMatches,
2175
2597
  hasTag: machineSnapshotHasTag,
2176
2598
  can: machineSnapshotCan,
@@ -2178,11 +2600,15 @@ function cloneMachineSnapshot(snapshot, config = {}) {
2178
2600
  getInputs: machineSnapshotGetInputs,
2179
2601
  toJSON: machineSnapshotToJSON
2180
2602
  };
2603
+ copySnapshotActorRef(snapshot, clonedSnapshot);
2604
+ return clonedSnapshot;
2181
2605
  }
2182
- return createMachineSnapshot({
2606
+ const clonedSnapshot = createMachineSnapshot({
2183
2607
  ...configWithSnapshot,
2184
2608
  value: undefined
2185
2609
  }, snapshot.machine);
2610
+ copySnapshotActorRef(snapshot, clonedSnapshot);
2611
+ return clonedSnapshot;
2186
2612
  }
2187
2613
  function serializeHistoryValue(historyValue) {
2188
2614
  const result = {};
@@ -2203,6 +2629,7 @@ function getPersistedSnapshot(snapshot, options) {
2203
2629
  tags,
2204
2630
  machine,
2205
2631
  children,
2632
+ timers,
2206
2633
  context,
2207
2634
  can,
2208
2635
  hasTag,
@@ -2213,6 +2640,7 @@ function getPersistedSnapshot(snapshot, options) {
2213
2640
  ...jsonValues
2214
2641
  } = snapshot;
2215
2642
  const childrenJson = {};
2643
+ const timersJson = {};
2216
2644
  for (const id in children) {
2217
2645
  const child = children[id];
2218
2646
  if (typeof child.src !== 'string' && (!options || !('__unsafeAllowInlineActors' in options))) {
@@ -2225,10 +2653,36 @@ function getPersistedSnapshot(snapshot, options) {
2225
2653
  syncSnapshot: child._syncSnapshot
2226
2654
  };
2227
2655
  }
2656
+ for (const id in timers) {
2657
+ const timer = timers[id];
2658
+ let target;
2659
+ if (timer.target === 'self') {
2660
+ target = 'self';
2661
+ } else {
2662
+ const childId = Object.entries(children).find(([, child]) => child === timer.target)?.[0];
2663
+ if (childId) {
2664
+ target = childId;
2665
+ } else if (timer.target === getSnapshotActorRef(snapshot)?.actor._parent) {
2666
+ target = {
2667
+ type: 'parent'
2668
+ };
2669
+ } else {
2670
+ throw new Error(`Unable to persist timer '${id}': target actor '${timer.target.id}' is no longer addressable from this snapshot.`);
2671
+ }
2672
+ }
2673
+ timersJson[id] = {
2674
+ id: timer.id,
2675
+ delay: timer.delay,
2676
+ type: timer.type,
2677
+ event: timer.event,
2678
+ target
2679
+ };
2680
+ }
2228
2681
  const persisted = {
2229
2682
  ...jsonValues,
2230
2683
  context: persistContext(context),
2231
2684
  children: childrenJson,
2685
+ timers: timersJson,
2232
2686
  historyValue: serializeHistoryValue(jsonValues.historyValue)
2233
2687
  };
2234
2688
  if (_stateInputs && Object.keys(_stateInputs).length > 0) {
@@ -2495,11 +2949,17 @@ function matchesEventDescriptor(eventType, descriptor) {
2495
2949
  return true;
2496
2950
  }
2497
2951
 
2498
- function createScheduledEventId(actor, id) {
2952
+ /**
2953
+ * Runtime operations used to execute effects.
2954
+ *
2955
+ * An external interpreter can override these operations while the default actor
2956
+ * system provides the local in-memory implementation.
2957
+ */
2958
+
2959
+ function createScheduledTimerId(actor, id) {
2499
2960
  return `${actor.sessionId}.${id}`;
2500
2961
  }
2501
2962
  function createRuntimeSystem(rootActor, options) {
2502
- let idCounter = 0;
2503
2963
  const children = new Map();
2504
2964
  const keyedActors = new Map();
2505
2965
  const reverseKeyedActors = new WeakMap();
@@ -2527,42 +2987,50 @@ function createRuntimeSystem(rootActor, options) {
2527
2987
  });
2528
2988
  };
2529
2989
  const scheduler = {
2530
- schedule: (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
2531
- recordSent(source, target, event, delay, id);
2532
- const scheduledEvent = {
2990
+ schedule: (source, id, delay) => {
2991
+ const existingId = createScheduledTimerId(source, id);
2992
+ if (timerMap[existingId] !== undefined) {
2993
+ scheduler.cancel(source, id);
2994
+ }
2995
+ const timer = source.getSnapshot()?.timers?.[id];
2996
+ if (timer) {
2997
+ const target = timer.target === 'self' ? source : timer.target;
2998
+ recordSent(source, target, timer.event, delay, id);
2999
+ }
3000
+ const scheduledAt = Date.now();
3001
+ const scheduledTimer = {
2533
3002
  source,
2534
- target,
2535
- event,
2536
3003
  delay,
2537
3004
  id,
2538
- startedAt: Date.now()
3005
+ scheduledAt,
3006
+ dueAt: scheduledAt + delay
2539
3007
  };
2540
- const scheduledEventId = createScheduledEventId(source, id);
2541
- system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
3008
+ const scheduledTimerId = createScheduledTimerId(source, id);
3009
+ system._snapshot._scheduledTimers[scheduledTimerId] = scheduledTimer;
2542
3010
  const timeout = clock.setTimeout(() => {
2543
- delete timerMap[scheduledEventId];
2544
- delete system._snapshot._scheduledEvents[scheduledEventId];
2545
-
2546
- // The send was already recorded at schedule time on the sender's
2547
- // transition, so deliver without re-recording.
2548
- deliver(source, target, event);
3011
+ delete timerMap[scheduledTimerId];
3012
+ delete system._snapshot._scheduledTimers[scheduledTimerId];
3013
+ deliver(source, source, {
3014
+ type: XSTATE_TIMER,
3015
+ id
3016
+ });
2549
3017
  }, delay);
2550
- timerMap[scheduledEventId] = timeout;
3018
+ timerMap[scheduledTimerId] = timeout;
2551
3019
  },
2552
3020
  cancel: (source, id) => {
2553
- const scheduledEventId = createScheduledEventId(source, id);
2554
- const timeout = timerMap[scheduledEventId];
2555
- delete timerMap[scheduledEventId];
2556
- delete system._snapshot._scheduledEvents[scheduledEventId];
3021
+ const scheduledTimerId = createScheduledTimerId(source, id);
3022
+ const timeout = timerMap[scheduledTimerId];
3023
+ delete timerMap[scheduledTimerId];
3024
+ delete system._snapshot._scheduledTimers[scheduledTimerId];
2557
3025
  if (timeout !== undefined) {
2558
3026
  clock.clearTimeout(timeout);
2559
3027
  }
2560
3028
  },
2561
3029
  cancelAll: actor => {
2562
- for (const scheduledEventId in system._snapshot._scheduledEvents) {
2563
- const scheduledEvent = system._snapshot._scheduledEvents[scheduledEventId];
2564
- if (scheduledEvent.source === actor) {
2565
- scheduler.cancel(actor, scheduledEvent.id);
3030
+ for (const scheduledTimerId in system._snapshot._scheduledTimers) {
3031
+ const scheduledTimer = system._snapshot._scheduledTimers[scheduledTimerId];
3032
+ if (scheduledTimer.source === actor) {
3033
+ scheduler.cancel(actor, scheduledTimer.id);
2566
3034
  }
2567
3035
  }
2568
3036
  }
@@ -2595,9 +3063,10 @@ function createRuntimeSystem(rootActor, options) {
2595
3063
  reverseKeyedActors,
2596
3064
  keyedActors,
2597
3065
  _snapshot: {
2598
- _scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
3066
+ _scheduledTimers: (options?.snapshot && options.snapshot.scheduler) ?? {},
3067
+ _nextActorId: 0
2599
3068
  },
2600
- _bookId: () => `x:${idCounter++}`,
3069
+ _bookId: () => `x:${system._snapshot._nextActorId++}`,
2601
3070
  _register: (sessionId, actor) => {
2602
3071
  children.set(sessionId, actor);
2603
3072
  return sessionId;
@@ -2606,7 +3075,9 @@ function createRuntimeSystem(rootActor, options) {
2606
3075
  children.delete(actor.sessionId);
2607
3076
  const registryKey = reverseKeyedActors.get(actor);
2608
3077
  if (registryKey !== undefined) {
2609
- keyedActors.delete(registryKey);
3078
+ if (keyedActors.get(registryKey) === actor) {
3079
+ keyedActors.delete(registryKey);
3080
+ }
2610
3081
  reverseKeyedActors.delete(actor);
2611
3082
  }
2612
3083
  },
@@ -2634,35 +3105,48 @@ function createRuntimeSystem(rootActor, options) {
2634
3105
  };
2635
3106
  },
2636
3107
  _sendInspectionEvent: sendInspectionEvent,
2637
- _relay: (source, target, event) => {
3108
+ createActorRef: options.createActorRef,
3109
+ spawnActor: () => {},
3110
+ startActor: actor => {
3111
+ actor.start();
3112
+ },
3113
+ stopActor: actor => {
3114
+ actor._stop();
3115
+ },
3116
+ terminateActor: (actor, termination) => {
3117
+ actor._terminate(termination);
3118
+ },
3119
+ sendEvent: (source, target, event) => {
2638
3120
  recordSent(source, target, event);
2639
3121
  deliver(source, target, event);
2640
3122
  },
3123
+ emitEvent: (source, event) => source._emit(event),
3124
+ scheduleTimer: (source, id, delay) => scheduler.schedule(source, id, delay),
3125
+ cancelTimer: (source, id) => scheduler.cancel(source, id),
3126
+ cancelAllTimers: source => scheduler.cancelAll(source),
3127
+ _relay: (source, target, event) => system.sendEvent(source, target, event),
2641
3128
  scheduler,
2642
3129
  getSnapshot: () => {
2643
3130
  return {
2644
- _scheduledEvents: {
2645
- ...system._snapshot._scheduledEvents
3131
+ _scheduledTimers: {
3132
+ ...system._snapshot._scheduledTimers
2646
3133
  }
2647
3134
  };
2648
3135
  },
2649
3136
  start: () => {
2650
- const scheduledEvents = system._snapshot._scheduledEvents;
2651
- system._snapshot._scheduledEvents = {};
2652
- for (const scheduledId in scheduledEvents) {
3137
+ const scheduledTimers = system._snapshot._scheduledTimers;
3138
+ system._snapshot._scheduledTimers = {};
3139
+ for (const scheduledId in scheduledTimers) {
2653
3140
  const {
2654
3141
  source,
2655
- target,
2656
- event,
2657
- delay,
3142
+ dueAt,
2658
3143
  id
2659
- } = scheduledEvents[scheduledId];
2660
- scheduler.schedule(source, target, event, delay, id);
3144
+ } = scheduledTimers[scheduledId];
3145
+ system.scheduleTimer(source, id, Math.max(0, dueAt - Date.now()));
2661
3146
  }
2662
3147
  },
2663
3148
  _clock: clock,
2664
- _logger: logger,
2665
- _timerStrategy: options.timers
3149
+ _logger: logger
2666
3150
  };
2667
3151
  return system;
2668
3152
  }
@@ -2697,22 +3181,13 @@ function safeCall(fn, arg) {
2697
3181
  reportUnhandledError(err);
2698
3182
  }
2699
3183
  }
2700
- function isExecutableActionObject(effect) {
2701
- return typeof effect === 'object' && effect !== null && 'args' in effect && 'exec' in effect;
2702
- }
2703
3184
  function executeExecutableEffects(effects, actorScope) {
2704
3185
  if (!effects?.length) {
2705
- return [];
3186
+ return;
2706
3187
  }
2707
- const logicEffects = [];
2708
3188
  for (const effect of effects) {
2709
- if (isExecutableActionObject(effect)) {
2710
- actorScope.actionExecutor(effect);
2711
- } else {
2712
- logicEffects.push(effect);
2713
- }
3189
+ actorScope.actionExecutor(effect);
2714
3190
  }
2715
- return logicEffects;
2716
3191
  }
2717
3192
 
2718
3193
  /**
@@ -2778,8 +3253,7 @@ class Actor {
2778
3253
  this.src = void 0;
2779
3254
  // array of functions to defer
2780
3255
  this._deferred = [];
2781
- // pending effects (timers) from a persisted snapshot, rescheduled on `start()`
2782
- this._pendingEffects = void 0;
3256
+ this._restored = false;
2783
3257
  const resolvedOptions = {
2784
3258
  ...defaultOptions,
2785
3259
  ...options
@@ -2796,7 +3270,7 @@ class Actor {
2796
3270
  this.system = parent ? parent.system : resolvedOptions._systemRef?.current ?? createRuntimeSystem(this, {
2797
3271
  clock,
2798
3272
  logger,
2799
- timers: resolvedOptions.timers
3273
+ createActorRef: (childLogic, childOptions) => createActor(childLogic, childOptions)
2800
3274
  });
2801
3275
  if (!parent && resolvedOptions._systemRef && !resolvedOptions._systemRef.current) {
2802
3276
  resolvedOptions._systemRef.current = this.system;
@@ -2829,15 +3303,7 @@ class Actor {
2829
3303
  }
2830
3304
  child._stop();
2831
3305
  },
2832
- emit: emittedEvent => {
2833
- for (const listeners of [this.eventListeners.get(emittedEvent.type), this.eventListeners.get('*')]) {
2834
- if (listeners) {
2835
- for (const handler of listeners) {
2836
- safeCall(handler, emittedEvent);
2837
- }
2838
- }
2839
- }
2840
- },
3306
+ emit: emittedEvent => this.system.emitEvent(this, emittedEvent),
2841
3307
  actionExecutor: action => {
2842
3308
  const exec = () => {
2843
3309
  // Record every executed action for the '@xstate.transition' inspection
@@ -2846,13 +3312,10 @@ class Actor {
2846
3312
  type: action.type,
2847
3313
  params: action.params
2848
3314
  });
2849
- if (!action.exec) {
2850
- return;
2851
- }
2852
3315
  const saveExecutingCustomAction = executingCustomAction;
2853
3316
  try {
2854
3317
  executingCustomAction = true;
2855
- action.exec();
3318
+ void action.exec();
2856
3319
  } finally {
2857
3320
  executingCustomAction = saveExecutingCustomAction;
2858
3321
  }
@@ -2885,38 +3348,29 @@ class Actor {
2885
3348
 
2886
3349
  // prepare to collect initial microsteps during initialTransition
2887
3350
  this._collectedMicrosteps = [];
2888
- let persistedState = options?.snapshot ?? options?.state;
2889
- if (persistedState && typeof persistedState === 'object' && '_pendingEffects' in persistedState) {
2890
- // Pending effects are restored by this actor on `start()`; the logic
2891
- // only sees the rest of the persisted snapshot.
2892
- const {
2893
- _pendingEffects,
2894
- ...rest
2895
- } = persistedState;
2896
- this._pendingEffects = _pendingEffects;
2897
- persistedState = rest;
2898
- }
3351
+ const persistedState = options?.snapshot ?? options?.state;
3352
+ this._restored = persistedState !== undefined;
2899
3353
  try {
2900
3354
  if (persistedState) {
2901
- this._snapshot = this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState;
3355
+ this._setSnapshot(this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState);
2902
3356
  } else if (options?._inert) {
2903
3357
  // Inert actors (createInertActorScope) only anchor a scope for pure
2904
3358
  // transition functions; computing an initial snapshot here would run
2905
3359
  // init-time side effects (context factories, entry) a second time.
2906
3360
  } else {
2907
- const [snapshot, effects] = this.logic.initialTransition(this.options?.input, this._actorScope);
2908
- this._snapshot = snapshot;
3361
+ const [snapshot, effects] = finalizeTransitionResult(this._actorScope, undefined, this.logic.initialTransition(this.options?.input, this._actorScope));
3362
+ this._setSnapshot(snapshot);
2909
3363
  this._initialEffects = effects;
2910
3364
  }
2911
3365
  } catch (err) {
2912
3366
  // if we get here then it means that we assign a value to this._snapshot that is not of the correct type
2913
3367
  // we can't get the true `TSnapshot & { status: 'error'; }`, it's impossible
2914
3368
  // so right now this is a lie of sorts
2915
- this._snapshot = {
3369
+ this._setSnapshot({
2916
3370
  status: 'error',
2917
3371
  output: undefined,
2918
3372
  error: err
2919
- };
3373
+ });
2920
3374
  // discard any functions deferred during the failed initial snapshot
2921
3375
  // computation so they can't run against an inconsistent actor
2922
3376
  this._deferred.length = 0;
@@ -2937,12 +3391,17 @@ class Actor {
2937
3391
  snapshot: this._snapshot
2938
3392
  });
2939
3393
  }
3394
+ /** Associates each live snapshot with this actor for later pure transitions. */
3395
+ _setSnapshot(snapshot) {
3396
+ this._snapshot = snapshot;
3397
+ setSnapshotActorRef(snapshot, this);
3398
+ }
2940
3399
  _setErrorSnapshot(err, snapshot = this._snapshot) {
2941
- this._snapshot = {
3400
+ this._setSnapshot({
2942
3401
  ...snapshot,
2943
3402
  status: 'error',
2944
3403
  error: err
2945
- };
3404
+ });
2946
3405
  }
2947
3406
 
2948
3407
  /** Recover via the logic's error event if possible; otherwise error out. */
@@ -2960,11 +3419,10 @@ class Actor {
2960
3419
  return false;
2961
3420
  }
2962
3421
  try {
2963
- const [nextSnapshot, effects] = this.logic.transition(snapshot, errorEvent, this._actorScope);
2964
- this._snapshot = nextSnapshot;
2965
- const logicEffects = executeExecutableEffects(effects, this._actorScope);
3422
+ const [nextSnapshot, effects] = finalizeTransitionResult(this._actorScope, snapshot, this.logic.transition(snapshot, errorEvent, this._actorScope));
3423
+ this._setSnapshot(nextSnapshot);
3424
+ executeExecutableEffects(effects, this._actorScope);
2966
3425
  this.update(nextSnapshot, errorEvent);
2967
- this.logic.executeEffects?.(logicEffects, this._actorScope);
2968
3426
  return true;
2969
3427
  } catch {
2970
3428
  return false;
@@ -2977,7 +3435,7 @@ class Actor {
2977
3435
  }
2978
3436
  update(snapshot, event) {
2979
3437
  // Update state
2980
- this._snapshot = snapshot;
3438
+ this._setSnapshot(snapshot);
2981
3439
 
2982
3440
  // Execute deferred effects
2983
3441
  for (let i = 0; i < this._deferred.length; i++) {
@@ -2995,6 +3453,7 @@ class Actor {
2995
3453
  return;
2996
3454
  }
2997
3455
  this._setErrorSnapshot(err, snapshot);
3456
+ this._error(err);
2998
3457
  break;
2999
3458
  }
3000
3459
  }
@@ -3003,26 +3462,6 @@ class Actor {
3003
3462
  case 'active':
3004
3463
  this._next(snapshot);
3005
3464
  break;
3006
- case 'done':
3007
- {
3008
- // next observers are meant to be notified about done snapshots
3009
- // this can be seen as something that is different from how observable work
3010
- // but with observables `complete` callback is called without any arguments
3011
- // it's more ergonomic for XState to treat a done snapshot as a "next" value
3012
- // and the completion event as something that is separate,
3013
- // something that merely follows emitting that done snapshot
3014
- this._next(snapshot);
3015
- this._stopProcedure();
3016
- this._complete();
3017
- const doneEvent = createDoneActorEvent(this.id, this._snapshot.output);
3018
- if (this._parent) {
3019
- this.system._relay(this, this._parent, doneEvent);
3020
- }
3021
- break;
3022
- }
3023
- case 'error':
3024
- this._error(this._snapshot.error);
3025
- break;
3026
3465
  }
3027
3466
  this.system._sendInspectionEvent({
3028
3467
  type: '@xstate.transition',
@@ -3047,8 +3486,7 @@ class Actor {
3047
3486
  }
3048
3487
  this._forceDeferredActions = true;
3049
3488
  try {
3050
- const logicEffects = executeExecutableEffects(this._initialEffects, this._actorScope);
3051
- this.logic.executeEffects?.(logicEffects, this._actorScope);
3489
+ executeExecutableEffects(this._initialEffects, this._actorScope);
3052
3490
  this._initialEffects = undefined;
3053
3491
  return true;
3054
3492
  } catch (err) {
@@ -3219,6 +3657,14 @@ class Actor {
3219
3657
  case 'done':
3220
3658
  // a state machine can be "done" upon initialization (it could reach a final state using initial microsteps)
3221
3659
  // we still need to complete observers, flush deferreds etc
3660
+ if (this._restored) {
3661
+ // Restoration does not replay the transition that originally
3662
+ // terminated the actor, and must not notify the parent again.
3663
+ this._next(this._snapshot);
3664
+ this._stopProcedure();
3665
+ this._complete();
3666
+ return this;
3667
+ }
3222
3668
  if (!this._flushInitialEffects()) {
3223
3669
  return this;
3224
3670
  }
@@ -3232,33 +3678,31 @@ class Actor {
3232
3678
  if (!this._parent) {
3233
3679
  this.system.start();
3234
3680
  }
3681
+ if (!this._flushInitialEffects()) {
3682
+ return this;
3683
+ }
3235
3684
  if (this.logic.start) {
3236
3685
  try {
3237
- this.logic.start(this._snapshot, this._actorScope);
3686
+ this.logic.start(this._snapshot, this._actorScope, {
3687
+ restored: this._restored
3688
+ });
3238
3689
  } catch (err) {
3239
3690
  this._setErrorSnapshot(err);
3240
3691
  this._error(err);
3241
3692
  return this;
3242
3693
  }
3243
3694
  }
3244
- if (!this._flushInitialEffects()) {
3245
- return this;
3246
- }
3247
3695
 
3248
3696
  // TODO: this notifies all subscribers but usually this is redundant
3249
3697
  // there is no real change happening here
3250
3698
  // we need to rethink if this needs to be refactored
3251
3699
  this.update(this._snapshot, initEvent);
3252
- if (this._pendingEffects) {
3253
- const strategy = this.options.timers ?? this.system._timerStrategy ?? 'resume';
3254
- for (const effect of this._pendingEffects) {
3255
- // Re-execute the pending effect with its remaining delay. Only
3256
- // self-targeted raises exist today; other effect types are ignored.
3257
- if (effect.type === '@xstate.raise') {
3258
- this.system.scheduler.schedule(this, this, effect.event, resolveRestoredTimerDelay(strategy, effect), effect.id);
3259
- }
3700
+ if (this._restored) {
3701
+ const timers = this._snapshot.timers ?? {};
3702
+ for (const timer of Object.values(timers)) {
3703
+ this.system.scheduleTimer(this, timer.id, timer.delay);
3260
3704
  }
3261
- this._pendingEffects = undefined;
3705
+ this._restored = false;
3262
3706
  }
3263
3707
  this.mailbox.start();
3264
3708
  return this;
@@ -3267,7 +3711,7 @@ class Actor {
3267
3711
  let nextState;
3268
3712
  let caughtError;
3269
3713
  try {
3270
- nextState = this.logic.transition(this._snapshot, event, this._actorScope);
3714
+ nextState = finalizeTransitionResult(this._actorScope, this._snapshot, this.logic.transition(this._snapshot, event, this._actorScope));
3271
3715
  } catch (err) {
3272
3716
  // we wrap it in a box so we can rethrow it later even if falsy value gets caught here
3273
3717
  caughtError = {
@@ -3282,10 +3726,9 @@ class Actor {
3282
3726
  try {
3283
3727
  const [nextSnapshot, effects] = nextState;
3284
3728
  snapshot = nextSnapshot;
3285
- this._snapshot = snapshot;
3286
- const logicEffects = executeExecutableEffects(effects, this._actorScope);
3729
+ this._setSnapshot(snapshot);
3730
+ executeExecutableEffects(effects, this._actorScope);
3287
3731
  this.update(snapshot, event);
3288
- this.logic.executeEffects?.(logicEffects, this._actorScope);
3289
3732
  } catch (err) {
3290
3733
  this._recoverOrError(err, snapshot);
3291
3734
  return;
@@ -3296,6 +3739,35 @@ class Actor {
3296
3739
  }
3297
3740
  }
3298
3741
 
3742
+ /** @internal */
3743
+ _emit(event) {
3744
+ for (const listeners of [this.eventListeners.get(event.type), this.eventListeners.get('*')]) {
3745
+ if (listeners) {
3746
+ for (const handler of listeners) {
3747
+ safeCall(handler, event);
3748
+ }
3749
+ }
3750
+ }
3751
+ }
3752
+
3753
+ /** @internal */
3754
+ _terminate(termination) {
3755
+ if (termination.status === 'done') {
3756
+ // The terminal snapshot is published before observer completion and the
3757
+ // parent notification, matching actor lifecycle order.
3758
+ this._next(this._snapshot);
3759
+ this._stopProcedure();
3760
+ this._complete();
3761
+ if (this._parent) {
3762
+ this.system._relay(this, this._parent, createDoneActorEvent(this.id, termination.output));
3763
+ }
3764
+ return;
3765
+ }
3766
+ if (termination.status === 'error') {
3767
+ this._error(termination.error);
3768
+ }
3769
+ }
3770
+
3299
3771
  /** @internal */
3300
3772
  _stop() {
3301
3773
  if (this._processingStatus === ProcessingStatus.Stopped) {
@@ -3304,6 +3776,7 @@ class Actor {
3304
3776
  this.mailbox.clear();
3305
3777
  if (this._processingStatus === ProcessingStatus.NotStarted) {
3306
3778
  this._processingStatus = ProcessingStatus.Stopped;
3779
+ this.system._unregister(this);
3307
3780
  return this;
3308
3781
  }
3309
3782
  this.mailbox.enqueue({
@@ -3362,7 +3835,7 @@ class Actor {
3362
3835
  }
3363
3836
 
3364
3837
  // Cancel all delayed events
3365
- this.system.scheduler.cancelAll(this);
3838
+ this.system.cancelAllTimers(this);
3366
3839
 
3367
3840
  // TODO: mailbox.reset
3368
3841
  this.mailbox.clear();
@@ -3423,35 +3896,7 @@ class Actor {
3423
3896
  */
3424
3897
 
3425
3898
  getPersistedSnapshot(options) {
3426
- const persisted = this.logic.getPersistedSnapshot(this._snapshot, options);
3427
-
3428
- // Capture this actor's pending self-targeted effects (delayed
3429
- // events/transitions) as serialized action descriptors plus their
3430
- // runtime progress, so they can be restored on `start()`.
3431
- const scheduledEvents = this.system._snapshot._scheduledEvents;
3432
- let pendingEffects;
3433
- const now = Date.now();
3434
- for (const key in scheduledEvents) {
3435
- const scheduled = scheduledEvents[key];
3436
- if (scheduled.source === this && scheduled.target === this) {
3437
- (pendingEffects ??= []).push({
3438
- type: '@xstate.raise',
3439
- event: scheduled.event,
3440
- id: scheduled.id,
3441
- delay: scheduled.delay,
3442
- startedAt: scheduled.startedAt,
3443
- elapsed: Math.max(0, now - scheduled.startedAt)
3444
- });
3445
- }
3446
- }
3447
-
3448
- // If this actor was restored but not yet started, its pending effects
3449
- // are not in the scheduler yet — carry them through as-is.
3450
- pendingEffects ??= this._pendingEffects;
3451
- return pendingEffects ? {
3452
- ...persisted,
3453
- _pendingEffects: pendingEffects
3454
- } : persisted;
3899
+ return this.logic.getPersistedSnapshot(this._snapshot, options);
3455
3900
  }
3456
3901
  [symbolObservable]() {
3457
3902
  return this;
@@ -3478,20 +3923,6 @@ class Actor {
3478
3923
  return this._snapshot;
3479
3924
  }
3480
3925
  }
3481
- function resolveRestoredTimerDelay(strategy, effect) {
3482
- if (typeof strategy === 'function') {
3483
- return strategy(effect);
3484
- }
3485
- switch (strategy) {
3486
- case 'restart':
3487
- return effect.delay;
3488
- case 'absolute':
3489
- return Math.max(0, effect.startedAt + effect.delay - Date.now());
3490
- case 'resume':
3491
- default:
3492
- return Math.max(0, effect.delay - effect.elapsed);
3493
- }
3494
- }
3495
3926
  /**
3496
3927
  * Creates a new actor instance for the given actor logic with the provided
3497
3928
  * options, if any.
@@ -3547,63 +3978,27 @@ function getEffectState(self) {
3547
3978
  }
3548
3979
  return state;
3549
3980
  }
3550
- function executeLogicEffects(effects, actorScope) {
3551
- if (!effects?.length) {
3981
+ function executeLogicEffect(self, key, effect) {
3982
+ const state = getEffectState(self);
3983
+ if (key !== undefined && state.has(key)) {
3552
3984
  return;
3553
3985
  }
3554
- for (const effect of effects) {
3555
- switch (effect.type) {
3556
- case 'emit':
3557
- actorScope.emit(effect.event);
3558
- break;
3559
- case 'sendBack':
3560
- {
3561
- const parent = actorScope.self._parent;
3562
- if (parent) {
3563
- actorScope.system._relay(actorScope.self, parent, effect.event);
3564
- }
3565
- break;
3566
- }
3567
- case 'raise':
3568
- actorScope.system._relay(actorScope.self, actorScope.self, effect.event);
3569
- break;
3570
- case 'effect':
3571
- {
3572
- if (!effect.key) {
3573
- const cleanup = effect.exec();
3574
- if (typeof cleanup === 'function') {
3575
- getEffectState(actorScope.self).set(Symbol(), {
3576
- cleanup
3577
- });
3578
- }
3579
- break;
3580
- }
3581
- const state = getEffectState(actorScope.self);
3582
- if (state.has(effect.key)) {
3583
- break;
3584
- }
3585
- const cleanup = effect.exec();
3586
- state.set(effect.key, {
3587
- cleanup: typeof cleanup === 'function' ? cleanup : undefined
3588
- });
3589
- break;
3590
- }
3591
- case 'cleanupEffects':
3592
- {
3593
- const state = effectStates.get(actorScope.self);
3594
- if (!state) {
3595
- break;
3596
- }
3597
- for (const {
3598
- cleanup
3599
- } of state.values()) {
3600
- cleanup?.();
3601
- }
3602
- effectStates.delete(actorScope.self);
3603
- break;
3604
- }
3605
- }
3986
+ const cleanup = effect();
3987
+ state.set(key ?? Symbol(), {
3988
+ cleanup: typeof cleanup === 'function' ? cleanup : undefined
3989
+ });
3990
+ }
3991
+ function cleanupLogicEffects(self) {
3992
+ const state = effectStates.get(self);
3993
+ if (!state) {
3994
+ return;
3995
+ }
3996
+ for (const {
3997
+ cleanup
3998
+ } of state.values()) {
3999
+ cleanup?.();
3606
4000
  }
4001
+ effectStates.delete(self);
3607
4002
  }
3608
4003
  function resolveContext(context, input) {
3609
4004
  return typeof context === 'function' ? context({
@@ -3620,9 +4015,7 @@ function createLogic(config) {
3620
4015
  ...snapshot,
3621
4016
  status: 'stopped',
3622
4017
  input: undefined
3623
- }, [{
3624
- type: 'cleanupEffects'
3625
- }]];
4018
+ }, [createCustomEffect('xstate.logic.cleanup', () => cleanupLogicEffects(actorScope.self))]];
3626
4019
  }
3627
4020
  if (event.type === XSTATE_LOGIC_EFFECT_START) {
3628
4021
  return [{
@@ -3665,43 +4058,33 @@ function createLogic(config) {
3665
4058
  if (snapshot.effects?.[key]) {
3666
4059
  return;
3667
4060
  }
3668
- effects.push({
3669
- type: 'effect',
3670
- key,
3671
- exec
3672
- });
4061
+ effects.push(createCustomEffect('xstate.logic.effect', (runtime = actorScope.self.system) => executeLogicEffect(actorScope.self, key, () => exec(runtime)), {
4062
+ key
4063
+ }));
3673
4064
  trackedEffects[key] = {
3674
4065
  status: 'active'
3675
4066
  };
3676
4067
  };
3677
4068
  const enq = {
3678
4069
  emit: emitted => {
3679
- effects.push({
3680
- type: 'emit',
3681
- event: emitted
3682
- });
4070
+ effects.push(createEmitEffect(actorScope, emitted));
3683
4071
  },
3684
4072
  sendBack: sentEvent => {
3685
- effects.push({
3686
- type: 'sendBack',
3687
- event: sentEvent
3688
- });
4073
+ const parent = actorScope.self._parent;
4074
+ if (parent) {
4075
+ effects.push(createSendToEffect(actorScope, parent, sentEvent));
4076
+ }
3689
4077
  },
3690
4078
  raise: raisedEvent => {
3691
- effects.push({
3692
- type: 'raise',
3693
- event: raisedEvent
3694
- });
4079
+ effects.push(createSendToEffect(actorScope, actorScope.self, raisedEvent));
3695
4080
  },
3696
4081
  effect: (keyOrExec, maybeExec) => {
3697
4082
  if (typeof keyOrExec === 'string') {
3698
4083
  enqueueEffect(keyOrExec, maybeExec);
3699
4084
  return;
3700
4085
  }
3701
- effects.push({
3702
- type: 'effect',
3703
- exec: keyOrExec
3704
- });
4086
+ const exec = keyOrExec;
4087
+ effects.push(createCustomEffect('xstate.logic.effect', (runtime = actorScope.self.system) => executeLogicEffect(actorScope.self, undefined, () => exec(runtime))));
3705
4088
  }
3706
4089
  };
3707
4090
  const patch = config.run({
@@ -3726,18 +4109,23 @@ function createLogic(config) {
3726
4109
  ...trackedEffects
3727
4110
  };
3728
4111
  }
3729
- return [nextSnapshot, effects];
4112
+ return finalizeTransitionResult(actorScope, snapshot, [nextSnapshot, effects]);
3730
4113
  };
3731
4114
  const logic = {
3732
4115
  id: config.id,
3733
4116
  config,
3734
4117
  transition,
3735
- start: (snapshot, actorScope) => {
4118
+ start: (snapshot, actorScope, options) => {
4119
+ if (!options?.restored) {
4120
+ return;
4121
+ }
3736
4122
  const [nextSnapshot, effects] = transition(snapshot, createInitEvent(snapshot.input), actorScope);
3737
4123
  Object.assign(snapshot, nextSnapshot);
3738
- executeLogicEffects(effects, actorScope);
4124
+ for (const effect of effects) {
4125
+ actorScope.actionExecutor(effect);
4126
+ }
3739
4127
  },
3740
- initialTransition: (input, _) => {
4128
+ initialTransition: (input, actorScope) => {
3741
4129
  const context = resolveContext(config.context, input);
3742
4130
  const snapshot = {
3743
4131
  status: 'active',
@@ -3748,14 +4136,13 @@ function createLogic(config) {
3748
4136
  if (context !== undefined) {
3749
4137
  snapshot.context = context;
3750
4138
  }
3751
- return [{
4139
+ return transition({
3752
4140
  ...snapshot
3753
- }, []];
4141
+ }, createInitEvent(input), actorScope);
3754
4142
  },
3755
4143
  getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
3756
4144
  getPersistedSnapshot: snapshot => snapshot,
3757
- restoreSnapshot: snapshot => snapshot,
3758
- executeEffects: executeLogicEffects
4145
+ restoreSnapshot: snapshot => snapshot
3759
4146
  };
3760
4147
  return logic;
3761
4148
  }
@@ -4015,7 +4402,6 @@ function createObservableLogic(observableCreatorOrConfig) {
4015
4402
  self,
4016
4403
  system
4017
4404
  } = args;
4018
- const emit = args.emit;
4019
4405
  switch (event.type) {
4020
4406
  case XSTATE_OBSERVABLE_NEXT:
4021
4407
  {
@@ -4046,28 +4432,32 @@ function createObservableLogic(observableCreatorOrConfig) {
4046
4432
  }
4047
4433
  };
4048
4434
  }
4049
- enq.effect('observable', () => {
4435
+ if (event.type !== XSTATE_INIT) {
4436
+ return;
4437
+ }
4438
+ enq.effect((runtime = system) => {
4050
4439
  const actorSelf = self;
4440
+ const sendSelf = event => void runtime.sendEvent(actorSelf, actorSelf, event);
4051
4441
  const subscription = observableCreator({
4052
4442
  input,
4053
4443
  system,
4054
4444
  self: self,
4055
- emit: emit
4445
+ emit: event => void runtime.emitEvent(actorSelf, event)
4056
4446
  }).subscribe({
4057
4447
  next: value => {
4058
- system._relay(actorSelf, actorSelf, {
4448
+ sendSelf({
4059
4449
  type: XSTATE_OBSERVABLE_NEXT,
4060
4450
  data: value
4061
4451
  });
4062
4452
  },
4063
4453
  error: err => {
4064
- system._relay(actorSelf, actorSelf, {
4454
+ sendSelf({
4065
4455
  type: XSTATE_OBSERVABLE_ERROR,
4066
4456
  data: err
4067
4457
  });
4068
4458
  },
4069
4459
  complete: () => {
4070
- system._relay(actorSelf, actorSelf, {
4460
+ sendSelf({
4071
4461
  type: XSTATE_OBSERVABLE_COMPLETE
4072
4462
  });
4073
4463
  }
@@ -4136,26 +4526,26 @@ function createEventObservableLogic(lazyObservableOrConfig) {
4136
4526
  // TODO: event types
4137
4527
  const logic = {
4138
4528
  config: lazyObservable,
4139
- transition: (state, event) => {
4529
+ transition: (state, event, actorScope) => {
4140
4530
  if (state.status !== 'active') {
4141
4531
  return [state, []];
4142
4532
  }
4143
4533
  switch (event.type) {
4144
4534
  case XSTATE_OBSERVABLE_ERROR:
4145
- return [{
4535
+ return finalizeTransitionResult(actorScope, state, [{
4146
4536
  ...state,
4147
4537
  status: 'error',
4148
4538
  error: event.data,
4149
4539
  input: undefined,
4150
4540
  _subscription: undefined
4151
- }, []];
4541
+ }, []]);
4152
4542
  case XSTATE_OBSERVABLE_COMPLETE:
4153
- return [{
4543
+ return finalizeTransitionResult(actorScope, state, [{
4154
4544
  ...state,
4155
4545
  status: 'done',
4156
4546
  input: undefined,
4157
4547
  _subscription: undefined
4158
- }, []];
4548
+ }, []]);
4159
4549
  case XSTATE_STOP:
4160
4550
  state._subscription.unsubscribe();
4161
4551
  return [{
@@ -4190,7 +4580,9 @@ function createEventObservableLogic(lazyObservableOrConfig) {
4190
4580
  input: state.input,
4191
4581
  system,
4192
4582
  self,
4193
- emit
4583
+ emit: event => {
4584
+ void emit(event);
4585
+ }
4194
4586
  }).subscribe({
4195
4587
  next: value => {
4196
4588
  if (self._parent) {
@@ -4391,8 +4783,9 @@ function createAsyncLogic(asyncLogic) {
4391
4783
  if (event.type !== XSTATE_INIT) {
4392
4784
  return;
4393
4785
  }
4394
- enq.effect(() => {
4786
+ enq.effect((runtime = system) => {
4395
4787
  const actorSelf = self;
4788
+ const sendSelf = event => void runtime.sendEvent(actorSelf, actorSelf, event);
4396
4789
  const controller = new AbortController();
4397
4790
  const timeout = config.timeout;
4398
4791
  const timeoutMs = parseDelayToMilliseconds(timeout);
@@ -4401,7 +4794,7 @@ function createAsyncLogic(asyncLogic) {
4401
4794
  return;
4402
4795
  }
4403
4796
  controller.abort();
4404
- system._relay(actorSelf, actorSelf, {
4797
+ sendSelf({
4405
4798
  type: XSTATE_ASYNC_REJECT,
4406
4799
  data: new TimeoutError(timeout)
4407
4800
  });
@@ -4417,7 +4810,7 @@ function createAsyncLogic(asyncLogic) {
4417
4810
  self: self,
4418
4811
  signal: controller.signal
4419
4812
  }, {
4420
- emit: enq.emit,
4813
+ emit: event => void runtime.emitEvent(actorSelf, event),
4421
4814
  step: async (key, exec) => {
4422
4815
  const effect = self.getSnapshot().effects?.[key];
4423
4816
  if (effect?.status === 'done') {
@@ -4429,20 +4822,20 @@ function createAsyncLogic(asyncLogic) {
4429
4822
  if (effect?.status === 'active') {
4430
4823
  return waitForEffect(self, key);
4431
4824
  }
4432
- system._relay(actorSelf, actorSelf, {
4825
+ sendSelf({
4433
4826
  type: XSTATE_LOGIC_EFFECT_START,
4434
4827
  key
4435
4828
  });
4436
4829
  try {
4437
4830
  const output = await exec();
4438
- system._relay(actorSelf, actorSelf, {
4831
+ sendSelf({
4439
4832
  type: XSTATE_LOGIC_EFFECT_RESOLVE,
4440
4833
  key,
4441
4834
  output
4442
4835
  });
4443
4836
  return output;
4444
4837
  } catch (error) {
4445
- system._relay(actorSelf, actorSelf, {
4838
+ sendSelf({
4446
4839
  type: XSTATE_LOGIC_EFFECT_REJECT,
4447
4840
  key,
4448
4841
  error
@@ -4456,7 +4849,7 @@ function createAsyncLogic(asyncLogic) {
4456
4849
  if (self.getSnapshot().status !== 'active') {
4457
4850
  return;
4458
4851
  }
4459
- system._relay(actorSelf, actorSelf, {
4852
+ sendSelf({
4460
4853
  type: XSTATE_ASYNC_RESOLVE,
4461
4854
  data: response
4462
4855
  });
@@ -4465,7 +4858,7 @@ function createAsyncLogic(asyncLogic) {
4465
4858
  if (self.getSnapshot().status !== 'active') {
4466
4859
  return;
4467
4860
  }
4468
- system._relay(actorSelf, actorSelf, {
4861
+ sendSelf({
4469
4862
  type: XSTATE_ASYNC_REJECT,
4470
4863
  data: errorData
4471
4864
  });
@@ -4497,11 +4890,11 @@ function createEmptyActor() {
4497
4890
  exports.$$ACTOR_TYPE = $$ACTOR_TYPE;
4498
4891
  exports.Actor = Actor;
4499
4892
  exports.NULL_EVENT = NULL_EVENT;
4500
- exports.ProcessingStatus = ProcessingStatus;
4501
4893
  exports.STATE_DELIMITER = STATE_DELIMITER;
4502
4894
  exports.TimeoutError = TimeoutError;
4503
4895
  exports.XSTATE_INIT = XSTATE_INIT;
4504
4896
  exports.XSTATE_STOP = XSTATE_STOP;
4897
+ exports.XSTATE_TIMER = XSTATE_TIMER;
4505
4898
  exports.builtInActions = builtInActions;
4506
4899
  exports.checkStateIn = checkStateIn;
4507
4900
  exports.cloneMachineSnapshot = cloneMachineSnapshot;
@@ -4518,10 +4911,15 @@ exports.createListenerLogic = createListenerLogic;
4518
4911
  exports.createLogic = createLogic;
4519
4912
  exports.createMachineSnapshot = createMachineSnapshot;
4520
4913
  exports.createObservableLogic = createObservableLogic;
4914
+ exports.createSendToEffect = createSendToEffect;
4915
+ exports.createSnapshotSystem = createSnapshotSystem;
4916
+ exports.createSpawnEffect = createSpawnEffect;
4521
4917
  exports.createSubscriptionLogic = createSubscriptionLogic;
4522
4918
  exports.createTransitionEnqueue = createTransitionEnqueue;
4523
4919
  exports.deriveDeferredStarts = deriveDeferredStarts;
4524
4920
  exports.evaluateCandidate = evaluateCandidate;
4921
+ exports.executeEffects = executeEffects;
4922
+ exports.finalizeTransitionResult = finalizeTransitionResult;
4525
4923
  exports.formatRouteTransitions = formatRouteTransitions;
4526
4924
  exports.formatTransition = formatTransition;
4527
4925
  exports.getAllOwnEventDescriptors = getAllOwnEventDescriptors;
@@ -4530,6 +4928,7 @@ exports.getCandidates = getCandidates;
4530
4928
  exports.getDelayedTransitions = getDelayedTransitions;
4531
4929
  exports.getPersistedSnapshot = getPersistedSnapshot;
4532
4930
  exports.getProperAncestors = getProperAncestors;
4931
+ exports.getSnapshotActorRef = getSnapshotActorRef;
4533
4932
  exports.getStateNodeByPath = getStateNodeByPath;
4534
4933
  exports.getStateNodes = getStateNodes;
4535
4934
  exports.getTransitionResult = getTransitionResult;
@@ -4550,6 +4949,7 @@ exports.pathToStateValue = pathToStateValue;
4550
4949
  exports.resolveActionsWithContext = resolveActionsWithContext;
4551
4950
  exports.resolveReferencedActor = resolveReferencedActor;
4552
4951
  exports.resolveStateValue = resolveStateValue;
4952
+ exports.setSnapshotActorRef = setSnapshotActorRef;
4553
4953
  exports.subscriptionLogic = subscriptionLogic;
4554
4954
  exports.toArray = toArray;
4555
4955
  exports.toObserver = toObserver;