xstate 5.0.0-beta.15 → 5.0.0-beta.16

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 (39) hide show
  1. package/actions/dist/xstate-actions.cjs.js +1 -3
  2. package/actions/dist/xstate-actions.cjs.mjs +0 -2
  3. package/actions/dist/xstate-actions.development.cjs.js +1 -3
  4. package/actions/dist/xstate-actions.development.esm.js +1 -1
  5. package/actions/dist/xstate-actions.esm.js +1 -1
  6. package/actions/dist/xstate-actions.umd.min.js +1 -1
  7. package/actions/dist/xstate-actions.umd.min.js.map +1 -1
  8. package/actors/dist/xstate-actors.cjs.js +1 -1
  9. package/actors/dist/xstate-actors.development.cjs.js +1 -1
  10. package/actors/dist/xstate-actors.development.esm.js +1 -1
  11. package/actors/dist/xstate-actors.esm.js +1 -1
  12. package/actors/dist/xstate-actors.umd.min.js +1 -1
  13. package/actors/dist/xstate-actors.umd.min.js.map +1 -1
  14. package/dist/{actions-6884fae8.esm.js → actions-0386b622.esm.js} +707 -742
  15. package/dist/{actions-d71ac253.development.cjs.js → actions-0f903c0d.development.cjs.js} +836 -874
  16. package/dist/{actions-81cc7f2b.cjs.js → actions-6b9073db.cjs.js} +706 -744
  17. package/dist/{actions-98f362b9.development.esm.js → actions-6f7fbc84.development.esm.js} +837 -872
  18. package/dist/declarations/src/StateMachine.d.ts +2 -4
  19. package/dist/declarations/src/actionTypes.d.ts +1 -1
  20. package/dist/declarations/src/actions/assign.d.ts +2 -2
  21. package/dist/declarations/src/actions/cancel.d.ts +2 -2
  22. package/dist/declarations/src/actions/send.d.ts +12 -22
  23. package/dist/declarations/src/actions/stop.d.ts +2 -2
  24. package/dist/declarations/src/actions.d.ts +1 -4
  25. package/dist/declarations/src/interpreter.d.ts +2 -2
  26. package/dist/declarations/src/stateUtils.d.ts +1 -1
  27. package/dist/declarations/src/types.d.ts +29 -49
  28. package/dist/xstate.cjs.js +20 -34
  29. package/dist/xstate.development.cjs.js +20 -34
  30. package/dist/xstate.development.esm.js +21 -35
  31. package/dist/xstate.esm.js +21 -35
  32. package/dist/xstate.umd.min.js +1 -1
  33. package/dist/xstate.umd.min.js.map +1 -1
  34. package/guards/dist/xstate-guards.cjs.js +1 -1
  35. package/guards/dist/xstate-guards.development.cjs.js +1 -1
  36. package/guards/dist/xstate-guards.development.esm.js +1 -1
  37. package/guards/dist/xstate-guards.esm.js +1 -1
  38. package/guards/dist/xstate-guards.umd.min.js.map +1 -1
  39. package/package.json +1 -1
