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
@@ -12,10 +12,10 @@ var dev_dist_xstateDev = require('../dev/dist/xstate-dev.development.cjs.js');
12
12
  */
13
13
 
14
14
  // TODO: do not accept machines without all implementations
15
- // we should also accept a raw machine as a behavior here
16
- // or just make machine a behavior
15
+ // we should also accept a raw machine as actor logic here
16
+ // or just make machine actor logic
17
17
 
18
- // TODO: narrow this to behaviors from machine
18
+ // TODO: narrow this to logic from machine
19
19
 
20
20
  // TODO: fix last param
21
21
 
@@ -479,6 +479,59 @@ function sendTo(actor, event, options) {
479
479
  });
480
480
  }
481
481
 
482
+ const cache = new WeakMap();
483
+ function memo(object, key, fn) {
484
+ let memoizedData = cache.get(object);
485
+ if (!memoizedData) {
486
+ memoizedData = {
487
+ [key]: fn()
488
+ };
489
+ cache.set(object, memoizedData);
490
+ } else if (!(key in memoizedData)) {
491
+ memoizedData[key] = fn();
492
+ }
493
+ return memoizedData[key];
494
+ }
495
+
496
+ /**
497
+ * Cancels an in-flight `send(...)` action. A canceled sent action will not
498
+ * be executed, nor will its event be sent, unless it has already been sent
499
+ * (e.g., if `cancel(...)` is called after the `send(...)` action's `delay`).
500
+ *
501
+ * @param sendId The `id` of the `send(...)` action to cancel.
502
+ */
503
+
504
+ function cancel(sendId) {
505
+ return createDynamicAction({
506
+ type: cancel$1,
507
+ params: {
508
+ sendId
509
+ }
510
+ }, (event, {
511
+ state,
512
+ actorContext
513
+ }) => {
514
+ const resolvedSendId = isFunction(sendId) ? sendId({
515
+ context: state.context,
516
+ event,
517
+ self: actorContext?.self ?? {},
518
+ system: actorContext?.system
519
+ }) : sendId;
520
+ return [state, {
521
+ type: 'xstate.cancel',
522
+ params: {
523
+ sendId: resolvedSendId
524
+ },
525
+ execute: actorCtx => {
526
+ const interpreter = actorCtx.self;
527
+ interpreter.cancel(resolvedSendId);
528
+ }
529
+ }];
530
+ });
531
+ }
532
+
533
+ const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
534
+
482
535
  class Mailbox {
483
536
  constructor(_process) {
484
537
  this._process = _process;
@@ -546,537 +599,92 @@ class Mailbox {
546
599
  }
547
600
  }
548
601
 
549
- const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
550
-
551
- /**
552
- * Returns an actor behavior from a transition function and its initial state.
553
- *
554
- * A transition function is a function that takes the current state and an event and returns the next state.
555
- *
556
- * @param transition The transition function that returns the next state given the current state and event.
557
- * @param initialState The initial state of the transition function.
558
- * @returns An actor behavior
559
- */
560
- function fromTransition(transition, initialState) {
561
- const behavior = {
562
- config: transition,
563
- transition: (state, event, actorContext) => {
564
- return transition(state, event, actorContext);
565
- },
566
- getInitialState: (_, input) => {
567
- return typeof initialState === 'function' ? initialState({
568
- input
569
- }) : initialState;
570
- },
571
- getSnapshot: state => state,
572
- getPersistedState: state => state,
573
- restoreState: state => state
574
- };
575
- return behavior;
576
- }
577
-
578
- function fromPromise(
579
- // TODO: add types
580
- promiseCreator) {
581
- const resolveEventType = '$$xstate.resolve';
582
- const rejectEventType = '$$xstate.reject';
583
-
584
- // TODO: add event types
585
- const behavior = {
586
- config: promiseCreator,
587
- transition: (state, event) => {
588
- if (state.status !== 'active') {
589
- return state;
590
- }
591
- switch (event.type) {
592
- case resolveEventType:
593
- return {
594
- ...state,
595
- status: 'done',
596
- data: event.data,
597
- input: undefined
598
- };
599
- case rejectEventType:
600
- return {
601
- ...state,
602
- status: 'error',
603
- data: event.data,
604
- input: undefined
605
- };
606
- case stopSignalType:
607
- return {
608
- ...state,
609
- status: 'canceled',
610
- input: undefined
611
- };
612
- default:
613
- return state;
614
- }
602
+ function createSystem() {
603
+ let sessionIdCounter = 0;
604
+ const children = new Map();
605
+ const keyedActors = new Map();
606
+ const reverseKeyedActors = new WeakMap();
607
+ const system = {
608
+ _bookId: () => `x:${sessionIdCounter++}`,
609
+ _register: (sessionId, actorRef) => {
610
+ children.set(sessionId, actorRef);
611
+ return sessionId;
615
612
  },
616
- start: (state, {
617
- self
618
- }) => {
619
- // TODO: determine how to allow customizing this so that promises
620
- // can be restarted if necessary
621
- if (state.status !== 'active') {
622
- return;
613
+ _unregister: actorRef => {
614
+ children.delete(actorRef.sessionId);
615
+ const systemId = reverseKeyedActors.get(actorRef);
616
+ if (systemId !== undefined) {
617
+ keyedActors.delete(systemId);
618
+ reverseKeyedActors.delete(actorRef);
623
619
  }
624
- const resolvedPromise = Promise.resolve(promiseCreator({
625
- input: state.input
626
- }));
627
- resolvedPromise.then(response => {
628
- // TODO: remove this condition once dead letter queue lands
629
- if (self._state.status !== 'active') {
630
- return;
631
- }
632
- self.send({
633
- type: resolveEventType,
634
- data: response
635
- });
636
- }, errorData => {
637
- // TODO: remove this condition once dead letter queue lands
638
- if (self._state.status !== 'active') {
639
- return;
640
- }
641
- self.send({
642
- type: rejectEventType,
643
- data: errorData
644
- });
645
- });
646
620
  },
647
- getInitialState: (_, input) => {
648
- return {
649
- status: 'active',
650
- data: undefined,
651
- input
652
- };
621
+ get: systemId => {
622
+ return keyedActors.get(systemId);
653
623
  },
654
- getSnapshot: state => state.data,
655
- getStatus: state => state,
656
- getPersistedState: state => state,
657
- restoreState: state => state
624
+ _set: (systemId, actorRef) => {
625
+ const existing = keyedActors.get(systemId);
626
+ if (existing && existing !== actorRef) {
627
+ throw new Error(`Actor with system ID '${systemId}' already exists.`);
628
+ }
629
+ keyedActors.set(systemId, actorRef);
630
+ reverseKeyedActors.set(actorRef, systemId);
631
+ }
658
632
  };
659
- return behavior;
633
+ return system;
660
634
  }
661
635
 
662
- // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
663
- function fromObservable(observableCreator) {
664
- const nextEventType = '$$xstate.next';
665
- const errorEventType = '$$xstate.error';
666
- const completeEventType = '$$xstate.complete';
667
-
668
- // TODO: add event types
669
- const behavior = {
670
- config: observableCreator,
671
- transition: (state, event, {
672
- self,
673
- id,
674
- defer
675
- }) => {
676
- if (state.status !== 'active') {
677
- return state;
678
- }
679
- switch (event.type) {
680
- case nextEventType:
681
- // match the exact timing of events sent by machines
682
- // send actions are not executed immediately
683
- defer(() => {
684
- self._parent?.send({
685
- type: `xstate.snapshot.${id}`,
686
- data: event.data
687
- });
688
- });
689
- return {
690
- ...state,
691
- data: event.data
692
- };
693
- case errorEventType:
694
- return {
695
- ...state,
696
- status: 'error',
697
- input: undefined,
698
- data: event.data,
699
- subscription: undefined
700
- };
701
- case completeEventType:
702
- return {
703
- ...state,
704
- status: 'done',
705
- input: undefined,
706
- subscription: undefined
707
- };
708
- case stopSignalType:
709
- state.subscription.unsubscribe();
710
- return {
711
- ...state,
712
- status: 'canceled',
713
- input: undefined,
714
- subscription: undefined
715
- };
716
- default:
717
- return state;
718
- }
719
- },
720
- getInitialState: (_, input) => {
721
- return {
722
- subscription: undefined,
723
- status: 'active',
724
- data: undefined,
725
- input
726
- };
636
+ let ActorStatus = /*#__PURE__*/function (ActorStatus) {
637
+ ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
638
+ ActorStatus[ActorStatus["Running"] = 1] = "Running";
639
+ ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
640
+ return ActorStatus;
641
+ }({});
642
+ const defaultOptions = {
643
+ deferEvents: true,
644
+ clock: {
645
+ setTimeout: (fn, ms) => {
646
+ return setTimeout(fn, ms);
727
647
  },
728
- start: (state, {
729
- self
730
- }) => {
731
- if (state.status === 'done') {
732
- // Do not restart a completed observable
733
- return;
734
- }
735
- state.subscription = observableCreator({
736
- input: state.input
737
- }).subscribe({
738
- next: value => {
739
- self.send({
740
- type: nextEventType,
741
- data: value
742
- });
743
- },
744
- error: err => {
745
- self.send({
746
- type: errorEventType,
747
- data: err
748
- });
749
- },
750
- complete: () => {
751
- self.send({
752
- type: completeEventType
753
- });
754
- }
755
- });
756
- },
757
- getSnapshot: state => state.data,
758
- getPersistedState: ({
759
- status,
760
- data,
761
- input
762
- }) => ({
763
- status,
764
- data,
765
- input
766
- }),
767
- getStatus: state => state,
768
- restoreState: state => ({
769
- ...state,
770
- subscription: undefined
771
- })
772
- };
773
- return behavior;
774
- }
648
+ clearTimeout: id => {
649
+ return clearTimeout(id);
650
+ }
651
+ },
652
+ logger: console.log.bind(console),
653
+ devTools: false
654
+ };
655
+ class Interpreter {
656
+ /**
657
+ * The current state of the interpreted logic.
658
+ */
775
659
 
776
- /**
777
- * Creates an event observable behavior that listens to an observable
778
- * that delivers event objects.
779
- *
780
- *
781
- * @param lazyObservable A function that creates an observable
782
- * @returns An event observable behavior
783
- */
660
+ /**
661
+ * The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
662
+ */
784
663
 
785
- function fromEventObservable(lazyObservable) {
786
- const errorEventType = '$$xstate.error';
787
- const completeEventType = '$$xstate.complete';
664
+ /**
665
+ * The unique identifier for this actor relative to its parent.
666
+ */
788
667
 
789
- // TODO: event types
790
- const behavior = {
791
- config: lazyObservable,
792
- transition: (state, event) => {
793
- if (state.status !== 'active') {
794
- return state;
795
- }
796
- switch (event.type) {
797
- case errorEventType:
798
- return {
799
- ...state,
800
- status: 'error',
801
- input: undefined,
802
- data: event.data,
803
- subscription: undefined
804
- };
805
- case completeEventType:
806
- return {
807
- ...state,
808
- status: 'done',
809
- input: undefined,
810
- subscription: undefined
811
- };
812
- case stopSignalType:
813
- state.subscription.unsubscribe();
814
- return {
815
- ...state,
816
- status: 'canceled',
817
- input: undefined,
818
- subscription: undefined
819
- };
820
- default:
821
- return state;
822
- }
823
- },
824
- getInitialState: (_, input) => {
825
- return {
826
- subscription: undefined,
827
- status: 'active',
828
- data: undefined,
829
- input
830
- };
831
- },
832
- start: (state, {
833
- self
834
- }) => {
835
- if (state.status === 'done') {
836
- // Do not restart a completed observable
837
- return;
838
- }
839
- state.subscription = lazyObservable({
840
- input: state.input
841
- }).subscribe({
842
- next: value => {
843
- self._parent?.send(value);
844
- },
845
- error: err => {
846
- self.send({
847
- type: errorEventType,
848
- data: err
849
- });
850
- },
851
- complete: () => {
852
- self.send({
853
- type: completeEventType
854
- });
855
- }
856
- });
857
- },
858
- getSnapshot: _ => undefined,
859
- getPersistedState: ({
860
- status,
861
- data,
862
- input
863
- }) => ({
864
- status,
865
- data,
866
- input
867
- }),
868
- getStatus: state => state,
869
- restoreState: state => ({
870
- ...state,
871
- subscription: undefined
872
- })
873
- };
874
- return behavior;
875
- }
668
+ /**
669
+ * Whether the service is started.
670
+ */
876
671
 
877
- function fromCallback(invokeCallback) {
878
- const behavior = {
879
- config: invokeCallback,
880
- start: (_state, {
881
- self
882
- }) => {
883
- self.send({
884
- type: startSignalType
885
- });
886
- },
887
- transition: (state, event, {
888
- self,
889
- id
890
- }) => {
891
- if (event.type === startSignalType) {
892
- const sender = eventForParent => {
893
- if (state.canceled) {
894
- return;
895
- }
896
- self._parent?.send(eventForParent);
897
- };
898
- const receiver = newListener => {
899
- state.receivers.add(newListener);
900
- };
901
- state.dispose = invokeCallback(sender, receiver, {
902
- input: state.input
903
- });
904
- if (isPromiseLike(state.dispose)) {
905
- state.dispose.then(resolved => {
906
- self._parent?.send(doneInvoke(id, resolved));
907
- state.canceled = true;
908
- }, errorData => {
909
- state.canceled = true;
910
- self._parent?.send(error(id, errorData));
911
- });
912
- }
913
- return state;
914
- }
915
- if (event.type === stopSignalType) {
916
- state.canceled = true;
917
- if (isFunction(state.dispose)) {
918
- state.dispose();
919
- }
920
- return state;
921
- }
922
- if (isSignal(event.type)) {
923
- // TODO: unrecognized signal
924
- return state;
925
- }
926
- if (!isSignal(event.type)) {
927
- state.receivers.forEach(receiver => receiver(event));
928
- }
929
- return state;
930
- },
931
- getInitialState: (_, input) => {
932
- return {
933
- canceled: false,
934
- receivers: new Set(),
935
- dispose: undefined,
936
- input
937
- };
938
- },
939
- getSnapshot: () => undefined,
940
- getPersistedState: ({
941
- input
942
- }) => input
943
- };
944
- return behavior;
945
- }
672
+ // Actor Ref
946
673
 
947
- const startSignalType = 'xstate.init';
948
- const stopSignalType = 'xstate.stop';
949
- const startSignal = {
950
- type: 'xstate.init'
951
- };
952
- const stopSignal = {
953
- type: 'xstate.stop'
954
- };
955
- /**
956
- * An object that expresses the behavior of an actor in reaction to received events,
957
- * as well as an optionally emitted stream of values.
958
- *
959
- * @template TReceived The received event
960
- * @template TSnapshot The emitted value
961
- */
674
+ // TODO: add typings for system
962
675
 
963
- function isSignal(eventType) {
964
- return eventType === startSignalType || eventType === stopSignalType;
965
- }
966
- function isActorRef(item) {
967
- return !!item && typeof item === 'object' && typeof item.send === 'function';
968
- }
969
-
970
- // TODO: refactor the return type, this could be written in a better way
971
- // but it's best to avoid unneccessary breaking changes now
972
- // @deprecated use `interpret(behavior)` instead
973
- function toActorRef(actorRefLike) {
974
- return {
975
- subscribe: () => ({
976
- unsubscribe: () => void 0
977
- }),
978
- id: 'anonymous',
979
- sessionId: '',
980
- getSnapshot: () => undefined,
981
- [symbolObservable]: function () {
982
- return this;
983
- },
984
- status: ActorStatus.Running,
985
- stop: () => void 0,
986
- ...actorRefLike
987
- };
988
- }
989
- const emptyBehavior = fromTransition(_ => undefined, undefined);
990
- function createEmptyActor() {
991
- return interpret(emptyBehavior);
992
- }
993
-
994
- function createSystem() {
995
- let sessionIdCounter = 0;
996
- const children = new Map();
997
- const keyedActors = new Map();
998
- const reverseKeyedActors = new WeakMap();
999
- const system = {
1000
- _bookId: () => `x:${sessionIdCounter++}`,
1001
- _register: (sessionId, actorRef) => {
1002
- children.set(sessionId, actorRef);
1003
- return sessionId;
1004
- },
1005
- _unregister: actorRef => {
1006
- children.delete(actorRef.sessionId);
1007
- const systemId = reverseKeyedActors.get(actorRef);
1008
- if (systemId !== undefined) {
1009
- keyedActors.delete(systemId);
1010
- reverseKeyedActors.delete(actorRef);
1011
- }
1012
- },
1013
- get: systemId => {
1014
- return keyedActors.get(systemId);
1015
- },
1016
- _set: (systemId, actorRef) => {
1017
- const existing = keyedActors.get(systemId);
1018
- if (existing && existing !== actorRef) {
1019
- throw new Error(`Actor with system ID '${systemId}' already exists.`);
1020
- }
1021
- keyedActors.set(systemId, actorRef);
1022
- reverseKeyedActors.set(actorRef, systemId);
1023
- }
1024
- };
1025
- return system;
1026
- }
1027
-
1028
- let ActorStatus = /*#__PURE__*/function (ActorStatus) {
1029
- ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
1030
- ActorStatus[ActorStatus["Running"] = 1] = "Running";
1031
- ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
1032
- return ActorStatus;
1033
- }({});
1034
- const defaultOptions = {
1035
- deferEvents: true,
1036
- clock: {
1037
- setTimeout: (fn, ms) => {
1038
- return setTimeout(fn, ms);
1039
- },
1040
- clearTimeout: id => {
1041
- return clearTimeout(id);
1042
- }
1043
- },
1044
- logger: console.log.bind(console),
1045
- devTools: false
1046
- };
1047
- class Interpreter {
1048
- /**
1049
- * The current state of the interpreted behavior.
1050
- */
1051
-
1052
- /**
1053
- * The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
1054
- */
1055
-
1056
- /**
1057
- * The unique identifier for this actor relative to its parent.
1058
- */
676
+ /**
677
+ * The globally unique process ID for this invocation.
678
+ */
1059
679
 
1060
680
  /**
1061
- * Whether the service is started.
1062
- */
1063
-
1064
- // Actor Ref
1065
-
1066
- // TODO: add typings for system
1067
-
1068
- /**
1069
- * The globally unique process ID for this invocation.
1070
- */
1071
-
1072
- /**
1073
- * Creates a new Interpreter instance (i.e., service) for the given behavior with the provided options, if any.
681
+ * Creates a new Interpreter instance (i.e., service) for the given logic with the provided options, if any.
1074
682
  *
1075
- * @param behavior The behavior to be interpreted
683
+ * @param logic The logic to be interpreted
1076
684
  * @param options Interpreter options
1077
685
  */
1078
- constructor(behavior, options) {
1079
- this.behavior = behavior;
686
+ constructor(logic, options) {
687
+ this.logic = logic;
1080
688
  this._state = void 0;
1081
689
  this.clock = void 0;
1082
690
  this.options = void 0;
@@ -1143,7 +751,7 @@ class Interpreter {
1143
751
  this._initState();
1144
752
  }
1145
753
  _initState() {
1146
- 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);
754
+ 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);
1147
755
  }
1148
756
 
1149
757
  // array of functions to defer
@@ -1156,12 +764,12 @@ class Interpreter {
1156
764
  // Execute deferred effects
1157
765
  let deferredFn;
1158
766
  while (deferredFn = this._deferred.shift()) {
1159
- deferredFn(state);
767
+ deferredFn();
1160
768
  }
1161
769
  for (const observer of this.observers) {
1162
770
  observer.next?.(snapshot);
1163
771
  }
1164
- const status = this.behavior.getStatus?.(state);
772
+ const status = this.logic.getStatus?.(state);
1165
773
  switch (status?.status) {
1166
774
  case 'done':
1167
775
  this._stopProcedure();
@@ -1203,8 +811,8 @@ class Interpreter {
1203
811
  this.system._set(this._systemId, this);
1204
812
  }
1205
813
  this.status = ActorStatus.Running;
1206
- if (this.behavior.start) {
1207
- this.behavior.start(this._state, this._actorContext);
814
+ if (this.logic.start) {
815
+ this.logic.start(this._state, this._actorContext);
1208
816
  }
1209
817
 
1210
818
  // TODO: this notifies all subscribers but usually this is redundant
@@ -1219,7 +827,7 @@ class Interpreter {
1219
827
  }
1220
828
  _process(event) {
1221
829
  try {
1222
- const nextState = this.behavior.transition(this._state, event, this._actorContext);
830
+ const nextState = this.logic.transition(this._state, event, this._actorContext);
1223
831
  this.update(nextState);
1224
832
  if (event.type === stopSignalType) {
1225
833
  this._stopProcedure();
@@ -1311,211 +919,604 @@ class Interpreter {
1311
919
  const eventString = JSON.stringify(event);
1312
920
  console.warn(`Event "${event.type.toString()}" was sent to stopped actor "${this.id} (${this.sessionId})". This actor has already reached its final state, and will not transition.\nEvent: ${eventString}`);
1313
921
  }
1314
- return;
1315
- }
1316
- if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
1317
- throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
1318
- // tslint:disable-next-line:max-line-length
1319
- }". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
1320
- }
1321
- this.mailbox.enqueue(event);
1322
- }
1323
-
1324
- // TODO: make private (and figure out a way to do this within the machine)
1325
- delaySend(sendAction) {
1326
- this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
1327
- if ('to' in sendAction.params && sendAction.params.to) {
1328
- sendAction.params.to.send(sendAction.params.event);
1329
- } else {
1330
- this.send(sendAction.params.event);
922
+ return;
923
+ }
924
+ if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
925
+ throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
926
+ // tslint:disable-next-line:max-line-length
927
+ }". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
928
+ }
929
+ this.mailbox.enqueue(event);
930
+ }
931
+
932
+ // TODO: make private (and figure out a way to do this within the machine)
933
+ delaySend(sendAction) {
934
+ this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
935
+ if ('to' in sendAction.params && sendAction.params.to) {
936
+ sendAction.params.to.send(sendAction.params.event);
937
+ } else {
938
+ this.send(sendAction.params.event);
939
+ }
940
+ }, sendAction.params.delay);
941
+ }
942
+
943
+ // TODO: make private (and figure out a way to do this within the machine)
944
+ cancel(sendId) {
945
+ this.clock.clearTimeout(this.delayedEventsMap[sendId]);
946
+ delete this.delayedEventsMap[sendId];
947
+ }
948
+ attachDevTools() {
949
+ const {
950
+ devTools
951
+ } = this.options;
952
+ if (devTools) {
953
+ const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
954
+ resolvedDevToolsAdapter(this);
955
+ }
956
+ }
957
+ toJSON() {
958
+ return {
959
+ id: this.id
960
+ };
961
+ }
962
+ getPersistedState() {
963
+ return this.logic.getPersistedState?.(this._state);
964
+ }
965
+ [symbolObservable]() {
966
+ return this;
967
+ }
968
+ getSnapshot() {
969
+ return this.logic.getSnapshot ? this.logic.getSnapshot(this._state) : this._state;
970
+ }
971
+ }
972
+
973
+ /**
974
+ * Creates a new Interpreter instance for the given machine with the provided options, if any.
975
+ *
976
+ * @param machine The machine to interpret
977
+ * @param options Interpreter options
978
+ */
979
+
980
+ function interpret(logic, options) {
981
+ const interpreter = new Interpreter(logic, options);
982
+ return interpreter;
983
+ }
984
+
985
+ /**
986
+ * Returns actor logic from a transition function and its initial state.
987
+ *
988
+ * A transition function is a function that takes the current state and an event and returns the next state.
989
+ *
990
+ * @param transition The transition function that returns the next state given the current state and event.
991
+ * @param initialState The initial state of the transition function.
992
+ * @returns Actor logic
993
+ */
994
+ function fromTransition(transition, initialState) {
995
+ const logic = {
996
+ config: transition,
997
+ transition: (state, event, actorContext) => {
998
+ return transition(state, event, actorContext);
999
+ },
1000
+ getInitialState: (_, input) => {
1001
+ return typeof initialState === 'function' ? initialState({
1002
+ input
1003
+ }) : initialState;
1004
+ },
1005
+ getSnapshot: state => state,
1006
+ getPersistedState: state => state,
1007
+ restoreState: state => state
1008
+ };
1009
+ return logic;
1010
+ }
1011
+
1012
+ function fromPromise(
1013
+ // TODO: add types
1014
+ promiseCreator) {
1015
+ const resolveEventType = '$$xstate.resolve';
1016
+ const rejectEventType = '$$xstate.reject';
1017
+
1018
+ // TODO: add event types
1019
+ const logic = {
1020
+ config: promiseCreator,
1021
+ transition: (state, event) => {
1022
+ if (state.status !== 'active') {
1023
+ return state;
1024
+ }
1025
+ switch (event.type) {
1026
+ case resolveEventType:
1027
+ return {
1028
+ ...state,
1029
+ status: 'done',
1030
+ data: event.data,
1031
+ input: undefined
1032
+ };
1033
+ case rejectEventType:
1034
+ return {
1035
+ ...state,
1036
+ status: 'error',
1037
+ data: event.data,
1038
+ input: undefined
1039
+ };
1040
+ case stopSignalType:
1041
+ return {
1042
+ ...state,
1043
+ status: 'canceled',
1044
+ input: undefined
1045
+ };
1046
+ default:
1047
+ return state;
1048
+ }
1049
+ },
1050
+ start: (state, {
1051
+ self,
1052
+ system
1053
+ }) => {
1054
+ // TODO: determine how to allow customizing this so that promises
1055
+ // can be restarted if necessary
1056
+ if (state.status !== 'active') {
1057
+ return;
1058
+ }
1059
+ const resolvedPromise = Promise.resolve(promiseCreator({
1060
+ input: state.input,
1061
+ system
1062
+ }));
1063
+ resolvedPromise.then(response => {
1064
+ // TODO: remove this condition once dead letter queue lands
1065
+ if (self._state.status !== 'active') {
1066
+ return;
1067
+ }
1068
+ self.send({
1069
+ type: resolveEventType,
1070
+ data: response
1071
+ });
1072
+ }, errorData => {
1073
+ // TODO: remove this condition once dead letter queue lands
1074
+ if (self._state.status !== 'active') {
1075
+ return;
1076
+ }
1077
+ self.send({
1078
+ type: rejectEventType,
1079
+ data: errorData
1080
+ });
1081
+ });
1082
+ },
1083
+ getInitialState: (_, input) => {
1084
+ return {
1085
+ status: 'active',
1086
+ data: undefined,
1087
+ input
1088
+ };
1089
+ },
1090
+ getSnapshot: state => state.data,
1091
+ getStatus: state => state,
1092
+ getPersistedState: state => state,
1093
+ restoreState: state => state
1094
+ };
1095
+ return logic;
1096
+ }
1097
+
1098
+ // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
1099
+ function fromObservable(observableCreator) {
1100
+ const nextEventType = '$$xstate.next';
1101
+ const errorEventType = '$$xstate.error';
1102
+ const completeEventType = '$$xstate.complete';
1103
+
1104
+ // TODO: add event types
1105
+ const logic = {
1106
+ config: observableCreator,
1107
+ transition: (state, event, {
1108
+ self,
1109
+ id,
1110
+ defer
1111
+ }) => {
1112
+ if (state.status !== 'active') {
1113
+ return state;
1114
+ }
1115
+ switch (event.type) {
1116
+ case nextEventType:
1117
+ // match the exact timing of events sent by machines
1118
+ // send actions are not executed immediately
1119
+ defer(() => {
1120
+ self._parent?.send({
1121
+ type: `xstate.snapshot.${id}`,
1122
+ data: event.data
1123
+ });
1124
+ });
1125
+ return {
1126
+ ...state,
1127
+ data: event.data
1128
+ };
1129
+ case errorEventType:
1130
+ return {
1131
+ ...state,
1132
+ status: 'error',
1133
+ input: undefined,
1134
+ data: event.data,
1135
+ subscription: undefined
1136
+ };
1137
+ case completeEventType:
1138
+ return {
1139
+ ...state,
1140
+ status: 'done',
1141
+ input: undefined,
1142
+ subscription: undefined
1143
+ };
1144
+ case stopSignalType:
1145
+ state.subscription.unsubscribe();
1146
+ return {
1147
+ ...state,
1148
+ status: 'canceled',
1149
+ input: undefined,
1150
+ subscription: undefined
1151
+ };
1152
+ default:
1153
+ return state;
1154
+ }
1155
+ },
1156
+ getInitialState: (_, input) => {
1157
+ return {
1158
+ subscription: undefined,
1159
+ status: 'active',
1160
+ data: undefined,
1161
+ input
1162
+ };
1163
+ },
1164
+ start: (state, {
1165
+ self,
1166
+ system
1167
+ }) => {
1168
+ if (state.status === 'done') {
1169
+ // Do not restart a completed observable
1170
+ return;
1171
+ }
1172
+ state.subscription = observableCreator({
1173
+ input: state.input,
1174
+ system
1175
+ }).subscribe({
1176
+ next: value => {
1177
+ self.send({
1178
+ type: nextEventType,
1179
+ data: value
1180
+ });
1181
+ },
1182
+ error: err => {
1183
+ self.send({
1184
+ type: errorEventType,
1185
+ data: err
1186
+ });
1187
+ },
1188
+ complete: () => {
1189
+ self.send({
1190
+ type: completeEventType
1191
+ });
1192
+ }
1193
+ });
1194
+ },
1195
+ getSnapshot: state => state.data,
1196
+ getPersistedState: ({
1197
+ status,
1198
+ data,
1199
+ input
1200
+ }) => ({
1201
+ status,
1202
+ data,
1203
+ input
1204
+ }),
1205
+ getStatus: state => state,
1206
+ restoreState: state => ({
1207
+ ...state,
1208
+ subscription: undefined
1209
+ })
1210
+ };
1211
+ return logic;
1212
+ }
1213
+
1214
+ /**
1215
+ * Creates event observable logic that listens to an observable
1216
+ * that delivers event objects.
1217
+ *
1218
+ *
1219
+ * @param lazyObservable A function that creates an observable
1220
+ * @returns Event observable logic
1221
+ */
1222
+
1223
+ function fromEventObservable(lazyObservable) {
1224
+ const errorEventType = '$$xstate.error';
1225
+ const completeEventType = '$$xstate.complete';
1226
+
1227
+ // TODO: event types
1228
+ const logic = {
1229
+ config: lazyObservable,
1230
+ transition: (state, event) => {
1231
+ if (state.status !== 'active') {
1232
+ return state;
1233
+ }
1234
+ switch (event.type) {
1235
+ case errorEventType:
1236
+ return {
1237
+ ...state,
1238
+ status: 'error',
1239
+ input: undefined,
1240
+ data: event.data,
1241
+ subscription: undefined
1242
+ };
1243
+ case completeEventType:
1244
+ return {
1245
+ ...state,
1246
+ status: 'done',
1247
+ input: undefined,
1248
+ subscription: undefined
1249
+ };
1250
+ case stopSignalType:
1251
+ state.subscription.unsubscribe();
1252
+ return {
1253
+ ...state,
1254
+ status: 'canceled',
1255
+ input: undefined,
1256
+ subscription: undefined
1257
+ };
1258
+ default:
1259
+ return state;
1260
+ }
1261
+ },
1262
+ getInitialState: (_, input) => {
1263
+ return {
1264
+ subscription: undefined,
1265
+ status: 'active',
1266
+ data: undefined,
1267
+ input
1268
+ };
1269
+ },
1270
+ start: (state, {
1271
+ self,
1272
+ system
1273
+ }) => {
1274
+ if (state.status === 'done') {
1275
+ // Do not restart a completed observable
1276
+ return;
1277
+ }
1278
+ state.subscription = lazyObservable({
1279
+ input: state.input,
1280
+ system
1281
+ }).subscribe({
1282
+ next: value => {
1283
+ self._parent?.send(value);
1284
+ },
1285
+ error: err => {
1286
+ self.send({
1287
+ type: errorEventType,
1288
+ data: err
1289
+ });
1290
+ },
1291
+ complete: () => {
1292
+ self.send({
1293
+ type: completeEventType
1294
+ });
1295
+ }
1296
+ });
1297
+ },
1298
+ getSnapshot: _ => undefined,
1299
+ getPersistedState: ({
1300
+ status,
1301
+ data,
1302
+ input
1303
+ }) => ({
1304
+ status,
1305
+ data,
1306
+ input
1307
+ }),
1308
+ getStatus: state => state,
1309
+ restoreState: state => ({
1310
+ ...state,
1311
+ subscription: undefined
1312
+ })
1313
+ };
1314
+ return logic;
1315
+ }
1316
+
1317
+ function fromCallback(invokeCallback) {
1318
+ const logic = {
1319
+ config: invokeCallback,
1320
+ start: (_state, {
1321
+ self
1322
+ }) => {
1323
+ self.send({
1324
+ type: startSignalType
1325
+ });
1326
+ },
1327
+ transition: (state, event, {
1328
+ self,
1329
+ id,
1330
+ system
1331
+ }) => {
1332
+ if (event.type === startSignalType) {
1333
+ const sender = eventForParent => {
1334
+ if (state.canceled) {
1335
+ return;
1336
+ }
1337
+ self._parent?.send(eventForParent);
1338
+ };
1339
+ const receiver = newListener => {
1340
+ state.receivers.add(newListener);
1341
+ };
1342
+ state.dispose = invokeCallback(sender, receiver, {
1343
+ input: state.input,
1344
+ system
1345
+ });
1346
+ if (isPromiseLike(state.dispose)) {
1347
+ state.dispose.then(resolved => {
1348
+ self._parent?.send(doneInvoke(id, resolved));
1349
+ state.canceled = true;
1350
+ }, errorData => {
1351
+ state.canceled = true;
1352
+ self._parent?.send(error(id, errorData));
1353
+ });
1354
+ }
1355
+ return state;
1356
+ }
1357
+ if (event.type === stopSignalType) {
1358
+ state.canceled = true;
1359
+ if (isFunction(state.dispose)) {
1360
+ state.dispose();
1361
+ }
1362
+ return state;
1331
1363
  }
1332
- }, sendAction.params.delay);
1333
- }
1334
-
1335
- // TODO: make private (and figure out a way to do this within the machine)
1336
- cancel(sendId) {
1337
- this.clock.clearTimeout(this.delayedEventsMap[sendId]);
1338
- delete this.delayedEventsMap[sendId];
1339
- }
1340
- attachDevTools() {
1341
- const {
1342
- devTools
1343
- } = this.options;
1344
- if (devTools) {
1345
- const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
1346
- resolvedDevToolsAdapter(this);
1347
- }
1348
- }
1349
- toJSON() {
1350
- return {
1351
- id: this.id
1352
- };
1353
- }
1354
- getPersistedState() {
1355
- return this.behavior.getPersistedState?.(this._state);
1356
- }
1357
- [symbolObservable]() {
1358
- return this;
1359
- }
1360
- getSnapshot() {
1361
- return this.behavior.getSnapshot ? this.behavior.getSnapshot(this._state) : this._state;
1362
- }
1364
+ if (isSignal(event.type)) {
1365
+ // TODO: unrecognized signal
1366
+ return state;
1367
+ }
1368
+ if (!isSignal(event.type)) {
1369
+ state.receivers.forEach(receiver => receiver(event));
1370
+ }
1371
+ return state;
1372
+ },
1373
+ getInitialState: (_, input) => {
1374
+ return {
1375
+ canceled: false,
1376
+ receivers: new Set(),
1377
+ dispose: undefined,
1378
+ input
1379
+ };
1380
+ },
1381
+ getSnapshot: () => undefined,
1382
+ getPersistedState: ({
1383
+ input
1384
+ }) => input
1385
+ };
1386
+ return logic;
1363
1387
  }
1364
1388
 
1389
+ const startSignalType = 'xstate.init';
1390
+ const stopSignalType = 'xstate.stop';
1391
+ const startSignal = {
1392
+ type: 'xstate.init'
1393
+ };
1394
+ const stopSignal = {
1395
+ type: 'xstate.stop'
1396
+ };
1365
1397
  /**
1366
- * Creates a new Interpreter instance for the given machine with the provided options, if any.
1398
+ * An object that expresses the actor logic in reaction to received events,
1399
+ * as well as an optionally emitted stream of values.
1367
1400
  *
1368
- * @param machine The machine to interpret
1369
- * @param options Interpreter options
1401
+ * @template TReceived The received event
1402
+ * @template TSnapshot The emitted value
1370
1403
  */
1371
1404
 
1372
- function interpret(behavior, options) {
1373
- const interpreter = new Interpreter(behavior, options);
1374
- return interpreter;
1405
+ function isSignal(eventType) {
1406
+ return eventType === startSignalType || eventType === stopSignalType;
1375
1407
  }
1376
-
1377
- /**
1378
- * Stops an actor.
1379
- *
1380
- * @param actorRef The actor to stop.
1381
- */
1382
-
1383
- function stop(actorRef) {
1384
- const actor = actorRef;
1385
- return createDynamicAction({
1386
- type: stop$1,
1387
- params: {
1388
- actor
1389
- }
1390
- }, (event, {
1391
- state
1392
- }) => {
1393
- const actorRefOrString = isFunction(actor) ? actor({
1394
- context: state.context,
1395
- event
1396
- }) : actor;
1397
- const actorRef = typeof actorRefOrString === 'string' ? state.children[actorRefOrString] : actorRefOrString;
1398
- return [state, {
1399
- type: 'xstate.stop',
1400
- params: {
1401
- actor: actorRef
1402
- },
1403
- execute: actorCtx => {
1404
- if (!actorRef) {
1405
- return;
1406
- }
1407
- if (actorRef.status !== ActorStatus.Running) {
1408
- actorCtx.stopChild(actorRef);
1409
- return;
1410
- }
1411
- actorCtx.defer(() => {
1412
- actorCtx.stopChild(actorRef);
1413
- });
1414
- }
1415
- }];
1416
- });
1408
+ function isActorRef(item) {
1409
+ return !!item && typeof item === 'object' && typeof item.send === 'function';
1417
1410
  }
1418
1411
 
1419
- const defaultLogExpr = ({
1420
- context,
1421
- event
1422
- }) => ({
1423
- context,
1424
- event
1425
- });
1426
-
1427
- /**
1428
- *
1429
- * @param expr The expression function to evaluate which will be logged.
1430
- * Takes in 2 arguments:
1431
- * - `ctx` - the current state context
1432
- * - `event` - the event that caused this action to be executed.
1433
- * @param label The label to give to the logged expression.
1434
- */
1412
+ // TODO: refactor the return type, this could be written in a better way
1413
+ // but it's best to avoid unneccessary breaking changes now
1414
+ // @deprecated use `interpret(actorLogic)` instead
1415
+ function toActorRef(actorRefLike) {
1416
+ return {
1417
+ subscribe: () => ({
1418
+ unsubscribe: () => void 0
1419
+ }),
1420
+ id: 'anonymous',
1421
+ sessionId: '',
1422
+ getSnapshot: () => undefined,
1423
+ [symbolObservable]: function () {
1424
+ return this;
1425
+ },
1426
+ status: ActorStatus.Running,
1427
+ stop: () => void 0,
1428
+ ...actorRefLike
1429
+ };
1430
+ }
1431
+ const emptyLogic = fromTransition(_ => undefined, undefined);
1432
+ function createEmptyActor() {
1433
+ return interpret(emptyLogic);
1434
+ }
1435
1435
 
1436
- function log(expr = defaultLogExpr, label) {
1436
+ function invoke(invokeDef) {
1437
1437
  return createDynamicAction({
1438
- type: log$1,
1439
- params: {
1440
- label,
1441
- expr
1442
- }
1438
+ type: invoke$1,
1439
+ params: invokeDef
1443
1440
  }, (event, {
1444
1441
  state,
1445
1442
  actorContext
1446
1443
  }) => {
1447
- const resolvedValue = typeof expr === 'function' ? expr({
1448
- context: state.context,
1449
- event,
1450
- self: actorContext?.self ?? {},
1451
- system: actorContext?.system
1452
- }) : expr;
1453
- return [state, {
1454
- type: 'xstate.log',
1455
- params: {
1456
- label,
1457
- value: resolvedValue
1458
- },
1459
- execute: actorCtx => {
1460
- if (label) {
1461
- actorCtx.logger?.(label, resolvedValue);
1462
- } else {
1463
- actorCtx.logger?.(resolvedValue);
1444
+ const type = invoke$1;
1445
+ const {
1446
+ id,
1447
+ src
1448
+ } = invokeDef;
1449
+ let resolvedInvokeAction;
1450
+ if (isActorRef(src)) {
1451
+ resolvedInvokeAction = {
1452
+ type,
1453
+ params: {
1454
+ ...invokeDef,
1455
+ ref: src
1464
1456
  }
1465
- }
1466
- }];
1467
- });
1468
- }
1469
-
1470
- /**
1471
- * Cancels an in-flight `send(...)` action. A canceled sent action will not
1472
- * be executed, nor will its event be sent, unless it has already been sent
1473
- * (e.g., if `cancel(...)` is called after the `send(...)` action's `delay`).
1474
- *
1475
- * @param sendId The `id` of the `send(...)` action to cancel.
1476
- */
1477
-
1478
- function cancel(sendId) {
1479
- return createDynamicAction({
1480
- type: cancel$1,
1481
- params: {
1482
- sendId
1457
+ };
1458
+ } else {
1459
+ const referenced = resolveReferencedActor(state.machine.options.actors[src]);
1460
+ if (!referenced) {
1461
+ resolvedInvokeAction = {
1462
+ type,
1463
+ params: invokeDef
1464
+ };
1465
+ } else {
1466
+ const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
1467
+ const ref = interpret(referenced.src, {
1468
+ id,
1469
+ src,
1470
+ parent: actorContext?.self,
1471
+ systemId: invokeDef.systemId,
1472
+ input: typeof input === 'function' ? input({
1473
+ context: state.context,
1474
+ event,
1475
+ self: actorContext?.self
1476
+ }) : input
1477
+ });
1478
+ resolvedInvokeAction = {
1479
+ type,
1480
+ params: {
1481
+ ...invokeDef,
1482
+ ref
1483
+ }
1484
+ };
1485
+ }
1483
1486
  }
1484
- }, (event, {
1485
- state,
1486
- actorContext
1487
- }) => {
1488
- const resolvedSendId = isFunction(sendId) ? sendId({
1489
- context: state.context,
1490
- event,
1491
- self: actorContext?.self ?? {},
1492
- system: actorContext?.system
1493
- }) : sendId;
1494
- return [state, {
1495
- type: 'xstate.cancel',
1496
- params: {
1497
- sendId: resolvedSendId
1498
- },
1499
- execute: actorCtx => {
1500
- const interpreter = actorCtx.self;
1501
- interpreter.cancel(resolvedSendId);
1487
+ const actorRef = resolvedInvokeAction.params.ref;
1488
+ const invokedState = cloneState(state, {
1489
+ children: {
1490
+ ...state.children,
1491
+ [id]: actorRef
1502
1492
  }
1503
- }];
1504
- });
1505
- }
1506
-
1507
- const cache = new WeakMap();
1508
- function memo(object, key, fn) {
1509
- let memoizedData = cache.get(object);
1510
- if (!memoizedData) {
1511
- memoizedData = {
1512
- [key]: fn()
1493
+ });
1494
+ resolvedInvokeAction.execute = actorCtx => {
1495
+ const parent = actorCtx.self;
1496
+ const {
1497
+ id,
1498
+ ref
1499
+ } = resolvedInvokeAction.params;
1500
+ if (!ref) {
1501
+ {
1502
+ console.warn(`Actor type '${resolvedInvokeAction.params.src}' not found in machine '${actorCtx.id}'.`);
1503
+ }
1504
+ return;
1505
+ }
1506
+ actorCtx.defer(() => {
1507
+ if (actorRef.status === ActorStatus.Stopped) {
1508
+ return;
1509
+ }
1510
+ try {
1511
+ actorRef.start?.();
1512
+ } catch (err) {
1513
+ parent.send(error(id, err));
1514
+ return;
1515
+ }
1516
+ });
1513
1517
  };
1514
- cache.set(object, memoizedData);
1515
- } else if (!(key in memoizedData)) {
1516
- memoizedData[key] = fn();
1517
- }
1518
- return memoizedData[key];
1518
+ return [invokedState, resolvedInvokeAction];
1519
+ });
1519
1520
  }
1520
1521
 
1521
1522
  function stateIn(stateValue) {
@@ -2258,15 +2259,11 @@ function microstep(transitions, currentState, actorCtx, event) {
2258
2259
  const willTransition = currentState._initial || transitions.length > 0;
2259
2260
  const mutConfiguration = new Set(currentState.configuration);
2260
2261
  if (!currentState._initial && !willTransition) {
2261
- const inertState = cloneState(currentState, {
2262
- event,
2263
- actions: [],
2264
- transitions: []
2265
- });
2262
+ const inertState = cloneState(currentState, {});
2266
2263
  inertState.changed = false;
2267
2264
  return inertState;
2268
2265
  }
2269
- const microstate = microstepProcedure(currentState._initial ? [{
2266
+ const [microstate, actions] = microstepProcedure(currentState._initial ? [{
2270
2267
  target: [...currentState.configuration].filter(isAtomicStateNode),
2271
2268
  source: machine.root,
2272
2269
  reenter: true,
@@ -2275,38 +2272,16 @@ function microstep(transitions, currentState, actorCtx, event) {
2275
2272
  toJSON: null // TODO: fix
2276
2273
  }] : transitions, currentState, mutConfiguration, event, actorCtx);
2277
2274
  const {
2278
- context,
2279
- actions: nonRaisedActions
2275
+ context
2280
2276
  } = microstate;
2281
- const children = setChildren(currentState, nonRaisedActions);
2282
2277
  const nextState = cloneState(microstate, {
2283
2278
  value: {},
2284
2279
  // TODO: make optional
2285
- transitions,
2286
- children
2280
+ transitions
2287
2281
  });
2288
- nextState.changed = currentState._initial ? undefined : !stateValuesEqual(nextState.value, currentState.value) || nextState.actions.length > 0 || context !== currentState.context;
2282
+ nextState.changed = currentState._initial ? undefined : !stateValuesEqual(nextState.value, currentState.value) || actions.length > 0 || context !== currentState.context;
2289
2283
  return nextState;
2290
2284
  }
2291
- function setChildren(currentState, nonRaisedActions) {
2292
- const children = {
2293
- ...currentState.children
2294
- };
2295
- for (const action of nonRaisedActions) {
2296
- if (action.type === invoke$1 && action.params.ref) {
2297
- const ref = action.params.ref;
2298
- if (ref) {
2299
- children[ref.id] = ref;
2300
- }
2301
- } else if (action.type === stop$1) {
2302
- const ref = action.params.actor;
2303
- if (ref) {
2304
- delete children[ref.id];
2305
- }
2306
- }
2307
- }
2308
- return children;
2309
- }
2310
2285
  function microstepProcedure(transitions, currentState, mutConfiguration, event, actorCtx) {
2311
2286
  const actions = [];
2312
2287
  const historyValue = {
@@ -2324,7 +2299,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2324
2299
  actions.push(...filteredTransitions.flatMap(t => t.actions));
2325
2300
 
2326
2301
  // Enter states
2327
- enterStates(filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue);
2302
+ enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue);
2328
2303
  const nextConfiguration = [...mutConfiguration];
2329
2304
  const done = isInFinalState(nextConfiguration);
2330
2305
  if (done) {
@@ -2332,29 +2307,25 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2332
2307
  actions.push(...finalActions);
2333
2308
  }
2334
2309
  try {
2335
- const {
2336
- nextState
2337
- } = resolveActionsAndContext(actions, event, currentState, actorCtx);
2310
+ const [nextState, resolvedActions] = resolveActionsAndContext(actions, event, currentState, actorCtx);
2338
2311
  const output = done ? getOutput(nextConfiguration, nextState.context, event) : undefined;
2339
2312
  internalQueue.push(...nextState._internalQueue);
2340
- return cloneState(currentState, {
2341
- actions: nextState.actions,
2313
+ return [cloneState(currentState, {
2342
2314
  configuration: nextConfiguration,
2343
2315
  historyValue,
2344
2316
  _internalQueue: internalQueue,
2345
2317
  context: nextState.context,
2346
- event,
2347
2318
  done,
2348
2319
  output,
2349
2320
  children: nextState.children
2350
- });
2321
+ }), resolvedActions];
2351
2322
  } catch (e) {
2352
2323
  // TODO: Refactor this once proper error handling is implemented.
2353
2324
  // See https://github.com/statelyai/rfcs/pull/4
2354
2325
  throw e;
2355
2326
  }
2356
2327
  }
2357
- function enterStates(filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue) {
2328
+ function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue) {
2358
2329
  const statesToEnter = new Set();
2359
2330
  const statesForDefaultEntry = new Set();
2360
2331
  computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
@@ -2382,7 +2353,7 @@ function enterStates(filteredTransitions, mutConfiguration, actions, internalQue
2382
2353
  if (!parent.parent) {
2383
2354
  continue;
2384
2355
  }
2385
- internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, currentState.event) : undefined));
2356
+ internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, event) : undefined));
2386
2357
  if (parent.parent) {
2387
2358
  const grandparent = parent.parent;
2388
2359
  if (grandparent.type === 'parallel') {
@@ -2500,8 +2471,8 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
2500
2471
  resolvedActions.push(action);
2501
2472
  if (actorCtx?.self.status === ActorStatus.Running) {
2502
2473
  action.execute?.(actorCtx);
2503
- // TODO: this is hacky; re-evaluate
2504
- delete action.execute;
2474
+ } else {
2475
+ actorCtx?.defer(() => action.execute?.(actorCtx));
2505
2476
  }
2506
2477
  }
2507
2478
  function resolveAction(actionObject) {
@@ -2530,12 +2501,9 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
2530
2501
  for (const actionObject of actions) {
2531
2502
  resolveAction(actionObject);
2532
2503
  }
2533
- return {
2534
- nextState: cloneState(intermediateState, {
2535
- actions: resolvedActions,
2536
- _internalQueue: raiseActions.map(a => a.params.event)
2537
- })
2538
- };
2504
+ return [cloneState(intermediateState, {
2505
+ _internalQueue: raiseActions.map(a => a.params.event)
2506
+ }), resolvedActions];
2539
2507
  }
2540
2508
  function macrostep(state, event, actorCtx) {
2541
2509
  if (event.type === WILDCARD) {
@@ -2546,54 +2514,42 @@ function macrostep(state, event, actorCtx) {
2546
2514
 
2547
2515
  // Handle stop event
2548
2516
  if (event.type === stopSignalType) {
2549
- nextState = stopStep(event, nextState, actorCtx);
2517
+ nextState = stopStep(event, nextState, actorCtx)[0];
2550
2518
  states.push(nextState);
2551
2519
  return {
2552
2520
  state: nextState,
2553
2521
  microstates: states
2554
2522
  };
2555
2523
  }
2524
+ let nextEvent = event;
2556
2525
 
2557
2526
  // Assume the state is at rest (no raised events)
2558
2527
  // Determine the next state based on the next microstep
2559
- if (event.type !== init) {
2560
- const transitions = selectTransitions(event, nextState);
2561
- nextState = microstep(transitions, state, actorCtx, event);
2528
+ if (nextEvent.type !== init) {
2529
+ const transitions = selectTransitions(nextEvent, nextState);
2530
+ nextState = microstep(transitions, state, actorCtx, nextEvent);
2562
2531
  states.push(nextState);
2563
2532
  }
2564
2533
  while (!nextState.done) {
2565
- let enabledTransitions = selectEventlessTransitions(nextState);
2566
- if (enabledTransitions.length === 0) {
2567
- // TODO: this is a bit of a hack, we need to review this
2568
- // this matches the behavior from v4 for eventless transitions
2569
- // where for `hasAlwaysTransitions` we were always trying to resolve with a NULL event
2570
- // and if a transition was not selected the `state.transitions` stayed empty
2571
- // without this we get into an infinite loop in the dieHard test in `@xstate/test` for the `simplePathsTo`
2572
- if (nextState.configuration.some(state => state.always)) {
2573
- nextState.transitions = [];
2574
- }
2534
+ let enabledTransitions = selectEventlessTransitions(nextState, nextEvent);
2535
+ if (!enabledTransitions.length) {
2575
2536
  if (!nextState._internalQueue.length) {
2576
2537
  break;
2577
2538
  } else {
2578
- const currentActions = nextState.actions;
2579
- const nextEvent = nextState._internalQueue[0];
2539
+ nextEvent = nextState._internalQueue[0];
2580
2540
  const transitions = selectTransitions(nextEvent, nextState);
2581
2541
  nextState = microstep(transitions, nextState, actorCtx, nextEvent);
2582
2542
  nextState._internalQueue.shift();
2583
- nextState.actions.unshift(...currentActions);
2584
2543
  states.push(nextState);
2585
2544
  }
2586
- }
2587
- if (enabledTransitions.length) {
2588
- const currentActions = nextState.actions;
2589
- nextState = microstep(enabledTransitions, nextState, actorCtx, nextState.event);
2590
- nextState.actions.unshift(...currentActions);
2545
+ } else {
2546
+ nextState = microstep(enabledTransitions, nextState, actorCtx, nextEvent);
2591
2547
  states.push(nextState);
2592
2548
  }
2593
2549
  }
2594
2550
  if (nextState.done) {
2595
2551
  // Perform the stop step to ensure that child actors are stopped
2596
- stopStep(nextState.event, nextState, actorCtx);
2552
+ stopStep(nextEvent, nextState, actorCtx);
2597
2553
  }
2598
2554
  return {
2599
2555
  state: nextState,
@@ -2608,15 +2564,12 @@ function stopStep(event, nextState, actorCtx) {
2608
2564
  for (const child of Object.values(nextState.children)) {
2609
2565
  actions.push(stop(child));
2610
2566
  }
2611
- const {
2612
- nextState: stoppedState
2613
- } = resolveActionsAndContext(actions, event, nextState, actorCtx);
2614
- return stoppedState;
2567
+ return resolveActionsAndContext(actions, event, nextState, actorCtx);
2615
2568
  }
2616
2569
  function selectTransitions(event, nextState) {
2617
2570
  return nextState.machine.getTransitionData(nextState, event);
2618
2571
  }
2619
- function selectEventlessTransitions(nextState) {
2572
+ function selectEventlessTransitions(nextState, event) {
2620
2573
  const enabledTransitionSet = new Set();
2621
2574
  const atomicStates = nextState.configuration.filter(isAtomicStateNode);
2622
2575
  for (const stateNode of atomicStates) {
@@ -2625,7 +2578,7 @@ function selectEventlessTransitions(nextState) {
2625
2578
  continue;
2626
2579
  }
2627
2580
  for (const transition of s.always) {
2628
- if (transition.guard === undefined || evaluateGuard(transition.guard, nextState.context, nextState.event, nextState)) {
2581
+ if (transition.guard === undefined || evaluateGuard(transition.guard, nextState.context, event, nextState)) {
2629
2582
  enabledTransitionSet.add(transition);
2630
2583
  break loop;
2631
2584
  }
@@ -2693,10 +2646,6 @@ class State {
2693
2646
  * The enabled state nodes representative of the state value.
2694
2647
  */
2695
2648
 
2696
- /**
2697
- * The transition definitions that resulted in this state.
2698
- */
2699
-
2700
2649
  /**
2701
2650
  * An object mapping actor names to spawned/invoked actors.
2702
2651
  */
@@ -2712,8 +2661,6 @@ class State {
2712
2661
  return new State({
2713
2662
  value: stateValue.value,
2714
2663
  context,
2715
- event: stateValue.event,
2716
- actions: [],
2717
2664
  meta: {},
2718
2665
  configuration: [],
2719
2666
  // TODO: fix,
@@ -2723,17 +2670,12 @@ class State {
2723
2670
  }
2724
2671
  return stateValue;
2725
2672
  }
2726
- const event = createInitEvent({}); // TODO: fix
2727
-
2728
2673
  const configuration = getConfiguration(getStateNodes(machine.root, stateValue));
2729
2674
  return new State({
2730
2675
  value: stateValue,
2731
2676
  context,
2732
- event,
2733
- actions: [],
2734
2677
  meta: undefined,
2735
2678
  configuration: Array.from(configuration),
2736
- transitions: [],
2737
2679
  children: {}
2738
2680
  }, machine);
2739
2681
  }
@@ -2751,23 +2693,17 @@ class State {
2751
2693
  this.output = void 0;
2752
2694
  this.context = void 0;
2753
2695
  this.historyValue = {};
2754
- this.actions = [];
2755
- this.event = void 0;
2756
2696
  this._internalQueue = void 0;
2757
2697
  this._initial = false;
2758
2698
  this.changed = void 0;
2759
2699
  this.configuration = void 0;
2760
- this.transitions = void 0;
2761
2700
  this.children = void 0;
2762
2701
  this.context = config.context;
2763
2702
  this._internalQueue = config._internalQueue ?? [];
2764
- this.event = config.event;
2765
2703
  this.historyValue = config.historyValue || {};
2766
- this.actions = config.actions ?? [];
2767
2704
  this.matches = this.matches.bind(this);
2768
2705
  this.toStrings = this.toStrings.bind(this);
2769
2706
  this.configuration = config.configuration ?? Array.from(getConfiguration(getStateNodes(machine.root, config.value)));
2770
- this.transitions = config.transitions;
2771
2707
  this.children = config.children;
2772
2708
  this.value = getStateValue(machine.root, this.configuration);
2773
2709
  this.tags = new Set(flatten(this.configuration.map(sn => sn.tags)));
@@ -2790,7 +2726,6 @@ class State {
2790
2726
  toJSON() {
2791
2727
  const {
2792
2728
  configuration,
2793
- transitions,
2794
2729
  tags,
2795
2730
  machine,
2796
2731
  ...jsonValues
@@ -2862,7 +2797,6 @@ function cloneState(state, config = {}) {
2862
2797
  function getPersistedState(state) {
2863
2798
  const {
2864
2799
  configuration,
2865
- transitions,
2866
2800
  tags,
2867
2801
  machine,
2868
2802
  children,
@@ -2881,89 +2815,105 @@ function getPersistedState(state) {
2881
2815
  };
2882
2816
  }
2883
2817
 
2884
- function invoke(invokeDef) {
2818
+ /**
2819
+ * Stops an actor.
2820
+ *
2821
+ * @param actorRef The actor to stop.
2822
+ */
2823
+
2824
+ function stop(actorRef) {
2825
+ const actor = actorRef;
2885
2826
  return createDynamicAction({
2886
- type: invoke$1,
2887
- params: invokeDef
2827
+ type: stop$1,
2828
+ params: {
2829
+ actor
2830
+ }
2888
2831
  }, (event, {
2889
- state,
2890
- actorContext
2832
+ state
2891
2833
  }) => {
2892
- const type = invoke$1;
2893
- const {
2894
- id,
2895
- src
2896
- } = invokeDef;
2897
- let resolvedInvokeAction;
2898
- if (isActorRef(src)) {
2899
- resolvedInvokeAction = {
2900
- type,
2901
- params: {
2902
- ...invokeDef,
2903
- ref: src
2904
- }
2834
+ const actorRefOrString = isFunction(actor) ? actor({
2835
+ context: state.context,
2836
+ event
2837
+ }) : actor;
2838
+ const actorRef = typeof actorRefOrString === 'string' ? state.children[actorRefOrString] : actorRefOrString;
2839
+ let children = state.children;
2840
+ if (actorRef) {
2841
+ children = {
2842
+ ...children
2905
2843
  };
2906
- } else {
2907
- const referenced = resolveReferencedActor(state.machine.options.actors[src]);
2908
- if (!referenced) {
2909
- resolvedInvokeAction = {
2910
- type,
2911
- params: invokeDef
2912
- };
2913
- } else {
2914
- const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
2915
- const ref = interpret(referenced.src, {
2916
- id,
2917
- src,
2918
- parent: actorContext?.self,
2919
- systemId: invokeDef.systemId,
2920
- input: typeof input === 'function' ? input({
2921
- context: state.context,
2922
- event,
2923
- self: actorContext?.self
2924
- }) : input
2925
- });
2926
- resolvedInvokeAction = {
2927
- type,
2928
- params: {
2929
- ...invokeDef,
2930
- ref
2931
- }
2932
- };
2933
- }
2844
+ delete children[actorRef.id];
2934
2845
  }
2935
- const actorRef = resolvedInvokeAction.params.ref;
2936
- const invokedState = cloneState(state, {
2937
- children: {
2938
- ...state.children,
2939
- [id]: actorRef
2940
- }
2941
- });
2942
- resolvedInvokeAction.execute = actorCtx => {
2943
- const parent = actorCtx.self;
2944
- const {
2945
- id,
2946
- ref
2947
- } = resolvedInvokeAction.params;
2948
- if (!ref) {
2949
- {
2950
- console.warn(`Actor type '${resolvedInvokeAction.params.src}' not found in machine '${actorCtx.id}'.`);
2951
- }
2952
- return;
2953
- }
2954
- actorCtx.defer(() => {
2955
- if (actorRef.status === ActorStatus.Stopped) {
2846
+ return [cloneState(state, {
2847
+ children
2848
+ }), {
2849
+ type: 'xstate.stop',
2850
+ params: {
2851
+ actor: actorRef
2852
+ },
2853
+ execute: actorCtx => {
2854
+ if (!actorRef) {
2956
2855
  return;
2957
2856
  }
2958
- try {
2959
- actorRef.start?.();
2960
- } catch (err) {
2961
- parent.send(error(id, err));
2857
+ if (actorRef.status !== ActorStatus.Running) {
2858
+ actorCtx.stopChild(actorRef);
2962
2859
  return;
2963
2860
  }
2964
- });
2965
- };
2966
- return [invokedState, resolvedInvokeAction];
2861
+ actorCtx.defer(() => {
2862
+ actorCtx.stopChild(actorRef);
2863
+ });
2864
+ }
2865
+ }];
2866
+ });
2867
+ }
2868
+
2869
+ const defaultLogExpr = ({
2870
+ context,
2871
+ event
2872
+ }) => ({
2873
+ context,
2874
+ event
2875
+ });
2876
+
2877
+ /**
2878
+ *
2879
+ * @param expr The expression function to evaluate which will be logged.
2880
+ * Takes in 2 arguments:
2881
+ * - `ctx` - the current state context
2882
+ * - `event` - the event that caused this action to be executed.
2883
+ * @param label The label to give to the logged expression.
2884
+ */
2885
+
2886
+ function log(expr = defaultLogExpr, label) {
2887
+ return createDynamicAction({
2888
+ type: log$1,
2889
+ params: {
2890
+ label,
2891
+ expr
2892
+ }
2893
+ }, (event, {
2894
+ state,
2895
+ actorContext
2896
+ }) => {
2897
+ const resolvedValue = typeof expr === 'function' ? expr({
2898
+ context: state.context,
2899
+ event,
2900
+ self: actorContext?.self ?? {},
2901
+ system: actorContext?.system
2902
+ }) : expr;
2903
+ return [state, {
2904
+ type: 'xstate.log',
2905
+ params: {
2906
+ label,
2907
+ value: resolvedValue
2908
+ },
2909
+ execute: actorCtx => {
2910
+ if (label) {
2911
+ actorCtx.logger?.(label, resolvedValue);
2912
+ } else {
2913
+ actorCtx.logger?.(resolvedValue);
2914
+ }
2915
+ }
2916
+ }];
2967
2917
  });
2968
2918
  }
2969
2919
 
@@ -3001,7 +2951,7 @@ function createSpawner(self, machine, context, event, mutCapturedActions) {
3001
2951
  return actorRef; // TODO: fix types
3002
2952
  }
3003
2953
 
3004
- throw new Error(`Behavior '${src}' not implemented in machine '${machine.id}'`);
2954
+ throw new Error(`Actor logic '${src}' not implemented in machine '${machine.id}'`);
3005
2955
  } else {
3006
2956
  // TODO: this should also receive `src`
3007
2957
  // TODO: instead of anonymous, it should be a unique stable ID