@wordpress/core-data 7.41.2-next.v.202603161435.0 → 7.43.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 (161) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/build/awareness/post-editor-awareness.cjs +12 -5
  3. package/build/awareness/post-editor-awareness.cjs.map +2 -2
  4. package/build/entities.cjs +30 -5
  5. package/build/entities.cjs.map +2 -2
  6. package/build/hooks/use-post-editor-awareness-state.cjs +1 -1
  7. package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
  8. package/build/private-actions.cjs +10 -0
  9. package/build/private-actions.cjs.map +2 -2
  10. package/build/private-selectors.cjs +20 -3
  11. package/build/private-selectors.cjs.map +2 -2
  12. package/build/queried-data/get-query-parts.cjs +8 -0
  13. package/build/queried-data/get-query-parts.cjs.map +2 -2
  14. package/build/queried-data/reducer.cjs +15 -9
  15. package/build/queried-data/reducer.cjs.map +2 -2
  16. package/build/queried-data/selectors.cjs +10 -2
  17. package/build/queried-data/selectors.cjs.map +2 -2
  18. package/build/reducer.cjs +22 -3
  19. package/build/reducer.cjs.map +2 -2
  20. package/build/resolvers.cjs +16 -8
  21. package/build/resolvers.cjs.map +2 -2
  22. package/build/selectors.cjs +16 -8
  23. package/build/selectors.cjs.map +2 -2
  24. package/build/sync.cjs +3 -0
  25. package/build/sync.cjs.map +2 -2
  26. package/build/types.cjs.map +2 -2
  27. package/build/utils/block-selection-history.cjs +1 -1
  28. package/build/utils/block-selection-history.cjs.map +2 -2
  29. package/build/utils/crdt-blocks.cjs +66 -5
  30. package/build/utils/crdt-blocks.cjs.map +2 -2
  31. package/build/utils/crdt-selection.cjs +4 -1
  32. package/build/utils/crdt-selection.cjs.map +2 -2
  33. package/build/utils/crdt-text.cjs +52 -0
  34. package/build/utils/crdt-text.cjs.map +7 -0
  35. package/build/utils/crdt-user-selections.cjs +1 -1
  36. package/build/utils/crdt-user-selections.cjs.map +2 -2
  37. package/build/utils/crdt-utils.cjs +54 -2
  38. package/build/utils/crdt-utils.cjs.map +2 -2
  39. package/build/utils/crdt.cjs +9 -23
  40. package/build/utils/crdt.cjs.map +2 -2
  41. package/build/utils/index.cjs +0 -3
  42. package/build/utils/index.cjs.map +2 -2
  43. package/build-module/awareness/post-editor-awareness.mjs +12 -5
  44. package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
  45. package/build-module/entities.mjs +30 -5
  46. package/build-module/entities.mjs.map +2 -2
  47. package/build-module/hooks/use-post-editor-awareness-state.mjs +1 -1
  48. package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
  49. package/build-module/private-actions.mjs +9 -0
  50. package/build-module/private-actions.mjs.map +2 -2
  51. package/build-module/private-selectors.mjs +19 -3
  52. package/build-module/private-selectors.mjs.map +2 -2
  53. package/build-module/queried-data/get-query-parts.mjs +8 -0
  54. package/build-module/queried-data/get-query-parts.mjs.map +2 -2
  55. package/build-module/queried-data/reducer.mjs +15 -9
  56. package/build-module/queried-data/reducer.mjs.map +2 -2
  57. package/build-module/queried-data/selectors.mjs +10 -2
  58. package/build-module/queried-data/selectors.mjs.map +2 -2
  59. package/build-module/reducer.mjs +20 -2
  60. package/build-module/reducer.mjs.map +2 -2
  61. package/build-module/resolvers.mjs +14 -7
  62. package/build-module/resolvers.mjs.map +2 -2
  63. package/build-module/selectors.mjs +16 -9
  64. package/build-module/selectors.mjs.map +2 -2
  65. package/build-module/sync.mjs +2 -0
  66. package/build-module/sync.mjs.map +2 -2
  67. package/build-module/types.mjs.map +2 -2
  68. package/build-module/utils/block-selection-history.mjs +5 -2
  69. package/build-module/utils/block-selection-history.mjs.map +2 -2
  70. package/build-module/utils/crdt-blocks.mjs +65 -5
  71. package/build-module/utils/crdt-blocks.mjs.map +2 -2
  72. package/build-module/utils/crdt-selection.mjs +8 -2
  73. package/build-module/utils/crdt-selection.mjs.map +2 -2
  74. package/build-module/utils/crdt-text.mjs +26 -0
  75. package/build-module/utils/crdt-text.mjs.map +7 -0
  76. package/build-module/utils/crdt-user-selections.mjs +2 -2
  77. package/build-module/utils/crdt-user-selections.mjs.map +2 -2
  78. package/build-module/utils/crdt-utils.mjs +51 -1
  79. package/build-module/utils/crdt-utils.mjs.map +2 -2
  80. package/build-module/utils/crdt.mjs +10 -23
  81. package/build-module/utils/crdt.mjs.map +2 -2
  82. package/build-module/utils/index.mjs +8 -10
  83. package/build-module/utils/index.mjs.map +2 -2
  84. package/build-types/awareness/post-editor-awareness.d.ts +2 -2
  85. package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
  86. package/build-types/entities.d.ts.map +1 -1
  87. package/build-types/index.d.ts.map +1 -1
  88. package/build-types/private-actions.d.ts +10 -0
  89. package/build-types/private-actions.d.ts.map +1 -1
  90. package/build-types/private-selectors.d.ts +11 -4
  91. package/build-types/private-selectors.d.ts.map +1 -1
  92. package/build-types/queried-data/get-query-parts.d.ts +13 -14
  93. package/build-types/queried-data/get-query-parts.d.ts.map +1 -1
  94. package/build-types/queried-data/reducer.d.ts +11 -5
  95. package/build-types/queried-data/reducer.d.ts.map +1 -1
  96. package/build-types/queried-data/selectors.d.ts.map +1 -1
  97. package/build-types/reducer.d.ts +11 -0
  98. package/build-types/reducer.d.ts.map +1 -1
  99. package/build-types/resolvers.d.ts +3 -0
  100. package/build-types/resolvers.d.ts.map +1 -1
  101. package/build-types/selectors.d.ts +1 -0
  102. package/build-types/selectors.d.ts.map +1 -1
  103. package/build-types/sync.d.ts +2 -2
  104. package/build-types/sync.d.ts.map +1 -1
  105. package/build-types/types.d.ts +4 -2
  106. package/build-types/types.d.ts.map +1 -1
  107. package/build-types/utils/block-selection-history.d.ts.map +1 -1
  108. package/build-types/utils/crdt-blocks.d.ts +11 -0
  109. package/build-types/utils/crdt-blocks.d.ts.map +1 -1
  110. package/build-types/utils/crdt-selection.d.ts.map +1 -1
  111. package/build-types/utils/crdt-text.d.ts +16 -0
  112. package/build-types/utils/crdt-text.d.ts.map +1 -0
  113. package/build-types/utils/crdt-user-selections.d.ts +1 -2
  114. package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
  115. package/build-types/utils/crdt-utils.d.ts +20 -0
  116. package/build-types/utils/crdt-utils.d.ts.map +1 -1
  117. package/build-types/utils/crdt.d.ts +6 -7
  118. package/build-types/utils/crdt.d.ts.map +1 -1
  119. package/build-types/utils/index.d.ts +0 -1
  120. package/build-types/utils/test/crdt-utils.d.ts +2 -0
  121. package/build-types/utils/test/crdt-utils.d.ts.map +1 -0
  122. package/package.json +18 -18
  123. package/src/awareness/post-editor-awareness.ts +13 -6
  124. package/src/awareness/test/post-editor-awareness.ts +15 -10
  125. package/src/entities.js +36 -5
  126. package/src/hooks/test/use-post-editor-awareness-state.ts +3 -3
  127. package/src/hooks/use-post-editor-awareness-state.ts +1 -1
  128. package/src/private-actions.js +18 -0
  129. package/src/private-selectors.ts +37 -4
  130. package/src/queried-data/get-query-parts.js +14 -7
  131. package/src/queried-data/reducer.js +28 -15
  132. package/src/queried-data/selectors.js +11 -3
  133. package/src/queried-data/test/get-query-parts.js +34 -0
  134. package/src/queried-data/test/reducer.js +78 -8
  135. package/src/queried-data/test/selectors.js +171 -0
  136. package/src/reducer.js +31 -0
  137. package/src/resolvers.js +23 -7
  138. package/src/selectors.ts +20 -19
  139. package/src/sync.ts +2 -0
  140. package/src/test/entities.js +185 -1
  141. package/src/types.ts +8 -2
  142. package/src/utils/block-selection-history.ts +5 -2
  143. package/src/utils/crdt-blocks.ts +115 -5
  144. package/src/utils/crdt-selection.ts +8 -2
  145. package/src/utils/crdt-text.ts +43 -0
  146. package/src/utils/crdt-user-selections.ts +13 -13
  147. package/src/utils/crdt-utils.ts +99 -0
  148. package/src/utils/crdt.ts +18 -30
  149. package/src/utils/index.js +0 -1
  150. package/src/utils/test/crdt-blocks.ts +199 -0
  151. package/src/utils/test/crdt-user-selections.ts +5 -0
  152. package/src/utils/test/crdt-utils.ts +387 -0
  153. package/src/utils/test/crdt.ts +229 -54
  154. package/build/utils/is-raw-attribute.cjs +0 -29
  155. package/build/utils/is-raw-attribute.cjs.map +0 -7
  156. package/build-module/utils/is-raw-attribute.mjs +0 -8
  157. package/build-module/utils/is-raw-attribute.mjs.map +0 -7
  158. package/build-types/utils/is-raw-attribute.d.ts +0 -10
  159. package/build-types/utils/is-raw-attribute.d.ts.map +0 -1
  160. package/src/utils/is-raw-attribute.js +0 -11
  161. package/src/utils/test/is-raw-attribute.js +0 -22
