@wordpress/core-data 7.41.2-next.v.202603102151.0 → 7.42.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.
- package/CHANGELOG.md +1 -1
- package/README.md +19 -0
- package/build/actions.cjs +17 -25
- package/build/actions.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +46 -6
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/entities.cjs +33 -7
- package/build/entities.cjs.map +2 -2
- package/build/hooks/use-entity-prop.cjs +2 -1
- package/build/hooks/use-entity-prop.cjs.map +2 -2
- package/build/hooks/use-post-editor-awareness-state.cjs +84 -3
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/index.cjs +3 -0
- package/build/index.cjs.map +2 -2
- package/build/private-apis.cjs +3 -1
- package/build/private-apis.cjs.map +2 -2
- package/build/queried-data/get-query-parts.cjs +7 -0
- package/build/queried-data/get-query-parts.cjs.map +2 -2
- package/build/queried-data/selectors.cjs +19 -5
- package/build/queried-data/selectors.cjs.map +2 -2
- package/build/reducer.cjs +6 -0
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +110 -74
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +29 -0
- package/build/selectors.cjs.map +2 -2
- package/build/sync.cjs +3 -0
- package/build/sync.cjs.map +2 -2
- package/build/types.cjs +16 -0
- package/build/types.cjs.map +3 -3
- package/build/utils/block-selection-history.cjs +1 -1
- package/build/utils/block-selection-history.cjs.map +2 -2
- package/build/utils/crdt-blocks.cjs +17 -3
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-selection.cjs +4 -1
- package/build/utils/crdt-selection.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs +9 -6
- package/build/utils/crdt-user-selections.cjs.map +2 -2
- package/build/utils/crdt-utils.cjs +54 -2
- package/build/utils/crdt-utils.cjs.map +2 -2
- package/build/utils/crdt.cjs +4 -23
- package/build/utils/crdt.cjs.map +2 -2
- package/build/utils/index.cjs +3 -0
- package/build/utils/index.cjs.map +2 -2
- package/build/utils/normalize-query-for-resolution.cjs +35 -0
- package/build/utils/normalize-query-for-resolution.cjs.map +7 -0
- package/build-module/actions.mjs +17 -25
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +46 -6
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/entities.mjs +33 -7
- package/build-module/entities.mjs.map +2 -2
- package/build-module/hooks/use-entity-prop.mjs +2 -1
- package/build-module/hooks/use-entity-prop.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +81 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/index.mjs +2 -0
- package/build-module/index.mjs.map +2 -2
- package/build-module/private-apis.mjs +6 -2
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/queried-data/get-query-parts.mjs +7 -0
- package/build-module/queried-data/get-query-parts.mjs.map +2 -2
- package/build-module/queried-data/selectors.mjs +19 -5
- package/build-module/queried-data/selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +6 -0
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +112 -75
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +28 -0
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/sync.mjs +2 -0
- package/build-module/sync.mjs.map +2 -2
- package/build-module/types.mjs +9 -0
- package/build-module/types.mjs.map +4 -4
- package/build-module/utils/block-selection-history.mjs +5 -2
- package/build-module/utils/block-selection-history.mjs.map +2 -2
- package/build-module/utils/crdt-blocks.mjs +17 -3
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-selection.mjs +8 -2
- package/build-module/utils/crdt-selection.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs +10 -7
- package/build-module/utils/crdt-user-selections.mjs.map +2 -2
- package/build-module/utils/crdt-utils.mjs +51 -1
- package/build-module/utils/crdt-utils.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +4 -23
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-module/utils/index.mjs +2 -0
- package/build-module/utils/index.mjs.map +2 -2
- package/build-module/utils/normalize-query-for-resolution.mjs +14 -0
- package/build-module/utils/normalize-query-for-resolution.mjs.map +7 -0
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts +2 -2
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/awareness/types.d.ts +1 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/entities.d.ts +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/hooks/use-entity-prop.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts +34 -10
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/index.d.ts +2 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/queried-data/get-query-parts.d.ts +7 -0
- package/build-types/queried-data/get-query-parts.d.ts.map +1 -1
- package/build-types/queried-data/selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +2 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +17 -0
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/sync.d.ts +2 -2
- package/build-types/sync.d.ts.map +1 -1
- package/build-types/types.d.ts +18 -1
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/block-selection-history.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt-selection.d.ts.map +1 -1
- package/build-types/utils/crdt-user-selections.d.ts +9 -5
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
- package/build-types/utils/crdt-utils.d.ts +20 -0
- package/build-types/utils/crdt-utils.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts +6 -7
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/index.d.ts +1 -0
- package/build-types/utils/normalize-query-for-resolution.d.ts +12 -0
- package/build-types/utils/normalize-query-for-resolution.d.ts.map +1 -0
- package/build-types/utils/test/crdt-utils.d.ts +2 -0
- package/build-types/utils/test/crdt-utils.d.ts.map +1 -0
- package/package.json +18 -18
- package/src/actions.js +25 -40
- package/src/awareness/post-editor-awareness.ts +106 -7
- package/src/awareness/test/post-editor-awareness.ts +50 -10
- package/src/awareness/types.ts +1 -1
- package/src/entities.js +38 -6
- package/src/hooks/test/use-post-editor-awareness-state.ts +446 -3
- package/src/hooks/use-entity-prop.js +2 -0
- package/src/hooks/use-post-editor-awareness-state.ts +160 -8
- package/src/index.js +1 -0
- package/src/private-apis.js +6 -2
- package/src/queried-data/get-query-parts.js +13 -0
- package/src/queried-data/selectors.js +33 -8
- package/src/queried-data/test/get-query-parts.js +34 -0
- package/src/queried-data/test/selectors.js +183 -0
- package/src/reducer.js +11 -0
- package/src/resolvers.js +136 -88
- package/src/selectors.ts +56 -0
- package/src/sync.ts +2 -0
- package/src/test/entities.js +185 -1
- package/src/test/resolvers.js +64 -11
- package/src/test/selectors.js +150 -0
- package/src/test/store.js +66 -0
- package/src/types.ts +26 -1
- package/src/utils/block-selection-history.ts +5 -2
- package/src/utils/crdt-blocks.ts +32 -3
- package/src/utils/crdt-selection.ts +8 -2
- package/src/utils/crdt-user-selections.ts +20 -8
- package/src/utils/crdt-utils.ts +99 -0
- package/src/utils/crdt.ts +8 -32
- package/src/utils/index.js +1 -0
- package/src/utils/normalize-query-for-resolution.js +23 -0
- package/src/utils/test/crdt-blocks.ts +146 -0
- package/src/utils/test/crdt-user-selections.ts +5 -0
- package/src/utils/test/crdt-utils.ts +387 -0
- package/src/utils/test/crdt.ts +120 -53
package/src/resolvers.js
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
ALLOWED_RESOURCE_ACTIONS,
|
|
25
25
|
RECEIVE_INTERMEDIATE_RESULTS,
|
|
26
26
|
isNumericID,
|
|
27
|
+
normalizeQueryForResolution,
|
|
27
28
|
} from './utils';
|
|
28
29
|
import { fetchBlockPatterns } from './fetch';
|
|
29
30
|
import { restoreSelection, getSelectionHistory } from './utils/crdt-selection';
|
|
@@ -229,18 +230,26 @@ export const getEntityRecord =
|
|
|
229
230
|
query
|
|
230
231
|
);
|
|
231
232
|
},
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
233
|
+
// Persist the CRDT document.
|
|
234
|
+
//
|
|
235
|
+
// TODO: Currently, persisted CRDT documents are stored in post meta.
|
|
236
|
+
// This effectively means that only post entities support CRDT
|
|
237
|
+
// persistence. As we add support for syncing additional entity,
|
|
238
|
+
// we'll need to revisit where persisted CRDT documents are stored.
|
|
239
|
+
persistCRDTDoc: () => {
|
|
235
240
|
resolveSelect
|
|
236
241
|
.getEditedEntityRecord( kind, name, key )
|
|
237
242
|
.then( ( editedRecord ) => {
|
|
238
|
-
// Don't
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
// Don't persist the CRDT document if the record is still an
|
|
244
|
+
// auto-draft or if the entity does not support meta.
|
|
245
|
+
const { meta, status } = editedRecord;
|
|
246
|
+
if ( 'auto-draft' === status || ! meta ) {
|
|
241
247
|
return;
|
|
242
248
|
}
|
|
243
249
|
|
|
250
|
+
// Trigger a save to persist the CRDT document. The entity's
|
|
251
|
+
// pre-persist hooks will create the persisted CRDT document
|
|
252
|
+
// and apply it to the record's meta.
|
|
244
253
|
dispatch.saveEntityRecord(
|
|
245
254
|
kind,
|
|
246
255
|
name,
|
|
@@ -350,18 +359,14 @@ export const getEntityRecords =
|
|
|
350
359
|
const key = entityConfig.key || DEFAULT_ENTITY_KEY;
|
|
351
360
|
|
|
352
361
|
function getResolutionsArgs( records, recordsQuery ) {
|
|
353
|
-
const
|
|
354
|
-
Object.entries( recordsQuery ).filter( ( [ k, v ] ) => {
|
|
355
|
-
return [ 'context', '_fields' ].includes( k ) && !! v;
|
|
356
|
-
} )
|
|
357
|
-
);
|
|
362
|
+
const normalizedQuery = normalizeQueryForResolution( recordsQuery );
|
|
358
363
|
return records
|
|
359
364
|
.filter( ( record ) => record?.[ key ] )
|
|
360
365
|
.map( ( record ) => [
|
|
361
366
|
kind,
|
|
362
367
|
name,
|
|
363
368
|
record[ key ],
|
|
364
|
-
|
|
369
|
+
normalizedQuery,
|
|
365
370
|
] );
|
|
366
371
|
}
|
|
367
372
|
|
|
@@ -1036,78 +1041,87 @@ export const getRevisions =
|
|
|
1036
1041
|
return;
|
|
1037
1042
|
}
|
|
1038
1043
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
...query,
|
|
1045
|
-
_fields: [
|
|
1046
|
-
...new Set( [
|
|
1047
|
-
...( getNormalizedCommaSeparable( query._fields ) ||
|
|
1048
|
-
[] ),
|
|
1049
|
-
entityConfig.revisionKey || DEFAULT_ENTITY_KEY,
|
|
1050
|
-
] ),
|
|
1051
|
-
].join(),
|
|
1052
|
-
};
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
const path = addQueryArgs(
|
|
1056
|
-
entityConfig.getRevisionsUrl( recordKey ),
|
|
1057
|
-
query
|
|
1044
|
+
const rawQuery = { ...query };
|
|
1045
|
+
const lock = await dispatch.__unstableAcquireStoreLock(
|
|
1046
|
+
STORE_NAME,
|
|
1047
|
+
[ 'entities', 'records', kind, name, recordKey, 'revisions' ],
|
|
1048
|
+
{ exclusive: false }
|
|
1058
1049
|
);
|
|
1059
1050
|
|
|
1060
|
-
let records, response;
|
|
1061
|
-
const meta = {};
|
|
1062
|
-
const isPaginated =
|
|
1063
|
-
entityConfig.supportsPagination && query.per_page !== -1;
|
|
1064
1051
|
try {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1052
|
+
if ( query._fields ) {
|
|
1053
|
+
// If requesting specific fields, items and query association to said
|
|
1054
|
+
// records are stored by ID reference. Thus, fields must always include
|
|
1055
|
+
// the ID.
|
|
1056
|
+
query = {
|
|
1057
|
+
...query,
|
|
1058
|
+
_fields: [
|
|
1059
|
+
...new Set( [
|
|
1060
|
+
...( getNormalizedCommaSeparable( query._fields ) ||
|
|
1061
|
+
[] ),
|
|
1062
|
+
entityConfig.revisionKey || DEFAULT_ENTITY_KEY,
|
|
1063
|
+
] ),
|
|
1064
|
+
].join(),
|
|
1065
|
+
};
|
|
1079
1066
|
}
|
|
1080
1067
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
records = records.map( ( record ) => {
|
|
1086
|
-
query._fields.split( ',' ).forEach( ( field ) => {
|
|
1087
|
-
if ( ! record.hasOwnProperty( field ) ) {
|
|
1088
|
-
record[ field ] = undefined;
|
|
1089
|
-
}
|
|
1090
|
-
} );
|
|
1068
|
+
const path = addQueryArgs(
|
|
1069
|
+
entityConfig.getRevisionsUrl( recordKey ),
|
|
1070
|
+
query
|
|
1071
|
+
);
|
|
1091
1072
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1073
|
+
let records, response;
|
|
1074
|
+
const meta = {};
|
|
1075
|
+
const isPaginated =
|
|
1076
|
+
entityConfig.supportsPagination && query.per_page !== -1;
|
|
1077
|
+
try {
|
|
1078
|
+
response = await apiFetch( { path, parse: ! isPaginated } );
|
|
1079
|
+
} catch ( error ) {
|
|
1080
|
+
// Do nothing if our request comes back with an API error.
|
|
1081
|
+
return;
|
|
1094
1082
|
}
|
|
1095
1083
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1084
|
+
if ( response ) {
|
|
1085
|
+
if ( isPaginated ) {
|
|
1086
|
+
records = Object.values( await response.json() );
|
|
1087
|
+
meta.totalItems = parseInt(
|
|
1088
|
+
response.headers.get( 'X-WP-Total' )
|
|
1089
|
+
);
|
|
1090
|
+
} else {
|
|
1091
|
+
records = Object.values( response );
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// If we request fields but the result doesn't contain the fields,
|
|
1095
|
+
// explicitly set these fields as "undefined"
|
|
1096
|
+
// that way we consider the query "fulfilled".
|
|
1097
|
+
if ( query._fields ) {
|
|
1098
|
+
records = records.map( ( record ) => {
|
|
1099
|
+
query._fields.split( ',' ).forEach( ( field ) => {
|
|
1100
|
+
if ( ! record.hasOwnProperty( field ) ) {
|
|
1101
|
+
record[ field ] = undefined;
|
|
1102
|
+
}
|
|
1103
|
+
} );
|
|
1104
|
+
|
|
1105
|
+
return record;
|
|
1106
|
+
} );
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
registry.batch( () => {
|
|
1110
|
+
dispatch.receiveRevisions(
|
|
1111
|
+
kind,
|
|
1112
|
+
name,
|
|
1113
|
+
recordKey,
|
|
1114
|
+
records,
|
|
1115
|
+
query,
|
|
1116
|
+
false,
|
|
1117
|
+
meta
|
|
1118
|
+
);
|
|
1106
1119
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
if ( ! query?._fields && ! query.context ) {
|
|
1120
|
+
// Mark individual getRevision resolutions as done so that
|
|
1121
|
+
// subsequent getRevision calls skip redundant API fetches.
|
|
1110
1122
|
const key = entityConfig.revisionKey || DEFAULT_ENTITY_KEY;
|
|
1123
|
+
const normalizedQuery =
|
|
1124
|
+
normalizeQueryForResolution( rawQuery );
|
|
1111
1125
|
const resolutionsArgs = records
|
|
1112
1126
|
.filter( ( record ) => record[ key ] )
|
|
1113
1127
|
.map( ( record ) => [
|
|
@@ -1115,14 +1129,17 @@ export const getRevisions =
|
|
|
1115
1129
|
name,
|
|
1116
1130
|
recordKey,
|
|
1117
1131
|
record[ key ],
|
|
1132
|
+
normalizedQuery,
|
|
1118
1133
|
] );
|
|
1119
1134
|
|
|
1120
1135
|
dispatch.finishResolutions(
|
|
1121
1136
|
'getRevision',
|
|
1122
1137
|
resolutionsArgs
|
|
1123
1138
|
);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1139
|
+
} );
|
|
1140
|
+
}
|
|
1141
|
+
} finally {
|
|
1142
|
+
dispatch.__unstableReleaseStoreLock( lock );
|
|
1126
1143
|
}
|
|
1127
1144
|
};
|
|
1128
1145
|
|
|
@@ -1147,7 +1164,7 @@ getRevisions.shouldInvalidate = ( action, kind, name, recordKey ) =>
|
|
|
1147
1164
|
*/
|
|
1148
1165
|
export const getRevision =
|
|
1149
1166
|
( kind, name, recordKey, revisionKey, query ) =>
|
|
1150
|
-
async ( { dispatch, resolveSelect } ) => {
|
|
1167
|
+
async ( { select, dispatch, resolveSelect } ) => {
|
|
1151
1168
|
const configs = await resolveSelect.getEntitiesConfig( kind );
|
|
1152
1169
|
const entityConfig = configs.find(
|
|
1153
1170
|
( config ) => config.name === name && config.kind === kind
|
|
@@ -1172,21 +1189,52 @@ export const getRevision =
|
|
|
1172
1189
|
].join(),
|
|
1173
1190
|
};
|
|
1174
1191
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1192
|
+
|
|
1193
|
+
const lock = await dispatch.__unstableAcquireStoreLock(
|
|
1194
|
+
STORE_NAME,
|
|
1195
|
+
[
|
|
1196
|
+
'entities',
|
|
1197
|
+
'records',
|
|
1198
|
+
kind,
|
|
1199
|
+
name,
|
|
1200
|
+
recordKey,
|
|
1201
|
+
'revisions',
|
|
1202
|
+
revisionKey,
|
|
1203
|
+
],
|
|
1204
|
+
{ exclusive: false }
|
|
1178
1205
|
);
|
|
1179
1206
|
|
|
1180
|
-
let record;
|
|
1181
1207
|
try {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1208
|
+
if (
|
|
1209
|
+
select.hasRevision( kind, name, recordKey, revisionKey, query )
|
|
1210
|
+
) {
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1187
1213
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1214
|
+
const path = addQueryArgs(
|
|
1215
|
+
entityConfig.getRevisionsUrl( recordKey, revisionKey ),
|
|
1216
|
+
query
|
|
1217
|
+
);
|
|
1218
|
+
|
|
1219
|
+
let record;
|
|
1220
|
+
try {
|
|
1221
|
+
record = await apiFetch( { path } );
|
|
1222
|
+
} catch ( error ) {
|
|
1223
|
+
// Do nothing if our request comes back with an API error.
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if ( record ) {
|
|
1228
|
+
dispatch.receiveRevisions(
|
|
1229
|
+
kind,
|
|
1230
|
+
name,
|
|
1231
|
+
recordKey,
|
|
1232
|
+
record,
|
|
1233
|
+
query
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
} finally {
|
|
1237
|
+
dispatch.__unstableReleaseStoreLock( lock );
|
|
1190
1238
|
}
|
|
1191
1239
|
};
|
|
1192
1240
|
|
package/src/selectors.ts
CHANGED
|
@@ -1524,6 +1524,62 @@ export const getRevisions = (
|
|
|
1524
1524
|
return getQueriedItems( queriedStateRevisions, query );
|
|
1525
1525
|
};
|
|
1526
1526
|
|
|
1527
|
+
/**
|
|
1528
|
+
* Returns true if a revision has been received for the given set of parameters,
|
|
1529
|
+
* or false otherwise.
|
|
1530
|
+
*
|
|
1531
|
+
* Note: This does not trigger a request for the revision from the API
|
|
1532
|
+
* if it's not available in the local state.
|
|
1533
|
+
*
|
|
1534
|
+
* @param state State tree
|
|
1535
|
+
* @param kind Entity kind.
|
|
1536
|
+
* @param name Entity name.
|
|
1537
|
+
* @param recordKey The key of the entity record whose revision you want to check.
|
|
1538
|
+
* @param revisionKey The revision's key.
|
|
1539
|
+
* @param query Optional query.
|
|
1540
|
+
*
|
|
1541
|
+
* @return Whether a revision has been received.
|
|
1542
|
+
*/
|
|
1543
|
+
export function hasRevision(
|
|
1544
|
+
state: State,
|
|
1545
|
+
kind: string,
|
|
1546
|
+
name: string,
|
|
1547
|
+
recordKey: EntityRecordKey,
|
|
1548
|
+
revisionKey: EntityRecordKey,
|
|
1549
|
+
query?: GetRecordsHttpQuery
|
|
1550
|
+
): boolean {
|
|
1551
|
+
const queriedState =
|
|
1552
|
+
state.entities.records?.[ kind ]?.[ name ]?.revisions?.[ recordKey ];
|
|
1553
|
+
if ( ! queriedState ) {
|
|
1554
|
+
return false;
|
|
1555
|
+
}
|
|
1556
|
+
const context = query?.context ?? 'default';
|
|
1557
|
+
|
|
1558
|
+
if ( ! query || ! query._fields ) {
|
|
1559
|
+
return !! queriedState.itemIsComplete[ context ]?.[ revisionKey ];
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
const item = queriedState.items[ context ]?.[ revisionKey ];
|
|
1563
|
+
if ( ! item ) {
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
const fields = getNormalizedCommaSeparable( query._fields ) ?? [];
|
|
1568
|
+
for ( let i = 0; i < fields.length; i++ ) {
|
|
1569
|
+
const path = fields[ i ].split( '.' );
|
|
1570
|
+
let value = item;
|
|
1571
|
+
for ( let p = 0; p < path.length; p++ ) {
|
|
1572
|
+
const part = path[ p ];
|
|
1573
|
+
if ( ! value || ! Object.hasOwn( value, part ) ) {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
value = value[ part ];
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
return true;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1527
1583
|
/**
|
|
1528
1584
|
* Returns a single, specific revision of a parent entity.
|
|
1529
1585
|
*
|
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,
|
package/src/test/entities.js
CHANGED
|
@@ -8,6 +8,10 @@ jest.mock( '../sync', () => ( {
|
|
|
8
8
|
...jest.requireActual( '../sync' ),
|
|
9
9
|
getSyncManager: jest.fn(),
|
|
10
10
|
} ) );
|
|
11
|
+
jest.mock( '../utils/crdt', () => ( {
|
|
12
|
+
...jest.requireActual( '../utils/crdt' ),
|
|
13
|
+
applyPostChangesToCRDTDoc: jest.fn(),
|
|
14
|
+
} ) );
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Internal dependencies
|
|
@@ -19,7 +23,10 @@ import {
|
|
|
19
23
|
additionalEntityConfigLoaders,
|
|
20
24
|
} from '../entities';
|
|
21
25
|
import { getSyncManager } from '../sync';
|
|
22
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
applyPostChangesToCRDTDoc,
|
|
28
|
+
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
29
|
+
} from '../utils/crdt';
|
|
23
30
|
|
|
24
31
|
describe( 'getMethodName', () => {
|
|
25
32
|
it( 'should return the right method name for an entity with the root kind', () => {
|
|
@@ -127,6 +134,183 @@ describe( 'prePersistPostType', () => {
|
|
|
127
134
|
} );
|
|
128
135
|
} );
|
|
129
136
|
|
|
137
|
+
describe( 'loadPostTypeEntities', () => {
|
|
138
|
+
let originalCollaborationEnabled;
|
|
139
|
+
|
|
140
|
+
beforeEach( () => {
|
|
141
|
+
apiFetch.mockReset();
|
|
142
|
+
applyPostChangesToCRDTDoc.mockReset();
|
|
143
|
+
originalCollaborationEnabled = window._wpCollaborationEnabled;
|
|
144
|
+
} );
|
|
145
|
+
|
|
146
|
+
afterEach( () => {
|
|
147
|
+
window._wpCollaborationEnabled = originalCollaborationEnabled;
|
|
148
|
+
} );
|
|
149
|
+
|
|
150
|
+
it( 'should include custom taxonomy rest_bases in synced properties when collaboration is enabled', async () => {
|
|
151
|
+
window._wpCollaborationEnabled = true;
|
|
152
|
+
|
|
153
|
+
const mockPostTypes = {
|
|
154
|
+
book: {
|
|
155
|
+
name: 'Books',
|
|
156
|
+
rest_base: 'books',
|
|
157
|
+
rest_namespace: 'wp/v2',
|
|
158
|
+
taxonomies: [ 'genre', 'audience' ],
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
const mockTaxonomies = {
|
|
162
|
+
genre: {
|
|
163
|
+
name: 'Genres',
|
|
164
|
+
rest_base: 'genres',
|
|
165
|
+
rest_namespace: 'wp/v2',
|
|
166
|
+
},
|
|
167
|
+
audience: {
|
|
168
|
+
name: 'Audiences',
|
|
169
|
+
rest_base: 'audiences',
|
|
170
|
+
rest_namespace: 'wp/v2',
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
apiFetch
|
|
175
|
+
.mockResolvedValueOnce( mockPostTypes )
|
|
176
|
+
.mockResolvedValueOnce( mockTaxonomies );
|
|
177
|
+
|
|
178
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
179
|
+
( loader ) => loader.kind === 'postType'
|
|
180
|
+
);
|
|
181
|
+
const entities = await postTypeLoader.loadEntities();
|
|
182
|
+
const bookEntity = entities.find( ( e ) => e.name === 'book' );
|
|
183
|
+
|
|
184
|
+
bookEntity.syncConfig.applyChangesToCRDTDoc( {}, {} );
|
|
185
|
+
|
|
186
|
+
expect( applyPostChangesToCRDTDoc ).toHaveBeenCalledWith(
|
|
187
|
+
{},
|
|
188
|
+
{},
|
|
189
|
+
expect.any( Set )
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const syncedProperties = applyPostChangesToCRDTDoc.mock.calls[ 0 ][ 2 ];
|
|
193
|
+
expect( syncedProperties ).toContain( 'genres' );
|
|
194
|
+
expect( syncedProperties ).toContain( 'audiences' );
|
|
195
|
+
} );
|
|
196
|
+
|
|
197
|
+
it( 'should not fetch taxonomies when collaboration is disabled', async () => {
|
|
198
|
+
window._wpCollaborationEnabled = false;
|
|
199
|
+
|
|
200
|
+
const mockPostTypes = {
|
|
201
|
+
post: {
|
|
202
|
+
name: 'Posts',
|
|
203
|
+
rest_base: 'posts',
|
|
204
|
+
rest_namespace: 'wp/v2',
|
|
205
|
+
taxonomies: [ 'category', 'post_tag' ],
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
apiFetch.mockResolvedValueOnce( mockPostTypes );
|
|
210
|
+
|
|
211
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
212
|
+
( loader ) => loader.kind === 'postType'
|
|
213
|
+
);
|
|
214
|
+
const entities = await postTypeLoader.loadEntities();
|
|
215
|
+
const postEntity = entities.find( ( e ) => e.name === 'post' );
|
|
216
|
+
|
|
217
|
+
postEntity.syncConfig.applyChangesToCRDTDoc( {}, {} );
|
|
218
|
+
|
|
219
|
+
// Only one apiFetch call (post types), no taxonomy fetch.
|
|
220
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
221
|
+
|
|
222
|
+
const syncedProperties = applyPostChangesToCRDTDoc.mock.calls[ 0 ][ 2 ];
|
|
223
|
+
expect( syncedProperties ).not.toContain( 'categories' );
|
|
224
|
+
expect( syncedProperties ).not.toContain( 'tags' );
|
|
225
|
+
} );
|
|
226
|
+
|
|
227
|
+
it( 'should skip taxonomy rest_base when taxonomy is not found in fetched taxonomies', async () => {
|
|
228
|
+
window._wpCollaborationEnabled = true;
|
|
229
|
+
|
|
230
|
+
const mockPostTypes = {
|
|
231
|
+
book: {
|
|
232
|
+
name: 'Books',
|
|
233
|
+
rest_base: 'books',
|
|
234
|
+
rest_namespace: 'wp/v2',
|
|
235
|
+
taxonomies: [ 'genre', 'missing_taxonomy' ],
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
const mockTaxonomies = {
|
|
239
|
+
genre: {
|
|
240
|
+
name: 'Genres',
|
|
241
|
+
rest_base: 'genres',
|
|
242
|
+
rest_namespace: 'wp/v2',
|
|
243
|
+
},
|
|
244
|
+
// 'missing_taxonomy' is intentionally absent.
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
apiFetch
|
|
248
|
+
.mockResolvedValueOnce( mockPostTypes )
|
|
249
|
+
.mockResolvedValueOnce( mockTaxonomies );
|
|
250
|
+
|
|
251
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
252
|
+
( loader ) => loader.kind === 'postType'
|
|
253
|
+
);
|
|
254
|
+
const entities = await postTypeLoader.loadEntities();
|
|
255
|
+
const bookEntity = entities.find( ( e ) => e.name === 'book' );
|
|
256
|
+
|
|
257
|
+
bookEntity.syncConfig.applyChangesToCRDTDoc( {}, {} );
|
|
258
|
+
|
|
259
|
+
const syncedProperties = applyPostChangesToCRDTDoc.mock.calls[ 0 ][ 2 ];
|
|
260
|
+
expect( syncedProperties ).toContain( 'genres' );
|
|
261
|
+
// missing_taxonomy has no rest_base entry, so nothing should be added for it.
|
|
262
|
+
expect( syncedProperties.size ).toBe( 16 ); // 15 base + 1 taxonomy (genres)
|
|
263
|
+
} );
|
|
264
|
+
|
|
265
|
+
it( 'should include base synced properties regardless of taxonomies', async () => {
|
|
266
|
+
window._wpCollaborationEnabled = true;
|
|
267
|
+
|
|
268
|
+
const mockPostTypes = {
|
|
269
|
+
page: {
|
|
270
|
+
name: 'Pages',
|
|
271
|
+
rest_base: 'pages',
|
|
272
|
+
rest_namespace: 'wp/v2',
|
|
273
|
+
taxonomies: [],
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
apiFetch
|
|
278
|
+
.mockResolvedValueOnce( mockPostTypes )
|
|
279
|
+
.mockResolvedValueOnce( {} );
|
|
280
|
+
|
|
281
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
282
|
+
( loader ) => loader.kind === 'postType'
|
|
283
|
+
);
|
|
284
|
+
const entities = await postTypeLoader.loadEntities();
|
|
285
|
+
const pageEntity = entities.find( ( e ) => e.name === 'page' );
|
|
286
|
+
|
|
287
|
+
pageEntity.syncConfig.applyChangesToCRDTDoc( {}, {} );
|
|
288
|
+
|
|
289
|
+
const syncedProperties = applyPostChangesToCRDTDoc.mock.calls[ 0 ][ 2 ];
|
|
290
|
+
const expectedBase = [
|
|
291
|
+
'author',
|
|
292
|
+
'blocks',
|
|
293
|
+
'content',
|
|
294
|
+
'comment_status',
|
|
295
|
+
'date',
|
|
296
|
+
'excerpt',
|
|
297
|
+
'featured_media',
|
|
298
|
+
'format',
|
|
299
|
+
'meta',
|
|
300
|
+
'ping_status',
|
|
301
|
+
'slug',
|
|
302
|
+
'status',
|
|
303
|
+
'sticky',
|
|
304
|
+
'template',
|
|
305
|
+
'title',
|
|
306
|
+
];
|
|
307
|
+
for ( const prop of expectedBase ) {
|
|
308
|
+
expect( syncedProperties ).toContain( prop );
|
|
309
|
+
}
|
|
310
|
+
expect( syncedProperties.size ).toBe( 15 );
|
|
311
|
+
} );
|
|
312
|
+
} );
|
|
313
|
+
|
|
130
314
|
describe( 'loadTaxonomyEntities', () => {
|
|
131
315
|
beforeEach( () => {
|
|
132
316
|
apiFetch.mockReset();
|