@wordpress/core-data 7.33.1 → 7.34.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 +2 -0
- package/build/entities.js +18 -4
- package/build/entities.js.map +2 -2
- package/build/resolvers.js +9 -1
- package/build/resolvers.js.map +2 -2
- package/build/sync.js +4 -0
- package/build/sync.js.map +2 -2
- package/build/utils/crdt.js +59 -3
- package/build/utils/crdt.js.map +2 -2
- package/build-module/entities.js +18 -4
- package/build-module/entities.js.map +2 -2
- package/build-module/resolvers.js +9 -1
- package/build-module/resolvers.js.map +2 -2
- package/build-module/sync.js +4 -0
- package/build-module/sync.js.map +2 -2
- package/build-module/utils/crdt.js +64 -4
- package/build-module/utils/crdt.js.map +2 -2
- package/build-types/entities.d.ts +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/resolvers.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/utils/crdt.d.ts +4 -7
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/entities.js +31 -7
- package/src/resolvers.js +8 -0
- package/src/sync.ts +9 -1
- package/src/test/entities.js +19 -4
- package/src/test/resolvers.js +2 -0
- package/src/utils/crdt.ts +108 -6
- package/src/utils/test/crdt.ts +124 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/utils/crdt.ts
CHANGED
|
@@ -6,6 +6,8 @@ import fastDeepEqual from 'fast-deep-equal/es6';
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies
|
|
8
8
|
*/
|
|
9
|
+
// @ts-expect-error No exported types.
|
|
10
|
+
import { __unstableSerializeAndClean } from '@wordpress/blocks';
|
|
9
11
|
import { type CRDTDoc, type ObjectData, Y } from '@wordpress/sync';
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -19,7 +21,11 @@ import {
|
|
|
19
21
|
} from './crdt-blocks';
|
|
20
22
|
import { type Post } from '../entity-types/post';
|
|
21
23
|
import { type Type } from '../entity-types';
|
|
22
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
CRDT_DOC_META_PERSISTENCE_KEY,
|
|
26
|
+
CRDT_RECORD_MAP_KEY,
|
|
27
|
+
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
28
|
+
} from '../sync';
|
|
23
29
|
import type { WPBlockSelection, WPSelection } from '../types';
|
|
24
30
|
|
|
25
31
|
export type PostChanges = Partial< Post > & {
|
|
@@ -42,6 +48,7 @@ const allowedPostProperties = new Set< string >( [
|
|
|
42
48
|
'featured_media',
|
|
43
49
|
'format',
|
|
44
50
|
'ping_status',
|
|
51
|
+
'meta',
|
|
45
52
|
'slug',
|
|
46
53
|
'status',
|
|
47
54
|
'sticky',
|
|
@@ -50,6 +57,11 @@ const allowedPostProperties = new Set< string >( [
|
|
|
50
57
|
'title',
|
|
51
58
|
] );
|
|
52
59
|
|
|
60
|
+
// Post meta keys that should *not* be synced.
|
|
61
|
+
const disallowedPostMetaKeys = new Set< string >( [
|
|
62
|
+
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
63
|
+
] );
|
|
64
|
+
|
|
53
65
|
/**
|
|
54
66
|
* Given a set of local changes to a generic entity record, apply those changes
|
|
55
67
|
* to the local Y.Doc.
|
|
@@ -92,13 +104,13 @@ export function defaultApplyChangesToCRDTDoc(
|
|
|
92
104
|
*
|
|
93
105
|
* @param {CRDTDoc} ydoc
|
|
94
106
|
* @param {PostChanges} changes
|
|
95
|
-
* @param {Type}
|
|
107
|
+
* @param {Type} _postType
|
|
96
108
|
* @return {void}
|
|
97
109
|
*/
|
|
98
110
|
export function applyPostChangesToCRDTDoc(
|
|
99
111
|
ydoc: CRDTDoc,
|
|
100
112
|
changes: PostChanges,
|
|
101
|
-
|
|
113
|
+
_postType: Type // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
102
114
|
): void {
|
|
103
115
|
const ymap = ydoc.getMap( CRDT_RECORD_MAP_KEY );
|
|
104
116
|
|
|
@@ -146,6 +158,36 @@ export function applyPostChangesToCRDTDoc(
|
|
|
146
158
|
break;
|
|
147
159
|
}
|
|
148
160
|
|
|
161
|
+
// "Meta" is overloaded term; here, it refers to post meta.
|
|
162
|
+
case 'meta': {
|
|
163
|
+
let metaMap = ymap.get( 'meta' ) as Y.Map< unknown >;
|
|
164
|
+
|
|
165
|
+
// Initialize.
|
|
166
|
+
if ( ! ( metaMap instanceof Y.Map ) ) {
|
|
167
|
+
metaMap = new Y.Map();
|
|
168
|
+
setValue( metaMap );
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Iterate over each meta property in the new value and merge it if it
|
|
172
|
+
// should be synced.
|
|
173
|
+
Object.entries( newValue ?? {} ).forEach(
|
|
174
|
+
( [ metaKey, metaValue ] ) => {
|
|
175
|
+
if ( disallowedPostMetaKeys.has( metaKey ) ) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
mergeValue(
|
|
180
|
+
metaMap.get( metaKey ), // current value in CRDT
|
|
181
|
+
metaValue, // new value from changes
|
|
182
|
+
( updatedMetaValue: unknown ): void => {
|
|
183
|
+
metaMap.set( metaKey, updatedMetaValue );
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
|
|
149
191
|
case 'slug': {
|
|
150
192
|
// Do not sync an empty slug. This indicates that the post is using
|
|
151
193
|
// the default auto-generated slug.
|
|
@@ -198,17 +240,19 @@ export function defaultGetChangesFromCRDTDoc( crdtDoc: CRDTDoc ): ObjectData {
|
|
|
198
240
|
*
|
|
199
241
|
* @param {CRDTDoc} ydoc
|
|
200
242
|
* @param {Post} editedRecord
|
|
201
|
-
* @param {Type}
|
|
243
|
+
* @param {Type} _postType
|
|
202
244
|
* @return {Partial<PostChanges>} The changes that should be applied to the local record.
|
|
203
245
|
*/
|
|
204
246
|
export function getPostChangesFromCRDTDoc(
|
|
205
247
|
ydoc: CRDTDoc,
|
|
206
248
|
editedRecord: Post,
|
|
207
|
-
|
|
249
|
+
_postType: Type // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
208
250
|
): PostChanges {
|
|
209
251
|
const ymap = ydoc.getMap( CRDT_RECORD_MAP_KEY );
|
|
210
252
|
|
|
211
|
-
|
|
253
|
+
let allowedMetaChanges: Post[ 'meta' ] = {};
|
|
254
|
+
|
|
255
|
+
const changes = Object.fromEntries(
|
|
212
256
|
Object.entries( ymap.toJSON() ).filter( ( [ key, newValue ] ) => {
|
|
213
257
|
if ( ! allowedPostProperties.has( key ) ) {
|
|
214
258
|
return false;
|
|
@@ -218,6 +262,35 @@ export function getPostChangesFromCRDTDoc(
|
|
|
218
262
|
|
|
219
263
|
switch ( key ) {
|
|
220
264
|
case 'blocks': {
|
|
265
|
+
// When we are passed a persisted CRDT document, make a special
|
|
266
|
+
// comparison of the content and blocks.
|
|
267
|
+
//
|
|
268
|
+
// When other fields (besides `blocks`) are mutated outside the block
|
|
269
|
+
// editor, the change is caught by an equality check (see other cases
|
|
270
|
+
// in this `switch` statement). As a transient property, `blocks`
|
|
271
|
+
// cannot be directly mutated outside the block editor -- only
|
|
272
|
+
// `content` can.
|
|
273
|
+
//
|
|
274
|
+
// Therefore, for this special comparison, we serialize the `blocks`
|
|
275
|
+
// from the persisted CRDT document and compare that to the content
|
|
276
|
+
// from the persisted record. If they differ, we know that the content
|
|
277
|
+
// in the database has changed, and therefore the blocks have changed.
|
|
278
|
+
//
|
|
279
|
+
// We cannot directly compare the `blocks` from the CRDT document to
|
|
280
|
+
// the `blocks` derived from the `content` in the persisted record,
|
|
281
|
+
// because the latter will have different client IDs.
|
|
282
|
+
if (
|
|
283
|
+
ydoc.meta?.get( CRDT_DOC_META_PERSISTENCE_KEY ) &&
|
|
284
|
+
editedRecord.content
|
|
285
|
+
) {
|
|
286
|
+
const blocks = ymap.get( 'blocks' ) as YBlocks;
|
|
287
|
+
return (
|
|
288
|
+
__unstableSerializeAndClean(
|
|
289
|
+
blocks.toJSON()
|
|
290
|
+
).trim() !== editedRecord.content.raw.trim()
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
221
294
|
// The consumers of blocks have memoization that renders optimization
|
|
222
295
|
// here unnecessary.
|
|
223
296
|
return true;
|
|
@@ -240,6 +313,24 @@ export function getPostChangesFromCRDTDoc(
|
|
|
240
313
|
return haveValuesChanged( currentValue, newValue );
|
|
241
314
|
}
|
|
242
315
|
|
|
316
|
+
case 'meta': {
|
|
317
|
+
allowedMetaChanges = Object.fromEntries(
|
|
318
|
+
Object.entries( newValue ?? {} ).filter(
|
|
319
|
+
( [ metaKey ] ) =>
|
|
320
|
+
! disallowedPostMetaKeys.has( metaKey )
|
|
321
|
+
)
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
// Merge the allowed meta changes with the current meta values since
|
|
325
|
+
// not all meta properties are synced.
|
|
326
|
+
const mergedValue = {
|
|
327
|
+
...( currentValue as PostChanges[ 'meta' ] ),
|
|
328
|
+
...allowedMetaChanges,
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
return haveValuesChanged( currentValue, mergedValue );
|
|
332
|
+
}
|
|
333
|
+
|
|
243
334
|
case 'status': {
|
|
244
335
|
// Do not sync an invalid status.
|
|
245
336
|
if ( 'auto-draft' === newValue ) {
|
|
@@ -265,6 +356,17 @@ export function getPostChangesFromCRDTDoc(
|
|
|
265
356
|
}
|
|
266
357
|
} )
|
|
267
358
|
);
|
|
359
|
+
|
|
360
|
+
// Meta changes must be merged with the edited record since not all meta
|
|
361
|
+
// properties are synced.
|
|
362
|
+
if ( 'object' === typeof changes.meta ) {
|
|
363
|
+
changes.meta = {
|
|
364
|
+
...editedRecord.meta,
|
|
365
|
+
...allowedMetaChanges,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return changes;
|
|
268
370
|
}
|
|
269
371
|
|
|
270
372
|
/**
|
package/src/utils/test/crdt.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CRDT_RECORD_MAP_KEY,
|
|
6
|
+
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
7
|
+
Y,
|
|
8
|
+
} from '@wordpress/sync';
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* External dependencies
|
|
@@ -152,6 +156,55 @@ describe( 'crdt', () => {
|
|
|
152
156
|
const blocks = map.get( 'blocks' );
|
|
153
157
|
expect( blocks ).toBeInstanceOf( Y.Array );
|
|
154
158
|
} );
|
|
159
|
+
|
|
160
|
+
it( 'syncs meta fields', () => {
|
|
161
|
+
const changes = {
|
|
162
|
+
meta: {
|
|
163
|
+
some_meta: 'new value',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const metaMap = new Y.Map< unknown >();
|
|
168
|
+
metaMap.set( 'some_meta', 'old value' );
|
|
169
|
+
map.set( 'meta', metaMap );
|
|
170
|
+
|
|
171
|
+
applyPostChangesToCRDTDoc( doc, changes, mockPostType );
|
|
172
|
+
|
|
173
|
+
expect( metaMap.get( 'some_meta' ) ).toBe( 'new value' );
|
|
174
|
+
} );
|
|
175
|
+
|
|
176
|
+
it( 'syncs non-single meta fields', () => {
|
|
177
|
+
const changes = {
|
|
178
|
+
meta: {
|
|
179
|
+
some_meta: [ 'value', 'value 2' ],
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const metaMap = new Y.Map< unknown >();
|
|
184
|
+
metaMap.set( 'some_meta', 'old value' );
|
|
185
|
+
map.set( 'meta', metaMap );
|
|
186
|
+
|
|
187
|
+
applyPostChangesToCRDTDoc( doc, changes, mockPostType );
|
|
188
|
+
|
|
189
|
+
expect( metaMap.get( 'some_meta' ) ).toStrictEqual( [
|
|
190
|
+
'value',
|
|
191
|
+
'value 2',
|
|
192
|
+
] );
|
|
193
|
+
} );
|
|
194
|
+
|
|
195
|
+
it( 'initializes meta as Y.Map when not present', () => {
|
|
196
|
+
const changes = {
|
|
197
|
+
meta: {
|
|
198
|
+
custom_field: 'value',
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
applyPostChangesToCRDTDoc( doc, changes, mockPostType );
|
|
203
|
+
|
|
204
|
+
const metaMap = map.get( 'meta' ) as Y.Map< unknown >;
|
|
205
|
+
expect( metaMap ).toBeInstanceOf( Y.Map );
|
|
206
|
+
expect( metaMap.get( 'custom_field' ) ).toBe( 'value' );
|
|
207
|
+
} );
|
|
155
208
|
} );
|
|
156
209
|
|
|
157
210
|
describe( 'getPostChangesFromCRDTDoc', () => {
|
|
@@ -250,5 +303,75 @@ describe( 'crdt', () => {
|
|
|
250
303
|
|
|
251
304
|
expect( changes ).toHaveProperty( 'blocks' );
|
|
252
305
|
} );
|
|
306
|
+
|
|
307
|
+
it( 'includes meta in changes', () => {
|
|
308
|
+
map.set( 'meta', {
|
|
309
|
+
public_meta: 'new value',
|
|
310
|
+
} );
|
|
311
|
+
|
|
312
|
+
const editedRecord = {
|
|
313
|
+
meta: {
|
|
314
|
+
public_meta: 'old value',
|
|
315
|
+
},
|
|
316
|
+
} as unknown as Post;
|
|
317
|
+
|
|
318
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
319
|
+
doc,
|
|
320
|
+
editedRecord,
|
|
321
|
+
mockPostType
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect( changes.meta ).toEqual( {
|
|
325
|
+
public_meta: 'new value', // from CRDT
|
|
326
|
+
} );
|
|
327
|
+
} );
|
|
328
|
+
|
|
329
|
+
it( 'includes non-single meta in changes', () => {
|
|
330
|
+
map.set( 'meta', {
|
|
331
|
+
public_meta: [ 'value', 'value 2' ],
|
|
332
|
+
} );
|
|
333
|
+
|
|
334
|
+
const editedRecord = {
|
|
335
|
+
meta: {
|
|
336
|
+
public_meta: 'value',
|
|
337
|
+
},
|
|
338
|
+
} as unknown as Post;
|
|
339
|
+
|
|
340
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
341
|
+
doc,
|
|
342
|
+
editedRecord,
|
|
343
|
+
mockPostType
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
expect( changes.meta ).toEqual( {
|
|
347
|
+
public_meta: [ 'value', 'value 2' ], // from CRDT
|
|
348
|
+
} );
|
|
349
|
+
} );
|
|
350
|
+
|
|
351
|
+
it( 'excludes disallowed meta keys in changes', () => {
|
|
352
|
+
map.set( 'meta', {
|
|
353
|
+
public_meta: 'new value',
|
|
354
|
+
[ WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE ]: 'exclude me',
|
|
355
|
+
} );
|
|
356
|
+
|
|
357
|
+
const editedRecord = {
|
|
358
|
+
meta: {
|
|
359
|
+
public_meta: 'old value',
|
|
360
|
+
},
|
|
361
|
+
} as unknown as Post;
|
|
362
|
+
|
|
363
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
364
|
+
doc,
|
|
365
|
+
editedRecord,
|
|
366
|
+
mockPostType
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect( changes.meta ).toEqual( {
|
|
370
|
+
public_meta: 'new value', // from CRDT
|
|
371
|
+
} );
|
|
372
|
+
expect( changes.meta ).not.toHaveProperty(
|
|
373
|
+
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE
|
|
374
|
+
);
|
|
375
|
+
} );
|
|
253
376
|
} );
|
|
254
377
|
} );
|