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