@@ -32,15 +32,23 @@ const queriedItemsCacheByState = new WeakMap();
32
32
  * @return {?Array} Query items.
33
33
  */
34
34
  function getQueriedItemsUncached( state, query ) {
35
- const { stableKey, page, perPage, include, fields, context } =
36
- getQueryParts( query );
35
+ const {
36
+ stableKey,
37
+ page,
38
+ perPage,
39
+ offset: queryOffset,
40
+ include,
41
+ fields,
42
+ context,
43
+ } = getQueryParts( query );
37
44
 
38
45
  const itemIds = state.queries?.[ context ]?.[ stableKey ]?.itemIds;
39
46
  if ( ! itemIds ) {
40
47
  return null;
41
48
  }
42
49
 
43
- const startOffset = perPage === -1 ? 0 : ( page - 1 ) * perPage;
50
+ const startOffset =
51
+ perPage === -1 ? 0 : queryOffset ?? ( page - 1 ) * perPage;
44
52
  const endOffset =
45
53
  perPage === -1
46
54
  ? itemIds.length
@@ -11,6 +11,7 @@ describe( 'getQueryParts', () => {
11
11
  context: 'default',
12
12
  page: 2,
13
13
  perPage: 2,
14
+ offset: null,
14
15
  stableKey: '',
15
16
  fields: null,
16
17
  include: null,
@@ -28,6 +29,7 @@ describe( 'getQueryParts', () => {
28
29
  context: 'default',
29
30
  page: 1,
30
31
  perPage: 10,
32
+ offset: null,
31
33
  stableKey: 'include=1',
32
34
  fields: null,
33
35
  include: [ 1 ],
@@ -43,6 +45,7 @@ describe( 'getQueryParts', () => {
43
45
  context: 'default',
44
46
  page: 1,
45
47
  perPage: 10,
48
+ offset: null,
46
49
  stableKey: '%3F=%26&b=2',
47
50
  fields: null,
48
51
  include: null,
@@ -56,6 +59,7 @@ describe( 'getQueryParts', () => {
56
59
  context: 'default',
57
60
  page: 1,
58
61
  perPage: 10,
62
+ offset: null,
59
63
  stableKey: 'a%5B0%5D=1&a%5B1%5D=2',
60
64
  fields: null,
61
65
  include: null,
@@ -71,6 +75,7 @@ describe( 'getQueryParts', () => {
71
75
  context: 'default',
72
76
  page: 1,
73
77
  perPage: 10,
78
+ offset: null,
74
79
  stableKey: 'b=2',
75
80
  fields: null,
76
81
  include: null,
@@ -84,6 +89,7 @@ describe( 'getQueryParts', () => {
84
89
  context: 'default',
85
90
  page: 1,
86
91
  perPage: -1,
92
+ offset: null,
87
93
  stableKey: 'b=2',
88
94
  fields: null,
89
95
  include: null,
@@ -97,6 +103,7 @@ describe( 'getQueryParts', () => {
97
103
  context: 'default',
98
104
  page: 1,
99
105
  perPage: 10,
106
+ offset: null,
100
107
  stableKey: '_fields=id%2Ctitle',
101
108
  fields: [ 'id', 'title' ],
102
109
  include: null,
@@ -109,10 +116,37 @@ describe( 'getQueryParts', () => {
109
116
  expect( parts ).toEqual( {
110
117
  page: 1,
111
118
  perPage: 10,
119
+ offset: null,
112
120
  stableKey: '',
113
121
  include: null,
114
122
  fields: null,
115
123
  context: 'view',
116
124
  } );
117
125
  } );
126
+
127
+ it( 'extracts offset and excludes it from stableKey', () => {
128
+ const parts = getQueryParts( {
129
+ per_page: 50,
130
+ offset: 100,
131
+ } );
132
+
133
+ expect( parts ).toEqual( {
134
+ context: 'default',
135
+ page: 1,
136
+ perPage: 50,
137
+ offset: 100,
138
+ stableKey: '',
139
+ fields: null,
140
+ include: null,
141
+ } );
142
+ } );
143
+
144
+ it( 'ignores non-numeric offset values', () => {
145
+ const parts = getQueryParts( {
146
+ per_page: 10,
147
+ offset: 'abc',
148
+ } );
149
+
150
+ expect( parts.offset ).toBeNull();
151
+ } );
118
152
  } );
@@ -11,7 +11,10 @@ import { removeItems } from '../actions';
11
11
 
12
12
  describe( 'getMergedItemIds', () => {
13
13
  it( 'should receive a page', () => {
14
- const result = getMergedItemIds( [], [ 4, 5, 6 ], 2, 3 );
14
+ const result = getMergedItemIds( [], [ 4, 5, 6 ], {
15
+ page: 2,
16
+ perPage: 3,
17
+ } );
15
18
 
16
19
  expect( result ).toEqual( [
17
20
  undefined,
@@ -32,46 +35,68 @@ describe( 'getMergedItemIds', () => {
32
35
  5,
33
36
  6,
34
37
  ] );
35
- const result = getMergedItemIds( original, [ 1, 2, 3 ], 1, 3 );
38
+ const result = getMergedItemIds( original, [ 1, 2, 3 ], {
39
+ page: 1,
40
+ perPage: 3,
41
+ } );
36
42
 
37
43
  expect( result ).toEqual( [ 1, 2, 3, 4, 5, 6 ] );
38
44
  } );
39
45
 
40
46
  it( 'should replace with new page', () => {
41
47
  const original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] );
42
- const result = getMergedItemIds( original, [ 'replaced', 5, 6 ], 2, 3 );
48
+ const result = getMergedItemIds( original, [ 'replaced', 5, 6 ], {
49
+ page: 2,
50
+ perPage: 3,
51
+ } );
43
52
 
44
53
  expect( result ).toEqual( [ 1, 2, 3, 'replaced', 5, 6 ] );
45
54
  } );
46
55
 
47
56
  it( 'should append a new partial page', () => {
48
57
  const original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] );
49
- const result = getMergedItemIds( original, [ 7 ], 3, 3 );
58
+ const result = getMergedItemIds( original, [ 7 ], {
59
+ page: 3,
60
+ perPage: 3,
61
+ } );
50
62
 
51
63
  expect( result ).toEqual( [ 1, 2, 3, 4, 5, 6, 7 ] );
52
64
  } );
53
65
 
54
66
  it( 'should return a copy of nextItemIds if it represents all ids (single id removed) (page=1 and perPage=-1)', () => {
55
67
  const original = deepFreeze( [ 1, 2, 3 ] );
56
- const result = getMergedItemIds( original, [ 1, 3 ], 1, -1 );
68
+ const result = getMergedItemIds( original, [ 1, 3 ], {
69
+ page: 1,
70
+ perPage: -1,
71
+ } );
57
72
 
58
73
  expect( result ).toEqual( [ 1, 3 ] );
59
74
  } );
60
75
 
61
76
  it( 'should return a copy of nextItemIds if it represents all ids (single id removed and another one added) (page=1 and perPage=-1)', () => {
62
77
  const original = deepFreeze( [ 1, 2, 3 ] );
63
- const result = getMergedItemIds( original, [ 1, 3, 4 ], 1, -1 );
78
+ const result = getMergedItemIds( original, [ 1, 3, 4 ], {
79
+ page: 1,
80
+ perPage: -1,
81
+ } );
64
82
 
65
83
  expect( result ).toEqual( [ 1, 3, 4 ] );
66
84
  } );
85
+
67
86
  it( 'should update a page properly if less items are provided than previously stored', () => {
68
87
  let original = deepFreeze( [ 1, 2, 3 ] );
69
- let result = getMergedItemIds( original, [ 1, 2 ], 1, 3 );
88
+ let result = getMergedItemIds( original, [ 1, 2 ], {
89
+ page: 1,
90
+ perPage: 3,
91
+ } );
70
92
 
71
93
  expect( result ).toEqual( [ 1, 2 ] );
72
94
 
73
95
  original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] );
74
- result = getMergedItemIds( original, [ 9 ], 2, 2 );
96
+ result = getMergedItemIds( original, [ 9 ], {
97
+ page: 2,
98
+ perPage: 2,
99
+ } );
75
100
 
76
101
  expect( result ).toEqual( [ 1, 2, 9, undefined, 5, 6 ] );
77
102
  } );
@@ -197,6 +222,51 @@ describe( 'reducer', () => {
197
222
  } );
198
223
  } );
199
224
 
225
+ it( 'receives a sparse subset of items at given offsets', () => {
226
+ const original = deepFreeze( {
227
+ items: { default: {} },
228
+ queries: {},
229
+ itemIsComplete: { default: {} },
230
+ } );
231
+ const state = [
232
+ {
233
+ type: 'RECEIVE_ITEMS',
234
+ query: { offset: 1, per_page: 2 },
235
+ items: [
236
+ { id: 2, name: 'def' },
237
+ { id: 3, name: 'ghi' },
238
+ ],
239
+ },
240
+ {
241
+ type: 'RECEIVE_ITEMS',
242
+ query: { offset: 4, per_page: 2 },
243
+ items: [
244
+ { id: 5, name: 'mno' },
245
+ { id: 6, name: 'pqr' },
246
+ ],
247
+ },
248
+ ].reduce( reducer, original );
249
+
250
+ expect( state ).toEqual( {
251
+ items: {
252
+ default: {
253
+ 2: { id: 2, name: 'def' },
254
+ 3: { id: 3, name: 'ghi' },
255
+ 5: { id: 5, name: 'mno' },
256
+ 6: { id: 6, name: 'pqr' },
257
+ },
258
+ },
259
+ itemIsComplete: {
260
+ default: { 2: true, 3: true, 5: true, 6: true },
261
+ },
262
+ queries: {
263
+ default: {
264
+ '': { itemIds: [ undefined, 2, 3, undefined, 5, 6 ] },
265
+ },
266
+ },
267
+ } );
268
+ } );
269
+
200
270
  it( 'deletes an item', () => {
201
271
  const kind = 'root';
202
272
  const name = 'menu';
@@ -2,6 +2,7 @@
2
2
  * Internal dependencies
3
3
  */
4
4
  import { getQueriedItems } from '../selectors';
5
+ import { getMergedItemIds } from '../reducer';
5
6
 
6
7
  describe( 'getQueriedItems', () => {
7
8
  it( 'should return null if requesting but no item IDs', () => {
@@ -264,4 +265,174 @@ describe( 'getQueriedItems', () => {
264
265
  const result = getQueriedItems( state, { per_page: 3 } );
265
266
  expect( result ).toBe( null );
266
267
  } );
268
+
269
+ it( 'should return items for offset-based query on the last partial page', () => {
270
+ // Infinite scroll scenario: 103 total items, perPage=50, and the
271
+ // last batch starts at offset=100. The API returns 3 items (items
272
+ // 101-103). X-WP-Total is 103 (the global count). The selector
273
+ // should recognise this as a complete response since
274
+ // 103 - 100 = 3 expected items.
275
+ const state = {
276
+ items: {
277
+ default: {
278
+ 101: { id: 101 },
279
+ 102: { id: 102 },
280
+ 103: { id: 103 },
281
+ },
282
+ },
283
+ itemIsComplete: {
284
+ default: { 101: true, 102: true, 103: true },
285
+ },
286
+ queries: {
287
+ default: {
288
+ '': {
289
+ itemIds: getMergedItemIds( [], [ 101, 102, 103 ], {
290
+ offset: 100,
291
+ perPage: 50,
292
+ } ),
293
+ meta: { totalItems: 103 },
294
+ },
295
+ },
296
+ },
297
+ };
298
+
299
+ const result = getQueriedItems( state, {
300
+ per_page: 50,
301
+ offset: 100,
302
+ } );
303
+ expect( result ).toEqual( [ { id: 101 }, { id: 102 }, { id: 103 } ] );
304
+ } );
305
+
306
+ it( 'should return null for offset-based query when items are still missing', () => {
307
+ // Offset=50, perPage=50, totalItems=200: the API should return
308
+ // 50 items for this batch but only 2 are stored so far.
309
+ const state = {
310
+ items: {
311
+ default: {
312
+ 51: { id: 51 },
313
+ 52: { id: 52 },
314
+ },
315
+ },
316
+ itemIsComplete: {
317
+ default: { 51: true, 52: true },
318
+ },
319
+ queries: {
320
+ default: {
321
+ '': {
322
+ itemIds: getMergedItemIds( [], [ 51, 52 ], {
323
+ offset: 50,
324
+ perPage: 50,
325
+ } ),
326
+ meta: { totalItems: 200 },
327
+ },
328
+ },
329
+ },
330
+ };
331
+
332
+ const result = getQueriedItems( state, {
333
+ per_page: 50,
334
+ offset: 50,
335
+ } );
336
+ expect( result ).toBe( null );
337
+ } );
338
+
339
+ it( 'should return null for offset query when items are still missing', () => {
340
+ // Query Block scenario: offset=3 with per_page=10. The effective
341
+ // total is totalItems - offset = 47. Only 5 items are stored, so
342
+ // the data is still incomplete.
343
+ const state = {
344
+ items: {
345
+ default: {
346
+ 4: { id: 4 },
347
+ 5: { id: 5 },
348
+ 6: { id: 6 },
349
+ 7: { id: 7 },
350
+ 8: { id: 8 },
351
+ },
352
+ },
353
+ itemIsComplete: {
354
+ default: { 4: true, 5: true, 6: true, 7: true, 8: true },
355
+ },
356
+ queries: {
357
+ default: {
358
+ '': {
359
+ itemIds: getMergedItemIds( [], [ 4, 5, 6, 7, 8 ], {
360
+ offset: 3,
361
+ perPage: 10,
362
+ } ),
363
+ meta: { totalItems: 50 },
364
+ },
365
+ },
366
+ },
367
+ };
368
+
369
+ const result = getQueriedItems( state, {
370
+ per_page: 10,
371
+ offset: 3,
372
+ } );
373
+ expect( result ).toBe( null );
374
+ } );
375
+
376
+ it( 'should treat offset=0 the same as no offset', () => {
377
+ // The Query Block defaults to offset=0. Since
378
+ // effectiveTotal = totalItems - 0 = totalItems, this should
379
+ // behave identically to a query without offset.
380
+ const state = {
381
+ items: {
382
+ default: {
383
+ 1: { id: 1 },
384
+ 2: { id: 2 },
385
+ },
386
+ },
387
+ itemIsComplete: {
388
+ default: { 1: true, 2: true },
389
+ },
390
+ queries: {
391
+ default: {
392
+ '': {
393
+ itemIds: [ 1, 2 ],
394
+ meta: { totalItems: 5 },
395
+ },
396
+ },
397
+ },
398
+ };
399
+
400
+ // 2 items stored, but 5 total exist — should return null.
401
+ const result = getQueriedItems( state, {
402
+ per_page: 3,
403
+ offset: 0,
404
+ } );
405
+ expect( result ).toBe( null );
406
+ } );
407
+
408
+ it( 'should return empty array when offset equals totalItems', () => {
409
+ // Edge case: offset lands exactly at the end (e.g. 84 items,
410
+ // per_page=7, offset=84). The API returns 0 items and that is
411
+ // a complete response — effectiveTotal is 0.
412
+ const state = {
413
+ items: {
414
+ default: {},
415
+ },
416
+ itemIsComplete: {
417
+ default: {},
418
+ },
419
+ queries: {
420
+ default: {
421
+ '': {
422
+ itemIds: getMergedItemIds( [], [], {
423
+ offset: 84,
424
+ perPage: 7,
425
+ } ),
426
+ meta: { totalItems: 84 },
427
+ },
428
+ },
429
+ },
430
+ };
431
+
432
+ const result = getQueriedItems( state, {
433
+ per_page: 7,
434
+ offset: 84,
435
+ } );
436
+ expect( result ).toEqual( [] );
437
+ } );
267
438
  } );
package/src/reducer.js CHANGED
@@ -16,6 +16,7 @@ import { createUndoManager } from '@wordpress/undo-manager';
16
16
  import { ifMatchingAction, replaceAction } from './utils';
17
17
  import { reducer as queriedDataReducer } from './queried-data';
18
18
  import { rootEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities';
19
+ import { ConnectionErrorCode } from './sync';
19
20
 
20
21
  /** @typedef {import('./types').AnyFunction} AnyFunction */
21
22
 
@@ -706,6 +707,35 @@ export function collaborationSupported( state = true, action ) {
706
707
  switch ( action.type ) {
707
708
  case 'SET_COLLABORATION_SUPPORTED':
708
709
  return action.supported;
710
+
711
+ case 'SET_SYNC_CONNECTION_STATUS':
712
+ if (
713
+ ConnectionErrorCode.DOCUMENT_SIZE_LIMIT_EXCEEDED ===
714
+ action.status?.error?.code
715
+ ) {
716
+ return false;
717
+ }
718
+
719
+ return state;
720
+ }
721
+ return state;
722
+ }
723
+
724
+ /**
725
+ * Reducer managing view configs, keyed by `kind/name`.
726
+ *
727
+ * @param {Object} state Current state.
728
+ * @param {Object} action Dispatched action.
729
+ *
730
+ * @return {Object} Updated state.
731
+ */
732
+ export function viewConfigs( state = {}, action ) {
733
+ switch ( action.type ) {
734
+ case 'RECEIVE_VIEW_CONFIG':
735
+ return {
736
+ ...state,
737
+ [ `${ action.kind }/${ action.name }` ]: action.config,
738
+ };
709
739
  }
710
740
  return state;
711
741
  }
@@ -734,4 +764,5 @@ export default combineReducers( {
734
764
  editorAssets,
735
765
  syncConnectionStatuses,
736
766
  collaborationSupported,
767
+ viewConfigs,
737
768
  } );
package/src/resolvers.js CHANGED
@@ -991,11 +991,12 @@ export const getDefaultTemplateId =
991
991
  const id = window?.__experimentalTemplateActivate
992
992
  ? template?.wp_id || template?.id
993
993
  : template?.id;
994
- // Endpoint may return an empty object if no template is found.
995
- if ( id ) {
996
- template.id = id;
997
- registry.batch( () => {
998
- dispatch.receiveDefaultTemplateId( query, id );
994
+
995
+ registry.batch( () => {
996
+ dispatch.receiveDefaultTemplateId( query, id || '' );
997
+ // Endpoint may return an empty object if no template is found.
998
+ if ( id ) {
999
+ template.id = id;
999
1000
  dispatch.receiveEntityRecords(
1000
1001
  'postType',
1001
1002
  template.type,
@@ -1007,8 +1008,8 @@ export const getDefaultTemplateId =
1007
1008
  template.type,
1008
1009
  id,
1009
1010
  ] );
1010
- } );
1011
- }
1011
+ }
1012
+ } );
1012
1013
  };
1013
1014
 
1014
1015
  getDefaultTemplateId.shouldInvalidate = ( action ) => {
@@ -1320,3 +1321,18 @@ export const getEditorAssets =
1320
1321
  } );
1321
1322
  dispatch.receiveEditorAssets( assets );
1322
1323
  };
1324
+
1325
+ /**
1326
+ * Requests view config for a given entity type from the REST API.
1327
+ *
1328
+ * @param {string} kind Entity kind.
1329
+ * @param {string} name Entity name.
1330
+ */
1331
+ export const getViewConfig =
1332
+ ( kind, name ) =>
1333
+ async ( { dispatch } ) => {
1334
+ const config = await apiFetch( {
1335
+ path: addQueryArgs( '/wp/v2/view-config', { kind, name } ),
1336
+ } );
1337
+ dispatch.receiveViewConfig( kind, name, config );
1338
+ };
package/src/selectors.ts CHANGED
@@ -20,7 +20,6 @@ import { DEFAULT_ENTITY_KEY } from './entities';
20
20
  import { getUndoManager } from './private-selectors';
21
21
  import {
22
22
  getNormalizedCommaSeparable,
23
- isRawAttribute,
24
23
  setNestedValue,
25
24
  isNumericID,
26
25
  getUserPermissionCacheKey,
@@ -55,6 +54,7 @@ export interface State {
55
54
  editorAssets: Record< string, any > | null;
56
55
  syncConnectionStatuses?: Record< string, ConnectionStatus >;
57
56
  collaborationSupported: boolean;
57
+ viewConfigs: Record< string, Record< string, any > >;
58
58
  }
59
59
 
60
60
  type EntityRecordKey = string | number;
@@ -523,25 +523,26 @@ export const getRawEntityRecord = createSelector(
523
523
  name,
524
524
  key
525
525
  );
526
- return (
527
- record &&
528
- Object.keys( record ).reduce( ( accumulator, _key ) => {
529
- if (
530
- isRawAttribute( getEntityConfig( state, kind, name ), _key )
531
- ) {
532
- // Because edits are the "raw" attribute values,
533
- // we return those from record selectors to make rendering,
534
- // comparisons, and joins with edits easier.
535
- accumulator[ _key ] =
536
- record[ _key ]?.raw !== undefined
537
- ? record[ _key ]?.raw
538
- : record[ _key ];
539
- } else {
540
- accumulator[ _key ] = record[ _key ];
526
+ const config = getEntityConfig( state, kind, name );
527
+ if ( ! record || ! config?.rawAttributes?.length ) {
528
+ return record;
529
+ }
530
+
531
+ // Because edits are the "raw" attribute values,
532
+ // we return those from record selectors to make rendering,
533
+ // comparisons, and joins with edits easier.
534
+ return Object.fromEntries(
535
+ Object.keys( record ).map( ( _key ) => {
536
+ if ( config.rawAttributes.includes( _key ) ) {
537
+ const rawValue = record[ _key ]?.raw;
538
+ return [
539
+ _key,
540
+ rawValue !== undefined ? rawValue : record[ _key ],
541
+ ];
541
542
  }
542
- return accumulator;
543
- }, {} as any )
544
- );
543
+ return [ _key, record[ _key ] ];
544
+ } )
545
+ ) as EntityRecord;
545
546
  },
546
547
  (
547
548
  state: State,
package/src/sync.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  import { unlock } from './lock-unlock';
13
13
 
14
14
  const {
15
+ ConnectionErrorCode,
15
16
  createSyncManager,
16
17
  Delta,
17
18
  CRDT_DOC_META_PERSISTENCE_KEY,
@@ -22,6 +23,7 @@ const {
22
23
  } = unlock( syncPrivateApis );
23
24
 
24
25
  export {
26
+ ConnectionErrorCode,
25
27
  Delta,
26
28
  CRDT_DOC_META_PERSISTENCE_KEY,
27
29
  CRDT_RECORD_MAP_KEY,