xstate 4.32.1 → 4.33.0

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.
package/es/interpreter.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import { __values, __spreadArray, __read, __assign } from './_virtual/_tslib.js';
2
- import { ActionTypes, SpecialTargets } from './types.js';
2
+ import { SpecialTargets, ActionTypes } from './types.js';
3
3
  import { isStateConfig, State, bindActionToState } from './State.js';
4
- import { errorPlatform, log, stop, start, cancel, send, update, error as error$1 } from './actionTypes.js';
5
- import { doneInvoke, initEvent, getActionFunction, error } from './actions.js';
4
+ import { raise, send, errorPlatform, update, error as error$1, log, stop, start, cancel } from './actionTypes.js';
5
+ import { initEvent, doneInvoke, toActionObjects, resolveActions, error, getActionFunction } from './actions.js';
6
6
  import { IS_PRODUCTION } from './environment.js';
7
- import { warn, mapContext, isFunction, toSCXMLEvent, toInvokeSource, isMachine, isPromiseLike, isObservable, isBehavior, reportUnhandledExceptionOnInvocation, symbolObservable, isArray, toEventObject, isString, isActor, uniqueId, toObserver } from './utils.js';
7
+ import { warn, mapContext, toObserver, toSCXMLEvent, flatten, isFunction, isPromiseLike, isObservable, isMachine, isBehavior, reportUnhandledExceptionOnInvocation, symbolObservable, isArray, toEventObject, isString, isActor, toInvokeSource, uniqueId } from './utils.js';
8
8
  import { Scheduler } from './scheduler.js';
9
- import { isSpawnedActor, createDeferredActor } from './Actor.js';
10
- import { isInFinalState } from './stateUtils.js';
9
+ import { createDeferredActor, isSpawnedActor } from './Actor.js';
11
10
  import { registry } from './registry.js';
12
11
  import { getGlobal, registerService } from './devTools.js';
13
12
  import { provide, consume } from './serviceScope.js';
@@ -128,17 +127,176 @@ function () {
128
127
  }
129
128
 
130
129
  if ('machine' in target) {
131
- // Send SCXML events to machines
132
- target.send(__assign(__assign({}, event), {
133
- name: event.name === error$1 ? "".concat(error(_this.id)) : event.name,
134
- origin: _this.sessionId
135
- }));
130
+ // perhaps those events should be rejected in the parent
131
+ // but atm it doesn't have easy access to all of the information that is required to do it reliably
132
+ if (_this.status !== InterpreterStatus.Stopped || _this.parent !== target || // we need to send events to the parent from exit handlers of a machine that reached its final state
133
+ _this.state.done) {
134
+ // Send SCXML events to machines
135
+ target.send(__assign(__assign({}, event), {
136
+ name: event.name === error$1 ? "".concat(error(_this.id)) : event.name,
137
+ origin: _this.sessionId
138
+ }));
139
+ }
136
140
  } else {
137
141
  // Send normal events to other targets
138
142
  target.send(event.data);
139
143
  }
140
144
  };
141
145
 
