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.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.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
  *
@@ -307,7 +314,7 @@ const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observabl
307
314
  * @returns Actor logic
308
315
  */
309
316
  function fromTransition(transition, initialState) {
310
- const logic = {
317
+ return {
311
318
  config: transition,
312
319
  transition: (state, event, actorContext) => {
313
320
  return transition(state, event, actorContext);
@@ -321,103 +328,255 @@ function fromTransition(transition, initialState) {
321
328
  getPersistedState: state => state,
322
329
  restoreState: state => state
323
330
  };
324
- return logic;
325
331
  }
326
332
 
327
- const resolveEventType = '$$xstate.resolve';
328
- const rejectEventType = '$$xstate.reject';
329
- function fromPromise(
330
- // TODO: add types
331
- promiseCreator) {
332
- // TODO: add event types, consider making the `PromiseEvent` a private type or smth alike
333
- const logic = {
334
- config: promiseCreator,
335
- transition: (state, event) => {
336
- if (state.status !== 'active') {
337
- return state;
338
- }
339
- switch (event.type) {
340
- case resolveEventType:
341
- return {
342
- ...state,
343
- status: 'done',
344
- data: event.data,
345
- input: undefined
346
- };
347
- case rejectEventType:
348
- return {
349
- ...state,
350
- status: 'error',
351
- data: event.data,
352
- input: undefined
353
- };
354
- case stopSignalType:
355
- return {
356
- ...state,
357
- status: 'canceled',
358
- input: undefined
359
- };
360
- default:
361
- return state;
362
- }
333
+ function matchesState(parentStateId, childStateId) {
334
+ const parentStateValue = toStateValue(parentStateId);
335
+ const childStateValue = toStateValue(childStateId);
336
+ if (typeof childStateValue === 'string') {
337
+ if (typeof parentStateValue === 'string') {
338
+ return childStateValue === parentStateValue;
339
+ }
340
+
341
+ // Parent more specific than child
342
+ return false;
343
+ }
344
+ if (typeof parentStateValue === 'string') {
345
+ return parentStateValue in childStateValue;
346
+ }
347
+ return Object.keys(parentStateValue).every(key => {
348
+ if (!(key in childStateValue)) {
349
+ return false;
350
+ }
351
+ return matchesState(parentStateValue[key], childStateValue[key]);
352
+ });
353
+ }
354
+ function toStatePath(stateId) {
355
+ try {
356
+ if (isArray(stateId)) {
357
+ return stateId;
358
+ }
359
+ return stateId.toString().split(STATE_DELIMITER);
360
+ } catch (e) {
361
+ throw new Error(`'${stateId}' is not a valid state path.`);
362
+ }
363
+ }
364
+ function isStateLike(state) {
365
+ return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
366
+ }
367
+ function toStateValue(stateValue) {
368
+ if (isStateLike(stateValue)) {
369
+ return stateValue.value;
370
+ }
371
+ if (isArray(stateValue)) {
372
+ return pathToStateValue(stateValue);
373
+ }
374
+ if (typeof stateValue !== 'string') {
375
+ return stateValue;
376
+ }
377
+ const statePath = toStatePath(stateValue);
378
+ return pathToStateValue(statePath);
379
+ }
380
+ function pathToStateValue(statePath) {
381
+ if (statePath.length === 1) {
382
+ return statePath[0];
383
+ }
384
+ const value = {};
385
+ let marker = value;
386
+ for (let i = 0; i < statePath.length - 1; i++) {
387
+ if (i === statePath.length - 2) {
388
+ marker[statePath[i]] = statePath[i + 1];
389
+ } else {
390
+ const previous = marker;
391
+ marker = {};
392
+ previous[statePath[i]] = marker;
393
+ }
394
+ }
395
+ return value;
396
+ }
397
+ function mapValues(collection, iteratee) {
398
+ const result = {};
399
+ const collectionKeys = Object.keys(collection);
400
+ for (let i = 0; i < collectionKeys.length; i++) {
401
+ const key = collectionKeys[i];
402
+ result[key] = iteratee(collection[key], key, collection, i);
403
+ }
404
+ return result;
405
+ }
406
+ function flatten(array) {
407
+ return [].concat(...array);
408
+ }
409
+ function toArrayStrict(value) {
410
+ if (isArray(value)) {
411
+ return value;
412
+ }
413
+ return [value];
414
+ }
415
+ function toArray(value) {
416
+ if (value === undefined) {
417
+ return [];
418
+ }
419
+ return toArrayStrict(value);
420
+ }
421
+ function mapContext(mapper, context, event, self) {
422
+ if (typeof mapper === 'function') {
423
+ return mapper({
424
+ context,
425
+ event,
426
+ self
427
+ });
428
+ }
429
+ return mapper;
430
+ }
431
+ function isPromiseLike(value) {
432
+ if (value instanceof Promise) {
433
+ return true;
434
+ }
435
+ // Check if shape matches the Promise/A+ specification for a "thenable".
436
+ if (value !== null && (typeof value === 'function' || typeof value === 'object') && typeof value.then === 'function') {
437
+ return true;
438
+ }
439
+ return false;
440
+ }
441
+ function isArray(value) {
442
+ return Array.isArray(value);
443
+ }
444
+ function isErrorEvent(event) {
445
+ return typeof event.type === 'string' && (event.type === errorExecution || event.type.startsWith(errorPlatform));
446
+ }
447
+ function toTransitionConfigArray(configLike) {
448
+ return toArrayStrict(configLike).map(transitionLike => {
449
+ if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
450
+ return {
451
+ target: transitionLike
452
+ };
453
+ }
454
+ return transitionLike;
455
+ });
456
+ }
457
+ function normalizeTarget(target) {
458
+ if (target === undefined || target === TARGETLESS_KEY) {
459
+ return undefined;
460
+ }
461
+ return toArray(target);
462
+ }
463
+ function toInvokeConfig(invocable, id) {
464
+ if (typeof invocable === 'object') {
465
+ if ('src' in invocable) {
466
+ return invocable;
467
+ }
468
+ if ('transition' in invocable) {
469
+ return {
470
+ id,
471
+ src: invocable
472
+ };
473
+ }
474
+ }
475
+ return {
476
+ id,
477
+ src: invocable
478
+ };
479
+ }
480
+ function toObserver(nextHandler, errorHandler, completionHandler) {
481
+ const isObserver = typeof nextHandler === 'object';
482
+ const self = isObserver ? nextHandler : undefined;
483
+ return {
484
+ next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
485
+ error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
486
+ complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
487
+ };
488
+ }
489
+ function createInvokeId(stateNodeId, index) {
490
+ return `${stateNodeId}:invocation[${index}]`;
491
+ }
492
+ function resolveReferencedActor(referenced) {
493
+ return referenced ? 'transition' in referenced ? {
494
+ src: referenced,
495
+ input: undefined
496
+ } : referenced : undefined;
497
+ }
498
+
499
+ function fromCallback(invokeCallback) {
500
+ return {
501
+ config: invokeCallback,
502
+ start: (_state, {
503
+ self
504
+ }) => {
505
+ self.send({
506
+ type: startSignalType
507
+ });
363
508
  },
364
- start: (state, {
509
+ transition: (state, event, {
365
510
  self,
511
+ id,
366
512
  system
367
513
  }) => {
368
- // TODO: determine how to allow customizing this so that promises
369
- // can be restarted if necessary
370
- if (state.status !== 'active') {
371
- return;
514
+ if (event.type === startSignalType) {
515
+ const sendBack = eventForParent => {
516
+ if (state.canceled) {
517
+ return;
518
+ }
519
+ self._parent?.send(eventForParent);
520
+ };
521
+ const receive = newListener => {
522
+ state.receivers.add(newListener);
523
+ };
524
+ state.dispose = invokeCallback({
525
+ input: state.input,
526
+ system,
527
+ self: self,
528
+ sendBack,
529
+ receive
530
+ });
531
+ if (isPromiseLike(state.dispose)) {
532
+ state.dispose.then(resolved => {
533
+ self._parent?.send(doneInvoke(id, resolved));
534
+ state.canceled = true;
535
+ }, errorData => {
536
+ state.canceled = true;
537
+ self._parent?.send(error(id, errorData));
538
+ });
539
+ }
540
+ return state;
372
541
  }
373
- const resolvedPromise = Promise.resolve(promiseCreator({
374
- input: state.input,
375
- system,
376
- self
377
- }));
378
- resolvedPromise.then(response => {
379
- // TODO: remove this condition once dead letter queue lands
380
- if (self._state.status !== 'active') {
381
- return;
542
+ if (event.type === stopSignalType) {
543
+ state.canceled = true;
544
+ if (typeof state.dispose === 'function') {
545
+ state.dispose();
382
546
  }
383
- self.send({
384
- type: resolveEventType,
385
- data: response
386
- });
387
- }, errorData => {
388
- // TODO: remove this condition once dead letter queue lands
389
- if (self._state.status !== 'active') {
390
- return;
391
- }
392
- self.send({
393
- type: rejectEventType,
394
- data: errorData
395
- });
396
- });
547
+ return state;
548
+ }
549
+ if (isSignal(event)) {
550
+ // TODO: unrecognized signal
551
+ return state;
552
+ }
553
+ state.receivers.forEach(receiver => receiver(event));
554
+ return state;
397
555
  },
398
556
  getInitialState: (_, input) => {
399
557
  return {
400
- status: 'active',
401
- data: undefined,
558
+ canceled: false,
559
+ receivers: new Set(),
560
+ dispose: undefined,
402
561
  input
403
562
  };
404
563
  },
405
- getSnapshot: state => state.data,
406
- getStatus: state => state,
407
- getPersistedState: state => state,
408
- restoreState: state => state
564
+ getSnapshot: () => undefined,
565
+ getPersistedState: ({
566
+ input,
567
+ canceled
568
+ }) => ({
569
+ input,
570
+ canceled
571
+ })
409
572
  };
410
- return logic;
411
573
  }
412
574
 
413
- // TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
414
575
  function fromObservable(observableCreator) {
415
576
  const nextEventType = '$$xstate.next';
416
577
  const errorEventType = '$$xstate.error';
417
578
  const completeEventType = '$$xstate.complete';
418
-
419
- // TODO: add event types
420
- const logic = {
579
+ return {
421
580
  config: observableCreator,
422
581
  transition: (state, event, {
423
582
  self,
@@ -447,6 +606,7 @@ function fromObservable(observableCreator) {
447
606
  status: 'error',
448
607
  input: undefined,
449
608
  data: event.data,
609
+ // TODO: if we keep this as `data` we should reflect this in the type
450
610
  subscription: undefined
451
611
  };
452
612
  case completeEventType:
@@ -524,7 +684,6 @@ function fromObservable(observableCreator) {
524
684
  subscription: undefined
525
685
  })
526
686
  };
527
- return logic;
528
687
  }
529
688
 
530
689
  /**
@@ -541,7 +700,7 @@ function fromEventObservable(lazyObservable) {
541
700
  const completeEventType = '$$xstate.complete';
542
701
 
543
702
  // TODO: event types
544
- const logic = {
703
+ return {
545
704
  config: lazyObservable,
546
705
  transition: (state, event) => {
547
706
  if (state.status !== 'active') {
@@ -554,329 +713,166 @@ function fromEventObservable(lazyObservable) {
554
713
  status: 'error',
555
714
  input: undefined,
556
715
  data: event.data,
557
- subscription: undefined
558
- };
559
- case completeEventType:
560
- return {
561
- ...state,
562
- status: 'done',
563
- input: undefined,
564
- subscription: undefined
565
- };
566
- case stopSignalType:
567
- state.subscription.unsubscribe();
568
- return {
569
- ...state,
570
- status: 'canceled',
571
- input: undefined,
572
- subscription: undefined
573
- };
574
- default:
575
- return state;
576
- }
577
- },
578
- getInitialState: (_, input) => {
579
- return {
580
- subscription: undefined,
581
- status: 'active',
582
- data: undefined,
583
- input
584
- };
585
- },
586
- start: (state, {
587
- self,
588
- system
589
- }) => {
590
- if (state.status === 'done') {
591
- // Do not restart a completed observable
592
- return;
593
- }
594
- state.subscription = lazyObservable({
595
- input: state.input,
596
- system,
597
- self
598
- }).subscribe({
599
- next: value => {
600
- self._parent?.send(value);
601
- },
602
- error: err => {
603
- self.send({
604
- type: errorEventType,
605
- data: err
606
- });
607
- },
608
- complete: () => {
609
- self.send({
610
- type: completeEventType
611
- });
612
- }
613
- });
614
- },
615
- getSnapshot: _ => undefined,
616
- getPersistedState: ({
617
- status,
618
- data,
619
- input
620
- }) => ({
621
- status,
622
- data,
623
- input
624
- }),
625
- getStatus: state => state,
626
- restoreState: state => ({
627
- ...state,
628
- subscription: undefined
629
- })
630
- };
631
- return logic;
632
- }
633
-
634
- function matchesState(parentStateId, childStateId) {
635
- const parentStateValue = toStateValue(parentStateId);
636
- const childStateValue = toStateValue(childStateId);
637
- if (typeof childStateValue === 'string') {
638
- if (typeof parentStateValue === 'string') {
639
- return childStateValue === parentStateValue;
640
- }
641
-
642
- // Parent more specific than child
643
- return false;
644
- }
645
- if (typeof parentStateValue === 'string') {
646
- return parentStateValue in childStateValue;
647
- }
648
- return Object.keys(parentStateValue).every(key => {
649
- if (!(key in childStateValue)) {
650
- return false;
651
- }
652
- return matchesState(parentStateValue[key], childStateValue[key]);
653
- });
654
- }
655
- function toStatePath(stateId) {
656
- try {
657
- if (isArray(stateId)) {
658
- return stateId;
659
- }
660
- return stateId.toString().split(STATE_DELIMITER);
661
- } catch (e) {
662
- throw new Error(`'${stateId}' is not a valid state path.`);
663
- }
664
- }
665
- function isStateLike(state) {
666
- return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
667
- }
668
- function toStateValue(stateValue) {
669
- if (isStateLike(stateValue)) {
670
- return stateValue.value;
671
- }
672
- if (isArray(stateValue)) {
673
- return pathToStateValue(stateValue);
674
- }
675
- if (typeof stateValue !== 'string') {
676
- return stateValue;
677
- }
678
- const statePath = toStatePath(stateValue);
679
- return pathToStateValue(statePath);
680
- }
681
- function pathToStateValue(statePath) {
682
- if (statePath.length === 1) {
683
- return statePath[0];
684
- }
685
- const value = {};
686
- let marker = value;
687
- for (let i = 0; i < statePath.length - 1; i++) {
688
- if (i === statePath.length - 2) {
689
- marker[statePath[i]] = statePath[i + 1];
690
- } else {
691
- const previous = marker;
692
- marker = {};
693
- previous[statePath[i]] = marker;
694
- }
695
- }
696
- return value;
697
- }
698
- function mapValues(collection, iteratee) {
699
- const result = {};
700
- const collectionKeys = Object.keys(collection);
701
- for (let i = 0; i < collectionKeys.length; i++) {
702
- const key = collectionKeys[i];
703
- result[key] = iteratee(collection[key], key, collection, i);
704
- }
705
- return result;
706
- }
707
- function flatten(array) {
708
- return [].concat(...array);
709
- }
710
- function toArrayStrict(value) {
711
- if (isArray(value)) {
712
- return value;
713
- }
714
- return [value];
715
- }
716
- function toArray(value) {
717
- if (value === undefined) {
718
- return [];
719
- }
720
- return toArrayStrict(value);
721
- }
722
- function mapContext(mapper, context, event) {
723
- if (typeof mapper === 'function') {
724
- return mapper({
725
- context,
726
- event
727
- });
728
- }
729
- const result = {};
730
- const args = {
731
- context,
732
- event
733
- };
734
- for (const key of Object.keys(mapper)) {
735
- const subMapper = mapper[key];
736
- if (typeof subMapper === 'function') {
737
- result[key] = subMapper(args);
738
- } else {
739
- result[key] = subMapper;
740
- }
741
- }
742
- return result;
743
- }
744
- function isPromiseLike(value) {
745
- if (value instanceof Promise) {
746
- return true;
747
- }
748
- // Check if shape matches the Promise/A+ specification for a "thenable".
749
- if (value !== null && (typeof value === 'function' || typeof value === 'object') && typeof value.then === 'function') {
750
- return true;
751
- }
752
- return false;
753
- }
754
- function isArray(value) {
755
- return Array.isArray(value);
756
- }
757
- function isErrorEvent(event) {
758
- return typeof event.type === 'string' && (event.type === errorExecution || event.type.startsWith(errorPlatform));
759
- }
760
- function toTransitionConfigArray(configLike) {
761
- return toArrayStrict(configLike).map(transitionLike => {
762
- if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
763
- return {
764
- target: transitionLike
765
- };
766
- }
767
- return transitionLike;
768
- });
769
- }
770
- function normalizeTarget(target) {
771
- if (target === undefined || target === TARGETLESS_KEY) {
772
- return undefined;
773
- }
774
- return toArray(target);
775
- }
776
- function toInvokeConfig(invocable, id) {
777
- if (typeof invocable === 'object') {
778
- if ('src' in invocable) {
779
- return invocable;
780
- }
781
- if ('transition' in invocable) {
716
+ // TODO: if we keep this as `data` we should reflect this in the type
717
+ subscription: undefined
718
+ };
719
+ case completeEventType:
720
+ return {
721
+ ...state,
722
+ status: 'done',
723
+ input: undefined,
724
+ subscription: undefined
725
+ };
726
+ case stopSignalType:
727
+ state.subscription.unsubscribe();
728
+ return {
729
+ ...state,
730
+ status: 'canceled',
731
+ input: undefined,
732
+ subscription: undefined
733
+ };
734
+ default:
735
+ return state;
736
+ }
737
+ },
738
+ getInitialState: (_, input) => {
782
739
  return {
783
- id,
784
- src: invocable
740
+ subscription: undefined,
741
+ status: 'active',
742
+ data: undefined,
743
+ input
785
744
  };
786
- }
787
- }
788
- return {
789
- id,
790
- src: invocable
791
- };
792
- }
793
- function toObserver(nextHandler, errorHandler, completionHandler) {
794
- const noop = () => {};
795
- const isObserver = typeof nextHandler === 'object';
796
- const self = isObserver ? nextHandler : null;
797
- return {
798
- next: ((isObserver ? nextHandler.next : nextHandler) || noop).bind(self),
799
- error: ((isObserver ? nextHandler.error : errorHandler) || noop).bind(self),
800
- complete: ((isObserver ? nextHandler.complete : completionHandler) || noop).bind(self)
801
- };
802
- }
803
- function createInvokeId(stateNodeId, index) {
804
- return `${stateNodeId}:invocation[${index}]`;
805
- }
806
- function resolveReferencedActor(referenced) {
807
- return referenced ? 'transition' in referenced ? {
808
- src: referenced,
809
- input: undefined
810
- } : referenced : undefined;
811
- }
812
-
813
- function fromCallback(invokeCallback) {
814
- const logic = {
815
- config: invokeCallback,
816
- start: (_state, {
817
- self
818
- }) => {
819
- self.send({
820
- type: startSignalType
821
- });
822
745
  },
823
- transition: (state, event, {
746
+ start: (state, {
824
747
  self,
825
- id,
826
748
  system
827
749
  }) => {
828
- if (event.type === startSignalType) {
829
- const sender = eventForParent => {
830
- if (state.canceled) {
831
- return;
832
- }
833
- self._parent?.send(eventForParent);
834
- };
835
- const receiver = newListener => {
836
- state.receivers.add(newListener);
837
- };
838
- state.dispose = invokeCallback(sender, receiver, {
839
- input: state.input,
840
- system,
841
- self: self
842
- });
843
- if (isPromiseLike(state.dispose)) {
844
- state.dispose.then(resolved => {
845
- self._parent?.send(doneInvoke(id, resolved));
846
- state.canceled = true;
847
- }, errorData => {
848
- state.canceled = true;
849
- self._parent?.send(error(id, errorData));
750
+ if (state.status === 'done') {
751
+ // Do not restart a completed observable
752
+ return;
753
+ }
754
+ state.subscription = lazyObservable({
755
+ input: state.input,
756
+ system,
757
+ self
758
+ }).subscribe({
759
+ next: value => {
760
+ self._parent?.send(value);
761
+ },
762
+ error: err => {
763
+ self.send({
764
+ type: errorEventType,
765
+ data: err
766
+ });
767
+ },
768
+ complete: () => {
769
+ self.send({
770
+ type: completeEventType
850
771
  });
851
772
  }
773
+ });
774
+ },
775
+ getSnapshot: _ => undefined,
776
+ getPersistedState: ({
777
+ status,
778
+ data,
779
+ input
780
+ }) => ({
781
+ status,
782
+ data,
783
+ input
784
+ }),
785
+ getStatus: state => state,
786
+ restoreState: state => ({
787
+ ...state,
788
+ subscription: undefined
789
+ })
790
+ };
791
+ }
792
+
793
+ const resolveEventType = '$$xstate.resolve';
794
+ const rejectEventType = '$$xstate.reject';
795
+ function fromPromise(
796
+ // TODO: add types
797
+ promiseCreator) {
798
+ // TODO: add event types
799
+ const logic = {
800
+ config: promiseCreator,
801
+ transition: (state, event) => {
802
+ if (state.status !== 'active') {
852
803
  return state;
853
804
  }
854
- if (event.type === stopSignalType) {
855
- state.canceled = true;
856
- if (typeof state.dispose === 'function') {
857
- state.dispose();
858
- }
859
- return state;
805
+ switch (event.type) {
806
+ case resolveEventType:
807
+ return {
808
+ ...state,
809
+ status: 'done',
810
+ data: event.data,
811
+ input: undefined
812
+ };
813
+ case rejectEventType:
814
+ return {
815
+ ...state,
816
+ status: 'error',
817
+ data: event.data,
818
+ // TODO: if we keep this as `data` we should reflect this in the type
819
+ input: undefined
820
+ };
821
+ case stopSignalType:
822
+ return {
823
+ ...state,
824
+ status: 'canceled',
825
+ input: undefined
826
+ };
827
+ default:
828
+ return state;
860
829
  }
861
- if (isSignal(event)) {
862
- // TODO: unrecognized signal
863
- return state;
830
+ },
831
+ start: (state, {
832
+ self,
833
+ system
834
+ }) => {
835
+ // TODO: determine how to allow customizing this so that promises
836
+ // can be restarted if necessary
837
+ if (state.status !== 'active') {
838
+ return;
864
839
  }
865
- state.receivers.forEach(receiver => receiver(event));
866
- return state;
840
+ const resolvedPromise = Promise.resolve(promiseCreator({
841
+ input: state.input,
842
+ system,
843
+ self
844
+ }));
845
+ resolvedPromise.then(response => {
846
+ // TODO: remove this condition once dead letter queue lands
847
+ if (self._state.status !== 'active') {
848
+ return;
849
+ }
850
+ self.send({
851
+ type: resolveEventType,
852
+ data: response
853
+ });
854
+ }, errorData => {
855
+ // TODO: remove this condition once dead letter queue lands
856
+ if (self._state.status !== 'active') {
857
+ return;
858
+ }
859
+ self.send({
860
+ type: rejectEventType,
861
+ data: errorData
862
+ });
863
+ });
867
864
  },
868
865
  getInitialState: (_, input) => {
869
866
  return {
870
- canceled: false,
871
- receivers: new Set(),
872
- dispose: undefined,
867
+ status: 'active',
868
+ data: undefined,
873
869
  input
874
870
  };
875
871
  },
876
- getSnapshot: () => undefined,
877
- getPersistedState: ({
878
- input
879
- }) => input
872
+ getSnapshot: state => state.data,
873
+ getStatus: state => state,
874
+ getPersistedState: state => state,
875
+ restoreState: state => state
880
876
  };
881
877
  return logic;
882
878
  }
@@ -915,6 +911,7 @@ function toActorRef(actorRefLike) {
915
911
  id: 'anonymous',
916
912
  sessionId: '',
917
913
  getSnapshot: () => undefined,
914
+ // TODO: this isn't safe
918
915
  [symbolObservable]: function () {
919
916
  return this;
920
917
  },
@@ -928,6 +925,19 @@ function createEmptyActor() {
928
925
  return interpret(emptyLogic);
929
926
  }
930
927
 
928
+ /**
929
+ * This function makes sure that unhandled errors are thrown in a separate macrotask.
930
+ * It allows those errors to be detected by global error handlers and reported to bug tracking services
931
+ * without interrupting our own stack of execution.
932
+ *
933
+ * @param err error to be thrown
934
+ */
935
+ function reportUnhandledError(err) {
936
+ setTimeout(() => {
937
+ throw err;
938
+ });
939
+ }
940
+
931
941
  function createSystem() {
932
942
  let sessionIdCounter = 0;
933
943
  const children = new Map();
@@ -1096,29 +1106,38 @@ class Interpreter {
1096
1106
  deferredFn();
1097
1107
  }
1098
1108
  for (const observer of this.observers) {
1099
- observer.next?.(snapshot);
1109
+ // TODO: should observers be notified in case of the error?
1110
+ try {
1111
+ observer.next?.(snapshot);
1112
+ } catch (err) {
1113
+ reportUnhandledError(err);
1114
+ }
1100
1115
  }
1101
1116
  const status = this.logic.getStatus?.(state);
1102
1117
  switch (status?.status) {
1103
1118
  case 'done':
1104
1119
  this._stopProcedure();
1120
+ this._complete();
1105
1121
  this._doneEvent = doneInvoke(this.id, status.data);
1106
1122
  this._parent?.send(this._doneEvent);
1107
- this._complete();
1108
1123
  break;
1109
1124
  case 'error':
1110
1125
  this._stopProcedure();
1111
- this._parent?.send(error(this.id, status.data));
1112
1126
  this._error(status.data);
1127
+ this._parent?.send(error(this.id, status.data));
1113
1128
  break;
1114
1129
  }
1115
1130
  }
1116
1131
  subscribe(nextListenerOrObserver, errorListener, completeListener) {
1117
1132
  const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
1118
- this.observers.add(observer);
1119
- if (this.status === ActorStatus.Stopped) {
1120
- observer.complete?.();
1121
- this.observers.delete(observer);
1133
+ if (this.status !== ActorStatus.Stopped) {
1134
+ this.observers.add(observer);
1135
+ } else {
1136
+ try {
1137
+ observer.complete?.();
1138
+ } catch (err) {
1139
+ reportUnhandledError(err);
1140
+ }
1122
1141
  }
1123
1142
  return {
1124
1143
  unsubscribe: () => {
@@ -1140,8 +1159,26 @@ class Interpreter {
1140
1159
  this.system._set(this._systemId, this);
1141
1160
  }
1142
1161
  this.status = ActorStatus.Running;
1162
+ const status = this.logic.getStatus?.(this._state);
1163
+ switch (status?.status) {
1164
+ case 'done':
1165
+ // a state machine can be "done" upon intialization (it could reach a final state using initial microsteps)
1166
+ // we still need to complete observers, flush deferreds etc
1167
+ this.update(this._state);
1168
+ // fallthrough
1169
+ case 'error':
1170
+ // TODO: rethink cleanup of observers, mailbox, etc
1171
+ return this;
1172
+ }
1143
1173
  if (this.logic.start) {
1144
- this.logic.start(this._state, this._actorContext);
1174
+ try {
1175
+ this.logic.start(this._state, this._actorContext);
1176
+ } catch (err) {
1177
+ this._stopProcedure();
1178
+ this._error(err);
1179
+ this._parent?.send(error(this.id, err));
1180
+ return this;
1181
+ }
1145
1182
  }
1146
1183
 
1147
1184
  // TODO: this notifies all subscribers but usually this is redundant
@@ -1155,23 +1192,30 @@ class Interpreter {
1155
1192
  return this;
1156
1193
  }
1157
1194
  _process(event) {
1195
+ // TODO: reexamine what happens when an action (or a guard or smth) throws
1196
+ let nextState;
1197
+ let caughtError;
1158
1198
  try {
1159
- const nextState = this.logic.transition(this._state, event, this._actorContext);
1160
- this.update(nextState);
1161
- if (event.type === stopSignalType) {
1162
- this._stopProcedure();
1163
- this._complete();
1164
- }
1199
+ nextState = this.logic.transition(this._state, event, this._actorContext);
1165
1200
  } catch (err) {
1166
- // TODO: properly handle errors
1167
- if (this.observers.size > 0) {
1168
- this.observers.forEach(observer => {
1169
- observer.error?.(err);
1170
- });
1171
- this.stop();
1172
- } else {
1173
- throw err;
1174
- }
1201
+ // we wrap it in a box so we can rethrow it later even if falsy value gets caught here
1202
+ caughtError = {
1203
+ err
1204
+ };
1205
+ }
1206
+ if (caughtError) {
1207
+ const {
1208
+ err
1209
+ } = caughtError;
1210
+ this._stopProcedure();
1211
+ this._error(err);
1212
+ this._parent?.send(error(this.id, err));
1213
+ return;
1214
+ }
1215
+ this.update(nextState);
1216
+ if (event.type === stopSignalType) {
1217
+ this._stopProcedure();
1218
+ this._complete();
1175
1219
  }
1176
1220
  }
1177
1221
  _stop() {
@@ -1200,15 +1244,35 @@ class Interpreter {
1200
1244
  }
1201
1245
  _complete() {
1202
1246
  for (const observer of this.observers) {
1203
- observer.complete?.();
1247
+ try {
1248
+ observer.complete?.();
1249
+ } catch (err) {
1250
+ reportUnhandledError(err);
1251
+ }
1204
1252
  }
1205
1253
  this.observers.clear();
1206
1254
  }
1207
- _error(data) {
1255
+ _error(err) {
1256
+ if (!this.observers.size) {
1257
+ if (!this._parent) {
1258
+ reportUnhandledError(err);
1259
+ }
1260
+ return;
1261
+ }
1262
+ let reportError = false;
1208
1263
  for (const observer of this.observers) {
1209
- observer.error?.(data);
1264
+ const errorListener = observer.error;
1265
+ reportError ||= !errorListener;
1266
+ try {
1267
+ errorListener?.(err);
1268
+ } catch (err2) {
1269
+ reportUnhandledError(err2);
1270
+ }
1210
1271
  }
1211
1272
  this.observers.clear();
1273
+ if (reportError) {
1274
+ reportUnhandledError(err);
1275
+ }
1212
1276
  }
1213
1277
  _stopProcedure() {
1214
1278
  if (this.status !== ActorStatus.Running) {
@@ -1530,10 +1594,10 @@ function toGuardDefinition(guardConfig, getPredicate) {
1530
1594
  }
1531
1595
  }
1532
1596
 
1533
- function getOutput(configuration, context, event) {
1597
+ function getOutput(configuration, context, event, self) {
1534
1598
  const machine = configuration[0].machine;
1535
1599
  const finalChildStateNode = configuration.find(stateNode => stateNode.type === 'final' && stateNode.parent === machine.root);
1536
- return finalChildStateNode && finalChildStateNode.output ? mapContext(finalChildStateNode.output, context, event) : undefined;
1600
+ return finalChildStateNode && finalChildStateNode.output ? mapContext(finalChildStateNode.output, context, event, self) : undefined;
1537
1601
  }
1538
1602
  const isAtomicStateNode = stateNode => stateNode.type === 'atomic' || stateNode.type === 'final';
1539
1603
  function getChildren(stateNode) {
@@ -2149,7 +2213,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2149
2213
  actions.push(...filteredTransitions.flatMap(t => t.actions));
2150
2214
 
2151
2215
  // Enter states
2152
- enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial);
2216
+ enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial, actorCtx);
2153
2217
  const nextConfiguration = [...mutConfiguration];
2154
2218
  const done = isInFinalState(nextConfiguration);
2155
2219
  if (done) {
@@ -2158,7 +2222,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2158
2222
  }
2159
2223
  try {
2160
2224
  const nextState = resolveActionsAndContext(actions, event, currentState, actorCtx);
2161
- const output = done ? getOutput(nextConfiguration, nextState.context, event) : undefined;
2225
+ const output = done ? getOutput(nextConfiguration, nextState.context, event, actorCtx.self) : undefined;
2162
2226
  internalQueue.push(...nextState._internalQueue);
2163
2227
  return cloneState(currentState, {
2164
2228
  configuration: nextConfiguration,
@@ -2175,7 +2239,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
2175
2239
  throw e;
2176
2240
  }
2177
2241
  }
2178
- function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial) {
2242
+ function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial, actorContext) {
2179
2243
  const statesToEnter = new Set();
2180
2244
  const statesForDefaultEntry = new Set();
2181
2245
  computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
@@ -2203,7 +2267,7 @@ function enterStates(event, filteredTransitions, mutConfiguration, actions, inte
2203
2267
  if (!parent.parent) {
2204
2268
  continue;
2205
2269
  }
2206
- internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, event) : undefined));
2270
+ internalQueue.push(done(parent.id, stateNodeToEnter.output ? mapContext(stateNodeToEnter.output, currentState.context, event, actorContext.self) : undefined));
2207
2271
  if (parent.parent) {
2208
2272
  const grandparent = parent.parent;
2209
2273
  if (grandparent.type === 'parallel') {
@@ -2523,6 +2587,7 @@ class State {
2523
2587
  this.value = void 0;
2524
2588
  this.done = void 0;
2525
2589
  this.output = void 0;
2590
+ this.error = void 0;
2526
2591
  this.context = void 0;
2527
2592
  this.historyValue = {};
2528
2593
  this._internalQueue = void 0;
@@ -2539,6 +2604,7 @@ class State {
2539
2604
  this.tags = new Set(flatten(this.configuration.map(sn => sn.tags)));
2540
2605
  this.done = config.done ?? false;
2541
2606
  this.output = config.output;
2607
+ this.error = config.error;
2542
2608
  }
2543
2609
 
2544
2610
  /**
@@ -2774,7 +2840,7 @@ function createSpawner(actorContext, {
2774
2840
  }
2775
2841
  };
2776
2842
  return (src, options) => {
2777
- const actorRef = spawn(src, options);
2843
+ const actorRef = spawn(src, options); // TODO: fix types
2778
2844
  spawnedChildren[actorRef.id] = actorRef;
2779
2845
  actorContext.defer(() => {
2780
2846
  if (actorRef.status === ActorStatus.Stopped) {
@@ -3000,6 +3066,7 @@ exports.and = and;
3000
3066
  exports.assign = assign;
3001
3067
  exports.cancel = cancel;
3002
3068
  exports.choose = choose;
3069
+ exports.cloneState = cloneState;
3003
3070
  exports.constantPrefixes = constantPrefixes;
3004
3071
  exports.createEmptyActor = createEmptyActor;
3005
3072
  exports.createInitEvent = createInitEvent;