xstate 4.32.1 → 4.33.2

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