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