xstate 5.0.0-beta.14 → 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 (46) 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-444a17c3.esm.js → actions-0386b622.esm.js} +934 -997
  15. package/dist/{actions-73b8d456.development.cjs.js → actions-0f903c0d.development.cjs.js} +863 -928
  16. package/dist/{actions-17c3bcfa.cjs.js → actions-6b9073db.cjs.js} +934 -999
  17. package/dist/{actions-60622c0c.development.esm.js → actions-6f7fbc84.development.esm.js} +863 -926
  18. package/dist/declarations/src/State.d.ts +1 -11
  19. package/dist/declarations/src/StateMachine.d.ts +4 -11
  20. package/dist/declarations/src/actionTypes.d.ts +1 -1
  21. package/dist/declarations/src/actions/assign.d.ts +2 -2
  22. package/dist/declarations/src/actions/cancel.d.ts +2 -2
  23. package/dist/declarations/src/actions/send.d.ts +12 -22
  24. package/dist/declarations/src/actions/stop.d.ts +2 -2
  25. package/dist/declarations/src/actions.d.ts +1 -4
  26. package/dist/declarations/src/actors/callback.d.ts +3 -3
  27. package/dist/declarations/src/actors/index.d.ts +1 -1
  28. package/dist/declarations/src/actors/promise.d.ts +13 -3
  29. package/dist/declarations/src/guards.d.ts +1 -1
  30. package/dist/declarations/src/interpreter.d.ts +2 -2
  31. package/dist/declarations/src/stateUtils.d.ts +3 -2
  32. package/dist/declarations/src/types.d.ts +44 -71
  33. package/dist/declarations/src/utils.d.ts +3 -3
  34. package/dist/xstate.cjs.js +44 -59
  35. package/dist/xstate.development.cjs.js +44 -59
  36. package/dist/xstate.development.esm.js +45 -60
  37. package/dist/xstate.esm.js +45 -60
  38. package/dist/xstate.umd.min.js +1 -1
  39. package/dist/xstate.umd.min.js.map +1 -1
  40. package/guards/dist/xstate-guards.cjs.js +1 -1
  41. package/guards/dist/xstate-guards.development.cjs.js +1 -1
  42. package/guards/dist/xstate-guards.development.esm.js +1 -1
  43. package/guards/dist/xstate-guards.esm.js +1 -1
  44. package/guards/dist/xstate-guards.umd.min.js +1 -1
  45. package/guards/dist/xstate-guards.umd.min.js.map +1 -1
  46. package/package.json +2 -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,
@@ -102,9 +102,9 @@ const NULL_EVENT = '';
102
102
  const STATE_IDENTIFIER = '#';
103
103
  const WILDCARD = '*';
104
104
 
