@wordpress/core-data 6.22.0 → 6.24.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 (88) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +77 -2
  3. package/build/actions.js +55 -0
  4. package/build/actions.js.map +1 -1
  5. package/build/entities.js +18 -3
  6. package/build/entities.js.map +1 -1
  7. package/build/entity-provider.js +7 -1
  8. package/build/entity-provider.js.map +1 -1
  9. package/build/entity-types/global-styles-revision.js +6 -0
  10. package/build/entity-types/global-styles-revision.js.map +1 -0
  11. package/build/entity-types/index.js.map +1 -1
  12. package/build/entity-types/post-revision.js +6 -0
  13. package/build/entity-types/post-revision.js.map +1 -0
  14. package/build/hooks/use-entity-record.js +16 -5
  15. package/build/hooks/use-entity-record.js.map +1 -1
  16. package/build/queried-data/reducer.js +3 -1
  17. package/build/queried-data/reducer.js.map +1 -1
  18. package/build/queried-data/selectors.js +3 -1
  19. package/build/queried-data/selectors.js.map +1 -1
  20. package/build/reducer.js +52 -4
  21. package/build/reducer.js.map +1 -1
  22. package/build/resolvers.js +131 -3
  23. package/build/resolvers.js.map +1 -1
  24. package/build/selectors.js +92 -1
  25. package/build/selectors.js.map +1 -1
  26. package/build-module/actions.js +52 -0
  27. package/build-module/actions.js.map +1 -1
  28. package/build-module/entities.js +18 -3
  29. package/build-module/entities.js.map +1 -1
  30. package/build-module/entity-provider.js +7 -1
  31. package/build-module/entity-provider.js.map +1 -1
  32. package/build-module/entity-types/global-styles-revision.js +2 -0
  33. package/build-module/entity-types/global-styles-revision.js.map +1 -0
  34. package/build-module/entity-types/index.js.map +1 -1
  35. package/build-module/entity-types/post-revision.js +2 -0
  36. package/build-module/entity-types/post-revision.js.map +1 -0
  37. package/build-module/hooks/use-entity-record.js +16 -5
  38. package/build-module/hooks/use-entity-record.js.map +1 -1
  39. package/build-module/queried-data/reducer.js +3 -1
  40. package/build-module/queried-data/reducer.js.map +1 -1
  41. package/build-module/queried-data/selectors.js +3 -1
  42. package/build-module/queried-data/selectors.js.map +1 -1
  43. package/build-module/reducer.js +51 -4
  44. package/build-module/reducer.js.map +1 -1
  45. package/build-module/resolvers.js +127 -2
  46. package/build-module/resolvers.js.map +1 -1
  47. package/build-module/selectors.js +88 -1
  48. package/build-module/selectors.js.map +1 -1
  49. package/build-types/actions.d.ts +14 -0
  50. package/build-types/actions.d.ts.map +1 -1
  51. package/build-types/entities.d.ts +23 -1
  52. package/build-types/entities.d.ts.map +1 -1
  53. package/build-types/entity-provider.d.ts.map +1 -1
  54. package/build-types/entity-types/global-styles-revision.d.ts +43 -0
  55. package/build-types/entity-types/global-styles-revision.d.ts.map +1 -0
  56. package/build-types/entity-types/index.d.ts +4 -2
  57. package/build-types/entity-types/index.d.ts.map +1 -1
  58. package/build-types/entity-types/post-revision.d.ts +76 -0
  59. package/build-types/entity-types/post-revision.d.ts.map +1 -0
  60. package/build-types/hooks/use-entity-record.d.ts.map +1 -1
  61. package/build-types/index.d.ts +19 -4
  62. package/build-types/index.d.ts.map +1 -1
  63. package/build-types/queried-data/selectors.d.ts.map +1 -1
  64. package/build-types/reducer.d.ts +10 -0
  65. package/build-types/reducer.d.ts.map +1 -1
  66. package/build-types/resolvers.d.ts +12 -0
  67. package/build-types/resolvers.d.ts.map +1 -1
  68. package/build-types/selectors.d.ts +54 -3
  69. package/build-types/selectors.d.ts.map +1 -1
  70. package/package.json +17 -17
  71. package/src/actions.js +61 -0
  72. package/src/entities.js +33 -1
  73. package/src/entity-provider.js +8 -1
  74. package/src/entity-types/global-styles-revision.ts +47 -0
  75. package/src/entity-types/index.ts +6 -0
  76. package/src/entity-types/post-revision.ts +93 -0
  77. package/src/hooks/test/use-entity-record.js +41 -0
  78. package/src/hooks/use-entity-record.ts +30 -18
  79. package/src/queried-data/reducer.js +3 -3
  80. package/src/queried-data/selectors.js +3 -1
  81. package/src/queried-data/test/reducer.js +11 -0
  82. package/src/reducer.js +68 -2
  83. package/src/resolvers.js +176 -2
  84. package/src/selectors.ts +141 -1
  85. package/src/test/entities.js +9 -0
  86. package/src/test/reducer.js +235 -0
  87. package/src/test/selectors.js +96 -0
  88. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import type { Context, ContextualField, OmitNevers } from './helpers';
