@wordpress/core-data 4.0.1-next.253d9b6e21.0 → 4.0.3

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 (106) hide show
  1. package/README.md +11 -3
  2. package/build/actions.js +124 -117
  3. package/build/actions.js.map +1 -1
  4. package/build/batch/default-processor.js +58 -27
  5. package/build/batch/default-processor.js.map +1 -1
  6. package/build/entities.js +24 -18
  7. package/build/entities.js.map +1 -1
  8. package/build/index.js +9 -17
  9. package/build/index.js.map +1 -1
  10. package/build/locks/actions.js +17 -77
  11. package/build/locks/actions.js.map +1 -1
  12. package/build/locks/engine.js +77 -0
  13. package/build/locks/engine.js.map +1 -0
  14. package/build/locks/reducer.js +1 -5
  15. package/build/locks/reducer.js.map +1 -1
  16. package/build/locks/selectors.js +6 -6
  17. package/build/locks/selectors.js.map +1 -1
  18. package/build/queried-data/get-query-parts.js +9 -4
  19. package/build/queried-data/get-query-parts.js.map +1 -1
  20. package/build/queried-data/selectors.js +3 -9
  21. package/build/queried-data/selectors.js.map +1 -1
  22. package/build/reducer.js +1 -4
  23. package/build/reducer.js.map +1 -1
  24. package/build/resolvers.js +120 -91
  25. package/build/resolvers.js.map +1 -1
  26. package/build/selectors.js +31 -11
  27. package/build/selectors.js.map +1 -1
  28. package/build/utils/if-not-resolved.js +6 -21
  29. package/build/utils/if-not-resolved.js.map +1 -1
  30. package/build/utils/index.js +8 -0
  31. package/build/utils/index.js.map +1 -1
  32. package/build/utils/is-raw-attribute.js +19 -0
  33. package/build/utils/is-raw-attribute.js.map +1 -0
  34. package/build-module/actions.js +106 -107
  35. package/build-module/actions.js.map +1 -1
  36. package/build-module/batch/default-processor.js +57 -27
  37. package/build-module/batch/default-processor.js.map +1 -1
  38. package/build-module/entities.js +19 -14
  39. package/build-module/entities.js.map +1 -1
  40. package/build-module/index.js +10 -14
  41. package/build-module/index.js.map +1 -1
  42. package/build-module/locks/actions.js +14 -68
  43. package/build-module/locks/actions.js.map +1 -1
  44. package/build-module/locks/engine.js +66 -0
  45. package/build-module/locks/engine.js.map +1 -0
  46. package/build-module/locks/reducer.js +1 -2
  47. package/build-module/locks/reducer.js.map +1 -1
  48. package/build-module/locks/selectors.js +4 -4
  49. package/build-module/locks/selectors.js.map +1 -1
  50. package/build-module/queried-data/get-query-parts.js +9 -4
  51. package/build-module/queried-data/get-query-parts.js.map +1 -1
  52. package/build-module/queried-data/selectors.js +3 -9
  53. package/build-module/queried-data/selectors.js.map +1 -1
  54. package/build-module/reducer.js +1 -3
  55. package/build-module/reducer.js.map +1 -1
  56. package/build-module/resolvers.js +94 -74
  57. package/build-module/resolvers.js.map +1 -1
  58. package/build-module/selectors.js +30 -10
  59. package/build-module/selectors.js.map +1 -1
  60. package/build-module/utils/if-not-resolved.js +6 -19
  61. package/build-module/utils/if-not-resolved.js.map +1 -1
  62. package/build-module/utils/index.js +1 -0
  63. package/build-module/utils/index.js.map +1 -1
  64. package/build-module/utils/is-raw-attribute.js +12 -0
  65. package/build-module/utils/is-raw-attribute.js.map +1 -0
  66. package/package.json +11 -12
  67. package/src/actions.js +112 -189
  68. package/src/batch/default-processor.js +57 -26
  69. package/src/batch/test/default-processor.js +53 -26
  70. package/src/entities.js +15 -16
  71. package/src/index.js +7 -10
  72. package/src/locks/actions.js +10 -61
  73. package/src/locks/engine.js +43 -0
  74. package/src/locks/reducer.js +1 -3
  75. package/src/locks/selectors.js +4 -4
  76. package/src/locks/test/engine.js +135 -0
  77. package/src/locks/test/reducer.js +1 -1
  78. package/src/locks/test/selectors.js +105 -124
  79. package/src/queried-data/get-query-parts.js +11 -6
  80. package/src/queried-data/selectors.js +2 -9
  81. package/src/queried-data/test/get-query-parts.js +1 -1
  82. package/src/queried-data/test/selectors.js +1 -0
  83. package/src/reducer.js +0 -2
  84. package/src/resolvers.js +86 -106
  85. package/src/selectors.js +113 -40
  86. package/src/test/actions.js +243 -172
  87. package/src/test/entities.js +40 -26
  88. package/src/test/resolvers.js +270 -223
  89. package/src/test/selectors.js +71 -0
  90. package/src/utils/if-not-resolved.js +8 -26
  91. package/src/utils/index.js +1 -0
  92. package/src/utils/is-raw-attribute.js +11 -0
  93. package/src/utils/test/if-not-resolved.js +28 -27
  94. package/src/utils/test/is-raw-attribute.js +22 -0
  95. package/build/controls.js +0 -44
  96. package/build/controls.js.map +0 -1
  97. package/build/locks/index.js +0 -47
  98. package/build/locks/index.js.map +0 -1
  99. package/build-module/controls.js +0 -31
  100. package/build-module/controls.js.map +0 -1
  101. package/build-module/locks/index.js +0 -4
  102. package/build-module/locks/index.js.map +0 -1
  103. package/src/controls.js +0 -31
  104. package/src/locks/index.js +0 -3
  105. package/src/locks/test/actions.js +0 -307
  106. package/src/test/integration.js +0 -264
