@wordpress/data 6.3.0 → 6.4.0

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 (64) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/components/use-select/index.js +1 -1
  3. package/build/components/use-select/index.js.map +1 -1
  4. package/build/factory.js +1 -1
  5. package/build/factory.js.map +1 -1
  6. package/build/plugins/persistence/index.js +114 -12
  7. package/build/plugins/persistence/index.js.map +1 -1
  8. package/build/redux-store/index.js +24 -8
  9. package/build/redux-store/index.js.map +1 -1
  10. package/build/redux-store/metadata/actions.js +47 -4
  11. package/build/redux-store/metadata/actions.js.map +1 -1
  12. package/build/redux-store/metadata/reducer.js +57 -4
  13. package/build/redux-store/metadata/reducer.js.map +1 -1
  14. package/build/redux-store/metadata/selectors.js +71 -7
  15. package/build/redux-store/metadata/selectors.js.map +1 -1
  16. package/build/registry.js +0 -2
  17. package/build/registry.js.map +1 -1
  18. package/build/resolvers-cache-middleware.js +3 -3
  19. package/build/resolvers-cache-middleware.js.map +1 -1
  20. package/build-module/components/use-select/index.js +1 -1
  21. package/build-module/components/use-select/index.js.map +1 -1
  22. package/build-module/factory.js +1 -1
  23. package/build-module/factory.js.map +1 -1
  24. package/build-module/plugins/persistence/index.js +111 -12
  25. package/build-module/plugins/persistence/index.js.map +1 -1
  26. package/build-module/redux-store/index.js +24 -8
  27. package/build-module/redux-store/index.js.map +1 -1
  28. package/build-module/redux-store/metadata/actions.js +43 -4
  29. package/build-module/redux-store/metadata/actions.js.map +1 -1
  30. package/build-module/redux-store/metadata/reducer.js +57 -4
  31. package/build-module/redux-store/metadata/reducer.js.map +1 -1
  32. package/build-module/redux-store/metadata/selectors.js +65 -7
  33. package/build-module/redux-store/metadata/selectors.js.map +1 -1
  34. package/build-module/registry.js +0 -2
  35. package/build-module/registry.js.map +1 -1
  36. package/build-module/resolvers-cache-middleware.js +3 -3
  37. package/build-module/resolvers-cache-middleware.js.map +1 -1
  38. package/build-types/redux-store/metadata/actions.d.ts +37 -4
  39. package/build-types/redux-store/metadata/actions.d.ts.map +1 -1
  40. package/build-types/redux-store/metadata/reducer.d.ts +10 -2
  41. package/build-types/redux-store/metadata/reducer.d.ts.map +1 -1
  42. package/build-types/redux-store/metadata/selectors.d.ts +40 -0
  43. package/build-types/redux-store/metadata/selectors.d.ts.map +1 -1
  44. package/build-types/types.d.ts.map +1 -1
  45. package/package.json +8 -8
  46. package/src/components/use-select/index.js +1 -1
  47. package/src/components/use-select/test/index.js +7 -7
  48. package/src/components/with-select/test/index.js +3 -3
  49. package/src/factory.js +1 -1
  50. package/src/plugins/persistence/index.js +142 -8
  51. package/src/plugins/persistence/test/index.js +405 -0
  52. package/src/redux-store/index.js +43 -15
  53. package/src/redux-store/metadata/actions.js +43 -4
  54. package/src/redux-store/metadata/reducer.ts +60 -9
  55. package/src/redux-store/metadata/selectors.js +60 -7
  56. package/src/redux-store/metadata/test/reducer.js +64 -24
  57. package/src/redux-store/metadata/test/selectors.js +263 -53
  58. package/src/redux-store/test/index.js +42 -0
  59. package/src/registry.js +0 -2
  60. package/src/resolvers-cache-middleware.js +4 -3
  61. package/src/test/controls.js +7 -7
  62. package/src/test/registry.js +6 -6
  63. package/src/types.ts +1 -3
  64. package/tsconfig.tsbuildinfo +1 -1
