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
@@ -1,9 +1,18 @@
1
1
  import { devToolsAdapter } from '../dev/dist/xstate-dev.development.esm.js';
2
2
 
3
- // https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
3
+ /**
4
+ * `T | unknown` reduces to `unknown` and that can be problematic when it comes to contextual typing.
5
+ * It especially is a problem when the union has a function member, like here:
6
+ *
7
+ * ```ts
8
+ * declare function test(cbOrVal: ((arg: number) => unknown) | unknown): void;
9
+ * test((arg) => {}) // oops, implicit any
10
+ * ```
11
+ *
12
+ * This type can be used to avoid this problem. This union represents the same value space as `unknown`.
13
+ */
4
14
 
5
- // TODO: replace in v5 with:
6
- // export type IndexByType<T extends { type: string }> = { [E in T as E['type']]: E; };
15
+ // https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
7
16
 
8
17
  /**
9
18
  * The full definition of an event, with a string `type`.
@@ -13,8 +22,6 @@ import { devToolsAdapter } from '../dev/dist/xstate-dev.development.esm.js';
13
22
  // we should also accept a raw machine as actor logic here
14
23
  // or just make machine actor logic
15
24
 
16
- // TODO: narrow this to logic from machine
17
-
18
25
  /**
19
26
  * The string or object representing the state value relative to the parent state node.
20
27
  *
@@ -321,7 +328,7 @@ const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observabl
321
328
  * @returns Actor logic
322
329
  */
