xstate 4.31.0 → 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.
@@ -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');
@@ -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,7 @@ function () {
319
477
  }
320
478
  }
321
479
 
322
- this.stop();
480
+ this._stop();
323
481
  }
324
482
  };
325
483
  /*
@@ -344,42 +502,35 @@ function () {
344
502
  completeListener) {
345
503
  var _this = this;
346
504
 
347
- if (!nextListenerOrObserver) {
348
- return {
349
- unsubscribe: function () {
350
- return void 0;
351
- }
352
- };
353
- }
354
-
355
- var listener;
356
- var resolvedCompleteListener = completeListener;
505
+ var observer = utils.toObserver(nextListenerOrObserver, _, completeListener);
506
+ this.listeners.add(observer.next); // Send current state to listener
357
507
 
358
- if (typeof nextListenerOrObserver === 'function') {
359
- listener = nextListenerOrObserver;
360
- } else {
361
- listener = nextListenerOrObserver.next.bind(nextListenerOrObserver);
362
- resolvedCompleteListener = nextListenerOrObserver.complete.bind(nextListenerOrObserver);
508
+ if (this.status !== exports.InterpreterStatus.NotStarted) {
509
+ observer.next(this.state);
363
510
  }
364
511
 
365
- this.listeners.add(listener); // Send current state to listener
512
+ var completeOnce = function () {
513
+ _this.doneListeners.delete(completeOnce);
366
514
 
367
- if (this.status !== exports.InterpreterStatus.NotStarted) {
368
- listener(this.state);
369
- }
515
+ _this.stopListeners.delete(completeOnce);
370
516
 
371
- if (resolvedCompleteListener) {
372
- if (this.status === exports.InterpreterStatus.Stopped) {
373
- resolvedCompleteListener();
374
- } else {
375
- this.onDone(resolvedCompleteListener);
376
- }
517
+ observer.complete();
518
+ };
519
+
520
+ if (this.status === exports.InterpreterStatus.Stopped) {
521
+ observer.complete();
522
+ } else {
523
+ this.onDone(completeOnce);
524
+ this.onStop(completeOnce);
377
525
  }
378
526
 
379
527
  return {
380
528
  unsubscribe: function () {
381
- listener && _this.listeners.delete(listener);
382
- resolvedCompleteListener && _this.doneListeners.delete(resolvedCompleteListener);
529
+ _this.listeners.delete(observer.next);
530
+
531
+ _this.doneListeners.delete(completeOnce);
532
+
533
+ _this.stopListeners.delete(completeOnce);
383
534
  }
384
535
  };
385
536
  };
@@ -484,18 +635,10 @@ function () {
484
635
  });
485
636
  return this;
486
637
  };
487
- /**
488
- * Stops the interpreter and unsubscribe all listeners.
489
- *
490
- * This will also notify the `onStop` listeners.
491
- */
492
-
493
638
 