146
+ this._exec = function (action, context, _event, actionFunctionMap) {
147
+ if (actionFunctionMap === void 0) {
148
+ actionFunctionMap = _this.machine.options.actions;
149
+ }
150
+
151
+ var actionOrExec = action.exec || getActionFunction(action.type, actionFunctionMap);
152
+ var exec = isFunction(actionOrExec) ? actionOrExec : actionOrExec ? actionOrExec.exec : action.exec;
153
+
154
+ if (exec) {
155
+ try {
156
+ return exec(context, _event.data, !_this.machine.config.predictableActionArguments ? {
157
+ action: action,
158
+ state: _this.state,
159
+ _event: _event
160
+ } : {
161
+ action: action,
162
+ _event: _event
163
+ });
164
+ } catch (err) {
165
+ if (_this.parent) {
166
+ _this.parent.send({
167
+ type: 'xstate.error',
168
+ data: err
169
+ });
170
+ }
171
+
172
+ throw err;
173
+ }
174
+ }
175
+
176
+ switch (action.type) {
177
+ case send:
178
+ var sendAction = action;
179
+
180
+ if (typeof sendAction.delay === 'number') {
181
+ _this.defer(sendAction);
182
+
183
+ return;
184
+ } else {
185
+ if (sendAction.to) {
186
+ _this.sendTo(sendAction._event, sendAction.to);
187
+ } else {
188
+ _this.send(sendAction._event);
189
+ }
190
+ }
191
+
192
+ break;
193
+
194
+ case cancel:
195
+ _this.cancel(action.sendId);
196
+
197
+ break;
198
+
199
+ case start:
200
+ {
201
+ if (_this.status !== InterpreterStatus.Running) {
202
+ return;
203
+ }
204
+
205
+ var activity = action.activity; // If the activity will be stopped right after it's started
206
+ // (such as in transient states)
207
+ // don't bother starting the activity.
208
+
209
+ if ( // in v4 with `predictableActionArguments` invokes are called eagerly when the `this.state` still points to the previous state
210
+ !_this.machine.config.predictableActionArguments && !_this.state.activities[activity.id || activity.type]) {
211
+ break;
212
+ } // Invoked services
213
+
214
+
215
+ if (activity.type === ActionTypes.Invoke) {
216
+ var invokeSource = toInvokeSource(activity.src);
217
+ var serviceCreator = _this.machine.options.services ? _this.machine.options.services[invokeSource.type] : undefined;
218
+ var id = activity.id,
219
+ data = activity.data;
220
+
221
+ if (!IS_PRODUCTION) {
222
+ warn(!('forward' in activity), // tslint:disable-next-line:max-line-length
223
+ "`forward` property is deprecated (found in invocation of '".concat(activity.src, "' in in machine '").concat(_this.machine.id, "'). ") + "Please use `autoForward` instead.");
224
+ }
225
+
226
+ var autoForward = 'autoForward' in activity ? activity.autoForward : !!activity.forward;
227
+
228
+ if (!serviceCreator) {
229
+ // tslint:disable-next-line:no-console
230
+ if (!IS_PRODUCTION) {
231
+ warn(false, "No service found for invocation '".concat(activity.src, "' in machine '").concat(_this.machine.id, "'."));
232
+ }
233
+
234
+ return;
235
+ }
236
+
237
+ var resolvedData = data ? mapContext(data, context, _event) : undefined;
238
+
239
+ if (typeof serviceCreator === 'string') {
240
+ // TODO: warn
241
+ return;
242
+ }
243
+
244
+ var source = isFunction(serviceCreator) ? serviceCreator(context, _event.data, {
245
+ data: resolvedData,
246
+ src: invokeSource,
247
+ meta: activity.meta
248
+ }) : serviceCreator;
249
+
250
+ if (!source) {
251
+ // TODO: warn?
252
+ return;
253
+ }
254
+
255
+ var options = void 0;
256
+
257
+ if (isMachine(source)) {
258
+ source = resolvedData ? source.withContext(resolvedData) : source;
259
+ options = {
260
+ autoForward: autoForward
261
+ };
262
+ }
263
+
264
+ _this.spawn(source, id, options);
265
+ } else {
266
+ _this.spawnActivity(activity);
267
+ }
268
+
269
+ break;
270
+ }
271
+
272
+ case stop:
273
+ {
274
+ _this.stopChild(action.activity.id);
275
+
276
+ break;
277
+ }
278
+
279
+ case log:
280
+ var label = action.label,
281
+ value = action.value;
282
+
283
+ if (label) {
284
+ _this.logger(label, value);
285
+ } else {
286
+ _this.logger(value);
287
+ }
288
+
289
+ break;
290
+
291
+ default:
292
+ if (!IS_PRODUCTION) {
293
+ warn(false, "No implementation found for action type '".concat(action.type, "'"));
294
+ }
295
+
296
+ break;
297
+ }
298
+ };
299
+
142
300
  var resolvedOptions = __assign(__assign({}, Interpreter.defaultOptions), options);
143
301
 
144
302
  var clock = resolvedOptions.clock,
