xstate 4.32.0 → 4.33.1

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,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
- }
505
+ var observer = utils.toObserver(nextListenerOrObserver, _, completeListener);
506
+ this.listeners.add(observer.next); // Send current state to listener
354
507
 
355
- var listener;
356
- var resolvedCompleteListener = completeListener;
357
-
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
638
 
493
-
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,18 +885,14 @@ 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, exec) {
713
890
  var _this = this;
714
891
 
892
+ if (exec === void 0) {
893
+ exec = !!this.machine.config.predictableActionArguments && this._exec;
894
+ }
895
+
715
896
  var _event = utils.toSCXMLEvent(event);
716
897
 
717
898
  if (_event.name.indexOf(actionTypes.errorPlatform) === 0 && !this.state.nextEvents.some(function (nextEvent) {
@@ -721,13 +902,25 @@ function () {
721
902
  }
722
903
 
723
904
  var nextState = serviceScope.provide(this, function () {
724
- return _this.machine.transition(_this.state, _event);
905
+ return _this.machine.transition(_this.state, _event, undefined, exec || undefined);
725
906
  });
726
907
  return nextState;
727
908
  };
909
+ /**
910
+ * Returns the next state given the interpreter's current state and the event.
911
+ *
912
+ * This is a pure method that does _not_ update the interpreter's state.
913
+ *
914
+ * @param event The event to determine the next state
915
+ */
916
+
917
+
918
+ Interpreter.prototype.nextState = function (event) {
919
+ return this._nextState(event, false);
920
+ };
728
921
 
729
922
  Interpreter.prototype.forward = function (event) {
730
- var e_13, _a;
923
+ var e_12, _a;
731
924
 
732
925
  try {
733
926
  for (var _b = _tslib.__values(this.forwardTo), _c = _b.next(); !_c.done; _c = _b.next()) {
@@ -740,15 +933,15 @@ function () {
740
933
 
741
934
  child.send(event);
742
935
  }
743
- } catch (e_13_1) {
744
- e_13 = {
745
- error: e_13_1
936
+ } catch (e_12_1) {
937
+ e_12 = {
938
+ error: e_12_1
746
939
  };
747
940
  } finally {
748
941
  try {
749
942
  if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
750
943
  } finally {
751
- if (e_13) throw e_13.error;
944
+ if (e_12) throw e_12.error;
752
945
  }
753
946
  }
754
947
  };
@@ -775,150 +968,7 @@ function () {
775
968
  actionFunctionMap = this.machine.options.actions;
776
969
  }
777
970
 
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;
971
+ this._exec(action, state.context, state._event, actionFunctionMap);
922
972
  };
923
973
 
924
974
  Interpreter.prototype.removeChild = function (childId) {
@@ -946,6 +996,10 @@ function () {
946
996
  };
947
997
 
948
998
  Interpreter.prototype.spawn = function (entity, name, options) {
999
+ if (this.status !== exports.InterpreterStatus.Running) {
1000
+ return Actor.createDeferredActor(entity, name);
1001
+ }
1002
+
949
1003
  if (utils.isPromiseLike(entity)) {
950
1004
  return this.spawnPromise(Promise.resolve(entity), name);
951
1005
  } else if (utils.isFunction(entity)) {