package/src/resolvers.js CHANGED
@@ -7,33 +7,14 @@ import { find, includes, get, hasIn, compact, uniq } from 'lodash';
7
7
  * WordPress dependencies
8
8
  */
9
9
  import { addQueryArgs } from '@wordpress/url';
10
- import { controls } from '@wordpress/data';
11
- import { apiFetch } from '@wordpress/data-controls';
12
- /**
13
- * Internal dependencies
14
- */
15
- import { regularFetch } from './controls';
16
- import { STORE_NAME } from './name';
10
+ import apiFetch from '@wordpress/api-fetch';
17
11
 
18
12
  /**
19
13
  * Internal dependencies
20
14
  */
21
- import {
22
- receiveUserQuery,
23
- receiveCurrentTheme,
24
- receiveCurrentUser,
25
- receiveEntityRecords,
26
- receiveThemeSupports,
27
- receiveEmbedPreview,
28
- receiveUserPermission,
29
- receiveAutosaves,
30
- } from './actions';
15
+ import { STORE_NAME } from './name';
31
16
  import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities';
32
17
  import { ifNotResolved, getNormalizedCommaSeparable } from './utils';
33
- import {
34
- __unstableAcquireStoreLock,
35
- __unstableReleaseStoreLock,
36
- } from './locks';
37
18
 
38
19
  /**
39
20
  * Requests authors from the REST API.
@@ -41,22 +22,22 @@ import {
41
22
  * @param {Object|undefined} query Optional object of query parameters to
42
23
  * include with request.
43
24
  */
44
- export function* getAuthors( query ) {
25
+ export const getAuthors = ( query ) => async ( { dispatch } ) => {
45
26
  const path = addQueryArgs(
46
27
  '/wp/v2/users/?who=authors&per_page=100',
47
28
  query
48
29
  );
49
- const users = yield apiFetch( { path } );
50
- yield receiveUserQuery( path, users );
51
- }
30
+ const users = await apiFetch( { path } );
31
+ dispatch.receiveUserQuery( path, users );
32
+ };
52
33
 
53
34
  /**
54
35
  * Requests the current user from the REST API.
55
36
  */
56
- export function* getCurrentUser() {
57
- const currentUser = yield apiFetch( { path: '/wp/v2/users/me' } );
58
- yield receiveCurrentUser( currentUser );
59
- }
37
+ export const getCurrentUser = () => async ( { dispatch } ) => {
38
+ const currentUser = await apiFetch( { path: '/wp/v2/users/me' } );
39
+ dispatch.receiveCurrentUser( currentUser );
40
+ };
60
41
 
