xstate 5.0.0-beta.20 → 5.0.0-beta.21

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 (40) hide show
  1. package/actions/dist/xstate-actions.cjs.js +1 -1
  2. package/actions/dist/xstate-actions.development.cjs.js +1 -1
  3. package/actions/dist/xstate-actions.development.esm.js +1 -1
  4. package/actions/dist/xstate-actions.esm.js +1 -1
  5. package/actions/dist/xstate-actions.umd.min.js +1 -1
  6. package/actions/dist/xstate-actions.umd.min.js.map +1 -1
  7. package/actors/dist/xstate-actors.cjs.js +1 -1
  8. package/actors/dist/xstate-actors.development.cjs.js +1 -1
  9. package/actors/dist/xstate-actors.development.esm.js +1 -1
  10. package/actors/dist/xstate-actors.esm.js +1 -1
  11. package/actors/dist/xstate-actors.umd.min.js +1 -1
  12. package/actors/dist/xstate-actors.umd.min.js.map +1 -1
  13. package/dist/{actions-b299d008.development.esm.js → actions-49f0501e.development.esm.js} +136 -63
  14. package/dist/{actions-a8a9433c.esm.js → actions-5039c951.esm.js} +134 -64
  15. package/dist/{actions-d1c41ed3.development.cjs.js → actions-a95d2e66.development.cjs.js} +136 -62
  16. package/dist/{actions-069d9805.cjs.js → actions-c619a105.cjs.js} +134 -63
  17. package/dist/declarations/src/Machine.d.ts +2 -2
  18. package/dist/declarations/src/State.d.ts +4 -7
  19. package/dist/declarations/src/StateMachine.d.ts +7 -6
  20. package/dist/declarations/src/StateNode.d.ts +3 -3
  21. package/dist/declarations/src/actions/send.d.ts +1 -1
  22. package/dist/declarations/src/actions/stop.d.ts +1 -1
  23. package/dist/declarations/src/actions.d.ts +2 -2
  24. package/dist/declarations/src/actors/callback.d.ts +4 -4
  25. package/dist/declarations/src/actors/observable.d.ts +7 -4
  26. package/dist/declarations/src/actors/promise.d.ts +4 -4
  27. package/dist/declarations/src/types.d.ts +41 -29
  28. package/dist/declarations/src/utils.d.ts +2 -2
  29. package/dist/xstate.cjs.js +11 -10
  30. package/dist/xstate.development.cjs.js +11 -10
  31. package/dist/xstate.development.esm.js +12 -11
  32. package/dist/xstate.esm.js +12 -11
  33. package/dist/xstate.umd.min.js +1 -1
  34. package/dist/xstate.umd.min.js.map +1 -1
  35. package/guards/dist/xstate-guards.cjs.js +1 -1
  36. package/guards/dist/xstate-guards.development.cjs.js +1 -1
  37. package/guards/dist/xstate-guards.development.esm.js +1 -1
  38. package/guards/dist/xstate-guards.esm.js +1 -1
  39. package/guards/dist/xstate-guards.umd.min.js.map +1 -1
  40. package/package.json +1 -1
@@ -2,6 +2,18 @@
2
2
 
3
3
  var dev_dist_xstateDev = require('../dev/dist/xstate-dev.development.cjs.js');
4
4
 
5
+ /**
6
+ * `T | unknown` reduces to `unknown` and that can be problematic when it comes to contextual typing.
7
+ * It especially is a problem when the union has a function member, like here:
8
+ *
9
+ * ```ts
10
+ * declare function test(cbOrVal: ((arg: number) => unknown) | unknown): void;
11
+ * test((arg) => {}) // oops, implicit any
12
+ * ```
13
+ *
14
+ * This type can be used to avoid this problem. This union represents the same value space as `unknown`.
15
+ */
16
+
5
17
  // https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
6
18
 