494
- Interpreter.prototype.stop = function () {
639
+ Interpreter.prototype._stop = function () {
495
640
  var e_6, _a, e_7, _b, e_8, _c, e_9, _d, e_10, _e;
496
641
 
497
- var _this = this;
498
-
499
642
  try {
500
643
  for (var _f = _tslib.__values(this.listeners), _g = _f.next(); !_g.done; _g = _f.next()) {
501
644
  var listener = _g.value;
@@ -571,40 +714,13 @@ function () {
571
714
  return this;
572
715
  }
573
716
 
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();
717
+ this.initialized = false;
718
+ this.status = exports.InterpreterStatus.Stopped;
719
+ this._initialState = undefined;
605
720
 
606
721
  try {
607
- // Cancel all delayed events
722
+ // we are going to stop within the current sync frame
723
+ // so we can safely just cancel this here as nothing async should be fired anyway
608
724
  for (var _p = _tslib.__values(Object.keys(this.delayedEventsMap)), _q = _p.next(); !_q.done; _q = _p.next()) {
609
725
  var key = _q.value;
610
726
  this.clock.clearTimeout(this.delayedEventsMap[key]);
@@ -619,16 +735,85 @@ function () {
619
735
  } finally {
620
736
  if (e_10) throw e_10.error;
621
737
  }
622
- }
738
+ } // clear everything that might be enqueued
739
+
623
740
 
624
741
  this.scheduler.clear();
625
742
  this.scheduler = new scheduler.Scheduler({
626
743
  deferEvents: this.options.deferEvents
627
744
  });
628
- this.initialized = false;
629
- this.status = exports.InterpreterStatus.Stopped;
630
- this._initialState = undefined;
631
- registry.registry.free(this.sessionId);
745
+ };
746
+ /**
747
+ * Stops the interpreter and unsubscribe all listeners.
748
+ *
749
+ * This will also notify the `onStop` listeners.
750
+ */
751
+
752
+
753
+ Interpreter.prototype.stop = function () {
754
+ // TODO: add warning for stopping non-root interpreters
755
+ var _this = this; // grab the current scheduler as it will be replaced in _stop
756
+
757
+
758
+ var scheduler = this.scheduler;
759
+
760
+ this._stop(); // let what is currently processed to be finished
761
+
762
+
763
+ scheduler.schedule(function () {
764
+ // it feels weird to handle this here but we need to handle this even slightly "out of band"
765
+ var _event = utils.toSCXMLEvent({
766
+ type: 'xstate.stop'
767
+ });
768
+
769
+ var nextState = serviceScope.provide(_this, function () {
770
+ var exitActions = utils.flatten(_tslib.__spreadArray([], _tslib.__read(_this.state.configuration), false).sort(function (a, b) {
771
+ return b.order - a.order;
772
+ }).map(function (stateNode) {
773
+ return actions.toActionObjects(stateNode.onExit, _this.machine.options.actions);
774
+ }));
775
+
776
+ 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),
777
+ resolvedActions = _a[0],
778
+ updatedContext = _a[1];
779
+
780
+ var newState = new State.State({
781
+ value: _this.state.value,
782
+ context: updatedContext,
783
+ _event: _event,
784
+ _sessionid: _this.sessionId,
785
+ historyValue: undefined,
786
+ history: _this.state,
787
+ actions: resolvedActions.filter(function (action) {
788
+ return action.type !== actionTypes.raise && (action.type !== actionTypes.send || !!action.to && action.to !== types.SpecialTargets.Internal);
789
+ }),
790
+ activities: {},
791
+ events: [],
792
+ configuration: [],
793
+ transitions: [],
794
+ children: {},
795
+ done: _this.state.done,
796
+ tags: _this.state.tags,
797
+ machine: _this.machine
798
+ });
799
+ newState.changed = true;
800
+ return newState;
801
+ });
802
+
803
+ _this.update(nextState, _event); // TODO: think about converting those to actions
804
+ // Stop all children
805
+
806
+
807
+ _this.children.forEach(function (child) {
808
+ if (utils.isFunction(child.stop)) {
809
+ child.stop();
810
+ }
811
+ });
812
+
813
+ _this.children.clear();
814
+
815
+ registry.registry.free(_this.sessionId);
816
+ });
632
817
  return this;
633
818
  };
634
819
 
@@ -646,7 +831,7 @@ function () {
646
831
  }
647
832
 
648
833
  this.scheduler.schedule(function () {
649
- var e_12, _a;
834
+ var e_11, _a;
650
835
 
651
836
  var nextState = _this.state;
652
837
  var batchChanged = false;
@@ -672,15 +857,15 @@ function () {
672
857
 
673
858
  _loop_1(event_1);
674
859
  }
675
- } catch (e_12_1) {
676
- e_12 = {
677
- error: e_12_1
860
+ } catch (e_11_1) {
861
+ e_11 = {
862
+ error: e_11_1
678
863
  };
679
864
  } finally {
680
865
  try {
681
866
  if (events_1_1 && !events_1_1.done && (_a = events_1.return)) _a.call(events_1);
682
867
  } finally {
683
- if (e_12) throw e_12.error;
868
+ if (e_11) throw e_11.error;
684
869
  }
685
870
  }
686
871
 
@@ -700,16 +885,8 @@ function () {
700
885
  Interpreter.prototype.sender = function (event) {
701
886
  return this.send.bind(this, event);
702
887
  };
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
-
711
888
 
712
- Interpreter.prototype.nextState = function (event) {
889
+ Interpreter.prototype._nextState = function (event) {
713
890
  var _this = this;
714
891
 
715
892
  var _event = utils.toSCXMLEvent(event);
@@ -721,13 +898,25 @@ function () {
721
898
  }
722
899
 
723
900
  var nextState = serviceScope.provide(this, function () {
724
- return _this.machine.transition(_this.state, _event);
901
+ return _this.machine.transition(_this.state, _event, undefined, _this.machine.config.predictableActionArguments ? _this._exec : undefined);
725
902
  });
726
903
  return nextState;
727
904
  };
905
+ /**
906
+ * Returns the next state given the interpreter's current state and the event.
907
+ *
908
+ * This is a pure method that does _not_ update the interpreter's state.
909
+ *
910
+ * @param event The event to determine the next state
911
+ */
912
+
913
+
914
+ Interpreter.prototype.nextState = function (event) {
915
+ return this._nextState(event);
916
+ };
728
917
 
729
918
  Interpreter.prototype.forward = function (event) {
730
- var e_13, _a;
919
+ var e_12, _a;
731
920
 
732
921
  try {
733
922
  for (var _b = _tslib.__values(this.forwardTo), _c = _b.next(); !_c.done; _c = _b.next()) {
@@ -740,15 +929,15 @@ function () {
740
929
 
741
930
  child.send(event);
742
931
  }
743
- } catch (e_13_1) {
744
- e_13 = {
745
- error: e_13_1
932
+ } catch (e_12_1) {
933
+ e_12 = {
934
+ error: e_12_1
746
935
  };
747
936
  } finally {
748
937
  try {
749
938
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
750
939
  } finally {
751
- if (e_13) throw e_13.error;
940
+ if (e_12) throw e_12.error;
752
941
  }
753
942
  }
754
943
  };
@@ -775,150 +964,7 @@ function () {
775
964
  actionFunctionMap = this.machine.options.actions;
776
965
  }
777
966
 
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;
967
+ this._exec(action, state.context, state._event, actionFunctionMap);
922
968
  };
923
969
 
924
970
  Interpreter.prototype.removeChild = function (childId) {
@@ -946,6 +992,10 @@ function () {
946
992
  };
947
993
 
948
994
  Interpreter.prototype.spawn = function (entity, name, options) {
995
+ if (this.status !== exports.InterpreterStatus.Running) {
996
+ return Actor.createDeferredActor(entity, name);
997
+ }
998
+
949
999
  if (utils.isPromiseLike(entity)) {
950
1000
  return this.spawnPromise(Promise.resolve(entity), name);
951
1001
  } else if (utils.isFunction(entity)) {