@wordpress/core-data 4.7.0 → 4.10.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 (95) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +56 -56
  3. package/build/actions.js.map +1 -1
  4. package/build/batch/create-batch.js +1 -1
  5. package/build/batch/create-batch.js.map +1 -1
  6. package/build/batch/default-processor.js +13 -7
  7. package/build/batch/default-processor.js.map +1 -1
  8. package/build/entities.js +49 -35
  9. package/build/entities.js.map +1 -1
  10. package/build/entity-provider.js.map +1 -1
  11. package/build/entity-types/entities.js +6 -0
  12. package/build/entity-types/entities.js.map +1 -0
  13. package/build/hooks/use-entity-record.js.map +1 -1
  14. package/build/hooks/use-query-select.js.map +1 -1
  15. package/build/hooks/use-resource-permissions.js +94 -0
  16. package/build/hooks/use-resource-permissions.js.map +1 -0
  17. package/build/index.js +28 -1
  18. package/build/index.js.map +1 -1
  19. package/build/queried-data/selectors.js.map +1 -1
  20. package/build/resolvers.js +4 -14
  21. package/build/resolvers.js.map +1 -1
  22. package/build/selectors.js +45 -8
  23. package/build/selectors.js.map +1 -1
  24. package/build/utils/forward-resolver.js.map +1 -1
  25. package/build/utils/on-sub-key.js.map +1 -1
  26. package/build/utils/with-weak-map-cache.js +1 -7
  27. package/build/utils/with-weak-map-cache.js.map +1 -1
  28. package/build-module/actions.js.map +1 -1
  29. package/build-module/batch/create-batch.js +2 -2
  30. package/build-module/batch/create-batch.js.map +1 -1
  31. package/build-module/batch/default-processor.js +12 -5
  32. package/build-module/batch/default-processor.js.map +1 -1
  33. package/build-module/entities.js +49 -35
  34. package/build-module/entities.js.map +1 -1
  35. package/build-module/entity-provider.js.map +1 -1
  36. package/build-module/entity-types/entities.js +2 -0
  37. package/build-module/entity-types/entities.js.map +1 -0
  38. package/build-module/hooks/use-entity-record.js.map +1 -1
  39. package/build-module/hooks/use-query-select.js.map +1 -1
  40. package/build-module/hooks/use-resource-permissions.js +82 -0
  41. package/build-module/hooks/use-resource-permissions.js.map +1 -0
  42. package/build-module/index.js +3 -0
  43. package/build-module/index.js.map +1 -1
  44. package/build-module/queried-data/selectors.js.map +1 -1
  45. package/build-module/resolvers.js +5 -15
  46. package/build-module/resolvers.js.map +1 -1
  47. package/build-module/selectors.js +40 -4
  48. package/build-module/selectors.js.map +1 -1
  49. package/build-module/utils/forward-resolver.js.map +1 -1
  50. package/build-module/utils/on-sub-key.js.map +1 -1
  51. package/build-module/utils/with-weak-map-cache.js +1 -6
  52. package/build-module/utils/with-weak-map-cache.js.map +1 -1
  53. package/package.json +12 -12
  54. package/src/actions.js +389 -372
  55. package/src/batch/create-batch.js +2 -2
  56. package/src/batch/default-processor.js +10 -5
  57. package/src/entities.ts +357 -135
  58. package/src/entity-provider.js +4 -6
  59. package/src/entity-types/attachment.ts +4 -3
  60. package/src/entity-types/comment.ts +4 -3
  61. package/src/entity-types/entities.ts +130 -0
  62. package/src/entity-types/index.ts +115 -20
  63. package/src/entity-types/menu-location.ts +4 -3
  64. package/src/entity-types/nav-menu-item.ts +4 -3
  65. package/src/entity-types/nav-menu.ts +3 -3
  66. package/src/entity-types/page.ts +3 -3
  67. package/src/entity-types/plugin.ts +3 -3
  68. package/src/entity-types/post.ts +3 -3
  69. package/src/entity-types/settings.ts +3 -3
  70. package/src/entity-types/sidebar.ts +4 -3
  71. package/src/entity-types/taxonomy.ts +4 -3
  72. package/src/entity-types/theme.ts +3 -3
  73. package/src/entity-types/type.ts +3 -3
  74. package/src/entity-types/user.ts +3 -3
  75. package/src/entity-types/widget-type.ts +4 -3
  76. package/src/entity-types/widget.ts +3 -3
  77. package/src/entity-types/wp-template-part.ts +4 -3
  78. package/src/entity-types/wp-template.ts +4 -3
  79. package/src/fetch/test/__experimental-fetch-link-suggestions.js +2 -4
  80. package/src/hooks/test/use-query-select.js +4 -2
  81. package/src/hooks/test/use-resource-permissions.js +115 -0
  82. package/src/hooks/use-entity-record.ts +0 -1
  83. package/src/hooks/use-query-select.ts +26 -24
  84. package/src/hooks/use-resource-permissions.ts +120 -0
  85. package/src/index.js +3 -0
  86. package/src/locks/test/selectors.js +2 -1
  87. package/src/queried-data/selectors.js +2 -8
  88. package/src/resolvers.js +344 -326
  89. package/src/selectors.ts +347 -194
  90. package/src/test/reducer.js +5 -4
  91. package/src/test/resolvers.js +1 -3
  92. package/src/test/selectors.js +1 -2
  93. package/src/utils/forward-resolver.js +6 -5
  94. package/src/utils/on-sub-key.js +20 -20
  95. package/src/utils/with-weak-map-cache.js +1 -6