@@ -222,7 +380,9 @@ function () {
222
380
 
223
381
  this._state = state; // Execute actions
224
382
 
225
- if (this.options.execute) {
383
+ if ((!this.machine.config.predictableActionArguments || // this is currently required to execute initial actions as the `initialState` gets cached
384
+ // we can't just recompute it (and execute actions while doing so) because we try to preserve identity of actors created within initial assigns
385
+ _event === initEvent) && this.options.execute) {
226
386
  this.execute(this.state);
227
387
  } // Update children
228
388
 
@@ -289,9 +449,7 @@ function () {
289
449
  }
290
450
  }
291
451
 
292
- var isDone = isInFinalState(state.configuration || [], this.machine);
293
-
294
- if (this.state.configuration && isDone) {
452
+ if (this.state.done) {
295
453
  // get final child state node
296
454
  var finalChildStateNode = state.configuration.find(function (sn) {
297
455
  return sn.type === 'final' && sn.parent === _this.machine;
@@ -315,7 +473,7 @@ function () {
315
473
  }
316
474
  }
317
475
 
318
- this.stop();
476
+ this._stop();
319
477
  }
320
478
  };
321
479
  /*
@@ -340,42 +498,35 @@ function () {
340
498
  completeListener) {
341
499
  var _this = this;
342
500
 
343
- if (!nextListenerOrObserver) {
344
- return {
345
- unsubscribe: function () {
346
- return void 0;
347
- }
348
- };
349
- }
350
-
351
- var listener;
352
- var resolvedCompleteListener = completeListener;
501
+ var observer = toObserver(nextListenerOrObserver, _, completeListener);
502
+ this.listeners.add(observer.next); // Send current state to listener
353
503
 
354
- if (typeof nextListenerOrObserver === 'function') {
355
- listener = nextListenerOrObserver;
356
- } else {
357
- listener = nextListenerOrObserver.next.bind(nextListenerOrObserver);
358
- resolvedCompleteListener = nextListenerOrObserver.complete.bind(nextListenerOrObserver);
504
+ if (this.status !== InterpreterStatus.NotStarted) {
505
+ observer.next(this.state);
359
506
  }
360
507
 
361
- this.listeners.add(listener); // Send current state to listener
508
+ var completeOnce = function () {
509
+ _this.doneListeners.delete(completeOnce);
362
510
 
363
- if (this.status !== InterpreterStatus.NotStarted) {
364
- listener(this.state);
365
- }
511
+ _this.stopListeners.delete(completeOnce);
366
512
 
367
- if (resolvedCompleteListener) {
368
- if (this.status === InterpreterStatus.Stopped) {
369
- resolvedCompleteListener();
370
- } else {
371
- this.onDone(resolvedCompleteListener);
372
- }
513
+ observer.complete();
514
+ };
515
+
516
+ if (this.status === InterpreterStatus.Stopped) {
517
+ observer.complete();
518
+ } else {
519
+ this.onDone(completeOnce);
520
+ this.onStop(completeOnce);
373
521
  }
374
522
 
375
523
  return {
376
524
  unsubscribe: function () {
377
- listener && _this.listeners.delete(listener);
378
- resolvedCompleteListener && _this.doneListeners.delete(resolvedCompleteListener);
525
+ _this.listeners.delete(observer.next);
526
+
527
+ _this.doneListeners.delete(completeOnce);
528
+
529
+ _this.stopListeners.delete(completeOnce);
379
530
  }
380
531
  };
381
532
  };
@@ -480,18 +631,10 @@ function () {
480
631
  });
481
632
  return this;
482
633
  };
483
- /**
484
- * Stops the interpreter and unsubscribe all listeners.
485
- *
486
- * This will also notify the `onStop` listeners.
487
- */
488
-
489
634
 
490
- Interpreter.prototype.stop = function () {
635
+ Interpreter.prototype._stop = function () {
491
636
  var e_6, _a, e_7, _b, e_8, _c, e_9, _d, e_10, _e;
492
637
 
493
- var _this = this;
494
-
495
638
  try {
496
639
  for (var _f = __values(this.listeners), _g = _f.next(); !_g.done; _g = _f.next()) {
497
640
  var listener = _g.value;
@@ -567,40 +710,13 @@ function () {
567
710
  return this;
568
711
  }
569
712
 
570
- __spreadArray([], __read(this.state.configuration), false).sort(function (a, b) {
571
- return b.order - a.order;
572
- }).forEach(function (stateNode) {
573
- var e_11, _a;
574
-
575
- try {
576
- for (var _b = __values(stateNode.definition.exit), _c = _b.next(); !_c.done; _c = _b.next()) {
577
- var action = _c.value;
578
-
579
- _this.exec(action, _this.state);
580
- }
581
- } catch (e_11_1) {
582
- e_11 = {
583
- error: e_11_1
584
- };
585
- } finally {
586
- try {
587
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
588
- } finally {
589
- if (e_11) throw e_11.error;
590
- }
591
- }
592
- }); // Stop all children
593
-
594
-
595
- this.children.forEach(function (child) {
596
- if (isFunction(child.stop)) {
597
- child.stop();
598
- }
599
- });
600
- this.children.clear();
713
+ this.initialized = false;
714
+ this.status = InterpreterStatus.Stopped;
715
+ this._initialState = undefined;
601
716
 
602
717
  try {
603
- // Cancel all delayed events
718
+ // we are going to stop within the current sync frame
719
+ // so we can safely just cancel this here as nothing async should be fired anyway
604
720
  for (var _p = __values(Object.keys(this.delayedEventsMap)), _q = _p.next(); !_q.done; _q = _p.next()) {
605
721
  var key = _q.value;
606
722
  this.clock.clearTimeout(this.delayedEventsMap[key]);
@@ -615,16 +731,85 @@ function () {
615
731
  } finally {
616
732
  if (e_10) throw e_10.error;
617
733
  }
618
- }
734
+ } // clear everything that might be enqueued
735
+
619
736
 
620
737
  this.scheduler.clear();
621
738
  this.scheduler = new Scheduler({
622
739
  deferEvents: this.options.deferEvents
623
740
  });
624
- this.initialized = false;
625
- this.status = InterpreterStatus.Stopped;
626
- this._initialState = undefined;
627
- registry.free(this.sessionId);
741
+ };
742
+ /**
743
+ * Stops the interpreter and unsubscribe all listeners.
744
+ *
745
+ * This will also notify the `onStop` listeners.
746
+ */
747
+
748
+
749
+ Interpreter.prototype.stop = function () {
750
+ // TODO: add warning for stopping non-root interpreters
751
+ var _this = this; // grab the current scheduler as it will be replaced in _stop
752
+
753
+
754
+ var scheduler = this.scheduler;
755
+
756
+ this._stop(); // let what is currently processed to be finished
757
+
758
+
759
+ scheduler.schedule(function () {
760
+ // it feels weird to handle this here but we need to handle this even slightly "out of band"
761
+ var _event = toSCXMLEvent({
762
+ type: 'xstate.stop'
763
+ });
764
+
765
+ var nextState = provide(_this, function () {
766
+ var exitActions = flatten(__spreadArray([], __read(_this.state.configuration), false).sort(function (a, b) {
767
+ return b.order - a.order;
768
+ }).map(function (stateNode) {
769
+ return toActionObjects(stateNode.onExit, _this.machine.options.actions);
770
+ }));
771
+
772
+ var _a = __read(resolveActions(_this.machine, _this.state, _this.state.context, _event, exitActions, _this.machine.config.predictableActionArguments ? _this._exec : undefined, _this.machine.config.predictableActionArguments || _this.machine.config.preserveActionOrder), 2),
773
+ resolvedActions = _a[0],
774
+ updatedContext = _a[1];
775
+
776
+ var newState = new State({
777
+ value: _this.state.value,
778
+ context: updatedContext,
779
+ _event: _event,
780
+ _sessionid: _this.sessionId,
781
+ historyValue: undefined,
782
+ history: _this.state,
783
+ actions: resolvedActions.filter(function (action) {
784
+ return action.type !== raise && (action.type !== send || !!action.to && action.to !== SpecialTargets.Internal);
785
+ }),
786
+ activities: {},
787
+ events: [],
788
+ configuration: [],
789
+ transitions: [],
790
+ children: {},
791
+ done: _this.state.done,
792
+ tags: _this.state.tags,
793
+ machine: _this.machine
794
+ });
795
+ newState.changed = true;
796
+ return newState;
797
+ });
798
+
799
+ _this.update(nextState, _event); // TODO: think about converting those to actions
800
+ // Stop all children
801
+
802
+
803
+ _this.children.forEach(function (child) {
804
+ if (isFunction(child.stop)) {
805
+ child.stop();
806
+ }
807
+ });
808
+
809
+ _this.children.clear();
810
+
811
+ registry.free(_this.sessionId);
812
+ });
628
813
  return this;
629
814
  };
630
815
 
@@ -642,7 +827,7 @@ function () {
642
827
  }
643
828
 
644
829
  this.scheduler.schedule(function () {
645
- var e_12, _a;
830
+ var e_11, _a;
646
831
 
647
832
  var nextState = _this.state;
648
833
  var batchChanged = false;
@@ -668,15 +853,15 @@ function () {
668
853
 
669
854
  _loop_1(event_1);
670
855
  }
671
- } catch (e_12_1) {
672
- e_12 = {
673
- error: e_12_1
856
+ } catch (e_11_1) {
857
+ e_11 = {
858
+ error: e_11_1
674
859
  };
675
860
  } finally {
676
861
  try {
677
862
  if (events_1_1 && !events_1_1.done && (_a = events_1.return)) _a.call(events_1);
678
863
  } finally {
679
- if (e_12) throw e_12.error;
864
+ if (e_11) throw e_11.error;
680
865
  }
681
866
  }
682
867
 
@@ -696,16 +881,8 @@ function () {
696
881
  Interpreter.prototype.sender = function (event) {
697
882
  return this.send.bind(this, event);
698
883
  };
699
- /**
700
- * Returns the next state given the interpreter's current state and the event.
701
- *
702
- * This is a pure method that does _not_ update the interpreter's state.
703
- *
704
- * @param event The event to determine the next state
705
- */
706
-
707
884
 
708
- Interpreter.prototype.nextState = function (event) {
885
+ Interpreter.prototype._nextState = function (event) {
709
886
  var _this = this;
710
887
 
711
888
  var _event = toSCXMLEvent(event);
@@ -717,13 +894,25 @@ function () {
717
894
  }
718
895
 
719
896
  var nextState = provide(this, function () {
720
- return _this.machine.transition(_this.state, _event);
897
+ return _this.machine.transition(_this.state, _event, undefined, _this.machine.config.predictableActionArguments ? _this._exec : undefined);
721
898
  });
722
899
  return nextState;
723
900
  };
901
+ /**
902
+ * Returns the next state given the interpreter's current state and the event.
903
+ *
904
+ * This is a pure method that does _not_ update the interpreter's state.
905
+ *
906
+ * @param event The event to determine the next state
907
+ */
908
+
909
+
910
+ Interpreter.prototype.nextState = function (event) {
911
+ return this._nextState(event);
912
+ };
724
913
 
725
914
  Interpreter.prototype.forward = function (event) {
726
- var e_13, _a;
915
+ var e_12, _a;
727
916
 
728
917
  try {
729
918
  for (var _b = __values(this.forwardTo), _c = _b.next(); !_c.done; _c = _b.next()) {
@@ -736,15 +925,15 @@ function () {
736
925
 
737
926
  child.send(event);
738
927
  }
739
- } catch (e_13_1) {
740
- e_13 = {
741
- error: e_13_1
928
+ } catch (e_12_1) {
929
+ e_12 = {
930
+ error: e_12_1
742
931
  };
743
932
  } finally {
744
933
  try {
745
934
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
746
935
  } finally {
747
- if (e_13) throw e_13.error;
936
+ if (e_12) throw e_12.error;
748
937
  }
749
938
  }
750
939
  };
@@ -771,150 +960,7 @@ function () {
771
960
  actionFunctionMap = this.machine.options.actions;
772
961
  }
773
962
 
774
- var context = state.context,
775
- _event = state._event;
776
- var actionOrExec = action.exec || getActionFunction(action.type, actionFunctionMap);
777
- var exec = isFunction(actionOrExec) ? actionOrExec : actionOrExec ? actionOrExec.exec : action.exec;
778
-
779
- if (exec) {
780
- try {
781
- return exec(context, _event.data, {
782
- action: action,
783
- state: this.state,
784
- _event: _event
785
- });
786
- } catch (err) {
787
- if (this.parent) {
788
- this.parent.send({
789
- type: 'xstate.error',
790
- data: err
791
- });
792
- }
793
-
794
- throw err;
795
- }
796
- }
797
-
798
- switch (action.type) {
799
- case send:
800
- var sendAction = action;
801
-
802
- if (typeof sendAction.delay === 'number') {
803
- this.defer(sendAction);
804
- return;
805
- } else {
806
- if (sendAction.to) {
807
- this.sendTo(sendAction._event, sendAction.to);
808
- } else {
809
- this.send(sendAction._event);
810
- }
811
- }
812
-
813
- break;
814
-
815
- case cancel:
816
- this.cancel(action.sendId);
817
- break;
818
-
819
- case start:
820
- {
821
- if (this.status !== InterpreterStatus.Running) {
822
- return;
823
- }
824
-
825
- var activity = action.activity; // If the activity will be stopped right after it's started
826
- // (such as in transient states)
827
- // don't bother starting the activity.
828
-
829
- if (!this.state.activities[activity.id || activity.type]) {
830
- break;
831
- } // Invoked services
832
-
833
-
834
- if (activity.type === ActionTypes.Invoke) {
835
- var invokeSource = toInvokeSource(activity.src);
836
- var serviceCreator = this.machine.options.services ? this.machine.options.services[invokeSource.type] : undefined;
837
- var id = activity.id,
838
- data = activity.data;
839
-
840
- if (!IS_PRODUCTION) {
841
- warn(!('forward' in activity), // tslint:disable-next-line:max-line-length
842
- "`forward` property is deprecated (found in invocation of '".concat(activity.src, "' in in machine '").concat(this.machine.id, "'). ") + "Please use `autoForward` instead.");
843
- }
844
-
845
- var autoForward = 'autoForward' in activity ? activity.autoForward : !!activity.forward;
846
-
847
- if (!serviceCreator) {
848
- // tslint:disable-next-line:no-console
849
- if (!IS_PRODUCTION) {
850
- warn(false, "No service found for invocation '".concat(activity.src, "' in machine '").concat(this.machine.id, "'."));
851
- }
852
-
853
- return;
854
- }
855
-
856
- var resolvedData = data ? mapContext(data, context, _event) : undefined;
857
-
858
- if (typeof serviceCreator === 'string') {
859
- // TODO: warn
860
- return;
861
- }
862
-
863
- var source = isFunction(serviceCreator) ? serviceCreator(context, _event.data, {
864
- data: resolvedData,
865
- src: invokeSource,
866
- meta: activity.meta
867
- }) : serviceCreator;
868
-
869
- if (!source) {
870
- // TODO: warn?
871
- return;
872
- }
873
-
874
- var options = void 0;
875
-
876
- if (isMachine(source)) {
877
- source = resolvedData ? source.withContext(resolvedData) : source;
878
- options = {
879
- autoForward: autoForward
880
- };
881
- }
882
-
883
- this.spawn(source, id, options);
884
- } else {
885
- this.spawnActivity(activity);
886
- }
887
-
888
- break;
889
- }
890
-
891
- case stop:
892
- {
893
- this.stopChild(action.activity.id);
894
- break;
895
- }
896
-
897
- case log:
898
- var label = action.label,
899
- value = action.value;
900
-
901
- if (label) {
902
- this.logger(label, value);
903
- } else {
904
- this.logger(value);
905
- }
906
-
907
- break;
908
-
909
- default:
910
- if (!IS_PRODUCTION) {
911
- warn(false, "No implementation found for action type '".concat(action.type, "'"));
912
- }
913
-
914
- break;
915
- }
916
-
917
- return undefined;
963
+ this._exec(action, state.context, state._event, actionFunctionMap);
918
964
  };
919
965
 
920
966
  Interpreter.prototype.removeChild = function (childId) {
@@ -942,6 +988,10 @@ function () {
942
988
  };
943
989
 
944
990
  Interpreter.prototype.spawn = function (entity, name, options) {
991
+ if (this.status !== InterpreterStatus.Running) {
992
+ return createDeferredActor(entity, name);
993
+ }
994
+
945
995
  if (isPromiseLike(entity)) {
946
996
  return this.spawnPromise(Promise.resolve(entity), name);
947
997
  } else if (isFunction(entity)) {