xstate 5.0.0-beta.19 → 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 (44) 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-72105f77.development.esm.js → actions-49f0501e.development.esm.js} +421 -352
  14. package/dist/{actions-3b74fb92.esm.js → actions-5039c951.esm.js} +489 -423
  15. package/dist/{actions-bce11b97.development.cjs.js → actions-a95d2e66.development.cjs.js} +421 -351
  16. package/dist/{actions-2d912781.cjs.js → actions-c619a105.cjs.js} +489 -422
  17. package/dist/declarations/src/Machine.d.ts +2 -2
  18. package/dist/declarations/src/State.d.ts +12 -4
  19. package/dist/declarations/src/StateMachine.d.ts +17 -16
  20. package/dist/declarations/src/StateNode.d.ts +6 -6
  21. package/dist/declarations/src/actions/send.d.ts +1 -1
  22. package/dist/declarations/src/actions.d.ts +2 -2
  23. package/dist/declarations/src/actors/callback.d.ts +16 -6
  24. package/dist/declarations/src/actors/index.d.ts +4 -4
  25. package/dist/declarations/src/actors/observable.d.ts +14 -11
  26. package/dist/declarations/src/actors/promise.d.ts +14 -8
  27. package/dist/declarations/src/actors/transition.d.ts +6 -6
  28. package/dist/declarations/src/guards.d.ts +2 -2
  29. package/dist/declarations/src/stateUtils.d.ts +8 -8
  30. package/dist/declarations/src/typegenTypes.d.ts +15 -17
  31. package/dist/declarations/src/types.d.ts +131 -79
  32. package/dist/declarations/src/utils.d.ts +3 -3
  33. package/dist/xstate.cjs.js +12 -12
  34. package/dist/xstate.development.cjs.js +12 -12
  35. package/dist/xstate.development.esm.js +13 -13
  36. package/dist/xstate.esm.js +13 -13
  37. package/dist/xstate.umd.min.js +1 -1
  38. package/dist/xstate.umd.min.js.map +1 -1
  39. package/guards/dist/xstate-guards.cjs.js +1 -1
  40. package/guards/dist/xstate-guards.development.cjs.js +1 -1
  41. package/guards/dist/xstate-guards.development.esm.js +1 -1
  42. package/guards/dist/xstate-guards.esm.js +1 -1
  43. package/guards/dist/xstate-guards.umd.min.js.map +1 -1
  44. package/package.json +1 -1
@@ -2,10 +2,19 @@
2
2
 
3
3
  var dev_dist_xstateDev = require('../dev/dist/xstate-dev.development.cjs.js');
4
4
 
5
- // https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
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
+ */
6
16
 
7
- // TODO: replace in v5 with:
8
- // export type IndexByType<T extends { type: string }> = { [E in T as E['type']]: E; };
17
+ // https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
9
18
 
10
19
  /**
11
20
  * The full definition of an event, with a string `type`.
@@ -15,8 +24,6 @@ var dev_dist_xstateDev = require('../dev/dist/xstate-dev.development.cjs.js');
15
24
  // we should also accept a raw machine as actor logic here
16
25
  // or just make machine actor logic
17
26
 
18
- // TODO: narrow this to logic from machine
19
-
20
27
  /**
21
28
  * The string or object representing the state value relative to the parent state node.
22
29
  *
@@ -323,7 +330,7 @@ const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observabl
323
330
  * @returns Actor logic
324
331
  */