105
- function matchesState(parentStateId, childStateId, delimiter = STATE_DELIMITER) {
106
- const parentStateValue = toStateValue(parentStateId, delimiter);
107
- const childStateValue = toStateValue(childStateId, delimiter);
105
+ function matchesState(parentStateId, childStateId) {
106
+ const parentStateValue = toStateValue(parentStateId);
107
+ const childStateValue = toStateValue(childStateId);
108
108
  if (isString(childStateValue)) {
109
109
  if (isString(parentStateValue)) {
110
110
  return childStateValue === parentStateValue;
@@ -123,12 +123,12 @@ function matchesState(parentStateId, childStateId, delimiter = STATE_DELIMITER)
123
123
  return matchesState(parentStateValue[key], childStateValue[key]);
124
124
  });
125
125
  }
126
- function toStatePath(stateId, delimiter) {
126
+ function toStatePath(stateId) {
127
127
  try {
128
128
  if (isArray(stateId)) {
129
129
  return stateId;
130
130
  }
131
- return stateId.toString().split(delimiter);
131
+ return stateId.toString().split(STATE_DELIMITER);
132
132
  } catch (e) {
133
133
  throw new Error(`'${stateId}' is not a valid state path.`);
134
134
  }
@@ -136,7 +136,7 @@ function toStatePath(stateId, delimiter) {
136
136
  function isStateLike(state) {
137
137
  return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
138
138
  }
139
- function toStateValue(stateValue, delimiter) {
139
+ function toStateValue(stateValue) {
140
140
  if (isStateLike(stateValue)) {
141
141
  return stateValue.value;
142
142
  }
@@ -146,7 +146,7 @@ function toStateValue(stateValue, delimiter) {
146
146
  if (typeof stateValue !== 'string') {
147
147
  return stateValue;
148
148
  }
149
- const statePath = toStatePath(stateValue, delimiter);
149
+ const statePath = toStatePath(stateValue);
150
150
  return pathToStateValue(statePath);
151
151
  }
152
152
  function pathToStateValue(statePath) {
@@ -159,8 +159,9 @@ function pathToStateValue(statePath) {
159
159
  if (i === statePath.length - 2) {
160
160
  marker[statePath[i]] = statePath[i + 1];
161
161
  } else {
162
- marker[statePath[i]] = {};
163
- marker = marker[statePath[i]];
162
+ const previous = marker;
163
+ marker = {};
164
+ previous[statePath[i]] = marker;
164
165
  }
165
166
  }
166
167
  return value;
@@ -305,22 +306,19 @@ function isDynamicAction(action) {
305
306
  }
306
307
 
307
308
  /**
308
- * Sends an event. This returns an action that will be read by an interpreter to
309
- * send the event in the next step, after the current step is finished executing.
310
- *
311
- * @deprecated Use the `sendTo(...)` action creator instead.
309
+ * Sends an event to an actor.
312
310
  *
313
- * @param eventOrExpr The event to send.
314
- * @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
315
314
  * - `id` - The unique send event identifier (used with `cancel()`).
316
315
  * - `delay` - The number of milliseconds to delay the sending of the event.
317
- * - `to` - The target of this event (by default, the machine the event was sent from).
318
316
  */
319
- function send(eventOrExpr, options) {
317
+ function sendTo(actor, eventOrExpr, options) {
320
318
  return createDynamicAction({
321
- type: send$1,
319
+ type: sendTo$1,
322
320
  params: {
323
- to: options ? options.to : undefined,
321
+ to: actor,
324
322
  delay: options ? options.delay : undefined,
325
323
  event: eventOrExpr,
326
324
  id: options && options.id !== undefined ? options.id : isFunction(eventOrExpr) ? eventOrExpr.name : eventOrExpr.type
@@ -330,7 +328,7 @@ function send(eventOrExpr, options) {
330
328
  state
331
329
  }) => {
332
330
  const params = {
333
- to: options ? options.to : undefined,
331
+ to: actor,
334
332
  delay: options ? options.delay : undefined,
335
333
  event: eventOrExpr,
336
334
  // TODO: don't auto-generate IDs here like that
@@ -343,7 +341,7 @@ function send(eventOrExpr, options) {
343
341
  self: actorContext?.self ?? null,
344
342
  system: actorContext?.system
345
343
  };
346
- const delaysMap = state.machine.options.delays;
344
+ const delaysMap = state.machine.implementations.delays;
347
345
 
348
346
  // TODO: helper function for resolving Expr
349
347
  if (typeof eventOrExpr === 'string') {
@@ -378,13 +376,12 @@ function send(eventOrExpr, options) {
378
376
  targetActorRef = resolvedTarget || actorContext?.self;
379
377
  }
380
378
  const resolvedAction = {
381
- type: send$1,
379
+ type: sendTo$1,
382
380
  params: {
383
381
  ...params,
384
382
  to: targetActorRef,
385
383
  event: resolvedEvent,
386
- delay: resolvedDelay,
387
- internal: resolvedTarget === SpecialTargets.Internal
384
+ delay: resolvedDelay
388
385
  },
389
386
  execute: actorCtx => {
390
387
  const sendAction = resolvedAction;
@@ -414,12 +411,8 @@ function send(eventOrExpr, options) {
414
411
  * @param options Options to pass into the send event.
415
412
  */
416
413
  function sendParent(event, options) {
417
- return send(event, {
418
- ...options,
419
- to: SpecialTargets.Parent
420
- });
414
+ return sendTo(SpecialTargets.Parent, event, options);
421
415
  }
422
-
423
416
  /**
424
417
  * Forwards (sends) an event to a specified service.
425
418
  *
@@ -437,12 +430,9 @@ function forwardTo(target, options) {
437
430
  return resolvedTarget;
438
431
  };
439
432
  }
440
- return send(({
433
+ return sendTo(target, ({
441
434
  event
442
- }) => event, {
443
- ...options,
444
- to: target
445
- });
435
+ }) => event, options);
446
436
  }
447
437
 
448
438
  /**
@@ -458,25 +448,7 @@ function escalate(errorData, options) {
458
448
  type: error$1,
459
449
  data: isFunction(errorData) ? errorData(arg) : errorData
460
450
  };
461
- }, {
462
- ...options,
463
- to: SpecialTargets.Parent
464
- });
465
- }
466
-
467
- /**
468
- * Sends an event to an actor.
469
- *
470
- * @param actor The `ActorRef` to send the event to.
471
- * @param event The event to send, or an expression that evaluates to the event to send
472
- * @param options Send action options
473
- * @returns An XState send action object
474
- */
475
- function sendTo(actor, event, options) {
476
- return send(event, {
477
- ...options,
478
- to: actor
479
- });
451
+ }, options);
480
452
  }
481
453
 
482
454
  const cache = new WeakMap();
@@ -530,8 +502,6 @@ function cancel(sendId) {
530
502
  });
531
503
  }
532
504
 
533
- const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
534
-
535
505
  class Mailbox {
536
506
  constructor(_process) {
537
507
  this._process = _process;
@@ -599,533 +569,257 @@ class Mailbox {
599
569
  }
600
570
  }
601
571
 
602
- function createSystem() {
603
- let sessionIdCounter = 0;
604
- const children = new Map();
605
- const keyedActors = new Map();
606
- const reverseKeyedActors = new WeakMap();
607
- const system = {
608
- _bookId: () => `x:${sessionIdCounter++}`,
609
- _register: (sessionId, actorRef) => {
610
- children.set(sessionId, actorRef);
611
- return sessionId;
612
- },
613
- _unregister: actorRef => {
614
- children.delete(actorRef.sessionId);
615
- const systemId = reverseKeyedActors.get(actorRef);
616
- if (systemId !== undefined) {
617
- keyedActors.delete(systemId);
618
- reverseKeyedActors.delete(actorRef);
619
- }
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);
620
588
  },
621
- get: systemId => {
622
- return keyedActors.get(systemId);
589
+ getInitialState: (_, input) => {
590
+ return typeof initialState === 'function' ? initialState({
591
+ input
592
+ }) : initialState;
623
593
  },
624
- _set: (systemId, actorRef) => {
625
- const existing = keyedActors.get(systemId);
626
- if (existing && existing !== actorRef) {
627
- throw new Error(`Actor with system ID '${systemId}' already exists.`);
628
- }
629
- keyedActors.set(systemId, actorRef);
630
- reverseKeyedActors.set(actorRef, systemId);
631
- }
594
+ getSnapshot: state => state,
595
+ getPersistedState: state => state,
596
+ restoreState: state => state
632
597
  };
633
- return system;
598
+ return logic;
634
599
  }
635
600
 
636
- let ActorStatus = /*#__PURE__*/function (ActorStatus) {
637
- ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
638
- ActorStatus[ActorStatus["Running"] = 1] = "Running";
639
- ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
640
- return ActorStatus;
641
- }({});
642
- const defaultOptions = {
643
- deferEvents: true,
644
- clock: {
645
- setTimeout: (fn, ms) => {
646
- return setTimeout(fn, ms);
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
+ }
647
637
  },
648
- clearTimeout: id => {
649
- return clearTimeout(id);
650
- }
651
- },
652
- logger: console.log.bind(console),
653
- devTools: false
654
- };
655
- class Interpreter {
656
- /**
657
- * The current state of the interpreted logic.
658
- */
659
-
660
- /**
661
- * The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
662
- */
663
-
664
- /**
665
- * The unique identifier for this actor relative to its parent.
666
- */
667
-
668
- /**
669
- * Whether the service is started.
670
- */
671
-
672
- // Actor Ref
673
-
674
- // 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
+ }
675
685
 
676
- /**
677
- * The globally unique process ID for this invocation.
678
- */
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';
679
691
 
680
- /**
681
- * Creates a new Interpreter instance (i.e., service) for the given logic with the provided options, if any.
682
- *
683
- * @param logic The logic to be interpreted
684
- * @param options Interpreter options
685
- */
686
- constructor(logic, options) {
687
- this.logic = logic;
688
- this._state = void 0;
689
- this.clock = void 0;
690
- this.options = void 0;
691
- this.id = void 0;
692
- this.mailbox = new Mailbox(this._process.bind(this));
693
- this.delayedEventsMap = {};
694
- this.observers = new Set();
695
- this.logger = void 0;
696
- this.status = ActorStatus.NotStarted;
697
- this._parent = void 0;
698
- this.ref = void 0;
699
- this._actorContext = void 0;
700
- this._systemId = void 0;
701
- this.sessionId = void 0;
702
- this.system = void 0;
703
- this._doneEvent = void 0;
704
- this.src = void 0;
705
- this._deferred = [];
706
- const resolvedOptions = {
707
- ...defaultOptions,
708
- ...options
709
- };
710
- const {
711
- clock,
712
- logger,
713
- parent,
692
+ // TODO: add event types
693
+ const logic = {
694
+ config: observableCreator,
695
+ transition: (state, event, {
696
+ self,
714
697
  id,
715
- systemId
716
- } = resolvedOptions;
717
- const self = this;
718
- this.system = parent?.system ?? createSystem();
719
- if (systemId) {
720
- this._systemId = systemId;
721
- this.system._set(systemId, this);
722
- }
723
- this.sessionId = this.system._bookId();
724
- this.id = id ?? this.sessionId;
725
- this.logger = logger;
726
- this.clock = clock;
727
- this._parent = parent;
728
- this.options = resolvedOptions;
729
- this.src = resolvedOptions.src;
730
- this.ref = this;
731
- this._actorContext = {
732
- self,
733
- id: this.id,
734
- sessionId: this.sessionId,
735
- logger: this.logger,
736
- defer: fn => {
737
- this._deferred.push(fn);
738
- },
739
- system: this.system,
740
- stopChild: child => {
741
- if (child._parent !== this) {
742
- throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
743
- }
744
- child._stop();
745
- }
746
- };
747
-
748
- // Ensure that the send method is bound to this interpreter instance
749
- // if destructured
750
- this.send = this.send.bind(this);
751
- this._initState();
752
- }
753
- _initState() {
754
- this._state = this.options.state ? this.logic.restoreState ? this.logic.restoreState(this.options.state, this._actorContext) : this.options.state : this.logic.getInitialState(this._actorContext, this.options?.input);
755
- }
756
-
757
- // array of functions to defer
758
-
759
- update(state) {
760
- // Update state
761
- this._state = state;
762
- const snapshot = this.getSnapshot();
763
-
764
- // Execute deferred effects
765
- let deferredFn;
766
- while (deferredFn = this._deferred.shift()) {
767
- deferredFn();
768
- }
769
- for (const observer of this.observers) {
770
- observer.next?.(snapshot);
771
- }
772
- const status = this.logic.getStatus?.(state);
773
- switch (status?.status) {
774
- case 'done':
775
- this._stopProcedure();
776
- this._doneEvent = doneInvoke(this.id, status.data);
777
- this._parent?.send(this._doneEvent);
778
- this._complete();
779
- break;
780
- case 'error':
781
- this._stopProcedure();
782
- this._parent?.send(error(this.id, status.data));
783
- this._error(status.data);
784
- break;
785
- }
786
- }
787
- subscribe(nextListenerOrObserver, errorListener, completeListener) {
788
- const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
789
- this.observers.add(observer);
790
- if (this.status === ActorStatus.Stopped) {
791
- observer.complete?.();
792
- this.observers.delete(observer);
793
- }
794
- return {
795
- unsubscribe: () => {
796
- this.observers.delete(observer);
797
- }
798
- };
799
- }
800
-
801
- /**
802
- * Starts the interpreter from the initial state
803
- */
804
- start() {
805
- if (this.status === ActorStatus.Running) {
806
- // Do not restart the service if it is already started
807
- return this;
808
- }
809
- this.system._register(this.sessionId, this);
810
- if (this._systemId) {
811
- this.system._set(this._systemId, this);
812
- }
813
- this.status = ActorStatus.Running;
814
- if (this.logic.start) {
815
- this.logic.start(this._state, this._actorContext);
816
- }
817
-
818
- // TODO: this notifies all subscribers but usually this is redundant
819
- // there is no real change happening here
820
- // we need to rethink if this needs to be refactored
821
- this.update(this._state);
822
- if (this.options.devTools) {
823
- this.attachDevTools();
824
- }
825
- this.mailbox.start();
826
- return this;
827
- }
828
- _process(event) {
829
- try {
830
- const nextState = this.logic.transition(this._state, event, this._actorContext);
831
- this.update(nextState);
832
- if (event.type === stopSignalType) {
833
- this._stopProcedure();
834
- this._complete();
835
- }
836
- } catch (err) {
837
- // TODO: properly handle errors
838
- if (this.observers.size > 0) {
839
- this.observers.forEach(observer => {
840
- observer.error?.(err);
841
- });
842
- this.stop();
843
- } else {
844
- throw err;
845
- }
846
- }
847
- }
848
- _stop() {
849
- if (this.status === ActorStatus.Stopped) {
850
- return this;
851
- }
852
- this.mailbox.clear();
853
- if (this.status === ActorStatus.NotStarted) {
854
- this.status = ActorStatus.Stopped;
855
- return this;
856
- }
857
- this.mailbox.enqueue({
858
- type: stopSignalType
859
- });
860
- return this;
861
- }
862
-
863
- /**
864
- * Stops the interpreter and unsubscribe all listeners.
865
- */
866
- stop() {
867
- if (this._parent) {
868
- throw new Error('A non-root actor cannot be stopped directly.');
869
- }
870
- return this._stop();
871
- }
872
- _complete() {
873
- for (const observer of this.observers) {
874
- observer.complete?.();
875
- }
876
- this.observers.clear();
877
- }
878
- _error(data) {
879
- for (const observer of this.observers) {
880
- observer.error?.(data);
881
- }
882
- this.observers.clear();
883
- }
884
- _stopProcedure() {
885
- if (this.status !== ActorStatus.Running) {
886
- // Interpreter already stopped; do nothing
887
- return this;
888
- }
889
-
890
- // Cancel all delayed events
891
- for (const key of Object.keys(this.delayedEventsMap)) {
892
- this.clock.clearTimeout(this.delayedEventsMap[key]);
893
- }
894
-
895
- // TODO: mailbox.reset
896
- this.mailbox.clear();
897
- // TODO: after `stop` we must prepare ourselves for receiving events again
898
- // events sent *after* stop signal must be queued
899
- // it seems like this should be the common behavior for all of our consumers
900
- // so perhaps this should be unified somehow for all of them
901
- this.mailbox = new Mailbox(this._process.bind(this));
902
- this.status = ActorStatus.Stopped;
903
- this.system._unregister(this);
904
- return this;
905
- }
906
-
907
- /**
908
- * Sends an event to the running interpreter to trigger a transition.
909
- *
910
- * @param event The event to send
911
- */
912
- send(event) {
913
- if (typeof event === 'string') {
914
- throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
915
- }
916
- if (this.status === ActorStatus.Stopped) {
917
- // do nothing
918
- {
919
- const eventString = JSON.stringify(event);
920
- console.warn(`Event "${event.type.toString()}" was sent to stopped actor "${this.id} (${this.sessionId})". This actor has already reached its final state, and will not transition.\nEvent: ${eventString}`);
921
- }
922
- return;
923
- }
924
- if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
925
- throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
926
- // tslint:disable-next-line:max-line-length
927
- }". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
928
- }
929
- this.mailbox.enqueue(event);
930
- }
931
-
932
- // TODO: make private (and figure out a way to do this within the machine)
933
- delaySend(sendAction) {
934
- this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
935
- if ('to' in sendAction.params && sendAction.params.to) {
936
- sendAction.params.to.send(sendAction.params.event);
937
- } else {
938
- this.send(sendAction.params.event);
939
- }
940
- }, sendAction.params.delay);
941
- }
942
-
943
- // TODO: make private (and figure out a way to do this within the machine)
944
- cancel(sendId) {
945
- this.clock.clearTimeout(this.delayedEventsMap[sendId]);
946
- delete this.delayedEventsMap[sendId];
947
- }
948
- attachDevTools() {
949
- const {
950
- devTools
951
- } = this.options;
952
- if (devTools) {
953
- const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
954
- resolvedDevToolsAdapter(this);
955
- }
956
- }
957
- toJSON() {
958
- return {
959
- id: this.id
960
- };
961
- }
962
- getPersistedState() {
963
- return this.logic.getPersistedState?.(this._state);
964
- }
965
- [symbolObservable]() {
966
- return this;
967
- }
968
- getSnapshot() {
969
- return this.logic.getSnapshot ? this.logic.getSnapshot(this._state) : this._state;
970
- }
971
- }
972
-
973
- /**
974
- * Creates a new Interpreter instance for the given machine with the provided options, if any.
975
- *
976
- * @param machine The machine to interpret
977
- * @param options Interpreter options
978
- */
979
-
980
- function interpret(logic, options) {
981
- const interpreter = new Interpreter(logic, options);
982
- return interpreter;
983
- }
984
-
985
- /**
986
- * Returns actor logic from a transition function and its initial state.
987
- *
988
- * A transition function is a function that takes the current state and an event and returns the next state.
989
- *
990
- * @param transition The transition function that returns the next state given the current state and event.
991
- * @param initialState The initial state of the transition function.
992
- * @returns Actor logic
993
- */
994
- function fromTransition(transition, initialState) {
995
- const logic = {
996
- config: transition,
997
- transition: (state, event, actorContext) => {
998
- return transition(state, event, actorContext);
999
- },
1000
- getInitialState: (_, input) => {
1001
- return typeof initialState === 'function' ? initialState({
1002
- input
1003
- }) : initialState;
1004
- },
1005
- getSnapshot: state => state,
1006
- getPersistedState: state => state,
1007
- restoreState: state => state
1008
- };
1009
- return logic;
1010
- }
1011
-
1012
- function fromPromise(
1013
- // TODO: add types
1014
- promiseCreator) {
1015
- const resolveEventType = '$$xstate.resolve';
1016
- const rejectEventType = '$$xstate.reject';
1017
-
1018
- // TODO: add event types
1019
- const logic = {
1020
- config: promiseCreator,
1021
- transition: (state, event) => {
698
+ defer
699
+ }) => {
1022
700
  if (state.status !== 'active') {
1023
701
  return state;
1024
702
  }
1025
703
  switch (event.type) {
1026
- case resolveEventType:
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
+ });
1027
713
  return {
1028
714
  ...state,
1029
- status: 'done',
1030
- data: event.data,
1031
- input: undefined
715
+ data: event.data
1032
716
  };
1033
- case rejectEventType:
717
+ case errorEventType:
1034
718
  return {
1035
719
  ...state,
1036
720
  status: 'error',
721
+ input: undefined,
1037
722
  data: event.data,
1038
- input: undefined
723
+ subscription: undefined
724
+ };
725
+ case completeEventType:
726
+ return {
727
+ ...state,
728
+ status: 'done',
729
+ input: undefined,
730
+ subscription: undefined
1039
731
  };
1040
732
  case stopSignalType:
733
+ state.subscription.unsubscribe();
1041
734
  return {
1042
735
  ...state,
1043
736
  status: 'canceled',
1044
- input: undefined
737
+ input: undefined,
738
+ subscription: undefined
1045
739
  };
1046
740
  default:
1047
741
  return state;
1048
742
  }
1049
743
  },
744
+ getInitialState: (_, input) => {
745
+ return {
746
+ subscription: undefined,
747
+ status: 'active',
748
+ data: undefined,
749
+ input
750
+ };
751
+ },
1050
752
  start: (state, {
1051
753
  self,
1052
754
  system
1053
755
  }) => {
1054
- // TODO: determine how to allow customizing this so that promises
1055
- // can be restarted if necessary
1056
- if (state.status !== 'active') {
756
+ if (state.status === 'done') {
757
+ // Do not restart a completed observable
1057
758
  return;
1058
759
  }
1059
- const resolvedPromise = Promise.resolve(promiseCreator({
760
+ state.subscription = observableCreator({
1060
761
  input: state.input,
1061
762
  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;
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
+ });
1076
780
  }
1077
- self.send({
1078
- type: rejectEventType,
1079
- data: errorData
1080
- });
1081
781
  });
1082
782
  },
1083
- getInitialState: (_, input) => {
1084
- return {
1085
- status: 'active',
1086
- data: undefined,
1087
- input
1088
- };
1089
- },
1090
783
  getSnapshot: state => state.data,
784
+ getPersistedState: ({
785
+ status,
786
+ data,
787
+ input
788
+ }) => ({
789
+ status,
790
+ data,
791
+ input
792
+ }),
1091
793
  getStatus: state => state,
1092
- getPersistedState: state => state,
1093
- restoreState: state => state
794
+ restoreState: state => ({
795
+ ...state,
796
+ subscription: undefined
797
+ })
1094
798
  };
1095
799
  return logic;
1096
800
  }
1097
801
 
1098
- // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
1099
- function fromObservable(observableCreator) {
1100
- const nextEventType = '$$xstate.next';
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
+ */
810
+
811
+ function fromEventObservable(lazyObservable) {
1101
812
  const errorEventType = '$$xstate.error';
1102
813
  const completeEventType = '$$xstate.complete';
1103
814
 
1104
- // TODO: add event types
815
+ // TODO: event types
1105
816
  const logic = {
1106
- config: observableCreator,
1107
- transition: (state, event, {
1108
- self,
1109
- id,
1110
- defer
1111
- }) => {
817
+ config: lazyObservable,
818
+ transition: (state, event) => {
1112
819
  if (state.status !== 'active') {
1113
820
  return state;
1114
821
  }
1115
822
  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
823
  case errorEventType:
1130
824
  return {
1131
825
  ...state,
@@ -1169,15 +863,12 @@ function fromObservable(observableCreator) {
1169
863
  // Do not restart a completed observable
1170
864
  return;
1171
865
  }
1172
- state.subscription = observableCreator({
866
+ state.subscription = lazyObservable({
1173
867
  input: state.input,
1174
868
  system
1175
869
  }).subscribe({
1176
870
  next: value => {
1177
- self.send({
1178
- type: nextEventType,
1179
- data: value
1180
- });
871
+ self._parent?.send(value);
1181
872
  },
1182
873
  error: err => {
1183
874
  self.send({
@@ -1192,7 +883,7 @@ function fromObservable(observableCreator) {
1192
883
  }
1193
884
  });
1194
885
  },
1195
- getSnapshot: state => state.data,
886
+ getSnapshot: _ => undefined,
1196
887
  getPersistedState: ({
1197
888
  status,
1198
889
  data,
@@ -1211,226 +902,504 @@ function fromObservable(observableCreator) {
1211
902
  return logic;
1212
903
  }
1213
904
 
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
- */
905
+ function fromCallback(invokeCallback) {
906
+ const logic = {
907
+ config: invokeCallback,
908
+ start: (_state, {
909
+ self
910
+ }) => {
911
+ self.send({
912
+ type: startSignalType
913
+ });
914
+ },
915
+ transition: (state, event, {
916
+ self,
917
+ id,
918
+ system
919
+ }) => {
920
+ if (event.type === startSignalType) {
921
+ const sender = eventForParent => {
922
+ if (state.canceled) {
923
+ return;
924
+ }
925
+ self._parent?.send(eventForParent);
926
+ };
927
+ const receiver = newListener => {
928
+ state.receivers.add(newListener);
929
+ };
930
+ state.dispose = invokeCallback(sender, receiver, {
931
+ input: state.input,
932
+ system
933
+ });
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
+ }
1222
1176
 
1223
- function fromEventObservable(lazyObservable) {
1224
- const errorEventType = '$$xstate.error';
1225
- const completeEventType = '$$xstate.complete';
1177
+ // array of functions to defer
1226
1178
 
1227
- // TODO: event types
1228
- const logic = {
1229
- config: lazyObservable,
1230
- transition: (state, event) => {
1231
- if (state.status !== 'active') {
1232
- return state;
1233
- }
1234
- switch (event.type) {
1235
- case errorEventType:
1236
- return {
1237
- ...state,
1238
- status: 'error',
1239
- input: undefined,
1240
- data: event.data,
1241
- subscription: undefined
1242
- };
1243
- case completeEventType:
1244
- return {
1245
- ...state,
1246
- status: 'done',
1247
- input: undefined,
1248
- subscription: undefined
1249
- };
1250
- case stopSignalType:
1251
- state.subscription.unsubscribe();
1252
- return {
1253
- ...state,
1254
- status: 'canceled',
1255
- input: undefined,
1256
- subscription: undefined
1257
- };
1258
- default:
1259
- return state;
1260
- }
1261
- },
1262
- getInitialState: (_, input) => {
1263
- return {
1264
- subscription: undefined,
1265
- status: 'active',
1266
- data: undefined,
1267
- input
1268
- };
1269
- },
1270
- start: (state, {
1271
- self,
1272
- system
1273
- }) => {
1274
- if (state.status === 'done') {
1275
- // Do not restart a completed observable
1276
- return;
1277
- }
1278
- state.subscription = lazyObservable({
1279
- input: state.input,
1280
- system
1281
- }).subscribe({
1282
- next: value => {
1283
- self._parent?.send(value);
1284
- },
1285
- error: err => {
1286
- self.send({
1287
- type: errorEventType,
1288
- data: err
1289
- });
1290
- },
1291
- complete: () => {
1292
- self.send({
1293
- type: completeEventType
1294
- });
1295
- }
1296
- });
1297
- },
1298
- getSnapshot: _ => undefined,
1299
- getPersistedState: ({
1300
- status,
1301
- data,
1302
- input
1303
- }) => ({
1304
- status,
1305
- data,
1306
- input
1307
- }),
1308
- getStatus: state => state,
1309
- restoreState: state => ({
1310
- ...state,
1311
- subscription: undefined
1312
- })
1313
- };
1314
- return logic;
1315
- }
1179
+ update(state) {
1180
+ // Update state
1181
+ this._state = state;
1182
+ const snapshot = this.getSnapshot();
1316
1183
 
1317
- function fromCallback(invokeCallback) {
1318
- const logic = {
1319
- config: invokeCallback,
1320
- start: (_state, {
1321
- self
1322
- }) => {
1323
- self.send({
1324
- type: startSignalType
1325
- });
1326
- },
1327
- transition: (state, event, {
1328
- self,
1329
- id,
1330
- system
1331
- }) => {
1332
- if (event.type === startSignalType) {
1333
- const sender = eventForParent => {
1334
- if (state.canceled) {
1335
- return;
1336
- }
1337
- self._parent?.send(eventForParent);
1338
- };
1339
- const receiver = newListener => {
1340
- state.receivers.add(newListener);
1341
- };
1342
- state.dispose = invokeCallback(sender, receiver, {
1343
- input: state.input,
1344
- system
1345
- });
1346
- if (isPromiseLike(state.dispose)) {
1347
- state.dispose.then(resolved => {
1348
- self._parent?.send(doneInvoke(id, resolved));
1349
- state.canceled = true;
1350
- }, errorData => {
1351
- state.canceled = true;
1352
- self._parent?.send(error(id, errorData));
1353
- });
1354
- }
1355
- return state;
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);
1356
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);
1357
1252
  if (event.type === stopSignalType) {
1358
- state.canceled = true;
1359
- if (isFunction(state.dispose)) {
1360
- state.dispose();
1361
- }
1362
- return state;
1253
+ this._stopProcedure();
1254
+ this._complete();
1363
1255
  }
1364
- if (isSignal(event.type)) {
1365
- // TODO: unrecognized signal
1366
- return state;
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;
1265
+ }
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}`);
1367
1341
  }
1368
- if (!isSignal(event.type)) {
1369
- state.receivers.forEach(receiver => receiver(event));
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);
1370
1359
  }
1371
- return state;
1372
- },
1373
- getInitialState: (_, input) => {
1374
- return {
1375
- canceled: false,
1376
- receivers: new Set(),
1377
- dispose: undefined,
1378
- input
1379
- };
1380
- },
1381
- getSnapshot: () => undefined,
1382
- getPersistedState: ({
1383
- input
1384
- }) => input
1385
- };
1386
- return logic;
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
+ }
1387
1391
  }
1388
1392
 
1389
- const startSignalType = 'xstate.init';
1390
- const stopSignalType = 'xstate.stop';
1391
- const startSignal = {
1392
- type: 'xstate.init'
1393
- };
1394
- const stopSignal = {
1395
- type: 'xstate.stop'
1396
- };
1397
1393
  /**
1398
- * An object that expresses the actor logic in reaction to received events,
1399
- * 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.
1400
1395
  *
1401
- * @template TReceived The received event
1402
- * @template TSnapshot The emitted value
1396
+ * @param machine The machine to interpret
1397
+ * @param options Interpreter options
1403
1398
  */
1404
1399
 
1405
- function isSignal(eventType) {
1406
- return eventType === startSignalType || eventType === stopSignalType;
1407
- }
1408
- function isActorRef(item) {
1409
- return !!item && typeof item === 'object' && typeof item.send === 'function';
1410
- }
1411
-
1412
- // TODO: refactor the return type, this could be written in a better way
1413
- // but it's best to avoid unneccessary breaking changes now
1414
- // @deprecated use `interpret(actorLogic)` instead
1415
- function toActorRef(actorRefLike) {
1416
- return {
1417
- subscribe: () => ({
1418
- unsubscribe: () => void 0
1419
- }),
1420
- id: 'anonymous',
1421
- sessionId: '',
1422
- getSnapshot: () => undefined,
1423
- [symbolObservable]: function () {
1424
- return this;
1425
- },
1426
- status: ActorStatus.Running,
1427
- stop: () => void 0,
1428
- ...actorRefLike
1429
- };
1430
- }
1431
- const emptyLogic = fromTransition(_ => undefined, undefined);
1432
- function createEmptyActor() {
1433
- return interpret(emptyLogic);
1400
+ function interpret(logic, options) {
1401
+ const interpreter = new Interpreter(logic, options);
1402
+ return interpreter;
1434
1403
  }
1435
1404
 
1436
1405
  function invoke(invokeDef) {
@@ -1447,42 +1416,32 @@ function invoke(invokeDef) {
1447
1416
  src
1448
1417
  } = invokeDef;
1449
1418
  let resolvedInvokeAction;
1450
- 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
+ });
1451
1438
  resolvedInvokeAction = {
1452
1439
  type,
1453
1440
  params: {
1454
1441
  ...invokeDef,
1455
- ref: src
1442
+ ref
1456
1443
  }
1457
1444
  };
1458
- } else {
1459
- const referenced = resolveReferencedActor(state.machine.options.actors[src]);
1460
- if (!referenced) {
1461
- resolvedInvokeAction = {
1462
- type,
1463
- params: invokeDef
1464
- };
1465
- } else {
1466
- const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
1467
- const ref = interpret(referenced.src, {
1468
- id,
1469
- src,
1470
- parent: actorContext?.self,
1471
- systemId: invokeDef.systemId,
1472
- input: typeof input === 'function' ? input({
1473
- context: state.context,
1474
- event,
1475
- self: actorContext?.self
1476
- }) : input
1477
- });
1478
- resolvedInvokeAction = {
1479
- type,
1480
- params: {
1481
- ...invokeDef,
1482
- ref
1483
- }
1484
- };
1485
- }
1486
1445
  }
1487
1446
  const actorRef = resolvedInvokeAction.params.ref;
1488
1447
  const invokedState = cloneState(state, {
@@ -1597,7 +1556,7 @@ function evaluateGuard(guard, context, event, state) {
1597
1556
  const {
1598
1557
  machine
1599
1558
  } = state;
1600
- const predicate = machine?.options?.guards?.[guard.type] ?? guard.predicate;
1559
+ const predicate = machine?.implementations?.guards?.[guard.type] ?? guard.predicate;
1601
1560
  if (!predicate) {
1602
1561
  throw new Error(`Guard '${guard.type}' is not implemented.'.`);
1603
1562
  }
@@ -1610,14 +1569,28 @@ function evaluateGuard(guard, context, event, state) {
1610
1569
  });
1611
1570
  }
1612
1571
  function toGuardDefinition(guardConfig, getPredicate) {
1572
+ // TODO: check for cycles and consider a refactor to more lazily evaluated guards
1573
+ // TODO: resolve this more recursively: https://github.com/statelyai/xstate/pull/4064#discussion_r1229915724
1613
1574
  if (isString(guardConfig)) {
1614
- return {
1615
- type: guardConfig,
1616
- predicate: getPredicate?.(guardConfig) || undefined,
1617
- params: {
1618
- type: guardConfig
1619
- }
1620
- };
1575
+ const predicateOrDef = getPredicate?.(guardConfig);
1576
+ if (isFunction(predicateOrDef)) {
1577
+ return {
1578
+ type: guardConfig,
1579
+ predicate: predicateOrDef,
1580
+ params: {
1581
+ type: guardConfig
1582
+ }
1583
+ };
1584
+ } else if (predicateOrDef) {
1585
+ return predicateOrDef;
1586
+ } else {
1587
+ return {
1588
+ type: guardConfig,
1589
+ params: {
1590
+ type: guardConfig
1591
+ }
1592
+ };
1593
+ }
1621
1594
  }
1622
1595
  if (isFunction(guardConfig)) {
1623
1596
  return {
@@ -1629,12 +1602,24 @@ function toGuardDefinition(guardConfig, getPredicate) {
1629
1602
  }
1630
1603
  };
1631
1604
  }
1632
- return {
1633
- type: guardConfig.type,
1634
- params: guardConfig.params || guardConfig,
1635
- children: guardConfig.children?.map(childGuard => toGuardDefinition(childGuard, getPredicate)),
1636
- predicate: getPredicate?.(guardConfig.type) || guardConfig.predicate
1637
- };
1605
+ const predicateOrDef = getPredicate?.(guardConfig.type);
1606
+ if (isFunction(predicateOrDef)) {
1607
+ return {
1608
+ type: guardConfig.type,
1609
+ params: guardConfig.params || guardConfig,
1610
+ children: guardConfig.children?.map(childGuard => toGuardDefinition(childGuard, getPredicate)),
1611
+ predicate: getPredicate?.(guardConfig.type) || guardConfig.predicate
1612
+ };
1613
+ } else if (predicateOrDef) {
1614
+ return predicateOrDef;
1615
+ } else {
1616
+ return {
1617
+ type: guardConfig.type,
1618
+ params: guardConfig.params || guardConfig,
1619
+ children: guardConfig.children?.map(childGuard => toGuardDefinition(childGuard, getPredicate)),
1620
+ predicate: guardConfig.predicate
1621
+ };
1622
+ }
1638
1623
  }
1639
1624
 
1640
1625
  function getOutput(configuration, context, event) {
@@ -1839,7 +1824,7 @@ function formatTransition(stateNode, transitionConfig) {
1839
1824
  const reenter = transitionConfig.reenter ?? false;
1840
1825
  const {
1841
1826
  guards
1842
- } = stateNode.machine.options;
1827
+ } = stateNode.machine.implementations;
1843
1828
  const target = resolveTarget(stateNode, normalizedTarget);
1844
1829
 
1845
1830
  // TODO: should this be part of a lint rule instead?
@@ -1935,7 +1920,7 @@ function formatInitialTransition(stateNode, _target) {
1935
1920
  return formatTransition(stateNode, {
1936
1921
  target: toArray(_target.target).map(t => {
1937
1922
  if (isString(t)) {
1938
- return isStateId(t) ? t : `${stateNode.machine.delimiter}${t}`;
1923
+ return isStateId(t) ? t : `${STATE_DELIMITER}${t}`;
1939
1924
  }
1940
1925
  return t;
1941
1926
  }),
@@ -1955,7 +1940,7 @@ function resolveTarget(stateNode, targets) {
1955
1940
  if (isStateId(target)) {
1956
1941
  return stateNode.machine.getStateNodeById(target);
1957
1942
  }
1958
- const isInternalTarget = target[0] === stateNode.machine.delimiter;
1943
+ const isInternalTarget = target[0] === STATE_DELIMITER;
1959
1944
  // If internal target is defined on machine,
1960
1945
  // do not include machine key on target
1961
1946
  if (isInternalTarget && !stateNode.parent) {
@@ -2038,7 +2023,7 @@ function getStateNodeByPath(stateNode, statePath) {
2038
2023
  // throw e;
2039
2024
  }
2040
2025
  }
2041
- const arrayStatePath = toStatePath(statePath, stateNode.machine.delimiter).slice();
2026
+ const arrayStatePath = toStatePath(statePath).slice();
2042
2027
  let currentStateNode = stateNode;
2043
2028
  while (arrayStatePath.length) {
2044
2029
  const key = arrayStatePath.shift();
@@ -2056,7 +2041,7 @@ function getStateNodeByPath(stateNode, statePath) {
2056
2041
  * @param state The state value or State instance
2057
2042
  */
2058
2043
  function getStateNodes(stateNode, state) {
2059
- const stateValue = state instanceof State ? state.value : toStateValue(state, stateNode.machine.delimiter);
2044
+ const stateValue = state instanceof State ? state.value : toStateValue(state);
2060
2045
  if (isString(stateValue)) {
2061
2046
  return [stateNode, stateNode.states[stateValue]];
2062
2047
  }
@@ -2249,40 +2234,18 @@ function computeExitSet(transitions, configuration, historyValue) {
2249
2234
  * @param mutConfiguration
2250
2235
  */
2251
2236
 
2252
- function microstep(transitions, currentState, actorCtx, event) {
2253
- const {
2254
- machine
2255
- } = currentState;
2256
- // Transition will "apply" if:
2257
- // - the state node is the initial state (there is no current state)
2258
- // - OR there are transitions
2259
- const willTransition = currentState._initial || transitions.length > 0;
2237
+ function microstep(transitions, currentState, actorCtx, event, isInitial) {
2260
2238
  const mutConfiguration = new Set(currentState.configuration);
2261
- if (!currentState._initial && !willTransition) {
2262
- const inertState = cloneState(currentState, {});
2263
- inertState.changed = false;
2264
- return inertState;
2265
- }
2266
- const [microstate, actions] = microstepProcedure(currentState._initial ? [{
2267
- target: [...currentState.configuration].filter(isAtomicStateNode),
2268
- source: machine.root,
2269
- reenter: true,
2270
- actions: [],
2271
- eventType: null,
2272
- toJSON: null // TODO: fix
2273
- }] : transitions, currentState, mutConfiguration, event, actorCtx);
2274
- const {
2275
- context
2276
- } = microstate;
2277
- const nextState = cloneState(microstate, {
2278
- value: {},
2279
- // TODO: make optional
2280
- transitions
2239
+ if (!transitions.length) {
2240
+ return currentState;
2241
+ }
2242
+ const microstate = microstepProcedure(transitions, currentState, mutConfiguration, event, actorCtx, isInitial);
2243
+ return cloneState(microstate, {
2244
+ value: {} // TODO: make optional
2281
2245
  });
2282
- nextState.changed = currentState._initial ? undefined : !stateValuesEqual(nextState.value, currentState.value) || actions.length > 0 || context !== currentState.context;
2283
- return nextState;
2284
2246
  }
2285
- function microstepProcedure(transitions, currentState, mutConfiguration, event, actorCtx) {
2247
+
2248
+ function microstepProcedure(transitions, currentState, mutConfiguration, event, actorCtx, isInitial) {
2286
2249
  const actions = [];
2287
2250
  const historyValue = {
2288
2251
  ...currentState.historyValue
@@ -2291,7 +2254,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2291
2254
  const internalQueue = [...currentState._internalQueue];
2292
2255
 
2293
2256
  // Exit states
2294
- if (!currentState._initial) {
2257
+ if (!isInitial) {
2295
2258
  exitStates(filteredTransitions, mutConfiguration, historyValue, actions);
2296
2259
  }
2297
2260
 
@@ -2299,7 +2262,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2299
2262
  actions.push(...filteredTransitions.flatMap(t => t.actions));
2300
2263
 
2301
2264
  // Enter states
2302
- enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue);
2265
+ enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial);
2303
2266
  const nextConfiguration = [...mutConfiguration];
2304
2267
  const done = isInFinalState(nextConfiguration);
2305
2268
  if (done) {
@@ -2307,10 +2270,10 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2307
2270
  actions.push(...finalActions);
2308
2271
  }
2309
2272
  try {
2310
- const [nextState, resolvedActions] = resolveActionsAndContext(actions, event, currentState, actorCtx);
2273
+ const nextState = resolveActionsAndContext(actions, event, currentState, actorCtx);
2311
2274
  const output = done ? getOutput(nextConfiguration, nextState.context, event) : undefined;
2312
2275
  internalQueue.push(...nextState._internalQueue);
2313
- return [cloneState(currentState, {
2276
+ return cloneState(currentState, {
2314
2277
  configuration: nextConfiguration,
2315
2278
  historyValue,
2316
2279
  _internalQueue: internalQueue,
@@ -2318,20 +2281,20 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2318
2281
  done,
2319
2282
  output,
2320
2283
  children: nextState.children
2321
- }), resolvedActions];
2284
+ });
2322
2285
  } catch (e) {
2323
2286
  // TODO: Refactor this once proper error handling is implemented.
2324
2287
  // See https://github.com/statelyai/rfcs/pull/4
2325
2288
  throw e;
2326
2289
  }
2327
2290
  }
2328
- function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue) {
2291
+ function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial) {
2329
2292
  const statesToEnter = new Set();
2330
2293
  const statesForDefaultEntry = new Set();
2331
2294
  computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
2332
2295
 
2333
2296
  // In the initial state, the root state node is "entered".
2334
- if (currentState._initial) {
2297
+ if (isInitial) {
2335
2298
  statesForDefaultEntry.add(currentState.machine.root);
2336
2299
  }
2337
2300
  for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
@@ -2456,7 +2419,7 @@ function exitStates(transitions, mutConfiguration, historyValue, actions) {
2456
2419
  }
2457
2420
  }
2458
2421
  for (const s of statesToExit) {
2459
- actions.push(...s.exit.flat(), ...s.invoke.map(def => stop(def.id)));
2422
+ actions.push(...s.exit, ...s.invoke.map(def => stop(def.id)));
2460
2423
  mutConfiguration.delete(s);
2461
2424
  }
2462
2425
  }
@@ -2464,11 +2427,9 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
2464
2427
  const {
2465
2428
  machine
2466
2429
  } = currentState;
2467
- const resolvedActions = [];
2468
2430
  const raiseActions = [];
2469
2431
  let intermediateState = currentState;
2470
2432
  function handleAction(action) {
2471
- resolvedActions.push(action);
2472
2433
  if (actorCtx?.self.status === ActorStatus.Running) {
2473
2434
  action.execute?.(actorCtx);
2474
2435
  } else {
@@ -2476,7 +2437,7 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
2476
2437
  }
2477
2438
  }
2478
2439
  function resolveAction(actionObject) {
2479
- const executableActionObject = resolveActionObject(actionObject, machine.options.actions);
2440
+ const executableActionObject = resolveActionObject(actionObject, machine.implementations.actions);
2480
2441
  if (isDynamicAction(executableActionObject)) {
2481
2442
  const [nextState, resolvedAction] = executableActionObject.resolve(event, {
2482
2443
  state: intermediateState,
@@ -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
 
@@ -2501,9 +2462,9 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
2501
2462
  for (const actionObject of actions) {
2502
2463
  resolveAction(actionObject);
2503
2464
  }
2504
- return [cloneState(intermediateState, {
2465
+ return cloneState(intermediateState, {
2505
2466
  _internalQueue: raiseActions.map(a => a.params.event)
2506
- }), resolvedActions];
2467
+ });
2507
2468
  }
2508
2469
  function macrostep(state, event, actorCtx) {
2509
2470
  if (event.type === WILDCARD) {
@@ -2514,7 +2475,7 @@ function macrostep(state, event, actorCtx) {
2514
2475
 
2515
2476
  // Handle stop event
2516
2477
  if (event.type === stopSignalType) {
2517
- nextState = stopStep(event, nextState, actorCtx)[0];
2478
+ nextState = stopStep(event, nextState, actorCtx);
2518
2479
  states.push(nextState);
2519
2480
  return {
2520
2481
  state: nextState,
@@ -2527,7 +2488,7 @@ function macrostep(state, event, actorCtx) {
2527
2488
  // Determine the next state based on the next microstep
2528
2489
  if (nextEvent.type !== init) {
2529
2490
  const transitions = selectTransitions(nextEvent, nextState);
2530
- nextState = microstep(transitions, state, actorCtx, nextEvent);
2491
+ nextState = microstep(transitions, state, actorCtx, nextEvent, false);
2531
2492
  states.push(nextState);
2532
2493
  }
2533
2494
  while (!nextState.done) {
@@ -2538,12 +2499,12 @@ function macrostep(state, event, actorCtx) {
2538
2499
  } else {
2539
2500
  nextEvent = nextState._internalQueue[0];
2540
2501
  const transitions = selectTransitions(nextEvent, nextState);
2541
- nextState = microstep(transitions, nextState, actorCtx, nextEvent);
2502
+ nextState = microstep(transitions, nextState, actorCtx, nextEvent, false);
2542
2503
  nextState._internalQueue.shift();
2543
2504
  states.push(nextState);
2544
2505
  }
2545
2506
  } else {
2546
- nextState = microstep(enabledTransitions, nextState, actorCtx, nextEvent);
2507
+ nextState = microstep(enabledTransitions, nextState, actorCtx, nextEvent, false);
2547
2508
  states.push(nextState);
2548
2509
  }
2549
2510
  }
@@ -2597,20 +2558,6 @@ function resolveStateValue(rootNode, stateValue) {
2597
2558
  const configuration = getConfiguration(getStateNodes(rootNode, stateValue));
2598
2559
  return getStateValue(rootNode, [...configuration]);
2599
2560
  }
2600
- function stateValuesEqual(a, b) {
2601
- if (a === b) {
2602
- return true;
2603
- }
2604
- if (a === undefined || b === undefined) {
2605
- return false;
2606
- }
2607
- if (isString(a) || isString(b)) {
2608
- return a === b;
2609
- }
2610
- const aKeys = Object.keys(a);
2611
- const bKeys = Object.keys(b);
2612
- return aKeys.length === bKeys.length && aKeys.every(key => stateValuesEqual(a[key], b[key]));
2613
- }
2614
2561
  function getInitialConfiguration(rootNode) {
2615
2562
  const configuration = [];
2616
2563
  const initialTransition = rootNode.initial;
@@ -2633,15 +2580,6 @@ class State {
2633
2580
  */
2634
2581
  // TODO: add an explicit type for `output`
2635
2582
 
2636
- /**
2637
- * Indicates whether the state has changed from the previous state. A state is considered "changed" if:
2638
- *
2639
- * - Its value is not equal to its previous value, or:
2640
- * - It has any new actions (side-effects) to execute.
2641
- *
2642
- * An initial state (with no history) will return `undefined`.
2643
- */
2644
-
2645
2583
  /**
2646
2584
  * The enabled state nodes representative of the state value.
2647
2585
  */
@@ -2664,7 +2602,6 @@ class State {
2664
2602
  meta: {},
2665
2603
  configuration: [],
2666
2604
  // TODO: fix,
2667
- transitions: [],
2668
2605
  children: {}
2669
2606
  }, machine);
2670
2607
  }
@@ -2694,8 +2631,6 @@ class State {
2694
2631
  this.context = void 0;
2695
2632
  this.historyValue = {};
2696
2633
  this._internalQueue = void 0;
2697
- this._initial = false;
2698
- this.changed = void 0;
2699
2634
  this.configuration = void 0;
2700
2635
  this.children = void 0;
2701
2636
  this.context = config.context;
@@ -2716,12 +2651,12 @@ class State {
2716
2651
  * @param stateValue
2717
2652
  * @param delimiter The character(s) that separate each subpath in the string state node path.
2718
2653
  */
2719
- toStrings(stateValue = this.value, delimiter = '.') {
2654
+ toStrings(stateValue = this.value) {
2720
2655
  if (isString(stateValue)) {
2721
2656
  return [stateValue];
2722
2657
  }
2723
2658
  const valueKeys = Object.keys(stateValue);
2724
- return valueKeys.concat(...valueKeys.map(key => this.toStrings(stateValue[key], delimiter).map(s => key + delimiter + s)));
2659
+ return valueKeys.concat(...valueKeys.map(key => this.toStrings(stateValue[key]).map(s => key + STATE_DELIMITER + s)));
2725
2660
  }
2726
2661
  toJSON() {
2727
2662
  const {
@@ -2829,11 +2764,14 @@ function stop(actorRef) {
2829
2764
  actor
2830
2765
  }
2831
2766
  }, (event, {
2832
- state
2767
+ state,
2768
+ actorContext
2833
2769
  }) => {
2834
2770
  const actorRefOrString = isFunction(actor) ? actor({
2835
2771
  context: state.context,
2836
- event
2772
+ event,
2773
+ self: actorContext?.self ?? {},
2774
+ system: actorContext?.system
2837
2775
  }) : actor;
2838
2776
  const actorRef = typeof actorRefOrString === 'string' ? state.children[actorRefOrString] : actorRefOrString;
2839
2777
  let children = state.children;
@@ -2917,61 +2855,60 @@ function log(expr = defaultLogExpr, label) {
2917
2855
  });
2918
2856
  }
2919
2857
 
2920
- function createSpawner(self, machine, context, event, mutCapturedActions) {
2921
- return (src, options = {}) => {
2858
+ function createSpawner(actorContext, {
2859
+ machine,
2860
+ context
2861
+ }, event, spawnedChildren) {
2862
+ const spawn = (src, options = {}) => {
2922
2863
  const {
2923
2864
  systemId
2924
2865
  } = options;
2925
- if (isString(src)) {
2926
- const referenced = resolveReferencedActor(machine.options.actors[src]);
2927
- if (referenced) {
2928
- const resolvedName = options.id ?? 'anon'; // TODO: better name
2929
- const input = 'input' in options ? options.input : referenced.input;
2930
-
2931
- // TODO: this should also receive `src`
2932
- const actorRef = interpret(referenced.src, {
2933
- id: resolvedName,
2934
- parent: self,
2935
- input: typeof input === 'function' ? input({
2936
- context,
2937
- event,
2938
- self
2939
- }) : input
2940
- });
2941
- mutCapturedActions.push(invoke({
2942
- id: actorRef.id,
2943
- // @ts-ignore TODO: fix types
2944
- src: actorRef,
2945
- // TODO
2946
- ref: actorRef,
2947
- meta: undefined,
2948
- input,
2949
- systemId
2950
- }));
2951
- return actorRef; // TODO: fix types
2952
- }
2953
-
2954
- throw new Error(`Actor logic '${src}' not implemented in machine '${machine.id}'`);
2866
+ if (typeof src === 'string') {
2867
+ const referenced = resolveReferencedActor(machine.implementations.actors[src]);
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;
2955
2886
  } else {
2956
2887
  // TODO: this should also receive `src`
2957
- // TODO: instead of anonymous, it should be a unique stable ID
2958
- const actorRef = interpret(src, {
2959
- id: options.id || 'anonymous',
2960
- parent: self,
2888
+ return interpret(src, {
2889
+ id: options.id,
2890
+ parent: actorContext.self,
2961
2891
  input: options.input,
2962
2892
  systemId
2963
2893
  });
2964
- mutCapturedActions.push(invoke({
2965
- // @ts-ignore TODO: fix types
2966
- src: actorRef,
2967
- ref: actorRef,
2968
- id: actorRef.id,
2969
- meta: undefined,
2970
- input: options.input
2971
- }));
2972
- return actorRef; // TODO: fix types
2973
2894
  }
2974
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
+ };
2975
2912
  }
2976
2913
 
2977
2914
  /**
@@ -2990,15 +2927,15 @@ function assign(assignment) {
2990
2927
  action,
2991
2928
  actorContext
2992
2929
  }) => {
2993
- const capturedActions = [];
2994
2930
  if (!state.context) {
2995
2931
  throw new Error('Cannot assign to undefined `context`. Ensure that `context` is defined in the machine config.');
2996
2932
  }
2933
+ const spawnedChildren = {};
2997
2934
  const args = {
2998
2935
  context: state.context,
2999
2936
  event,
3000
2937
  action,
3001
- spawn: createSpawner(actorContext?.self, state.machine, state.context, event, capturedActions),
2938
+ spawn: createSpawner(actorContext, state, event, spawnedChildren),
3002
2939
  self: actorContext?.self ?? {},
3003
2940
  system: actorContext?.system
3004
2941
  };
@@ -3013,12 +2950,15 @@ function assign(assignment) {
3013
2950
  }
3014
2951
  const updatedContext = Object.assign({}, state.context, partialUpdate);
3015
2952
  return [cloneState(state, {
3016
- context: updatedContext
2953
+ context: updatedContext,
2954
+ children: Object.keys(spawnedChildren).length ? {
2955
+ ...state.children,
2956
+ ...spawnedChildren
2957
+ } : state.children
3017
2958
  }), {
3018
2959
  type: assign$1,
3019
2960
  params: {
3020
- context: updatedContext,
3021
- actions: capturedActions
2961
+ context: updatedContext
3022
2962
  }
3023
2963
  }];
3024
2964
  });
@@ -3054,7 +2994,7 @@ function raise(eventOrExpr, options) {
3054
2994
  self: actorContext?.self ?? {},
3055
2995
  system: actorContext?.system
3056
2996
  };
3057
- const delaysMap = state.machine.options.delays;
2997
+ const delaysMap = state.machine.implementations.delays;
3058
2998
 
3059
2999
  // TODO: helper function for resolving Expr
3060
3000
  if (typeof eventOrExpr === 'string') {
@@ -3096,7 +3036,7 @@ function choose(guards) {
3096
3036
  state
3097
3037
  }) => {
3098
3038
  const matchedActions = guards.find(condition => {
3099
- const guard = condition.guard && toGuardDefinition(condition.guard, guardType => state.machine.options.guards[guardType]);
3039
+ const guard = condition.guard && toGuardDefinition(condition.guard, guardType => state.machine.implementations.guards[guardType]);
3100
3040
  return !guard || evaluateGuard(guard, state.context, event, state);
3101
3041
  })?.actions;
3102
3042
  return [state, {
@@ -3129,9 +3069,6 @@ function pure(getActions) {
3129
3069
  });
3130
3070
  }
3131
3071
 
3132
- const initEvent = {
3133
- type: init
3134
- };
3135
3072
  function resolveActionObject(actionObject, actionFunctionMap) {
3136
3073
  if (isDynamicAction(actionObject)) {
3137
3074
  return actionObject;
@@ -3292,7 +3229,6 @@ exports.choose = choose;
3292
3229
  exports.createEmptyActor = createEmptyActor;
3293
3230
  exports.createInitEvent = createInitEvent;
3294
3231
  exports.createInvokeId = createInvokeId;
3295
- exports.createSpawner = createSpawner;
3296
3232
  exports.done = done;
3297
3233
  exports.doneInvoke = doneInvoke;
3298
3234
  exports.error = error;
@@ -3315,10 +3251,10 @@ exports.getInitialConfiguration = getInitialConfiguration;
3315
3251
  exports.getPersistedState = getPersistedState;
3316
3252
  exports.getStateNodeByPath = getStateNodeByPath;
3317
3253
  exports.getStateNodes = getStateNodes;
3318
- exports.initEvent = initEvent;
3319
3254
  exports.interpret = interpret;
3320
3255
  exports.invoke = invoke$1;
3321
3256
  exports.isActorRef = isActorRef;
3257
+ exports.isAtomicStateNode = isAtomicStateNode;
3322
3258
  exports.isErrorEvent = isErrorEvent;
3323
3259
  exports.isInFinalState = isInFinalState;
3324
3260
  exports.isSignal = isSignal;
@@ -3339,7 +3275,6 @@ exports.resolveActionObject = resolveActionObject;
3339
3275
  exports.resolveActionsAndContext = resolveActionsAndContext;
3340
3276
  exports.resolveReferencedActor = resolveReferencedActor;
3341
3277
  exports.resolveStateValue = resolveStateValue;
3342
- exports.send = send;
3343
3278
  exports.sendParent = sendParent;
3344
3279
  exports.sendTo = sendTo;
3345
3280
  exports.startSignal = startSignal;