@@ -10,6 +10,8 @@ import plugin, {
10
10
  createPersistenceInterface,
11
11
  withLazySameState,
12
12
  migrateFeaturePreferencesToInterfaceStore,
13
+ migrateFeaturePreferencesToPreferencesStore,
14
+ migrateIndividualPreferenceToPreferencesStore,
13
15
  } from '../';
14
16
  import objectStorage from '../storage/object';
15
17
  import { createRegistry } from '../../../';
@@ -492,4 +494,407 @@ describe( 'migrateFeaturePreferencesToInterfaceStore', () => {
492
494
  },
493
495
  } );
494
496
  } );
497
+
498
+ describe( 'migrateFeaturePreferencesToPreferencesStore', () => {
499
+ it( 'migrates multiple preferences from persisted source stores to preferences', () => {
500
+ const persistenceInterface = createPersistenceInterface( {
501
+ storageKey: 'test-username',
502
+ } );
503
+
504
+ const sourceStateA = {
505
+ preferences: {
506
+ features: {
507
+ featureA: true,
508
+ featureB: false,
509
+ featureC: true,
510
+ },
511
+ },
512
+ };
513
+
514
+ const sourceStateB = {
515
+ preferences: {
516
+ features: {
517
+ featureD: true,
518
+ featureE: false,
519
+ featureF: true,
520
+ },
521
+ },
522
+ };
523
+
524
+ persistenceInterface.set( 'core/test-a', sourceStateA );
525
+ persistenceInterface.set( 'core/test-b', sourceStateB );
526
+
527
+ migrateFeaturePreferencesToPreferencesStore(
528
+ persistenceInterface,
529
+ 'core/test-a'
530
+ );
531
+
532
+ migrateFeaturePreferencesToPreferencesStore(
533
+ persistenceInterface,
534
+ 'core/test-b'
535
+ );
536
+
537
+ expect( persistenceInterface.get() ).toEqual( {
538
+ 'core/preferences': {
539
+ preferences: {
540
+ 'core/test-a': {
541
+ featureA: true,
542
+ featureB: false,
543
+ featureC: true,
544
+ },
545
+ 'core/test-b': {
546
+ featureD: true,
547
+ featureE: false,
548
+ featureF: true,
549
+ },
550
+ },
551
+ },
552
+ 'core/test-a': {
553
+ preferences: {
554
+ features: undefined,
555
+ },
556
+ },
557
+ 'core/test-b': {
558
+ preferences: {
559
+ features: undefined,
560
+ },
561
+ },
562
+ } );
563
+ } );
564
+
565
+ it( 'migrates multiple preferences from the persisted interface store to preferences, with interface state taking precedence over source stores', () => {
566
+ const persistenceInterface = createPersistenceInterface( {
567
+ storageKey: 'test-username',
568
+ } );
569
+
570
+ const sourceStateA = {
571
+ preferences: {
572
+ features: {
573
+ featureA: true,
574
+ featureB: false,
575
+ featureC: true,
576
+ },
577
+ },
578
+ };
579
+
580
+ const sourceStateB = {
581
+ preferences: {
582
+ features: {
583
+ featureD: true,
584
+ featureE: false,
585
+ featureF: true,
586
+ },
587
+ },
588
+ };
589
+
590
+ const interfaceState = {
591
+ preferences: {
592
+ features: {
593
+ 'core/test-a': {
594
+ featureG: true,
595
+ featureH: false,
596
+ featureI: true,
597
+ },
598
+ 'core/test-b': {
599
+ featureJ: true,
600
+ featureK: false,
601
+ featureL: true,
602
+ },
603
+ },
604
+ },
605
+ };
606
+
607
+ persistenceInterface.set( 'core/test-a', sourceStateA );
608
+ persistenceInterface.set( 'core/test-b', sourceStateB );
609
+ persistenceInterface.set( 'core/interface', interfaceState );
610
+
611
+ migrateFeaturePreferencesToPreferencesStore(
612
+ persistenceInterface,
613
+ 'core/test-a'
614
+ );
615
+
616
+ migrateFeaturePreferencesToPreferencesStore(
617
+ persistenceInterface,
618
+ 'core/test-b'
619
+ );
620
+
621
+ expect( persistenceInterface.get() ).toEqual( {
622
+ 'core/preferences': {
623
+ preferences: {
624
+ 'core/test-a': {
625
+ featureG: true,
626
+ featureH: false,
627
+ featureI: true,
628
+ },
629
+ 'core/test-b': {
630
+ featureJ: true,
631
+ featureK: false,
632
+ featureL: true,
633
+ },
634
+ },
635
+ },
636
+ 'core/interface': {
637
+ preferences: {
638
+ features: {
639
+ 'core/test-a': undefined,
640
+ 'core/test-b': undefined,
641
+ },
642
+ },
643
+ },
644
+ 'core/test-a': {
645
+ preferences: {
646
+ features: undefined,
647
+ },
648
+ },
649
+ 'core/test-b': {
650
+ preferences: {
651
+ features: undefined,
652
+ },
653
+ },
654
+ } );
655
+ } );
656
+
657
+ it( 'only migrates persisted data for the source name from source stores', () => {
658
+ const persistenceInterface = createPersistenceInterface( {
659
+ storageKey: 'test-username',
660
+ } );
661
+
662
+ const sourceStateA = {
663
+ preferences: {
664
+ features: {
665
+ featureA: true,
666
+ featureB: false,
667
+ featureC: true,
668
+ },
669
+ },
670
+ };
671
+
672
+ const sourceStateB = {
673
+ preferences: {
674
+ features: {
675
+ featureD: true,
676
+ featureE: false,
677
+ featureF: true,
678
+ },
679
+ },
680
+ };
681
+
682
+ persistenceInterface.set( 'core/test-a', sourceStateA );
683
+ persistenceInterface.set( 'core/test-b', sourceStateB );
684
+
685
+ migrateFeaturePreferencesToPreferencesStore(
686
+ persistenceInterface,
687
+ 'core/test-a'
688
+ );
689
+
690
+ expect( persistenceInterface.get() ).toEqual( {
691
+ 'core/preferences': {
692
+ preferences: {
693
+ 'core/test-a': {
694
+ featureA: true,
695
+ featureB: false,
696
+ featureC: true,
697
+ },
698
+ },
699
+ },
700
+ 'core/test-a': {
701
+ preferences: {
702
+ features: undefined,
703
+ },
704
+ },
705
+ 'core/test-b': {
706
+ preferences: {
707
+ features: {
708
+ featureD: true,
709
+ featureE: false,
710
+ featureF: true,
711
+ },
712
+ },
713
+ },
714
+ } );
715
+ } );
716
+
717
+ it( 'only migrates persisted data for the source name from interface', () => {
718
+ const persistenceInterface = createPersistenceInterface( {
719
+ storageKey: 'test-username',
720
+ } );
721
+
722
+ const interfaceState = {
723
+ preferences: {
724
+ features: {
725
+ 'core/test-a': {
726
+ featureG: true,
727
+ featureH: false,
728
+ featureI: true,
729
+ },
730
+ 'core/test-b': {
731
+ featureJ: true,
732
+ featureK: false,
733
+ featureL: true,
734
+ },
735
+ },
736
+ },
737
+ };
738
+
739
+ persistenceInterface.set( 'core/interface', interfaceState );
740
+
741
+ migrateFeaturePreferencesToPreferencesStore(
742
+ persistenceInterface,
743
+ 'core/test-a'
744
+ );
745
+
746
+ expect( persistenceInterface.get() ).toEqual( {
747
+ 'core/preferences': {
748
+ preferences: {
749
+ 'core/test-a': {
750
+ featureG: true,
751
+ featureH: false,
752
+ featureI: true,
753
+ },
754
+ },
755
+ },
756
+ 'core/interface': {
757
+ preferences: {
758
+ features: {
759
+ 'core/test-a': undefined,
760
+ 'core/test-b': {
761
+ featureJ: true,
762
+ featureK: false,
763
+ featureL: true,
764
+ },
765
+ },
766
+ },
767
+ },
768
+ } );
769
+ } );
770
+ } );
771
+
772
+ describe( 'migrateIndividualPreferenceToPreferencesStore', () => {
773
+ it( 'migrates an individual preference from the source to the preferences store', () => {
774
+ const persistenceInterface = createPersistenceInterface( {
775
+ storageKey: 'test-username',
776
+ } );
777
+
778
+ const initialState = {
779
+ preferences: {
780
+ myPreference: '123',
781
+ },
782
+ };
783
+
784
+ persistenceInterface.set( 'core/test', initialState );
785
+
786
+ migrateIndividualPreferenceToPreferencesStore(
787
+ persistenceInterface,
788
+ 'core/test',
789
+ 'myPreference'
790
+ );
791
+
792
+ expect( persistenceInterface.get() ).toEqual( {
793
+ 'core/preferences': {
794
+ preferences: {
795
+ 'core/test': {
796
+ myPreference: '123',
797
+ },
798
+ },
799
+ },
800
+ 'core/test': {
801
+ preferences: {
802
+ myPreference: undefined,
803
+ },
804
+ },
805
+ } );
806
+ } );
807
+
808
+ it( 'does not overwrite other preferences in the preferences store', () => {
809
+ const persistenceInterface = createPersistenceInterface( {
810
+ storageKey: 'test-username',
811
+ } );
812
+
813
+ const initialState = {
814
+ preferences: {
815
+ myPreference: '123',
816
+ },
817
+ };
818
+
819
+ persistenceInterface.set( 'core/test', initialState );
820
+ persistenceInterface.set( 'core/preferences', {
821
+ preferences: {
822
+ 'core/other-store': {
823
+ preferenceA: 1,
824
+ preferenceB: 2,
825
+ },
826
+ 'core/test': {
827
+ unrelatedPreference: 'unrelated-value',
828
+ },
829
+ },
830
+ } );
831
+
832
+ migrateIndividualPreferenceToPreferencesStore(
833
+ persistenceInterface,
834
+ 'core/test',
835
+ 'myPreference'
836
+ );
837
+
838
+ expect( persistenceInterface.get() ).toEqual( {
839
+ 'core/preferences': {
840
+ preferences: {
841
+ 'core/other-store': {
842
+ preferenceA: 1,
843
+ preferenceB: 2,
844
+ },
845
+ 'core/test': {
846
+ unrelatedPreference: 'unrelated-value',
847
+ myPreference: '123',
848
+ },
849
+ },
850
+ },
851
+ 'core/test': {
852
+ preferences: {
853
+ myPreference: undefined,
854
+ },
855
+ },
856
+ } );
857
+ } );
858
+
859
+ it( 'does not migrate data if there is already a matching preference key at the target', () => {
860
+ const persistenceInterface = createPersistenceInterface( {
861
+ storageKey: 'test-username',
862
+ } );
863
+
864
+ persistenceInterface.set( 'core/test', {
865
+ preferences: {
866
+ myPreference: '123',
867
+ },
868
+ } );
869
+
870
+ persistenceInterface.set( 'core/preferences', {
871
+ preferences: {
872
+ 'core/test': {
873
+ myPreference: 'already-set',
874
+ },
875
+ },
876
+ } );
877
+
878
+ migrateIndividualPreferenceToPreferencesStore(
879
+ persistenceInterface,
880
+ 'core/test',
881
+ 'myPreference'
882
+ );
883
+
884
+ expect( persistenceInterface.get() ).toEqual( {
885
+ 'core/preferences': {
886
+ preferences: {
887
+ 'core/test': {
888
+ myPreference: 'already-set',
889
+ },
890
+ },
891
+ },
892
+ 'core/test': {
893
+ preferences: {
894
+ myPreference: '123',
895
+ },
896
+ },
897
+ } );
898
+ } );
899
+ } );
495
900
  } );