325
332
  function fromTransition(transition, initialState) {
326
- const logic = {
333
+ return {
327
334
  config: transition,
328
335
  transition: (state, event, actorContext) => {
329
336
  return transition(state, event, actorContext);
@@ -337,103 +344,258 @@ function fromTransition(transition, initialState) {
337
344
  getPersistedState: state => state,
338
345
  restoreState: state => state
339
346
  };
340
- return logic;
341
347
  }
342
348
 
343
- const resolveEventType = '$$xstate.resolve';
344
- const rejectEventType = '$$xstate.reject';
345
- function fromPromise(
346
- // TODO: add types
347
- promiseCreator) {
348
- // TODO: add event types, consider making the `PromiseEvent` a private type or smth alike
349
- const logic = {
350
- config: promiseCreator,
351
- transition: (state, event) => {
352
- if (state.status !== 'active') {
353
- return state;
354
- }
355
- switch (event.type) {
356
- case resolveEventType:
357
- return {
358
- ...state,
359
- status: 'done',
360
- data: event.data,
361
- input: undefined
362
- };
363
- case rejectEventType:
364
- return {
365
- ...state,
366
- status: 'error',
367
- data: event.data,
368
- input: undefined
369
- };
370
- case stopSignalType:
371
- return {
372
- ...state,
373
- status: 'canceled',
374
- input: undefined
375
- };
376
- default:
377
- return state;
378
- }
349
+ function matchesState(parentStateId, childStateId) {
350
+ const parentStateValue = toStateValue(parentStateId);
351
+ const childStateValue = toStateValue(childStateId);
352
+ if (typeof childStateValue === 'string') {
353
+ if (typeof parentStateValue === 'string') {
354
+ return childStateValue === parentStateValue;
355
+ }
356
+
357
+ // Parent more specific than child
358
+ return false;
359
+ }
360
+ if (typeof parentStateValue === 'string') {
361
+ return parentStateValue in childStateValue;
362
+ }
363
+ return Object.keys(parentStateValue).every(key => {
364
+ if (!(key in childStateValue)) {
365
+ return false;
366
+ }
367
+ return matchesState(parentStateValue[key], childStateValue[key]);
368
+ });
369
+ }
370
+ function toStatePath(stateId) {
371
+ try {
372
+ if (isArray(stateId)) {
373
+ return stateId;
374
+ }
375
+ return stateId.toString().split(STATE_DELIMITER);
376
+ } catch (e) {
377
+ throw new Error(`'${stateId}' is not a valid state path.`);
378
+ }
379
+ }
380
+ function isStateLike(state) {
381
+ return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
382
+ }
383
+ function toStateValue(stateValue) {
384
+ if (isStateLike(stateValue)) {
385
+ return stateValue.value;
386
+ }
387
+ if (isArray(stateValue)) {
388
+ return pathToStateValue(stateValue);
389
+ }
390
+ if (typeof stateValue !== 'string') {
391
+ return stateValue;
392
+ }
393
+ const statePath = toStatePath(stateValue);
394
+ return pathToStateValue(statePath);
395
+ }
396
+ function pathToStateValue(statePath) {
397
+ if (statePath.length === 1) {
398
+ return statePath[0];
399
+ }
400
+ const value = {};
401
+ let marker = value;
402
+ for (let i = 0; i < statePath.length - 1; i++) {
403
+ if (i === statePath.length - 2) {
404
+ marker[statePath[i]] = statePath[i + 1];
405
+ } else {
406
+ const previous = marker;
407
+ marker = {};
408
+ previous[statePath[i]] = marker;
409
+ }
410
+ }
411
+ return value;
412
+ }
413
+ function mapValues(collection, iteratee) {
414
+ const result = {};
415
+ const collectionKeys = Object.keys(collection);
416
+ for (let i = 0; i < collectionKeys.length; i++) {
417
+ const key = collectionKeys[i];
418
+ result[key] = iteratee(collection[key], key, collection, i);
419
+ }
420
+ return result;
421
+ }
422
+ function flatten(array) {
423
+ return [].concat(...array);
424
+ }
425
+ function toArrayStrict(value) {
426
+ if (isArray(value)) {
427
+ return value;
428
+ }
429
+ return [value];
430
+ }
431
+ function toArray(value) {
432
+ if (value === undefined) {
433
+ return [];
434
+ }
435
+ return toArrayStrict(value);
436
+ }
437
+ function mapContext(mapper, context, event, self) {
438
+ if (typeof mapper === 'function') {
439
+ return mapper({
440
+ context,
441
+ event,
442
+ self
443
+ });
444
+ }
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('')}`);
447
+ }
448
+ return mapper;
449
+ }
450
+ function isPromiseLike(value) {
451
+ if (value instanceof Promise) {
452
+ return true;
453
+ }
454
+ // Check if shape matches the Promise/A+ specification for a "thenable".
455
+ if (value !== null && (typeof value === 'function' || typeof value === 'object') && typeof value.then === 'function') {
456
+ return true;
457
+ }
458
+ return false;
459
+ }
460
+ function isArray(value) {
461
+ return Array.isArray(value);
462
+ }
463
+ function isErrorEvent(event) {
464
+ return typeof event.type === 'string' && (event.type === errorExecution || event.type.startsWith(errorPlatform));
465
+ }
466
+ function toTransitionConfigArray(configLike) {
467
+ return toArrayStrict(configLike).map(transitionLike => {
468
+ if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
469
+ return {
470
+ target: transitionLike
471
+ };
472
+ }
473
+ return transitionLike;
474
+ });
475
+ }
476
+ function normalizeTarget(target) {
477
+ if (target === undefined || target === TARGETLESS_KEY) {
478
+ return undefined;
479
+ }
480
+ return toArray(target);
481
+ }
482
+ function toInvokeConfig(invocable, id) {
483
+ if (typeof invocable === 'object') {
484
+ if ('src' in invocable) {
485
+ return invocable;
486
+ }
487
+ if ('transition' in invocable) {
488
+ return {
489
+ id,
490
+ src: invocable
491
+ };
492
+ }
493
+ }
494
+ return {
495
+ id,
496
+ src: invocable
497
+ };
498
+ }
499
+ function toObserver(nextHandler, errorHandler, completionHandler) {
500
+ const isObserver = typeof nextHandler === 'object';
501
+ const self = isObserver ? nextHandler : undefined;
502
+ return {
503
+ next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
504
+ error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
505
+ complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
506
+ };
507
+ }
508
+ function createInvokeId(stateNodeId, index) {
509
+ return `${stateNodeId}:invocation[${index}]`;
510
+ }
511
+ function resolveReferencedActor(referenced) {
512
+ return referenced ? 'transition' in referenced ? {
513
+ src: referenced,
514
+ input: undefined
515
+ } : referenced : undefined;
516
+ }
517
+
518
+ function fromCallback(invokeCallback) {
519
+ return {
520
+ config: invokeCallback,
521
+ start: (_state, {
522
+ self
523
+ }) => {
524
+ self.send({
525
+ type: startSignalType
526
+ });
379
527
  },
380
- start: (state, {
528
+ transition: (state, event, {
381
529
  self,
530
+ id,
382
531
  system
383
532
  }) => {
384
- // TODO: determine how to allow customizing this so that promises
385
- // can be restarted if necessary
386
- if (state.status !== 'active') {
387
- return;
533
+ if (event.type === startSignalType) {
534
+ const sendBack = eventForParent => {
535
+ if (state.canceled) {
536
+ return;
537
+ }
538
+ self._parent?.send(eventForParent);
539
+ };
540
+ const receive = newListener => {
541
+ state.receivers.add(newListener);
542
+ };
543
+ state.dispose = invokeCallback({
544
+ input: state.input,
545
+ system,
546
+ self: self,
547
+ sendBack,
548
+ receive
549
+ });
550
+ if (isPromiseLike(state.dispose)) {
551
+ state.dispose.then(resolved => {
552
+ self._parent?.send(doneInvoke(id, resolved));
553
+ state.canceled = true;
554
+ }, errorData => {
555
+ state.canceled = true;
556
+ self._parent?.send(error(id, errorData));
557
+ });
558
+ }
559
+ return state;
388
560
  }
389
- const resolvedPromise = Promise.resolve(promiseCreator({
390
- input: state.input,
391
- system,
392
- self
393
- }));
394
- resolvedPromise.then(response => {
395
- // TODO: remove this condition once dead letter queue lands
396
- if (self._state.status !== 'active') {
397
- return;
561
+ if (event.type === stopSignalType) {
562
+ state.canceled = true;
563
+ if (typeof state.dispose === 'function') {
564
+ state.dispose();
398
565
  }
399
- self.send({
400
- type: resolveEventType,
401
- data: response
402
- });
403
- }, errorData => {
404
- // TODO: remove this condition once dead letter queue lands
405
- if (self._state.status !== 'active') {
406
- return;
407
- }
408
- self.send({
409
- type: rejectEventType,
410
- data: errorData
411
- });
412
- });
566
+ return state;
567
+ }
568
+ if (isSignal(event)) {
569
+ // TODO: unrecognized signal
570
+ return state;
571
+ }
572
+ state.receivers.forEach(receiver => receiver(event));
573
+ return state;
413
574
  },
414
575
  getInitialState: (_, input) => {
415
576
  return {
416
- status: 'active',
417
- data: undefined,
577
+ canceled: false,
578
+ receivers: new Set(),
579
+ dispose: undefined,
418
580
  input
419
581
  };
420
582
  },
421
- getSnapshot: state => state.data,
422
- getStatus: state => state,
423
- getPersistedState: state => state,
424
- restoreState: state => state
583
+ getSnapshot: () => undefined,
584
+ getPersistedState: ({
585
+ input,
586
+ canceled
587
+ }) => ({
588
+ input,
589
+ canceled
590
+ })
425
591
  };
426
- return logic;
427
592
  }
428
593
 
429
- // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
430
594
  function fromObservable(observableCreator) {
431
595
  const nextEventType = '$$xstate.next';
432
596
  const errorEventType = '$$xstate.error';
433
597
  const completeEventType = '$$xstate.complete';
434
-
435
- // TODO: add event types
436
- const logic = {
598
+ return {
437
599
  config: observableCreator,
438
600
  transition: (state, event, {
439
601
  self,
@@ -463,6 +625,7 @@ function fromObservable(observableCreator) {
463
625
  status: 'error',
464
626
  input: undefined,
465
627
  data: event.data,
628
+ // TODO: if we keep this as `data` we should reflect this in the type
466
629
  subscription: undefined
467
630
  };
468
631
  case completeEventType:
@@ -540,7 +703,6 @@ function fromObservable(observableCreator) {
540
703
  subscription: undefined
541
704
  })
542
705
  };
543
- return logic;
544
706
  }
545
707
 
546
708
  /**
@@ -557,7 +719,7 @@ function fromEventObservable(lazyObservable) {
557
719
  const completeEventType = '$$xstate.complete';
558
720
 
559
721
  // TODO: event types
560
- const logic = {
722
+ return {
561
723
  config: lazyObservable,
562
724
  transition: (state, event) => {
563
725
  if (state.status !== 'active') {
@@ -570,6 +732,7 @@ function fromEventObservable(lazyObservable) {
570
732
  status: 'error',
571
733
  input: undefined,
572
734
  data: event.data,
735
+ // TODO: if we keep this as `data` we should reflect this in the type
573
736
  subscription: undefined
574
737
  };
575
738
  case completeEventType:
@@ -644,255 +807,91 @@ function fromEventObservable(lazyObservable) {
644
807
  subscription: undefined
645
808
  })
646
809
  };
647
- return logic;
648
- }
649
-
650
- function matchesState(parentStateId, childStateId) {
651
- const parentStateValue = toStateValue(parentStateId);
652
- const childStateValue = toStateValue(childStateId);
653
- if (typeof childStateValue === 'string') {
654
- if (typeof parentStateValue === 'string') {
655
- return childStateValue === parentStateValue;
656
- }
657
-
658
- // Parent more specific than child
659
- return false;
660
- }
661
- if (typeof parentStateValue === 'string') {
662
- return parentStateValue in childStateValue;
663
- }
664
- return Object.keys(parentStateValue).every(key => {
665
- if (!(key in childStateValue)) {
666
- return false;
667
- }
668
- return matchesState(parentStateValue[key], childStateValue[key]);
669
- });
670
- }
671
- function toStatePath(stateId) {
672
- try {
673
- if (isArray(stateId)) {
674
- return stateId;
675
- }
676
- return stateId.toString().split(STATE_DELIMITER);
677
- } catch (e) {
678
- throw new Error(`'${stateId}' is not a valid state path.`);
679
- }
680
- }
681
- function isStateLike(state) {
682
- return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
683
- }
684
- function toStateValue(stateValue) {
685
- if (isStateLike(stateValue)) {
686
- return stateValue.value;
687
- }
688
- if (isArray(stateValue)) {
689
- return pathToStateValue(stateValue);
690
- }
691
- if (typeof stateValue !== 'string') {
692
- return stateValue;
693
- }
694
- const statePath = toStatePath(stateValue);
695
- return pathToStateValue(statePath);
696
- }
697
- function pathToStateValue(statePath) {
698
- if (statePath.length === 1) {
699
- return statePath[0];
700
- }
701
- const value = {};
702
- let marker = value;
703
- for (let i = 0; i < statePath.length - 1; i++) {
704
- if (i === statePath.length - 2) {
705
- marker[statePath[i]] = statePath[i + 1];
706
- } else {
707
- const previous = marker;
708
- marker = {};
709
- previous[statePath[i]] = marker;
710
- }
711
- }
712
- return value;
713
- }
714
- function mapValues(collection, iteratee) {
715
- const result = {};
716
- const collectionKeys = Object.keys(collection);
717
- for (let i = 0; i < collectionKeys.length; i++) {
718
- const key = collectionKeys[i];
719
- result[key] = iteratee(collection[key], key, collection, i);
720
- }
721
- return result;
722
- }
723
- function flatten(array) {
724
- return [].concat(...array);
725
- }
726
- function toArrayStrict(value) {
727
- if (isArray(value)) {
728
- return value;
729
- }
730
- return [value];
731
- }
732
- function toArray(value) {
733
- if (value === undefined) {
734
- return [];
735
- }
736
- return toArrayStrict(value);
737
- }
738
- function mapContext(mapper, context, event) {
739
- if (typeof mapper === 'function') {
740
- return mapper({
741
- context,
742
- event
743
- });
744
- }
745
- const result = {};
746
- const args = {
747
- context,
748
- event
749
- };
750
- for (const key of Object.keys(mapper)) {
751
- const subMapper = mapper[key];
752
- if (typeof subMapper === 'function') {
753
- result[key] = subMapper(args);
754
- } else {
755
- result[key] = subMapper;
756
- }
757
- }
758
- return result;
759
- }
760
- function isPromiseLike(value) {
761
- if (value instanceof Promise) {
762
- return true;
763
- }
764
- // Check if shape matches the Promise/A+ specification for a "thenable".
765
- if (value !== null && (typeof value === 'function' || typeof value === 'object') && typeof value.then === 'function') {
766
- return true;
767
- }
768
- return false;
769
- }
770
- function isArray(value) {
771
- return Array.isArray(value);
772
- }
773
- function isErrorEvent(event) {
774
- return typeof event.type === 'string' && (event.type === errorExecution || event.type.startsWith(errorPlatform));
775
- }
776
- function toTransitionConfigArray(configLike) {
777
- return toArrayStrict(configLike).map(transitionLike => {
778
- if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
779
- return {
780
- target: transitionLike
781
- };
782
- }
783
- return transitionLike;
784
- });
785
- }
786
- function normalizeTarget(target) {
787
- if (target === undefined || target === TARGETLESS_KEY) {
788
- return undefined;
789
- }
790
- return toArray(target);
791
- }
792
- function toInvokeConfig(invocable, id) {
793
- if (typeof invocable === 'object') {
794
- if ('src' in invocable) {
795
- return invocable;
796
- }
797
- if ('transition' in invocable) {
798
- return {
799
- id,
800
- src: invocable
801
- };
802
- }
803
- }
804
- return {
805
- id,
806
- src: invocable
807
- };
808
- }
809
- function toObserver(nextHandler, errorHandler, completionHandler) {
810
- const noop = () => {};
811
- const isObserver = typeof nextHandler === 'object';
812
- const self = isObserver ? nextHandler : null;
813
- return {
814
- next: ((isObserver ? nextHandler.next : nextHandler) || noop).bind(self),
815
- error: ((isObserver ? nextHandler.error : errorHandler) || noop).bind(self),
816
- complete: ((isObserver ? nextHandler.complete : completionHandler) || noop).bind(self)
817
- };
818
- }
819
- function createInvokeId(stateNodeId, index) {
820
- return `${stateNodeId}:invocation[${index}]`;
821
- }
822
- function resolveReferencedActor(referenced) {
823
- return referenced ? 'transition' in referenced ? {
824
- src: referenced,
825
- input: undefined
826
- } : referenced : undefined;
827
810
  }
828
811
 
829
- function fromCallback(invokeCallback) {
812
+ const resolveEventType = '$$xstate.resolve';
813
+ const rejectEventType = '$$xstate.reject';
814
+ function fromPromise(
815
+ // TODO: add types
816
+ promiseCreator) {
817
+ // TODO: add event types
830
818
  const logic = {
831
- config: invokeCallback,
832
- start: (_state, {
833
- self
834
- }) => {
835
- self.send({
836
- type: startSignalType
837
- });
819
+ config: promiseCreator,
820
+ transition: (state, event) => {
821
+ if (state.status !== 'active') {
822
+ return state;
823
+ }
824
+ switch (event.type) {
825
+ case resolveEventType:
826
+ return {
827
+ ...state,
828
+ status: 'done',
829
+ data: event.data,
830
+ input: undefined
831
+ };
832
+ case rejectEventType:
833
+ return {
834
+ ...state,
835
+ status: 'error',
836
+ data: event.data,
837
+ // TODO: if we keep this as `data` we should reflect this in the type
838
+ input: undefined
839
+ };
840
+ case stopSignalType:
841
+ return {
842
+ ...state,
843
+ status: 'canceled',
844
+ input: undefined
845
+ };
846
+ default:
847
+ return state;
848
+ }
838
849
  },
839
- transition: (state, event, {
850
+ start: (state, {
840
851
  self,
841
- id,
842
852
  system
843
853
  }) => {
844
- if (event.type === startSignalType) {
845
- const sender = eventForParent => {
846
- if (state.canceled) {
847
- return;
848
- }
849
- self._parent?.send(eventForParent);
850
- };
851
- const receiver = newListener => {
852
- state.receivers.add(newListener);
853
- };
854
- state.dispose = invokeCallback(sender, receiver, {
855
- input: state.input,
856
- system,
857
- self: self
858
- });
859
- if (isPromiseLike(state.dispose)) {
860
- state.dispose.then(resolved => {
861
- self._parent?.send(doneInvoke(id, resolved));
862
- state.canceled = true;
863
- }, errorData => {
864
- state.canceled = true;
865
- self._parent?.send(error(id, errorData));
866
- });
867
- }
868
- return state;
854
+ // TODO: determine how to allow customizing this so that promises
855
+ // can be restarted if necessary
856
+ if (state.status !== 'active') {
857
+ return;
869
858
  }
870
- if (event.type === stopSignalType) {
871
- state.canceled = true;
872
- if (typeof state.dispose === 'function') {
873
- state.dispose();
859
+ const resolvedPromise = Promise.resolve(promiseCreator({
860
+ input: state.input,
861
+ system,
862
+ self
863
+ }));
864
+ resolvedPromise.then(response => {
865
+ // TODO: remove this condition once dead letter queue lands
866
+ if (self._state.status !== 'active') {
867
+ return;
874
868
  }
875
- return state;
876
- }
877
- if (isSignal(event)) {
878
- // TODO: unrecognized signal
879
- return state;
880
- }
881
- state.receivers.forEach(receiver => receiver(event));
882
- return state;
869
+ self.send({
870
+ type: resolveEventType,
871
+ data: response
872
+ });
873
+ }, errorData => {
874
+ // TODO: remove this condition once dead letter queue lands
875
+ if (self._state.status !== 'active') {
876
+ return;
877
+ }
878
+ self.send({
879
+ type: rejectEventType,
880
+ data: errorData
881
+ });
882
+ });
883
883
  },
884
884
  getInitialState: (_, input) => {
885
885
  return {
886
- canceled: false,
887
- receivers: new Set(),
888
- dispose: undefined,
886
+ status: 'active',
887
+ data: undefined,
889
888
  input
890
889
  };
891
890
  },
892
- getSnapshot: () => undefined,
893
- getPersistedState: ({
894
- input
895
- }) => input
891
+ getSnapshot: state => state.data,
892
+ getStatus: state => state,
893
+ getPersistedState: state => state,
894
+ restoreState: state => state
896
895
  };
897
896
  return logic;
898
897
  }
@@ -931,6 +930,7 @@ function toActorRef(actorRefLike) {
931
930
  id: 'anonymous',
932
931
  sessionId: '',
933
932
  getSnapshot: () => undefined,
933
+ // TODO: this isn't safe
934
934
  [symbolObservable]: function () {
935
935
  return this;
936
936
  },
@@ -944,6 +944,19 @@ function createEmptyActor() {
944
944
  return interpret(emptyLogic);
945
945
  }
946
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
+
947
960
  function createSystem() {
948
961
  let sessionIdCounter = 0;
949
962
  const children = new Map();
@@ -1112,29 +1125,38 @@ class Interpreter {
1112
1125
  deferredFn();
1113
1126
  }
1114
1127
  for (const observer of this.observers) {
1115
- 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
+ }
1116
1134
  }
1117
1135
  const status = this.logic.getStatus?.(state);
1118
1136
  switch (status?.status) {
1119
1137
  case 'done':
1120
1138
  this._stopProcedure();
1139
+ this._complete();
1121
1140
  this._doneEvent = doneInvoke(this.id, status.data);
1122
1141
  this._parent?.send(this._doneEvent);
1123
- this._complete();
1124
1142
  break;
1125
1143
  case 'error':
1126
1144
  this._stopProcedure();
1127
- this._parent?.send(error(this.id, status.data));
1128
1145
  this._error(status.data);
1146
+ this._parent?.send(error(this.id, status.data));
1129
1147
  break;
1130
1148
  }
1131
1149
  }
1132
1150
  subscribe(nextListenerOrObserver, errorListener, completeListener) {
1133
1151
  const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
1134
- this.observers.add(observer);
1135
- if (this.status === ActorStatus.Stopped) {
1136
- observer.complete?.();
1137
- 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
+ }
1138
1160
  }
1139
1161
  return {
1140
1162
  unsubscribe: () => {
@@ -1156,8 +1178,26 @@ class Interpreter {
1156
1178
  this.system._set(this._systemId, this);
1157
1179
  }
1158
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
+ }
1159
1192
  if (this.logic.start) {
1160
- 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
+ }
1161
1201
  }
1162
1202
 
1163
1203
  // TODO: this notifies all subscribers but usually this is redundant
@@ -1171,23 +1211,30 @@ class Interpreter {
1171
1211
  return this;
1172
1212
  }
1173
1213
  _process(event) {
1214
+ // TODO: reexamine what happens when an action (or a guard or smth) throws
1215
+ let nextState;
1216
+ let caughtError;
1174
1217
  try {
1175
- const nextState = this.logic.transition(this._state, event, this._actorContext);
1176
- this.update(nextState);
1177
- if (event.type === stopSignalType) {
1178
- this._stopProcedure();
1179
- this._complete();
1180
- }
1218
+ nextState = this.logic.transition(this._state, event, this._actorContext);
1181
1219
  } catch (err) {
1182
- // TODO: properly handle errors
1183
- if (this.observers.size > 0) {
1184
- this.observers.forEach(observer => {
1185
- observer.error?.(err);
1186
- });
1187
- this.stop();
1188
- } else {
1189
- throw err;
1190
- }
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();
1191
1238
  }
1192
1239
  }
1193
1240
  _stop() {
@@ -1216,15 +1263,35 @@ class Interpreter {
1216
1263
  }
1217
1264
  _complete() {
1218
1265
  for (const observer of this.observers) {
1219
- observer.complete?.();
1266
+ try {
1267
+ observer.complete?.();
1268
+ } catch (err) {
1269
+ reportUnhandledError(err);
1270
+ }
1220
1271
  }
1221
1272
  this.observers.clear();
1222
1273
  }
1223
- _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;
1224
1282
  for (const observer of this.observers) {
1225
- observer.error?.(data);
1283
+ const errorListener = observer.error;
1284
+ reportError ||= !errorListener;
1285
+ try {
1286
+ errorListener?.(err);
1287
+ } catch (err2) {
1288
+ reportUnhandledError(err2);
1289
+ }
1226
1290
  }
1227
1291
  this.observers.clear();
1292
+ if (reportError) {
1293
+ reportUnhandledError(err);
1294
+ }
1228
1295
  }
1229
1296
  _stopProcedure() {
1230
1297
  if (this.status !== ActorStatus.Running) {
@@ -1557,10 +1624,10 @@ function toGuardDefinition(guardConfig, getPredicate) {
1557
1624
  }
1558
1625
  }
1559
1626
 
1560
- function getOutput(configuration, context, event) {
1627
+ function getOutput(configuration, context, event, self) {
1561
1628
  const machine = configuration[0].machine;
1562
1629
  const finalChildStateNode = configuration.find(stateNode => stateNode.type === 'final' && stateNode.parent === machine.root);
1563
- return finalChildStateNode && finalChildStateNode.output ? mapContext(finalChildStateNode.output, context, event) : undefined;
1630
+ return finalChildStateNode && finalChildStateNode.output ? mapContext(finalChildStateNode.output, context, event, self) : undefined;
1564
1631
  }
1565
1632
  const isAtomicStateNode = stateNode => stateNode.type === 'atomic' || stateNode.type === 'final';
1566
1633
  function getChildren(stateNode) {
@@ -2187,7 +2254,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2187
2254
  actions.push(...filteredTransitions.flatMap(t => t.actions));
2188
2255
 
2189
2256
  // Enter states
2190
- enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial);
2257
+ enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial, actorCtx);
2191
2258
  const nextConfiguration = [...mutConfiguration];
2192
2259
  const done = isInFinalState(nextConfiguration);
2193
2260
  if (done) {
@@ -2196,7 +2263,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2196
2263
  }
2197
2264
  try {
2198
2265
  const nextState = resolveActionsAndContext(actions, event, currentState, actorCtx);
2199
- const output = done ? getOutput(nextConfiguration, nextState.context, event) : undefined;
2266
+ const output = done ? getOutput(nextConfiguration, nextState.context, event, actorCtx.self) : undefined;
2200
2267
  internalQueue.push(...nextState._internalQueue);
2201
2268
  return cloneState(currentState, {
2202
2269
  configuration: nextConfiguration,
@@ -2213,7 +2280,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2213
2280
  throw e;
2214
2281
  }
2215
2282
  }
2216
- function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial) {
2283
+ function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial, actorContext) {
2217
2284
  const statesToEnter = new Set();
2218
2285
  const statesForDefaultEntry = new Set();
2219
2286
  computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
@@ -2241,7 +2308,7 @@ function enterStates(event, filteredTransitions, mutConfiguration, actions, inte
2241
2308
  if (!parent.parent) {
2242
2309
  continue;
2243
2310
  }
2244
- 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));
2245
2312
  if (parent.parent) {
2246
2313
  const grandparent = parent.parent;
2247
2314
  if (grandparent.type === 'parallel') {
@@ -2564,6 +2631,7 @@ class State {
2564
2631
  this.value = void 0;
2565
2632
  this.done = void 0;
2566
2633
  this.output = void 0;
2634
+ this.error = void 0;
2567
2635
  this.context = void 0;
2568
2636
  this.historyValue = {};
2569
2637
  this._internalQueue = void 0;
@@ -2580,6 +2648,7 @@ class State {
2580
2648
  this.tags = new Set(flatten(this.configuration.map(sn => sn.tags)));
2581
2649
  this.done = config.done ?? false;
2582
2650
  this.output = config.output;
2651
+ this.error = config.error;
2583
2652
  }
2584
2653
 
2585
2654
  /**
@@ -2824,7 +2893,7 @@ function createSpawner(actorContext, {
2824
2893
  }
2825
2894
  };
2826
2895
  return (src, options) => {
2827
- const actorRef = spawn(src, options);
2896
+ const actorRef = spawn(src, options); // TODO: fix types
2828
2897
  spawnedChildren[actorRef.id] = actorRef;
2829
2898
  actorContext.defer(() => {
2830
2899
  if (actorRef.status === ActorStatus.Stopped) {
@@ -3062,6 +3131,7 @@ exports.and = and;
3062
3131
  exports.assign = assign;
3063
3132
  exports.cancel = cancel;
3064
3133
  exports.choose = choose;
3134
+ exports.cloneState = cloneState;
3065
3135
  exports.constantPrefixes = constantPrefixes;
3066
3136
  exports.createEmptyActor = createEmptyActor;
3067
3137
  exports.createInitEvent = createInitEvent;