323
330
  function fromTransition(transition, initialState) {
324
- const logic = {
331
+ return {
325
332
  config: transition,
326
333
  transition: (state, event, actorContext) => {
327
334
  return transition(state, event, actorContext);
@@ -335,103 +342,258 @@ function fromTransition(transition, initialState) {
335
342
  getPersistedState: state => state,
336
343
  restoreState: state => state
337
344
  };
338
- return logic;
339
345
  }
340
346
 
341
- const resolveEventType = '$$xstate.resolve';
342
- const rejectEventType = '$$xstate.reject';
343
- function fromPromise(
344
- // TODO: add types
345
- promiseCreator) {
346
- // TODO: add event types, consider making the `PromiseEvent` a private type or smth alike
347
- const logic = {
348
- config: promiseCreator,
349
- transition: (state, event) => {
350
- if (state.status !== 'active') {
351
- return state;
352
- }
353
- switch (event.type) {
354
- case resolveEventType:
355
- return {
356
- ...state,
357
- status: 'done',
358
- data: event.data,
359
- input: undefined
360
- };
361
- case rejectEventType:
362
- return {
363
- ...state,
364
- status: 'error',
365
- data: event.data,
366
- input: undefined
367
- };
368
- case stopSignalType:
369
- return {
370
- ...state,
371
- status: 'canceled',
372
- input: undefined
373
- };
374
- default:
375
- return state;
376
- }
347
+ function matchesState(parentStateId, childStateId) {
348
+ const parentStateValue = toStateValue(parentStateId);
349
+ const childStateValue = toStateValue(childStateId);
350
+ if (typeof childStateValue === 'string') {
351
+ if (typeof parentStateValue === 'string') {
352
+ return childStateValue === parentStateValue;
353
+ }
354
+
355
+ // Parent more specific than child
356
+ return false;
357
+ }
358
+ if (typeof parentStateValue === 'string') {
359
+ return parentStateValue in childStateValue;
360
+ }
361
+ return Object.keys(parentStateValue).every(key => {
362
+ if (!(key in childStateValue)) {
363
+ return false;
364
+ }
365
+ return matchesState(parentStateValue[key], childStateValue[key]);
366
+ });
367
+ }
368
+ function toStatePath(stateId) {
369
+ try {
370
+ if (isArray(stateId)) {
371
+ return stateId;
372
+ }
373
+ return stateId.toString().split(STATE_DELIMITER);
374
+ } catch (e) {
375
+ throw new Error(`'${stateId}' is not a valid state path.`);
376
+ }
377
+ }
378
+ function isStateLike(state) {
379
+ return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
380
+ }
381
+ function toStateValue(stateValue) {
382
+ if (isStateLike(stateValue)) {
383
+ return stateValue.value;
384
+ }
385
+ if (isArray(stateValue)) {
386
+ return pathToStateValue(stateValue);
387
+ }
388
+ if (typeof stateValue !== 'string') {
389
+ return stateValue;
390
+ }
391
+ const statePath = toStatePath(stateValue);
392
+ return pathToStateValue(statePath);
393
+ }
394
+ function pathToStateValue(statePath) {
395
+ if (statePath.length === 1) {
396
+ return statePath[0];
397
+ }
398
+ const value = {};
399
+ let marker = value;
400
+ for (let i = 0; i < statePath.length - 1; i++) {
401
+ if (i === statePath.length - 2) {
402
+ marker[statePath[i]] = statePath[i + 1];
403
+ } else {
404
+ const previous = marker;
405
+ marker = {};
406
+ previous[statePath[i]] = marker;
407
+ }
408
+ }
409
+ return value;
410
+ }
411
+ function mapValues(collection, iteratee) {
412
+ const result = {};
413
+ const collectionKeys = Object.keys(collection);
414
+ for (let i = 0; i < collectionKeys.length; i++) {
415
+ const key = collectionKeys[i];
416
+ result[key] = iteratee(collection[key], key, collection, i);
417
+ }
418
+ return result;
419
+ }
420
+ function flatten(array) {
421
+ return [].concat(...array);
422
+ }
423
+ function toArrayStrict(value) {
424
+ if (isArray(value)) {
425
+ return value;
426
+ }
427
+ return [value];
428
+ }
429
+ function toArray(value) {
430
+ if (value === undefined) {
431
+ return [];
432
+ }
433
+ return toArrayStrict(value);
434
+ }
435
+ function mapContext(mapper, context, event, self) {
436
+ if (typeof mapper === 'function') {
437
+ return mapper({
438
+ context,
439
+ event,
440
+ self
441
+ });
442
+ }
443
+ if (typeof mapper === 'object' && Object.values(mapper).some(val => typeof val === 'function')) {
444
+ 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('')}`);
445
+ }
446
+ return mapper;
447
+ }
448
+ function isPromiseLike(value) {
449
+ if (value instanceof Promise) {
450
+ return true;
451
+ }
452
+ // Check if shape matches the Promise/A+ specification for a "thenable".
453
+ if (value !== null && (typeof value === 'function' || typeof value === 'object') && typeof value.then === 'function') {
454
+ return true;
455
+ }
456
+ return false;
457
+ }
458
+ function isArray(value) {
459
+ return Array.isArray(value);
460
+ }
461
+ function isErrorEvent(event) {
462
+ return typeof event.type === 'string' && (event.type === errorExecution || event.type.startsWith(errorPlatform));
463
+ }
464
+ function toTransitionConfigArray(configLike) {
465
+ return toArrayStrict(configLike).map(transitionLike => {
466
+ if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
467
+ return {
468
+ target: transitionLike
469
+ };
470
+ }
471
+ return transitionLike;
472
+ });
473
+ }
474
+ function normalizeTarget(target) {
475
+ if (target === undefined || target === TARGETLESS_KEY) {
476
+ return undefined;
477
+ }
478
+ return toArray(target);
479
+ }
480
+ function toInvokeConfig(invocable, id) {
481
+ if (typeof invocable === 'object') {
482
+ if ('src' in invocable) {
483
+ return invocable;
484
+ }
485
+ if ('transition' in invocable) {
486
+ return {
487
+ id,
488
+ src: invocable
489
+ };
490
+ }
491
+ }
492
+ return {
493
+ id,
494
+ src: invocable
495
+ };
496
+ }
497
+ function toObserver(nextHandler, errorHandler, completionHandler) {
498
+ const isObserver = typeof nextHandler === 'object';
499
+ const self = isObserver ? nextHandler : undefined;
500
+ return {
501
+ next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
502
+ error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
503
+ complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
504
+ };
505
+ }
506
+ function createInvokeId(stateNodeId, index) {
507
+ return `${stateNodeId}:invocation[${index}]`;
508
+ }
509
+ function resolveReferencedActor(referenced) {
510
+ return referenced ? 'transition' in referenced ? {
511
+ src: referenced,
512
+ input: undefined
513
+ } : referenced : undefined;
514
+ }
515
+
516
+ function fromCallback(invokeCallback) {
517
+ return {
518
+ config: invokeCallback,
519
+ start: (_state, {
520
+ self
521
+ }) => {
522
+ self.send({
523
+ type: startSignalType
524
+ });
377
525
  },
378
- start: (state, {
526
+ transition: (state, event, {
379
527
  self,
528
+ id,
380
529
  system
381
530
  }) => {
382
- // TODO: determine how to allow customizing this so that promises
383
- // can be restarted if necessary
384
- if (state.status !== 'active') {
385
- return;
531
+ if (event.type === startSignalType) {
532
+ const sendBack = eventForParent => {
533
+ if (state.canceled) {
534
+ return;
535
+ }
536
+ self._parent?.send(eventForParent);
537
+ };
538
+ const receive = newListener => {
539
+ state.receivers.add(newListener);
540
+ };
541
+ state.dispose = invokeCallback({
542
+ input: state.input,
543
+ system,
544
+ self: self,
545
+ sendBack,
546
+ receive
547
+ });
548
+ if (isPromiseLike(state.dispose)) {
549
+ state.dispose.then(resolved => {
550
+ self._parent?.send(doneInvoke(id, resolved));
551
+ state.canceled = true;
552
+ }, errorData => {
553
+ state.canceled = true;
554
+ self._parent?.send(error(id, errorData));
555
+ });
556
+ }
557
+ return state;
386
558
  }
387
- const resolvedPromise = Promise.resolve(promiseCreator({
388
- input: state.input,
389
- system,
390
- self
391
- }));
392
- resolvedPromise.then(response => {
393
- // TODO: remove this condition once dead letter queue lands
394
- if (self._state.status !== 'active') {
395
- return;
559
+ if (event.type === stopSignalType) {
560
+ state.canceled = true;
561
+ if (typeof state.dispose === 'function') {
562
+ state.dispose();
396
563
  }
397
- self.send({
398
- type: resolveEventType,
399
- data: response
400
- });
401
- }, errorData => {
402
- // TODO: remove this condition once dead letter queue lands
403
- if (self._state.status !== 'active') {
404
- return;
405
- }
406
- self.send({
407
- type: rejectEventType,
408
- data: errorData
409
- });
410
- });
564
+ return state;
565
+ }
566
+ if (isSignal(event)) {
567
+ // TODO: unrecognized signal
568
+ return state;
569
+ }
570
+ state.receivers.forEach(receiver => receiver(event));
571
+ return state;
411
572
  },
412
573
  getInitialState: (_, input) => {
413
574
  return {
414
- status: 'active',
415
- data: undefined,
575
+ canceled: false,
576
+ receivers: new Set(),
577
+ dispose: undefined,
416
578
  input
417
579
  };
418
580
  },
419
- getSnapshot: state => state.data,
420
- getStatus: state => state,
421
- getPersistedState: state => state,
422
- restoreState: state => state
581
+ getSnapshot: () => undefined,
582
+ getPersistedState: ({
583
+ input,
584
+ canceled
585
+ }) => ({
586
+ input,
587
+ canceled
588
+ })
423
589
  };
424
- return logic;
425
590
  }
426
591
 
427
- // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
428
592
  function fromObservable(observableCreator) {
429
593
  const nextEventType = '$$xstate.next';
430
594
  const errorEventType = '$$xstate.error';
431
595
  const completeEventType = '$$xstate.complete';
432
-
433
- // TODO: add event types
434
- const logic = {
596
+ return {
435
597
  config: observableCreator,
436
598
  transition: (state, event, {
437
599
  self,
@@ -461,6 +623,7 @@ function fromObservable(observableCreator) {
461
623
  status: 'error',
462
624
  input: undefined,
463
625
  data: event.data,
626
+ // TODO: if we keep this as `data` we should reflect this in the type
464
627
  subscription: undefined
465
628
  };
466
629
  case completeEventType:
@@ -538,7 +701,6 @@ function fromObservable(observableCreator) {
538
701
  subscription: undefined
539
702
  })
540
703
  };
541
- return logic;
542
704
  }
543
705
 
544
706
  /**
@@ -555,7 +717,7 @@ function fromEventObservable(lazyObservable) {
555
717
  const completeEventType = '$$xstate.complete';
556
718
 
557
719
  // TODO: event types
558
- const logic = {
720
+ return {
559
721
  config: lazyObservable,
560
722
  transition: (state, event) => {
561
723
  if (state.status !== 'active') {
@@ -568,6 +730,7 @@ function fromEventObservable(lazyObservable) {
568
730
  status: 'error',
569
731
  input: undefined,
570
732
  data: event.data,
733
+ // TODO: if we keep this as `data` we should reflect this in the type
571
734
  subscription: undefined
572
735
  };
573
736
  case completeEventType:
@@ -642,255 +805,91 @@ function fromEventObservable(lazyObservable) {
642
805
  subscription: undefined
643
806
  })
644
807
  };
645
- return logic;
646
- }
647
-
648
- function matchesState(parentStateId, childStateId) {
649
- const parentStateValue = toStateValue(parentStateId);
650
- const childStateValue = toStateValue(childStateId);
651
- if (typeof childStateValue === 'string') {
652
- if (typeof parentStateValue === 'string') {
653
- return childStateValue === parentStateValue;
654
- }
655
-
656
- // Parent more specific than child
657
- return false;
658
- }
659
- if (typeof parentStateValue === 'string') {
660
- return parentStateValue in childStateValue;
661
- }
662
- return Object.keys(parentStateValue).every(key => {
663
- if (!(key in childStateValue)) {
664
- return false;
665
- }
666
- return matchesState(parentStateValue[key], childStateValue[key]);
667
- });
668
- }
669
- function toStatePath(stateId) {
670
- try {
671
- if (isArray(stateId)) {
672
- return stateId;
673
- }
674
- return stateId.toString().split(STATE_DELIMITER);
675
- } catch (e) {
676
- throw new Error(`'${stateId}' is not a valid state path.`);
677
- }
678
- }
679
- function isStateLike(state) {
680
- return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
681
- }
682
- function toStateValue(stateValue) {
683
- if (isStateLike(stateValue)) {
684
- return stateValue.value;
685
- }
686
- if (isArray(stateValue)) {
687
- return pathToStateValue(stateValue);
688
- }
689
- if (typeof stateValue !== 'string') {
690
- return stateValue;
691
- }
692
- const statePath = toStatePath(stateValue);
693
- return pathToStateValue(statePath);
694
- }
695
- function pathToStateValue(statePath) {
696
- if (statePath.length === 1) {
697
- return statePath[0];
698
- }
699
- const value = {};
700
- let marker = value;
701
- for (let i = 0; i < statePath.length - 1; i++) {
702
- if (i === statePath.length - 2) {
703
- marker[statePath[i]] = statePath[i + 1];
704
- } else {
705
- const previous = marker;
706
- marker = {};
707
- previous[statePath[i]] = marker;
708
- }
709
- }
710
- return value;
711
- }
712
- function mapValues(collection, iteratee) {
713
- const result = {};
714
- const collectionKeys = Object.keys(collection);
715
- for (let i = 0; i < collectionKeys.length; i++) {
716
- const key = collectionKeys[i];
717
- result[key] = iteratee(collection[key], key, collection, i);
718
- }
719
- return result;
720
- }
721
- function flatten(array) {
722
- return [].concat(...array);
723
- }
724
- function toArrayStrict(value) {
725
- if (isArray(value)) {
726
- return value;
727
- }
728
- return [value];
729
- }
730
- function toArray(value) {
731
- if (value === undefined) {
732
- return [];
733
- }
734
- return toArrayStrict(value);
735
- }
736
- function mapContext(mapper, context, event) {
737
- if (typeof mapper === 'function') {
738
- return mapper({
739
- context,
740
- event
741
- });
742
- }
743
- const result = {};
744
- const args = {
745
- context,
746
- event
747
- };
748
- for (const key of Object.keys(mapper)) {
749
- const subMapper = mapper[key];
750
- if (typeof subMapper === 'function') {
751
- result[key] = subMapper(args);
752
- } else {
753
- result[key] = subMapper;
754
- }
755
- }
756
- return result;
757
- }
758
- function isPromiseLike(value) {
759
- if (value instanceof Promise) {
760
- return true;
761
- }
762
- // Check if shape matches the Promise/A+ specification for a "thenable".
763
- if (value !== null && (typeof value === 'function' || typeof value === 'object') && typeof value.then === 'function') {
764
- return true;
765
- }
766
- return false;
767
- }
768
- function isArray(value) {
769
- return Array.isArray(value);
770
- }
771
- function isErrorEvent(event) {
772
- return typeof event.type === 'string' && (event.type === errorExecution || event.type.startsWith(errorPlatform));
773
- }
774
- function toTransitionConfigArray(configLike) {
775
- return toArrayStrict(configLike).map(transitionLike => {
776
- if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
777
- return {
778
- target: transitionLike
779
- };
780
- }
781
- return transitionLike;
782
- });
783
- }
784
- function normalizeTarget(target) {
785
- if (target === undefined || target === TARGETLESS_KEY) {
786
- return undefined;
787
- }
788
- return toArray(target);
789
- }
790
- function toInvokeConfig(invocable, id) {
791
- if (typeof invocable === 'object') {
792
- if ('src' in invocable) {
793
- return invocable;
794
- }
795
- if ('transition' in invocable) {
796
- return {
797
- id,
798
- src: invocable
799
- };
800
- }
801
- }
802
- return {
803
- id,
804
- src: invocable
805
- };
806
- }
807
- function toObserver(nextHandler, errorHandler, completionHandler) {
808
- const noop = () => {};
809
- const isObserver = typeof nextHandler === 'object';
810
- const self = isObserver ? nextHandler : null;
811
- return {
812
- next: ((isObserver ? nextHandler.next : nextHandler) || noop).bind(self),
813
- error: ((isObserver ? nextHandler.error : errorHandler) || noop).bind(self),
814
- complete: ((isObserver ? nextHandler.complete : completionHandler) || noop).bind(self)
815
- };
816
- }
817
- function createInvokeId(stateNodeId, index) {
818
- return `${stateNodeId}:invocation[${index}]`;
819
- }
820
- function resolveReferencedActor(referenced) {
821
- return referenced ? 'transition' in referenced ? {
822
- src: referenced,
823
- input: undefined
824
- } : referenced : undefined;
825
808
  }
826
809
 
827
- function fromCallback(invokeCallback) {
810
+ const resolveEventType = '$$xstate.resolve';
811
+ const rejectEventType = '$$xstate.reject';
812
+ function fromPromise(
813
+ // TODO: add types
814
+ promiseCreator) {
815
+ // TODO: add event types
828
816
  const logic = {
829
- config: invokeCallback,
830
- start: (_state, {
831
- self
832
- }) => {
833
- self.send({
834
- type: startSignalType
835
- });
817
+ config: promiseCreator,
818
+ transition: (state, event) => {
819
+ if (state.status !== 'active') {
820
+ return state;
821
+ }
822
+ switch (event.type) {
823
+ case resolveEventType:
824
+ return {
825
+ ...state,
826
+ status: 'done',
827
+ data: event.data,
828
+ input: undefined
829
+ };
830
+ case rejectEventType:
831
+ return {
832
+ ...state,
833
+ status: 'error',
834
+ data: event.data,
835
+ // TODO: if we keep this as `data` we should reflect this in the type
836
+ input: undefined
837
+ };
838
+ case stopSignalType:
839
+ return {
840
+ ...state,
841
+ status: 'canceled',
842
+ input: undefined
843
+ };
844
+ default:
845
+ return state;
846
+ }
836
847
  },
837
- transition: (state, event, {
848
+ start: (state, {
838
849
  self,
839
- id,
840
850
  system
841
851
  }) => {
842
- if (event.type === startSignalType) {
843
- const sender = eventForParent => {
844
- if (state.canceled) {
845
- return;
846
- }
847
- self._parent?.send(eventForParent);
848
- };
849
- const receiver = newListener => {
850
- state.receivers.add(newListener);
851
- };
852
- state.dispose = invokeCallback(sender, receiver, {
853
- input: state.input,
854
- system,
855
- self: self
856
- });
857
- if (isPromiseLike(state.dispose)) {
858
- state.dispose.then(resolved => {
859
- self._parent?.send(doneInvoke(id, resolved));
860
- state.canceled = true;
861
- }, errorData => {
862
- state.canceled = true;
863
- self._parent?.send(error(id, errorData));
864
- });
865
- }
866
- return state;
852
+ // TODO: determine how to allow customizing this so that promises
853
+ // can be restarted if necessary
854
+ if (state.status !== 'active') {
855
+ return;
867
856
  }
868
- if (event.type === stopSignalType) {
869
- state.canceled = true;
870
- if (typeof state.dispose === 'function') {
871
- state.dispose();
857
+ const resolvedPromise = Promise.resolve(promiseCreator({
858
+ input: state.input,
859
+ system,
860
+ self
861
+ }));
862
+ resolvedPromise.then(response => {
863
+ // TODO: remove this condition once dead letter queue lands
864
+ if (self._state.status !== 'active') {
865
+ return;
872
866
  }
873
- return state;
874
- }
875
- if (isSignal(event)) {
876
- // TODO: unrecognized signal
877
- return state;
878
- }
879
- state.receivers.forEach(receiver => receiver(event));
880
- return state;
867
+ self.send({
868
+ type: resolveEventType,
869
+ data: response
870
+ });
871
+ }, errorData => {
872
+ // TODO: remove this condition once dead letter queue lands
873
+ if (self._state.status !== 'active') {
874
+ return;
875
+ }
876
+ self.send({
877
+ type: rejectEventType,
878
+ data: errorData
879
+ });
880
+ });
881
881
  },
882
882
  getInitialState: (_, input) => {
883
883
  return {
884
- canceled: false,
885
- receivers: new Set(),
886
- dispose: undefined,
884
+ status: 'active',
885
+ data: undefined,
887
886
  input
888
887
  };
889
888
  },
890
- getSnapshot: () => undefined,
891
- getPersistedState: ({
892
- input
893
- }) => input
889
+ getSnapshot: state => state.data,
890
+ getStatus: state => state,
891
+ getPersistedState: state => state,
892
+ restoreState: state => state
894
893
  };
895
894
  return logic;
896
895
  }
@@ -929,6 +928,7 @@ function toActorRef(actorRefLike) {
929
928
  id: 'anonymous',
930
929
  sessionId: '',
931
930
  getSnapshot: () => undefined,
931
+ // TODO: this isn't safe
932
932
  [symbolObservable]: function () {
933
933
  return this;
934
934
  },
@@ -942,6 +942,19 @@ function createEmptyActor() {
942
942
  return interpret(emptyLogic);
943
943
  }
944
944
 
945
+ /**
946
+ * This function makes sure that unhandled errors are thrown in a separate macrotask.
947
+ * It allows those errors to be detected by global error handlers and reported to bug tracking services
948
+ * without interrupting our own stack of execution.
949
+ *
950
+ * @param err error to be thrown
951
+ */
952
+ function reportUnhandledError(err) {
953
+ setTimeout(() => {
954
+ throw err;
955
+ });
956
+ }
957
+
945
958
  function createSystem() {
946
959
  let sessionIdCounter = 0;
947
960
  const children = new Map();
@@ -1110,29 +1123,38 @@ class Interpreter {
1110
1123
  deferredFn();
1111
1124
  }
1112
1125
  for (const observer of this.observers) {
1113
- observer.next?.(snapshot);
1126
+ // TODO: should observers be notified in case of the error?
1127
+ try {
1128
+ observer.next?.(snapshot);
1129
+ } catch (err) {
1130
+ reportUnhandledError(err);
1131
+ }
1114
1132
  }
1115
1133
  const status = this.logic.getStatus?.(state);
1116
1134
  switch (status?.status) {
1117
1135
  case 'done':
1118
1136
  this._stopProcedure();
1137
+ this._complete();
1119
1138
  this._doneEvent = doneInvoke(this.id, status.data);
1120
1139
  this._parent?.send(this._doneEvent);
1121
- this._complete();
1122
1140
  break;
1123
1141
  case 'error':
1124
1142
  this._stopProcedure();
1125
- this._parent?.send(error(this.id, status.data));
1126
1143
  this._error(status.data);
1144
+ this._parent?.send(error(this.id, status.data));
1127
1145
  break;
1128
1146
  }
1129
1147
  }
1130
1148
  subscribe(nextListenerOrObserver, errorListener, completeListener) {
1131
1149
  const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
1132
- this.observers.add(observer);
1133
- if (this.status === ActorStatus.Stopped) {
1134
- observer.complete?.();
1135
- this.observers.delete(observer);
1150
+ if (this.status !== ActorStatus.Stopped) {
1151
+ this.observers.add(observer);
1152
+ } else {
1153
+ try {
1154
+ observer.complete?.();
1155
+ } catch (err) {
1156
+ reportUnhandledError(err);
1157
+ }
1136
1158
  }
1137
1159
  return {
1138
1160
  unsubscribe: () => {
@@ -1154,8 +1176,26 @@ class Interpreter {
1154
1176
  this.system._set(this._systemId, this);
1155
1177
  }
1156
1178
  this.status = ActorStatus.Running;
1179
+ const status = this.logic.getStatus?.(this._state);
1180
+ switch (status?.status) {
1181
+ case 'done':
1182
+ // a state machine can be "done" upon intialization (it could reach a final state using initial microsteps)
1183
+ // we still need to complete observers, flush deferreds etc
1184
+ this.update(this._state);
1185
+ // fallthrough
1186
+ case 'error':
1187
+ // TODO: rethink cleanup of observers, mailbox, etc
1188
+ return this;
1189
+ }
1157
1190
  if (this.logic.start) {
1158
- this.logic.start(this._state, this._actorContext);
1191
+ try {
1192
+ this.logic.start(this._state, this._actorContext);
1193
+ } catch (err) {
1194
+ this._stopProcedure();
1195
+ this._error(err);
1196
+ this._parent?.send(error(this.id, err));
1197
+ return this;
1198
+ }
1159
1199
  }
1160
1200
 
1161
1201
  // TODO: this notifies all subscribers but usually this is redundant
@@ -1169,23 +1209,30 @@ class Interpreter {
1169
1209
  return this;
1170
1210
  }
1171
1211
  _process(event) {
1212
+ // TODO: reexamine what happens when an action (or a guard or smth) throws
1213
+ let nextState;
1214
+ let caughtError;
1172
1215
  try {
1173
- const nextState = this.logic.transition(this._state, event, this._actorContext);
1174
- this.update(nextState);
1175
- if (event.type === stopSignalType) {
1176
- this._stopProcedure();
1177
- this._complete();
1178
- }
1216
+ nextState = this.logic.transition(this._state, event, this._actorContext);
1179
1217
  } catch (err) {
1180
- // TODO: properly handle errors
1181
- if (this.observers.size > 0) {
1182
- this.observers.forEach(observer => {
1183
- observer.error?.(err);
1184
- });
1185
- this.stop();
1186
- } else {
1187
- throw err;
1188
- }
1218
+ // we wrap it in a box so we can rethrow it later even if falsy value gets caught here
1219
+ caughtError = {
1220
+ err
1221
+ };
1222
+ }
1223
+ if (caughtError) {
1224
+ const {
1225
+ err
1226
+ } = caughtError;
1227
+ this._stopProcedure();
1228
+ this._error(err);
1229
+ this._parent?.send(error(this.id, err));
1230
+ return;
1231
+ }
1232
+ this.update(nextState);
1233
+ if (event.type === stopSignalType) {
1234
+ this._stopProcedure();
1235
+ this._complete();
1189
1236
  }
1190
1237
  }
1191
1238
  _stop() {
@@ -1214,15 +1261,35 @@ class Interpreter {
1214
1261
  }
1215
1262
  _complete() {
1216
1263
  for (const observer of this.observers) {
1217
- observer.complete?.();
1264
+ try {
1265
+ observer.complete?.();
1266
+ } catch (err) {
1267
+ reportUnhandledError(err);
1268
+ }
1218
1269
  }
1219
1270
  this.observers.clear();
1220
1271
  }
1221
- _error(data) {
1272
+ _error(err) {
1273
+ if (!this.observers.size) {
1274
+ if (!this._parent) {
1275
+ reportUnhandledError(err);
1276
+ }
1277
+ return;
1278
+ }
1279
+ let reportError = false;
1222
1280
  for (const observer of this.observers) {
1223
- observer.error?.(data);
1281
+ const errorListener = observer.error;
1282
+ reportError ||= !errorListener;
1283
+ try {
1284
+ errorListener?.(err);
1285
+ } catch (err2) {
1286
+ reportUnhandledError(err2);
1287
+ }
1224
1288
  }
1225
1289
  this.observers.clear();
1290
+ if (reportError) {
1291
+ reportUnhandledError(err);
1292
+ }
1226
1293
  }
1227
1294
  _stopProcedure() {
1228
1295
  if (this.status !== ActorStatus.Running) {
@@ -1555,10 +1622,10 @@ function toGuardDefinition(guardConfig, getPredicate) {
1555
1622
  }
1556
1623
  }
1557
1624
 
1558
- function getOutput(configuration, context, event) {
1625
+ function getOutput(configuration, context, event, self) {
1559
1626
  const machine = configuration[0].machine;
1560
1627
  const finalChildStateNode = configuration.find(stateNode => stateNode.type === 'final' && stateNode.parent === machine.root);
1561
- return finalChildStateNode && finalChildStateNode.output ? mapContext(finalChildStateNode.output, context, event) : undefined;
1628
+ return finalChildStateNode && finalChildStateNode.output ? mapContext(finalChildStateNode.output, context, event, self) : undefined;
1562
1629
  }
1563
1630
  const isAtomicStateNode = stateNode => stateNode.type === 'atomic' || stateNode.type === 'final';
1564
1631
  function getChildren(stateNode) {
@@ -2185,7 +2252,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2185
2252
  actions.push(...filteredTransitions.flatMap(t => t.actions));
2186
2253
 
2187
2254
  // Enter states
2188
- enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial);
2255
+ enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial, actorCtx);
2189
2256
  const nextConfiguration = [...mutConfiguration];
2190
2257
  const done = isInFinalState(nextConfiguration);
2191
2258
  if (done) {
@@ -2194,7 +2261,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2194
2261
  }
2195
2262
  try {
2196
2263
  const nextState = resolveActionsAndContext(actions, event, currentState, actorCtx);
2197
- const output = done ? getOutput(nextConfiguration, nextState.context, event) : undefined;
2264
+ const output = done ? getOutput(nextConfiguration, nextState.context, event, actorCtx.self) : undefined;
2198
2265
  internalQueue.push(...nextState._internalQueue);
2199
2266
  return cloneState(currentState, {
2200
2267
  configuration: nextConfiguration,
@@ -2211,7 +2278,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2211
2278
  throw e;
2212
2279
  }
2213
2280
  }
2214
- function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial) {
2281
+ function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial, actorContext) {
2215
2282
  const statesToEnter = new Set();
2216
2283
  const statesForDefaultEntry = new Set();
2217
2284
  computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
@@ -2239,7 +2306,7 @@ function enterStates(event, filteredTransitions, mutConfiguration, actions, inte
2239
2306
  if (!parent.parent) {
2240
2307
  continue;
2241
2308
  }
2242
- internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, event) : undefined));
2309
+ internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, event, actorContext.self) : undefined));
2243
2310
  if (parent.parent) {
2244
2311
  const grandparent = parent.parent;
2245
2312
  if (grandparent.type === 'parallel') {
@@ -2562,6 +2629,7 @@ class State {
2562
2629
  this.value = void 0;
2563
2630
  this.done = void 0;
2564
2631
  this.output = void 0;
2632
+ this.error = void 0;
2565
2633
  this.context = void 0;
2566
2634
  this.historyValue = {};
2567
2635
  this._internalQueue = void 0;
@@ -2578,6 +2646,7 @@ class State {
2578
2646
  this.tags = new Set(flatten(this.configuration.map(sn => sn.tags)));
2579
2647
  this.done = config.done ?? false;
2580
2648
  this.output = config.output;
2649
+ this.error = config.error;
2581
2650
  }
2582
2651
 
2583
2652
  /**
@@ -2822,7 +2891,7 @@ function createSpawner(actorContext, {
2822
2891
  }
2823
2892
  };
2824
2893
  return (src, options) => {
2825
- const actorRef = spawn(src, options);
2894
+ const actorRef = spawn(src, options); // TODO: fix types
2826
2895
  spawnedChildren[actorRef.id] = actorRef;
2827
2896
  actorContext.defer(() => {
2828
2897
  if (actorRef.status === ActorStatus.Stopped) {
@@ -3048,4 +3117,4 @@ function createInitEvent(input) {
3048
3117
  };
3049
3118
  }
3050
3119
 
3051
- export { fromEventObservable as $, isAtomicStateNode as A, error as B, isStateId as C, getStateNodeByPath as D, getPersistedState as E, resolveReferencedActor as F, interpret as G, matchesState as H, sendTo as I, sendParent as J, forwardTo as K, Interpreter as L, ActorStatus as M, NULL_EVENT as N, doneInvoke as O, cancel as P, choose as Q, log as R, STATE_DELIMITER as S, pure as T, raise as U, stop as V, pathToStateValue as W, toObserver as X, fromPromise as Y, fromObservable as Z, fromCallback as _, toTransitionConfigArray as a, fromTransition as a0, stateIn as a1, not as a2, and as a3, or as a4, ConstantPrefix as a5, SpecialTargets as a6, startSignalType as a7, stopSignalType as a8, startSignal as a9, stopSignal as aa, isSignal as ab, isActorRef as ac, toActorRef as ad, createEmptyActor as ae, toGuardDefinition as af, constantPrefixes as ag, after as ah, done as ai, escalate as aj, formatTransition as b, memo as c, flatten as d, evaluateGuard as e, formatTransitions as f, createInvokeId as g, getDelayedTransitions as h, formatInitialTransition as i, getCandidates as j, toInvokeConfig as k, getConfiguration as l, mapValues as m, getStateNodes as n, isInFinalState as o, State as p, isErrorEvent as q, resolveStateValue as r, macrostep as s, toArray as t, transitionNode as u, getInitialConfiguration as v, resolveActionsAndContext as w, assign as x, createInitEvent as y, microstep as z };
3120
+ export { fromEventObservable as $, microstep as A, isAtomicStateNode as B, isStateId as C, getStateNodeByPath as D, getPersistedState as E, resolveReferencedActor as F, interpret as G, matchesState as H, sendTo as I, sendParent as J, forwardTo as K, Interpreter as L, ActorStatus as M, NULL_EVENT as N, doneInvoke as O, cancel as P, choose as Q, log as R, STATE_DELIMITER as S, pure as T, raise as U, stop as V, pathToStateValue as W, toObserver as X, fromPromise as Y, fromObservable as Z, fromCallback as _, toTransitionConfigArray as a, fromTransition as a0, stateIn as a1, not as a2, and as a3, or as a4, ConstantPrefix as a5, SpecialTargets as a6, startSignalType as a7, stopSignalType as a8, startSignal as a9, stopSignal as aa, isSignal as ab, isActorRef as ac, toActorRef as ad, createEmptyActor as ae, toGuardDefinition as af, constantPrefixes as ag, after as ah, done as ai, error as aj, escalate as ak, formatTransition as b, memo as c, flatten as d, evaluateGuard as e, formatTransitions as f, createInvokeId as g, getDelayedTransitions as h, formatInitialTransition as i, getCandidates as j, toInvokeConfig as k, getConfiguration as l, mapValues as m, getStateNodes as n, isInFinalState as o, State as p, isErrorEvent as q, resolveStateValue as r, cloneState as s, toArray as t, macrostep as u, transitionNode as v, getInitialConfiguration as w, resolveActionsAndContext as x, assign as y, createInitEvent as z };