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
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
/**
* Internal dependencies
*/
import {
TypedAwareness,
type EnhancedState,
type EqualityFieldCheck,
} from './awareness-types';
import { getTypedKeys, areMapsEqual } from '../utils';
import { REMOVAL_DELAY_IN_MS } from '../config';
import { REMOVAL_DELAY_IN_MS } from './config';
import { TypedAwareness } from './typed-awareness';
import type { EnhancedState, EqualityFieldCheck } from './types';
import { getTypedKeys, areMapsEqual } from './utils';

type AwarenessClientID = number;

Expand Down
2 changes: 1 addition & 1 deletion packages/core-data/src/awareness/base-awareness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
* WordPress dependencies
*/
import { resolveSelect } from '@wordpress/data';
import { AwarenessState } from '@wordpress/sync';

/**
* Internal dependencies
*/
import { AwarenessState } from './awareness-state';
import { STORE_NAME as coreStore } from '../name';
import { generateUserInfo, areUserInfosEqual } from './utils';

Expand Down
5 changes: 5 additions & 0 deletions packages/core-data/src/awareness/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ export const AWARENESS_CURSOR_UPDATE_THROTTLE_IN_MS = 100;
* Delay in milliseconds before updating the cursor position.
*/
export const LOCAL_CURSOR_UPDATE_DEBOUNCE_IN_MS = 5;

/**
* Delay in milliseconds before removing a user from presence indicators.
*/
export const REMOVAL_DELAY_IN_MS = 5000;
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* External dependencies
*/
import { Awareness } from 'y-protocols/awareness';
import { Awareness } from '@wordpress/sync';

/**
* Internal dependencies
*/
import { getRecordValue } from '../utils';
import { getRecordValue } from './utils';

/**
* Extended Awareness class with typed state accessors.
Expand Down Expand Up @@ -42,17 +42,3 @@ export class TypedAwareness< State extends object > extends Awareness {
super.setLocalStateField( field, value );
}
}

/**
* An enhanced state includes additional metadata about the user's connection.
*/
export type EnhancedState< State > = State & {
clientId: number;
isConnected: boolean;
isMe: boolean;
};

export type EqualityFieldCheck< State, FieldName extends keyof State > = (
value1?: State[ FieldName ],
value2?: State[ FieldName ]
) => boolean;
16 changes: 15 additions & 1 deletion packages/core-data/src/awareness/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import type { EnhancedState, Y } from '@wordpress/sync';
import type { Y } from '@wordpress/sync';

/**
* Internal dependencies
Expand Down Expand Up @@ -42,6 +42,15 @@ export interface PostEditorState extends BaseState {
editorState?: EditorState;
}

/**
* An enhanced state includes additional metadata about the user's connection.
*/
export type EnhancedState< State > = State & {
clientId: number;
isConnected: boolean;
isMe: boolean;
};

