xstate 5.0.0-beta.12 → 5.0.0-beta.14

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 (57) hide show
  1. package/actions/dist/xstate-actions.cjs.js +1 -1
  2. package/actions/dist/xstate-actions.development.cjs.js +1 -1
  3. package/actions/dist/xstate-actions.development.esm.js +1 -1
  4. package/actions/dist/xstate-actions.esm.js +1 -1
  5. package/actions/dist/xstate-actions.umd.min.js +1 -1
  6. package/actions/dist/xstate-actions.umd.min.js.map +1 -1
  7. package/actors/dist/xstate-actors.cjs.js +1 -1
  8. package/actors/dist/xstate-actors.development.cjs.js +1 -1
  9. package/actors/dist/xstate-actors.development.esm.js +1 -1
  10. package/actors/dist/xstate-actors.esm.js +1 -1
  11. package/actors/dist/xstate-actors.umd.min.js +1 -1
  12. package/actors/dist/xstate-actors.umd.min.js.map +1 -1
  13. package/dist/{actions-e4c704f3.cjs.js → actions-17c3bcfa.cjs.js} +803 -853
  14. package/dist/{actions-b34f6ce7.esm.js → actions-444a17c3.esm.js} +804 -854
  15. package/dist/{actions-c8b9504d.development.esm.js → actions-60622c0c.development.esm.js} +829 -879
  16. package/dist/{actions-d9c19f35.development.cjs.js → actions-73b8d456.development.cjs.js} +828 -878
  17. package/dist/declarations/src/Machine.d.ts +3 -3
  18. package/dist/declarations/src/SimulatedClock.d.ts +1 -1
  19. package/dist/declarations/src/State.d.ts +4 -10
  20. package/dist/declarations/src/StateMachine.d.ts +8 -13
  21. package/dist/declarations/src/StateNode.d.ts +4 -4
  22. package/dist/declarations/src/actionTypes.d.ts +1 -1
  23. package/dist/declarations/src/actions/assign.d.ts +1 -1
  24. package/dist/declarations/src/actions/cancel.d.ts +2 -2
  25. package/dist/declarations/src/actions/choose.d.ts +2 -2
  26. package/dist/declarations/src/actions/log.d.ts +2 -2
  27. package/dist/declarations/src/actions/pure.d.ts +2 -2
  28. package/dist/declarations/src/actions/raise.d.ts +1 -1
  29. package/dist/declarations/src/actions/send.d.ts +2 -2
  30. package/dist/declarations/src/actions/stop.d.ts +1 -1
  31. package/dist/declarations/src/actions.d.ts +10 -10
  32. package/dist/declarations/src/actors/callback.d.ts +2 -2
  33. package/dist/declarations/src/actors/index.d.ts +6 -6
  34. package/dist/declarations/src/actors/observable.d.ts +8 -6
  35. package/dist/declarations/src/actors/promise.d.ts +4 -3
  36. package/dist/declarations/src/actors/transition.d.ts +4 -4
  37. package/dist/declarations/src/dev/index.d.ts +1 -1
  38. package/dist/declarations/src/guards.d.ts +2 -2
  39. package/dist/declarations/src/index.d.ts +22 -22
  40. package/dist/declarations/src/interpreter.d.ts +18 -18
  41. package/dist/declarations/src/stateUtils.d.ts +8 -10
  42. package/dist/declarations/src/typegenTypes.d.ts +1 -1
  43. package/dist/declarations/src/types.d.ts +29 -39
  44. package/dist/declarations/src/utils.d.ts +9 -9
  45. package/dist/declarations/src/waitFor.d.ts +1 -1
  46. package/dist/xstate.cjs.js +13 -37
  47. package/dist/xstate.development.cjs.js +13 -37
  48. package/dist/xstate.development.esm.js +14 -38
  49. package/dist/xstate.esm.js +14 -38
  50. package/dist/xstate.umd.min.js +1 -1
  51. package/dist/xstate.umd.min.js.map +1 -1
  52. package/guards/dist/xstate-guards.cjs.js +1 -1
  53. package/guards/dist/xstate-guards.development.cjs.js +1 -1
  54. package/guards/dist/xstate-guards.development.esm.js +1 -1
  55. package/guards/dist/xstate-guards.esm.js +1 -1
  56. package/guards/dist/xstate-guards.umd.min.js.map +1 -1
  57. package/package.json +1 -1
@@ -10,10 +10,10 @@ import { devToolsAdapter } from '../dev/dist/xstate-dev.esm.js';
10
10
  */
11
11
 
12
12
  // TODO: do not accept machines without all implementations
13
- // we should also accept a raw machine as a behavior here
14
- // or just make machine a behavior
13
+ // we should also accept a raw machine as actor logic here
14
+ // or just make machine actor logic
15
15
 
16
- // TODO: narrow this to behaviors from machine
16
+ // TODO: narrow this to logic from machine
17
17
 
18
18
  // TODO: fix last param
19
19
 
@@ -467,6 +467,59 @@ function sendTo(actor, event, options) {
467
467
  });
468
468
  }
469
469
 