@@ -330,20 +330,35 @@ function mapResolveSelectors( selectors, store ) {
330
330
  'getCachedResolvers',
331
331
  ] ),
332
332
  ( selector, selectorName ) => ( ...args ) =>
333
- new Promise( ( resolve ) => {
333
+ new Promise( ( resolve, reject ) => {
334
334
  const hasFinished = () =>
335
335
  selectors.hasFinishedResolution( selectorName, args );
336
+ const finalize = ( result ) => {
337
+ const hasFailed = selectors.hasResolutionFailed(
338
+ selectorName,
339
+ args
340
+ );
341
+ if ( hasFailed ) {
342
+ const error = selectors.getResolutionError(
343
+ selectorName,
344
+ args
345
+ );
346
+ reject( error );
347
+ } else {
348
+ resolve( result );
349
+ }
350
+ };
336
351
  const getResult = () => selector.apply( null, args );
337
- // trigger the selector (to trigger the resolver)
352
+ // Trigger the selector (to trigger the resolver)
338
353
  const result = getResult();
339
354
  if ( hasFinished() ) {
340
- return resolve( result );
355
+ return finalize( result );
341
356
  }
342
357
 
343
358
  const unsubscribe = store.subscribe( () => {
344
359
  if ( hasFinished() ) {
345
360
  unsubscribe();
346
- resolve( getResult() );
361
+ finalize( getResult() );
347
362
  }
348
363
  } );
349
364
  } )
@@ -370,8 +385,8 @@ function mapResolvers( resolvers, selectors, store, resolversCache ) {
370
385
  }
371
386
 
372
387
  return {
373
- ...resolver, // copy the enumerable properties of the resolver function
374
- fulfill: resolver, // add the fulfill method
388
+ ...resolver, // Copy the enumerable properties of the resolver function.
389
+ fulfill: resolver, // Add the fulfill method.
375
390
  };
376
391
  } );
