Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions packages/core-data/src/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
applyPostChangesToCRDTDoc,
defaultSyncConfig,
getPostChangesFromCRDTDoc,
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
} from './utils/crdt';

export const DEFAULT_ENTITY_KEY = 'id';
Expand Down Expand Up @@ -305,11 +306,17 @@ export const prePersistPostType = (
if ( persistedRecord ) {
const objectType = `postType/${ name }`;
const objectId = persistedRecord.id;
const meta = getSyncManager()?.createMeta( objectType, objectId );
newEdits.meta = {
...edits.meta,
...meta,
};
const serializedDoc = getSyncManager()?.createPersistedCRDTDoc(
objectType,
objectId
);

if ( serializedDoc ) {
newEdits.meta = {
...edits.meta,
[ POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE ]: serializedDoc,
};
}
}

return newEdits;
Expand Down Expand Up @@ -404,13 +411,14 @@ async function loadPostTypeEntities() {
getPostChangesFromCRDTDoc( crdtDoc, editedRecord, postType ),

/**
* Sync features supported by the entity.
* Extract changes from a CRDT document that can be used to update the
* local editor state.
*
* @type {Record< string, boolean >}
* @param {import('@wordpress/sync').ObjectData} record
* @return {Partial< import('@wordpress/sync').ObjectData >} Changes to record
*/
supports: {
crdtPersistence: true,
},
getPersistedCRDTDoc: ( record ) =>
record[ POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE ] || null,
};

return entity;
Expand Down
6 changes: 6 additions & 0 deletions packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ export const getEntityRecord =
resolveSelect
.getEditedEntityRecord( kind, name, key )
.then( ( editedRecord ) => {
// Don't trigger a save if the record is still an auto-draft.
const { status } = editedRecord;
if ( 'auto-draft' === status ) {
return;
}

dispatch.saveEntityRecord(
kind,
name,
Expand Down
2 changes: 0 additions & 2 deletions packages/core-data/src/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ const {
CRDT_DOC_META_PERSISTENCE_KEY,
CRDT_RECORD_MAP_KEY,
LOCAL_EDITOR_ORIGIN,
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
} = unlock( syncPrivateApis );

export {
Delta,
CRDT_DOC_META_PERSISTENCE_KEY,
CRDT_RECORD_MAP_KEY,
LOCAL_EDITOR_ORIGIN,
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
};

let syncManager: SyncManager;
Expand Down
49 changes: 39 additions & 10 deletions packages/core-data/src/test/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import apiFetch from '@wordpress/api-fetch';

jest.mock( '@wordpress/api-fetch' );
jest.mock( '../sync', () => ( {
...jest.requireActual( '../sync' ),
getSyncManager: jest.fn(),
} ) );

/**
* Internal dependencies
Expand All @@ -14,6 +18,8 @@ import {
prePersistPostType,
additionalEntityConfigLoaders,
} from '../entities';
import { getSyncManager } from '../sync';
import { POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE } from '../utils/crdt';

describe( 'getMethodName', () => {
it( 'should return the right method name for an entity with the root kind', () => {
Expand Down Expand Up @@ -53,15 +59,14 @@ describe( 'prePersistPostType', () => {
expect( prePersistPostType( record, edits, 'post', false ) ).toEqual( {
status: 'draft',
title: '',
meta: {},
} );

record = {
status: 'publish',
};
expect( prePersistPostType( record, edits, 'post', false ) ).toEqual( {
meta: {},
} );
expect( prePersistPostType( record, edits, 'post', false ) ).toEqual(
{}
);

record = {
status: 'auto-draft',
Expand All @@ -70,16 +75,15 @@ describe( 'prePersistPostType', () => {
expect( prePersistPostType( record, edits, 'post', false ) ).toEqual( {
status: 'draft',
title: '',
meta: {},
} );

record = {
status: 'publish',
title: 'My Title',
};
expect( prePersistPostType( record, edits, 'post', false ) ).toEqual( {
meta: {},
} );
expect( prePersistPostType( record, edits, 'post', false ) ).toEqual(
{}
);
} );

it( 'does not set the status to draft and empty the title when saving templates', () => {
Expand All @@ -88,9 +92,34 @@ describe( 'prePersistPostType', () => {
title: 'Auto Draft',
};
const edits = {};
expect( prePersistPostType( record, edits, 'post', true ) ).toEqual( {
meta: {},
expect( prePersistPostType( record, edits, 'post', true ) ).toEqual(
{}
);
} );

it( 'adds meta with serialized CRDT doc when createPersistedCRDTDoc returns a value', () => {
const mockSerializedDoc = 'serialized-crdt-doc-data';
getSyncManager.mockReturnValue( {
createPersistedCRDTDoc: jest
.fn()
.mockReturnValue( mockSerializedDoc ),
} );

const record = { id: 123, status: 'publish' };
const edits = {};
const result = prePersistPostType( record, edits, 'post', false );

expect( result.meta ).toEqual( {
[ POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE ]: mockSerializedDoc,
} );

expect( getSyncManager ).toHaveBeenCalled();
expect( getSyncManager().createPersistedCRDTDoc ).toHaveBeenCalledWith(
'postType/post',
123
);

getSyncManager.mockReset();
} );
} );

Expand Down
10 changes: 4 additions & 6 deletions packages/core-data/src/utils/crdt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ import {
} from './crdt-blocks';
import { type Post } from '../entity-types/post';
import { type Type } from '../entity-types';
import {
CRDT_DOC_META_PERSISTENCE_KEY,
CRDT_RECORD_MAP_KEY,
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
} from '../sync';
import { CRDT_DOC_META_PERSISTENCE_KEY, CRDT_RECORD_MAP_KEY } from '../sync';
import type { WPSelection } from '../types';
import { updateSelectionHistory } from './crdt-selection';
import {
Expand Down Expand Up @@ -74,6 +70,8 @@ export interface YPostRecord extends YMapRecord {
title: Y.Text;
}

export const POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE = '_crdt_document';

// Properties that are allowed to be synced for a post.
const allowedPostProperties = new Set< string >( [
'author',
Expand All @@ -97,7 +95,7 @@ const allowedPostProperties = new Set< string >( [

// Post meta keys that should *not* be synced.
const disallowedPostMetaKeys = new Set< string >( [
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
] );

/**
Expand Down
13 changes: 4 additions & 9 deletions packages/core-data/src/utils/test/crdt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ import { describe, expect, it, jest, beforeEach } from '@jest/globals';
/**
* Internal dependencies
*/
import {
CRDT_RECORD_MAP_KEY,
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
} from '../../sync';
import { CRDT_RECORD_MAP_KEY } from '../../sync';
import {
applyPostChangesToCRDTDoc,
getPostChangesFromCRDTDoc,
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
type PostChanges,
type YPostRecord,
} from '../crdt';
Expand Down Expand Up @@ -604,10 +602,7 @@ describe( 'crdt', () => {
it( 'excludes disallowed meta keys in changes', () => {
const metaMap = createYMap();
metaMap.set( 'public_meta', 'new value' );
metaMap.set(
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
'exclude me'
);
metaMap.set( POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE, 'exclude me' );
map.set( 'meta', metaMap );

const editedRecord = {
Expand All @@ -626,7 +621,7 @@ describe( 'crdt', () => {
public_meta: 'new value', // from CRDT
} );
expect( changes.meta ).not.toHaveProperty(
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE
);
} );
} );
Expand Down
5 changes: 0 additions & 5 deletions packages/sync/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,3 @@ export const LOCAL_EDITOR_ORIGIN = 'gutenberg';
* Origin string for CRDT document changes originating from the sync manager.
*/
export const LOCAL_SYNC_MANAGER_ORIGIN = 'syncManager';

/**
* WordPress meta key used to persist the CRDT document for an entity.
*/
export const WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE = '_crdt_document';
50 changes: 22 additions & 28 deletions packages/sync/src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ import type { Awareness } from 'y-protocols/awareness';
* Internal dependencies
*/
import {
CRDT_RECORD_MAP_KEY as RECORD_KEY,
LOCAL_SYNC_MANAGER_ORIGIN,
CRDT_RECORD_MAP_KEY,
CRDT_STATE_MAP_KEY,
CRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,
LOCAL_SYNC_MANAGER_ORIGIN,
} from './config';
import {
logPerformanceTiming,
passThru,
yieldToEventLoop,
} from './performance';
import { createPersistedCRDTDoc, getPersistedCrdtDoc } from './persistence';
import { getProviderCreators } from './providers';
import type {
CollectionHandlers,
Expand All @@ -35,7 +34,12 @@ import type {
SyncUndoManager,
} from './types';
import { createUndoManager } from './undo-manager';
import { createYjsDoc, markEntityAsSaved } from './utils';
import {
createYjsDoc,
deserializeCrdtDoc,
markEntityAsSaved,
serializeCrdtDoc,
} from './utils';

interface CollectionState {
awareness?: Awareness;
Expand Down Expand Up @@ -150,7 +154,7 @@ export function createSyncManager( debug = false ): SyncManager {
};

const ydoc = createYjsDoc( { objectType } );
const recordMap = ydoc.getMap( RECORD_KEY );
const recordMap = ydoc.getMap( CRDT_RECORD_MAP_KEY );
const stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );
const now = Date.now();

Expand Down Expand Up @@ -404,32 +408,22 @@ export function createSyncManager( debug = false ): SyncManager {
syncConfig: {
applyChangesToCRDTDoc,
getChangesFromCRDTDoc,
supports,
getPersistedCrdtDoc,
},
ydoc: targetDoc,
} = entityState;

if ( ! supports?.crdtPersistence ) {
// Apply the current record as changes.
targetDoc.transact( () => {
applyChangesToCRDTDoc( targetDoc, record );
}, LOCAL_SYNC_MANAGER_ORIGIN );
return;
}

// Get the persisted CRDT document, if it exists.
const tempDoc = getPersistedCrdtDoc( record );
const serialized = getPersistedCrdtDoc?.( record );
const tempDoc = serialized ? deserializeCrdtDoc( serialized ) : null;

if ( ! tempDoc ) {
// Apply the current record as changes and trigger a save, which will
// persist the CRDT document. (The entity should call `createEntityMeta`
// persist the CRDT document. (The entity should call `createPersistedCRDTDoc`
// via its pre-persist hook.)
targetDoc.transact( () => {
applyChangesToCRDTDoc( targetDoc, record );

if ( 'auto-draft' !== record.status ) {
handlers.saveRecord();
}
handlers.saveRecord();
}, LOCAL_SYNC_MANAGER_ORIGIN );
return;
}
Expand Down Expand Up @@ -477,8 +471,8 @@ export function createSyncManager( debug = false ): SyncManager {
);

// Apply the changes and trigger a save, which will persist the CRDT
// document. (The entity should call `createEntityMeta` via its pre-persist
// hook.)
// document. (The entity should call `createPersistedCRDTDoc` via its
// pre-persist hook.)
targetDoc.transact( () => {
applyChangesToCRDTDoc( targetDoc, changes );
handlers.saveRecord();
Expand Down Expand Up @@ -580,18 +574,18 @@ export function createSyncManager( debug = false ): SyncManager {
* @param {ObjectType} objectType Object type.
* @param {ObjectID} objectId Object ID.
*/
function createEntityMeta(
function createPersistedCRDTDoc(
objectType: ObjectType,
objectId: ObjectID
): Record< string, string > {
): string | null {
const entityId = getEntityId( objectType, objectId );
const entityState = entityStates.get( entityId );

if ( ! entityState?.syncConfig.supports?.crdtPersistence ) {
return {};
if ( ! entityState?.ydoc ) {
return null;
}

return createPersistedCRDTDoc( entityState.ydoc );
return serializeCrdtDoc( entityState.ydoc );
}

// Collect internal functions so that they can be wrapped before calling.
Expand All @@ -602,7 +596,7 @@ export function createSyncManager( debug = false ): SyncManager {

// Wrap and return the public API.
return {
createMeta: debugWrap( createEntityMeta ),
createPersistedCRDTDoc: debugWrap( createPersistedCRDTDoc ),
getAwareness,
load: debugWrap( loadEntity ),
loadCollection: debugWrap( loadCollection ),
Expand Down
Loading
Loading