7
19
  /**
@@ -422,27 +434,18 @@ function toArray(value) {
422
434
  }
423
435
  return toArrayStrict(value);
424
436
  }
425
- function mapContext(mapper, context, event) {
437
+ function mapContext(mapper, context, event, self) {
426
438
  if (typeof mapper === 'function') {
427
439
  return mapper({
428
440
  context,
429
- event
441
+ event,
442
+ self
430
443
  });
431
444
  }
432
- const result = {};
433
- const args = {
434
- context,
435
- event
436
- };
437
- for (const key of Object.keys(mapper)) {
438
- const subMapper = mapper[key];
439
- if (typeof subMapper === 'function') {
440
- result[key] = subMapper(args);
441
- } else {
442
- result[key] = subMapper;
443
- }
445
+ if (typeof mapper === 'object' && Object.values(mapper).some(val => typeof val === 'function')) {
446
+ console.warn(`Dynamically mapping values to individual properties is deprecated. Use a single function that returns the mapped object instead.\nFound object containing properties whose values are possibly mapping functions: ${Object.entries(mapper).filter(([key, value]) => typeof value === 'function').map(([key, value]) => `\n - ${key}: ${value.toString().replace(/\n\s*/g, '')}`).join('')}`);
444
447
  }
445
- return result;
448
+ return mapper;
446
449
  }