@@ -37,7 +37,7 @@ var dev_dist_xstateDev = require('../dev/dist/xstate-dev.development.cjs.js');
37
37
  let ActionTypes = /*#__PURE__*/function (ActionTypes) {
38
38
  ActionTypes["Stop"] = "xstate.stop";
39
39
  ActionTypes["Raise"] = "xstate.raise";
40
- ActionTypes["Send"] = "xstate.send";
40
+ ActionTypes["SendTo"] = "xstate.sendTo";
41
41
  ActionTypes["Cancel"] = "xstate.cancel";
42
42
  ActionTypes["Assign"] = "xstate.assign";
43
43
  ActionTypes["After"] = "xstate.after";
@@ -63,7 +63,7 @@ let SpecialTargets = /*#__PURE__*/function (SpecialTargets) {
63
63
  // xstate-specific action types
64
64
  const stop$1 = ActionTypes.Stop;
65
65
  const raise$1 = ActionTypes.Raise;
66
- const send$1 = ActionTypes.Send;
66
+ const sendTo$1 = ActionTypes.SendTo;
67
67
  const cancel$1 = ActionTypes.Cancel;
68
68
  const assign$1 = ActionTypes.Assign;
69
69
  const after$1 = ActionTypes.After;
@@ -81,7 +81,7 @@ var actionTypes = /*#__PURE__*/Object.freeze({
81
81
  __proto__: null,
82
82
  stop: stop$1,
83
83
  raise: raise$1,
84
- send: send$1,
84
+ sendTo: sendTo$1,
85
85
  cancel: cancel$1,
86
86
  assign: assign$1,
87
87
  after: after$1,
@@ -306,22 +306,19 @@ function isDynamicAction(action) {
306
306
  }
307
307
 
308
308
  /**
309
- * Sends an event. This returns an action that will be read by an interpreter to
310
- * send the event in the next step, after the current step is finished executing.
311
- *
312
- * @deprecated Use the `sendTo(...)` action creator instead.
309
+ * Sends an event to an actor.
313
310
  *
314
- * @param eventOrExpr The event to send.
315
- * @param options Options to pass into the send event:
311
+ * @param actor The `ActorRef` to send the event to.
312
+ * @param event The event to send, or an expression that evaluates to the event to send
313
+ * @param options Send action options
316
314
  * - `id` - The unique send event identifier (used with `cancel()`).
317
315
  * - `delay` - The number of milliseconds to delay the sending of the event.
318
- * - `to` - The target of this event (by default, the machine the event was sent from).
319
316
  */
320
- function send(eventOrExpr, options) {
317
+ function sendTo(actor, eventOrExpr, options) {
321
318
  return createDynamicAction({
322
- type: send$1,
319
+ type: sendTo$1,
323
320
  params: {
324
- to: options ? options.to : undefined,
321
+ to: actor,
325
322
  delay: options ? options.delay : undefined,
326
323
  event: eventOrExpr,
327
324
  id: options && options.id !== undefined ? options.id : isFunction(eventOrExpr) ? eventOrExpr.name : eventOrExpr.type
@@ -331,7 +328,7 @@ function send(eventOrExpr, options) {
331
328
  state
332
329
  }) => {
333
330
  const params = {
334
- to: options ? options.to : undefined,
331
+ to: actor,
335
332
  delay: options ? options.delay : undefined,
336
333
  event: eventOrExpr,
337
334
  // TODO: don't auto-generate IDs here like that
@@ -379,13 +376,12 @@ function send(eventOrExpr, options) {
379
376
  targetActorRef = resolvedTarget || actorContext?.self;
380
377
  }
381
378
  const resolvedAction = {
382
- type: send$1,
379
+ type: sendTo$1,
383
380
  params: {
384
381
  ...params,
385
382
  to: targetActorRef,
386
383
  event: resolvedEvent,
387
- delay: resolvedDelay,
388
- internal: resolvedTarget === SpecialTargets.Internal
384
+ delay: resolvedDelay
389
385
  },
390
386
  execute: actorCtx => {
391
387
  const sendAction = resolvedAction;
@@ -415,12 +411,8 @@ function send(eventOrExpr, options) {
415
411
  * @param options Options to pass into the send event.
416
412
  */
417
413
  function sendParent(event, options) {
418
- return send(event, {
419
- ...options,
420
- to: SpecialTargets.Parent
421
- });
414
+ return sendTo(SpecialTargets.Parent, event, options);
422
415
  }
423
-
424
416
  /**
425
417
  * Forwards (sends) an event to a specified service.
426
418
  *
@@ -438,12 +430,9 @@ function forwardTo(target, options) {
438
430
  return resolvedTarget;
439
431
  };
440
432
  }
441
- return send(({
433
+ return sendTo(target, ({
442
434
  event
443
- }) => event, {
444
- ...options,
445
- to: target
446
- });
435
+ }) => event, options);
447
436
  }
448
437
 
449
438
  /**
@@ -459,25 +448,7 @@ function escalate(errorData, options) {
459
448
  type: error$1,
460
449
  data: isFunction(errorData) ? errorData(arg) : errorData
461
450
  };
462
- }, {
463
- ...options,
464
- to: SpecialTargets.Parent
465
- });
466
- }
467
-
468
- /**
469
- * Sends an event to an actor.
470
- *
471
- * @param actor The `ActorRef` to send the event to.
472
- * @param event The event to send, or an expression that evaluates to the event to send
473
- * @param options Send action options
474
- * @returns An XState send action object
475
- */
476
- function sendTo(actor, event, options) {
477
- return send(event, {
478
- ...options,
479
- to: actor
480
- });
451
+ }, options);
481
452
  }
482
453
 
483
454
  const cache = new WeakMap();
@@ -531,8 +502,6 @@ function cancel(sendId) {
531
502
  });
532
503
  }
533
504
 
534
- const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
535
-
536
505
  class Mailbox {
537
506
  constructor(_process) {
538
507
  this._process = _process;
@@ -600,698 +569,317 @@ class Mailbox {
600
569
  }
601
570
  }
602
571
 
603
- function createSystem() {
604
- let sessionIdCounter = 0;
605
- const children = new Map();
606
- const keyedActors = new Map();
607
- const reverseKeyedActors = new WeakMap();
608
- const system = {
609
- _bookId: () => `x:${sessionIdCounter++}`,
610
- _register: (sessionId, actorRef) => {
611
- children.set(sessionId, actorRef);
612
- return sessionId;
613
- },
614
- _unregister: actorRef => {
615
- children.delete(actorRef.sessionId);
616
- const systemId = reverseKeyedActors.get(actorRef);
617
- if (systemId !== undefined) {
618
- keyedActors.delete(systemId);
619
- reverseKeyedActors.delete(actorRef);
620
- }
572
+ const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
573
+
574
+ /**
575
+ * Returns actor logic from a transition function and its initial state.
576
+ *
577
+ * A transition function is a function that takes the current state and an event and returns the next state.
578
+ *
579
+ * @param transition The transition function that returns the next state given the current state and event.
580
+ * @param initialState The initial state of the transition function.
581
+ * @returns Actor logic
582
+ */
583
+ function fromTransition(transition, initialState) {
584
+ const logic = {
585
+ config: transition,
586
+ transition: (state, event, actorContext) => {
587
+ return transition(state, event, actorContext);
621
588
  },
622
- get: systemId => {
623
- return keyedActors.get(systemId);
589
+ getInitialState: (_, input) => {
590
+ return typeof initialState === 'function' ? initialState({
591
+ input
592
+ }) : initialState;
624
593
  },
625
- _set: (systemId, actorRef) => {
626
- const existing = keyedActors.get(systemId);
627
- if (existing && existing !== actorRef) {
628
- throw new Error(`Actor with system ID '${systemId}' already exists.`);
629
- }
630
- keyedActors.set(systemId, actorRef);
631
- reverseKeyedActors.set(actorRef, systemId);
632
- }
594
+ getSnapshot: state => state,
595
+ getPersistedState: state => state,
596
+ restoreState: state => state
633
597
  };
634
- return system;
598
+ return logic;
635
599
  }
636
600
 
637
- let ActorStatus = /*#__PURE__*/function (ActorStatus) {
638
- ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
639
- ActorStatus[ActorStatus["Running"] = 1] = "Running";
640
- ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
641
- return ActorStatus;
642
- }({});
643
- const defaultOptions = {
644
- deferEvents: true,
645
- clock: {
646
- setTimeout: (fn, ms) => {
647
- return setTimeout(fn, ms);
601
+ const resolveEventType = '$$xstate.resolve';
602
+ const rejectEventType = '$$xstate.reject';
603
+ function fromPromise(
604
+ // TODO: add types
605
+ promiseCreator) {
606
+ // TODO: add event types, consider making the `PromiseEvent` a private type or smth alike
607
+ const logic = {
608
+ config: promiseCreator,
609
+ transition: (state, event) => {
610
+ if (state.status !== 'active') {
611
+ return state;
612
+ }
613
+ switch (event.type) {
614
+ case resolveEventType:
615
+ return {
616
+ ...state,
617
+ status: 'done',
618
+ data: event.data,
619
+ input: undefined
620
+ };
621
+ case rejectEventType:
622
+ return {
623
+ ...state,
624
+ status: 'error',
625
+ data: event.data,
626
+ input: undefined
627
+ };
628
+ case stopSignalType:
629
+ return {
630
+ ...state,
631
+ status: 'canceled',
632
+ input: undefined
633
+ };
634
+ default:
635
+ return state;
636
+ }
648
637
  },
649
- clearTimeout: id => {
650
- return clearTimeout(id);
651
- }
652
- },
653
- logger: console.log.bind(console),
654
- devTools: false
655
- };
656
- class Interpreter {
657
- /**
658
- * The current state of the interpreted logic.
659
- */
660
-
661
- /**
662
- * The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
663
- */
664
-
665
- /**
666
- * The unique identifier for this actor relative to its parent.
667
- */
668
-
669
- /**
670
- * Whether the service is started.
671
- */
672
-
673
- // Actor Ref
674
-
675
- // TODO: add typings for system
638
+ start: (state, {
639
+ self,
640
+ system
641
+ }) => {
642
+ // TODO: determine how to allow customizing this so that promises
643
+ // can be restarted if necessary
644
+ if (state.status !== 'active') {
645
+ return;
646
+ }
647
+ const resolvedPromise = Promise.resolve(promiseCreator({
648
+ input: state.input,
649
+ system
650
+ }));
651
+ resolvedPromise.then(response => {
652
+ // TODO: remove this condition once dead letter queue lands
653
+ if (self._state.status !== 'active') {
654
+ return;
655
+ }
656
+ self.send({
657
+ type: resolveEventType,
658
+ data: response
659
+ });
660
+ }, errorData => {
661
+ // TODO: remove this condition once dead letter queue lands
662
+ if (self._state.status !== 'active') {
663
+ return;
664
+ }
665
+ self.send({
666
+ type: rejectEventType,
667
+ data: errorData
668
+ });
669
+ });
670
+ },
671
+ getInitialState: (_, input) => {
672
+ return {
673
+ status: 'active',
674
+ data: undefined,
675
+ input
676
+ };
677
+ },
678
+ getSnapshot: state => state.data,
679
+ getStatus: state => state,
680
+ getPersistedState: state => state,
681
+ restoreState: state => state
682
+ };
683
+ return logic;
684
+ }
676
685
 
677
- /**
678
- * The globally unique process ID for this invocation.
679
- */
686
+ // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
687
+ function fromObservable(observableCreator) {
688
+ const nextEventType = '$$xstate.next';
689
+ const errorEventType = '$$xstate.error';
690
+ const completeEventType = '$$xstate.complete';
680
691
 
681
- /**
682
- * Creates a new Interpreter instance (i.e., service) for the given logic with the provided options, if any.
683
- *
684
- * @param logic The logic to be interpreted
685
- * @param options Interpreter options
686
- */
687
- constructor(logic, options) {
688
- this.logic = logic;
689
- this._state = void 0;
690
- this.clock = void 0;
691
- this.options = void 0;
692
- this.id = void 0;
693
- this.mailbox = new Mailbox(this._process.bind(this));
694
- this.delayedEventsMap = {};
695
- this.observers = new Set();
696
- this.logger = void 0;
697
- this.status = ActorStatus.NotStarted;
698
- this._parent = void 0;
699
- this.ref = void 0;
700
- this._actorContext = void 0;
701
- this._systemId = void 0;
702
- this.sessionId = void 0;
703
- this.system = void 0;
704
- this._doneEvent = void 0;
705
- this.src = void 0;
706
- this._deferred = [];
707
- const resolvedOptions = {
708
- ...defaultOptions,
709
- ...options
710
- };
711
- const {
712
- clock,
713
- logger,
714
- parent,
692
+ // TODO: add event types
693
+ const logic = {
694
+ config: observableCreator,
695
+ transition: (state, event, {
696
+ self,
715
697
  id,
716
- systemId
717
- } = resolvedOptions;
718
- const self = this;
719
- this.system = parent?.system ?? createSystem();
720
- if (systemId) {
721
- this._systemId = systemId;
722
- this.system._set(systemId, this);
723
- }
724
- this.sessionId = this.system._bookId();
725
- this.id = id ?? this.sessionId;
726
- this.logger = logger;
727
- this.clock = clock;
728
- this._parent = parent;
729
- this.options = resolvedOptions;
730
- this.src = resolvedOptions.src;
731
- this.ref = this;
732
- this._actorContext = {
698
+ defer
699
+ }) => {
700
+ if (state.status !== 'active') {
701
+ return state;
702
+ }
703
+ switch (event.type) {
704
+ case nextEventType:
705
+ // match the exact timing of events sent by machines
706
+ // send actions are not executed immediately
707
+ defer(() => {
708
+ self._parent?.send({
709
+ type: `xstate.snapshot.${id}`,
710
+ data: event.data
711
+ });
712
+ });
713
+ return {
714
+ ...state,
715
+ data: event.data
716
+ };
717
+ case errorEventType:
718
+ return {
719
+ ...state,
720
+ status: 'error',
721
+ input: undefined,
722
+ data: event.data,
723
+ subscription: undefined
724
+ };
725
+ case completeEventType:
726
+ return {
727
+ ...state,
728
+ status: 'done',
729
+ input: undefined,
730
+ subscription: undefined
731
+ };
732
+ case stopSignalType:
733
+ state.subscription.unsubscribe();
734
+ return {
735
+ ...state,
736
+ status: 'canceled',
737
+ input: undefined,
738
+ subscription: undefined
739
+ };
740
+ default:
741
+ return state;
742
+ }
743
+ },
744
+ getInitialState: (_, input) => {
745
+ return {
746
+ subscription: undefined,
747
+ status: 'active',
748
+ data: undefined,
749
+ input
750
+ };
751
+ },
752
+ start: (state, {
733
753
  self,
734
- id: this.id,
735
- sessionId: this.sessionId,
736
- logger: this.logger,
737
- defer: fn => {
738
- this._deferred.push(fn);
739
- },
740
- system: this.system,
741
- stopChild: child => {
742
- if (child._parent !== this) {
743
- throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
744
- }
745
- child._stop();
754
+ system
755
+ }) => {
756
+ if (state.status === 'done') {
757
+ // Do not restart a completed observable
758
+ return;
746
759
  }
747
- };
748
-
749
- // Ensure that the send method is bound to this interpreter instance
750
- // if destructured
751
- this.send = this.send.bind(this);
752
- this._initState();
753
- }
754
- _initState() {
755
- 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);
756
- }
757
-
758
- // array of functions to defer
759
-
760
- update(state) {
761
- // Update state
762
- this._state = state;
763
- const snapshot = this.getSnapshot();
760
+ state.subscription = observableCreator({
761
+ input: state.input,
762
+ system
763
+ }).subscribe({
764
+ next: value => {
765
+ self.send({
766
+ type: nextEventType,
767
+ data: value
768
+ });
769
+ },
770
+ error: err => {
771
+ self.send({
772
+ type: errorEventType,
773
+ data: err
774
+ });
775
+ },
776
+ complete: () => {
777
+ self.send({
778
+ type: completeEventType
779
+ });
780
+ }
781
+ });
782
+ },
783
+ getSnapshot: state => state.data,
784
+ getPersistedState: ({
785
+ status,
786
+ data,
787
+ input
788
+ }) => ({
789
+ status,
790
+ data,
791
+ input
792
+ }),
793
+ getStatus: state => state,
794
+ restoreState: state => ({
795
+ ...state,
796
+ subscription: undefined
797
+ })
798
+ };
799
+ return logic;
800
+ }
764
801
 
765
- // Execute deferred effects
766
- let deferredFn;
767
- while (deferredFn = this._deferred.shift()) {
768
- deferredFn();
769
- }
770
- for (const observer of this.observers) {
771
- observer.next?.(snapshot);
772
- }
773
- const status = this.logic.getStatus?.(state);
774
- switch (status?.status) {
775
- case 'done':
776
- this._stopProcedure();
777
- this._doneEvent = doneInvoke(this.id, status.data);
778
- this._parent?.send(this._doneEvent);
779
- this._complete();
780
- break;
781
- case 'error':
782
- this._stopProcedure();
783
- this._parent?.send(error(this.id, status.data));
784
- this._error(status.data);
785
- break;
786
- }
787
- }
788
- subscribe(nextListenerOrObserver, errorListener, completeListener) {
789
- const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
790
- this.observers.add(observer);
791
- if (this.status === ActorStatus.Stopped) {
792
- observer.complete?.();
793
- this.observers.delete(observer);
794
- }
795
- return {
796
- unsubscribe: () => {
797
- this.observers.delete(observer);
798
- }
799
- };
800
- }
802
+ /**
803
+ * Creates event observable logic that listens to an observable
804
+ * that delivers event objects.
805
+ *
806
+ *
807
+ * @param lazyObservable A function that creates an observable
808
+ * @returns Event observable logic
809
+ */
801
810
 
802
- /**
803
- * Starts the interpreter from the initial state
804
- */
805
- start() {
806
- if (this.status === ActorStatus.Running) {
807
- // Do not restart the service if it is already started
808
- return this;
809
- }
810
- this.system._register(this.sessionId, this);
811
- if (this._systemId) {
812
- this.system._set(this._systemId, this);
813
- }
814
- this.status = ActorStatus.Running;
815
- if (this.logic.start) {
816
- this.logic.start(this._state, this._actorContext);
817
- }
811
+ function fromEventObservable(lazyObservable) {
812
+ const errorEventType = '$$xstate.error';
813
+ const completeEventType = '$$xstate.complete';
818
814
 
819
- // TODO: this notifies all subscribers but usually this is redundant
820
- // there is no real change happening here
821
- // we need to rethink if this needs to be refactored
822
- this.update(this._state);
823
- if (this.options.devTools) {
824
- this.attachDevTools();
825
- }
826
- this.mailbox.start();
827
- return this;
828
- }
829
- _process(event) {
830
- try {
831
- const nextState = this.logic.transition(this._state, event, this._actorContext);
832
- this.update(nextState);
833
- if (event.type === stopSignalType) {
834
- this._stopProcedure();
835
- this._complete();
815
+ // TODO: event types
816
+ const logic = {
817
+ config: lazyObservable,
818
+ transition: (state, event) => {
819
+ if (state.status !== 'active') {
820
+ return state;
836
821
  }
837
- } catch (err) {
838
- // TODO: properly handle errors
839
- if (this.observers.size > 0) {
840
- this.observers.forEach(observer => {
841
- observer.error?.(err);
842
- });
843
- this.stop();
844
- } else {
845
- throw err;
822
+ switch (event.type) {
823
+ case errorEventType:
824
+ return {
825
+ ...state,
826
+ status: 'error',
827
+ input: undefined,
828
+ data: event.data,
829
+ subscription: undefined
830
+ };
831
+ case completeEventType:
832
+ return {
833
+ ...state,
834
+ status: 'done',
835
+ input: undefined,
836
+ subscription: undefined
837
+ };
838
+ case stopSignalType:
839
+ state.subscription.unsubscribe();
840
+ return {
841
+ ...state,
842
+ status: 'canceled',
843
+ input: undefined,
844
+ subscription: undefined
845
+ };
846
+ default:
847
+ return state;
846
848
  }
847
- }
848
- }
849
- _stop() {
850
- if (this.status === ActorStatus.Stopped) {
851
- return this;
852
- }
853
- this.mailbox.clear();
854
- if (this.status === ActorStatus.NotStarted) {
855
- this.status = ActorStatus.Stopped;
856
- return this;
857
- }
858
- this.mailbox.enqueue({
859
- type: stopSignalType
860
- });
861
- return this;
862
- }
863
-
864
- /**
865
- * Stops the interpreter and unsubscribe all listeners.
866
- */
867
- stop() {
868
- if (this._parent) {
869
- throw new Error('A non-root actor cannot be stopped directly.');
870
- }
871
- return this._stop();
872
- }
873
- _complete() {
874
- for (const observer of this.observers) {
875
- observer.complete?.();
876
- }
877
- this.observers.clear();
878
- }
879
- _error(data) {
880
- for (const observer of this.observers) {
881
- observer.error?.(data);
882
- }
883
- this.observers.clear();
884
- }
885
- _stopProcedure() {
886
- if (this.status !== ActorStatus.Running) {
887
- // Interpreter already stopped; do nothing
888
- return this;
889
- }
890
-
891
- // Cancel all delayed events
892
- for (const key of Object.keys(this.delayedEventsMap)) {
893
- this.clock.clearTimeout(this.delayedEventsMap[key]);
894
- }
895
-
896
- // TODO: mailbox.reset
897
- this.mailbox.clear();
898
- // TODO: after `stop` we must prepare ourselves for receiving events again
899
- // events sent *after* stop signal must be queued
900
- // it seems like this should be the common behavior for all of our consumers
901
- // so perhaps this should be unified somehow for all of them
902
- this.mailbox = new Mailbox(this._process.bind(this));
903
- this.status = ActorStatus.Stopped;
904
- this.system._unregister(this);
905
- return this;
906
- }
907
-
908
- /**
909
- * Sends an event to the running interpreter to trigger a transition.
910
- *
911
- * @param event The event to send
912
- */
913
- send(event) {
914
- if (typeof event === 'string') {
915
- throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
916
- }
917
- if (this.status === ActorStatus.Stopped) {
918
- // do nothing
919
- {
920
- const eventString = JSON.stringify(event);
921
- 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}`);
922
- }
923
- return;
924
- }
925
- if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
926
- throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
927
- // tslint:disable-next-line:max-line-length
928
- }". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
929
- }
930
- this.mailbox.enqueue(event);
931
- }
932
-
933
- // TODO: make private (and figure out a way to do this within the machine)
934
- delaySend(sendAction) {
935
- this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
936
- if ('to' in sendAction.params && sendAction.params.to) {
937
- sendAction.params.to.send(sendAction.params.event);
938
- } else {
939
- this.send(sendAction.params.event);
940
- }
941
- }, sendAction.params.delay);
942
- }
943
-
944
- // TODO: make private (and figure out a way to do this within the machine)
945
- cancel(sendId) {
946
- this.clock.clearTimeout(this.delayedEventsMap[sendId]);
947
- delete this.delayedEventsMap[sendId];
948
- }
949
- attachDevTools() {
950
- const {
951
- devTools
952
- } = this.options;
953
- if (devTools) {
954
- const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
955
- resolvedDevToolsAdapter(this);
956
- }
957
- }
958
- toJSON() {
959
- return {
960
- id: this.id
961
- };
962
- }
963
- getPersistedState() {
964
- return this.logic.getPersistedState?.(this._state);
965
- }
966
- [symbolObservable]() {
967
- return this;
968
- }
969
- getSnapshot() {
970
- return this.logic.getSnapshot ? this.logic.getSnapshot(this._state) : this._state;
971
- }
972
- }
973
-
974
- /**
975
- * Creates a new Interpreter instance for the given machine with the provided options, if any.
976
- *
977
- * @param machine The machine to interpret
978
- * @param options Interpreter options
979
- */
980
-
981
- function interpret(logic, options) {
982
- const interpreter = new Interpreter(logic, options);
983
- return interpreter;
984
- }
985
-
986
- /**
987
- * Returns actor logic from a transition function and its initial state.
988
- *
989
- * A transition function is a function that takes the current state and an event and returns the next state.
990
- *
991
- * @param transition The transition function that returns the next state given the current state and event.
992
- * @param initialState The initial state of the transition function.
993
- * @returns Actor logic
994
- */
995
- function fromTransition(transition, initialState) {
996
- const logic = {
997
- config: transition,
998
- transition: (state, event, actorContext) => {
999
- return transition(state, event, actorContext);
1000
849
  },
1001
850
  getInitialState: (_, input) => {
1002
- return typeof initialState === 'function' ? initialState({
851
+ return {
852
+ subscription: undefined,
853
+ status: 'active',
854
+ data: undefined,
1003
855
  input
1004
- }) : initialState;
1005
- },
1006
- getSnapshot: state => state,
1007
- getPersistedState: state => state,
1008
- restoreState: state => state
1009
- };
1010
- return logic;
1011
- }
1012
-
1013
- const resolveEventType = '$$xstate.resolve';
1014
- const rejectEventType = '$$xstate.reject';
1015
- function fromPromise(
1016
- // TODO: add types
1017
- promiseCreator) {
1018
- // TODO: add event types, consider making the `PromiseEvent` a private type or smth alike
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
- }
856
+ };
1049
857
  },
1050
858
  start: (state, {
1051
859
  self,
1052
860
  system
1053
861
  }) => {
1054
- // TODO: determine how to allow customizing this so that promises
1055
- // can be restarted if necessary
1056
- if (state.status !== 'active') {
862
+ if (state.status === 'done') {
863
+ // Do not restart a completed observable
1057
864
  return;
1058
865
  }
1059
- const resolvedPromise = Promise.resolve(promiseCreator({
866
+ state.subscription = lazyObservable({
1060
867
  input: state.input,
1061
868
  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
- });
869
+ }).subscribe({
870
+ next: value => {
871
+ self._parent?.send(value);
872
+ },
873
+ error: err => {
874
+ self.send({
875
+ type: errorEventType,
876
+ data: err
877
+ });
878
+ },
879
+ complete: () => {
880
+ self.send({
881
+ type: completeEventType
882
+ });
1295
883
  }
1296
884
  });
1297
885
  },
@@ -1343,92 +931,475 @@ function fromCallback(invokeCallback) {
1343
931
  input: state.input,
1344
932
  system
1345
933
  });
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;
934
+ if (isPromiseLike(state.dispose)) {
935
+ state.dispose.then(resolved => {
936
+ self._parent?.send(doneInvoke(id, resolved));
937
+ state.canceled = true;
938
+ }, errorData => {
939
+ state.canceled = true;
940
+ self._parent?.send(error(id, errorData));
941
+ });
942
+ }
943
+ return state;
944
+ }
945
+ if (event.type === stopSignalType) {
946
+ state.canceled = true;
947
+ if (isFunction(state.dispose)) {
948
+ state.dispose();
949
+ }
950
+ return state;
951
+ }
952
+ if (isSignal(event)) {
953
+ // TODO: unrecognized signal
954
+ return state;
955
+ }
956
+ state.receivers.forEach(receiver => receiver(event));
957
+ return state;
958
+ },
959
+ getInitialState: (_, input) => {
960
+ return {
961
+ canceled: false,
962
+ receivers: new Set(),
963
+ dispose: undefined,
964
+ input
965
+ };
966
+ },
967
+ getSnapshot: () => undefined,
968
+ getPersistedState: ({
969
+ input
970
+ }) => input
971
+ };
972
+ return logic;
973
+ }
974
+
975
+ const startSignalType = 'xstate.init';
976
+ const stopSignalType = 'xstate.stop';
977
+ const startSignal = {
978
+ type: 'xstate.init'
979
+ };
980
+ const stopSignal = {
981
+ type: 'xstate.stop'
982
+ };
983
+ /**
984
+ * An object that expresses the actor logic in reaction to received events,
985
+ * as well as an optionally emitted stream of values.
986
+ *
987
+ * @template TReceived The received event
988
+ * @template TSnapshot The emitted value
989
+ */
990
+
991
+ function isSignal(event) {
992
+ return event.type === startSignalType || event.type === stopSignalType;
993
+ }
994
+ function isActorRef(item) {
995
+ return !!item && typeof item === 'object' && typeof item.send === 'function';
996
+ }
997
+
998
+ // TODO: refactor the return type, this could be written in a better way
999
+ // but it's best to avoid unneccessary breaking changes now
1000
+ // @deprecated use `interpret(actorLogic)` instead
1001
+ function toActorRef(actorRefLike) {
1002
+ return {
1003
+ subscribe: () => ({
1004
+ unsubscribe: () => void 0
1005
+ }),
1006
+ id: 'anonymous',
1007
+ sessionId: '',
1008
+ getSnapshot: () => undefined,
1009
+ [symbolObservable]: function () {
1010
+ return this;
1011
+ },
1012
+ status: ActorStatus.Running,
1013
+ stop: () => void 0,
1014
+ ...actorRefLike
1015
+ };
1016
+ }
1017
+ const emptyLogic = fromTransition(_ => undefined, undefined);
1018
+ function createEmptyActor() {
1019
+ return interpret(emptyLogic);
1020
+ }
1021
+
1022
+ function createSystem() {
1023
+ let sessionIdCounter = 0;
1024
+ const children = new Map();
1025
+ const keyedActors = new Map();
1026
+ const reverseKeyedActors = new WeakMap();
1027
+ const system = {
1028
+ _bookId: () => `x:${sessionIdCounter++}`,
1029
+ _register: (sessionId, actorRef) => {
1030
+ children.set(sessionId, actorRef);
1031
+ return sessionId;
1032
+ },
1033
+ _unregister: actorRef => {
1034
+ children.delete(actorRef.sessionId);
1035
+ const systemId = reverseKeyedActors.get(actorRef);
1036
+ if (systemId !== undefined) {
1037
+ keyedActors.delete(systemId);
1038
+ reverseKeyedActors.delete(actorRef);
1039
+ }
1040
+ },
1041
+ get: systemId => {
1042
+ return keyedActors.get(systemId);
1043
+ },
1044
+ _set: (systemId, actorRef) => {
1045
+ const existing = keyedActors.get(systemId);
1046
+ if (existing && existing !== actorRef) {
1047
+ throw new Error(`Actor with system ID '${systemId}' already exists.`);
1048
+ }
1049
+ keyedActors.set(systemId, actorRef);
1050
+ reverseKeyedActors.set(actorRef, systemId);
1051
+ }
1052
+ };
1053
+ return system;
1054
+ }
1055
+
1056
+ let ActorStatus = /*#__PURE__*/function (ActorStatus) {
1057
+ ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
1058
+ ActorStatus[ActorStatus["Running"] = 1] = "Running";
1059
+ ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
1060
+ return ActorStatus;
1061
+ }({});
1062
+ const defaultOptions = {
1063
+ deferEvents: true,
1064
+ clock: {
1065
+ setTimeout: (fn, ms) => {
1066
+ return setTimeout(fn, ms);
1067
+ },
1068
+ clearTimeout: id => {
1069
+ return clearTimeout(id);
1070
+ }
1071
+ },
1072
+ logger: console.log.bind(console),
1073
+ devTools: false
1074
+ };
1075
+ class Interpreter {
1076
+ /**
1077
+ * The current state of the interpreted logic.
1078
+ */
1079
+
1080
+ /**
1081
+ * The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
1082
+ */
1083
+
1084
+ /**
1085
+ * The unique identifier for this actor relative to its parent.
1086
+ */
1087
+
1088
+ /**
1089
+ * Whether the service is started.
1090
+ */
1091
+
1092
+ // Actor Ref
1093
+
1094
+ // TODO: add typings for system
1095
+
1096
+ /**
1097
+ * The globally unique process ID for this invocation.
1098
+ */
1099
+
1100
+ /**
1101
+ * Creates a new Interpreter instance (i.e., service) for the given logic with the provided options, if any.
1102
+ *
1103
+ * @param logic The logic to be interpreted
1104
+ * @param options Interpreter options
1105
+ */
1106
+ constructor(logic, options) {
1107
+ this.logic = logic;
1108
+ this._state = void 0;
1109
+ this.clock = void 0;
1110
+ this.options = void 0;
1111
+ this.id = void 0;
1112
+ this.mailbox = new Mailbox(this._process.bind(this));
1113
+ this.delayedEventsMap = {};
1114
+ this.observers = new Set();
1115
+ this.logger = void 0;
1116
+ this.status = ActorStatus.NotStarted;
1117
+ this._parent = void 0;
1118
+ this.ref = void 0;
1119
+ this._actorContext = void 0;
1120
+ this._systemId = void 0;
1121
+ this.sessionId = void 0;
1122
+ this.system = void 0;
1123
+ this._doneEvent = void 0;
1124
+ this.src = void 0;
1125
+ this._deferred = [];
1126
+ const resolvedOptions = {
1127
+ ...defaultOptions,
1128
+ ...options
1129
+ };
1130
+ const {
1131
+ clock,
1132
+ logger,
1133
+ parent,
1134
+ id,
1135
+ systemId
1136
+ } = resolvedOptions;
1137
+ const self = this;
1138
+ this.system = parent?.system ?? createSystem();
1139
+ if (systemId) {
1140
+ this._systemId = systemId;
1141
+ this.system._set(systemId, this);
1142
+ }
1143
+ this.sessionId = this.system._bookId();
1144
+ this.id = id ?? this.sessionId;
1145
+ this.logger = logger;
1146
+ this.clock = clock;
1147
+ this._parent = parent;
1148
+ this.options = resolvedOptions;
1149
+ this.src = resolvedOptions.src;
1150
+ this.ref = this;
1151
+ this._actorContext = {
1152
+ self,
1153
+ id: this.id,
1154
+ sessionId: this.sessionId,
1155
+ logger: this.logger,
1156
+ defer: fn => {
1157
+ this._deferred.push(fn);
1158
+ },
1159
+ system: this.system,
1160
+ stopChild: child => {
1161
+ if (child._parent !== this) {
1162
+ throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
1163
+ }
1164
+ child._stop();
1165
+ }
1166
+ };
1167
+
1168
+ // Ensure that the send method is bound to this interpreter instance
1169
+ // if destructured
1170
+ this.send = this.send.bind(this);
1171
+ this._initState();
1172
+ }
1173
+ _initState() {
1174
+ 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);
1175
+ }
1176
+
1177
+ // array of functions to defer
1178
+
1179
+ update(state) {
1180
+ // Update state
1181
+ this._state = state;
1182
+ const snapshot = this.getSnapshot();
1183
+
1184
+ // Execute deferred effects
1185
+ let deferredFn;
1186
+ while (deferredFn = this._deferred.shift()) {
1187
+ deferredFn();
1188
+ }
1189
+ for (const observer of this.observers) {
1190
+ observer.next?.(snapshot);
1191
+ }
1192
+ const status = this.logic.getStatus?.(state);
1193
+ switch (status?.status) {
1194
+ case 'done':
1195
+ this._stopProcedure();
1196
+ this._doneEvent = doneInvoke(this.id, status.data);
1197
+ this._parent?.send(this._doneEvent);
1198
+ this._complete();
1199
+ break;
1200
+ case 'error':
1201
+ this._stopProcedure();
1202
+ this._parent?.send(error(this.id, status.data));
1203
+ this._error(status.data);
1204
+ break;
1205
+ }
1206
+ }
1207
+ subscribe(nextListenerOrObserver, errorListener, completeListener) {
1208
+ const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
1209
+ this.observers.add(observer);
1210
+ if (this.status === ActorStatus.Stopped) {
1211
+ observer.complete?.();
1212
+ this.observers.delete(observer);
1213
+ }
1214
+ return {
1215
+ unsubscribe: () => {
1216
+ this.observers.delete(observer);
1217
+ }
1218
+ };
1219
+ }
1220
+
1221
+ /**
1222
+ * Starts the interpreter from the initial state
1223
+ */
1224
+ start() {
1225
+ if (this.status === ActorStatus.Running) {
1226
+ // Do not restart the service if it is already started
1227
+ return this;
1228
+ }
1229
+ this.system._register(this.sessionId, this);
1230
+ if (this._systemId) {
1231
+ this.system._set(this._systemId, this);
1232
+ }
1233
+ this.status = ActorStatus.Running;
1234
+ if (this.logic.start) {
1235
+ this.logic.start(this._state, this._actorContext);
1236
+ }
1237
+
1238
+ // TODO: this notifies all subscribers but usually this is redundant
1239
+ // there is no real change happening here
1240
+ // we need to rethink if this needs to be refactored
1241
+ this.update(this._state);
1242
+ if (this.options.devTools) {
1243
+ this.attachDevTools();
1244
+ }
1245
+ this.mailbox.start();
1246
+ return this;
1247
+ }
1248
+ _process(event) {
1249
+ try {
1250
+ const nextState = this.logic.transition(this._state, event, this._actorContext);
1251
+ this.update(nextState);
1252
+ if (event.type === stopSignalType) {
1253
+ this._stopProcedure();
1254
+ this._complete();
1255
+ }
1256
+ } catch (err) {
1257
+ // TODO: properly handle errors
1258
+ if (this.observers.size > 0) {
1259
+ this.observers.forEach(observer => {
1260
+ observer.error?.(err);
1261
+ });
1262
+ this.stop();
1263
+ } else {
1264
+ throw err;
1356
1265
  }
1357
- if (event.type === stopSignalType) {
1358
- state.canceled = true;
1359
- if (isFunction(state.dispose)) {
1360
- state.dispose();
1361
- }
1362
- return state;
1266
+ }
1267
+ }
1268
+ _stop() {
1269
+ if (this.status === ActorStatus.Stopped) {
1270
+ return this;
1271
+ }
1272
+ this.mailbox.clear();
1273
+ if (this.status === ActorStatus.NotStarted) {
1274
+ this.status = ActorStatus.Stopped;
1275
+ return this;
1276
+ }
1277
+ this.mailbox.enqueue({
1278
+ type: stopSignalType
1279
+ });
1280
+ return this;
1281
+ }
1282
+
1283
+ /**
1284
+ * Stops the interpreter and unsubscribe all listeners.
1285
+ */
1286
+ stop() {
1287
+ if (this._parent) {
1288
+ throw new Error('A non-root actor cannot be stopped directly.');
1289
+ }
1290
+ return this._stop();
1291
+ }
1292
+ _complete() {
1293
+ for (const observer of this.observers) {
1294
+ observer.complete?.();
1295
+ }
1296
+ this.observers.clear();
1297
+ }
1298
+ _error(data) {
1299
+ for (const observer of this.observers) {
1300
+ observer.error?.(data);
1301
+ }
1302
+ this.observers.clear();
1303
+ }
1304
+ _stopProcedure() {
1305
+ if (this.status !== ActorStatus.Running) {
1306
+ // Interpreter already stopped; do nothing
1307
+ return this;
1308
+ }
1309
+
1310
+ // Cancel all delayed events
1311
+ for (const key of Object.keys(this.delayedEventsMap)) {
1312
+ this.clock.clearTimeout(this.delayedEventsMap[key]);
1313
+ }
1314
+
1315
+ // TODO: mailbox.reset
1316
+ this.mailbox.clear();
1317
+ // TODO: after `stop` we must prepare ourselves for receiving events again
1318
+ // events sent *after* stop signal must be queued
1319
+ // it seems like this should be the common behavior for all of our consumers
1320
+ // so perhaps this should be unified somehow for all of them
1321
+ this.mailbox = new Mailbox(this._process.bind(this));
1322
+ this.status = ActorStatus.Stopped;
1323
+ this.system._unregister(this);
1324
+ return this;
1325
+ }
1326
+
1327
+ /**
1328
+ * Sends an event to the running interpreter to trigger a transition.
1329
+ *
1330
+ * @param event The event to send
1331
+ */
1332
+ send(event) {
1333
+ if (typeof event === 'string') {
1334
+ throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
1335
+ }
1336
+ if (this.status === ActorStatus.Stopped) {
1337
+ // do nothing
1338
+ {
1339
+ const eventString = JSON.stringify(event);
1340
+ 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}`);
1363
1341
  }
1364
- if (isSignal(event)) {
1365
- // TODO: unrecognized signal
1366
- return state;
1342
+ return;
1343
+ }
1344
+ if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
1345
+ throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
1346
+ // tslint:disable-next-line:max-line-length
1347
+ }". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
1348
+ }
1349
+ this.mailbox.enqueue(event);
1350
+ }
1351
+
1352
+ // TODO: make private (and figure out a way to do this within the machine)
1353
+ delaySend(sendAction) {
1354
+ this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
1355
+ if ('to' in sendAction.params && sendAction.params.to) {
1356
+ sendAction.params.to.send(sendAction.params.event);
1357
+ } else {
1358
+ this.send(sendAction.params.event);
1367
1359
  }
1368
- state.receivers.forEach(receiver => receiver(event));
1369
- return state;
1370
- },
1371
- getInitialState: (_, input) => {
1372
- return {
1373
- canceled: false,
1374
- receivers: new Set(),
1375
- dispose: undefined,
1376
- input
1377
- };
1378
- },
1379
- getSnapshot: () => undefined,
1380
- getPersistedState: ({
1381
- input
1382
- }) => input
1383
- };
1384
- return logic;
1360
+ }, sendAction.params.delay);
1361
+ }
1362
+
1363
+ // TODO: make private (and figure out a way to do this within the machine)
1364
+ cancel(sendId) {
1365
+ this.clock.clearTimeout(this.delayedEventsMap[sendId]);
1366
+ delete this.delayedEventsMap[sendId];
1367
+ }
1368
+ attachDevTools() {
1369
+ const {
1370
+ devTools
1371
+ } = this.options;
1372
+ if (devTools) {
1373
+ const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
1374
+ resolvedDevToolsAdapter(this);
1375
+ }
1376
+ }
1377
+ toJSON() {
1378
+ return {
1379
+ id: this.id
1380
+ };
1381
+ }
1382
+ getPersistedState() {
1383
+ return this.logic.getPersistedState?.(this._state);
1384
+ }
1385
+ [symbolObservable]() {
1386
+ return this;
1387
+ }
1388
+ getSnapshot() {
1389
+ return this.logic.getSnapshot ? this.logic.getSnapshot(this._state) : this._state;
1390
+ }
1385
1391
  }
1386
1392
 
1387
- const startSignalType = 'xstate.init';
1388
- const stopSignalType = 'xstate.stop';
1389
- const startSignal = {
1390
- type: 'xstate.init'
1391
- };
1392
- const stopSignal = {
1393
- type: 'xstate.stop'
1394
- };
1395
1393
  /**
1396
- * An object that expresses the actor logic in reaction to received events,
1397
- * as well as an optionally emitted stream of values.
1394
+ * Creates a new Interpreter instance for the given machine with the provided options, if any.
1398
1395
  *
1399
- * @template TReceived The received event
1400
- * @template TSnapshot The emitted value
1396
+ * @param machine The machine to interpret
1397
+ * @param options Interpreter options
1401
1398
  */
1402
1399
 
1403
- function isSignal(event) {
1404
- return event.type === startSignalType || event.type === stopSignalType;
1405
- }
1406
- function isActorRef(item) {
1407
- return !!item && typeof item === 'object' && typeof item.send === 'function';
1408
- }
1409
-
1410
- // TODO: refactor the return type, this could be written in a better way
1411
- // but it's best to avoid unneccessary breaking changes now
1412
- // @deprecated use `interpret(actorLogic)` instead
1413
- function toActorRef(actorRefLike) {
1414
- return {
1415
- subscribe: () => ({
1416
- unsubscribe: () => void 0
1417
- }),
1418
- id: 'anonymous',
1419
- sessionId: '',
1420
- getSnapshot: () => undefined,
1421
- [symbolObservable]: function () {
1422
- return this;
1423
- },
1424
- status: ActorStatus.Running,
1425
- stop: () => void 0,
1426
- ...actorRefLike
1427
- };
1428
- }
1429
- const emptyLogic = fromTransition(_ => undefined, undefined);
1430
- function createEmptyActor() {
1431
- return interpret(emptyLogic);
1400
+ function interpret(logic, options) {
1401
+ const interpreter = new Interpreter(logic, options);
1402
+ return interpreter;
1432
1403
  }
1433
1404
 
1434
1405
  function invoke(invokeDef) {
@@ -1445,42 +1416,32 @@ function invoke(invokeDef) {
1445
1416
  src
1446
1417
  } = invokeDef;
1447
1418
  let resolvedInvokeAction;
1448
- if (isActorRef(src)) {
1419
+ const referenced = resolveReferencedActor(state.machine.implementations.actors[src]);
1420
+ if (!referenced) {
1421
+ resolvedInvokeAction = {
1422
+ type,
1423
+ params: invokeDef
1424
+ };
1425
+ } else {
1426
+ const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
1427
+ const ref = interpret(referenced.src, {
1428
+ id,
1429
+ src,
1430
+ parent: actorContext?.self,
1431
+ systemId: invokeDef.systemId,
1432
+ input: typeof input === 'function' ? input({
1433
+ context: state.context,
1434
+ event,
1435
+ self: actorContext?.self
1436
+ }) : input
1437
+ });
1449
1438
  resolvedInvokeAction = {
1450
1439
  type,
1451
1440
  params: {
1452
1441
  ...invokeDef,
1453
- ref: src
1442
+ ref
1454
1443
  }
1455
1444
  };
1456
- } else {
1457
- const referenced = resolveReferencedActor(state.machine.implementations.actors[src]);
1458
- if (!referenced) {
1459
- resolvedInvokeAction = {
1460
- type,
1461
- params: invokeDef
1462
- };
1463
- } else {
1464
- const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
1465
- const ref = interpret(referenced.src, {
1466
- id,
1467
- src,
1468
- parent: actorContext?.self,
1469
- systemId: invokeDef.systemId,
1470
- input: typeof input === 'function' ? input({
1471
- context: state.context,
1472
- event,
1473
- self: actorContext?.self
1474
- }) : input
1475
- });
1476
- resolvedInvokeAction = {
1477
- type,
1478
- params: {
1479
- ...invokeDef,
1480
- ref
1481
- }
1482
- };
1483
- }
1484
1445
  }
1485
1446
  const actorRef = resolvedInvokeAction.params.ref;
1486
1447
  const invokedState = cloneState(state, {
@@ -2485,7 +2446,7 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
2485
2446
  });
2486
2447
  const matchedActions = resolvedAction.params?.actions;
2487
2448
  intermediateState = nextState;
2488
- if ((resolvedAction.type === raise$1 || resolvedAction.type === send$1 && resolvedAction.params.internal) && typeof resolvedAction.params.delay !== 'number') {
2449
+ if (resolvedAction.type === raise$1 && typeof resolvedAction.params.delay !== 'number') {
2489
2450
  raiseActions.push(resolvedAction);
2490
2451
  }
2491
2452
 
@@ -2803,11 +2764,14 @@ function stop(actorRef) {
2803
2764
  actor
2804
2765
  }
2805
2766
  }, (event, {
2806
- state
2767
+ state,
2768
+ actorContext
2807
2769
  }) => {
2808
2770
  const actorRefOrString = isFunction(actor) ? actor({
2809
2771
  context: state.context,
2810
- event
2772
+ event,
2773
+ self: actorContext?.self ?? {},
2774
+ system: actorContext?.system
2811
2775
  }) : actor;
2812
2776
  const actorRef = typeof actorRefOrString === 'string' ? state.children[actorRefOrString] : actorRefOrString;
2813
2777
  let children = state.children;
@@ -2891,59 +2855,60 @@ function log(expr = defaultLogExpr, label) {
2891
2855
  });
2892
2856
  }
2893
2857
 
2894
- function createSpawner(self, machine, context, event, mutCapturedActions) {
2895
- return (src, options = {}) => {
2858
+ function createSpawner(actorContext, {
2859
+ machine,
2860
+ context
2861
+ }, event, spawnedChildren) {
2862
+ const spawn = (src, options = {}) => {
2896
2863
  const {
2897
2864
  systemId
2898
2865
  } = options;
2899
- if (isString(src)) {
2866
+ if (typeof src === 'string') {
2900
2867
  const referenced = resolveReferencedActor(machine.implementations.actors[src]);
2901
- if (referenced) {
2902
- const input = 'input' in options ? options.input : referenced.input;
2903
-
2904
- // TODO: this should also receive `src`
2905
- const actorRef = interpret(referenced.src, {
2906
- id: options.id,
2907
- parent: self,
2908
- input: typeof input === 'function' ? input({
2909
- context,
2910
- event,
2911
- self
2912
- }) : input
2913
- });
2914
- mutCapturedActions.push(invoke({
2915
- id: actorRef.id,
2916
- // @ts-ignore TODO: fix types
2917
- src: actorRef,
2918
- // TODO
2919
- ref: actorRef,
2920
- meta: undefined,
2921
- input,
2922
- systemId
2923
- }));
2924
- return actorRef; // TODO: fix types
2925
- }
2926
-
2927
- throw new Error(`Actor logic '${src}' not implemented in machine '${machine.id}'`);
2868
+ if (!referenced) {
2869
+ throw new Error(`Actor logic '${src}' not implemented in machine '${machine.id}'`);
2870
+ }
2871
+ const input = 'input' in options ? options.input : referenced.input;
2872
+
2873
+ // TODO: this should also receive `src`
2874
+ const actor = interpret(referenced.src, {
2875
+ id: options.id,
2876
+ parent: actorContext.self,
2877
+ input: typeof input === 'function' ? input({
2878
+ context,
2879
+ event,
2880
+ self: actorContext.self
2881
+ }) : input,
2882
+ systemId
2883
+ });
2884
+ spawnedChildren[actor.id] = actor;
2885
+ return actor;
2928
2886
  } else {
2929
2887
  // TODO: this should also receive `src`
2930
- const actorRef = interpret(src, {
2888
+ return interpret(src, {
2931
2889
  id: options.id,
2932
- parent: self,
2890
+ parent: actorContext.self,
2933
2891
  input: options.input,
2934
2892
  systemId
2935
2893
  });
2936
- mutCapturedActions.push(invoke({
2937
- // @ts-ignore TODO: fix types
2938
- src: actorRef,
2939
- ref: actorRef,
2940
- id: actorRef.id,
2941
- meta: undefined,
2942
- input: options.input
2943
- }));
2944
- return actorRef; // TODO: fix types
2945
2894
  }
2946
2895
  };
2896
+ return (src, options) => {
2897
+ const actorRef = spawn(src, options);
2898
+ spawnedChildren[actorRef.id] = actorRef;
2899
+ actorContext.defer(() => {
2900
+ if (actorRef.status === ActorStatus.Stopped) {
2901
+ return;
2902
+ }
2903
+ try {
2904
+ actorRef.start?.();
2905
+ } catch (err) {
2906
+ actorContext.self.send(error(actorRef.id, err));
2907
+ return;
2908
+ }
2909
+ });
2910
+ return actorRef;
2911
+ };
2947
2912
  }
2948
2913
 
2949
2914
  /**
@@ -2962,15 +2927,15 @@ function assign(assignment) {
2962
2927
  action,
2963
2928
  actorContext
2964
2929
  }) => {
2965
- const capturedActions = [];
2966
2930
  if (!state.context) {
2967
2931
  throw new Error('Cannot assign to undefined `context`. Ensure that `context` is defined in the machine config.');
2968
2932
  }
2933
+ const spawnedChildren = {};
2969
2934
  const args = {
2970
2935
  context: state.context,
2971
2936
  event,
2972
2937
  action,
2973
- spawn: createSpawner(actorContext?.self, state.machine, state.context, event, capturedActions),
2938
+ spawn: createSpawner(actorContext, state, event, spawnedChildren),
2974
2939
  self: actorContext?.self ?? {},
2975
2940
  system: actorContext?.system
2976
2941
  };
@@ -2985,12 +2950,15 @@ function assign(assignment) {
2985
2950
  }
2986
2951
  const updatedContext = Object.assign({}, state.context, partialUpdate);
2987
2952
  return [cloneState(state, {
2988
- context: updatedContext
2953
+ context: updatedContext,
2954
+ children: Object.keys(spawnedChildren).length ? {
2955
+ ...state.children,
2956
+ ...spawnedChildren
2957
+ } : state.children
2989
2958
  }), {
2990
2959
  type: assign$1,
2991
2960
  params: {
2992
- context: updatedContext,
2993
- actions: capturedActions
2961
+ context: updatedContext
2994
2962
  }
2995
2963
  }];
2996
2964
  });
@@ -3101,9 +3069,6 @@ function pure(getActions) {
3101
3069
  });
3102
3070
  }
3103
3071
 
3104
- const initEvent = {
3105
- type: init
3106
- };
3107
3072
  function resolveActionObject(actionObject, actionFunctionMap) {
3108
3073
  if (isDynamicAction(actionObject)) {
3109
3074
  return actionObject;
@@ -3264,7 +3229,6 @@ exports.choose = choose;
3264
3229
  exports.createEmptyActor = createEmptyActor;
3265
3230
  exports.createInitEvent = createInitEvent;
3266
3231
  exports.createInvokeId = createInvokeId;
3267
- exports.createSpawner = createSpawner;
3268
3232
  exports.done = done;
3269
3233
  exports.doneInvoke = doneInvoke;
3270
3234
  exports.error = error;
@@ -3287,7 +3251,6 @@ exports.getInitialConfiguration = getInitialConfiguration;
3287
3251
  exports.getPersistedState = getPersistedState;
3288
3252
  exports.getStateNodeByPath = getStateNodeByPath;
3289
3253
  exports.getStateNodes = getStateNodes;
3290
- exports.initEvent = initEvent;
3291
3254
  exports.interpret = interpret;
3292
3255
  exports.invoke = invoke$1;
3293
3256
  exports.isActorRef = isActorRef;
@@ -3312,7 +3275,6 @@ exports.resolveActionObject = resolveActionObject;
3312
3275
  exports.resolveActionsAndContext = resolveActionsAndContext;
3313
3276
  exports.resolveReferencedActor = resolveReferencedActor;
3314
3277
  exports.resolveStateValue = resolveStateValue;
3315
- exports.send = send;
3316
3278
  exports.sendParent = sendParent;
3317
3279
  exports.sendTo = sendTo;
3318
3280
  exports.startSignal = startSignal;