Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
8da7dc2
Add `core.getSyncProvider` filter
chriszarate Jul 24, 2025
2879565
Promote @wordpress/sync to public package
chriszarate Jul 24, 2025
da6ac8e
Separate experimental flags for feature and transport
chriszarate Jul 24, 2025
b354743
Restore `register` property, for now.
chriszarate Jul 25, 2025
019fd34
Fix unit test
chriszarate Jul 25, 2025
4144f8c
Remove conditional loading of @wordpress/sync
chriszarate Jul 28, 2025
b91ef4b
Remove @wordpress/hooks dependency
chriszarate Jul 28, 2025
8d62300
Remove new experimental flag
chriszarate Jul 28, 2025
2cfe35c
Correctly fall back to no-op sync provider
chriszarate Jul 28, 2025
3865ac9
Remove dependency from base package-lock
chriszarate Jul 28, 2025
571c951
Add Yjs-aware entity config
chriszarate Jul 29, 2025
b0d09f7
Remove awareness implementation
chriszarate Jul 29, 2025
4e01172
Convert SyncProvider to class
chriszarate Jul 30, 2025
b3cc7e3
Update the package-lock.json and bump the y- packages
ingeniumed Aug 1, 2025
76b6288
Add awareness as optional property on ConnectDocResult
chriszarate Aug 5, 2025
323eaab
Update SyncProvider for better extensibility
chriszarate Aug 5, 2025
e9aabe2
Add the Yjs undo manager in the provider.ts
ingeniumed Aug 6, 2025
8404bed
Make a separate exported clas for the undo manager coming from the pr…
ingeniumed Aug 6, 2025
9ef29f5
Cleanup and separation of concerns
chriszarate Aug 6, 2025
cdb3bc4
Rely on a single instance of UndoManager
chriszarate Aug 6, 2025
c9844b5
Remove launch.json
chriszarate Aug 6, 2025
c5ae8fb
Remove feature flag check
chriszarate Aug 6, 2025
f8d0ac2
Add supportsAwareness: true to post entities
chriszarate Aug 6, 2025
0f37ce2
Merge branch 'add/experimental-collaborative-editing' into add/yjs-un…
chriszarate Aug 6, 2025
7132976
Minor change in the return for undo and undo manger return type
ingeniumed Aug 7, 2025
8924f85
Merge pull request #9 from Automattic/add/yjs-undo-manager-back
ingeniumed Aug 7, 2025
640177a
Fix the missing path in tsconfig for hooks
ingeniumed Aug 7, 2025
8b64dde
Merge pull request #10 from Automattic/fix/failing-checks
ingeniumed Aug 7, 2025
29e972e
Merging latest trunk in
ingeniumed Aug 13, 2025
3d4e50e
Remove sync configs for non-synced entities
chriszarate Aug 14, 2025
c490c8a
Allow custom sync provider to override initial CRDT doc
chriszarate Aug 14, 2025
9feeef1
Merge branch 'trunk' of github.com:Automattic/gutenberg into add/expe…
ingeniumed Aug 15, 2025
c4d613d
Rename method for clarity
chriszarate Aug 15, 2025
1e3b014
Merge pull request #11 from Automattic/improve/yjs-doc-persistence
chriszarate Aug 15, 2025
ffe6c4a
Merge branch 'trunk' of github.com:Automattic/gutenberg into add/expe…
ingeniumed Aug 18, 2025
e51717d
Filter synced properties
chriszarate Aug 18, 2025
4c4fbc9
Reform object
chriszarate Aug 18, 2025
c9ffba1
Remove content and excerpt from synced property list
chriszarate Aug 18, 2025
2f1213d
Move merge functions to separate utils file
chriszarate Aug 19, 2025
2bcce30
Rename Foo type and add comment
chriszarate Aug 19, 2025
0c46711
Merge pull request #12 from Automattic/improve/crdt-utils
ingeniumed Aug 19, 2025
74ed47b
Sync post title
chriszarate Aug 21, 2025
0f67dc4
Merge pull request #13 from Automattic/add/title-sync
chriszarate Aug 21, 2025
6bbaa95
Improve block support
ingeniumed Aug 21, 2025
a78f85f
Move applyChangesToDoc core code to utils/crdt
chriszarate Aug 21, 2025
8e268c5
Add post type support for collaborative editing
chriszarate Aug 21, 2025
81d0d13
Add editor support check
chriszarate Aug 21, 2025
e4321f9
Merge pull request #15 from Automattic/improve/post-type-support
chriszarate Aug 21, 2025
2190d49
Merge branch 'add/experimental-collaborative-editing' of github.com:A…
ingeniumed Aug 22, 2025
b87b3e2
Merge branch 'trunk' of github.com:Automattic/gutenberg into add/expe…
ingeniumed Aug 25, 2025
2f587c7
Merge branch 'add/experimental-collaborative-editing' of github.com:A…
ingeniumed Aug 25, 2025
12cdf98
Tweak the flow with comments
ingeniumed Aug 25, 2025
220b6e5
Drop the filter
ingeniumed Aug 25, 2025
f2dd14f
Merge pull request #14 from Automattic/improve/block-support
ingeniumed Aug 25, 2025
189697e
Change 'mergeBlocks()' to 'mergeCrdtBlocks()' to make it distinct
alecgeatches Aug 27, 2025
be6e0be
Provide bindings for CRDT persistence
chriszarate Aug 27, 2025
860d892
Merge pull request #16 from Automattic/add/persisted-crdt-hooks
chriszarate Aug 27, 2025
ec24872
Change mergeCrdtBlocks() to use direct Y types for yblocks. First ste…
alecgeatches Aug 27, 2025
a41e518
Control and validate CRDT doc version internally
chriszarate Aug 28, 2025
49155fd
Merge branch 'add/experimental-collaborative-editing' into improve/yj…
chriszarate Aug 28, 2025
ec8df1b
Fix CRDT merge object equality check against yBlockAsJson
alecgeatches Aug 28, 2025
dd71bdf
Add createNewYBlock for recursive insert
chriszarate Aug 28, 2025
c12aa9b
Ensure we always recursively merge innerBlocks
chriszarate Aug 28, 2025
131f0e5
In areBlocksEqual(), ensure YBlock type instead of JSON value for com…
alecgeatches Aug 28, 2025
a3720b6
Use Y.Map type for attributes in yblocks
alecgeatches Aug 28, 2025
3b11604
Bugfix: Don't allow non-array values for innerBlocks
chriszarate Aug 28, 2025
cf9a1ce
Cast rich-text types into Y.Text in the ydoc
alecgeatches Aug 28, 2025
9a67df3
Improve type safety, DRY up, and slightly more efficient
chriszarate Aug 28, 2025
a7e8228
Ensure attributes are deleted when removed
chriszarate Aug 28, 2025
15ad201
Bugfix: Make sure new attributes are set.
chriszarate Aug 28, 2025
ac0ffa2
Merge pull request #17 from Automattic/improve/yjs-document-types
alecgeatches Aug 28, 2025
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
Prev Previous commit
Next Next commit
Convert SyncProvider to class
  • Loading branch information
