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