/**
* An enhanced post editor awareness state includes additional metadata about
* the user and their connection.
Expand Down Expand Up @@ -81,3 +90,8 @@ export type SerializableYItem = Pick<
left: SerializableYItemRef | null;
right: SerializableYItemRef | null;
};

export type EqualityFieldCheck< State, FieldName extends keyof State > = (
value1?: State[ FieldName ],
value2?: State[ FieldName ]
) => boolean;
37 changes: 37 additions & 0 deletions packages/core-data/src/awareness/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,28 @@ function getBrowserName(): string {
return browserName;
}

export function areMapsEqual< Key, Value >(
map1: Map< Key, Value >,
map2: Map< Key, Value >,
comparatorFn: ( value1: Value, value2: Value ) => boolean
): boolean {
if ( map1.size !== map2.size ) {
return false;
}

for ( const [ key, value1 ] of map1.entries() ) {
if ( ! map2.has( key ) ) {
return false;
}

if ( ! comparatorFn( value1, map2.get( key )! ) ) {
return false;
}
}

return true;
}

/**
* Check if two user infos are equal.
*
Expand Down Expand Up @@ -157,3 +179,18 @@ export function generateUserInfo(
enteredAt: Date.now(),
};
}

export function getRecordValue< RecordType, Key extends keyof RecordType >(
obj: unknown,
key: Key
): RecordType[ Key ] | null {
if ( 'object' === typeof obj && null !== obj && key in obj ) {
return ( obj as RecordType )[ key ];
}

return null;
}

export function getTypedKeys< T extends object >( obj: T ): Array< keyof T > {
return Object.keys( obj ) as Array< keyof T >;
}
2 changes: 1 addition & 1 deletion packages/core-data/src/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ async function loadPostTypeEntities() {
*
* @param {import('@wordpress/sync').CRDTDoc} ydoc
* @param {import('@wordpress/sync').ObjectID} objectId
* @return {import('@wordpress/sync').AwarenessState} AwarenessState instance
* @return {import('@wordpress/sync').Awareness} Awareness instance
*/
createAwareness: ( ydoc, objectId ) => {
const kind = 'postType';
Expand Down
3 changes: 1 addition & 2 deletions packages/core-data/src/utils/crdt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
* Internal dependencies
*/
import { BaseAwareness } from '../awareness/base-awareness';
import { type BaseState } from '../awareness/types';
import {
mergeCrdtBlocks,
type Block,
Expand Down Expand Up @@ -409,7 +408,7 @@ export function getPostChangesFromCRDTDoc(
* This default sync config can be used for entities that are flat maps of
* primitive values and do not require custom logic to merge changes.
*/
export const defaultSyncConfig: SyncConfig< BaseState > = {
export const defaultSyncConfig: SyncConfig = {
applyChangesToCRDTDoc: defaultApplyChangesToCRDTDoc,
createAwareness: ( ydoc: CRDTDoc ) => new BaseAwareness( ydoc ),
getChangesFromCRDTDoc: defaultGetChangesFromCRDTDoc,
Expand Down
12 changes: 2 additions & 10 deletions packages/sync/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ npm install @wordpress/sync --save

<!-- START TOKEN(Autogenerated API docs) -->

### AwarenessState
### Awareness

Abstract class to manage awareness and allow external code to subscribe to state updates.

_Type_

- `AwarenessState`
Awareness is used to track user presence and state.

### CRDT_DOC_META_PERSISTENCE_KEY

Expand Down Expand Up @@ -50,10 +46,6 @@ The sync manager orchestrates the lifecycle of syncing entity records. It create

Deltas are used to calculate incremental Y.Text updates.

### EnhancedState

An enhanced state includes additional metadata about the user's connection.

### LOCAL_EDITOR_ORIGIN

Origin string for CRDT document changes originating from the local editor.
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 @@ -58,8 +58,3 @@ 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';

/**
* Delay in milliseconds before removing a user from presence indicators.
*/
export const REMOVAL_DELAY_IN_MS = 5000;
10 changes: 5 additions & 5 deletions packages/sync/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
*/
export * as Y from 'yjs';

/**
* Awareness is used to track user presence and state.
*/
export { Awareness } from 'y-protocols/awareness';

/**
* Deltas are used to calculate incremental Y.Text updates.
*/
export { default as Delta } from './quill-delta/Delta';

export { AwarenessState } from './awareness/awareness-state';
export {
CRDT_DOC_META_PERSISTENCE_KEY,
CRDT_RECORD_MAP_KEY,
Expand All @@ -29,8 +33,4 @@ export {
} from './config';
export { createSyncManager } from './manager';

/**
* An enhanced state includes additional metadata about the user's connection.
*/
export type { EnhancedState } from './awareness/awareness-types';
export type * from './types';
11 changes: 5 additions & 6 deletions packages/sync/src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import * as Y from 'yjs';
import type { Awareness } from 'y-protocols/awareness';

/**
* Internal dependencies
Expand Down Expand Up @@ -30,18 +31,17 @@ import type {
} from './types';
import { createUndoManager } from './undo-manager';
import { createYjsDoc, markEntityAsSaved } from './utils';
import type { AwarenessState } from './awareness/awareness-state';

interface CollectionState {
awareness?: AwarenessState;
awareness?: Awareness;
handlers: CollectionHandlers;
syncConfig: SyncConfig;
unload: () => void;
ydoc: CRDTDoc;
}

interface EntityState {
awareness?: AwarenessState;
awareness?: Awareness;
handlers: RecordHandlers;
objectId: ObjectID;
objectType: ObjectType;
Expand Down Expand Up @@ -269,7 +269,6 @@ export function createSyncManager(): SyncManager {

// If the sync config supports awareness, create it.
const awareness = syncConfig.createAwareness?.( ydoc );
awareness?.setUp();

const collectionState: CollectionState = {
awareness,
Expand Down Expand Up @@ -325,12 +324,12 @@ export function createSyncManager(): SyncManager {
/**
* Get the awareness instance for the given object type and object ID, if supported.
*
* @template {AwarenessState<any>} State
* @template {Awareness} State
* @param {ObjectType} objectType Object type.
* @param {ObjectID} objectId Object ID.
* @return {State | undefined} The awareness instance, or undefined if not supported.
*/
function getAwareness< State extends AwarenessState< any > >(
function getAwareness< State extends Awareness >(
objectType: ObjectType,
objectId: ObjectID
): State | undefined {
Expand Down
14 changes: 1 addition & 13 deletions packages/sync/src/test/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,6 @@ import type {
RecordHandlers,
SyncConfig,
} from '../types';
import { AwarenessState } from '../awareness/awareness-state';

/**
* A minimal mock awareness class for testing.
*/
class MockAwarenessState extends AwarenessState {
protected equalityFieldChecks = {};

protected onSetUp(): void {
// No-op for testing.
}
}

// Mock dependencies.
jest.mock( '../providers', () => ( {
Expand Down Expand Up @@ -96,7 +84,7 @@ describe( 'SyncManager', () => {
),
supports: {},
createAwareness: jest.fn(
( ydoc: Y.Doc ) => new MockAwarenessState( ydoc )
( ydoc: Y.Doc ) => new Awareness( ydoc )
),
};

Expand Down
7 changes: 3 additions & 4 deletions packages/sync/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type { Awareness } from 'y-protocols/awareness';
/**
* Internal dependencies
*/
import type { AwarenessState } from './awareness/awareness-state';
import type { WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE } from './config';

export type CRDTDoc = Y.Doc;
Expand Down Expand Up @@ -73,15 +72,15 @@ export interface RecordHandlers {
saveRecord: () => Promise< void >;
}

export interface SyncConfig< State extends object = {} > {
export interface SyncConfig {
applyChangesToCRDTDoc: (
ydoc: Y.Doc,
changes: Partial< ObjectData >
) => void;
createAwareness?: (
ydoc: Y.Doc,
objectId?: ObjectID
) => AwarenessState< State > | undefined;
) => Awareness | undefined;
getChangesFromCRDTDoc: (
ydoc: Y.Doc,
editedRecord: ObjectData
Expand All @@ -94,7 +93,7 @@ export interface SyncManager {
objectType: ObjectType,
objectId: ObjectID
) => Record< string, string >;
getAwareness: < State extends AwarenessState< any > >(
getAwareness: < State extends Awareness >(
objectType: ObjectType,
objectId: ObjectID
) => State | undefined;
Expand Down
37 changes: 0 additions & 37 deletions packages/sync/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,40 +83,3 @@ export function deserializeCrdtDoc(
return null;
}
}

export function getRecordValue< RecordType, Key extends keyof RecordType >(
obj: unknown,
key: Key
): RecordType[ Key ] | null {
if ( 'object' === typeof obj && null !== obj && key in obj ) {
return ( obj as RecordType )[ key ];
}

return null;
}

export function getTypedKeys< T extends object >( obj: T ): Array< keyof T > {
return Object.keys( obj ) as Array< keyof T >;
}

export function areMapsEqual< Key, Value >(
map1: Map< Key, Value >,
map2: Map< Key, Value >,
comparatorFn: ( value1: Value, value2: Value ) => boolean
): boolean {
if ( map1.size !== map2.size ) {
return false;
}

for ( const [ key, value1 ] of map1.entries() ) {
if ( ! map2.has( key ) ) {
return false;
}

if ( ! comparatorFn( value1, map2.get( key )! ) ) {
return false;
}
}

return true;
}
Loading