447
450
  function isPromiseLike(value) {
448
451
  if (value instanceof Promise) {
@@ -494,13 +497,12 @@ function toInvokeConfig(invocable, id) {
494
497
  };
495
498
  }
496
499
  function toObserver(nextHandler, errorHandler, completionHandler) {
497
- const noop = () => {};
498
500
  const isObserver = typeof nextHandler === 'object';
499
- const self = isObserver ? nextHandler : null;
501
+ const self = isObserver ? nextHandler : undefined;
500
502
  return {
501
- next: ((isObserver ? nextHandler.next : nextHandler) || noop).bind(self),
502
- error: ((isObserver ? nextHandler.error : errorHandler) || noop).bind(self),
503
- complete: ((isObserver ? nextHandler.complete : completionHandler) || noop).bind(self)
503
+ next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
504
+ error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
505
+ complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
504
506
  };
505
507
  }
506
508
  function createInvokeId(stateNodeId, index) {
@@ -514,7 +516,7 @@ function resolveReferencedActor(referenced) {
514
516
  }
515
517
 
516
518
  function fromCallback(invokeCallback) {
517
- const logic = {
519
+ return {
518
520
  config: invokeCallback,
519
521
  start: (_state, {
520
522
  self
@@ -580,19 +582,20 @@ function fromCallback(invokeCallback) {
580
582
  },
581
583
  getSnapshot: () => undefined,
582
584
  getPersistedState: ({
583
- input
584
- }) => input
585
+ input,
586
+ canceled
587
+ }) => ({
588
+ input,
589
+ canceled
590
+ })
585
591
  };
586
- return logic;
587
592
  }
588
593
 
589
594
  function fromObservable(observableCreator) {
590
595
  const nextEventType = '$$xstate.next';
591
596
  const errorEventType = '$$xstate.error';
592
597
  const completeEventType = '$$xstate.complete';
593
-
594
- // TODO: add event types
595
- const logic = {
598
+ return {
596
599
  config: observableCreator,
597
600
  transition: (state, event, {
598
601
  self,
@@ -622,6 +625,7 @@ function fromObservable(observableCreator) {
622
625
  status: 'error',
623
626
  input: undefined,
624
627
  data: event.data,
628
+ // TODO: if we keep this as `data` we should reflect this in the type
625
629
  subscription: undefined
626
630
  };
627
631
  case completeEventType:
@@ -699,7 +703,6 @@ function fromObservable(observableCreator) {
699
703
  subscription: undefined
700
704
  })
701
705
  };
702
- return logic;
703
706
  }
704
707
 
705
708
  /**
@@ -716,7 +719,7 @@ function fromEventObservable(lazyObservable) {
716
719
  const completeEventType = '$$xstate.complete';
717
720
 
718
721
  // TODO: event types
719
- const logic = {
722
+ return {
720
723
  config: lazyObservable,
721
724
  transition: (state, event) => {
722
725
  if (state.status !== 'active') {
@@ -729,6 +732,7 @@ function fromEventObservable(lazyObservable) {
729
732
  status: 'error',
730
733
  input: undefined,
731
734
  data: event.data,
735
+ // TODO: if we keep this as `data` we should reflect this in the type
732
736
  subscription: undefined
733
737
  };
734
738
  case completeEventType:
@@ -803,7 +807,6 @@ function fromEventObservable(lazyObservable) {
803
807
  subscription: undefined
804
808
  })
805
809
  };
806
- return logic;
807
810
  }
808
811
 
809
812
  const resolveEventType = '$$xstate.resolve';
@@ -831,6 +834,7 @@ promiseCreator) {
831
834
  ...state,
832
835
  status: 'error',
833
836
  data: event.data,
837
+ // TODO: if we keep this as `data` we should reflect this in the type
834
838
  input: undefined
835
839
  };
836
840
  case stopSignalType:
@@ -940,6 +944,19 @@ function createEmptyActor() {
940
944
  return interpret(emptyLogic);
941
945
  }
942
946
 
947
+ /**
948
+ * This function makes sure that unhandled errors are thrown in a separate macrotask.
949
+ * It allows those errors to be detected by global error handlers and reported to bug tracking services
950
+ * without interrupting our own stack of execution.
951
+ *
952
+ * @param err error to be thrown
953
+ */
954
+ function reportUnhandledError(err) {
955
+ setTimeout(() => {
956
+ throw err;
957
+ });
958
+ }
959
+
943
960
  function createSystem() {
944
961
  let sessionIdCounter = 0;
945
962
  const children = new Map();
@@ -1108,29 +1125,38 @@ class Interpreter {
1108
1125
  deferredFn();
1109
1126
  }
1110
1127
  for (const observer of this.observers) {
1111
- observer.next?.(snapshot);
1128
+ // TODO: should observers be notified in case of the error?
1129
+ try {
1130
+ observer.next?.(snapshot);
1131
+ } catch (err) {
1132
+ reportUnhandledError(err);
1133
+ }
1112
1134
  }
1113
1135
  const status = this.logic.getStatus?.(state);
1114
1136
  switch (status?.status) {
1115
1137
  case 'done':
1116
1138
  this._stopProcedure();
1139
+ this._complete();
1117
1140
  this._doneEvent = doneInvoke(this.id, status.data);
1118
1141
  this._parent?.send(this._doneEvent);
1119
- this._complete();
1120
1142
  break;
1121
1143
  case 'error':
1122
1144
  this._stopProcedure();
1123
- this._parent?.send(error(this.id, status.data));
1124
1145
  this._error(status.data);
1146
+ this._parent?.send(error(this.id, status.data));
1125
1147
  break;
1126
1148
  }
1127
1149
  }
1128
1150
  subscribe(nextListenerOrObserver, errorListener, completeListener) {
1129
1151
  const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
1130
- this.observers.add(observer);
1131
- if (this.status === ActorStatus.Stopped) {
1132
- observer.complete?.();
1133
- this.observers.delete(observer);
1152
+ if (this.status !== ActorStatus.Stopped) {
1153
+ this.observers.add(observer);
1154
+ } else {
1155
+ try {
1156
+ observer.complete?.();
1157
+ } catch (err) {
1158
+ reportUnhandledError(err);
1159
+ }
1134
1160
  }
1135
1161
  return {
1136
1162
  unsubscribe: () => {
@@ -1152,8 +1178,26 @@ class Interpreter {
1152
1178
  this.system._set(this._systemId, this);
1153
1179
  }
1154
1180
  this.status = ActorStatus.Running;
1181
+ const status = this.logic.getStatus?.(this._state);
1182
+ switch (status?.status) {
1183
+ case 'done':
1184
+ // a state machine can be "done" upon intialization (it could reach a final state using initial microsteps)
1185
+ // we still need to complete observers, flush deferreds etc
1186
+ this.update(this._state);
1187
+ // fallthrough
1188
+ case 'error':
1189
+ // TODO: rethink cleanup of observers, mailbox, etc
1190
+ return this;
1191
+ }
1155
1192
  if (this.logic.start) {
1156
- this.logic.start(this._state, this._actorContext);
1193
+ try {
1194
+ this.logic.start(this._state, this._actorContext);
1195
+ } catch (err) {
1196
+ this._stopProcedure();
1197
+ this._error(err);
1198
+ this._parent?.send(error(this.id, err));
1199
+ return this;
1200
+ }
1157
1201
  }
1158
1202
 
1159
1203
  // TODO: this notifies all subscribers but usually this is redundant
@@ -1167,23 +1211,30 @@ class Interpreter {
1167
1211
  return this;
1168
1212
  }
1169
1213
  _process(event) {
1214
+ // TODO: reexamine what happens when an action (or a guard or smth) throws
1215
+ let nextState;
1216
+ let caughtError;
1170
1217
  try {
1171
- const nextState = this.logic.transition(this._state, event, this._actorContext);
1172
- this.update(nextState);
1173
- if (event.type === stopSignalType) {
1174
- this._stopProcedure();
1175
- this._complete();
1176
- }
1218
+ nextState = this.logic.transition(this._state, event, this._actorContext);
1177
1219
  } catch (err) {
1178
- // TODO: properly handle errors
1179
- if (this.observers.size > 0) {
1180
- this.observers.forEach(observer => {
1181
- observer.error?.(err);
1182
- });
1183
- this.stop();
1184
- } else {
1185
- throw err;
1186
- }
1220
+ // we wrap it in a box so we can rethrow it later even if falsy value gets caught here
1221
+ caughtError = {
1222
+ err
1223
+ };
1224
+ }
1225
+ if (caughtError) {
1226
+ const {
1227
+ err
1228
+ } = caughtError;
1229
+ this._stopProcedure();
1230
+ this._error(err);
1231
+ this._parent?.send(error(this.id, err));
1232
+ return;
1233
+ }
1234
+ this.update(nextState);
1235
+ if (event.type === stopSignalType) {
1236
+ this._stopProcedure();
1237
+ this._complete();
1187
1238
  }
1188
1239
  }
1189
1240
  _stop() {
@@ -1212,15 +1263,35 @@ class Interpreter {
1212
1263
  }
1213
1264
  _complete() {
1214
1265
  for (const observer of this.observers) {
1215
- observer.complete?.();
1266
+ try {
1267
+ observer.complete?.();
1268
+ } catch (err) {
1269
+ reportUnhandledError(err);
1270
+ }
1216
1271
  }
1217
1272
  this.observers.clear();
1218
1273
  }
1219
- _error(data) {
1274
+ _error(err) {
1275
+ if (!this.observers.size) {
1276
+ if (!this._parent) {
1277
+ reportUnhandledError(err);
1278
+ }
1279
+ return;
1280
+ }
1281
+ let reportError = false;
1220
1282
  for (const observer of this.observers) {
1221
- observer.error?.(data);
1283
+ const errorListener = observer.error;
1284
+ reportError ||= !errorListener;
1285
+ try {
1286
+ errorListener?.(err);
1287
+ } catch (err2) {
1288
+ reportUnhandledError(err2);
1289
+ }
1222
1290
  }
1223
1291
  this.observers.clear();
1292
+ if (reportError) {
1293
+ reportUnhandledError(err);
1294
+ }
1224
1295
  }
1225
1296
  _stopProcedure() {
1226
1297
  if (this.status !== ActorStatus.Running) {
@@ -1553,10 +1624,10 @@ function toGuardDefinition(guardConfig, getPredicate) {
1553
1624
  }
1554
1625
  }
1555
1626
 
1556
- function getOutput(configuration, context, event) {
1627
+ function getOutput(configuration, context, event, self) {
1557
1628
  const machine = configuration[0].machine;
1558
1629
  const finalChildStateNode = configuration.find(stateNode => stateNode.type === 'final' && stateNode.parent === machine.root);
1559
- return finalChildStateNode && finalChildStateNode.output ? mapContext(finalChildStateNode.output, context, event) : undefined;
1630
+ return finalChildStateNode && finalChildStateNode.output ? mapContext(finalChildStateNode.output, context, event, self) : undefined;
1560
1631
  }
1561
1632
  const isAtomicStateNode = stateNode => stateNode.type === 'atomic' || stateNode.type === 'final';
1562
1633
  function getChildren(stateNode) {
@@ -2183,7 +2254,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2183
2254
  actions.push(...filteredTransitions.flatMap(t => t.actions));
2184
2255
 
2185
2256
  // Enter states
2186
- enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial);
2257
+ enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial, actorCtx);
2187
2258
  const nextConfiguration = [...mutConfiguration];
2188
2259
  const done = isInFinalState(nextConfiguration);
2189
2260
  if (done) {
@@ -2192,7 +2263,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2192
2263
  }
2193
2264
  try {
2194
2265
  const nextState = resolveActionsAndContext(actions, event, currentState, actorCtx);
2195
- const output = done ? getOutput(nextConfiguration, nextState.context, event) : undefined;
2266
+ const output = done ? getOutput(nextConfiguration, nextState.context, event, actorCtx.self) : undefined;
2196
2267
  internalQueue.push(...nextState._internalQueue);
2197
2268
  return cloneState(currentState, {
2198
2269
  configuration: nextConfiguration,
@@ -2209,7 +2280,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2209
2280
  throw e;
2210
2281
  }
2211
2282
  }
2212
- function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial) {
2283
+ function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial, actorContext) {
2213
2284
  const statesToEnter = new Set();
2214
2285
  const statesForDefaultEntry = new Set();
2215
2286
  computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
@@ -2237,7 +2308,7 @@ function enterStates(event, filteredTransitions, mutConfiguration, actions, inte
2237
2308
  if (!parent.parent) {
2238
2309
  continue;
2239
2310
  }
2240
- internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, event) : undefined));
2311
+ internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, event, actorContext.self) : undefined));
2241
2312
  if (parent.parent) {
2242
2313
  const grandparent = parent.parent;
2243
2314
  if (grandparent.type === 'parallel') {
@@ -2560,6 +2631,7 @@ class State {
2560
2631
  this.value = void 0;
2561
2632
  this.done = void 0;
2562
2633
  this.output = void 0;
2634
+ this.error = void 0;
2563
2635
  this.context = void 0;
2564
2636
  this.historyValue = {};
2565
2637
  this._internalQueue = void 0;
@@ -2576,6 +2648,7 @@ class State {
2576
2648
  this.tags = new Set(flatten(this.configuration.map(sn => sn.tags)));
2577
2649
  this.done = config.done ?? false;
2578
2650
  this.output = config.output;
2651
+ this.error = config.error;
2579
2652
  }
2580
2653
 
2581
2654
  /**
@@ -3058,6 +3131,7 @@ exports.and = and;
3058
3131
  exports.assign = assign;
3059
3132
  exports.cancel = cancel;
3060
3133
  exports.choose = choose;
3134
+ exports.cloneState = cloneState;
3061
3135
  exports.constantPrefixes = constantPrefixes;
3062
3136
  exports.createEmptyActor = createEmptyActor;
3063
3137
  exports.createInitEvent = createInitEvent;