5
+
6
+ import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records';
7
+
8
+ declare module './base-entity-records' {
9
+ export namespace BaseEntityRecords {
10
+ export interface GlobalStylesRevision< C extends Context > {
11
+ /**
12
+ * The ID for the author of the global styles revision.
13
+ */
14
+ author: number;
15
+ /**
16
+ * The date the post global styles revision published, in the site's timezone.
17
+ */
18
+ date: string | null;
19
+ /**
20
+ * The date the global styles revision was published, as GMT.
21
+ */
22
+ date_gmt: ContextualField< string | null, 'view' | 'edit', C >;
23
+ /**
24
+ * Unique identifier for the revision.
25
+ */
26
+ id: number;
27
+ /**
28
+ * The date the global styles revision was last modified, in the site's timezone.
29
+ */
30
+ modified: ContextualField< string, 'view' | 'edit', C >;
31
+ /**
32
+ * The date the global styles revision was last modified, as GMT.
33
+ */
34
+ modified_gmt: ContextualField< string, 'view' | 'edit', C >;
35
+ /**
36
+ * Identifier for the parent of the revision.
37
+ */
38
+ parent: number;
39
+ styles: Record< string, Object >;
40
+ settings: Record< string, Object >;
41
+ }
42
+ }
43
+ }
44
+
45
+ export type GlobalStylesRevision< C extends Context = 'view' > = OmitNevers<
46
+ _BaseEntityRecords.GlobalStylesRevision< C >
47
+ >;
@@ -4,12 +4,14 @@
4
4
  import type { Context, Updatable } from './helpers';
5
5
  import type { Attachment } from './attachment';
6
6
  import type { Comment } from './comment';
7
+ import type { GlobalStylesRevision } from './global-styles-revision';
7
8
  import type { MenuLocation } from './menu-location';
8
9
  import type { NavMenu } from './nav-menu';
9
10
  import type { NavMenuItem } from './nav-menu-item';
10
11
  import type { Page } from './page';
11
12
  import type { Plugin } from './plugin';
12
13
  import type { Post } from './post';
14
+ import type { PostRevision } from './post-revision';
13
15
  import type { Settings } from './settings';
14
16
  import type { Sidebar } from './sidebar';
15
17
  import type { Taxonomy } from './taxonomy';