377
392
 
@@ -413,15 +428,28 @@ function mapResolvers( resolvers, selectors, store, resolversCache ) {
413
428
  store.dispatch(
414
429
  metadataActions.startResolution( selectorName, args )
415
430
  );
416
- await fulfillResolver(
417
- store,
418
- mappedResolvers,
419
- selectorName,
420
- ...args
421
- );
422
- store.dispatch(
423
- metadataActions.finishResolution( selectorName, args )
424
- );
431
+ try {
432
+ await fulfillResolver(
433
+ store,
434
+ mappedResolvers,
435
+ selectorName,
436
+ ...args
437
+ );
438
+ store.dispatch(
439
+ metadataActions.finishResolution(
440
+ selectorName,
441
+ args
442
+ )
443
+ );
444
+ } catch ( error ) {
445
+ store.dispatch(
446
+ metadataActions.failResolution(
447
+ selectorName,
448
+ args,
449
+ error
450
+ )
451
+ );
452
+ }
425
453
  } );
426
454
  }
427
455
 
@@ -32,13 +32,32 @@ export function finishResolution( selectorName, args ) {
32
32
  };
33
33
  }
34
34
 
35
+ /**
36
+ * Returns an action object used in signalling that selector resolution has
37
+ * failed.
38
+ *
39
+ * @param {string} selectorName Name of selector for which resolver triggered.
40
+ * @param {unknown[]} args Arguments to associate for uniqueness.
41
+ * @param {Error|unknown} error The error that caused the failure.
42
+ *
43
+ * @return {{ type: 'FAIL_RESOLUTION', selectorName: string, args: unknown[], error: Error|unknown }} Action object.
44
+ */
45
+ export function failResolution( selectorName, args, error ) {
46
+ return {
47
+ type: 'FAIL_RESOLUTION',
48
+ selectorName,
49
+ args,
50
+ error,
51
+ };
52
+ }
53
+
35
54
  /**
36
55
  * Returns an action object used in signalling that a batch of selector resolutions has
37
56
  * started.
38
57
  *
39
- * @param {string} selectorName Name of selector for which resolver triggered.
58
+ * @param {string} selectorName Name of selector for which resolver triggered.
40
59
  * @param {unknown[][]} args Array of arguments to associate for uniqueness, each item
41
- * is associated to a resolution.
60
+ * is associated to a resolution.
42
61
  *
43
62
  * @return {{ type: 'START_RESOLUTIONS', selectorName: string, args: unknown[][] }} Action object.
44
63
  */