chriszarate committed Aug 5, 2025
commit 4e01172f5dc63eade8d1e5eddb5cabb36ff9b17d
11 changes: 2 additions & 9 deletions packages/core-data/src/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
* WordPress dependencies
*/
import { applyFilters } from '@wordpress/hooks';
import { getWebRTCSyncProvider } from '@wordpress/sync';
import type { SyncProvider } from '@wordpress/sync';
import { getWebRTCSyncProvider, SyncProvider } from '@wordpress/sync';

declare global {
interface Window {
Expand All @@ -26,13 +25,7 @@ export function getSyncProvider(): SyncProvider {
return syncProvider;
}

const fallbackNoOpSyncProvider: SyncProvider = {
__fallback: true,
bootstrap: async () => {},
configs: new Map(),
discard: async () => {},
update: () => {},
};
const fallbackNoOpSyncProvider = new SyncProvider( null, null );

syncProvider = applyFilters(
'core.getSyncProvider',
Expand Down
7 changes: 3 additions & 4 deletions packages/sync/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
*/
import { connectIndexDb } from './connect-indexdb';
import { createWebRTCConnection } from './create-webrtc-connection';
import { createSyncProvider } from './provider';
import type { SyncProvider } from './types';
import { SyncProvider } from './provider';

export * as Y from 'yjs';
export { connectIndexDb } from './connect-indexdb';
export { createWebRTCConnection } from './create-webrtc-connection';
export { createSyncProvider } from './provider';
export { SyncProvider } from './provider';
export * from './types';

declare global {
Expand All @@ -35,7 +34,7 @@ declare global {
* @return {SyncProvider} The WebRTC sync provider.
*/
export function getWebRTCSyncProvider(): SyncProvider {
return createSyncProvider(
return new SyncProvider(
connectIndexDb,
createWebRTCConnection( {
password: window?.__experimentalCollaborativeEditingSecret,
Expand Down
127 changes: 76 additions & 51 deletions packages/sync/src/provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
/**
* WordPress dependencies
*/

/**
* External dependencies
*/
Expand All @@ -13,56 +9,64 @@ import * as Y from 'yjs';
import type {
ConnectDoc,
ConnectDocResult,
CRDTDoc,
ObjectID,
ObjectData,
ObjectType,
SyncConfig,
SyncProvider,
} from './types';

interface EntityState {
destroy: () => void;
ydoc: Y.Doc;
ydoc: CRDTDoc;
}

/**
* Create a sync provider.
*
* @param {ConnectDoc | null} connectLocal Connect the document to a local database.
* @param {ConnectDoc | null} connectRemote Connect the document to a remote sync connection.
* @return {SyncProvider} Sync provider.
*/
export const createSyncProvider = (
connectLocal: ConnectDoc | null,
connectRemote: ConnectDoc | null
): SyncProvider => {
const configs: Map< ObjectType, SyncConfig > = new Map<
export class SyncProvider {
protected connectLocal: ConnectDoc | null;
protected connectRemote: ConnectDoc | null;

protected configs: Map< ObjectType, SyncConfig > = new Map<
ObjectType,
SyncConfig
>();
const entityStates: Map< string, EntityState > = new Map<

protected entityStates: Map< string, EntityState > = new Map<
string,
EntityState
>();

/**
* Constructor.
*
* @param {ConnectDoc | null} connectLocal Connect the document to a local database.
* @param {ConnectDoc | null} connectRemote Connect the document to a remote sync connection.
*/
public constructor(
connectLocal: ConnectDoc | null,
connectRemote: ConnectDoc | null
) {
this.connectLocal = connectLocal;
this.connectRemote = connectRemote;
}

/**
* Fetch data from local database or remote source.
*
* @param {SyncConfig} syncConfig Sync configuration for the object type.
* @param {ObjectData} initialData Initial data to apply to the document.
* @param {Function} handleChanges Callback to call when data changes.
*/
async function bootstrap(
public async bootstrap(
syncConfig: SyncConfig,
initialData: ObjectData,
handleChanges: ( data: Partial< ObjectData > ) => void
): Promise< void > {
const ydoc = new Y.Doc( { meta: new Map() } );
const objectId = syncConfig.getObjectId( initialData );
const objectType = syncConfig.objectType;
const entityId = `${ objectType }_${ objectId }`;
const entityId = this.getEntityId( objectType, objectId );

configs.set( objectType, syncConfig );
this.configs.set( objectType, syncConfig );

const updateHandler: ( _update: Uint8Array, origin: string ) => void = (
_update,
Expand All @@ -77,9 +81,10 @@ export const createSyncProvider = (
ydoc.on( 'update', updateHandler );

const connectLocalResult: ConnectDocResult | null =
( await connectLocal?.( objectId, objectType, ydoc ) ) ?? null;
( await this.connectLocal?.( objectId, objectType, ydoc ) ) ?? null;
const connectRemoteResult =
( await connectRemote?.( objectId, objectType, ydoc ) ) ?? null;
( await this.connectRemote?.( objectId, objectType, ydoc ) ) ??
null;

const entityState: EntityState = {
destroy: () => {
Expand All @@ -88,14 +93,43 @@ export const createSyncProvider = (

ydoc.off( 'update', updateHandler );
ydoc.destroy();
entityStates.delete( entityId );
this.entityStates.delete( entityId );
},
ydoc,
};

entityStates.set( entityId, entityState );
this.entityStates.set( entityId, entityState );

update( objectType, initialData, initialData, 'gutenberg' );
this.update( objectType, initialData, initialData, 'gutenberg' );
}

/**
* Get the entity ID for the given object type and object ID.
*
* @param {ObjectType} objectType Object type.
* @param {ObjectID} objectId Object ID.
*/
protected getEntityId(
objectType: ObjectType,
objectId: ObjectID
): string {
return `${ objectType }_${ objectId }`;
}

/**
* Get the entity state for the given object type and object ID.
*
* @param {ObjectType} objectType Object type.
* @param {ObjectID} objectId Object ID.
*/
protected getEntityState(
objectType: ObjectType,
objectId: ObjectID
): EntityState | null {
return (
this.entityStates.get( this.getEntityId( objectType, objectId ) ) ??
null
);
}

/**
Expand All @@ -106,24 +140,22 @@ export const createSyncProvider = (
* @param {Partial< ObjectData >} changes Updates to make.
* @param {string} origin The source of change.
*/
function update(
public update(
objectType: ObjectType,
record: ObjectData,
changes: Partial< ObjectData >,
origin: string
) {
const objectId = configs.get( objectType )?.getObjectId( record );
const entityId = `${ objectType }_${ objectId }`;
const entityState = entityStates.get( entityId );
): void {
const objectId = this.configs.get( objectType )?.getObjectId( record );

if ( ! entityState ) {
throw new Error(
`Entity ${ objectType }:${ objectId } not found `
);
if ( ! objectId ) {
return;
}

entityState.ydoc.transact( () => {
configs
const entityState = this.getEntityState( objectType, objectId );

entityState?.ydoc.transact( () => {
this.configs
.get( objectType )
?.applyChangesToDoc( entityState.ydoc, changes );
}, origin );
Expand All @@ -132,20 +164,13 @@ export const createSyncProvider = (
/**
* Stop updating a document and discard it.
*
* @param {ObjectType} objectType Object type to load.
* @param {ObjectID} objectId Object ID to load.
* @param {ObjectType} objectType Object type to discard.
* @param {ObjectID} objectId Object ID to discard.
*/
function discard( objectType: ObjectType, objectId: ObjectID ) {
public discard( objectType: ObjectType, objectId: ObjectID ): void {
const entityId = `${ objectType }_${ objectId }`;

entityStates.get( entityId )?.destroy();
entityStates.delete( entityId );
this.getEntityState( objectType, objectId )?.destroy();
this.entityStates.delete( entityId );
}

return {
bootstrap,
configs,
discard,
update,
};
};
}
17 changes: 0 additions & 17 deletions packages/sync/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,3 @@ export type SyncConfig = {
getObjectId: ( data: ObjectData ) => ObjectID;
objectType: ObjectType;
};

export type SyncProvider = {
__fallback?: boolean;
bootstrap: (
syncConfig: SyncConfig,
initialData: ObjectData,
handleChanges: ( data: Partial< ObjectData > ) => void
) => Promise< void >;
configs: Map< ObjectType, SyncConfig >;
discard: ( type: ObjectType, id: ObjectID ) => void;
update: (
type: ObjectType,
record: ObjectData,
changes: Partial< ObjectData >,
origin: string
) => void;
};