@@ -27,12 +29,14 @@ export type {
27
29
  Attachment,
28
30
  Comment,
29
31
  Context,
32
+ GlobalStylesRevision,
30
33
  MenuLocation,
31
34
  NavMenu,
32
35
  NavMenuItem,
33
36
  Page,
34
37
  Plugin,
35
38
  Post,
39
+ PostRevision,
36
40
  Settings,
37
41
  Sidebar,
38
42
  Taxonomy,
@@ -82,12 +86,14 @@ export interface PerPackageEntityRecords< C extends Context > {
82
86
  core:
83
87
  | Attachment< C >
84
88
  | Comment< C >
89
+ | GlobalStylesRevision< C >
85
90
  | MenuLocation< C >
86
91
  | NavMenu< C >
87
92
  | NavMenuItem< C >
88
93
  | Page< C >
89
94
  | Plugin< C >
90
95
  | Post< C >
96
+ | PostRevision< C >
91
97
  | Settings< C >
92
98
  | Sidebar< C >
93
99
  | Taxonomy< C >
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import type {
5
+ Context,
6
+ ContextualField,
7
+ RenderedText,
8
+ OmitNevers,
9
+ } from './helpers';
10
+
11
+ import type { BaseEntityRecords as _BaseEntityRecords } from './base-entity-records';
12
+
13
+ declare module './base-entity-records' {
14
+ export namespace BaseEntityRecords {
15
+ export interface PostRevision< C extends Context > {
16
+ /**
17
+ * The ID for the author of the post revision.
18
+ */
19
+ author: number;
20
+ /**
21
+ * The content for the post.
22
+ */
23
+ content: ContextualField<
24
+ RenderedText< C > & {
25
+ /**
26
+ * Whether the content is protected with a password.
27
+ */
28
+ is_protected: boolean;
29
+ /**
30
+ * Version of the content block format used by the post.
31
+ */
32
+ block_version: ContextualField< string, 'edit', C >;
33
+ },
34
+ 'view' | 'edit',
35
+ C
36
+ >;
37
+ /**
38
+ * The date the post was published, in the site's timezone.
39
+ */
40
+ date: string | null;
41
+ /**
42
+ * The date the post was published, as GMT.
43
+ */
44
+ date_gmt: ContextualField< string | null, 'view' | 'edit', C >;
45
+ /**
46
+ * The excerpt for the post revision.
47
+ */
48
+ excerpt: RenderedText< C > & {
49
+ protected: boolean;
50
+ };
51
+ /**
52
+ * The globally unique identifier for the post.
53
+ */
54
+ guid: ContextualField< RenderedText< C >, 'view' | 'edit', C >;
55
+ /**
56
+ * Unique identifier for the revision.
57
+ */
58
+ id: number;
59
+ /**
60
+ * Meta fields.
61
+ */
62
+ meta: ContextualField<
63
+ Record< string, string >,
64
+ 'view' | 'edit',
65
+ C
66
+ >;
67
+ /**
68
+ * The date the post was last modified, in the site's timezone.
69
+ */
70
+ modified: ContextualField< string, 'view' | 'edit', C >;
71
+ /**
72
+ * The date the post revision was last modified, as GMT.
73
+ */
74
+ modified_gmt: ContextualField< string, 'view' | 'edit', C >;
75
+ /**
76
+ * Identifier for the parent of the revision.
77
+ */
78
+ parent: number;
79
+ /**
80
+ * An alphanumeric identifier for the post unique to its type.
81
+ */
82
+ slug: string;
83
+ /**
84
+ * The title for the post revision.
85
+ */
86
+ title: RenderedText< C >;
87
+ }
88
+ }
89
+ }
90
+
91
+ export type PostRevision< C extends Context = 'view' > = OmitNevers<
92
+ _BaseEntityRecords.PostRevision< C >
93
+ >;
@@ -23,6 +23,7 @@ describe( 'useEntityRecord', () => {
23
23
  beforeEach( () => {
24
24
  registry = createRegistry();
25
25
  registry.register( coreDataStore );
26
+ triggerFetch.mockReset();
26
27
  } );
27
28
 
28
29
  const TEST_RECORD = { id: 1, hello: 'world' };
@@ -113,4 +114,44 @@ describe( 'useEntityRecord', () => {
113
114
  expect( widget.editedRecord ).toEqual( { hello: 'foo', id: 1 } );
114
115
  expect( widget.edits ).toEqual( { hello: 'foo' } );
115
116
  } );
117
+
118
+ it( 'does not resolve entity record when disabled via options', async () => {
119
+ triggerFetch.mockImplementation( () => TEST_RECORD );
120
+
121
+ let data;
122
+ const TestComponent = ( { enabled } ) => {
123
+ data = useEntityRecord( 'root', 'widget', 1, { enabled } );
124
+ return <div />;
125
+ };
126
+ const UI = ( { enabled } ) => (
127
+ <RegistryProvider value={ registry }>
128
+ <TestComponent enabled={ enabled } />
129
+ </RegistryProvider>
130
+ );
131
+
132
+ const { rerender } = render( <UI enabled={ true } /> );
133
+
134
+ // A minimum delay for a fetch request. The same delay is used again as a control.
135
+ await act(
136
+ () => new Promise( ( resolve ) => setTimeout( resolve, 0 ) )
137
+ );
138
+ expect( triggerFetch ).toHaveBeenCalledTimes( 1 );
139
+
140
+ rerender( <UI enabled={ false } /> );
141
+
142
+ expect( data ).toEqual( {
143
+ edit: expect.any( Function ),
144
+ editedRecord: {},
145
+ hasEdits: false,
146
+ edits: {},
147
+ record: null,
148
+ save: expect.any( Function ),
149
+ } );
150
+
151
+ // The same delay.
152
+ await act(
153
+ () => new Promise( ( resolve ) => setTimeout( resolve, 0 ) )
154
+ );
155
+ expect( triggerFetch ).toHaveBeenCalledTimes( 1 );
156
+ } );
116
157
  } );
@@ -56,6 +56,8 @@ export interface Options {
56
56
  enabled: boolean;
57
57
  }
58
58
 
59
+ const EMPTY_OBJECT = {};
60
+
59
61
  /**
60
62
  * Resolves the specified entity record.
61
63
  *
@@ -167,24 +169,34 @@ export default function useEntityRecord< RecordType >(
167
169
  );
168
170
 
169
171
  const { editedRecord, hasEdits, edits } = useSelect(
170
- ( select ) => ( {
171
- editedRecord: select( coreStore ).getEditedEntityRecord(
172
- kind,
173
- name,
174
- recordId
175
- ),
176
- hasEdits: select( coreStore ).hasEditsForEntityRecord(
177
- kind,
178
- name,
179
- recordId
180
- ),
181
- edits: select( coreStore ).getEntityRecordNonTransientEdits(
182
- kind,
183
- name,
184
- recordId
185
- ),
186
- } ),
187
- [ kind, name, recordId ]
172
+ ( select ) => {
173
+ if ( ! options.enabled ) {
174
+ return {
175
+ editedRecord: EMPTY_OBJECT,
176
+ hasEdits: false,
177
+ edits: EMPTY_OBJECT,
178
+ };
179
+ }
180
+
181
+ return {
182
+ editedRecord: select( coreStore ).getEditedEntityRecord(
183
+ kind,
184
+ name,
185
+ recordId
186
+ ),
187
+ hasEdits: select( coreStore ).hasEditsForEntityRecord(
188
+ kind,
189
+ name,
190
+ recordId
191
+ ),
192
+ edits: select( coreStore ).getEntityRecordNonTransientEdits(
193
+ kind,
194
+ name,
195
+ recordId
196
+ ),
197
+ };
198
+ },
199
+ [ kind, name, recordId, options.enabled ]
188
200
  );
189
201
 
190
202
  const { data: record, ...querySelectRest } = useQuerySelect(
@@ -56,10 +56,10 @@ export function getMergedItemIds( itemIds, nextItemIds, page, perPage ) {
56
56
 
57
57
  for ( let i = 0; i < size; i++ ) {
58
58
  // Preserve existing item ID except for subset of range of next items.
59
+ // We need to check against the possible maximum upper boundary because
60
+ // a page could receive fewer than what was previously stored.
59
61
  const isInNextItemsRange =
60
- i >= nextItemIdsStartIndex &&
61
- i < nextItemIdsStartIndex + nextItemIds.length;
62
-
62
+ i >= nextItemIdsStartIndex && i < nextItemIdsStartIndex + perPage;
63
63
  mergedItemIds[ i ] = isInNextItemsRange
64
64
  ? nextItemIds[ i - nextItemIdsStartIndex ]
65
65
  : itemIds?.[ i ];
@@ -52,7 +52,9 @@ function getQueriedItemsUncached( state, query ) {
52
52
  if ( Array.isArray( include ) && ! include.includes( itemId ) ) {
53
53
  continue;
54
54
  }
55
-
55
+ if ( itemId === undefined ) {
56
+ continue;
57
+ }
56
58
  // Having a target item ID doesn't guarantee that this object has been queried.
57
59
  if ( ! state.items[ context ]?.hasOwnProperty( itemId ) ) {
58
60
  return null;
@@ -64,6 +64,17 @@ describe( 'getMergedItemIds', () => {
64
64
 
65
65
  expect( result ).toEqual( [ 1, 3, 4 ] );
66
66
  } );
67
+ it( 'should update a page properly if less items are provided than previously stored', () => {
68
+ let original = deepFreeze( [ 1, 2, 3 ] );
69
+ let result = getMergedItemIds( original, [ 1, 2 ], 1, 3 );
70
+
71
+ expect( result ).toEqual( [ 1, 2 ] );
72
+
73
+ original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] );
74
+ result = getMergedItemIds( original, [ 9 ], 2, 2 );
75
+
76
+ expect( result ).toEqual( [ 1, 2, 9, undefined, 5, 6 ] );
77
+ } );
67
78
  } );
68
79
 
69
80
  describe( 'itemIsComplete', () => {
package/src/reducer.js CHANGED
@@ -238,14 +238,13 @@ function entity( entityConfig ) {
238
238
  // Inject the entity config into the action.
239
239
  replaceAction( ( action ) => {
240
240
  return {
241
- ...action,
242
241
  key: entityConfig.key || DEFAULT_ENTITY_KEY,
242
+ ...action,
243
243
  };
244
244
  } ),
245
245
  ] )(
246
246
  combineReducers( {
247
247
  queriedData: queriedDataReducer,
248
-
249
248
  edits: ( state = {}, action ) => {
250
249
  switch ( action.type ) {
251
250
  case 'RECEIVE_ITEMS':
@@ -355,6 +354,52 @@ function entity( entityConfig ) {
355
354
 
356
355
  return state;
357
356
  },
357
+
358
+ // Add revisions to the state tree if the post type supports it.
359
+ ...( entityConfig?.supports?.revisions
360
+ ? {
361
+ revisions: ( state = {}, action ) => {
362
+ // Use the same queriedDataReducer shape for revisions.
363
+ if ( action.type === 'RECEIVE_ITEM_REVISIONS' ) {
364
+ const recordKey = action.recordKey;
365
+ delete action.recordKey;
366
+ const newState = queriedDataReducer(
367
+ state[ recordKey ],
368
+ {
369
+ ...action,
370
+ type: 'RECEIVE_ITEMS',
371
+ }
372
+ );
373
+ return {
374
+ ...state,
375
+ [ recordKey ]: newState,
376
+ };
377
+ }
378
+
379
+ if ( action.type === 'REMOVE_ITEMS' ) {
380
+ return Object.fromEntries(
381
+ Object.entries( state ).filter(
382
+ ( [ id ] ) =>
383
+ ! action.itemIds.some(
384
+ ( itemId ) => {
385
+ if (
386
+ Number.isInteger(
387
+ itemId
388
+ )
389
+ ) {
390
+ return itemId === +id;
391
+ }
392
+ return itemId === id;
393
+ }
394
+ )
395
+ )
396
+ );
397
+ }
398
+
399
+ return state;
400
+ },
401
+ }
402
+ : {} ),
358
403
  } )
359
404
  );
360
405
  }
@@ -572,6 +617,26 @@ export function themeGlobalStyleRevisions( state = {}, action ) {
572
617
  return state;
573
618
  }
574
619
 
620
+ /**
621
+ * Reducer managing the template lookup per query.
622
+ *
623
+ * @param {Record<string, string>} state Current state.
624
+ * @param {Object} action Dispatched action.
625
+ *
626
+ * @return {Record<string, string>} Updated state.
627
+ */
628
+ export function defaultTemplates( state = {}, action ) {
629
+ switch ( action.type ) {
630
+ case 'RECEIVE_DEFAULT_TEMPLATE':
631
+ return {
632
+ ...state,
633
+ [ JSON.stringify( action.query ) ]: action.templateId,
634
+ };
635
+ }
636
+
637
+ return state;
638
+ }
639
+
575
640
  export default combineReducers( {
576
641
  terms,
577
642
  users,
@@ -592,4 +657,5 @@ export default combineReducers( {
592
657
  blockPatternCategories,
593
658
  userPatternCategories,
594
659
  navigationFallbackId,
660
+ defaultTemplates,
595
661
  } );
package/src/resolvers.js CHANGED
@@ -244,7 +244,7 @@ export const getEntityRecords =
244
244
 
245
245
  // If we request fields but the result doesn't contain the fields,
246
246
  // explicitly set these fields as "undefined"
247
- // that way we consider the query "fullfilled".
247
+ // that way we consider the query "fulfilled".
248
248
  if ( query._fields ) {
249
249
  records = records.map( ( record ) => {
250
250
  query._fields.split( ',' ).forEach( ( field ) => {
@@ -322,7 +322,7 @@ export const getCurrentTheme =
322
322
  export const getThemeSupports = forwardResolver( 'getCurrentTheme' );
323
323
 
324
324
  /**
325
- * Requests a preview from the from the Embed API.
325
+ * Requests a preview from the Embed API.
326
326
  *
327
327
  * @param {string} url URL to get the preview for.
328
328
  */
@@ -707,3 +707,177 @@ export const getNavigationFallbackId =
707
707
  ] );
708
708
  }
709
709
  };
710
+
711
+ export const getDefaultTemplateId =
712
+ ( query ) =>
713
+ async ( { dispatch } ) => {
714
+ const template = await apiFetch( {
715
+ path: addQueryArgs( '/wp/v2/templates/lookup', query ),
716
+ } );
717
+ if ( template ) {
718
+ dispatch.receiveDefaultTemplateId( query, template.id );
719
+ }
720
+ };
721
+
722
+ /**
723
+ * Requests an entity's revisions from the REST API.
724
+ *
725
+ * @param {string} kind Entity kind.
726
+ * @param {string} name Entity name.
727
+ * @param {number|string} recordKey The key of the entity record whose revisions you want to fetch.
728
+ * @param {Object|undefined} query Optional object of query parameters to
729
+ * include with request. If requesting specific
730
+ * fields, fields must always include the ID.
731
+ */
732
+ export const getRevisions =
733
+ ( kind, name, recordKey, query = {} ) =>
734
+ async ( { dispatch } ) => {
735
+ const configs = await dispatch( getOrLoadEntitiesConfig( kind ) );
736
+ const entityConfig = configs.find(
737
+ ( config ) => config.name === name && config.kind === kind
738
+ );
739
+
740
+ if (
741
+ ! entityConfig ||
742
+ entityConfig?.__experimentalNoFetch ||
743
+ ! entityConfig?.supports?.revisions
744
+ ) {
745
+ return;
746
+ }
747
+
748
+ if ( query._fields ) {
749
+ // If requesting specific fields, items and query association to said
750
+ // records are stored by ID reference. Thus, fields must always include
751
+ // the ID.
752
+ query = {
753
+ ...query,
754
+ _fields: [
755
+ ...new Set( [
756
+ ...( getNormalizedCommaSeparable( query._fields ) ||
757
+ [] ),
758
+ entityConfig.revisionKey || DEFAULT_ENTITY_KEY,
759
+ ] ),
760
+ ].join(),
761
+ };
762
+ }
763
+
764
+ const path = addQueryArgs(
765
+ entityConfig.getRevisionsUrl( recordKey ),
766
+ query
767
+ );
768
+
769
+ let records, meta;
770
+ if ( entityConfig.supportsPagination && query.per_page !== -1 ) {
771
+ const response = await apiFetch( { path, parse: false } );
772
+ records = Object.values( await response.json() );
773
+ meta = {
774
+ totalItems: parseInt( response.headers.get( 'X-WP-Total' ) ),
775
+ };
776
+ } else {
777
+ records = Object.values( await apiFetch( { path } ) );
778
+ }
779
+
780
+ // If we request fields but the result doesn't contain the fields,
781
+ // explicitly set these fields as "undefined"
782
+ // that way we consider the query "fulfilled".
783
+ if ( query._fields ) {
784
+ records = records.map( ( record ) => {
785
+ query._fields.split( ',' ).forEach( ( field ) => {
786
+ if ( ! record.hasOwnProperty( field ) ) {
787
+ record[ field ] = undefined;
788
+ }
789
+ } );
790
+
791
+ return record;
792
+ } );
793
+ }
794
+
795
+ dispatch.receiveRevisions(
796
+ kind,
797
+ name,
798
+ recordKey,
799
+ records,
800
+ query,
801
+ false,
802
+ meta
803
+ );
804
+
805
+ // When requesting all fields, the list of results can be used to
806
+ // resolve the `getRevision` selector in addition to `getRevisions`.
807
+ if ( ! query?._fields && ! query.context ) {
808
+ const key = entityConfig.key || DEFAULT_ENTITY_KEY;
809
+ const resolutionsArgs = records
810
+ .filter( ( record ) => record[ key ] )
811
+ .map( ( record ) => [ kind, name, recordKey, record[ key ] ] );
812
+
813
+ dispatch( {
814
+ type: 'START_RESOLUTIONS',
815
+ selectorName: 'getRevision',
816
+ args: resolutionsArgs,
817
+ } );
818
+ dispatch( {
819
+ type: 'FINISH_RESOLUTIONS',
820
+ selectorName: 'getRevision',
821
+ args: resolutionsArgs,
822
+ } );
823
+ }
824
+ };
825
+
826
+ // Invalidate cache when a new revision is created.
827
+ getRevisions.shouldInvalidate = ( action, kind, name, recordKey ) =>
828
+ action.type === 'SAVE_ENTITY_RECORD_FINISH' &&
829
+ name === action.name &&
830
+ kind === action.kind &&
831
+ ! action.error &&
832
+ recordKey === action.recordId;
833
+
834
+ /**
835
+ * Requests a specific Entity revision from the REST API.
836
+ *
837
+ * @param {string} kind Entity kind.
838
+ * @param {string} name Entity name.
839
+ * @param {number|string} recordKey The key of the entity record whose revisions you want to fetch.
840
+ * @param {number|string} revisionKey The revision's key.
841
+ * @param {Object|undefined} query Optional object of query parameters to
842
+ * include with request. If requesting specific
843
+ * fields, fields must always include the ID.
844
+ */
845
+ export const getRevision =
846
+ ( kind, name, recordKey, revisionKey, query ) =>
847
+ async ( { dispatch } ) => {
848
+ const configs = await dispatch( getOrLoadEntitiesConfig( kind ) );
849
+ const entityConfig = configs.find(
850
+ ( config ) => config.name === name && config.kind === kind
851
+ );
852
+
853
+ if (
854
+ ! entityConfig ||
855
+ entityConfig?.__experimentalNoFetch ||
856
+ ! entityConfig?.supports?.revisions
857
+ ) {
858
+ return;
859
+ }
860
+
861
+ if ( query !== undefined && query._fields ) {
862
+ // If requesting specific fields, items and query association to said
863
+ // records are stored by ID reference. Thus, fields must always include
864
+ // the ID.
865
+ query = {
866
+ ...query,
867
+ _fields: [
868
+ ...new Set( [
869
+ ...( getNormalizedCommaSeparable( query._fields ) ||
870
+ [] ),
871
+ entityConfig.revisionKey || DEFAULT_ENTITY_KEY,
872
+ ] ),
873
+ ].join(),
874
+ };
875
+ }
876
+ const path = addQueryArgs(
877
+ entityConfig.getRevisionsUrl( recordKey, revisionKey ),
878
+ query
879
+ );
880
+
881
+ const record = await apiFetch( { path } );
882
+ dispatch.receiveRevisions( kind, name, recordKey, record, query );
883
+ };