@wordpress/core-data 6.4.0 → 6.6.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 (89) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/build/entities.js +7 -7
  4. package/build/entities.js.map +1 -1
  5. package/build/queried-data/selectors.js +7 -4
  6. package/build/queried-data/selectors.js.map +1 -1
  7. package/build/reducer.js +3 -1
  8. package/build/reducer.js.map +1 -1
  9. package/build/selectors.js +43 -22
  10. package/build/selectors.js.map +1 -1
  11. package/build/utils/index.js +8 -0
  12. package/build/utils/index.js.map +1 -1
  13. package/build/utils/set-nested-value.js +46 -0
  14. package/build/utils/set-nested-value.js.map +1 -0
  15. package/build-module/entities.js +7 -6
  16. package/build-module/entities.js.map +1 -1
  17. package/build-module/queried-data/selectors.js +6 -3
  18. package/build-module/queried-data/selectors.js.map +1 -1
  19. package/build-module/reducer.js +4 -2
  20. package/build-module/reducer.js.map +1 -1
  21. package/build-module/selectors.js +44 -22
  22. package/build-module/selectors.js.map +1 -1
  23. package/build-module/utils/index.js +1 -0
  24. package/build-module/utils/index.js.map +1 -1
  25. package/build-module/utils/set-nested-value.js +39 -0
  26. package/build-module/utils/set-nested-value.js.map +1 -0
  27. package/build-types/entities.d.ts.map +1 -1
  28. package/build-types/entity-types/attachment.d.ts +1 -1
  29. package/build-types/entity-types/attachment.d.ts.map +1 -1
  30. package/build-types/entity-types/comment.d.ts +2 -2
  31. package/build-types/entity-types/comment.d.ts.map +1 -1
  32. package/build-types/entity-types/helpers.d.ts +9 -9
  33. package/build-types/entity-types/helpers.d.ts.map +1 -1
  34. package/build-types/entity-types/index.d.ts +1 -1
  35. package/build-types/entity-types/index.d.ts.map +1 -1
  36. package/build-types/entity-types/menu-location.d.ts +1 -1
  37. package/build-types/entity-types/menu-location.d.ts.map +1 -1
  38. package/build-types/entity-types/nav-menu-item.d.ts +4 -4
  39. package/build-types/entity-types/nav-menu-item.d.ts.map +1 -1
  40. package/build-types/entity-types/nav-menu.d.ts +1 -1
  41. package/build-types/entity-types/nav-menu.d.ts.map +1 -1
  42. package/build-types/entity-types/page.d.ts +1 -1
  43. package/build-types/entity-types/page.d.ts.map +1 -1
  44. package/build-types/entity-types/plugin.d.ts +2 -2
  45. package/build-types/entity-types/plugin.d.ts.map +1 -1
  46. package/build-types/entity-types/post.d.ts +1 -1
  47. package/build-types/entity-types/post.d.ts.map +1 -1
  48. package/build-types/entity-types/settings.d.ts +1 -1
  49. package/build-types/entity-types/settings.d.ts.map +1 -1
  50. package/build-types/entity-types/sidebar.d.ts +2 -2
  51. package/build-types/entity-types/sidebar.d.ts.map +1 -1
  52. package/build-types/entity-types/taxonomy.d.ts +1 -1
  53. package/build-types/entity-types/taxonomy.d.ts.map +1 -1
  54. package/build-types/entity-types/theme.d.ts +1 -1
  55. package/build-types/entity-types/theme.d.ts.map +1 -1
  56. package/build-types/entity-types/type.d.ts +1 -1
  57. package/build-types/entity-types/type.d.ts.map +1 -1
  58. package/build-types/entity-types/user.d.ts +1 -1
  59. package/build-types/entity-types/user.d.ts.map +1 -1
  60. package/build-types/entity-types/widget-type.d.ts +1 -1
  61. package/build-types/entity-types/widget-type.d.ts.map +1 -1
  62. package/build-types/entity-types/widget.d.ts +1 -1
  63. package/build-types/entity-types/widget.d.ts.map +1 -1
  64. package/build-types/entity-types/wp-template-part.d.ts +1 -1
  65. package/build-types/entity-types/wp-template-part.d.ts.map +1 -1
  66. package/build-types/entity-types/wp-template.d.ts +1 -1
  67. package/build-types/entity-types/wp-template.d.ts.map +1 -1
  68. package/build-types/hooks/use-resource-permissions.d.ts +2 -2
  69. package/build-types/hooks/use-resource-permissions.d.ts.map +1 -1
  70. package/build-types/queried-data/get-query-parts.d.ts +1 -1
  71. package/build-types/queried-data/reducer.d.ts +1 -1
  72. package/build-types/queried-data/selectors.d.ts.map +1 -1
  73. package/build-types/reducer.d.ts +1 -1
  74. package/build-types/reducer.d.ts.map +1 -1
  75. package/build-types/selectors.d.ts +19 -6
  76. package/build-types/selectors.d.ts.map +1 -1
  77. package/build-types/utils/index.d.ts +1 -0
  78. package/build-types/utils/set-nested-value.d.ts +18 -0
  79. package/build-types/utils/set-nested-value.d.ts.map +1 -0
  80. package/package.json +12 -12
  81. package/src/entities.js +1 -2
  82. package/src/locks/test/engine.js +0 -2
  83. package/src/queried-data/selectors.js +7 -3
  84. package/src/reducer.js +2 -6
  85. package/src/selectors.ts +63 -104
  86. package/src/utils/index.js +1 -0
  87. package/src/utils/set-nested-value.js +37 -0
  88. package/src/utils/test/set-nested-value.js +72 -0
  89. package/tsconfig.tsbuildinfo +1 -1