61
42
  /**
62
43
  * Requests an entity's record from the REST API.
@@ -67,18 +48,22 @@ export function* getCurrentUser() {
67
48
  * @param {Object|undefined} query Optional object of query parameters to
68
49
  * include with request.
69
50
  */
70
- export function* getEntityRecord( kind, name, key = '', query ) {
71
- const entities = yield getKindEntities( kind );
51
+ export const getEntityRecord = ( kind, name, key = '', query ) => async ( {
52
+ select,
53
+ dispatch,
54
+ } ) => {
55
+ const entities = await dispatch( getKindEntities( kind ) );
72
56
  const entity = find( entities, { kind, name } );
73
- if ( ! entity ) {
57
+ if ( ! entity || entity?.__experimentalNoFetch ) {
74
58
  return;
75
59
  }
76
60
 
77
- const lock = yield* __unstableAcquireStoreLock(
61
+ const lock = await dispatch.__unstableAcquireStoreLock(
78
62
  STORE_NAME,
79
63
  [ 'entities', 'data', kind, name, key ],
80
64
  { exclusive: false }
81
65
  );
66
+
82
67
  try {
83
68
  if ( query !== undefined && query._fields ) {
84
69
  // If requesting specific fields, items and query association to said
@@ -111,27 +96,21 @@ export function* getEntityRecord( kind, name, key = '', query ) {
111
96
  // The resolution cache won't consider query as reusable based on the
112
97
  // fields, so it's tested here, prior to initiating the REST request,
113
98
  // and without causing `getEntityRecords` resolution to occur.
114
- const hasRecords = yield controls.select(
115
- STORE_NAME,
116
- 'hasEntityRecords',
117
- kind,
118
- name,
119
- query
120
- );
99
+ const hasRecords = select.hasEntityRecords( kind, name, query );
121
100
  if ( hasRecords ) {
122
101
  return;
123
102
  }
124
103
  }
125
104
 
126
- const record = yield apiFetch( { path } );
127
- yield receiveEntityRecords( kind, name, record, query );
105
+ const record = await apiFetch( { path } );
106
+ dispatch.receiveEntityRecords( kind, name, record, query );
128
107
  } catch ( error ) {
129
108
  // We need a way to handle and access REST API errors in state
130
109
  // Until then, catching the error ensures the resolver is marked as resolved.
131
110
  } finally {
132
- yield* __unstableReleaseStoreLock( lock );
111
+ dispatch.__unstableReleaseStoreLock( lock );
133
112
  }
134
- }
113
+ };
135
114
 
136
115
  /**
137
116
  * Requests an entity's record from the REST API.
@@ -156,18 +135,21 @@ export const getEditedEntityRecord = ifNotResolved(
156
135
  * @param {string} name Entity name.
157
136
  * @param {Object?} query Query Object.
158
137
  */
159
- export function* getEntityRecords( kind, name, query = {} ) {
160
- const entities = yield getKindEntities( kind );
138
+ export const getEntityRecords = ( kind, name, query = {} ) => async ( {
139
+ dispatch,
140
+ } ) => {
141
+ const entities = await dispatch( getKindEntities( kind ) );
161
142
  const entity = find( entities, { kind, name } );
162
- if ( ! entity ) {
143
+ if ( ! entity || entity?.__experimentalNoFetch ) {
163
144
  return;
164
145
  }
165
146
 
166
- const lock = yield* __unstableAcquireStoreLock(
147
+ const lock = await dispatch.__unstableAcquireStoreLock(
167
148
  STORE_NAME,
168
149
  [ 'entities', 'data', kind, name ],
169
150
  { exclusive: false }
170
151
  );
152
+
171
153
  try {
172
154
  if ( query._fields ) {
173
155
  // If requesting specific fields, items and query association to said
@@ -187,7 +169,7 @@ export function* getEntityRecords( kind, name, query = {} ) {
187
169
  ...query,
188
170
  } );
189
171
 
190
- let records = Object.values( yield apiFetch( { path } ) );
172
+ let records = Object.values( await apiFetch( { path } ) );
191
173
  // If we request fields but the result doesn't contain the fields,
192
174
  // explicitely set these fields as "undefined"
193
175
  // that way we consider the query "fullfilled".
@@ -203,7 +185,8 @@ export function* getEntityRecords( kind, name, query = {} ) {
203
185
  } );
204
186
  }
205
187
 
206
- yield receiveEntityRecords( kind, name, records, query );
188
+ dispatch.receiveEntityRecords( kind, name, records, query );
189
+
207
190
  // When requesting all fields, the list of results can be used to
208
191
  // resolve the `getEntityRecord` selector in addition to `getEntityRecords`.
209
192
  // See https://github.com/WordPress/gutenberg/pull/26575
@@ -213,21 +196,21 @@ export function* getEntityRecords( kind, name, query = {} ) {
213
196
  .filter( ( record ) => record[ key ] )
214
197
  .map( ( record ) => [ kind, name, record[ key ] ] );
215
198
 
216
- yield {
199
+ dispatch( {
217
200
  type: 'START_RESOLUTIONS',
218
201
  selectorName: 'getEntityRecord',
219
202
  args: resolutionsArgs,
220
- };
221
- yield {
203
+ } );
204
+ dispatch( {
222
205
  type: 'FINISH_RESOLUTIONS',
223
206
  selectorName: 'getEntityRecord',
224
207
  args: resolutionsArgs,
225
- };
208
+ } );
226
209
  }
227
210
  } finally {
228
- yield* __unstableReleaseStoreLock( lock );
211
+ dispatch.__unstableReleaseStoreLock( lock );
229
212
  }
230
- }
213
+ };
231
214
 
232
215
  getEntityRecords.shouldInvalidate = ( action, kind, name ) => {
233
216
  return (
@@ -241,39 +224,39 @@ getEntityRecords.shouldInvalidate = ( action, kind, name ) => {
241
224
  /**
242
225
  * Requests the current theme.
243
226
  */
244
- export function* getCurrentTheme() {
245
- const activeThemes = yield apiFetch( {
227
+ export const getCurrentTheme = () => async ( { dispatch } ) => {
228
+ const activeThemes = await apiFetch( {
246
229
  path: '/wp/v2/themes?status=active',
247
230
  } );
248
- yield receiveCurrentTheme( activeThemes[ 0 ] );
249
- }
231
+ dispatch.receiveCurrentTheme( activeThemes[ 0 ] );
232
+ };
250
233
 
251
234
  /**
252
235
  * Requests theme supports data from the index.
253
236
  */
254
- export function* getThemeSupports() {
255
- const activeThemes = yield apiFetch( {
237
+ export const getThemeSupports = () => async ( { dispatch } ) => {
238
+ const activeThemes = await apiFetch( {
256
239
  path: '/wp/v2/themes?status=active',
257
240
  } );
258
- yield receiveThemeSupports( activeThemes[ 0 ].theme_supports );
259
- }
241
+ dispatch.receiveThemeSupports( activeThemes[ 0 ].theme_supports );
242
+ };
260
243
 
261
244
  /**
262
245
  * Requests a preview from the from the Embed API.
263
246
  *
264
247
  * @param {string} url URL to get the preview for.
265
248
  */
266
- export function* getEmbedPreview( url ) {
249
+ export const getEmbedPreview = ( url ) => async ( { dispatch } ) => {
267
250
  try {
268
- const embedProxyResponse = yield apiFetch( {
251
+ const embedProxyResponse = await apiFetch( {
269
252
  path: addQueryArgs( '/oembed/1.0/proxy', { url } ),
270
253
  } );
271
- yield receiveEmbedPreview( url, embedProxyResponse );
254
+ dispatch.receiveEmbedPreview( url, embedProxyResponse );
272
255
  } catch ( error ) {
273
256
  // Embed API 404s if the URL cannot be embedded, so we have to catch the error from the apiRequest here.
274
- yield receiveEmbedPreview( url, false );
257
+ dispatch.receiveEmbedPreview( url, false );
275
258
  }
276
- }
259
+ };
277
260
 
278
261
  /**
279
262
  * Checks whether the current user can perform the given action on the given
@@ -284,7 +267,7 @@ export function* getEmbedPreview( url ) {
284
267
  * @param {string} resource REST resource to check, e.g. 'media' or 'posts'.
285
268
  * @param {?string} id ID of the rest resource to check.
286
269
  */
287
- export function* canUser( action, resource, id ) {
270
+ export const canUser = ( action, resource, id ) => async ( { dispatch } ) => {
288
271
  const methods = {
289
272
  create: 'POST',
290
273
  read: 'GET',
@@ -301,7 +284,7 @@ export function* canUser( action, resource, id ) {
301
284
 
302
285
  let response;
303
286
  try {
304
- response = yield apiFetch( {
287
+ response = await apiFetch( {
305
288
  path,
306
289
  // Ideally this would always be an OPTIONS request, but unfortunately there's
307
290
  // a bug in the REST API which causes the Allow header to not be sent on
@@ -329,8 +312,8 @@ export function* canUser( action, resource, id ) {
329
312
 
330
313
  const key = compact( [ action, resource, id ] ).join( '/' );
331
314
  const isAllowed = includes( allowHeader, method );
332
- yield receiveUserPermission( key, isAllowed );
333
- }
315
+ dispatch.receiveUserPermission( key, isAllowed );
316
+ };
334
317
 
335
318
  /**
336
319
  * Checks whether the current user can perform the given action on the given
@@ -340,16 +323,18 @@ export function* canUser( action, resource, id ) {
340
323
  * @param {string} name Entity name.
341
324
  * @param {string} recordId Record's id.
342
325
  */
343
- export function* canUserEditEntityRecord( kind, name, recordId ) {
344
- const entities = yield getKindEntities( kind );
326
+ export const canUserEditEntityRecord = ( kind, name, recordId ) => async ( {
327
+ dispatch,
328
+ } ) => {
329
+ const entities = await dispatch( getKindEntities( kind ) );
345
330
  const entity = find( entities, { kind, name } );
346
331
  if ( ! entity ) {
347
332
  return;
348
333
  }
349
334
 
350
335
  const resource = entity.__unstable_rest_base;
351
- yield canUser( 'update', resource, recordId );
352
- }
336
+ await dispatch( canUser( 'update', resource, recordId ) );
337
+ };
353
338
 
354
339
  /**
355
340
  * Request autosave data from the REST API.
@@ -357,20 +342,19 @@ export function* canUserEditEntityRecord( kind, name, recordId ) {
357
342
  * @param {string} postType The type of the parent post.
358
343
  * @param {number} postId The id of the parent post.
359
344
  */
360
- export function* getAutosaves( postType, postId ) {
361
- const { rest_base: restBase } = yield controls.resolveSelect(
362
- STORE_NAME,
363
- 'getPostType',
364
- postType
365
- );
366
- const autosaves = yield apiFetch( {
345
+ export const getAutosaves = ( postType, postId ) => async ( {
346
+ dispatch,
347
+ resolveSelect,
348
+ } ) => {
349
+ const { rest_base: restBase } = await resolveSelect.getPostType( postType );
350
+ const autosaves = await apiFetch( {
367
351
  path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit`,
368
352
  } );
369
353
 
370
354
  if ( autosaves && autosaves.length ) {
371
- yield receiveAutosaves( postId, autosaves );
355
+ dispatch.receiveAutosaves( postId, autosaves );
372
356
  }
373
- }
357
+ };
374
358
 
375
359
  /**
376
360
  * Request autosave data from the REST API.
@@ -381,31 +365,30 @@ export function* getAutosaves( postType, postId ) {
381
365
  * @param {string} postType The type of the parent post.
382
366
  * @param {number} postId The id of the parent post.
383
367
  */
384
- export function* getAutosave( postType, postId ) {
385
- yield controls.resolveSelect(
386
- STORE_NAME,
387
- 'getAutosaves',
388
- postType,
389
- postId
390
- );
391
- }
368
+ export const getAutosave = ( postType, postId ) => async ( {
369
+ resolveSelect,
370
+ } ) => {
371
+ await resolveSelect.getAutosaves( postType, postId );
372
+ };
392
373
 
393
374
  /**
394
375
  * Retrieve the frontend template used for a given link.
395
376
  *
396
377
  * @param {string} link Link.
397
378
  */
398
- export function* __experimentalGetTemplateForLink( link ) {
379
+ export const __experimentalGetTemplateForLink = ( link ) => async ( {
380
+ dispatch,
381
+ resolveSelect,
382
+ } ) => {
399
383
  // Ideally this should be using an apiFetch call
400
384
  // We could potentially do so by adding a "filter" to the `wp_template` end point.
401
385
  // Also it seems the returned object is not a regular REST API post type.
402
386
  let template;
403
387
  try {
404
- template = yield regularFetch(
405
- addQueryArgs( link, {
406
- '_wp-find-template': true,
407
- } )
408
- );
388
+ template = await window
389
+ .fetch( addQueryArgs( link, { '_wp-find-template': true } ) )
390
+ .then( ( res ) => res.json() )
391
+ .then( ( { data } ) => data );
409
392
  } catch ( e ) {
410
393
  // For non-FSE themes, it is possible that this request returns an error.
411
394
  }
@@ -414,21 +397,18 @@ export function* __experimentalGetTemplateForLink( link ) {
414
397
  return;
415
398
  }
416
399
 
417
- yield getEntityRecord( 'postType', 'wp_template', template.id );
418
- const record = yield controls.select(
419
- STORE_NAME,
420
- 'getEntityRecord',
400
+ const record = await resolveSelect.getEntityRecord(
421
401
  'postType',
422
402
  'wp_template',
423
403
  template.id
424
404
  );
425
405
 
426
406
  if ( record ) {
427
- yield receiveEntityRecords( 'postType', 'wp_template', [ record ], {
407
+ dispatch.receiveEntityRecords( 'postType', 'wp_template', [ record ], {
428
408
  'find-template': link,
429
409
  } );
430
410
  }
431
- }
411
+ };
432
412
 
433
413
  __experimentalGetTemplateForLink.shouldInvalidate = ( action ) => {
434
414
  return (
package/src/selectors.js CHANGED
@@ -17,7 +17,7 @@ import deprecated from '@wordpress/deprecated';
17
17
  import { STORE_NAME } from './name';
18
18
  import { getQueriedItems } from './queried-data';
19
19
  import { DEFAULT_ENTITY_KEY } from './entities';
20
- import { getNormalizedCommaSeparable } from './utils';
20
+ import { getNormalizedCommaSeparable, isRawAttribute } from './utils';
21
21
 
22
22
  /**
23
23
  * Shared reference to an empty array for cases where it is important to avoid
@@ -134,40 +134,63 @@ export function getEntity( state, kind, name ) {
134
134
  *
135
135
  * @return {Object?} Record.
136
136
  */
137
- export function getEntityRecord( state, kind, name, key, query ) {
138
- const queriedState = get( state.entities.data, [
139
- kind,
140
- name,
141
- 'queriedData',
142
- ] );
143
- if ( ! queriedState ) {
144
- return undefined;
145
- }
146
- const context = query?.context ?? 'default';
147
-
148
- if ( query === undefined ) {
149
- // If expecting a complete item, validate that completeness.
150
- if ( ! queriedState.itemIsComplete[ context ]?.[ key ] ) {
137
+ export const getEntityRecord = createSelector(
138
+ ( state, kind, name, key, query ) => {
139
+ const queriedState = get( state.entities.data, [
140
+ kind,
141
+ name,
142
+ 'queriedData',
143
+ ] );
144
+ if ( ! queriedState ) {
151
145
  return undefined;
152
146
  }
147
+ const context = query?.context ?? 'default';
153
148
 
154
- return queriedState.items[ context ][ key ];
155
- }
149
+ if ( query === undefined ) {
150
+ // If expecting a complete item, validate that completeness.
151
+ if ( ! queriedState.itemIsComplete[ context ]?.[ key ] ) {
152
+ return undefined;
153
+ }
156
154
 
157
- const item = queriedState.items[ context ]?.[ key ];
158
- if ( item && query._fields ) {
159
- const filteredItem = {};
160
- const fields = getNormalizedCommaSeparable( query._fields );
161
- for ( let f = 0; f < fields.length; f++ ) {
162
- const field = fields[ f ].split( '.' );
163
- const value = get( item, field );
164
- set( filteredItem, field, value );
155
+ return queriedState.items[ context ][ key ];
165
156
  }
166
- return filteredItem;
167
- }
168
157
 
169
- return item;
170
- }
158
+ const item = queriedState.items[ context ]?.[ key ];
159
+ if ( item && query._fields ) {
160
+ const filteredItem = {};
161
+ const fields = getNormalizedCommaSeparable( query._fields );
162
+ for ( let f = 0; f < fields.length; f++ ) {
163
+ const field = fields[ f ].split( '.' );
164
+ const value = get( item, field );
165
+ set( filteredItem, field, value );
166
+ }
167
+ return filteredItem;
168
+ }
169
+
170
+ return item;
171
+ },
172
+ ( state, kind, name, recordId, query ) => {
173
+ const context = query?.context ?? 'default';
174
+ return [
175
+ get( state.entities.data, [
176
+ kind,
177
+ name,
178
+ 'queriedData',
179
+ 'items',
180
+ context,
181
+ recordId,
182
+ ] ),
183
+ get( state.entities.data, [
184
+ kind,
185
+ name,
186
+ 'queriedData',
187
+ 'itemIsComplete',
188
+ context,
189
+ recordId,
190
+ ] ),
191
+ ];
192
+ }
193
+ );
171
194
 
172
195
  /**
173
196
  * Returns the Entity's record object by key. Doesn't trigger a resolver nor requests the entity from the API if the entity record isn't available in the local state.
@@ -205,19 +228,44 @@ export const getRawEntityRecord = createSelector(
205
228
  return (
206
229
  record &&
207
230
  Object.keys( record ).reduce( ( accumulator, _key ) => {
208
- // Because edits are the "raw" attribute values,
209
- // we return those from record selectors to make rendering,
210
- // comparisons, and joins with edits easier.
211
- accumulator[ _key ] = get(
212
- record[ _key ],
213
- 'raw',
214
- record[ _key ]
215
- );
231
+ if ( isRawAttribute( getEntity( state, kind, name ), _key ) ) {
232
+ // Because edits are the "raw" attribute values,
233
+ // we return those from record selectors to make rendering,
234
+ // comparisons, and joins with edits easier.
235
+ accumulator[ _key ] = get(
236
+ record[ _key ],
237
+ 'raw',
238
+ record[ _key ]
239
+ );
240
+ } else {
241
+ accumulator[ _key ] = record[ _key ];
242
+ }
216
243
  return accumulator;
217
244
  }, {} )
218
245
  );
219
246
  },
220
- ( state ) => [ state.entities.data ]
247
+ ( state, kind, name, recordId, query ) => {
248
+ const context = query?.context ?? 'default';
249
+ return [
250
+ state.entities.config,
251
+ get( state.entities.data, [
252
+ kind,
253
+ name,
254
+ 'queriedData',
255
+ 'items',
256
+ context,
257
+ recordId,
258
+ ] ),
259
+ get( state.entities.data, [
260
+ kind,
261
+ name,
262
+ 'queriedData',
263
+ 'itemIsComplete',
264
+ context,
265
+ recordId,
266
+ ] ),
267
+ ];
268
+ }
221
269
  );
222
270
 
223
271
  /**
@@ -404,7 +452,10 @@ export const getEntityRecordNonTransientEdits = createSelector(
404
452
  return acc;
405
453
  }, {} );
406
454
  },
407
- ( state ) => [ state.entities.config, state.entities.data ]
455
+ ( state, kind, name, recordId ) => [
456
+ state.entities.config,
457
+ get( state.entities.data, [ kind, name, 'edits', recordId ] ),
458
+ ]
408
459
  );
409
460
 
410
461
  /**
@@ -442,7 +493,29 @@ export const getEditedEntityRecord = createSelector(
442
493
  ...getRawEntityRecord( state, kind, name, recordId ),
443
494
  ...getEntityRecordEdits( state, kind, name, recordId ),
444
495
  } ),
445
- ( state ) => [ state.entities.data ]
496
+ ( state, kind, name, recordId, query ) => {
497
+ const context = query?.context ?? 'default';
498
+ return [
499
+ state.entities.config,
500
+ get( state.entities.data, [
501
+ kind,
502
+ name,
503
+ 'queriedData',
504
+ 'items',
505
+ context,
506
+ recordId,
507
+ ] ),
508
+ get( state.entities.data, [
509
+ kind,
510
+ name,
511
+ 'queriedData',
512
+ 'itemIsComplete',
513
+ context,
514
+ recordId,
515
+ ] ),
516
+ get( state.entities.data, [ kind, name, 'edits', recordId ] ),
517
+ ];
518
+ }
446
519
  );
447
520
 
448
521
  /**