package/src/selectors.ts CHANGED
@@ -18,7 +18,18 @@ import { STORE_NAME } from './name';
18
18
  import { getQueriedItems } from './queried-data';
19
19
  import { DEFAULT_ENTITY_KEY } from './entities';
20
20
  import { getNormalizedCommaSeparable, isRawAttribute } from './utils';
21
- import type { Context, User, Theme, WpTemplate } from './entity-types';
21
+ import type {
22
+ Context,
23
+ DefaultContextOf,
24
+ EntityRecordOf,
25
+ KeyOf,
26
+ Kind,
27
+ KindOf,
28
+ Name,
29
+ NameOf,
30
+ User,
31
+ WpTemplate,
32
+ } from './entity-types';
22
33
 
23
34
  // This is an incomplete, high-level approximation of the State type.
24
35
  // It makes the selectors slightly more safe, but is intended to evolve
@@ -29,7 +40,7 @@ interface State {
29
40
  blockPatterns: Array< unknown >;
30
41
  blockPatternCategories: Array< unknown >;
31
42
  currentGlobalStylesId: string;
32
- currentTheme: Theme< 'edit' >;
43
+ currentTheme: string;
33
44
  currentUser: User< 'edit' >;
34
45
  embedPreviews: Record< string, { html: string } >;
35
46
  entities: EntitiesState;
@@ -41,12 +52,17 @@ interface State {
41
52
 
42
53
  interface EntitiesState {
43
54
  config: EntityConfig[];
44
- records: Record< string, unknown >;
55
+ records: Record< Kind, Record< Name, EntityState< Kind, Name > > >;
56
+ }
57
+
58
+ interface EntityState< K extends Kind, N extends Name > {
59
+ edits: Record< KeyOf< K, N >, Partial< EntityRecordOf< K, N > > >;
60
+ saving: Record< KeyOf< K, N >, { pending: boolean } >;
45
61
  }
46
62
 
47
63
  interface EntityConfig {
48
- name: string;
49
- kind: string;
64
+ name: Name;
65
+ kind: Kind;
50
66
  }
51
67
 
52
68
  interface UndoState extends Array< Object > {
@@ -55,11 +71,11 @@ interface UndoState extends Array< Object > {
55
71
  }
56
72
 
57
73
  interface UserState {
58
- queries: Record< string, RecordKey[] >;
59
- byId: Record< RecordKey, User< 'edit' > >;
74
+ queries: Record< string, GenericRecordKey[] >;
75
+ byId: Record< GenericRecordKey, User< 'edit' > >;
60
76
  }
61
77
 
62
- type RecordKey = number | string;
78
+ type GenericRecordKey = number | string;
63
79
  type EntityRecord = any;
64
80
  type Optional< T > = T | undefined;
65
81
 
@@ -68,15 +84,18 @@ type Optional< T > = T | undefined;
68
84
  */
69
85
  export type EntityQuery<
70
86
  C extends Context,
71
- Fields extends string[] | undefined = undefined
72
- > = Record< string, any > & {
87
+ WithFields extends boolean = true
88
+ > = Omit< Record< string, any >, '_fields' > & {
73
89
  context?: C;
74
- /**
75
- * The requested fields. If specified, the REST API will remove from the response
76
- * any fields not on that list.
77
- */
78
- _fields?: Fields;
79
- };
90
+ } & ( WithFields extends true
91
+ ? {
92
+ /**
93
+ * The requested fields. If specified, the REST API will remove from the response
94
+ * any fields not on that list.
95
+ */
96
+ _fields: string[];
97
+ }
98
+ : {} );
80
99
 
81
100
  /**
82
101
  * Shared reference to an empty object for cases where it is important to avoid
@@ -97,9 +116,12 @@ const EMPTY_OBJECT = {};
97
116
  * @return Whether a request is in progress for an embed preview.
98
117
  */
99
118
  export const isRequestingEmbedPreview = createRegistrySelector(
100
- ( select ) => ( state: State, url: string ): boolean => {
101
- return select( STORE_NAME ).isResolving( 'getEmbedPreview', [ url ] );
102
- }
119
+ ( select ) =>
120
+ ( state: State, url: string ): boolean => {
121
+ return select( STORE_NAME ).isResolving( 'getEmbedPreview', [
122
+ url,
123
+ ] );
124
+ }
103
125
  );
104
126
 
105
127
  /**
@@ -168,7 +190,7 @@ export const getUserQueryResults = createSelector(
168
190
  *
169
191
  * @return Array of entities with config matching kind.
170
192
  */
171
- export function getEntitiesByKind( state: State, kind: string ): Array< any > {
193
+ export function getEntitiesByKind( state: State, kind: Kind ): Array< any > {
172
194
  deprecated( "wp.data.select( 'core' ).getEntitiesByKind()", {
173
195
  since: '6.0',
174
196
  alternative: "wp.data.select( 'core' ).getEntitiesConfig()",
@@ -184,7 +206,7 @@ export function getEntitiesByKind( state: State, kind: string ): Array< any > {
184
206
  *
185
207
  * @return Array of entities with config matching kind.
186
208
  */
187
- export function getEntitiesConfig( state: State, kind: string ): Array< any > {
209
+ export function getEntitiesConfig( state: State, kind: Kind ): Array< any > {
188
210
  return filter( state.entities.config, { kind } );
189
211
  }
190
212
 
@@ -198,7 +220,7 @@ export function getEntitiesConfig( state: State, kind: string ): Array< any > {
198
220
  *
199
221
  * @return Entity config
200
222
  */
201
- export function getEntity( state: State, kind: string, name: string ): any {
223
+ export function getEntity( state: State, kind: Kind, name: Name ): any {
202
224
  deprecated( "wp.data.select( 'core' ).getEntity()", {
203
225
  since: '6.0',
204
226
  alternative: "wp.data.select( 'core' ).getEntityConfig()",
@@ -215,14 +237,56 @@ export function getEntity( state: State, kind: string, name: string ): any {
215
237
  *
216
238
  * @return Entity config
217
239
  */
218
- export function getEntityConfig(
219
- state: State,
220
- kind: string,
221
- name: string
222
- ): any {
240
+ export function getEntityConfig( state: State, kind: Kind, name: Name ): any {
223
241
  return find( state.entities.config, { kind, name } );
224
242
  }
225
243
 
244
+ /**
245
+ * GetEntityRecord is declared as an *interface*, but it actually describes
246
+ * the specifies the getEntityRecord *function* signature. It may seem unusual,
247
+ * but it's just how TypeScript implements function overloading.
248
+ *
249
+ * More accurately, GetEntityRecord distinguishes between two different signatures
250
+ * the getEntityRecord selector has:
251
+ *
252
+ * 1. When query._fields is not given, the returned type is EntityRecordOf< K, N, C >
253
+ * 2. When query._fields is given, the returned type is Partial<EntityRecordOf< K, N, C >>
254
+ *
255
+ * Unfortunately, due to a TypeScript limitation (https://github.com/microsoft/TypeScript/issues/23132)
256
+ * we can't use a single function signature with a return type such as:
257
+ *
258
+ * Fields extends undefined
259
+ * ? EntityRecordOf< K, N, C >
260
+ * : Partial< EntityRecordOf< K, N, C > >
261
+ */
262
+ interface GetEntityRecord {
263
+ <
264
+ R extends EntityRecordOf< K, N >,
265
+ C extends Context = DefaultContextOf< R >,
266
+ K extends Kind = KindOf< R >,
267
+ N extends Name = NameOf< R >
268
+ >(
269
+ state: State,
270
+ kind: K,
271
+ name: N,
272
+ key: KeyOf< K, N >,
273
+ query: EntityQuery< C, true >
274
+ ): Partial< EntityRecordOf< K, N, C > > | null | undefined;
275
+
276
+ <
277
+ R extends EntityRecordOf< K, N >,
278
+ C extends Context = DefaultContextOf< R >,
279
+ K extends Kind = KindOf< R >,
280
+ N extends Name = NameOf< R >
281
+ >(
282
+ state: State,
283
+ kind: K,
284
+ name: N,
285
+ key: KeyOf< K, N >,
286
+ query?: EntityQuery< C, false >
287
+ ): EntityRecordOf< K, N, C > | null | undefined;
288
+ }
289
+
226
290
  /**
227
291
  * Returns the Entity's record object by key. Returns `null` if the value is not
228
292
  * yet received, undefined if the value entity is known to not exist, or the
@@ -236,14 +300,19 @@ export function getEntityConfig(
236
300
  *
237
301
  * @return Record.
238
302
  */
239
- export const getEntityRecord = createSelector(
240
- (
303
+ export const getEntityRecord: GetEntityRecord = createSelector(
304
+ <
305
+ R extends EntityRecordOf< K, N >,
306
+ C extends Context = DefaultContextOf< R >,
307
+ K extends Kind = KindOf< R >,
308
+ N extends Name = NameOf< R >
309
+ >(
241
310
  state: State,
242
- kind: string,
243
- name: string,
244
- key: RecordKey,
245
- query?: EntityQuery< any >
246
- ): EntityRecord | undefined => {
311
+ kind: K,
312
+ name: N,
313
+ key: KeyOf< R >,
314
+ query
315
+ ) => {
247
316
  const queriedState = get( state.entities.records, [
248
317
  kind,
249
318
  name,
@@ -277,13 +346,7 @@ export const getEntityRecord = createSelector(
277
346
 
278
347
  return item;
279
348
  },
280
- (
281
- state: State,
282
- kind: string,
283
- name: string,
284
- recordId: RecordKey,
285
- query?: EntityQuery< any >
286
- ) => {
349
+ ( state: State, kind, name, recordId, query ) => {
287
350
  const context = query?.context ?? 'default';
288
351
  return [
289
352
  get( state.entities.records, [
@@ -316,12 +379,10 @@ export const getEntityRecord = createSelector(
316
379
  *
317
380
  * @return Record.
318
381
  */
319
- export function __experimentalGetEntityRecordNoResolver(
320
- state: State,
321
- kind: string,
322
- name: string,
323
- key: RecordKey
324
- ): EntityRecord | null {
382
+ export function __experimentalGetEntityRecordNoResolver<
383
+ K extends Kind,
384
+ N extends Name
385
+ >( state: State, kind: K, name: N, key: KeyOf< K, N > ) {
325
386
  return getEntityRecord( state, kind, name, key );
326
387
  }
327
388
 
@@ -337,11 +398,11 @@ export function __experimentalGetEntityRecordNoResolver(
337
398
  * @return Object with the entity's raw attributes.
338
399
  */
339
400
  export const getRawEntityRecord = createSelector(
340
- (
401
+ < K extends Kind, N extends Name >(
341
402
  state: State,
342
- kind: string,
343
- name: string,
344
- key: RecordKey
403
+ kind: K,
404
+ name: N,
405
+ key: KeyOf< K, N >
345
406
  ): EntityRecord | undefined => {
346
407
  const record = getEntityRecord( state, kind, name, key );
347
408
  return (
@@ -367,9 +428,9 @@ export const getRawEntityRecord = createSelector(
367
428
  },
368
429
  (
369
430
  state: State,
370
- kind: string,
371
- name: string,
372
- recordId: RecordKey,
431
+ kind: Kind,
432
+ name: Name,
433
+ recordId: GenericRecordKey,
373
434
  query?: EntityQuery< any >
374
435
  ) => {
375
436
  const context = query?.context ?? 'default';
@@ -406,15 +467,59 @@ export const getRawEntityRecord = createSelector(
406
467
  *
407
468
  * @return Whether entity records have been received.
408
469
  */
409
- export function hasEntityRecords(
410
- state: State,
411
- kind: string,
412
- name: string,
413
- query?: EntityQuery< any >
414
- ): boolean {
470
+ export function hasEntityRecords<
471
+ R extends EntityRecordOf< K, N >,
472
+ C extends Context = DefaultContextOf< R >,
473
+ K extends Kind = KindOf< R >,
474
+ N extends Name = NameOf< R >
475
+ >( state: State, kind: K, name: N, query?: EntityQuery< C > ): boolean {
415
476
  return Array.isArray( getEntityRecords( state, kind, name, query ) );
416
477
  }
417
478
 
479
+ /**
480
+ * GetEntityRecord is declared as an *interface*, but it actually describes
481
+ * the specifies the getEntityRecord *function* signature. It may seem unusual,
482
+ * but it's just how TypeScript implements function overloading.
483
+ *
484
+ * More accurately, GetEntityRecord distinguishes between two different signatures
485
+ * the getEntityRecord selector has:
486
+ *
487
+ * 1. When query._fields is not given, the returned type is EntityRecordOf< K, N, C >[]
488
+ * 2. When query._fields is given, the returned type is Partial<EntityRecordOf< K, N, C >>[]
489
+ *
490
+ * Unfortunately, due to a TypeScript limitation (https://github.com/microsoft/TypeScript/issues/23132)
491
+ * we can't use a single function signature with a return type such as:
492
+ *
493
+ * Fields extends undefined
494
+ * ? EntityRecordOf< K, N, C >[]
495
+ * : Partial< EntityRecordOf< K, N, C > >[]
496
+ */
497
+ interface GetEntityRecords {
498
+ <
499
+ R extends EntityRecordOf< K, N >,
500
+ C extends Context = DefaultContextOf< R >,
501
+ K extends Kind = KindOf< R >,
502
+ N extends Name = NameOf< R >
503
+ >(
504
+ state: State,
505
+ kind: K,
506
+ name: N,
507
+ query: EntityQuery< C, true >
508
+ ): Partial< EntityRecordOf< K, N, C > >[] | null | undefined;
509
+
510
+ <
511
+ R extends EntityRecordOf< K, N >,
512
+ C extends Context = DefaultContextOf< R >,
513
+ K extends Kind = KindOf< R >,
514
+ N extends Name = NameOf< R >
515
+ >(
516
+ state: State,
517
+ kind: K,
518
+ name: N,
519
+ query?: EntityQuery< C, false >
520
+ ): EntityRecordOf< K, N, C >[] | null | undefined;
521
+ }
522
+
418
523
  /**
419
524
  * Returns the Entity's records.
420
525
  *
@@ -425,12 +530,17 @@ export function hasEntityRecords(
425
530
  *
426
531
  * @return Records.
427
532
  */
428
- export function getEntityRecords(
533
+ export const getEntityRecords: GetEntityRecords = <
534
+ R extends EntityRecordOf< K, N >,
535
+ C extends Context = DefaultContextOf< R >,
536
+ K extends Kind = KindOf< R >,
537
+ N extends Name = NameOf< R >
538
+ >(
429
539
  state: State,
430
- kind: string,
431
- name: string,
432
- query?: EntityQuery< any >
433
- ): Array< EntityRecord > | undefined {
540
+ kind: K,
541
+ name: N,
542
+ query
543
+ ) => {
434
544
  // Queried data state is prepopulated for all known entities. If this is not
435
545
  // assigned for the given parameters, then it is known to not exist.
436
546
  const queriedState = get( state.entities.records, [
@@ -442,13 +552,13 @@ export function getEntityRecords(
442
552
  return null;
443
553
  }
444
554
  return getQueriedItems( queriedState, query );
445
- }
555
+ };
446
556
 
447
557
  type DirtyEntityRecord = {
448
558
  title: string;
449
- key: RecordKey;
450
- name: string;
451
- kind: string;
559
+ key: GenericRecordKey;
560
+ name: Name;
561
+ kind: Kind;
452
562
  };
453
563
  /**
454
564
  * Returns the list of dirty entity records.
@@ -462,44 +572,65 @@ export const __experimentalGetDirtyEntityRecords = createSelector(
462
572
  const {
463
573
  entities: { records },
464
574
  } = state;
465
- const dirtyRecords = [];
466
- Object.keys( records ).forEach( ( kind ) => {
467
- Object.keys( records[ kind ] ).forEach( ( name ) => {
468
- const primaryKeys = Object.keys(
469
- records[ kind ][ name ].edits
470
- ).filter(
471
- ( primaryKey: RecordKey ) =>
472
- // The entity record must exist (not be deleted),
473
- // and it must have edits.
474
- getEntityRecord( state, kind, name, primaryKey ) &&
475
- hasEditsForEntityRecord( state, kind, name, primaryKey )
476
- );
477
-
478
- if ( primaryKeys.length ) {
479
- const entityConfig = getEntityConfig( state, kind, name );
480
- primaryKeys.forEach( ( primaryKey ) => {
481
- const entityRecord = getEditedEntityRecord(
482
- state,
483
- kind,
484
- name,
485
- primaryKey
575
+ const dirtyRecords: DirtyEntityRecord[] = [];
576
+ ( Object.keys( records ) as Kind[] ).forEach(
577
+ < K extends Kind >( kind: K ) => {
578
+ ( Object.keys( records[ kind ] ) as Name[] ).forEach(
579
+ < N extends Name >( name: N ) => {
580
+ const primaryKeys = (
581
+ Object.keys(
582
+ records[ kind ][ name ].edits
583
+ ) as KeyOf< K, N >[]
584
+ ).filter(
585
+ ( primaryKey ) =>
586
+ // The entity record must exist (not be deleted),
587
+ // and it must have edits.
588
+ getEntityRecord(
589
+ state,
590
+ kind,
591
+ name,
592
+ primaryKey
593
+ ) &&
594
+ hasEditsForEntityRecord(
595
+ state,
596
+ kind,
597
+ name,
598
+ primaryKey
599
+ )
486
600
  );
487
- dirtyRecords.push( {
488
- // We avoid using primaryKey because it's transformed into a string
489
- // when it's used as an object key.
490
- key:
491
- entityRecord[
492
- entityConfig.key || DEFAULT_ENTITY_KEY
493
- ],
494
- title:
495
- entityConfig?.getTitle?.( entityRecord ) || '',
496
- name,
497
- kind,
498
- } );
499
- } );
500
- }
501
- } );
502
- } );
601
+
602
+ if ( primaryKeys.length ) {
603
+ const entityConfig = getEntityConfig(
604
+ state,
605
+ kind,
606
+ name
607
+ );
608
+ primaryKeys.forEach( ( primaryKey ) => {
609
+ const entityRecord = getEditedEntityRecord(
610
+ state,
611
+ kind,
612
+ name,
613
+ primaryKey
614
+ );
615
+ dirtyRecords.push( {
616
+ // We avoid using primaryKey because it's transformed into a string
617
+ // when it's used as an object key.
618
+ key: entityRecord[
619
+ entityConfig.key || DEFAULT_ENTITY_KEY
620
+ ],
621
+ title:
622
+ entityConfig?.getTitle?.(
623
+ entityRecord
624
+ ) || '',
625
+ name,
626
+ kind,
627
+ } );
628
+ } );
629
+ }
630
+ }
631
+ );
632
+ }
633
+ );
503
634
 
504
635
  return dirtyRecords;
505
636
  },
@@ -518,40 +649,56 @@ export const __experimentalGetEntitiesBeingSaved = createSelector(
518
649
  const {
519
650
  entities: { records },
520
651
  } = state;
521
- const recordsBeingSaved = [];
522
- Object.keys( records ).forEach( ( kind ) => {
523
- Object.keys( records[ kind ] ).forEach( ( name ) => {
524
- const primaryKeys = Object.keys(
525
- records[ kind ][ name ].saving
526
- ).filter( ( primaryKey ) =>
527
- isSavingEntityRecord( state, kind, name, primaryKey )
528
- );
529
-
530
- if ( primaryKeys.length ) {
531
- const entityConfig = getEntityConfig( state, kind, name );
532
- primaryKeys.forEach( ( primaryKey ) => {
533
- const entityRecord = getEditedEntityRecord(
534
- state,
535
- kind,
536
- name,
537
- primaryKey
652
+ const recordsBeingSaved: DirtyEntityRecord[] = [];
653
+ ( Object.keys( records ) as Kind[] ).forEach(
654
+ < K extends Kind >( kind: K ) => {
655
+ ( Object.keys( records[ kind ] ) as Name[] ).forEach(
656
+ < N extends Name >( name: N ) => {
657
+ const primaryKeys = (
658
+ Object.keys(
659
+ records[ kind ][ name ].saving
660
+ ) as KeyOf< K, N >[]
661
+ ).filter( ( primaryKey ) =>
662
+ isSavingEntityRecord(
663
+ state,
664
+ kind,
665
+ name,
666
+ primaryKey
667
+ )
538
668
  );
539
- recordsBeingSaved.push( {
540
- // We avoid using primaryKey because it's transformed into a string
541
- // when it's used as an object key.
542
- key:
543
- entityRecord[
544
- entityConfig.key || DEFAULT_ENTITY_KEY
545
- ],
546
- title:
547
- entityConfig?.getTitle?.( entityRecord ) || '',
548
- name,
549
- kind,
550
- } );
551
- } );
552
- }
553
- } );
554
- } );
669
+
670
+ if ( primaryKeys.length ) {
671
+ const entityConfig = getEntityConfig(
672
+ state,
673
+ kind,
674
+ name
675
+ );
676
+ primaryKeys.forEach( ( primaryKey ) => {
677
+ const entityRecord = getEditedEntityRecord(
678
+ state,
679
+ kind,
680
+ name,
681
+ primaryKey
682
+ );
683
+ recordsBeingSaved.push( {
684
+ // We avoid using primaryKey because it's transformed into a string
685
+ // when it's used as an object key.
686
+ key: entityRecord[
687
+ entityConfig.key || DEFAULT_ENTITY_KEY
688
+ ],
689
+ title:
690
+ entityConfig?.getTitle?.(
691
+ entityRecord
692
+ ) || '',
693
+ name,
694
+ kind,
695
+ } );
696
+ } );
697
+ }
698
+ }
699
+ );
700
+ }
701
+ );
555
702
  return recordsBeingSaved;
556
703
  },
557
704
  ( state ) => [ state.entities.records ]
@@ -567,13 +714,18 @@ export const __experimentalGetEntitiesBeingSaved = createSelector(
567
714
  *
568
715
  * @return The entity record's edits.
569
716
  */
570
- export function getEntityRecordEdits(
717
+ export function getEntityRecordEdits< K extends Kind, N extends Name >(
571
718
  state: State,
572
- kind: string,
573
- name: string,
574
- recordId: RecordKey
719
+ kind: K,
720
+ name: N,
721
+ recordId: KeyOf< K, N >
575
722
  ): Optional< any > {
576
- return get( state.entities.records, [ kind, name, 'edits', recordId ] );
723
+ return get( state.entities.records, [
724
+ kind,
725
+ name,
726
+ 'edits',
727
+ recordId as string | number,
728
+ ] );
577
729
  }
578
730
 
579
731
  /**
@@ -591,11 +743,11 @@ export function getEntityRecordEdits(
591
743
  * @return The entity record's non transient edits.
592
744
  */
593
745
  export const getEntityRecordNonTransientEdits = createSelector(
594
- (
746
+ < K extends Kind, N extends Name >(
595
747
  state: State,
596
- kind: string,
597
- name: string,
598
- recordId: RecordKey
748
+ kind: K,
749
+ name: N,
750
+ recordId: KeyOf< K, N >
599
751
  ): Optional< any > => {
600
752
  const { transientEdits } = getEntityConfig( state, kind, name ) || {};
601
753
  const edits = getEntityRecordEdits( state, kind, name, recordId ) || {};
@@ -609,7 +761,7 @@ export const getEntityRecordNonTransientEdits = createSelector(
609
761
  return acc;
610
762
  }, {} );
611
763
  },
612
- ( state: State, kind: string, name: string, recordId: RecordKey ) => [
764
+ ( state: State, kind: Kind, name: Name, recordId: GenericRecordKey ) => [
613
765
  state.entities.config,
614
766
  get( state.entities.records, [ kind, name, 'edits', recordId ] ),
615
767
  ]
@@ -626,11 +778,11 @@ export const getEntityRecordNonTransientEdits = createSelector(
626
778
  *
627
779
  * @return Whether the entity record has edits or not.
628
780
  */
629
- export function hasEditsForEntityRecord(
781
+ export function hasEditsForEntityRecord< K extends Kind, N extends Name >(
630
782
  state: State,
631
- kind: string,
632
- name: string,
633
- recordId: RecordKey
783
+ kind: K,
784
+ name: N,
785
+ recordId: KeyOf< K, N >
634
786
  ): boolean {
635
787
  return (
636
788
  isSavingEntityRecord( state, kind, name, recordId ) ||
@@ -651,20 +803,20 @@ export function hasEditsForEntityRecord(
651
803
  * @return The entity record, merged with its edits.
652
804
  */
653
805
  export const getEditedEntityRecord = createSelector(
654
- (
806
+ < K extends Kind, N extends Name >(
655
807
  state: State,
656
- kind: string,
657
- name: string,
658
- recordId: RecordKey
808
+ kind: K,
809
+ name: N,
810
+ recordId: KeyOf< K, N >
659
811
  ): EntityRecord | undefined => ( {
660
812
  ...getRawEntityRecord( state, kind, name, recordId ),
661
813
  ...getEntityRecordEdits( state, kind, name, recordId ),
662
814
  } ),
663
815
  (
664
816
  state: State,
665
- kind: string,
666
- name: string,
667
- recordId: RecordKey,
817
+ kind: Kind,
818
+ name: Name,
819
+ recordId: GenericRecordKey,
668
820
  query?: EntityQuery< any >
669
821
  ) => {
670
822
  const context = query?.context ?? 'default';
@@ -703,9 +855,9 @@ export const getEditedEntityRecord = createSelector(
703
855
  */
704
856
  export function isAutosavingEntityRecord(
705
857
  state: State,
706
- kind: string,
707
- name: string,
708
- recordId: RecordKey
858
+ kind: Kind,
859
+ name: Name,
860
+ recordId: GenericRecordKey
709
861
  ): boolean {
710
862
  const { pending, isAutosave } = get(
711
863
  state.entities.records,
@@ -725,15 +877,15 @@ export function isAutosavingEntityRecord(
725
877
  *
726
878
  * @return Whether the entity record is saving or not.
727
879
  */
728
- export function isSavingEntityRecord(
880
+ export function isSavingEntityRecord< K extends Kind, N extends Name >(
729
881
  state: State,
730
- kind: string,
731
- name: string,
732
- recordId: RecordKey
882
+ kind: K,
883
+ name: N,
884
+ recordId: KeyOf< K, N >
733
885
  ): boolean {
734
886
  return get(
735
887
  state.entities.records,
736
- [ kind, name, 'saving', recordId, 'pending' ],
888
+ [ kind, name, 'saving', recordId as GenericRecordKey, 'pending' ],
737
889
  false
738
890
  );
739
891
  }
@@ -750,9 +902,9 @@ export function isSavingEntityRecord(
750
902
  */
751
903
  export function isDeletingEntityRecord(
752
904
  state: State,
753
- kind: string,
754
- name: string,
755
- recordId: RecordKey
905
+ kind: Kind,
906
+ name: Name,
907
+ recordId: GenericRecordKey
756
908
  ): boolean {
757
909
  return get(
758
910
  state.entities.records,
@@ -773,9 +925,9 @@ export function isDeletingEntityRecord(
773
925
  */
774
926
  export function getLastEntitySaveError(
775
927
  state: State,
776
- kind: string,
777
- name: string,
778
- recordId: RecordKey
928
+ kind: Kind,
929
+ name: Name,
930
+ recordId: GenericRecordKey
779
931
  ): any {
780
932
  return get( state.entities.records, [
781
933
  kind,
@@ -798,9 +950,9 @@ export function getLastEntitySaveError(
798
950
  */
799
951
  export function getLastEntityDeleteError(
800
952
  state: State,
801
- kind: string,
802
- name: string,
803
- recordId: RecordKey
953
+ kind: Kind,
954
+ name: Name,
955
+ recordId: GenericRecordKey
804
956
  ): any {
805
957
  return get( state.entities.records, [
806
958
  kind,
@@ -961,7 +1113,7 @@ export function canUser(
961
1113
  state: State,
962
1114
  action: string,
963
1115
  resource: string,
964
- id?: RecordKey
1116
+ id?: GenericRecordKey
965
1117
  ): boolean | undefined {
966
1118
  const key = compact( [ action, resource, id ] ).join( '/' );
967
1119
  return get( state, [ 'userPermissions', key ] );
@@ -984,9 +1136,9 @@ export function canUser(
984
1136
  */
985
1137
  export function canUserEditEntityRecord(
986
1138
  state: State,
987
- kind: string,
988
- name: string,
989
- recordId: RecordKey
1139
+ kind: Kind,
1140
+ name: Name,
1141
+ recordId: GenericRecordKey
990
1142
  ): boolean | undefined {
991
1143
  const entityConfig = getEntityConfig( state, kind, name );
992
1144
  if ( ! entityConfig ) {
@@ -1012,7 +1164,7 @@ export function canUserEditEntityRecord(
1012
1164
  export function getAutosaves(
1013
1165
  state: State,
1014
1166
  postType: string,
1015
- postId: RecordKey
1167
+ postId: GenericRecordKey
1016
1168
  ): Array< any > | undefined {
1017
1169
  return state.autosaves[ postId ];
1018
1170
  }
@@ -1030,8 +1182,8 @@ export function getAutosaves(
1030
1182
  export function getAutosave(
1031
1183
  state: State,
1032
1184
  postType: string,
1033
- postId: RecordKey,
1034
- authorId: RecordKey
1185
+ postId: GenericRecordKey,
1186
+ authorId: GenericRecordKey
1035
1187
  ): EntityRecord | undefined {
1036
1188
  if ( authorId === undefined ) {
1037
1189
  return;
@@ -1051,16 +1203,17 @@ export function getAutosave(
1051
1203
  * @return True if the REST request was completed. False otherwise.
1052
1204
  */
1053
1205
  export const hasFetchedAutosaves = createRegistrySelector(
1054
- ( select ) => (
1055
- state: State,
1056
- postType: string,
1057
- postId: RecordKey
1058
- ): boolean => {
1059
- return select( STORE_NAME ).hasFinishedResolution( 'getAutosaves', [
1060
- postType,
1061
- postId,
1062
- ] );
1063
- }
1206
+ ( select ) =>
1207
+ (
1208
+ state: State,
1209
+ postType: string,
1210
+ postId: GenericRecordKey
1211
+ ): boolean => {
1212
+ return select( STORE_NAME ).hasFinishedResolution( 'getAutosaves', [
1213
+ postType,
1214
+ postId,
1215
+ ] );
1216
+ }
1064
1217
  );
1065
1218
 
1066
1219
  /**