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