470
+ const cache = new WeakMap();
471
+ function memo(object, key, fn) {
472
+ let memoizedData = cache.get(object);
473
+ if (!memoizedData) {
474
+ memoizedData = {
475
+ [key]: fn()
476
+ };
477
+ cache.set(object, memoizedData);
478
+ } else if (!(key in memoizedData)) {
479
+ memoizedData[key] = fn();
480
+ }
481
+ return memoizedData[key];
482
+ }
483
+
484
+ /**
485
+ * Cancels an in-flight `send(...)` action. A canceled sent action will not
486
+ * be executed, nor will its event be sent, unless it has already been sent
487
+ * (e.g., if `cancel(...)` is called after the `send(...)` action's `delay`).
488
+ *
489
+ * @param sendId The `id` of the `send(...)` action to cancel.
490
+ */
491
+
492
+ function cancel(sendId) {
493
+ return createDynamicAction({
494
+ type: cancel$1,
495
+ params: {
496
+ sendId
497
+ }
498
+ }, (event, {
499
+ state,
500
+ actorContext
501
+ }) => {
502
+ const resolvedSendId = isFunction(sendId) ? sendId({
503
+ context: state.context,
504
+ event,
505
+ self: actorContext?.self ?? {},
506
+ system: actorContext?.system
507
+ }) : sendId;
508
+ return [state, {
509
+ type: 'xstate.cancel',
510
+ params: {
511
+ sendId: resolvedSendId
512
+ },
513
+ execute: actorCtx => {
514
+ const interpreter = actorCtx.self;
515
+ interpreter.cancel(resolvedSendId);
516
+ }
517
+ }];
518
+ });
519
+ }
520
+
521
+ const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
522
+
470
523
  class Mailbox {
471
524
  constructor(_process) {
472
525
  this._process = _process;
@@ -534,496 +587,51 @@ class Mailbox {
534
587
  }
535
588
  }
536
589
 
537
- const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
538
-
539
- /**
540
- * Returns an actor behavior from a transition function and its initial state.
541
- *
542
- * A transition function is a function that takes the current state and an event and returns the next state.
543
- *
544
- * @param transition The transition function that returns the next state given the current state and event.
545
- * @param initialState The initial state of the transition function.
546
- * @returns An actor behavior
547
- */
548
- function fromTransition(transition, initialState) {
549
- const behavior = {
550
- config: transition,
551
- transition: (state, event, actorContext) => {
552
- return transition(state, event, actorContext);
553
- },
554
- getInitialState: (_, input) => {
555
- return typeof initialState === 'function' ? initialState({
556
- input
557
- }) : initialState;
558
- },
559
- getSnapshot: state => state,
560
- getPersistedState: state => state,
561
- restoreState: state => state
562
- };
563
- return behavior;
564
- }
565
-
566
- function fromPromise(
567
- // TODO: add types
568
- promiseCreator) {
569
- const resolveEventType = '$$xstate.resolve';
570
- const rejectEventType = '$$xstate.reject';
571
-
572
- // TODO: add event types
573
- const behavior = {
574
- config: promiseCreator,
575
- transition: (state, event) => {
576
- if (state.status !== 'active') {
577
- return state;
578
- }
579
- switch (event.type) {
580
- case resolveEventType:
581
- return {
582
- ...state,
583
- status: 'done',
584
- data: event.data,
585
- input: undefined
586
- };
587
- case rejectEventType:
588
- return {
589
- ...state,
590
- status: 'error',
591
- data: event.data,
592
- input: undefined
593
- };
594
- case stopSignalType:
595
- return {
596
- ...state,
597
- status: 'canceled',
598
- input: undefined
599
- };
600
- default:
601
- return state;
602
- }
590
+ function createSystem() {
591
+ let sessionIdCounter = 0;
592
+ const children = new Map();
593
+ const keyedActors = new Map();
594
+ const reverseKeyedActors = new WeakMap();
595
+ const system = {
596
+ _bookId: () => `x:${sessionIdCounter++}`,
597
+ _register: (sessionId, actorRef) => {
598
+ children.set(sessionId, actorRef);
599
+ return sessionId;
603
600
  },
604
- start: (state, {
605
- self
606
- }) => {
607
- // TODO: determine how to allow customizing this so that promises
608
- // can be restarted if necessary
609
- if (state.status !== 'active') {
610
- return;
601
+ _unregister: actorRef => {
602
+ children.delete(actorRef.sessionId);
603
+ const systemId = reverseKeyedActors.get(actorRef);
604
+ if (systemId !== undefined) {
605
+ keyedActors.delete(systemId);
606
+ reverseKeyedActors.delete(actorRef);
611
607
  }
612
- const resolvedPromise = Promise.resolve(promiseCreator({
613
- input: state.input
614
- }));
615
- resolvedPromise.then(response => {
616
- // TODO: remove this condition once dead letter queue lands
617
- if (self._state.status !== 'active') {
618
- return;
619
- }
620
- self.send({
621
- type: resolveEventType,
622
- data: response
623
- });
624
- }, errorData => {
625
- // TODO: remove this condition once dead letter queue lands
626
- if (self._state.status !== 'active') {
627
- return;
628
- }
629
- self.send({
630
- type: rejectEventType,
631
- data: errorData
632
- });
633
- });
634
608
  },
635
- getInitialState: (_, input) => {
636
- return {
637
- status: 'active',
638
- data: undefined,
639
- input
640
- };
609
+ get: systemId => {
610
+ return keyedActors.get(systemId);
641
611
  },
642
- getSnapshot: state => state.data,
643
- getStatus: state => state,
644
- getPersistedState: state => state,
645
- restoreState: state => state
612
+ _set: (systemId, actorRef) => {
613
+ const existing = keyedActors.get(systemId);
614
+ if (existing && existing !== actorRef) {
615
+ throw new Error(`Actor with system ID '${systemId}' already exists.`);
616
+ }
617
+ keyedActors.set(systemId, actorRef);
618
+ reverseKeyedActors.set(actorRef, systemId);
619
+ }
646
620
  };
647
- return behavior;
621
+ return system;
648
622
  }
649
623
 
650
- // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
651
- function fromObservable(observableCreator) {
652
- const nextEventType = '$$xstate.next';
653
- const errorEventType = '$$xstate.error';
654
- const completeEventType = '$$xstate.complete';
655
-
656
- // TODO: add event types
657
- const behavior = {
658
- config: observableCreator,
659
- transition: (state, event, {
660
- self,
661
- id,
662
- defer
663
- }) => {
664
- if (state.status !== 'active') {
665
- return state;
666
- }
667
- switch (event.type) {
668
- case nextEventType:
669
- // match the exact timing of events sent by machines
670
- // send actions are not executed immediately
671
- defer(() => {
672
- self._parent?.send({
673
- type: `xstate.snapshot.${id}`,
674
- data: event.data
675
- });
676
- });
677
- return {
678
- ...state,
679
- data: event.data
680
- };
681
- case errorEventType:
682
- return {
683
- ...state,
684
- status: 'error',
685
- input: undefined,
686
- data: event.data,
687
- subscription: undefined
688
- };
689
- case completeEventType:
690
- return {
691
- ...state,
692
- status: 'done',
693
- input: undefined,
694
- subscription: undefined
695
- };
696
- case stopSignalType:
697
- state.subscription.unsubscribe();
698
- return {
699
- ...state,
700
- status: 'canceled',
701
- input: undefined,
702
- subscription: undefined
703
- };
704
- default:
705
- return state;
706
- }
707
- },
708
- getInitialState: (_, input) => {
709
- return {
710
- subscription: undefined,
711
- status: 'active',
712
- data: undefined,
713
- input
714
- };
715
- },
716
- start: (state, {
717
- self
718
- }) => {
719
- if (state.status === 'done') {
720
- // Do not restart a completed observable
721
- return;
722
- }
723
- state.subscription = observableCreator({
724
- input: state.input
725
- }).subscribe({
726
- next: value => {
727
- self.send({
728
- type: nextEventType,
729
- data: value
730
- });
731
- },
732
- error: err => {
733
- self.send({
734
- type: errorEventType,
735
- data: err
736
- });
737
- },
738
- complete: () => {
739
- self.send({
740
- type: completeEventType
741
- });
742
- }
743
- });
744
- },
745
- getSnapshot: state => state.data,
746
- getPersistedState: ({
747
- status,
748
- data,
749
- input
750
- }) => ({
751
- status,
752
- data,
753
- input
754
- }),
755
- getStatus: state => state,
756
- restoreState: state => ({
757
- ...state,
758
- subscription: undefined
759
- })
760
- };
761
- return behavior;
762
- }
763
-
764
- /**
765
- * Creates an event observable behavior that listens to an observable
766
- * that delivers event objects.
767
- *
768
- *
769
- * @param lazyObservable A function that creates an observable
770
- * @returns An event observable behavior
771
- */
772
-
773
- function fromEventObservable(lazyObservable) {
774
- const errorEventType = '$$xstate.error';
775
- const completeEventType = '$$xstate.complete';
776
-
777
- // TODO: event types
778
- const behavior = {
779
- config: lazyObservable,
780
- transition: (state, event) => {
781
- if (state.status !== 'active') {
782
- return state;
783
- }
784
- switch (event.type) {
785
- case errorEventType:
786
- return {
787
- ...state,
788
- status: 'error',
789
- input: undefined,
790
- data: event.data,
791
- subscription: undefined
792
- };
793
- case completeEventType:
794
- return {
795
- ...state,
796
- status: 'done',
797
- input: undefined,
798
- subscription: undefined
799
- };
800
- case stopSignalType:
801
- state.subscription.unsubscribe();
802
- return {
803
- ...state,
804
- status: 'canceled',
805
- input: undefined,
806
- subscription: undefined
807
- };
808
- default:
809
- return state;
810
- }
811
- },
812
- getInitialState: (_, input) => {
813
- return {
814
- subscription: undefined,
815
- status: 'active',
816
- data: undefined,
817
- input
818
- };
819
- },
820
- start: (state, {
821
- self
822
- }) => {
823
- if (state.status === 'done') {
824
- // Do not restart a completed observable
825
- return;
826
- }
827
- state.subscription = lazyObservable({
828
- input: state.input
829
- }).subscribe({
830
- next: value => {
831
- self._parent?.send(value);
832
- },
833
- error: err => {
834
- self.send({
835
- type: errorEventType,
836
- data: err
837
- });
838
- },
839
- complete: () => {
840
- self.send({
841
- type: completeEventType
842
- });
843
- }
844
- });
845
- },
846
- getSnapshot: _ => undefined,
847
- getPersistedState: ({
848
- status,
849
- data,
850
- input
851
- }) => ({
852
- status,
853
- data,
854
- input
855
- }),
856
- getStatus: state => state,
857
- restoreState: state => ({
858
- ...state,
859
- subscription: undefined
860
- })
861
- };
862
- return behavior;
863
- }
864
-
865
- function fromCallback(invokeCallback) {
866
- const behavior = {
867
- config: invokeCallback,
868
- start: (_state, {
869
- self
870
- }) => {
871
- self.send({
872
- type: startSignalType
873
- });
874
- },
875
- transition: (state, event, {
876
- self,
877
- id
878
- }) => {
879
- if (event.type === startSignalType) {
880
- const sender = eventForParent => {
881
- if (state.canceled) {
882
- return;
883
- }
884
- self._parent?.send(eventForParent);
885
- };
886
- const receiver = newListener => {
887
- state.receivers.add(newListener);
888
- };
889
- state.dispose = invokeCallback(sender, receiver, {
890
- input: state.input
891
- });
892
- if (isPromiseLike(state.dispose)) {
893
- state.dispose.then(resolved => {
894
- self._parent?.send(doneInvoke(id, resolved));
895
- state.canceled = true;
896
- }, errorData => {
897
- state.canceled = true;
898
- self._parent?.send(error(id, errorData));
899
- });
900
- }
901
- return state;
902
- }
903
- if (event.type === stopSignalType) {
904
- state.canceled = true;
905
- if (isFunction(state.dispose)) {
906
- state.dispose();
907
- }
908
- return state;
909
- }
910
- if (isSignal(event.type)) {
911
- // TODO: unrecognized signal
912
- return state;
913
- }
914
- if (!isSignal(event.type)) {
915
- state.receivers.forEach(receiver => receiver(event));
916
- }
917
- return state;
918
- },
919
- getInitialState: (_, input) => {
920
- return {
921
- canceled: false,
922
- receivers: new Set(),
923
- dispose: undefined,
924
- input
925
- };
926
- },
927
- getSnapshot: () => undefined,
928
- getPersistedState: ({
929
- input
930
- }) => input
931
- };
932
- return behavior;
933
- }
934
-
935
- const startSignalType = 'xstate.init';
936
- const stopSignalType = 'xstate.stop';
937
- const startSignal = {
938
- type: 'xstate.init'
939
- };
940
- const stopSignal = {
941
- type: 'xstate.stop'
942
- };
943
- /**
944
- * An object that expresses the behavior of an actor in reaction to received events,
945
- * as well as an optionally emitted stream of values.
946
- *
947
- * @template TReceived The received event
948
- * @template TSnapshot The emitted value
949
- */
950
-
951
- function isSignal(eventType) {
952
- return eventType === startSignalType || eventType === stopSignalType;
953
- }
954
- function isActorRef(item) {
955
- return !!item && typeof item === 'object' && typeof item.send === 'function';
956
- }
957
-
958
- // TODO: refactor the return type, this could be written in a better way
959
- // but it's best to avoid unneccessary breaking changes now
960
- // @deprecated use `interpret(behavior)` instead
961
- function toActorRef(actorRefLike) {
962
- return {
963
- subscribe: () => ({
964
- unsubscribe: () => void 0
965
- }),
966
- id: 'anonymous',
967
- sessionId: '',
968
- getSnapshot: () => undefined,
969
- [symbolObservable]: function () {
970
- return this;
971
- },
972
- status: ActorStatus.Running,
973
- stop: () => void 0,
974
- ...actorRefLike
975
- };
976
- }
977
- const emptyBehavior = fromTransition(_ => undefined, undefined);
978
- function createEmptyActor() {
979
- return interpret(emptyBehavior);
980
- }
981
-
982
- function createSystem() {
983
- let sessionIdCounter = 0;
984
- const children = new Map();
985
- const keyedActors = new Map();
986
- const reverseKeyedActors = new WeakMap();
987
- const system = {
988
- _bookId: () => `x:${sessionIdCounter++}`,
989
- _register: (sessionId, actorRef) => {
990
- children.set(sessionId, actorRef);
991
- return sessionId;
992
- },
993
- _unregister: actorRef => {
994
- children.delete(actorRef.sessionId);
995
- const systemId = reverseKeyedActors.get(actorRef);
996
- if (systemId !== undefined) {
997
- keyedActors.delete(systemId);
998
- reverseKeyedActors.delete(actorRef);
999
- }
1000
- },
1001
- get: systemId => {
1002
- return keyedActors.get(systemId);
1003
- },
1004
- _set: (systemId, actorRef) => {
1005
- const existing = keyedActors.get(systemId);
1006
- if (existing && existing !== actorRef) {
1007
- throw new Error(`Actor with system ID '${systemId}' already exists.`);
1008
- }
1009
- keyedActors.set(systemId, actorRef);
1010
- reverseKeyedActors.set(actorRef, systemId);
1011
- }
1012
- };
1013
- return system;
1014
- }
1015
-
1016
- let ActorStatus = /*#__PURE__*/function (ActorStatus) {
1017
- ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
1018
- ActorStatus[ActorStatus["Running"] = 1] = "Running";
1019
- ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
1020
- return ActorStatus;
1021
- }({});
1022
- const defaultOptions = {
1023
- deferEvents: true,
1024
- clock: {
1025
- setTimeout: (fn, ms) => {
1026
- return setTimeout(fn, ms);
624
+ let ActorStatus = /*#__PURE__*/function (ActorStatus) {
625
+ ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
626
+ ActorStatus[ActorStatus["Running"] = 1] = "Running";
627
+ ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
628
+ return ActorStatus;
629
+ }({});
630
+ const defaultOptions = {
631
+ deferEvents: true,
632
+ clock: {
633
+ setTimeout: (fn, ms) => {
634
+ return setTimeout(fn, ms);
1027
635
  },
1028
636
  clearTimeout: id => {
1029
637
  return clearTimeout(id);
@@ -1034,7 +642,7 @@ const defaultOptions = {
1034
642
  };
1035
643
  class Interpreter {
1036
644
  /**
1037
- * The current state of the interpreted behavior.
645
+ * The current state of the interpreted logic.
1038
646
  */
1039
647
 
1040
648
  /**
@@ -1058,13 +666,13 @@ class Interpreter {
1058
666
  */
1059
667
 
1060
668
  /**
1061
- * Creates a new Interpreter instance (i.e., service) for the given behavior with the provided options, if any.
669
+ * Creates a new Interpreter instance (i.e., service) for the given logic with the provided options, if any.
1062
670
  *
1063
- * @param behavior The behavior to be interpreted
671
+ * @param logic The logic to be interpreted
1064
672
  * @param options Interpreter options
1065
673
  */
1066
- constructor(behavior, options) {
1067
- this.behavior = behavior;
674
+ constructor(logic, options) {
675
+ this.logic = logic;
1068
676
  this._state = void 0;
1069
677
  this.clock = void 0;
1070
678
  this.options = void 0;
@@ -1131,7 +739,7 @@ class Interpreter {
1131
739
  this._initState();
1132
740
  }
1133
741
  _initState() {
1134
- this._state = this.options.state ? this.behavior.restoreState ? this.behavior.restoreState(this.options.state, this._actorContext) : this.options.state : this.behavior.getInitialState(this._actorContext, this.options?.input);
742
+ this._state = this.options.state ? this.logic.restoreState ? this.logic.restoreState(this.options.state, this._actorContext) : this.options.state : this.logic.getInitialState(this._actorContext, this.options?.input);
1135
743
  }
1136
744
 
1137
745
  // array of functions to defer
@@ -1144,12 +752,12 @@ class Interpreter {
1144
752
  // Execute deferred effects
1145
753
  let deferredFn;
1146
754
  while (deferredFn = this._deferred.shift()) {
1147
- deferredFn(state);
755
+ deferredFn();
1148
756
  }
1149
757
  for (const observer of this.observers) {
1150
758
  observer.next?.(snapshot);
1151
759
  }
1152
- const status = this.behavior.getStatus?.(state);
760
+ const status = this.logic.getStatus?.(state);
1153
761
  switch (status?.status) {
1154
762
  case 'done':
1155
763
  this._stopProcedure();
@@ -1191,8 +799,8 @@ class Interpreter {
1191
799
  this.system._set(this._systemId, this);
1192
800
  }
1193
801
  this.status = ActorStatus.Running;
1194
- if (this.behavior.start) {
1195
- this.behavior.start(this._state, this._actorContext);
802
+ if (this.logic.start) {
803
+ this.logic.start(this._state, this._actorContext);
1196
804
  }
1197
805
 
1198
806
  // TODO: this notifies all subscribers but usually this is redundant
@@ -1207,7 +815,7 @@ class Interpreter {
1207
815
  }
1208
816
  _process(event) {
1209
817
  try {
1210
- const nextState = this.behavior.transition(this._state, event, this._actorContext);
818
+ const nextState = this.logic.transition(this._state, event, this._actorContext);
1211
819
  this.update(nextState);
1212
820
  if (event.type === stopSignalType) {
1213
821
  this._stopProcedure();
@@ -1284,221 +892,611 @@ class Interpreter {
1284
892
  return this;
1285
893
  }
1286
894
 
1287
- /**
1288
- * Sends an event to the running interpreter to trigger a transition.
1289
- *
1290
- * @param event The event to send
1291
- */
1292
- send(event) {
1293
- if (typeof event === 'string') {
1294
- throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
1295
- }
1296
- if (this.status === ActorStatus.Stopped) {
1297
- return;
1298
- }
1299
- if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
1300
- throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
1301
- // tslint:disable-next-line:max-line-length
1302
- }". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
1303
- }
1304
- this.mailbox.enqueue(event);
1305
- }
895
+ /**
896
+ * Sends an event to the running interpreter to trigger a transition.
897
+ *
898
+ * @param event The event to send
899
+ */
900
+ send(event) {
901
+ if (typeof event === 'string') {
902
+ throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
903
+ }
904
+ if (this.status === ActorStatus.Stopped) {
905
+ return;
906
+ }
907
+ if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
908
+ throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
909
+ // tslint:disable-next-line:max-line-length
910
+ }". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
911
+ }
912
+ this.mailbox.enqueue(event);
913
+ }
914
+
915
+ // TODO: make private (and figure out a way to do this within the machine)
916
+ delaySend(sendAction) {
917
+ this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
918
+ if ('to' in sendAction.params && sendAction.params.to) {
919
+ sendAction.params.to.send(sendAction.params.event);
920
+ } else {
921
+ this.send(sendAction.params.event);
922
+ }
923
+ }, sendAction.params.delay);
924
+ }
925
+
926
+ // TODO: make private (and figure out a way to do this within the machine)
927
+ cancel(sendId) {
928
+ this.clock.clearTimeout(this.delayedEventsMap[sendId]);
929
+ delete this.delayedEventsMap[sendId];
930
+ }
931
+ attachDevTools() {
932
+ const {
933
+ devTools
934
+ } = this.options;
935
+ if (devTools) {
936
+ const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : devToolsAdapter;
937
+ resolvedDevToolsAdapter(this);
938
+ }
939
+ }
940
+ toJSON() {
941
+ return {
942
+ id: this.id
943
+ };
944
+ }
945
+ getPersistedState() {
946
+ return this.logic.getPersistedState?.(this._state);
947
+ }
948
+ [symbolObservable]() {
949
+ return this;
950
+ }
951
+ getSnapshot() {
952
+ return this.logic.getSnapshot ? this.logic.getSnapshot(this._state) : this._state;
953
+ }
954
+ }
955
+
956
+ /**
957
+ * Creates a new Interpreter instance for the given machine with the provided options, if any.
958
+ *
959
+ * @param machine The machine to interpret
960
+ * @param options Interpreter options
961
+ */
962
+
963
+ function interpret(logic, options) {
964
+ const interpreter = new Interpreter(logic, options);
965
+ return interpreter;
966
+ }
967
+
968
+ /**
969
+ * Returns actor logic from a transition function and its initial state.
970
+ *
971
+ * A transition function is a function that takes the current state and an event and returns the next state.
972
+ *
973
+ * @param transition The transition function that returns the next state given the current state and event.
974
+ * @param initialState The initial state of the transition function.
975
+ * @returns Actor logic
976
+ */
977
+ function fromTransition(transition, initialState) {
978
+ const logic = {
979
+ config: transition,
980
+ transition: (state, event, actorContext) => {
981
+ return transition(state, event, actorContext);
982
+ },
983
+ getInitialState: (_, input) => {
984
+ return typeof initialState === 'function' ? initialState({
985
+ input
986
+ }) : initialState;
987
+ },
988
+ getSnapshot: state => state,
989
+ getPersistedState: state => state,
990
+ restoreState: state => state
991
+ };
992
+ return logic;
993
+ }
994
+
995
+ function fromPromise(
996
+ // TODO: add types
997
+ promiseCreator) {
998
+ const resolveEventType = '$$xstate.resolve';
999
+ const rejectEventType = '$$xstate.reject';
1000
+
1001
+ // TODO: add event types
1002
+ const logic = {
1003
+ config: promiseCreator,
1004
+ transition: (state, event) => {
1005
+ if (state.status !== 'active') {
1006
+ return state;
1007
+ }
1008
+ switch (event.type) {
1009
+ case resolveEventType:
1010
+ return {
1011
+ ...state,
1012
+ status: 'done',
1013
+ data: event.data,
1014
+ input: undefined
1015
+ };
1016
+ case rejectEventType:
1017
+ return {
1018
+ ...state,
1019
+ status: 'error',
1020
+ data: event.data,
1021
+ input: undefined
1022
+ };
1023
+ case stopSignalType:
1024
+ return {
1025
+ ...state,
1026
+ status: 'canceled',
1027
+ input: undefined
1028
+ };
1029
+ default:
1030
+ return state;
1031
+ }
1032
+ },
1033
+ start: (state, {
1034
+ self,
1035
+ system
1036
+ }) => {
1037
+ // TODO: determine how to allow customizing this so that promises
1038
+ // can be restarted if necessary
1039
+ if (state.status !== 'active') {
1040
+ return;
1041
+ }
1042
+ const resolvedPromise = Promise.resolve(promiseCreator({
1043
+ input: state.input,
1044
+ system
1045
+ }));
1046
+ resolvedPromise.then(response => {
1047
+ // TODO: remove this condition once dead letter queue lands
1048
+ if (self._state.status !== 'active') {
1049
+ return;
1050
+ }
1051
+ self.send({
1052
+ type: resolveEventType,
1053
+ data: response
1054
+ });
1055
+ }, errorData => {
1056
+ // TODO: remove this condition once dead letter queue lands
1057
+ if (self._state.status !== 'active') {
1058
+ return;
1059
+ }
1060
+ self.send({
1061
+ type: rejectEventType,
1062
+ data: errorData
1063
+ });
1064
+ });
1065
+ },
1066
+ getInitialState: (_, input) => {
1067
+ return {
1068
+ status: 'active',
1069
+ data: undefined,
1070
+ input
1071
+ };
1072
+ },
1073
+ getSnapshot: state => state.data,
1074
+ getStatus: state => state,
1075
+ getPersistedState: state => state,
1076
+ restoreState: state => state
1077
+ };
1078
+ return logic;
1079
+ }
1080
+
1081
+ // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
1082
+ function fromObservable(observableCreator) {
1083
+ const nextEventType = '$$xstate.next';
1084
+ const errorEventType = '$$xstate.error';
1085
+ const completeEventType = '$$xstate.complete';
1306
1086
 
1307
- // TODO: make private (and figure out a way to do this within the machine)
1308
- delaySend(sendAction) {
1309
- this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
1310
- if ('to' in sendAction.params && sendAction.params.to) {
1311
- sendAction.params.to.send(sendAction.params.event);
1312
- } else {
1313
- this.send(sendAction.params.event);
1087
+ // TODO: add event types
1088
+ const logic = {
1089
+ config: observableCreator,
1090
+ transition: (state, event, {
1091
+ self,
1092
+ id,
1093
+ defer
1094
+ }) => {
1095
+ if (state.status !== 'active') {
1096
+ return state;
1314
1097
  }
1315
- }, sendAction.params.delay);
1316
- }
1317
-
1318
- // TODO: make private (and figure out a way to do this within the machine)
1319
- cancel(sendId) {
1320
- this.clock.clearTimeout(this.delayedEventsMap[sendId]);
1321
- delete this.delayedEventsMap[sendId];
1322
- }
1323
- attachDevTools() {
1324
- const {
1325
- devTools
1326
- } = this.options;
1327
- if (devTools) {
1328
- const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : devToolsAdapter;
1329
- resolvedDevToolsAdapter(this);
1330
- }
1331
- }
1332
- toJSON() {
1333
- return {
1334
- id: this.id
1335
- };
1336
- }
1337
- getPersistedState() {
1338
- return this.behavior.getPersistedState?.(this._state);
1339
- }
1340
- [symbolObservable]() {
1341
- return this;
1342
- }
1343
- getSnapshot() {
1344
- return this.behavior.getSnapshot ? this.behavior.getSnapshot(this._state) : this._state;
1345
- }
1098
+ switch (event.type) {
1099
+ case nextEventType:
1100
+ // match the exact timing of events sent by machines
1101
+ // send actions are not executed immediately
1102
+ defer(() => {
1103
+ self._parent?.send({
1104
+ type: `xstate.snapshot.${id}`,
1105
+ data: event.data
1106
+ });
1107
+ });
1108
+ return {
1109
+ ...state,
1110
+ data: event.data
1111
+ };
1112
+ case errorEventType:
1113
+ return {
1114
+ ...state,
1115
+ status: 'error',
1116
+ input: undefined,
1117
+ data: event.data,
1118
+ subscription: undefined
1119
+ };
1120
+ case completeEventType:
1121
+ return {
1122
+ ...state,
1123
+ status: 'done',
1124
+ input: undefined,
1125
+ subscription: undefined
1126
+ };
1127
+ case stopSignalType:
1128
+ state.subscription.unsubscribe();
1129
+ return {
1130
+ ...state,
1131
+ status: 'canceled',
1132
+ input: undefined,
1133
+ subscription: undefined
1134
+ };
1135
+ default:
1136
+ return state;
1137
+ }
1138
+ },
1139
+ getInitialState: (_, input) => {
1140
+ return {
1141
+ subscription: undefined,
1142
+ status: 'active',
1143
+ data: undefined,
1144
+ input
1145
+ };
1146
+ },
1147
+ start: (state, {
1148
+ self,
1149
+ system
1150
+ }) => {
1151
+ if (state.status === 'done') {
1152
+ // Do not restart a completed observable
1153
+ return;
1154
+ }
1155
+ state.subscription = observableCreator({
1156
+ input: state.input,
1157
+ system
1158
+ }).subscribe({
1159
+ next: value => {
1160
+ self.send({
1161
+ type: nextEventType,
1162
+ data: value
1163
+ });
1164
+ },
1165
+ error: err => {
1166
+ self.send({
1167
+ type: errorEventType,
1168
+ data: err
1169
+ });
1170
+ },
1171
+ complete: () => {
1172
+ self.send({
1173
+ type: completeEventType
1174
+ });
1175
+ }
1176
+ });
1177
+ },
1178
+ getSnapshot: state => state.data,
1179
+ getPersistedState: ({
1180
+ status,
1181
+ data,
1182
+ input
1183
+ }) => ({
1184
+ status,
1185
+ data,
1186
+ input
1187
+ }),
1188
+ getStatus: state => state,
1189
+ restoreState: state => ({
1190
+ ...state,
1191
+ subscription: undefined
1192
+ })
1193
+ };
1194
+ return logic;
1346
1195
  }
1347
1196
 
1348
1197
  /**
1349
- * Creates a new Interpreter instance for the given machine with the provided options, if any.
1198
+ * Creates event observable logic that listens to an observable
1199
+ * that delivers event objects.
1350
1200
  *
1351
- * @param machine The machine to interpret
1352
- * @param options Interpreter options
1201
+ *
1202
+ * @param lazyObservable A function that creates an observable
1203
+ * @returns Event observable logic
1353
1204
  */
1354
1205
 
1355
- function interpret(behavior, options) {
1356
- const interpreter = new Interpreter(behavior, options);
1357
- return interpreter;
1206
+ function fromEventObservable(lazyObservable) {
1207
+ const errorEventType = '$$xstate.error';
1208
+ const completeEventType = '$$xstate.complete';
1209
+
1210
+ // TODO: event types
1211
+ const logic = {
1212
+ config: lazyObservable,
1213
+ transition: (state, event) => {
1214
+ if (state.status !== 'active') {
1215
+ return state;
1216
+ }
1217
+ switch (event.type) {
1218
+ case errorEventType:
1219
+ return {
1220
+ ...state,
1221
+ status: 'error',
1222
+ input: undefined,
1223
+ data: event.data,
1224
+ subscription: undefined
1225
+ };
1226
+ case completeEventType:
1227
+ return {
1228
+ ...state,
1229
+ status: 'done',
1230
+ input: undefined,
1231
+ subscription: undefined
1232
+ };
1233
+ case stopSignalType:
1234
+ state.subscription.unsubscribe();
1235
+ return {
1236
+ ...state,
1237
+ status: 'canceled',
1238
+ input: undefined,
1239
+ subscription: undefined
1240
+ };
1241
+ default:
1242
+ return state;
1243
+ }
1244
+ },
1245
+ getInitialState: (_, input) => {
1246
+ return {
1247
+ subscription: undefined,
1248
+ status: 'active',
1249
+ data: undefined,
1250
+ input
1251
+ };
1252
+ },
1253
+ start: (state, {
1254
+ self,
1255
+ system
1256
+ }) => {
1257
+ if (state.status === 'done') {
1258
+ // Do not restart a completed observable
1259
+ return;
1260
+ }
1261
+ state.subscription = lazyObservable({
1262
+ input: state.input,
1263
+ system
1264
+ }).subscribe({
1265
+ next: value => {
1266
+ self._parent?.send(value);
1267
+ },
1268
+ error: err => {
1269
+ self.send({
1270
+ type: errorEventType,
1271
+ data: err
1272
+ });
1273
+ },
1274
+ complete: () => {
1275
+ self.send({
1276
+ type: completeEventType
1277
+ });
1278
+ }
1279
+ });
1280
+ },
1281
+ getSnapshot: _ => undefined,
1282
+ getPersistedState: ({
1283
+ status,
1284
+ data,
1285
+ input
1286
+ }) => ({
1287
+ status,
1288
+ data,
1289
+ input
1290
+ }),
1291
+ getStatus: state => state,
1292
+ restoreState: state => ({
1293
+ ...state,
1294
+ subscription: undefined
1295
+ })
1296
+ };
1297
+ return logic;
1358
1298
  }
1359
1299
 
1360
- /**
1361
- * Stops an actor.
1362
- *
1363
- * @param actorRef The actor to stop.
1364
- */
1365
-
1366
- function stop(actorRef) {
1367
- const actor = actorRef;
1368
- return createDynamicAction({
1369
- type: stop$1,
1370
- params: {
1371
- actor
1372
- }
1373
- }, (event, {
1374
- state
1375
- }) => {
1376
- const actorRefOrString = isFunction(actor) ? actor({
1377
- context: state.context,
1378
- event
1379
- }) : actor;
1380
- const actorRef = typeof actorRefOrString === 'string' ? state.children[actorRefOrString] : actorRefOrString;
1381
- return [state, {
1382
- type: 'xstate.stop',
1383
- params: {
1384
- actor: actorRef
1385
- },
1386
- execute: actorCtx => {
1387
- if (!actorRef) {
1388
- return;
1300
+ function fromCallback(invokeCallback) {
1301
+ const logic = {
1302
+ config: invokeCallback,
1303
+ start: (_state, {
1304
+ self
1305
+ }) => {
1306
+ self.send({
1307
+ type: startSignalType
1308
+ });
1309
+ },
1310
+ transition: (state, event, {
1311
+ self,
1312
+ id,
1313
+ system
1314
+ }) => {
1315
+ if (event.type === startSignalType) {
1316
+ const sender = eventForParent => {
1317
+ if (state.canceled) {
1318
+ return;
1319
+ }
1320
+ self._parent?.send(eventForParent);
1321
+ };
1322
+ const receiver = newListener => {
1323
+ state.receivers.add(newListener);
1324
+ };
1325
+ state.dispose = invokeCallback(sender, receiver, {
1326
+ input: state.input,
1327
+ system
1328
+ });
1329
+ if (isPromiseLike(state.dispose)) {
1330
+ state.dispose.then(resolved => {
1331
+ self._parent?.send(doneInvoke(id, resolved));
1332
+ state.canceled = true;
1333
+ }, errorData => {
1334
+ state.canceled = true;
1335
+ self._parent?.send(error(id, errorData));
1336
+ });
1389
1337
  }
1390
- if (actorRef.status !== ActorStatus.Running) {
1391
- actorCtx.stopChild(actorRef);
1392
- return;
1338
+ return state;
1339
+ }
1340
+ if (event.type === stopSignalType) {
1341
+ state.canceled = true;
1342
+ if (isFunction(state.dispose)) {
1343
+ state.dispose();
1393
1344
  }
1394
- actorCtx.defer(() => {
1395
- actorCtx.stopChild(actorRef);
1396
- });
1345
+ return state;
1397
1346
  }
1398
- }];
1399
- });
1347
+ if (isSignal(event.type)) {
1348
+ // TODO: unrecognized signal
1349
+ return state;
1350
+ }
1351
+ if (!isSignal(event.type)) {
1352
+ state.receivers.forEach(receiver => receiver(event));
1353
+ }
1354
+ return state;
1355
+ },
1356
+ getInitialState: (_, input) => {
1357
+ return {
1358
+ canceled: false,
1359
+ receivers: new Set(),
1360
+ dispose: undefined,
1361
+ input
1362
+ };
1363
+ },
1364
+ getSnapshot: () => undefined,
1365
+ getPersistedState: ({
1366
+ input
1367
+ }) => input
1368
+ };
1369
+ return logic;
1400
1370
  }
1401
1371
 
1402
- const defaultLogExpr = ({
1403
- context,
1404
- event
1405
- }) => ({
1406
- context,
1407
- event
1408
- });
1409
-
1372
+ const startSignalType = 'xstate.init';
1373
+ const stopSignalType = 'xstate.stop';
1374
+ const startSignal = {
1375
+ type: 'xstate.init'
1376
+ };
1377
+ const stopSignal = {
1378
+ type: 'xstate.stop'
1379
+ };
1410
1380
  /**
1381
+ * An object that expresses the actor logic in reaction to received events,
1382
+ * as well as an optionally emitted stream of values.
1411
1383
  *
1412
- * @param expr The expression function to evaluate which will be logged.
1413
- * Takes in 2 arguments:
1414
- * - `ctx` - the current state context
1415
- * - `event` - the event that caused this action to be executed.
1416
- * @param label The label to give to the logged expression.
1384
+ * @template TReceived The received event
1385
+ * @template TSnapshot The emitted value
1417
1386
  */
1418
1387
 
1419
- function log(expr = defaultLogExpr, label) {
1388
+ function isSignal(eventType) {
1389
+ return eventType === startSignalType || eventType === stopSignalType;
1390
+ }
1391
+ function isActorRef(item) {
1392
+ return !!item && typeof item === 'object' && typeof item.send === 'function';
1393
+ }
1394
+
1395
+ // TODO: refactor the return type, this could be written in a better way
1396
+ // but it's best to avoid unneccessary breaking changes now
1397
+ // @deprecated use `interpret(actorLogic)` instead
1398
+ function toActorRef(actorRefLike) {
1399
+ return {
1400
+ subscribe: () => ({
1401
+ unsubscribe: () => void 0
1402
+ }),
1403
+ id: 'anonymous',
1404
+ sessionId: '',
1405
+ getSnapshot: () => undefined,
1406
+ [symbolObservable]: function () {
1407
+ return this;
1408
+ },
1409
+ status: ActorStatus.Running,
1410
+ stop: () => void 0,
1411
+ ...actorRefLike
1412
+ };
1413
+ }
1414
+ const emptyLogic = fromTransition(_ => undefined, undefined);
1415
+ function createEmptyActor() {
1416
+ return interpret(emptyLogic);
1417
+ }
1418
+
1419
+ function invoke(invokeDef) {
1420
1420
  return createDynamicAction({
1421
- type: log$1,
1422
- params: {
1423
- label,
1424
- expr
1425
- }
1421
+ type: invoke$1,
1422
+ params: invokeDef
1426
1423
  }, (event, {
1427
1424
  state,
1428
1425
  actorContext
1429
1426
  }) => {
1430
- const resolvedValue = typeof expr === 'function' ? expr({
1431
- context: state.context,
1432
- event,
1433
- self: actorContext?.self ?? {},
1434
- system: actorContext?.system
1435
- }) : expr;
1436
- return [state, {
1437
- type: 'xstate.log',
1438
- params: {
1439
- label,
1440
- value: resolvedValue
1441
- },
1442
- execute: actorCtx => {
1443
- if (label) {
1444
- actorCtx.logger?.(label, resolvedValue);
1445
- } else {
1446
- actorCtx.logger?.(resolvedValue);
1427
+ const type = invoke$1;
1428
+ const {
1429
+ id,
1430
+ src
1431
+ } = invokeDef;
1432
+ let resolvedInvokeAction;
1433
+ if (isActorRef(src)) {
1434
+ resolvedInvokeAction = {
1435
+ type,
1436
+ params: {
1437
+ ...invokeDef,
1438
+ ref: src
1447
1439
  }
1448
- }
1449
- }];
1450
- });
1451
- }
1452
-
1453
- /**
1454
- * Cancels an in-flight `send(...)` action. A canceled sent action will not
1455
- * be executed, nor will its event be sent, unless it has already been sent
1456
- * (e.g., if `cancel(...)` is called after the `send(...)` action's `delay`).
1457
- *
1458
- * @param sendId The `id` of the `send(...)` action to cancel.
1459
- */
1460
-
1461
- function cancel(sendId) {
1462
- return createDynamicAction({
1463
- type: cancel$1,
1464
- params: {
1465
- sendId
1440
+ };
1441
+ } else {
1442
+ const referenced = resolveReferencedActor(state.machine.options.actors[src]);
1443
+ if (!referenced) {
1444
+ resolvedInvokeAction = {
1445
+ type,
1446
+ params: invokeDef
1447
+ };
1448
+ } else {
1449
+ const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
1450
+ const ref = interpret(referenced.src, {
1451
+ id,
1452
+ src,
1453
+ parent: actorContext?.self,
1454
+ systemId: invokeDef.systemId,
1455
+ input: typeof input === 'function' ? input({
1456
+ context: state.context,
1457
+ event,
1458
+ self: actorContext?.self
1459
+ }) : input
1460
+ });
1461
+ resolvedInvokeAction = {
1462
+ type,
1463
+ params: {
1464
+ ...invokeDef,
1465
+ ref
1466
+ }
1467
+ };
1468
+ }
1466
1469
  }
1467
- }, (event, {
1468
- state,
1469
- actorContext
1470
- }) => {
1471
- const resolvedSendId = isFunction(sendId) ? sendId({
1472
- context: state.context,
1473
- event,
1474
- self: actorContext?.self ?? {},
1475
- system: actorContext?.system
1476
- }) : sendId;
1477
- return [state, {
1478
- type: 'xstate.cancel',
1479
- params: {
1480
- sendId: resolvedSendId
1481
- },
1482
- execute: actorCtx => {
1483
- const interpreter = actorCtx.self;
1484
- interpreter.cancel(resolvedSendId);
1470
+ const actorRef = resolvedInvokeAction.params.ref;
1471
+ const invokedState = cloneState(state, {
1472
+ children: {
1473
+ ...state.children,
1474
+ [id]: actorRef
1485
1475
  }
1486
- }];
1487
- });
1488
- }
1489
-
1490
- const cache = new WeakMap();
1491
- function memo(object, key, fn) {
1492
- let memoizedData = cache.get(object);
1493
- if (!memoizedData) {
1494
- memoizedData = {
1495
- [key]: fn()
1476
+ });
1477
+ resolvedInvokeAction.execute = actorCtx => {
1478
+ const parent = actorCtx.self;
1479
+ const {
1480
+ id,
1481
+ ref
1482
+ } = resolvedInvokeAction.params;
1483
+ if (!ref) {
1484
+ return;
1485
+ }
1486
+ actorCtx.defer(() => {
1487
+ if (actorRef.status === ActorStatus.Stopped) {
1488
+ return;
1489
+ }
1490
+ try {
1491
+ actorRef.start?.();
1492
+ } catch (err) {
1493
+ parent.send(error(id, err));
1494
+ return;
1495
+ }
1496
+ });
1496
1497
  };
1497
- cache.set(object, memoizedData);
1498
- } else if (!(key in memoizedData)) {
1499
- memoizedData[key] = fn();
1500
- }
1501
- return memoizedData[key];
1498
+ return [invokedState, resolvedInvokeAction];
1499
+ });
1502
1500
  }
1503
1501
 
1504
1502
  function stateIn(stateValue) {
@@ -2230,15 +2228,11 @@ function microstep(transitions, currentState, actorCtx, event) {
2230
2228
  const willTransition = currentState._initial || transitions.length > 0;
2231
2229
  const mutConfiguration = new Set(currentState.configuration);
2232
2230
  if (!currentState._initial && !willTransition) {
2233
- const inertState = cloneState(currentState, {
2234
- event,
2235
- actions: [],
2236
- transitions: []
2237
- });
2231
+ const inertState = cloneState(currentState, {});
2238
2232
  inertState.changed = false;
2239
2233
  return inertState;
2240
2234
  }
2241
- const microstate = microstepProcedure(currentState._initial ? [{
2235
+ const [microstate, actions] = microstepProcedure(currentState._initial ? [{
2242
2236
  target: [...currentState.configuration].filter(isAtomicStateNode),
2243
2237
  source: machine.root,
2244
2238
  reenter: true,
@@ -2247,38 +2241,16 @@ function microstep(transitions, currentState, actorCtx, event) {
2247
2241
  toJSON: null // TODO: fix
2248
2242
  }] : transitions, currentState, mutConfiguration, event, actorCtx);
2249
2243
  const {
2250
- context,
2251
- actions: nonRaisedActions
2244
+ context
2252
2245
  } = microstate;
2253
- const children = setChildren(currentState, nonRaisedActions);
2254
2246
  const nextState = cloneState(microstate, {
2255
2247
  value: {},
2256
2248
  // TODO: make optional
2257
- transitions,
2258
- children
2249
+ transitions
2259
2250
  });
2260
- nextState.changed = currentState._initial ? undefined : !stateValuesEqual(nextState.value, currentState.value) || nextState.actions.length > 0 || context !== currentState.context;
2251
+ nextState.changed = currentState._initial ? undefined : !stateValuesEqual(nextState.value, currentState.value) || actions.length > 0 || context !== currentState.context;
2261
2252
  return nextState;
2262
2253
  }
2263
- function setChildren(currentState, nonRaisedActions) {
2264
- const children = {
2265
- ...currentState.children
2266
- };
2267
- for (const action of nonRaisedActions) {
2268
- if (action.type === invoke$1 && action.params.ref) {
2269
- const ref = action.params.ref;
2270
- if (ref) {
2271
- children[ref.id] = ref;
2272
- }
2273
- } else if (action.type === stop$1) {
2274
- const ref = action.params.actor;
2275
- if (ref) {
2276
- delete children[ref.id];
2277
- }
2278
- }
2279
- }
2280
- return children;
2281
- }
2282
2254
  function microstepProcedure(transitions, currentState, mutConfiguration, event, actorCtx) {
2283
2255
  const actions = [];
2284
2256
  const historyValue = {
@@ -2296,7 +2268,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2296
2268
  actions.push(...filteredTransitions.flatMap(t => t.actions));
2297
2269
 
2298
2270
  // Enter states
2299
- enterStates(filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue);
2271
+ enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue);
2300
2272
  const nextConfiguration = [...mutConfiguration];
2301
2273
  const done = isInFinalState(nextConfiguration);
2302
2274
  if (done) {
@@ -2304,29 +2276,25 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2304
2276
  actions.push(...finalActions);
2305
2277
  }
2306
2278
  try {
2307
- const {
2308
- nextState
2309
- } = resolveActionsAndContext(actions, event, currentState, actorCtx);
2279
+ const [nextState, resolvedActions] = resolveActionsAndContext(actions, event, currentState, actorCtx);
2310
2280
  const output = done ? getOutput(nextConfiguration, nextState.context, event) : undefined;
2311
2281
  internalQueue.push(...nextState._internalQueue);
2312
- return cloneState(currentState, {
2313
- actions: nextState.actions,
2282
+ return [cloneState(currentState, {
2314
2283
  configuration: nextConfiguration,
2315
2284
  historyValue,
2316
2285
  _internalQueue: internalQueue,
2317
2286
  context: nextState.context,
2318
- event,
2319
2287
  done,
2320
2288
  output,
2321
2289
  children: nextState.children
2322
- });
2290
+ }), resolvedActions];
2323
2291
  } catch (e) {
2324
2292
  // TODO: Refactor this once proper error handling is implemented.
2325
2293
  // See https://github.com/statelyai/rfcs/pull/4
2326
2294
  throw e;
2327
2295
  }
2328
2296
  }
2329
- function enterStates(filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue) {
2297
+ function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue) {
2330
2298
  const statesToEnter = new Set();
2331
2299
  const statesForDefaultEntry = new Set();
2332
2300
  computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
@@ -2354,7 +2322,7 @@ function enterStates(filteredTransitions, mutConfiguration, actions, internalQue
2354
2322
  if (!parent.parent) {
2355
2323
  continue;
2356
2324
  }
2357
- internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, currentState.event) : undefined));
2325
+ internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, event) : undefined));
2358
2326
  if (parent.parent) {
2359
2327
  const grandparent = parent.parent;
2360
2328
  if (grandparent.type === 'parallel') {
@@ -2472,8 +2440,8 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
2472
2440
  resolvedActions.push(action);
2473
2441
  if (actorCtx?.self.status === ActorStatus.Running) {
2474
2442
  action.execute?.(actorCtx);
2475
- // TODO: this is hacky; re-evaluate
2476
- delete action.execute;
2443
+ } else {
2444
+ actorCtx?.defer(() => action.execute?.(actorCtx));
2477
2445
  }
2478
2446
  }
2479
2447
  function resolveAction(actionObject) {
@@ -2502,12 +2470,9 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
2502
2470
  for (const actionObject of actions) {
2503
2471
  resolveAction(actionObject);
2504
2472
  }
2505
- return {
2506
- nextState: cloneState(intermediateState, {
2507
- actions: resolvedActions,
2508
- _internalQueue: raiseActions.map(a => a.params.event)
2509
- })
2510
- };
2473
+ return [cloneState(intermediateState, {
2474
+ _internalQueue: raiseActions.map(a => a.params.event)
2475
+ }), resolvedActions];
2511
2476
  }
2512
2477
  function macrostep(state, event, actorCtx) {
2513
2478
  let nextState = state;
@@ -2515,54 +2480,42 @@ function macrostep(state, event, actorCtx) {
2515
2480
 
2516
2481
  // Handle stop event
2517
2482
  if (event.type === stopSignalType) {
2518
- nextState = stopStep(event, nextState, actorCtx);
2483
+ nextState = stopStep(event, nextState, actorCtx)[0];
2519
2484
  states.push(nextState);
2520
2485
  return {
2521
2486
  state: nextState,
2522
2487
  microstates: states
2523
2488
  };
2524
2489
  }
2490
+ let nextEvent = event;
2525
2491
 
2526
2492
  // Assume the state is at rest (no raised events)
2527
2493
  // Determine the next state based on the next microstep
2528
- if (event.type !== init) {
2529
- const transitions = selectTransitions(event, nextState);
2530
- nextState = microstep(transitions, state, actorCtx, event);
2494
+ if (nextEvent.type !== init) {
2495
+ const transitions = selectTransitions(nextEvent, nextState);
2496
+ nextState = microstep(transitions, state, actorCtx, nextEvent);
2531
2497
  states.push(nextState);
2532
2498
  }
2533
2499
  while (!nextState.done) {
2534
- let enabledTransitions = selectEventlessTransitions(nextState);
2535
- if (enabledTransitions.length === 0) {
2536
- // TODO: this is a bit of a hack, we need to review this
2537
- // this matches the behavior from v4 for eventless transitions
2538
- // where for `hasAlwaysTransitions` we were always trying to resolve with a NULL event
2539
- // and if a transition was not selected the `state.transitions` stayed empty
2540
- // without this we get into an infinite loop in the dieHard test in `@xstate/test` for the `simplePathsTo`
2541
- if (nextState.configuration.some(state => state.always)) {
2542
- nextState.transitions = [];
2543
- }
2500
+ let enabledTransitions = selectEventlessTransitions(nextState, nextEvent);
2501
+ if (!enabledTransitions.length) {
2544
2502
  if (!nextState._internalQueue.length) {
2545
2503
  break;
2546
2504
  } else {
2547
- const currentActions = nextState.actions;
2548
- const nextEvent = nextState._internalQueue[0];
2505
+ nextEvent = nextState._internalQueue[0];
2549
2506
  const transitions = selectTransitions(nextEvent, nextState);
2550
2507
  nextState = microstep(transitions, nextState, actorCtx, nextEvent);
2551
2508
  nextState._internalQueue.shift();
2552
- nextState.actions.unshift(...currentActions);
2553
2509
  states.push(nextState);
2554
2510
  }
2555
- }
2556
- if (enabledTransitions.length) {
2557
- const currentActions = nextState.actions;
2558
- nextState = microstep(enabledTransitions, nextState, actorCtx, nextState.event);
2559
- nextState.actions.unshift(...currentActions);
2511
+ } else {
2512
+ nextState = microstep(enabledTransitions, nextState, actorCtx, nextEvent);
2560
2513
  states.push(nextState);
2561
2514
  }
2562
2515
  }
2563
2516
  if (nextState.done) {
2564
2517
  // Perform the stop step to ensure that child actors are stopped
2565
- stopStep(nextState.event, nextState, actorCtx);
2518
+ stopStep(nextEvent, nextState, actorCtx);
2566
2519
  }
2567
2520
  return {
2568
2521
  state: nextState,
@@ -2577,15 +2530,12 @@ function stopStep(event, nextState, actorCtx) {
2577
2530
  for (const child of Object.values(nextState.children)) {
2578
2531
  actions.push(stop(child));
2579
2532
  }
2580
- const {
2581
- nextState: stoppedState
2582
- } = resolveActionsAndContext(actions, event, nextState, actorCtx);
2583
- return stoppedState;
2533
+ return resolveActionsAndContext(actions, event, nextState, actorCtx);
2584
2534
  }
2585
2535
  function selectTransitions(event, nextState) {
2586
2536
  return nextState.machine.getTransitionData(nextState, event);
2587
2537
  }
2588
- function selectEventlessTransitions(nextState) {
2538
+ function selectEventlessTransitions(nextState, event) {
2589
2539
  const enabledTransitionSet = new Set();
2590
2540
  const atomicStates = nextState.configuration.filter(isAtomicStateNode);
2591
2541
  for (const stateNode of atomicStates) {
@@ -2594,7 +2544,7 @@ function selectEventlessTransitions(nextState) {
2594
2544
  continue;
2595
2545
  }
2596
2546
  for (const transition of s.always) {
2597
- if (transition.guard === undefined || evaluateGuard(transition.guard, nextState.context, nextState.event, nextState)) {
2547
+ if (transition.guard === undefined || evaluateGuard(transition.guard, nextState.context, event, nextState)) {
2598
2548
  enabledTransitionSet.add(transition);
2599
2549
  break loop;
2600
2550
  }
@@ -2662,10 +2612,6 @@ class State {
2662
2612
  * The enabled state nodes representative of the state value.
2663
2613
  */
2664
2614
 
2665
- /**
2666
- * The transition definitions that resulted in this state.
2667
- */
2668
-
2669
2615
  /**
2670
2616
  * An object mapping actor names to spawned/invoked actors.
2671
2617
  */
@@ -2681,8 +2627,6 @@ class State {
2681
2627
  return new State({
2682
2628
  value: stateValue.value,
2683
2629
  context,
2684
- event: stateValue.event,
2685
- actions: [],
2686
2630
  meta: {},
2687
2631
  configuration: [],
2688
2632
  // TODO: fix,
@@ -2692,17 +2636,12 @@ class State {
2692
2636
  }
2693
2637
  return stateValue;
2694
2638
  }
2695
- const event = createInitEvent({}); // TODO: fix
2696
-
2697
2639
  const configuration = getConfiguration(getStateNodes(machine.root, stateValue));
2698
2640
  return new State({
2699
2641
  value: stateValue,
2700
2642
  context,
2701
- event,
2702
- actions: [],
2703
2643
  meta: undefined,
2704
2644
  configuration: Array.from(configuration),
2705
- transitions: [],
2706
2645
  children: {}
2707
2646
  }, machine);
2708
2647
  }
@@ -2720,23 +2659,17 @@ class State {
2720
2659
  this.output = void 0;
2721
2660
  this.context = void 0;
2722
2661
  this.historyValue = {};
2723
- this.actions = [];
2724
- this.event = void 0;
2725
2662
  this._internalQueue = void 0;
2726
2663
  this._initial = false;
2727
2664
  this.changed = void 0;
2728
2665
  this.configuration = void 0;
2729
- this.transitions = void 0;
2730
2666
  this.children = void 0;
2731
2667
  this.context = config.context;
2732
2668
  this._internalQueue = config._internalQueue ?? [];
2733
- this.event = config.event;
2734
2669
  this.historyValue = config.historyValue || {};
2735
- this.actions = config.actions ?? [];
2736
2670
  this.matches = this.matches.bind(this);
2737
2671
  this.toStrings = this.toStrings.bind(this);
2738
2672
  this.configuration = config.configuration ?? Array.from(getConfiguration(getStateNodes(machine.root, config.value)));
2739
- this.transitions = config.transitions;
2740
2673
  this.children = config.children;
2741
2674
  this.value = getStateValue(machine.root, this.configuration);
2742
2675
  this.tags = new Set(flatten(this.configuration.map(sn => sn.tags)));
@@ -2759,7 +2692,6 @@ class State {
2759
2692
  toJSON() {
2760
2693
  const {
2761
2694
  configuration,
2762
- transitions,
2763
2695
  tags,
2764
2696
  machine,
2765
2697
  ...jsonValues
@@ -2828,7 +2760,6 @@ function cloneState(state, config = {}) {
2828
2760
  function getPersistedState(state) {
2829
2761
  const {
2830
2762
  configuration,
2831
- transitions,
2832
2763
  tags,
2833
2764
  machine,
2834
2765
  children,
@@ -2847,86 +2778,105 @@ function getPersistedState(state) {
2847
2778
  };
2848
2779
  }
2849
2780
 
2850
- function invoke(invokeDef) {
2781
+ /**
2782
+ * Stops an actor.
2783
+ *
2784
+ * @param actorRef The actor to stop.
2785
+ */
2786
+
2787
+ function stop(actorRef) {
2788
+ const actor = actorRef;
2851
2789
  return createDynamicAction({
2852
- type: invoke$1,
2853
- params: invokeDef
2790
+ type: stop$1,
2791
+ params: {
2792
+ actor
2793
+ }
2854
2794
  }, (event, {
2855
- state,
2856
- actorContext
2795
+ state
2857
2796
  }) => {
2858
- const type = invoke$1;
2859
- const {
2860
- id,
2861
- src
2862
- } = invokeDef;
2863
- let resolvedInvokeAction;
2864
- if (isActorRef(src)) {
2865
- resolvedInvokeAction = {
2866
- type,
2867
- params: {
2868
- ...invokeDef,
2869
- ref: src
2870
- }
2797
+ const actorRefOrString = isFunction(actor) ? actor({
2798
+ context: state.context,
2799
+ event
2800
+ }) : actor;
2801
+ const actorRef = typeof actorRefOrString === 'string' ? state.children[actorRefOrString] : actorRefOrString;
2802
+ let children = state.children;
2803
+ if (actorRef) {
2804
+ children = {
2805
+ ...children
2871
2806
  };
2872
- } else {
2873
- const referenced = resolveReferencedActor(state.machine.options.actors[src]);
2874
- if (!referenced) {
2875
- resolvedInvokeAction = {
2876
- type,
2877
- params: invokeDef
2878
- };
2879
- } else {
2880
- const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
2881
- const ref = interpret(referenced.src, {
2882
- id,
2883
- src,
2884
- parent: actorContext?.self,
2885
- systemId: invokeDef.systemId,
2886
- input: typeof input === 'function' ? input({
2887
- context: state.context,
2888
- event,
2889
- self: actorContext?.self
2890
- }) : input
2891
- });
2892
- resolvedInvokeAction = {
2893
- type,
2894
- params: {
2895
- ...invokeDef,
2896
- ref
2897
- }
2898
- };
2899
- }
2807
+ delete children[actorRef.id];
2900
2808
  }
2901
- const actorRef = resolvedInvokeAction.params.ref;
2902
- const invokedState = cloneState(state, {
2903
- children: {
2904
- ...state.children,
2905
- [id]: actorRef
2906
- }
2907
- });
2908
- resolvedInvokeAction.execute = actorCtx => {
2909
- const parent = actorCtx.self;
2910
- const {
2911
- id,
2912
- ref
2913
- } = resolvedInvokeAction.params;
2914
- if (!ref) {
2915
- return;
2916
- }
2917
- actorCtx.defer(() => {
2918
- if (actorRef.status === ActorStatus.Stopped) {
2809
+ return [cloneState(state, {
2810
+ children
2811
+ }), {
2812
+ type: 'xstate.stop',
2813
+ params: {
2814
+ actor: actorRef
2815
+ },
2816
+ execute: actorCtx => {
2817
+ if (!actorRef) {
2919
2818
  return;
2920
2819
  }
2921
- try {
2922
- actorRef.start?.();
2923
- } catch (err) {
2924
- parent.send(error(id, err));
2820
+ if (actorRef.status !== ActorStatus.Running) {
2821
+ actorCtx.stopChild(actorRef);
2925
2822
  return;
2926
2823
  }
2927
- });
2928
- };
2929
- return [invokedState, resolvedInvokeAction];
2824
+ actorCtx.defer(() => {
2825
+ actorCtx.stopChild(actorRef);
2826
+ });
2827
+ }
2828
+ }];
2829
+ });
2830
+ }
2831
+
2832
+ const defaultLogExpr = ({
2833
+ context,
2834
+ event
2835
+ }) => ({
2836
+ context,
2837
+ event
2838
+ });
2839
+
2840
+ /**
2841
+ *
2842
+ * @param expr The expression function to evaluate which will be logged.
2843
+ * Takes in 2 arguments:
2844
+ * - `ctx` - the current state context
2845
+ * - `event` - the event that caused this action to be executed.
2846
+ * @param label The label to give to the logged expression.
2847
+ */
2848
+
2849
+ function log(expr = defaultLogExpr, label) {
2850
+ return createDynamicAction({
2851
+ type: log$1,
2852
+ params: {
2853
+ label,
2854
+ expr
2855
+ }
2856
+ }, (event, {
2857
+ state,
2858
+ actorContext
2859
+ }) => {
2860
+ const resolvedValue = typeof expr === 'function' ? expr({
2861
+ context: state.context,
2862
+ event,
2863
+ self: actorContext?.self ?? {},
2864
+ system: actorContext?.system
2865
+ }) : expr;
2866
+ return [state, {
2867
+ type: 'xstate.log',
2868
+ params: {
2869
+ label,
2870
+ value: resolvedValue
2871
+ },
2872
+ execute: actorCtx => {
2873
+ if (label) {
2874
+ actorCtx.logger?.(label, resolvedValue);
2875
+ } else {
2876
+ actorCtx.logger?.(resolvedValue);
2877
+ }
2878
+ }
2879
+ }];
2930
2880
  });
2931
2881
  }
2932
2882
 
@@ -2964,7 +2914,7 @@ function createSpawner(self, machine, context, event, mutCapturedActions) {
2964
2914
  return actorRef; // TODO: fix types
2965
2915
  }
2966
2916
 
2967
- throw new Error(`Behavior '${src}' not implemented in machine '${machine.id}'`);
2917
+ throw new Error(`Actor logic '${src}' not implemented in machine '${machine.id}'`);
2968
2918
  } else {
2969
2919
  // TODO: this should also receive `src`
2970
2920
  // TODO: instead of anonymous, it should be a unique stable ID
@@ -3289,4 +3239,4 @@ function createInitEvent(input) {
3289
3239
  };
3290
3240
  }
3291
3241
 
3292
- export { toObserver as $, createInitEvent as A, resolveActionsAndContext as B, microstep as C, error as D, isStateId as E, getStateNodeByPath as F, getPersistedState as G, resolveReferencedActor as H, interpret as I, initEvent as J, matchesState as K, sendTo as L, sendParent as M, NULL_EVENT as N, forwardTo as O, Interpreter as P, ActorStatus as Q, doneInvoke as R, STATE_DELIMITER as S, assign as T, cancel as U, choose as V, log as W, pure as X, raise as Y, stop as Z, pathToStateValue as _, toArray as a, fromPromise as a0, fromObservable as a1, fromCallback as a2, fromEventObservable as a3, fromTransition as a4, stateIn as a5, not as a6, and as a7, or as a8, ActionTypes as a9, SpecialTargets as aa, startSignalType as ab, stopSignalType as ac, startSignal as ad, stopSignal as ae, isSignal as af, isActorRef as ag, toActorRef as ah, createEmptyActor as ai, toGuardDefinition as aj, actionTypes as ak, resolveActionObject as al, toActionObject as am, after as an, done as ao, send as ap, escalate as aq, toTransitionConfigArray as b, formatTransition as c, memo as d, evaluateGuard as e, formatTransitions as f, flatten as g, createInvokeId as h, isString as i, invoke$1 as j, getDelayedTransitions as k, formatInitialTransition as l, mapValues as m, getCandidates as n, toInvokeConfig as o, createSpawner as p, getConfiguration as q, getStateNodes as r, resolveStateValue as s, toActionObjects as t, isInFinalState as u, State as v, isErrorEvent as w, macrostep as x, transitionNode as y, getInitialConfiguration as z };
3242
+ export { toObserver as $, resolveActionsAndContext as A, microstep as B, error as C, isStateId as D, getStateNodeByPath as E, getPersistedState as F, resolveReferencedActor as G, interpret as H, createInitEvent as I, initEvent as J, matchesState as K, sendTo as L, sendParent as M, NULL_EVENT as N, forwardTo as O, Interpreter as P, ActorStatus as Q, doneInvoke as R, STATE_DELIMITER as S, assign as T, cancel as U, choose as V, log as W, pure as X, raise as Y, stop as Z, pathToStateValue as _, toArray as a, fromPromise as a0, fromObservable as a1, fromCallback as a2, fromEventObservable as a3, fromTransition as a4, stateIn as a5, not as a6, and as a7, or as a8, ActionTypes as a9, SpecialTargets as aa, startSignalType as ab, stopSignalType as ac, startSignal as ad, stopSignal as ae, isSignal as af, isActorRef as ag, toActorRef as ah, createEmptyActor as ai, toGuardDefinition as aj, actionTypes as ak, resolveActionObject as al, toActionObject as am, after as an, done as ao, send as ap, escalate as aq, toTransitionConfigArray as b, formatTransition as c, memo as d, evaluateGuard as e, formatTransitions as f, flatten as g, createInvokeId as h, isString as i, invoke$1 as j, getDelayedTransitions as k, formatInitialTransition as l, mapValues as m, getCandidates as n, toInvokeConfig as o, createSpawner as p, getConfiguration as q, getStateNodes as r, resolveStateValue as s, toActionObjects as t, isInFinalState as u, State as v, isErrorEvent as w, macrostep as x, transitionNode as y, getInitialConfiguration as z };