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