@@ -54,9 +73,9 @@ export function startResolutions( selectorName, args ) {
54
73
  * Returns an action object used in signalling that a batch of selector resolutions has
55
74
  * completed.
56
75
  *
57
- * @param {string} selectorName Name of selector for which resolver triggered.
76
+ * @param {string} selectorName Name of selector for which resolver triggered.
58
77
  * @param {unknown[][]} args Array of arguments to associate for uniqueness, each item
59
- * is associated to a resolution.
78
+ * is associated to a resolution.
60
79
  *
61
80
  * @return {{ type: 'FINISH_RESOLUTIONS', selectorName: string, args: unknown[][] }} Action object.
62
81
  */
@@ -68,6 +87,26 @@ export function finishResolutions( selectorName, args ) {
68
87
  };
69
88
  }
70
89
 
90
+ /**
91
+ * Returns an action object used in signalling that a batch of selector resolutions has
92
+ * completed and at least one of them has failed.
93
+ *
94
+ * @param {string} selectorName Name of selector for which resolver triggered.
95
+ * @param {unknown[]} args Array of arguments to associate for uniqueness, each item
96
+ * is associated to a resolution.
97
+ * @param {(Error|unknown)[]} errors Array of errors to associate for uniqueness, each item
98
+ * is associated to a resolution.
99
+ * @return {{ type: 'FAIL_RESOLUTIONS', selectorName: string, args: unknown[], errors: Array<Error|unknown> }} Action object.
100
+ */
101
+ export function failResolutions( selectorName, args, errors ) {
102
+ return {
103
+ type: 'FAIL_RESOLUTIONS',
104
+ selectorName,
105
+ args,
106
+ errors,
107
+ };
108
+ }
109
+
71
110
  /**
72
111
  * Returns an action object used in signalling that we should invalidate the resolution cache.
73
112
  *
@@ -13,15 +13,23 @@ import { selectorArgsToStateKey, onSubKey } from './utils';
13
13
  type Action =
14
14
  | ReturnType< typeof import('./actions').startResolution >
15
15
  | ReturnType< typeof import('./actions').finishResolution >
16
+ | ReturnType< typeof import('./actions').failResolution >
16
17
  | ReturnType< typeof import('./actions').startResolutions >
17
18
  | ReturnType< typeof import('./actions').finishResolutions >
19
+ | ReturnType< typeof import('./actions').failResolutions >
18
20
  | ReturnType< typeof import('./actions').invalidateResolution >
19
21
  | ReturnType< typeof import('./actions').invalidateResolutionForStore >
20
22
  | ReturnType<
21
23
  typeof import('./actions').invalidateResolutionForStoreSelector
22
24
  >;
23
25
 
24
- export type State = EquivalentKeyMap< unknown[] | unknown, boolean >;
26
+ type StateKey = unknown[] | unknown;
27
+ export type StateValue =
28
+ | { status: 'resolving' | 'finished' }
29
+ | { status: 'error'; error: Error | unknown };
30
+
31
+ export type Status = StateValue[ 'status' ];
32
+ export type State = EquivalentKeyMap< StateKey, StateValue >;
25
33
 
26
34
  /**
27
35
  * Reducer function returning next state for selector resolution of
@@ -34,23 +42,64 @@ const subKeysIsResolved: Reducer< Record< string, State >, Action > = onSubKey<
34
42
  Action
35
43
  >( 'selectorName' )( ( state = new EquivalentKeyMap(), action: Action ) => {
36
44
  switch ( action.type ) {
37
- case 'START_RESOLUTION':
45
+ case 'START_RESOLUTION': {
46
+ const nextState = new EquivalentKeyMap( state );
47
+ nextState.set( selectorArgsToStateKey( action.args ), {
48
+ status: 'resolving',
49
+ } );
50
+ return nextState;
51
+ }
38
52
  case 'FINISH_RESOLUTION': {
39
- const isStarting = action.type === 'START_RESOLUTION';
40
53
  const nextState = new EquivalentKeyMap( state );
41
- nextState.set( selectorArgsToStateKey( action.args ), isStarting );
54
+ nextState.set( selectorArgsToStateKey( action.args ), {
55
+ status: 'finished',
56
+ } );
57
+ return nextState;
58
+ }
59
+ case 'FAIL_RESOLUTION': {
60
+ const nextState = new EquivalentKeyMap( state );
61
+ nextState.set( selectorArgsToStateKey( action.args ), {
62
+ status: 'error',
63
+ error: action.error,
64
+ } );
65
+ return nextState;
66
+ }
67
+ case 'START_RESOLUTIONS': {
68
+ const nextState = new EquivalentKeyMap( state );
69
+ for ( const resolutionArgs of action.args ) {
70
+ nextState.set( selectorArgsToStateKey( resolutionArgs ), {
71
+ status: 'resolving',
72
+ } );
73
+ }
42
74
  return nextState;
43
75
  }
44
- case 'START_RESOLUTIONS':
45
76
  case 'FINISH_RESOLUTIONS': {
46
- const isStarting = action.type === 'START_RESOLUTIONS';
47
77
  const nextState = new EquivalentKeyMap( state );
48
78
  for ( const resolutionArgs of action.args ) {
79
+ nextState.set( selectorArgsToStateKey( resolutionArgs ), {
80
+ status: 'finished',
81
+ } );
82
+ }
83
+ return nextState;
84
+ }
85
+ case 'FAIL_RESOLUTIONS': {
86
+ const nextState = new EquivalentKeyMap( state );
87
+ action.args.forEach( ( resolutionArgs, idx ) => {
88
+ const resolutionState: StateValue = {
89
+ status: 'error',
90
+ error: undefined,
91
+ };
92
+
93
+ const error = action.errors[ idx ];
94
+ if ( error ) {
95
+ resolutionState.error = error;
96
+ }
97
+
49
98
  nextState.set(
50
- selectorArgsToStateKey( resolutionArgs ),
51
- isStarting
99
+ selectorArgsToStateKey( resolutionArgs as unknown[] ),
100
+ resolutionState
52
101
  );
53
- }
102
+ } );
54
103
  return nextState;
55
104
  }
56
105
  case 'INVALIDATE_RESOLUTION': {
@@ -82,8 +131,10 @@ const isResolved = ( state: Record< string, State > = {}, action: Action ) => {
82
131
  : state;
83
132
  case 'START_RESOLUTION':
84
133
  case 'FINISH_RESOLUTION':
134
+ case 'FAIL_RESOLUTION':
85
135
  case 'START_RESOLUTIONS':
86
136
  case 'FINISH_RESOLUTIONS':
137
+ case 'FAIL_RESOLUTIONS':
87
138
  case 'INVALIDATE_RESOLUTION':
88
139
  return subKeysIsResolved( state, action );
89
140
  }