package/src/selectors.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  * External dependencies
3
3
  */
4
4
  import createSelector from 'rememo';
5
- import { set, get } from 'lodash';
6
5
 
7
6
  /**
8
7
  * WordPress dependencies
@@ -17,7 +16,11 @@ import deprecated from '@wordpress/deprecated';
17
16
  import { STORE_NAME } from './name';
18
17
  import { getQueriedItems } from './queried-data';
19
18
  import { DEFAULT_ENTITY_KEY } from './entities';
20
- import { getNormalizedCommaSeparable, isRawAttribute } from './utils';
19
+ import {
20
+ getNormalizedCommaSeparable,
21
+ isRawAttribute,
22
+ setNestedValue,
23
+ } from './utils';
21
24
  import type * as ET from './entity-types';
22
25
 
23
26
  // This is an incomplete, high-level approximation of the State type.
@@ -36,6 +39,7 @@ export interface State {
36
39
  themeBaseGlobalStyles: Record< string, Object >;
37
40
  themeGlobalStyleVariations: Record< string, string >;
38
41
  undo: UndoState;
42
+ userPermissions: Record< string, boolean >;
39
43
  users: UserState;
40
44
  }
41
45
 
@@ -46,9 +50,20 @@ interface EntitiesState {
46
50
  records: Record< string, Record< string, EntityState< ET.EntityRecord > > >;
47
51
  }
48
52
 
53
+ interface QueriedData {
54
+ items: Record< ET.Context, Record< number, ET.EntityRecord > >;
55
+ itemIsComplete: Record< ET.Context, Record< number, boolean > >;
56
+ queries: Record< ET.Context, Record< string, Array< number > > >;
57
+ }
58
+
49
59
  interface EntityState< EntityRecord extends ET.EntityRecord > {
50
60
  edits: Record< string, Partial< EntityRecord > >;
51
- saving: Record< string, { pending: boolean } >;
61
+ saving: Record<
62
+ string,
63
+ Partial< { pending: boolean; isAutosave: boolean; error: Error } >
64
+ >;
65
+ deleting: Record< string, Partial< { pending: boolean; error: Error } > >;
66
+ queriedData: QueriedData;
52
67
  }
53
68
 
54
69
  interface EntityConfig {
@@ -298,11 +313,8 @@ export const getEntityRecord = createSelector(
298
313
  key: EntityRecordKey,
299
314
  query?: GetRecordsHttpQuery
300
315
  ): EntityRecord | undefined => {
301
- const queriedState = get( state.entities.records, [
302
- kind,
303
- name,
304
- 'queriedData',
305
- ] );
316
+ const queriedState =
317
+ state.entities.records?.[ kind ]?.[ name ]?.queriedData;
306
318
  if ( ! queriedState ) {
307
319
  return undefined;
308
320
  }
@@ -323,8 +335,11 @@ export const getEntityRecord = createSelector(
323
335
  const fields = getNormalizedCommaSeparable( query._fields ) ?? [];
324
336
  for ( let f = 0; f < fields.length; f++ ) {
325
337
  const field = fields[ f ].split( '.' );
326
- const value = get( item, field );
327
- set( filteredItem, field, value );
338
+ let value = item;
339
+ field.forEach( ( fieldName ) => {
340
+ value = value[ fieldName ];
341
+ } );
342
+ setNestedValue( filteredItem, field, value );
328
343
  }
329
344
  return filteredItem as EntityRecord;
330
345
  }
@@ -334,22 +349,11 @@ export const getEntityRecord = createSelector(
334
349
  ( state: State, kind, name, recordId, query ) => {
335
350
  const context = query?.context ?? 'default';
336
351
  return [
337
- get( state.entities.records, [
338
- kind,
339
- name,
340
- 'queriedData',
341
- 'items',
342
- context,
343
- recordId,
344
- ] ),
345
- get( state.entities.records, [
346
- kind,
347
- name,
348
- 'queriedData',
349
- 'itemIsComplete',
350
- context,
351
- recordId,
352
- ] ),
352
+ state.entities.records?.[ kind ]?.[ name ]?.queriedData?.items[
353
+ context
354
+ ]?.[ recordId ],
355
+ state.entities.records?.[ kind ]?.[ name ]?.queriedData
356
+ ?.itemIsComplete[ context ]?.[ recordId ],
353
357
  ];
354
358
  }
355
359
  ) as GetEntityRecord;
@@ -403,11 +407,7 @@ export const getRawEntityRecord = createSelector(
403
407
  // Because edits are the "raw" attribute values,
404
408
  // we return those from record selectors to make rendering,
405
409
  // comparisons, and joins with edits easier.
406
- accumulator[ _key ] = get(
407
- record[ _key ],
408
- 'raw',
409
- record[ _key ]
410
- );
410
+ accumulator[ _key ] = record[ _key ]?.raw ?? record[ _key ];
411
411
  } else {
412
412
  accumulator[ _key ] = record[ _key ];
413
413
  }
@@ -425,22 +425,11 @@ export const getRawEntityRecord = createSelector(
425
425
  const context = query?.context ?? 'default';
426
426
  return [
427
427
  state.entities.config,
428
- get( state.entities.records, [
429
- kind,
430
- name,
431
- 'queriedData',
432
- 'items',
433
- context,
434
- recordId,
435
- ] ),
436
- get( state.entities.records, [
437
- kind,
438
- name,
439
- 'queriedData',
440
- 'itemIsComplete',
441
- context,
442
- recordId,
443
- ] ),
428
+ state.entities.records?.[ kind ]?.[ name ]?.queriedData?.items[
429
+ context
430
+ ]?.[ recordId ],
431
+ state.entities.records?.[ kind ]?.[ name ]?.queriedData
432
+ ?.itemIsComplete[ context ]?.[ recordId ],
444
433
  ];
445
434
  }
446
435
  );
@@ -519,11 +508,8 @@ export const getEntityRecords = ( <
519
508
  ): EntityRecord[] | null => {
520
509
  // Queried data state is prepopulated for all known entities. If this is not
521
510
  // assigned for the given parameters, then it is known to not exist.
522
- const queriedState = get( state.entities.records, [
523
- kind,
524
- name,
525
- 'queriedData',
526
- ] );
511
+ const queriedState =
512
+ state.entities.records?.[ kind ]?.[ name ]?.queriedData;
527
513
  if ( ! queriedState ) {
528
514
  return null;
529
515
  }
@@ -661,12 +647,9 @@ export function getEntityRecordEdits(
661
647
  name: string,
662
648
  recordId: EntityRecordKey
663
649
  ): Optional< any > {
664
- return get( state.entities.records, [
665
- kind,
666
- name,
667
- 'edits',
668
- recordId as string | number,
669
- ] );
650
+ return state.entities.records?.[ kind ]?.[ name ]?.edits?.[
651
+ recordId as string | number
652
+ ];
670
653
  }
671
654
 
672
655
  /**
@@ -704,7 +687,7 @@ export const getEntityRecordNonTransientEdits = createSelector(
704
687
  },
705
688
  ( state: State, kind: string, name: string, recordId: EntityRecordKey ) => [
706
689
  state.entities.config,
707
- get( state.entities.records, [ kind, name, 'edits', recordId ] ),
690
+ state.entities.records?.[ kind ]?.[ name ]?.edits?.[ recordId ],
708
691
  ]
709
692
  );
710
693
 
@@ -763,23 +746,12 @@ export const getEditedEntityRecord = createSelector(
763
746
  const context = query?.context ?? 'default';
764
747
  return [
765
748
  state.entities.config,
766
- get( state.entities.records, [
767
- kind,
768
- name,
769
- 'queriedData',
770
- 'items',
771
- context,
772
- recordId,
773
- ] ),
774
- get( state.entities.records, [
775
- kind,
776
- name,
777
- 'queriedData',
778
- 'itemIsComplete',
779
- context,
780
- recordId,
781
- ] ),
782
- get( state.entities.records, [ kind, name, 'edits', recordId ] ),
749
+ state.entities.records?.[ kind ]?.[ name ]?.queriedData.items[
750
+ context
751
+ ]?.[ recordId ],
752
+ state.entities.records?.[ kind ]?.[ name ]?.queriedData
753
+ .itemIsComplete[ context ]?.[ recordId ],
754
+ state.entities.records?.[ kind ]?.[ name ]?.edits?.[ recordId ],
783
755
  ];
784
756
  }
785
757
  );
@@ -800,11 +772,8 @@ export function isAutosavingEntityRecord(
800
772
  name: string,
801
773
  recordId: EntityRecordKey
802
774
  ): boolean {
803
- const { pending, isAutosave } = get(
804
- state.entities.records,
805
- [ kind, name, 'saving', recordId ],
806
- {}
807
- );
775
+ const { pending, isAutosave } =
776
+ state.entities.records?.[ kind ]?.[ name ]?.saving?.[ recordId ] ?? {};
808
777
  return Boolean( pending && isAutosave );
809
778
  }
810
779
 
@@ -824,10 +793,10 @@ export function isSavingEntityRecord(
824
793
  name: string,
825
794
  recordId: EntityRecordKey
826
795
  ): boolean {
827
- return get(
828
- state.entities.records,
829
- [ kind, name, 'saving', recordId as EntityRecordKey, 'pending' ],
830
- false
796
+ return (
797
+ state.entities.records?.[ kind ]?.[ name ]?.saving?.[
798
+ recordId as EntityRecordKey
799
+ ]?.pending ?? false
831
800
  );
832
801
  }
833
802
 
@@ -847,10 +816,10 @@ export function isDeletingEntityRecord(
847
816
  name: string,
848
817
  recordId: EntityRecordKey
849
818
  ): boolean {
850
- return get(
851
- state.entities.records,
852
- [ kind, name, 'deleting', recordId, 'pending' ],
853
- false
819
+ return (
820
+ state.entities.records?.[ kind ]?.[ name ]?.deleting?.[
821
+ recordId as EntityRecordKey
822
+ ]?.pending ?? false
854
823
  );
855
824
  }
856
825
 
@@ -870,13 +839,8 @@ export function getLastEntitySaveError(
870
839
  name: string,
871
840
  recordId: EntityRecordKey
872
841
  ): any {
873
- return get( state.entities.records, [
874
- kind,
875
- name,
876
- 'saving',
877
- recordId,
878
- 'error',
879
- ] );
842
+ return state.entities.records?.[ kind ]?.[ name ]?.saving?.[ recordId ]
843
+ ?.error;
880
844
  }
881
845
 
882
846
  /**
@@ -895,13 +859,8 @@ export function getLastEntityDeleteError(
895
859
  name: string,
896
860
  recordId: EntityRecordKey
897
861
  ): any {
898
- return get( state.entities.records, [
899
- kind,
900
- name,
901
- 'deleting',
902
- recordId,
903
- 'error',
904
- ] );
862
+ return state.entities.records?.[ kind ]?.[ name ]?.deleting?.[ recordId ]
863
+ ?.error;
905
864
  }
906
865
 
907
866
  /**
@@ -1057,7 +1016,7 @@ export function canUser(
1057
1016
  id?: EntityRecordKey
1058
1017
  ): boolean | undefined {
1059
1018
  const key = [ action, resource, id ].filter( Boolean ).join( '/' );
1060
- return get( state, [ 'userPermissions', key ] );
1019
+ return state.userPermissions[ key ];
1061
1020
  }
1062
1021
 
1063
1022
  /**
@@ -6,3 +6,4 @@ export { default as onSubKey } from './on-sub-key';
6
6
  export { default as replaceAction } from './replace-action';
7
7
  export { default as withWeakMapCache } from './with-weak-map-cache';
8
8
  export { default as isRawAttribute } from './is-raw-attribute';
9
+ export { default as setNestedValue } from './set-nested-value';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Sets the value at path of object.
3
+ * If a portion of path doesn’t exist, it’s created.
4
+ * Arrays are created for missing index properties while objects are created
5
+ * for all other missing properties.
6
+ *
7
+ * This function intentionally mutates the input object.
8
+ *
9
+ * Inspired by _.set().
10
+ *
11
+ * @see https://lodash.com/docs/4.17.15#set
12
+ *
13
+ * @param {Object} object Object to modify
14
+ * @param {Array} path Path of the property to set.
15
+ * @param {*} value Value to set.
16
+ */
17
+ export default function setNestedValue( object, path, value ) {
18
+ if ( ! object || typeof object !== 'object' ) {
19
+ return object;
20
+ }
21
+
22
+ path.reduce( ( acc, key, idx ) => {
23
+ if ( acc[ key ] === undefined ) {
24
+ if ( Number.isInteger( path[ idx + 1 ] ) ) {
25
+ acc[ key ] = [];
26
+ } else {
27
+ acc[ key ] = {};
28
+ }
29
+ }
30
+ if ( idx === path.length - 1 ) {
31
+ acc[ key ] = value;
32
+ }
33
+ return acc[ key ];
34
+ }, object );
35
+
36
+ return object;
37
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import setNestedValue from '../set-nested-value';
5
+
6
+ describe( 'setNestedValue', () => {
7
+ it( 'should return the same object unmodified if path is an empty array', () => {
8
+ const input = { x: 'y' };
9
+ const result = setNestedValue( input, [], 123 );
10
+
11
+ expect( result ).toBe( input );
12
+ expect( result ).toEqual( { x: 'y' } );
13
+ } );
14
+
15
+ it( 'should set values at deep level', () => {
16
+ const input = { x: { y: { z: 123 } } };
17
+ const result = setNestedValue( input, [ 'x', 'y', 'z' ], 456 );
18
+
19
+ expect( result ).toEqual( { x: { y: { z: 456 } } } );
20
+ } );
21
+
22
+ it( 'should create nested objects if necessary', () => {
23
+ const result = setNestedValue( {}, [ 'x', 'y', 'z' ], 123 );
24
+
25
+ expect( result ).toEqual( { x: { y: { z: 123 } } } );
26
+ } );
27
+
28
+ it( 'should create nested arrays when keys are numeric', () => {
29
+ const result = setNestedValue( {}, [ 'x', 0, 'z' ], 123 );
30
+
31
+ expect( result ).toEqual( { x: [ { z: 123 } ] } );
32
+ } );
33
+
34
+ it( 'should also work with arrays', () => {
35
+ const result = setNestedValue( [], [ 0, 1, 2 ], 123 );
36
+
37
+ expect( result ).toEqual( [ [ , [ , , 123 ] ] ] );
38
+ } );
39
+
40
+ it( 'should keep remaining properties unaffected', () => {
41
+ const input = { x: { y: { z: 123, z1: 'z1' }, y1: 'y1' }, x1: 'x1' };
42
+ const result = setNestedValue( input, [ 'x', 'y', 'z' ], 456 );
43
+
44
+ expect( result ).toEqual( {
45
+ x: { y: { z: 456, z1: 'z1' }, y1: 'y1' },
46
+ x1: 'x1',
47
+ } );
48
+ } );
49
+
50
+ it( 'should intentionally mutate the original object', () => {
51
+ const input = { x: 'y' };
52
+ const result = setNestedValue( input, [ 'x' ], 'z' );
53
+
54
+ expect( result ).toBe( input );
55
+ expect( result ).toEqual( { x: 'z' } );
56
+ } );
57
+
58
+ it.each( [
59
+ undefined,
60
+ null,
61
+ 0,
62
+ 5,
63
+ NaN,
64
+ Infinity,
65
+ 'test',
66
+ false,
67
+ true,
68
+ Symbol( 'foo' ),
69
+ ] )( 'should return the original input if it is %s', ( value ) => {
70
+ expect( setNestedValue( value, [ 'x' ], 123 ) ).toBe( value );
71
+ } );
72
+ } );