@wordpress/sync
Advanced tools
| export {}; | ||
| //# sourceMappingURL=polling-manager.test.d.ts.map |
| {"version":3,"file":"polling-manager.test.d.ts","sourceRoot":"","sources":["../../../../src/providers/http-polling/test/polling-manager.test.ts"],"names":[],"mappings":""} |
| /** | ||
| * External dependencies | ||
| */ | ||
| import { | ||
| afterEach, | ||
| beforeEach, | ||
| describe, | ||
| expect, | ||
| it, | ||
| jest, | ||
| } from '@jest/globals'; | ||
| import type { SyncResponse } from '../types'; | ||
| // Mock all external dependencies before imports. | ||
| jest.mock( 'yjs', () => ( { | ||
| mergeUpdates: jest.fn( () => new Uint8Array() ), | ||
| applyUpdate: jest.fn(), | ||
| encodeStateAsUpdate: jest.fn( () => new Uint8Array() ), | ||
| } ) ); | ||
| jest.mock( 'lib0/encoding', () => ( { | ||
| createEncoder: jest.fn( () => ( {} ) ), | ||
| toUint8Array: jest.fn( () => new Uint8Array( [ 0 ] ) ), | ||
| } ) ); | ||
| jest.mock( 'lib0/decoding', () => ( { | ||
| createDecoder: jest.fn( () => ( {} ) ), | ||
| } ) ); | ||
| jest.mock( 'y-protocols/sync', () => ( { | ||
| writeSyncStep1: jest.fn(), | ||
| readSyncMessage: jest.fn(), | ||
| } ) ); | ||
| jest.mock( 'y-protocols/awareness', () => ( { | ||
| removeAwarenessStates: jest.fn(), | ||
| } ) ); | ||
| jest.mock( '../utils', () => ( { | ||
| base64ToUint8Array: jest.fn( () => new Uint8Array() ), | ||
| createSyncUpdate: jest.fn( ( _data: unknown, type: string ) => ( { | ||
| data: '', | ||
| type, | ||
| } ) ), | ||
| createUpdateQueue: jest.fn( () => ( { | ||
| add: jest.fn(), | ||
| addBulk: jest.fn(), | ||
| clear: jest.fn(), | ||
| get: jest.fn( () => [] ), | ||
| pause: jest.fn(), | ||
| restore: jest.fn(), | ||
| resume: jest.fn(), | ||
| size: jest.fn( () => 0 ), | ||
| } ) ), | ||
| postSyncUpdate: jest.fn(), | ||
| postSyncUpdateNonBlocking: jest.fn(), | ||
| } ) ); | ||
| interface PollingManager { | ||
| registerRoom: ( options: { | ||
| room: string; | ||
| doc: unknown; | ||
| awareness: unknown; | ||
| log: () => void; | ||
| onStatusChange: () => void; | ||
| onSync: () => void; | ||
| } ) => void; | ||
| unregisterRoom: ( room: string ) => void; | ||
| } | ||
| function createDeferred< T >() { | ||
| let resolve!: ( value: T ) => void; | ||
| const promise = new Promise< T >( ( res ) => { | ||
| resolve = res; | ||
| } ); | ||
| return { promise, resolve }; | ||
| } | ||
| function createMockDoc( clientID = 1 ) { | ||
| return { clientID, on: jest.fn(), off: jest.fn() }; | ||
| } | ||
| function createMockAwareness() { | ||
| return { | ||
| clientID: 1, | ||
| getLocalState: jest.fn( () => ( {} ) ), | ||
| getStates: jest.fn( () => new Map() ), | ||
| on: jest.fn(), | ||
| off: jest.fn(), | ||
| emit: jest.fn(), | ||
| }; | ||
| } | ||
| function simulateVisibilityChange( state: string ) { | ||
| Object.defineProperty( document, 'visibilityState', { | ||
| configurable: true, | ||
| get: () => state, | ||
| } ); | ||
| document.dispatchEvent( new Event( 'visibilitychange' ) ); | ||
| } | ||
| const syncResponse = { | ||
| rooms: [ | ||
| { | ||
| room: 'test-room', | ||
| end_cursor: 1, | ||
| awareness: {}, | ||
| updates: [], | ||
| }, | ||
| ], | ||
| }; | ||
| describe( 'polling-manager', () => { | ||
| let pollingManager: PollingManager; | ||
| let mockPostSyncUpdate: jest.Mock< | ||
| typeof import('../utils').postSyncUpdate | ||
| >; | ||
| beforeEach( () => { | ||
| jest.useFakeTimers(); | ||
| // Use isolateModules so each test gets fresh module-level state | ||
| // (isPolling, pollingTimeoutId, roomStates, etc.). | ||
| jest.isolateModules( () => { | ||
| pollingManager = require( '../polling-manager' ).pollingManager; | ||
| mockPostSyncUpdate = require( '../utils' ).postSyncUpdate; | ||
| } ); | ||
| } ); | ||
| afterEach( () => { | ||
| jest.clearAllTimers(); | ||
| jest.useRealTimers(); | ||
| Object.defineProperty( document, 'visibilityState', { | ||
| configurable: true, | ||
| get: () => 'visible', | ||
| } ); | ||
| } ); | ||
| describe( 'visibility change', () => { | ||
| it( 'does not spawn a duplicate poll when a request is in-flight', () => { | ||
| // Keep the first postSyncUpdate pending so we can simulate | ||
| // a visibility change while the request is in-flight. | ||
| const deferred = createDeferred< SyncResponse >(); | ||
| mockPostSyncUpdate.mockReturnValue( deferred.promise ); | ||
| pollingManager.registerRoom( { | ||
| room: 'test-room', | ||
| doc: createMockDoc(), | ||
| awareness: createMockAwareness(), | ||
| log: jest.fn(), | ||
| onStatusChange: jest.fn(), | ||
| onSync: jest.fn(), | ||
| } ); | ||
| // registerRoom → poll() → start() → postSyncUpdate (pending). | ||
| expect( mockPostSyncUpdate ).toHaveBeenCalledTimes( 1 ); | ||
| // Simulate tab hidden → visible while the request is in-flight. | ||
| simulateVisibilityChange( 'hidden' ); | ||
| simulateVisibilityChange( 'visible' ); | ||
| // No second poll should have been spawned. | ||
| expect( mockPostSyncUpdate ).toHaveBeenCalledTimes( 1 ); | ||
| } ); | ||
| it( 'repolls immediately when tab becomes visible with a pending timeout', async () => { | ||
| mockPostSyncUpdate.mockResolvedValue( syncResponse ); | ||
| pollingManager.registerRoom( { | ||
| room: 'test-room', | ||
| doc: createMockDoc(), | ||
| awareness: createMockAwareness(), | ||
| log: jest.fn(), | ||
| onStatusChange: jest.fn(), | ||
| onSync: jest.fn(), | ||
| } ); | ||
| // Flush so the first poll completes and schedules a timeout. | ||
| await jest.advanceTimersByTimeAsync( 0 ); | ||
| expect( mockPostSyncUpdate ).toHaveBeenCalledTimes( 1 ); | ||
| // Tab hidden → visible while a timeout is pending. | ||
| simulateVisibilityChange( 'hidden' ); | ||
| simulateVisibilityChange( 'visible' ); | ||
| // Should trigger an immediate repoll (not wait for timeout). | ||
| await jest.advanceTimersByTimeAsync( 0 ); | ||
| expect( mockPostSyncUpdate ).toHaveBeenCalledTimes( 2 ); | ||
| } ); | ||
| } ); | ||
| } ); |
@@ -19,2 +19,3 @@ // packages/sync/src/manager.ts | ||
| deserializeCrdtDoc, | ||
| initializeYjsDoc, | ||
| markEntityAsSaved, | ||
@@ -131,2 +132,3 @@ serializeCrdtDoc | ||
| stateMap.observe(onStateMapUpdate); | ||
| initializeYjsDoc(ydoc); | ||
| internal.applyPersistedCrdtDoc(objectType, objectId, record); | ||
@@ -196,2 +198,3 @@ } | ||
| stateMap.observe(onStateMapUpdate); | ||
| initializeYjsDoc(ydoc); | ||
| } | ||
@@ -308,3 +311,3 @@ function unloadEntity(objectType, objectId) { | ||
| } | ||
| function createPersistedCRDTDoc(objectType, objectId) { | ||
| async function createPersistedCRDTDoc(objectType, objectId) { | ||
| const entityId = getEntityId(objectType, objectId); | ||
@@ -315,2 +318,3 @@ const entityState = entityStates.get(entityId); | ||
| } | ||
| await new Promise((resolve) => setTimeout(resolve, 0)); | ||
| return serializeCrdtDoc(entityState.ydoc); | ||
@@ -317,0 +321,0 @@ } |
| { | ||
| "version": 3, | ||
| "sources": ["../src/manager.ts"], | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\n/**\n * Internal dependencies\n */\nimport {\n\tCRDT_RECORD_MAP_KEY,\n\tCRDT_STATE_MAP_KEY,\n\tCRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,\n\tLOCAL_SYNC_MANAGER_ORIGIN,\n} from './config';\nimport {\n\tlogPerformanceTiming,\n\tpassThru,\n\tyieldToEventLoop,\n} from './performance';\nimport { getProviderCreators } from './providers';\nimport type {\n\tCollectionHandlers,\n\tCRDTDoc,\n\tEntityID,\n\tObjectID,\n\tObjectData,\n\tObjectType,\n\tProviderCreator,\n\tRecordHandlers,\n\tSyncConfig,\n\tSyncManager,\n\tSyncManagerUpdateOptions,\n\tSyncUndoManager,\n} from './types';\nimport { createUndoManager } from './undo-manager';\nimport {\n\tcreateYjsDoc,\n\tdeserializeCrdtDoc,\n\tmarkEntityAsSaved,\n\tserializeCrdtDoc,\n} from './utils';\n\ninterface CollectionState {\n\tawareness?: Awareness;\n\thandlers: CollectionHandlers;\n\tsyncConfig: SyncConfig;\n\tunload: () => void;\n\tydoc: CRDTDoc;\n}\n\ninterface EntityState {\n\tawareness?: Awareness;\n\thandlers: RecordHandlers;\n\tobjectId: ObjectID;\n\tobjectType: ObjectType;\n\tsyncConfig: SyncConfig;\n\tunload: () => void;\n\tydoc: CRDTDoc;\n}\n\n/**\n * Get the entity ID for the given object type and object ID.\n *\n * @param {ObjectType} objectType Object type.\n * @param {ObjectID|null} objectId Object ID.\n */\nfunction getEntityId(\n\tobjectType: ObjectType,\n\tobjectId: ObjectID | null\n): EntityID {\n\treturn `${ objectType }_${ objectId }`;\n}\n\n/**\n * The sync manager orchestrates the lifecycle of syncing entity records. It\n * creates Yjs documents, connects to providers, creates awareness instances,\n * and coordinates with the `core-data` store.\n *\n * @param debug Whether to enable performance and debug logging.\n */\nexport function createSyncManager( debug = false ): SyncManager {\n\tconst debugWrap = debug ? logPerformanceTiming : passThru;\n\tconst collectionStates: Map< ObjectType, CollectionState > = new Map();\n\tconst entityStates: Map< EntityID, EntityState > = new Map();\n\n\t/**\n\t * A \"sync-aware\" undo manager for all synced entities. It is lazily created\n\t * when the first entity is loaded.\n\t *\n\t * IMPORTANT: In Gutenberg, the undo manager is effectively global and manages\n\t * undo/redo state for all entities. If the default WPUndoManager is used,\n\t * changes to entities are recorded in the `editEntityRecord` action:\n\t *\n\t * https://github.com/WordPress/gutenberg/blob/b63451e26e3c91b6bb291a2f9994722e3850417e/packages/core-data/src/actions.js#L428-L442\n\t *\n\t * In contrast, the `SyncUndoManager` only manages undo/redo for entities that\n\t * **are being synced by this sync manager**. The `addRecord` method is still\n\t * called in the code linked above, but it is a no-op. Yjs automatically tracks\n\t * changes to entities via the associated CRDT doc:\n\t *\n\t * https://github.com/WordPress/gutenberg/blob/b63451e26e3c91b6bb291a2f9994722e3850417e/packages/sync/src/undo-manager.ts#L42-L48\n\t *\n\t * This means that if at least one entity is being synced, then undo/redo\n\t * operations will be **restricted to synced entities only.**\n\t *\n\t * We could improve the `SyncUndoManager` to also track non-synced entities by\n\t * delegating to a secondary `WPUndoManager`, but this would add complexity\n\t * since we would need to maintain two separate undo/redo stacks and ensure\n\t * that they retain ordering and integrity.\n\t *\n\t * However, we also anticipate that most entities being edited in Gutenberg\n\t * will be synced entities (e.g. posts, pages, templates, template parts,\n\t * etc.), so this limitation may be temporary.\n\t */\n\tlet undoManager: SyncUndoManager | undefined;\n\n\t/**\n\t * Log debug messages if debugging is enabled.\n\t *\n\t * @param component The component or context related to the log message\n\t * @param message The debug message\n\t * @param entityId The entity ID related to the log message\n\t * @param context Additional debug context\n\t */\n\tfunction log(\n\t\tcomponent: string,\n\t\tmessage: string,\n\t\tentityId: string,\n\t\tcontext: object = {}\n\t): void {\n\t\tif ( ! debug ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log( `[SyncManager][${ component }]: ${ message }`, {\n\t\t\t...context,\n\t\t\tentityId,\n\t\t} );\n\t}\n\n\t/**\n\t * Load an entity for syncing and manage its lifecycle.\n\t *\n\t * @param {SyncConfig} syncConfig Sync configuration for the object type.\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {ObjectData} record Entity record representing this object type.\n\t * @param {RecordHandlers} handlers Handlers for updating and fetching the record.\n\t */\n\tasync function loadEntity(\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData,\n\t\thandlers: RecordHandlers\n\t): Promise< void > {\n\t\tconst providerCreators = getProviderCreators();\n\t\tconst entityId = getEntityId( objectType, objectId );\n\n\t\tif ( 0 === providerCreators.length ) {\n\t\t\tlog( 'loadEntity', 'no providers, skipping', entityId );\n\t\t\treturn; // No provider creators, so syncing is effectively disabled.\n\t\t}\n\n\t\tif ( entityStates.has( entityId ) ) {\n\t\t\tlog( 'loadEntity', 'already loaded', entityId );\n\t\t\treturn; // Already bootstrapped.\n\t\t}\n\n\t\tlog( 'loadEntity', 'loading', entityId );\n\n\t\thandlers = {\n\t\t\taddUndoMeta: debugWrap( handlers.addUndoMeta ),\n\t\t\teditRecord: debugWrap( handlers.editRecord ),\n\t\t\tgetEditedRecord: debugWrap( handlers.getEditedRecord ),\n\t\t\tonStatusChange: debugWrap( handlers.onStatusChange ),\n\t\t\trefetchRecord: debugWrap( handlers.refetchRecord ),\n\t\t\trestoreUndoMeta: debugWrap( handlers.restoreUndoMeta ),\n\t\t\tsaveRecord: debugWrap( handlers.saveRecord ),\n\t\t};\n\n\t\tconst ydoc = createYjsDoc( { objectType } );\n\t\tconst recordMap = ydoc.getMap( CRDT_RECORD_MAP_KEY );\n\t\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\t\tconst now = Date.now();\n\n\t\t// Clean up providers and in-memory state when the entity is unloaded.\n\t\tconst unload = (): void => {\n\t\t\tlog( 'loadEntity', 'unloading', entityId );\n\t\t\tproviderResults.forEach( ( result ) => result.destroy() );\n\t\t\thandlers.onStatusChange( null );\n\t\t\trecordMap.unobserveDeep( onRecordUpdate );\n\t\t\tstateMap.unobserve( onStateMapUpdate );\n\t\t\tydoc.destroy();\n\t\t\tentityStates.delete( entityId );\n\t\t};\n\n\t\t// If the sync config supports awareness, create it.\n\t\tconst awareness = syncConfig.createAwareness?.( ydoc, objectId );\n\n\t\t// When the CRDT document is updated by an UndoManager or a connection (not\n\t\t// a local origin), update the local store.\n\t\tconst onRecordUpdate = (\n\t\t\t_events: Y.YEvent< any >[],\n\t\t\ttransaction: Y.Transaction\n\t\t): void => {\n\t\t\tif (\n\t\t\t\ttransaction.local &&\n\t\t\t\t! ( transaction.origin instanceof Y.UndoManager )\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid internal.updateEntityRecord( objectType, objectId );\n\t\t};\n\n\t\tconst onStateMapUpdate = (\n\t\t\tevent: Y.YMapEvent< unknown >,\n\t\t\ttransaction: Y.Transaction\n\t\t) => {\n\t\t\tif ( transaction.local ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.keysChanged.forEach( ( key ) => {\n\t\t\t\tswitch ( key ) {\n\t\t\t\t\tcase SAVED_AT_KEY:\n\t\t\t\t\t\tconst newValue = stateMap.get( SAVED_AT_KEY );\n\t\t\t\t\t\tif ( 'number' === typeof newValue && newValue > now ) {\n\t\t\t\t\t\t\t// Another peer has saved the record. Refetch it so that we have\n\t\t\t\t\t\t\t// a correct understanding of our own unsaved edits.\n\t\t\t\t\t\t\tlog( 'loadEntity', 'refetching record', entityId );\n\t\t\t\t\t\t\tvoid handlers.refetchRecord().catch( () => {} );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\n\t\t// Lazily create the undo manager when the first entity is loaded.\n\t\tif ( ! undoManager ) {\n\t\t\tundoManager = createUndoManager();\n\t\t}\n\n\t\tconst { addUndoMeta, restoreUndoMeta } = handlers;\n\t\tundoManager.addToScope( recordMap, {\n\t\t\taddUndoMeta,\n\t\t\trestoreUndoMeta,\n\t\t} );\n\n\t\tconst entityState: EntityState = {\n\t\t\tawareness,\n\t\t\thandlers,\n\t\t\tobjectId,\n\t\t\tobjectType,\n\t\t\tsyncConfig,\n\t\t\tunload,\n\t\t\tydoc,\n\t\t};\n\n\t\tentityStates.set( entityId, entityState );\n\n\t\t// Create providers for the given entity and its Yjs document.\n\t\tlog( 'loadEntity', 'connecting', entityId );\n\t\tconst providerResults = await Promise.all(\n\t\t\tproviderCreators.map( async ( create ) => {\n\t\t\t\tconst provider = await create( {\n\t\t\t\t\tobjectType,\n\t\t\t\t\tobjectId,\n\t\t\t\t\tydoc,\n\t\t\t\t\tawareness,\n\t\t\t\t} );\n\n\t\t\t\t// Attach status listener after provider creation.\n\t\t\t\tprovider.on( 'status', handlers.onStatusChange );\n\n\t\t\t\treturn provider;\n\t\t\t} )\n\t\t);\n\n\t\t// Attach observers.\n\t\trecordMap.observeDeep( onRecordUpdate );\n\t\tstateMap.observe( onStateMapUpdate );\n\n\t\t// Get and apply the persisted CRDT document, if it exists.\n\t\tinternal.applyPersistedCrdtDoc( objectType, objectId, record );\n\t}\n\n\t/**\n\t * Load a collection for syncing and manage its lifecycle.\n\t *\n\t * @param {SyncConfig} syncConfig Sync configuration for the object type.\n\t * @param {ObjectType} objectType Object type.\n\t * @param {CollectionHandlers} handlers Handlers for updating the collection.\n\t */\n\tasync function loadCollection(\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\thandlers: CollectionHandlers\n\t): Promise< void > {\n\t\tconst providerCreators: ProviderCreator[] = getProviderCreators();\n\t\tconst entityId = getEntityId( objectType, null );\n\n\t\tif ( 0 === providerCreators.length ) {\n\t\t\tlog( 'loadCollection', 'no providers, skipping', entityId );\n\t\t\treturn; // No provider creators, so syncing is effectively disabled.\n\t\t}\n\n\t\tif ( collectionStates.has( objectType ) ) {\n\t\t\tlog( 'loadCollection', 'already loaded', entityId );\n\t\t\treturn; // Already loaded.\n\t\t}\n\n\t\tlog( 'loadCollection', 'loading', entityId );\n\n\t\tconst ydoc = createYjsDoc( { collection: true, objectType } );\n\t\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\t\tconst now = Date.now();\n\n\t\t// Clean up providers and in-memory state when the entity is unloaded.\n\t\tconst unload = (): void => {\n\t\t\tlog( 'loadCollection', 'unloading', entityId );\n\t\t\tproviderResults.forEach( ( result ) => result.destroy() );\n\t\t\thandlers.onStatusChange( null );\n\t\t\tstateMap.unobserve( onStateMapUpdate );\n\t\t\tydoc.destroy();\n\t\t\tcollectionStates.delete( objectType );\n\t\t};\n\n\t\tconst onStateMapUpdate = (\n\t\t\tevent: Y.YMapEvent< unknown >,\n\t\t\ttransaction: Y.Transaction\n\t\t) => {\n\t\t\tif ( transaction.local ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.keysChanged.forEach( ( key ) => {\n\t\t\t\tswitch ( key ) {\n\t\t\t\t\tcase SAVED_AT_KEY:\n\t\t\t\t\t\tconst newValue = stateMap.get( SAVED_AT_KEY );\n\t\t\t\t\t\tif ( 'number' === typeof newValue && newValue > now ) {\n\t\t\t\t\t\t\t// Another peer has mutated the collection. Refetch it so that we\n\t\t\t\t\t\t\t// obtain the updated records.\n\t\t\t\t\t\t\tvoid handlers.refetchRecords().catch( () => {} );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\n\t\t// If the sync config supports awareness, create it.\n\t\tconst awareness = syncConfig.createAwareness?.( ydoc );\n\n\t\tconst collectionState: CollectionState = {\n\t\t\tawareness,\n\t\t\thandlers,\n\t\t\tsyncConfig,\n\t\t\tunload,\n\t\t\tydoc,\n\t\t};\n\n\t\tcollectionStates.set( objectType, collectionState );\n\n\t\t// Create providers for the given entity and its Yjs document.\n\t\tlog( 'loadCollection', 'connecting', entityId );\n\t\tconst providerResults = await Promise.all(\n\t\t\tproviderCreators.map( async ( create ) => {\n\t\t\t\tconst provider = await create( {\n\t\t\t\t\tawareness,\n\t\t\t\t\tobjectType,\n\t\t\t\t\tobjectId: null,\n\t\t\t\t\tydoc,\n\t\t\t\t} );\n\n\t\t\t\t// Attach status listener after provider creation.\n\t\t\t\tprovider.on( 'status', handlers.onStatusChange );\n\n\t\t\t\treturn provider;\n\t\t\t} )\n\t\t);\n\n\t\t// Attach observers.\n\t\tstateMap.observe( onStateMapUpdate );\n\t}\n\n\t/**\n\t * Unload an entity, stop syncing, destroy its in-memory state, and trigger an\n\t * update of the collection.\n\t *\n\t * @param {ObjectType} objectType Object type to discard.\n\t * @param {ObjectID} objectId Object ID to discard, or null for collections.\n\t */\n\tfunction unloadEntity( objectType: ObjectType, objectId: ObjectID ): void {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tlog( 'unloadEntity', 'unloading', entityId );\n\t\tentityStates.get( entityId )?.unload();\n\t\tupdateCRDTDoc( objectType, null, {}, origin, { isSave: true } );\n\t}\n\n\t/**\n\t * Get the awareness instance for the given object type and object ID, if supported.\n\t *\n\t * @template {Awareness} State\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @return {State | undefined} The awareness instance, or undefined if not supported.\n\t */\n\tfunction getAwareness< State extends Awareness >(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): State | undefined {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState || ! entityState.awareness ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn entityState.awareness as State;\n\t}\n\n\t/**\n\t * Load and inspect the persisted CRDT document. If supported and it exists,\n\t * compare it against the current entity record. If there are differences,\n\t * apply the changes from the entity record.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {ObjectData} record Entity record representing this object type.\n\t */\n\tfunction _applyPersistedCrdtDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData\n\t): void {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'no entity state', entityId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst {\n\t\t\thandlers,\n\t\t\tsyncConfig: {\n\t\t\t\tapplyChangesToCRDTDoc,\n\t\t\t\tgetChangesFromCRDTDoc,\n\t\t\t\tgetPersistedCRDTDoc,\n\t\t\t},\n\t\t\tydoc: targetDoc,\n\t\t} = entityState;\n\n\t\t// Get the persisted CRDT document, if it exists.\n\t\tconst serialized = getPersistedCRDTDoc?.( record );\n\t\tconst tempDoc = serialized ? deserializeCrdtDoc( serialized ) : null;\n\n\t\tif ( ! tempDoc ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'no persisted doc', entityId );\n\t\t\t// Apply the current record as changes and trigger a save, which will\n\t\t\t// persist the CRDT document. (The entity should call `createPersistedCRDTDoc`\n\t\t\t// via its pre-persist hook.)\n\t\t\ttargetDoc.transact( () => {\n\t\t\t\tapplyChangesToCRDTDoc( targetDoc, record );\n\t\t\t\thandlers.saveRecord();\n\t\t\t}, LOCAL_SYNC_MANAGER_ORIGIN );\n\t\t\treturn;\n\t\t}\n\n\t\t// Apply the persisted document to the current document as a single update.\n\t\t// This is done even if the persisted document has been invalidated. This\n\t\t// prevents a newly joining peer (or refreshing user) from re-initializing\n\t\t// the CRDT document (the \"initialization problem\").\n\t\t//\n\t\t// IMPORTANT: Do not wrap this in a transaction with the local origin. It\n\t\t// effectively advances the state vector for the current client, which causes\n\t\t// Yjs to think that another client is using this client ID.\n\t\tconst update = Y.encodeStateAsUpdateV2( tempDoc );\n\t\tY.applyUpdateV2( targetDoc, update );\n\n\t\t// Compute the differences between the persisted doc and the current\n\t\t// record. This can happen when:\n\t\t//\n\t\t// 1. The server makes updates on save that mutate the entity. Example: On\n\t\t// initial save, the server adds the \"Uncategorized\" category to the\n\t\t// post.\n\t\t// 2. An \"out-of-band\" update occurs. Example: a WP-CLI command or direct\n\t\t// database update mutates the entity.\n\t\t// 3. Unsaved changes are synced from a peer _before_ this code runs. We\n\t\t// can't control when (or if) remote changes are synced, so this is a\n\t\t// race condition.\n\t\tconst invalidations = getChangesFromCRDTDoc( tempDoc, record );\n\t\tconst invalidatedKeys = Object.keys( invalidations );\n\n\t\t// Destroy the temporary document to prevent leaks.\n\t\ttempDoc.destroy();\n\n\t\tif ( 0 === invalidatedKeys.length ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'valid persisted doc', entityId );\n\t\t\t// The persisted CRDT document is valid. There are no updates to apply.\n\t\t\treturn;\n\t\t}\n\n\t\tlog( 'applyPersistedCrdtDoc', 'invalidated keys', entityId, {\n\t\t\tinvalidatedKeys,\n\t\t} );\n\n\t\t// Use the invalidated keys to get the updated values from the entity.\n\t\tconst changes = invalidatedKeys.reduce(\n\t\t\t( acc, key ) =>\n\t\t\t\tObject.assign( acc, {\n\t\t\t\t\t[ key ]: record[ key ],\n\t\t\t\t} ),\n\t\t\t{}\n\t\t);\n\n\t\t// Apply the changes and trigger a save, which will persist the CRDT\n\t\t// document. (The entity should call `createPersistedCRDTDoc` via its\n\t\t// pre-persist hook.)\n\t\ttargetDoc.transact( () => {\n\t\t\tapplyChangesToCRDTDoc( targetDoc, changes );\n\t\t\thandlers.saveRecord();\n\t\t}, LOCAL_SYNC_MANAGER_ORIGIN );\n\t}\n\n\t/**\n\t * Update CRDT document with changes from the local store.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {Partial< ObjectData >} changes Updates to make.\n\t * @param {string} origin The source of change.\n\t * @param {SyncManagerUpdateOptions} options Optional flags for the update.\n\t * @param {boolean} options.isSave Whether this update is part of a save operation. Defaults to false.\n\t * @param {boolean} options.isNewUndoLevel Whether to create a new undo level for this change. Defaults to false.\n\t */\n\tfunction updateCRDTDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID | null,\n\t\tchanges: Partial< ObjectData >,\n\t\torigin: string,\n\t\toptions: SyncManagerUpdateOptions = {}\n\t): void {\n\t\tconst { isSave = false, isNewUndoLevel = false } = options;\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\t\tconst collectionState = collectionStates.get( objectType );\n\n\t\tif ( entityState ) {\n\t\t\tconst { syncConfig, ydoc } = entityState;\n\n\t\t\t// If this is change should create a new undo level, tell the undo\n\t\t\t// manager to stop capturing and create a new undo group.\n\t\t\t// We can't do this in the undo manager itself, because addRecord() is\n\t\t\t// called after the CRDT changes have been applied, and we want to\n\t\t\t// ensure that the undo set is created before the changes are applied.\n\t\t\tif ( isNewUndoLevel && undoManager ) {\n\t\t\t\tundoManager.stopCapturing?.();\n\t\t\t}\n\n\t\t\tydoc.transact( () => {\n\t\t\t\tlog( 'updateCRDTDoc', 'applying changes', entityId, {\n\t\t\t\t\tchangedKeys: Object.keys( changes ),\n\t\t\t\t} );\n\t\t\t\tsyncConfig.applyChangesToCRDTDoc( ydoc, changes );\n\n\t\t\t\tif ( isSave ) {\n\t\t\t\t\tmarkEntityAsSaved( ydoc );\n\t\t\t\t}\n\t\t\t}, origin );\n\t\t}\n\n\t\tif ( collectionState && isSave ) {\n\t\t\tcollectionState.ydoc.transact( () => {\n\t\t\t\tmarkEntityAsSaved( collectionState.ydoc );\n\t\t\t}, origin );\n\t\t}\n\t}\n\n\t/**\n\t * Update the entity record in the local store with changes from the CRDT\n\t * document.\n\t *\n\t * @param {ObjectType} objectType Object type of record to update.\n\t * @param {ObjectID} objectId Object ID of record to update.\n\t */\n\tasync function _updateEntityRecord(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): Promise< void > {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState ) {\n\t\t\tlog( 'updateEntityRecord', 'no entity state', entityId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst { handlers, syncConfig, ydoc } = entityState;\n\n\t\t// Determine which synced properties have actually changed by comparing\n\t\t// them against the current edited entity record.\n\t\tconst changes = syncConfig.getChangesFromCRDTDoc(\n\t\t\tydoc,\n\t\t\tawait handlers.getEditedRecord()\n\t\t);\n\n\t\tconst changedKeys = Object.keys( changes );\n\n\t\tif ( 0 === changedKeys.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlog( 'updateEntityRecord', 'changes', entityId, {\n\t\t\tchangedKeys,\n\t\t} );\n\t\thandlers.editRecord( changes );\n\t}\n\n\t/**\n\t * Create object meta to persist the CRDT document in the entity record.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t */\n\tfunction createPersistedCRDTDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): string | null {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState?.ydoc ) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn serializeCrdtDoc( entityState.ydoc );\n\t}\n\n\t// Collect internal functions so that they can be wrapped before calling.\n\tconst internal = {\n\t\tapplyPersistedCrdtDoc: debugWrap( _applyPersistedCrdtDoc ),\n\t\tupdateEntityRecord: debugWrap( _updateEntityRecord ),\n\t};\n\n\t// Wrap and return the public API.\n\treturn {\n\t\tcreatePersistedCRDTDoc: debugWrap( createPersistedCRDTDoc ),\n\t\tgetAwareness,\n\t\tload: debugWrap( loadEntity ),\n\t\tloadCollection: debugWrap( loadCollection ),\n\t\t// Use getter to ensure we always return the current value of `undoManager`.\n\t\tget undoManager(): SyncUndoManager | undefined {\n\t\t\treturn undoManager;\n\t\t},\n\t\tunload: debugWrap( unloadEntity ),\n\t\tupdate: debugWrap( yieldToEventLoop( updateCRDTDoc ) ),\n\t};\n}\n"], | ||
| "mappings": "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", | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\n/**\n * Internal dependencies\n */\nimport {\n\tCRDT_RECORD_MAP_KEY,\n\tCRDT_STATE_MAP_KEY,\n\tCRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,\n\tLOCAL_SYNC_MANAGER_ORIGIN,\n} from './config';\nimport {\n\tlogPerformanceTiming,\n\tpassThru,\n\tyieldToEventLoop,\n} from './performance';\nimport { getProviderCreators } from './providers';\nimport type {\n\tCollectionHandlers,\n\tCRDTDoc,\n\tEntityID,\n\tObjectID,\n\tObjectData,\n\tObjectType,\n\tProviderCreator,\n\tRecordHandlers,\n\tSyncConfig,\n\tSyncManager,\n\tSyncManagerUpdateOptions,\n\tSyncUndoManager,\n} from './types';\nimport { createUndoManager } from './undo-manager';\nimport {\n\tcreateYjsDoc,\n\tdeserializeCrdtDoc,\n\tinitializeYjsDoc,\n\tmarkEntityAsSaved,\n\tserializeCrdtDoc,\n} from './utils';\n\ninterface CollectionState {\n\tawareness?: Awareness;\n\thandlers: CollectionHandlers;\n\tsyncConfig: SyncConfig;\n\tunload: () => void;\n\tydoc: CRDTDoc;\n}\n\ninterface EntityState {\n\tawareness?: Awareness;\n\thandlers: RecordHandlers;\n\tobjectId: ObjectID;\n\tobjectType: ObjectType;\n\tsyncConfig: SyncConfig;\n\tunload: () => void;\n\tydoc: CRDTDoc;\n}\n\n/**\n * Get the entity ID for the given object type and object ID.\n *\n * @param {ObjectType} objectType Object type.\n * @param {ObjectID|null} objectId Object ID.\n */\nfunction getEntityId(\n\tobjectType: ObjectType,\n\tobjectId: ObjectID | null\n): EntityID {\n\treturn `${ objectType }_${ objectId }`;\n}\n\n/**\n * The sync manager orchestrates the lifecycle of syncing entity records. It\n * creates Yjs documents, connects to providers, creates awareness instances,\n * and coordinates with the `core-data` store.\n *\n * @param debug Whether to enable performance and debug logging.\n */\nexport function createSyncManager( debug = false ): SyncManager {\n\tconst debugWrap = debug ? logPerformanceTiming : passThru;\n\tconst collectionStates: Map< ObjectType, CollectionState > = new Map();\n\tconst entityStates: Map< EntityID, EntityState > = new Map();\n\n\t/**\n\t * A \"sync-aware\" undo manager for all synced entities. It is lazily created\n\t * when the first entity is loaded.\n\t *\n\t * IMPORTANT: In Gutenberg, the undo manager is effectively global and manages\n\t * undo/redo state for all entities. If the default WPUndoManager is used,\n\t * changes to entities are recorded in the `editEntityRecord` action:\n\t *\n\t * https://github.com/WordPress/gutenberg/blob/b63451e26e3c91b6bb291a2f9994722e3850417e/packages/core-data/src/actions.js#L428-L442\n\t *\n\t * In contrast, the `SyncUndoManager` only manages undo/redo for entities that\n\t * **are being synced by this sync manager**. The `addRecord` method is still\n\t * called in the code linked above, but it is a no-op. Yjs automatically tracks\n\t * changes to entities via the associated CRDT doc:\n\t *\n\t * https://github.com/WordPress/gutenberg/blob/b63451e26e3c91b6bb291a2f9994722e3850417e/packages/sync/src/undo-manager.ts#L42-L48\n\t *\n\t * This means that if at least one entity is being synced, then undo/redo\n\t * operations will be **restricted to synced entities only.**\n\t *\n\t * We could improve the `SyncUndoManager` to also track non-synced entities by\n\t * delegating to a secondary `WPUndoManager`, but this would add complexity\n\t * since we would need to maintain two separate undo/redo stacks and ensure\n\t * that they retain ordering and integrity.\n\t *\n\t * However, we also anticipate that most entities being edited in Gutenberg\n\t * will be synced entities (e.g. posts, pages, templates, template parts,\n\t * etc.), so this limitation may be temporary.\n\t */\n\tlet undoManager: SyncUndoManager | undefined;\n\n\t/**\n\t * Log debug messages if debugging is enabled.\n\t *\n\t * @param component The component or context related to the log message\n\t * @param message The debug message\n\t * @param entityId The entity ID related to the log message\n\t * @param context Additional debug context\n\t */\n\tfunction log(\n\t\tcomponent: string,\n\t\tmessage: string,\n\t\tentityId: string,\n\t\tcontext: object = {}\n\t): void {\n\t\tif ( ! debug ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log( `[SyncManager][${ component }]: ${ message }`, {\n\t\t\t...context,\n\t\t\tentityId,\n\t\t} );\n\t}\n\n\t/**\n\t * Load an entity for syncing and manage its lifecycle.\n\t *\n\t * @param {SyncConfig} syncConfig Sync configuration for the object type.\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {ObjectData} record Entity record representing this object type.\n\t * @param {RecordHandlers} handlers Handlers for updating and fetching the record.\n\t */\n\tasync function loadEntity(\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData,\n\t\thandlers: RecordHandlers\n\t): Promise< void > {\n\t\tconst providerCreators = getProviderCreators();\n\t\tconst entityId = getEntityId( objectType, objectId );\n\n\t\tif ( 0 === providerCreators.length ) {\n\t\t\tlog( 'loadEntity', 'no providers, skipping', entityId );\n\t\t\treturn; // No provider creators, so syncing is effectively disabled.\n\t\t}\n\n\t\tif ( entityStates.has( entityId ) ) {\n\t\t\tlog( 'loadEntity', 'already loaded', entityId );\n\t\t\treturn; // Already bootstrapped.\n\t\t}\n\n\t\tlog( 'loadEntity', 'loading', entityId );\n\n\t\thandlers = {\n\t\t\taddUndoMeta: debugWrap( handlers.addUndoMeta ),\n\t\t\teditRecord: debugWrap( handlers.editRecord ),\n\t\t\tgetEditedRecord: debugWrap( handlers.getEditedRecord ),\n\t\t\tonStatusChange: debugWrap( handlers.onStatusChange ),\n\t\t\trefetchRecord: debugWrap( handlers.refetchRecord ),\n\t\t\trestoreUndoMeta: debugWrap( handlers.restoreUndoMeta ),\n\t\t\tsaveRecord: debugWrap( handlers.saveRecord ),\n\t\t};\n\n\t\tconst ydoc = createYjsDoc( { objectType } );\n\t\tconst recordMap = ydoc.getMap( CRDT_RECORD_MAP_KEY );\n\t\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\t\tconst now = Date.now();\n\n\t\t// Clean up providers and in-memory state when the entity is unloaded.\n\t\tconst unload = (): void => {\n\t\t\tlog( 'loadEntity', 'unloading', entityId );\n\t\t\tproviderResults.forEach( ( result ) => result.destroy() );\n\t\t\thandlers.onStatusChange( null );\n\t\t\trecordMap.unobserveDeep( onRecordUpdate );\n\t\t\tstateMap.unobserve( onStateMapUpdate );\n\t\t\tydoc.destroy();\n\t\t\tentityStates.delete( entityId );\n\t\t};\n\n\t\t// If the sync config supports awareness, create it.\n\t\tconst awareness = syncConfig.createAwareness?.( ydoc, objectId );\n\n\t\t// When the CRDT document is updated by an UndoManager or a connection (not\n\t\t// a local origin), update the local store.\n\t\tconst onRecordUpdate = (\n\t\t\t_events: Y.YEvent< any >[],\n\t\t\ttransaction: Y.Transaction\n\t\t): void => {\n\t\t\tif (\n\t\t\t\ttransaction.local &&\n\t\t\t\t! ( transaction.origin instanceof Y.UndoManager )\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid internal.updateEntityRecord( objectType, objectId );\n\t\t};\n\n\t\tconst onStateMapUpdate = (\n\t\t\tevent: Y.YMapEvent< unknown >,\n\t\t\ttransaction: Y.Transaction\n\t\t) => {\n\t\t\tif ( transaction.local ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.keysChanged.forEach( ( key ) => {\n\t\t\t\tswitch ( key ) {\n\t\t\t\t\tcase SAVED_AT_KEY:\n\t\t\t\t\t\tconst newValue = stateMap.get( SAVED_AT_KEY );\n\t\t\t\t\t\tif ( 'number' === typeof newValue && newValue > now ) {\n\t\t\t\t\t\t\t// Another peer has saved the record. Refetch it so that we have\n\t\t\t\t\t\t\t// a correct understanding of our own unsaved edits.\n\t\t\t\t\t\t\tlog( 'loadEntity', 'refetching record', entityId );\n\t\t\t\t\t\t\tvoid handlers.refetchRecord().catch( () => {} );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\n\t\t// Lazily create the undo manager when the first entity is loaded.\n\t\tif ( ! undoManager ) {\n\t\t\tundoManager = createUndoManager();\n\t\t}\n\n\t\tconst { addUndoMeta, restoreUndoMeta } = handlers;\n\t\tundoManager.addToScope( recordMap, {\n\t\t\taddUndoMeta,\n\t\t\trestoreUndoMeta,\n\t\t} );\n\n\t\tconst entityState: EntityState = {\n\t\t\tawareness,\n\t\t\thandlers,\n\t\t\tobjectId,\n\t\t\tobjectType,\n\t\t\tsyncConfig,\n\t\t\tunload,\n\t\t\tydoc,\n\t\t};\n\n\t\tentityStates.set( entityId, entityState );\n\n\t\t// Create providers for the given entity and its Yjs document.\n\t\tlog( 'loadEntity', 'connecting', entityId );\n\t\tconst providerResults = await Promise.all(\n\t\t\tproviderCreators.map( async ( create ) => {\n\t\t\t\tconst provider = await create( {\n\t\t\t\t\tobjectType,\n\t\t\t\t\tobjectId,\n\t\t\t\t\tydoc,\n\t\t\t\t\tawareness,\n\t\t\t\t} );\n\n\t\t\t\t// Attach status listener after provider creation.\n\t\t\t\tprovider.on( 'status', handlers.onStatusChange );\n\n\t\t\t\treturn provider;\n\t\t\t} )\n\t\t);\n\n\t\t// Attach observers.\n\t\trecordMap.observeDeep( onRecordUpdate );\n\t\tstateMap.observe( onStateMapUpdate );\n\n\t\t// Initialize the Yjs document with the necessary CRDT state.\n\t\tinitializeYjsDoc( ydoc );\n\n\t\t// Get and apply the persisted CRDT document, if it exists.\n\t\tinternal.applyPersistedCrdtDoc( objectType, objectId, record );\n\t}\n\n\t/**\n\t * Load a collection for syncing and manage its lifecycle.\n\t *\n\t * @param {SyncConfig} syncConfig Sync configuration for the object type.\n\t * @param {ObjectType} objectType Object type.\n\t * @param {CollectionHandlers} handlers Handlers for updating the collection.\n\t */\n\tasync function loadCollection(\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\thandlers: CollectionHandlers\n\t): Promise< void > {\n\t\tconst providerCreators: ProviderCreator[] = getProviderCreators();\n\t\tconst entityId = getEntityId( objectType, null );\n\n\t\tif ( 0 === providerCreators.length ) {\n\t\t\tlog( 'loadCollection', 'no providers, skipping', entityId );\n\t\t\treturn; // No provider creators, so syncing is effectively disabled.\n\t\t}\n\n\t\tif ( collectionStates.has( objectType ) ) {\n\t\t\tlog( 'loadCollection', 'already loaded', entityId );\n\t\t\treturn; // Already loaded.\n\t\t}\n\n\t\tlog( 'loadCollection', 'loading', entityId );\n\n\t\tconst ydoc = createYjsDoc( { collection: true, objectType } );\n\t\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\t\tconst now = Date.now();\n\n\t\t// Clean up providers and in-memory state when the entity is unloaded.\n\t\tconst unload = (): void => {\n\t\t\tlog( 'loadCollection', 'unloading', entityId );\n\t\t\tproviderResults.forEach( ( result ) => result.destroy() );\n\t\t\thandlers.onStatusChange( null );\n\t\t\tstateMap.unobserve( onStateMapUpdate );\n\t\t\tydoc.destroy();\n\t\t\tcollectionStates.delete( objectType );\n\t\t};\n\n\t\tconst onStateMapUpdate = (\n\t\t\tevent: Y.YMapEvent< unknown >,\n\t\t\ttransaction: Y.Transaction\n\t\t) => {\n\t\t\tif ( transaction.local ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.keysChanged.forEach( ( key ) => {\n\t\t\t\tswitch ( key ) {\n\t\t\t\t\tcase SAVED_AT_KEY:\n\t\t\t\t\t\tconst newValue = stateMap.get( SAVED_AT_KEY );\n\t\t\t\t\t\tif ( 'number' === typeof newValue && newValue > now ) {\n\t\t\t\t\t\t\t// Another peer has mutated the collection. Refetch it so that we\n\t\t\t\t\t\t\t// obtain the updated records.\n\t\t\t\t\t\t\tvoid handlers.refetchRecords().catch( () => {} );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\n\t\t// If the sync config supports awareness, create it.\n\t\tconst awareness = syncConfig.createAwareness?.( ydoc );\n\n\t\tconst collectionState: CollectionState = {\n\t\t\tawareness,\n\t\t\thandlers,\n\t\t\tsyncConfig,\n\t\t\tunload,\n\t\t\tydoc,\n\t\t};\n\n\t\tcollectionStates.set( objectType, collectionState );\n\n\t\t// Create providers for the given entity and its Yjs document.\n\t\tlog( 'loadCollection', 'connecting', entityId );\n\t\tconst providerResults = await Promise.all(\n\t\t\tproviderCreators.map( async ( create ) => {\n\t\t\t\tconst provider = await create( {\n\t\t\t\t\tawareness,\n\t\t\t\t\tobjectType,\n\t\t\t\t\tobjectId: null,\n\t\t\t\t\tydoc,\n\t\t\t\t} );\n\n\t\t\t\t// Attach status listener after provider creation.\n\t\t\t\tprovider.on( 'status', handlers.onStatusChange );\n\n\t\t\t\treturn provider;\n\t\t\t} )\n\t\t);\n\n\t\t// Attach observers.\n\t\tstateMap.observe( onStateMapUpdate );\n\n\t\t// Initialize the Yjs document with the necessary CRDT state.\n\t\tinitializeYjsDoc( ydoc );\n\t}\n\n\t/**\n\t * Unload an entity, stop syncing, destroy its in-memory state, and trigger an\n\t * update of the collection.\n\t *\n\t * @param {ObjectType} objectType Object type to discard.\n\t * @param {ObjectID} objectId Object ID to discard, or null for collections.\n\t */\n\tfunction unloadEntity( objectType: ObjectType, objectId: ObjectID ): void {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tlog( 'unloadEntity', 'unloading', entityId );\n\t\tentityStates.get( entityId )?.unload();\n\t\tupdateCRDTDoc( objectType, null, {}, origin, { isSave: true } );\n\t}\n\n\t/**\n\t * Get the awareness instance for the given object type and object ID, if supported.\n\t *\n\t * @template {Awareness} State\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @return {State | undefined} The awareness instance, or undefined if not supported.\n\t */\n\tfunction getAwareness< State extends Awareness >(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): State | undefined {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState || ! entityState.awareness ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn entityState.awareness as State;\n\t}\n\n\t/**\n\t * Load and inspect the persisted CRDT document. If supported and it exists,\n\t * compare it against the current entity record. If there are differences,\n\t * apply the changes from the entity record.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {ObjectData} record Entity record representing this object type.\n\t */\n\tfunction _applyPersistedCrdtDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData\n\t): void {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'no entity state', entityId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst {\n\t\t\thandlers,\n\t\t\tsyncConfig: {\n\t\t\t\tapplyChangesToCRDTDoc,\n\t\t\t\tgetChangesFromCRDTDoc,\n\t\t\t\tgetPersistedCRDTDoc,\n\t\t\t},\n\t\t\tydoc: targetDoc,\n\t\t} = entityState;\n\n\t\t// Get the persisted CRDT document, if it exists.\n\t\tconst serialized = getPersistedCRDTDoc?.( record );\n\t\tconst tempDoc = serialized ? deserializeCrdtDoc( serialized ) : null;\n\n\t\tif ( ! tempDoc ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'no persisted doc', entityId );\n\t\t\t// Apply the current record as changes and trigger a save, which will\n\t\t\t// persist the CRDT document. (The entity should call `createPersistedCRDTDoc`\n\t\t\t// via its pre-persist hook.)\n\t\t\ttargetDoc.transact( () => {\n\t\t\t\tapplyChangesToCRDTDoc( targetDoc, record );\n\t\t\t\thandlers.saveRecord();\n\t\t\t}, LOCAL_SYNC_MANAGER_ORIGIN );\n\t\t\treturn;\n\t\t}\n\n\t\t// Apply the persisted document to the current document as a single update.\n\t\t// This is done even if the persisted document has been invalidated. This\n\t\t// prevents a newly joining peer (or refreshing user) from re-initializing\n\t\t// the CRDT document (the \"initialization problem\").\n\t\t//\n\t\t// IMPORTANT: Do not wrap this in a transaction with the local origin. It\n\t\t// effectively advances the state vector for the current client, which causes\n\t\t// Yjs to think that another client is using this client ID.\n\t\tconst update = Y.encodeStateAsUpdateV2( tempDoc );\n\t\tY.applyUpdateV2( targetDoc, update );\n\n\t\t// Compute the differences between the persisted doc and the current\n\t\t// record. This can happen when:\n\t\t//\n\t\t// 1. The server makes updates on save that mutate the entity. Example: On\n\t\t// initial save, the server adds the \"Uncategorized\" category to the\n\t\t// post.\n\t\t// 2. An \"out-of-band\" update occurs. Example: a WP-CLI command or direct\n\t\t// database update mutates the entity.\n\t\t// 3. Unsaved changes are synced from a peer _before_ this code runs. We\n\t\t// can't control when (or if) remote changes are synced, so this is a\n\t\t// race condition.\n\t\tconst invalidations = getChangesFromCRDTDoc( tempDoc, record );\n\t\tconst invalidatedKeys = Object.keys( invalidations );\n\n\t\t// Destroy the temporary document to prevent leaks.\n\t\ttempDoc.destroy();\n\n\t\tif ( 0 === invalidatedKeys.length ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'valid persisted doc', entityId );\n\t\t\t// The persisted CRDT document is valid. There are no updates to apply.\n\t\t\treturn;\n\t\t}\n\n\t\tlog( 'applyPersistedCrdtDoc', 'invalidated keys', entityId, {\n\t\t\tinvalidatedKeys,\n\t\t} );\n\n\t\t// Use the invalidated keys to get the updated values from the entity.\n\t\tconst changes = invalidatedKeys.reduce(\n\t\t\t( acc, key ) =>\n\t\t\t\tObject.assign( acc, {\n\t\t\t\t\t[ key ]: record[ key ],\n\t\t\t\t} ),\n\t\t\t{}\n\t\t);\n\n\t\t// Apply the changes and trigger a save, which will persist the CRDT\n\t\t// document. (The entity should call `createPersistedCRDTDoc` via its\n\t\t// pre-persist hook.)\n\t\ttargetDoc.transact( () => {\n\t\t\tapplyChangesToCRDTDoc( targetDoc, changes );\n\t\t\thandlers.saveRecord();\n\t\t}, LOCAL_SYNC_MANAGER_ORIGIN );\n\t}\n\n\t/**\n\t * Update CRDT document with changes from the local store.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {Partial< ObjectData >} changes Updates to make.\n\t * @param {string} origin The source of change.\n\t * @param {SyncManagerUpdateOptions} options Optional flags for the update.\n\t * @param {boolean} options.isSave Whether this update is part of a save operation. Defaults to false.\n\t * @param {boolean} options.isNewUndoLevel Whether to create a new undo level for this change. Defaults to false.\n\t */\n\tfunction updateCRDTDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID | null,\n\t\tchanges: Partial< ObjectData >,\n\t\torigin: string,\n\t\toptions: SyncManagerUpdateOptions = {}\n\t): void {\n\t\tconst { isSave = false, isNewUndoLevel = false } = options;\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\t\tconst collectionState = collectionStates.get( objectType );\n\n\t\tif ( entityState ) {\n\t\t\tconst { syncConfig, ydoc } = entityState;\n\n\t\t\t// If this is change should create a new undo level, tell the undo\n\t\t\t// manager to stop capturing and create a new undo group.\n\t\t\t// We can't do this in the undo manager itself, because addRecord() is\n\t\t\t// called after the CRDT changes have been applied, and we want to\n\t\t\t// ensure that the undo set is created before the changes are applied.\n\t\t\tif ( isNewUndoLevel && undoManager ) {\n\t\t\t\tundoManager.stopCapturing?.();\n\t\t\t}\n\n\t\t\tydoc.transact( () => {\n\t\t\t\tlog( 'updateCRDTDoc', 'applying changes', entityId, {\n\t\t\t\t\tchangedKeys: Object.keys( changes ),\n\t\t\t\t} );\n\t\t\t\tsyncConfig.applyChangesToCRDTDoc( ydoc, changes );\n\n\t\t\t\tif ( isSave ) {\n\t\t\t\t\tmarkEntityAsSaved( ydoc );\n\t\t\t\t}\n\t\t\t}, origin );\n\t\t}\n\n\t\tif ( collectionState && isSave ) {\n\t\t\tcollectionState.ydoc.transact( () => {\n\t\t\t\tmarkEntityAsSaved( collectionState.ydoc );\n\t\t\t}, origin );\n\t\t}\n\t}\n\n\t/**\n\t * Update the entity record in the local store with changes from the CRDT\n\t * document.\n\t *\n\t * @param {ObjectType} objectType Object type of record to update.\n\t * @param {ObjectID} objectId Object ID of record to update.\n\t */\n\tasync function _updateEntityRecord(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): Promise< void > {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState ) {\n\t\t\tlog( 'updateEntityRecord', 'no entity state', entityId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst { handlers, syncConfig, ydoc } = entityState;\n\n\t\t// Determine which synced properties have actually changed by comparing\n\t\t// them against the current edited entity record.\n\t\tconst changes = syncConfig.getChangesFromCRDTDoc(\n\t\t\tydoc,\n\t\t\tawait handlers.getEditedRecord()\n\t\t);\n\n\t\tconst changedKeys = Object.keys( changes );\n\n\t\tif ( 0 === changedKeys.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlog( 'updateEntityRecord', 'changes', entityId, {\n\t\t\tchangedKeys,\n\t\t} );\n\t\thandlers.editRecord( changes );\n\t}\n\n\t/**\n\t * Create object meta to persist the CRDT document in the entity record.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t */\n\tasync function createPersistedCRDTDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): Promise< string | null > {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState?.ydoc ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Y.Doc updates are deferred via yieldToEventLoop. Await a promise that\n\t\t// resolves on the next tick of the event loop so pending updates are flushed\n\t\t// before we serialize the document.\n\t\tawait new Promise( ( resolve ) => setTimeout( resolve, 0 ) );\n\n\t\treturn serializeCrdtDoc( entityState.ydoc );\n\t}\n\n\t// Collect internal functions so that they can be wrapped before calling.\n\tconst internal = {\n\t\tapplyPersistedCrdtDoc: debugWrap( _applyPersistedCrdtDoc ),\n\t\tupdateEntityRecord: debugWrap( _updateEntityRecord ),\n\t};\n\n\t// Wrap and return the public API.\n\treturn {\n\t\tcreatePersistedCRDTDoc: debugWrap( createPersistedCRDTDoc ),\n\t\tgetAwareness,\n\t\tload: debugWrap( loadEntity ),\n\t\tloadCollection: debugWrap( loadCollection ),\n\t\t// Use getter to ensure we always return the current value of `undoManager`.\n\t\tget undoManager(): SyncUndoManager | undefined {\n\t\t\treturn undoManager;\n\t\t},\n\t\tunload: debugWrap( unloadEntity ),\n\t\tupdate: debugWrap( yieldToEventLoop( updateCRDTDoc ) ),\n\t};\n}\n"], | ||
| "mappings": "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", | ||
| "names": ["origin"] | ||
| } |
@@ -9,2 +9,3 @@ // packages/sync/src/private-apis.ts | ||
| import { createSyncManager } from "./manager.mjs"; | ||
| import { pollingManager } from "./providers/http-polling/polling-manager.mjs"; | ||
| import { default as Delta } from "./quill-delta/Delta.mjs"; | ||
@@ -17,3 +18,4 @@ var privateApis = {}; | ||
| CRDT_RECORD_MAP_KEY, | ||
| LOCAL_EDITOR_ORIGIN | ||
| LOCAL_EDITOR_ORIGIN, | ||
| retrySyncConnection: () => pollingManager.retryNow() | ||
| }); | ||
@@ -20,0 +22,0 @@ export { |
| { | ||
| "version": 3, | ||
| "sources": ["../src/private-apis.ts"], | ||
| "sourcesContent": ["/**\n * Internal dependencies\n */\nimport {\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_RECORD_MAP_KEY,\n\tLOCAL_EDITOR_ORIGIN,\n} from './config';\nimport { lock } from './lock-unlock';\nimport { createSyncManager } from './manager';\nimport { default as Delta } from './quill-delta/Delta';\n\nexport const privateApis = {};\n\nlock( privateApis, {\n\tcreateSyncManager,\n\tDelta,\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_RECORD_MAP_KEY,\n\tLOCAL_EDITOR_ORIGIN,\n} );\n"], | ||
| "mappings": ";AAGA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,YAAY;AACrB,SAAS,yBAAyB;AAClC,SAAS,WAAW,aAAa;AAE1B,IAAM,cAAc,CAAC;AAE5B,KAAM,aAAa;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAE;", | ||
| "sourcesContent": ["/**\n * Internal dependencies\n */\nimport {\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_RECORD_MAP_KEY,\n\tLOCAL_EDITOR_ORIGIN,\n} from './config';\nimport { lock } from './lock-unlock';\nimport { createSyncManager } from './manager';\nimport { pollingManager } from './providers/http-polling/polling-manager';\nimport { default as Delta } from './quill-delta/Delta';\n\nexport const privateApis = {};\n\nlock( privateApis, {\n\tcreateSyncManager,\n\tDelta,\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_RECORD_MAP_KEY,\n\tLOCAL_EDITOR_ORIGIN,\n\tretrySyncConnection: () => pollingManager.retryNow(),\n} );\n"], | ||
| "mappings": ";AAGA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,YAAY;AACrB,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,WAAW,aAAa;AAE1B,IAAM,cAAc,CAAC;AAE5B,KAAM,aAAa;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,qBAAqB,MAAM,eAAe,SAAS;AACpD,CAAE;", | ||
| "names": [] | ||
| } |
@@ -46,9 +46,10 @@ // packages/sync/src/providers/http-polling/http-polling-provider.ts | ||
| /** | ||
| * Emit connection status. | ||
| * Emit connection status, passing the full object through so that | ||
| * additional fields (e.g. `retryInMs`) are preserved for consumers. | ||
| * | ||
| * @param status The connection status | ||
| * @param status.error Optional error information when status is 'disconnected' | ||
| * @param status.status The connection status ('connected', 'connecting', 'disconnected') | ||
| * @param connectionStatus The connection status object | ||
| */ | ||
| emitStatus = ({ error, status }) => { | ||
| emitStatus = (connectionStatus) => { | ||
| const { status } = connectionStatus; | ||
| const error = status === "disconnected" ? connectionStatus.error : void 0; | ||
| if (this.status === status && !error) { | ||
@@ -62,3 +63,3 @@ return; | ||
| this.status = status; | ||
| this.emit("status", [{ error, status }]); | ||
| this.emit("status", [connectionStatus]); | ||
| }; | ||
@@ -65,0 +66,0 @@ /** |
| { | ||
| "version": 3, | ||
| "sources": ["../../../src/providers/http-polling/http-polling-provider.ts"], | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport type * as Y from 'yjs';\nimport { ObservableV2 } from 'lib0/observable';\nimport { Awareness } from 'y-protocols/awareness';\n\n/**\n * Internal dependencies\n */\nimport type {\n\tConnectionStatus,\n\tProviderCreator,\n\tProviderCreatorResult,\n} from '../../types';\nimport { pollingManager } from './polling-manager';\n\nexport interface ProviderOptions {\n\tawareness?: Awareness;\n\tdebug?: boolean;\n\troom: string;\n\tydoc: Y.Doc;\n}\n\n/**\n * Event types for HttpPollingProvider.\n * ObservableV2 expects event handlers as functions.\n */\ntype HttpPollingEvents = {\n\tstatus: ( status: ConnectionStatus ) => void;\n};\n\n/**\n * Yjs provider that uses HTTP polling for real-time synchronization. It manages\n * document updates and awareness states through a central sync server.\n */\nclass HttpPollingProvider extends ObservableV2< HttpPollingEvents > {\n\tprotected awareness: Awareness;\n\tprotected status: ConnectionStatus[ 'status' ] = 'disconnected';\n\tprotected synced = false;\n\n\tpublic constructor( protected options: ProviderOptions ) {\n\t\tsuper();\n\t\tthis.log( 'Initializing', { room: options.room } );\n\n\t\tthis.awareness = options.awareness ?? new Awareness( options.ydoc );\n\t\tthis.connect();\n\t}\n\n\t/**\n\t * Connect to the endpoint and initialize sync.\n\t */\n\tpublic connect(): void {\n\t\tthis.log( 'Connecting' );\n\n\t\tpollingManager.registerRoom( {\n\t\t\troom: this.options.room,\n\t\t\tdoc: this.options.ydoc,\n\t\t\tawareness: this.awareness,\n\t\t\tlog: this.log,\n\t\t\tonStatusChange: this.emitStatus,\n\t\t\tonSync: this.onSync,\n\t\t} );\n\t}\n\n\t/**\n\t * Destroy the provider and cleanup resources.\n\t */\n\tpublic destroy(): void {\n\t\tthis.disconnect();\n\t\tsuper.destroy();\n\t}\n\n\t/**\n\t * Disconnect the provider and allow reconnection later.\n\t */\n\tpublic disconnect(): void {\n\t\tthis.log( 'Disconnecting' );\n\n\t\tpollingManager.unregisterRoom( this.options.room );\n\t\tthis.emitStatus( { status: 'disconnected' } );\n\t}\n\n\t/**\n\t * Emit connection status.\n\t *\n\t * @param status The connection status\n\t * @param status.error Optional error information when status is 'disconnected'\n\t * @param status.status The connection status ('connected', 'connecting', 'disconnected')\n\t */\n\tprotected emitStatus = ( { error, status }: ConnectionStatus ): void => {\n\t\tif ( this.status === status && ! error ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Only emit 'connecting' status if transitioning from 'disconnected'.\n\t\tif ( status === 'connecting' && this.status !== 'disconnected' ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.log( 'Status change', { status, error } );\n\n\t\t// ObservableV2 expects arguments as an array\n\t\tthis.status = status;\n\t\tthis.emit( 'status', [ { error, status } ] );\n\t};\n\n\t/**\n\t * Log debug messages if debugging is enabled.\n\t *\n\t * @param message The debug message\n\t * @param debug Additional debug information\n\t */\n\tprotected log = ( message: string, debug: object = {} ): void => {\n\t\tif ( this.options.debug ) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.log( `[${ this.constructor.name }]: ${ message }`, {\n\t\t\t\troom: this.options.room,\n\t\t\t\t...debug,\n\t\t\t} );\n\t\t}\n\t};\n\n\t/**\n\t * Handle synchronization events from the polling manager.\n\t */\n\tprotected onSync = (): void => {\n\t\tif ( ! this.synced ) {\n\t\t\tthis.synced = true;\n\t\t\tthis.log( 'Synced' );\n\t\t}\n\t};\n}\n\n/**\n * Create a provider creator function for the HttpPollingProvider\n */\nexport function createHttpPollingProvider(): ProviderCreator {\n\treturn async ( {\n\t\tawareness,\n\t\tobjectType,\n\t\tobjectId,\n\t\tydoc,\n\t} ): Promise< ProviderCreatorResult > => {\n\t\t// Generate room name from objectType and objectId\n\t\tconst room = objectId ? `${ objectType }:${ objectId }` : objectType;\n\t\tconst provider = new HttpPollingProvider( {\n\t\t\tawareness,\n\t\t\t// debug: true,\n\t\t\troom,\n\t\t\tydoc,\n\t\t} );\n\n\t\treturn {\n\t\t\tdestroy: () => provider.destroy(),\n\t\t\t// Adapter: ObservableV2.on is compatible with ProviderOn\n\t\t\t// The callback receives data as the first parameter\n\t\t\ton: ( event, callback ) => {\n\t\t\t\tprovider.on( event, callback );\n\t\t\t},\n\t\t};\n\t};\n}\n"], | ||
| "mappings": ";AAIA,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAU1B,SAAS,sBAAsB;AAqB/B,IAAM,sBAAN,cAAkC,aAAkC;AAAA,EAK5D,YAAuB,SAA2B;AACxD,UAAM;AADuB;AAE7B,SAAK,IAAK,gBAAgB,EAAE,MAAM,QAAQ,KAAK,CAAE;AAEjD,SAAK,YAAY,QAAQ,aAAa,IAAI,UAAW,QAAQ,IAAK;AAClE,SAAK,QAAQ;AAAA,EACd;AAAA,EAVU;AAAA,EACA,SAAuC;AAAA,EACvC,SAAS;AAAA;AAAA;AAAA;AAAA,EAaZ,UAAgB;AACtB,SAAK,IAAK,YAAa;AAEvB,mBAAe,aAAc;AAAA,MAC5B,MAAM,KAAK,QAAQ;AAAA,MACnB,KAAK,KAAK,QAAQ;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,KAAK,KAAK;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,IACd,CAAE;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,UAAgB;AACtB,SAAK,WAAW;AAChB,UAAM,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKO,aAAmB;AACzB,SAAK,IAAK,eAAgB;AAE1B,mBAAe,eAAgB,KAAK,QAAQ,IAAK;AACjD,SAAK,WAAY,EAAE,QAAQ,eAAe,CAAE;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASU,aAAa,CAAE,EAAE,OAAO,OAAO,MAA+B;AACvE,QAAK,KAAK,WAAW,UAAU,CAAE,OAAQ;AACxC;AAAA,IACD;AAGA,QAAK,WAAW,gBAAgB,KAAK,WAAW,gBAAiB;AAChE;AAAA,IACD;AAEA,SAAK,IAAK,iBAAiB,EAAE,QAAQ,MAAM,CAAE;AAG7C,SAAK,SAAS;AACd,SAAK,KAAM,UAAU,CAAE,EAAE,OAAO,OAAO,CAAE,CAAE;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,MAAM,CAAE,SAAiB,QAAgB,CAAC,MAAa;AAChE,QAAK,KAAK,QAAQ,OAAQ;AAEzB,cAAQ,IAAK,IAAK,KAAK,YAAY,IAAK,MAAO,OAAQ,IAAI;AAAA,QAC1D,MAAM,KAAK,QAAQ;AAAA,QACnB,GAAG;AAAA,MACJ,CAAE;AAAA,IACH;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKU,SAAS,MAAY;AAC9B,QAAK,CAAE,KAAK,QAAS;AACpB,WAAK,SAAS;AACd,WAAK,IAAK,QAAS;AAAA,IACpB;AAAA,EACD;AACD;AAKO,SAAS,4BAA6C;AAC5D,SAAO,OAAQ;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,MAAyC;AAExC,UAAM,OAAO,WAAW,GAAI,UAAW,IAAK,QAAS,KAAK;AAC1D,UAAM,WAAW,IAAI,oBAAqB;AAAA,MACzC;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,IACD,CAAE;AAEF,WAAO;AAAA,MACN,SAAS,MAAM,SAAS,QAAQ;AAAA;AAAA;AAAA,MAGhC,IAAI,CAAE,OAAO,aAAc;AAC1B,iBAAS,GAAI,OAAO,QAAS;AAAA,MAC9B;AAAA,IACD;AAAA,EACD;AACD;", | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport type * as Y from 'yjs';\nimport { ObservableV2 } from 'lib0/observable';\nimport { Awareness } from 'y-protocols/awareness';\n\n/**\n * Internal dependencies\n */\nimport type {\n\tConnectionStatus,\n\tProviderCreator,\n\tProviderCreatorResult,\n} from '../../types';\nimport { pollingManager } from './polling-manager';\n\nexport interface ProviderOptions {\n\tawareness?: Awareness;\n\tdebug?: boolean;\n\troom: string;\n\tydoc: Y.Doc;\n}\n\n/**\n * Event types for HttpPollingProvider.\n * ObservableV2 expects event handlers as functions.\n */\ntype HttpPollingEvents = {\n\tstatus: ( status: ConnectionStatus ) => void;\n};\n\n/**\n * Yjs provider that uses HTTP polling for real-time synchronization. It manages\n * document updates and awareness states through a central sync server.\n */\nclass HttpPollingProvider extends ObservableV2< HttpPollingEvents > {\n\tprotected awareness: Awareness;\n\tprotected status: ConnectionStatus[ 'status' ] = 'disconnected';\n\tprotected synced = false;\n\n\tpublic constructor( protected options: ProviderOptions ) {\n\t\tsuper();\n\t\tthis.log( 'Initializing', { room: options.room } );\n\n\t\tthis.awareness = options.awareness ?? new Awareness( options.ydoc );\n\t\tthis.connect();\n\t}\n\n\t/**\n\t * Connect to the endpoint and initialize sync.\n\t */\n\tpublic connect(): void {\n\t\tthis.log( 'Connecting' );\n\n\t\tpollingManager.registerRoom( {\n\t\t\troom: this.options.room,\n\t\t\tdoc: this.options.ydoc,\n\t\t\tawareness: this.awareness,\n\t\t\tlog: this.log,\n\t\t\tonStatusChange: this.emitStatus,\n\t\t\tonSync: this.onSync,\n\t\t} );\n\t}\n\n\t/**\n\t * Destroy the provider and cleanup resources.\n\t */\n\tpublic destroy(): void {\n\t\tthis.disconnect();\n\t\tsuper.destroy();\n\t}\n\n\t/**\n\t * Disconnect the provider and allow reconnection later.\n\t */\n\tpublic disconnect(): void {\n\t\tthis.log( 'Disconnecting' );\n\n\t\tpollingManager.unregisterRoom( this.options.room );\n\t\tthis.emitStatus( { status: 'disconnected' } );\n\t}\n\n\t/**\n\t * Emit connection status, passing the full object through so that\n\t * additional fields (e.g. `retryInMs`) are preserved for consumers.\n\t *\n\t * @param connectionStatus The connection status object\n\t */\n\tprotected emitStatus = ( connectionStatus: ConnectionStatus ): void => {\n\t\tconst { status } = connectionStatus;\n\t\tconst error =\n\t\t\tstatus === 'disconnected' ? connectionStatus.error : undefined;\n\n\t\tif ( this.status === status && ! error ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Only emit 'connecting' status if transitioning from 'disconnected'.\n\t\tif ( status === 'connecting' && this.status !== 'disconnected' ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.log( 'Status change', { status, error } );\n\n\t\t// ObservableV2 expects arguments as an array\n\t\tthis.status = status;\n\t\tthis.emit( 'status', [ connectionStatus ] );\n\t};\n\n\t/**\n\t * Log debug messages if debugging is enabled.\n\t *\n\t * @param message The debug message\n\t * @param debug Additional debug information\n\t */\n\tprotected log = ( message: string, debug: object = {} ): void => {\n\t\tif ( this.options.debug ) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.log( `[${ this.constructor.name }]: ${ message }`, {\n\t\t\t\troom: this.options.room,\n\t\t\t\t...debug,\n\t\t\t} );\n\t\t}\n\t};\n\n\t/**\n\t * Handle synchronization events from the polling manager.\n\t */\n\tprotected onSync = (): void => {\n\t\tif ( ! this.synced ) {\n\t\t\tthis.synced = true;\n\t\t\tthis.log( 'Synced' );\n\t\t}\n\t};\n}\n\n/**\n * Create a provider creator function for the HttpPollingProvider\n */\nexport function createHttpPollingProvider(): ProviderCreator {\n\treturn async ( {\n\t\tawareness,\n\t\tobjectType,\n\t\tobjectId,\n\t\tydoc,\n\t} ): Promise< ProviderCreatorResult > => {\n\t\t// Generate room name from objectType and objectId\n\t\tconst room = objectId ? `${ objectType }:${ objectId }` : objectType;\n\t\tconst provider = new HttpPollingProvider( {\n\t\t\tawareness,\n\t\t\t// debug: true,\n\t\t\troom,\n\t\t\tydoc,\n\t\t} );\n\n\t\treturn {\n\t\t\tdestroy: () => provider.destroy(),\n\t\t\t// Adapter: ObservableV2.on is compatible with ProviderOn\n\t\t\t// The callback receives data as the first parameter\n\t\t\ton: ( event, callback ) => {\n\t\t\t\tprovider.on( event, callback );\n\t\t\t},\n\t\t};\n\t};\n}\n"], | ||
| "mappings": ";AAIA,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAU1B,SAAS,sBAAsB;AAqB/B,IAAM,sBAAN,cAAkC,aAAkC;AAAA,EAK5D,YAAuB,SAA2B;AACxD,UAAM;AADuB;AAE7B,SAAK,IAAK,gBAAgB,EAAE,MAAM,QAAQ,KAAK,CAAE;AAEjD,SAAK,YAAY,QAAQ,aAAa,IAAI,UAAW,QAAQ,IAAK;AAClE,SAAK,QAAQ;AAAA,EACd;AAAA,EAVU;AAAA,EACA,SAAuC;AAAA,EACvC,SAAS;AAAA;AAAA;AAAA;AAAA,EAaZ,UAAgB;AACtB,SAAK,IAAK,YAAa;AAEvB,mBAAe,aAAc;AAAA,MAC5B,MAAM,KAAK,QAAQ;AAAA,MACnB,KAAK,KAAK,QAAQ;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,KAAK,KAAK;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,IACd,CAAE;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,UAAgB;AACtB,SAAK,WAAW;AAChB,UAAM,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKO,aAAmB;AACzB,SAAK,IAAK,eAAgB;AAE1B,mBAAe,eAAgB,KAAK,QAAQ,IAAK;AACjD,SAAK,WAAY,EAAE,QAAQ,eAAe,CAAE;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,aAAa,CAAE,qBAA8C;AACtE,UAAM,EAAE,OAAO,IAAI;AACnB,UAAM,QACL,WAAW,iBAAiB,iBAAiB,QAAQ;AAEtD,QAAK,KAAK,WAAW,UAAU,CAAE,OAAQ;AACxC;AAAA,IACD;AAGA,QAAK,WAAW,gBAAgB,KAAK,WAAW,gBAAiB;AAChE;AAAA,IACD;AAEA,SAAK,IAAK,iBAAiB,EAAE,QAAQ,MAAM,CAAE;AAG7C,SAAK,SAAS;AACd,SAAK,KAAM,UAAU,CAAE,gBAAiB,CAAE;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,MAAM,CAAE,SAAiB,QAAgB,CAAC,MAAa;AAChE,QAAK,KAAK,QAAQ,OAAQ;AAEzB,cAAQ,IAAK,IAAK,KAAK,YAAY,IAAK,MAAO,OAAQ,IAAI;AAAA,QAC1D,MAAM,KAAK,QAAQ;AAAA,QACnB,GAAG;AAAA,MACJ,CAAE;AAAA,IACH;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKU,SAAS,MAAY;AAC9B,QAAK,CAAE,KAAK,QAAS;AACpB,WAAK,SAAS;AACd,WAAK,IAAK,QAAS;AAAA,IACpB;AAAA,EACD;AACD;AAKO,SAAS,4BAA6C;AAC5D,SAAO,OAAQ;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,MAAyC;AAExC,UAAM,OAAO,WAAW,GAAI,UAAW,IAAK,QAAS,KAAK;AAC1D,UAAM,WAAW,IAAI,oBAAqB;AAAA,MACzC;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,IACD,CAAE;AAEF,WAAO;AAAA,MACN,SAAS,MAAM,SAAS,QAAQ;AAAA;AAAA;AAAA,MAGhC,IAAI,CAAE,OAAO,aAAc;AAC1B,iBAAS,GAAI,OAAO,QAAS;AAAA,MAC9B;AAAA,IACD;AAAA,EACD;AACD;", | ||
| "names": [] | ||
| } |
@@ -19,3 +19,3 @@ // packages/sync/src/providers/http-polling/polling-manager.ts | ||
| var POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS = 250; | ||
| var POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 30 * 1e3; | ||
| var POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 25 * 1e3; | ||
| var MAX_ERROR_BACKOFF_IN_MS = 30 * 1e3; | ||
@@ -156,4 +156,2 @@ var POLLING_MANAGER_ORIGIN = "polling-manager"; | ||
| pollingTimeoutId = null; | ||
| } | ||
| if (isPolling) { | ||
| poll(); | ||
@@ -165,2 +163,3 @@ } | ||
| isPolling = true; | ||
| pollingTimeoutId = null; | ||
| async function start() { | ||
@@ -249,3 +248,6 @@ if (0 === roomStates.size) { | ||
| roomStates.forEach((state) => { | ||
| state.onStatusChange({ status: "disconnected" }); | ||
| state.onStatusChange({ | ||
| status: "disconnected", | ||
| retryInMs: pollInterval | ||
| }); | ||
| }); | ||
@@ -338,4 +340,13 @@ } | ||
| } | ||
| function retryNow() { | ||
| pollInterval = POLLING_INTERVAL_IN_MS * 2; | ||
| if (pollingTimeoutId) { | ||
| clearTimeout(pollingTimeoutId); | ||
| pollingTimeoutId = null; | ||
| poll(); | ||
| } | ||
| } | ||
| var pollingManager = { | ||
| registerRoom, | ||
| retryNow, | ||
| unregisterRoom | ||
@@ -342,0 +353,0 @@ }; |
| { | ||
| "version": 3, | ||
| "sources": ["../../../src/providers/http-polling/polling-manager.ts"], | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport * as encoding from 'lib0/encoding';\nimport * as decoding from 'lib0/decoding';\nimport type { Awareness } from 'y-protocols/awareness';\nimport { removeAwarenessStates } from 'y-protocols/awareness';\nimport * as syncProtocol from 'y-protocols/sync';\n\n/**\n * Internal dependencies\n */\nimport type { ConnectionStatus } from '../../types';\nimport {\n\ttype AwarenessState,\n\ttype LocalAwarenessState,\n\ttype SyncPayload,\n\ttype SyncUpdate,\n\tSyncUpdateType,\n\ttype UpdateQueue,\n} from './types';\nimport {\n\tbase64ToUint8Array,\n\tcreateSyncUpdate,\n\tcreateUpdateQueue,\n\tpostSyncUpdate,\n\tpostSyncUpdateNonBlocking,\n} from './utils';\n\nconst POLLING_INTERVAL_IN_MS = 1000; // 1 second or 1000 milliseconds\nconst POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS = 250; // 250 milliseconds\nconst POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 30 * 1000; // 30 seconds\nconst MAX_ERROR_BACKOFF_IN_MS = 30 * 1000; // 30 seconds\nconst POLLING_MANAGER_ORIGIN = 'polling-manager';\n\ntype LogFunction = ( message: string, debug?: object ) => void;\n\ninterface PollingManager {\n\tregisterRoom: ( options: RegisterRoomOptions ) => void;\n\tunregisterRoom: ( room: string ) => void;\n}\n\ninterface RegisterRoomOptions {\n\troom: string;\n\tdoc: Y.Doc;\n\tawareness: Awareness;\n\tlog: LogFunction;\n\tonStatusChange: ( status: ConnectionStatus ) => void;\n\tonSync: () => void;\n}\n\ninterface RoomState {\n\tclientId: number;\n\tcreateCompactionUpdate: () => SyncUpdate;\n\tendCursor: number;\n\tlocalAwarenessState: LocalAwarenessState;\n\tlog: LogFunction;\n\tonStatusChange: ( status: ConnectionStatus ) => void;\n\tprocessAwarenessUpdate: ( state: AwarenessState ) => void;\n\tprocessDocUpdate: ( update: SyncUpdate ) => SyncUpdate | void;\n\tunregister: () => void;\n\tupdateQueue: UpdateQueue;\n}\n\nconst roomStates: Map< string, RoomState > = new Map();\n\n/**\n * Create a compaction update by merging existing updates. This preserves\n * the original operation metadata (client IDs, logical clocks) so that\n * Yjs deduplication works correctly when the compaction is applied.\n *\n * Deprecated: The server is moving towards full state updates for compaction.\n *\n * @param updates The updates to merge\n */\nfunction createDeprecatedCompactionUpdate( updates: SyncUpdate[] ): SyncUpdate {\n\t// Extract only compaction and update types for merging (skip sync-step updates).\n\t// Decode base64 updates to Uint8Array for merging.\n\tconst mergeable = updates\n\t\t.filter( ( u ) =>\n\t\t\t[ SyncUpdateType.COMPACTION, SyncUpdateType.UPDATE ].includes(\n\t\t\t\tu.type\n\t\t\t)\n\t\t)\n\t\t.map( ( u ) => base64ToUint8Array( u.data ) );\n\n\t// Merge all updates while preserving operation metadata.\n\treturn createSyncUpdate(\n\t\tY.mergeUpdates( mergeable ),\n\t\tSyncUpdateType.COMPACTION\n\t);\n}\n\n/**\n * Create sync step 1 update (announce our state vector).\n *\n * @param doc The Yjs document\n */\nfunction createSyncStep1Update( doc: Y.Doc ): SyncUpdate {\n\tconst encoder = encoding.createEncoder();\n\tsyncProtocol.writeSyncStep1( encoder, doc );\n\treturn createSyncUpdate(\n\t\tencoding.toUint8Array( encoder ),\n\t\tSyncUpdateType.SYNC_STEP_1\n\t);\n}\n\n/**\n * Create sync step 2 update (acknowledge sync step 1).\n *\n * @param doc The Yjs document\n * @param step1 The sync step 1 update received\n */\nfunction createSyncStep2Update( doc: Y.Doc, step1: Uint8Array ): SyncUpdate {\n\tconst decoder = decoding.createDecoder( step1 );\n\tconst encoder = encoding.createEncoder();\n\tsyncProtocol.readSyncMessage(\n\t\tdecoder,\n\t\tencoder,\n\t\tdoc,\n\t\tPOLLING_MANAGER_ORIGIN\n\t);\n\treturn createSyncUpdate(\n\t\tencoding.toUint8Array( encoder ),\n\t\tSyncUpdateType.SYNC_STEP_2\n\t);\n}\n\n/**\n * Process an incoming awareness update from the server.\n *\n * @param state The awareness state received\n * @param awareness The local Awareness instance\n */\nfunction processAwarenessUpdate(\n\tstate: AwarenessState,\n\tawareness: Awareness\n): void {\n\tconst currentStates = awareness.getStates();\n\tconst added = new Set< number >();\n\tconst updated = new Set< number >();\n\n\t// Removed clients are missing from the server state.\n\tconst removed = new Set< number >(\n\t\tcurrentStates.keys().filter( ( clientId ) => ! state[ clientId ] )\n\t);\n\n\tObject.entries( state ).forEach( ( [ clientIdString, awarenessState ] ) => {\n\t\tconst clientId = Number( clientIdString );\n\n\t\t// Skip our own state (we already have it locally).\n\t\tif ( clientId === awareness.clientID ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// A null state should be removed by the server, but handle it here just in case.\n\t\tif ( null === awarenessState ) {\n\t\t\tcurrentStates.delete( clientId );\n\t\t\tremoved.add( clientId );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( ! currentStates.has( clientId ) ) {\n\t\t\tcurrentStates.set( clientId, awarenessState );\n\t\t\tadded.add( clientId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentState = currentStates.get( clientId );\n\n\t\tif (\n\t\t\tJSON.stringify( currentState ) !== JSON.stringify( awarenessState )\n\t\t) {\n\t\t\tcurrentStates.set( clientId, awarenessState );\n\t\t\tupdated.add( clientId );\n\t\t}\n\t} );\n\n\tif ( added.size + updated.size > 0 ) {\n\t\tawareness.emit( 'change', [\n\t\t\t{\n\t\t\t\tadded: Array.from( added ),\n\t\t\t\tupdated: Array.from( updated ),\n\t\t\t\t// Left blank on purpose, as the removal of clients is handled in the if condition below.\n\t\t\t\tremoved: [],\n\t\t\t},\n\t\t] );\n\t}\n\n\tif ( removed.size > 0 ) {\n\t\tremoveAwarenessStates(\n\t\t\tawareness,\n\t\t\tArray.from( removed ),\n\t\t\tPOLLING_MANAGER_ORIGIN\n\t\t);\n\t}\n}\n\n/**\n * Process an incoming sync / document update based on its type.\n *\n * @param update The typed update received\n * @param doc The Yjs document\n * @param onSync Callback when sync is complete\n * @return A response update if needed (e.g., sync_step2 in response to sync_step1)\n */\nfunction processDocUpdate(\n\tupdate: SyncUpdate,\n\tdoc: Y.Doc,\n\tonSync: () => void\n): SyncUpdate | void {\n\tconst data = base64ToUint8Array( update.data );\n\n\tswitch ( update.type ) {\n\t\tcase SyncUpdateType.SYNC_STEP_1: {\n\t\t\t// Respond to sync step 1 with sync step 2.\n\t\t\treturn createSyncStep2Update( doc, data );\n\t\t}\n\n\t\tcase SyncUpdateType.SYNC_STEP_2: {\n\t\t\t// Apply sync step 2 (potentially contains missing updates).\n\t\t\tconst decoder = decoding.createDecoder( data );\n\t\t\tconst encoder = encoding.createEncoder();\n\t\t\tsyncProtocol.readSyncMessage(\n\t\t\t\tdecoder,\n\t\t\t\tencoder,\n\t\t\t\tdoc,\n\t\t\t\tPOLLING_MANAGER_ORIGIN\n\t\t\t);\n\t\t\tonSync();\n\t\t\treturn;\n\t\t}\n\n\t\tcase SyncUpdateType.COMPACTION:\n\t\tcase SyncUpdateType.UPDATE: {\n\t\t\t// Apply document update directly.\n\t\t\tY.applyUpdate( doc, data, POLLING_MANAGER_ORIGIN );\n\t\t}\n\t}\n}\n\nlet areListenersRegistered = false;\nlet hasCollaborators = false;\nlet isActiveBrowser = 'visible' === document.visibilityState;\nlet isPolling = false;\nlet isUnloadPending = false;\nlet pollInterval = POLLING_INTERVAL_IN_MS;\nlet pollingTimeoutId: ReturnType< typeof setTimeout > | null = null;\n\n/**\n * Mark that a page unload has been requested. This fires on\n * `beforeunload` which happens before the browser aborts in-flight\n * fetches, allowing us to distinguish poll failures caused by\n * navigation from genuine server errors in the catch block.\n *\n * If the user cancels the unload (e.g. by dismissing a \"Save Changes?\" dialog),\n * the flag is reset at the start of the next poll cycle so that polling can\n * resume.\n */\nfunction handleBeforeUnload(): void {\n\tisUnloadPending = true;\n}\n\n/**\n * Send a disconnect signal for all registered rooms when the page is\n * being unloaded. Uses `sendBeacon` so the request survives navigation.\n */\nfunction handlePageHide(): void {\n\tconst rooms = Array.from( roomStates.entries() ).map(\n\t\t( [ room, state ] ) => ( {\n\t\t\tafter: 0,\n\t\t\tawareness: null,\n\t\t\tclient_id: state.clientId,\n\t\t\troom,\n\t\t\tupdates: [],\n\t\t} )\n\t);\n\n\tpostSyncUpdateNonBlocking( { rooms } );\n}\n\n/**\n * Hangle change in visibility state of browser tab.\n *\n * Used to trigger a slow down of the collaboration syncs when the\n * browser tab becomes inactive (either the user switches tabs or the\n * screen saver comes on).\n *\n * Fires on the document's visibilitychange event.\n */\nfunction handleVisibilityChange() {\n\tconst wasActive = isActiveBrowser;\n\tisActiveBrowser = document.visibilityState === 'visible';\n\n\tif ( isActiveBrowser && ! wasActive ) {\n\t\t/*\n\t\t * Remove scheduled polling and repoll immediately when reactivated.\n\t\t *\n\t\t * This ensures that any updates by collaborators are immediately reflected\n\t\t * in the document once the browser tab becomes active. Otherwise there would\n\t\t * be a delay of 30 seconds before the updates came through.\n\t\t */\n\t\tif ( pollingTimeoutId ) {\n\t\t\tclearTimeout( pollingTimeoutId );\n\t\t\tpollingTimeoutId = null;\n\t\t}\n\n\t\tif ( isPolling ) {\n\t\t\tpoll();\n\t\t}\n\t}\n}\n\nfunction poll(): void {\n\tisPolling = true;\n\n\tasync function start(): Promise< void > {\n\t\tif ( 0 === roomStates.size ) {\n\t\t\tisPolling = false;\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the unloading flag at the start of each poll cycle so\n\t\t// it doesn't permanently suppress disconnect after the user\n\t\t// cancels a beforeunload dialog.\n\t\tisUnloadPending = false;\n\n\t\t// Emit 'connecting' status.\n\t\troomStates.forEach( ( state ) => {\n\t\t\tstate.onStatusChange( { status: 'connecting' } );\n\t\t} );\n\n\t\t// Create a payload with all queued updates. We include rooms even if they\n\t\t// have no updates to ensure we receive any incoming updates. Note that we\n\t\t// withhold our own updates until we detect another collaborator using the\n\t\t// queue's pause / resume mechanism.\n\t\tconst payload: SyncPayload = {\n\t\t\trooms: Array.from( roomStates.entries() ).map(\n\t\t\t\t( [ room, state ] ) => ( {\n\t\t\t\t\tafter: state.endCursor ?? 0,\n\t\t\t\t\tawareness: state.localAwarenessState,\n\t\t\t\t\tclient_id: state.clientId,\n\t\t\t\t\troom,\n\t\t\t\t\tupdates: state.updateQueue.get(),\n\t\t\t\t} )\n\t\t\t),\n\t\t};\n\n\t\ttry {\n\t\t\tconst { rooms } = await postSyncUpdate( payload );\n\n\t\t\t// Emit 'connected' status.\n\t\t\troomStates.forEach( ( state ) => {\n\t\t\t\tstate.onStatusChange( { status: 'connected' } );\n\t\t\t} );\n\n\t\t\trooms.forEach( ( room ) => {\n\t\t\t\tif ( ! roomStates.has( room.room ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst roomState = roomStates.get( room.room )!;\n\t\t\t\troomState.endCursor = room.end_cursor;\n\n\t\t\t\t// Process awareness update.\n\t\t\t\troomState.processAwarenessUpdate( room.awareness );\n\n\t\t\t\t// If there is another collaborator, resume the queue for the next poll\n\t\t\t\t// and increase polling frequency.\n\t\t\t\tif ( Object.keys( room.awareness ).length > 1 ) {\n\t\t\t\t\thasCollaborators = true;\n\t\t\t\t\troomState.updateQueue.resume();\n\t\t\t\t}\n\n\t\t\t\t// Process each incoming update and collect any responses.\n\t\t\t\tconst responseUpdates = room.updates\n\t\t\t\t\t.map( ( update ) => roomState.processDocUpdate( update ) )\n\t\t\t\t\t.filter( ( update ): update is SyncUpdate =>\n\t\t\t\t\t\tBoolean( update )\n\t\t\t\t\t);\n\t\t\t\troomState.updateQueue.addBulk( responseUpdates );\n\n\t\t\t\t// Respond to compaction requests from server. The server asks only one\n\t\t\t\t// client at a time to compact (lowest active client ID). We encode our\n\t\t\t\t// full document state to replace all prior updates on the server.\n\t\t\t\tif ( room.should_compact ) {\n\t\t\t\t\troomState.log( 'Server requested compaction update' );\n\t\t\t\t\troomState.updateQueue.clear();\n\t\t\t\t\troomState.updateQueue.add(\n\t\t\t\t\t\troomState.createCompactionUpdate()\n\t\t\t\t\t);\n\t\t\t\t} else if ( room.compaction_request ) {\n\t\t\t\t\t// Deprecated\n\t\t\t\t\troomState.log( 'Server requested (old) compaction update' );\n\t\t\t\t\troomState.updateQueue.add(\n\t\t\t\t\t\tcreateDeprecatedCompactionUpdate(\n\t\t\t\t\t\t\troom.compaction_request\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Recalculate polling interval.\n\t\t\tif ( isActiveBrowser && hasCollaborators ) {\n\t\t\t\tpollInterval = POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS;\n\t\t\t} else if ( isActiveBrowser ) {\n\t\t\t\tpollInterval = POLLING_INTERVAL_IN_MS;\n\t\t\t} else {\n\t\t\t\tpollInterval = POLLING_INTERVAL_BACKGROUND_TAB_IN_MS;\n\t\t\t}\n\t\t} catch ( error ) {\n\t\t\t// Exponential backoff on error: double the backoff time, up to max\n\t\t\tpollInterval = Math.min(\n\t\t\t\tpollInterval * 2,\n\t\t\t\tMAX_ERROR_BACKOFF_IN_MS\n\t\t\t);\n\n\t\t\t// Restore updates to queues on failure so they can be retried.\n\t\t\tfor ( const room of payload.rooms ) {\n\t\t\t\tif ( ! roomStates.has( room.room ) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst state = roomStates.get( room.room )!;\n\t\t\t\tstate.updateQueue.restore( room.updates );\n\t\t\t\tstate.log(\n\t\t\t\t\t'Error posting sync update, will retry with backoff',\n\t\t\t\t\t{\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tnextPoll: pollInterval,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Don't report disconnected status when the request was aborted\n\t\t\t// due to page unload (e.g. during a refresh) to avoid briefly\n\t\t\t// flashing the disconnect dialog before the new page loads.\n\t\t\tif ( ! isUnloadPending ) {\n\t\t\t\troomStates.forEach( ( state ) => {\n\t\t\t\t\tstate.onStatusChange( { status: 'disconnected' } );\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\tpollingTimeoutId = setTimeout( poll, pollInterval );\n\t}\n\n\t// Start polling.\n\tvoid start();\n}\nfunction registerRoom( {\n\troom,\n\tdoc,\n\tawareness,\n\tlog,\n\tonSync,\n\tonStatusChange,\n}: RegisterRoomOptions ): void {\n\tif ( roomStates.has( room ) ) {\n\t\treturn;\n\t}\n\n\t// Note: Queue is initially paused. Call .resume() to unpause.\n\tconst updateQueue = createUpdateQueue( [ createSyncStep1Update( doc ) ] );\n\n\tfunction onAwarenessUpdate(): void {\n\t\troomState.localAwarenessState = awareness.getLocalState() ?? {};\n\t}\n\n\tfunction onDocUpdate( update: Uint8Array, origin: unknown ): void {\n\t\tif ( POLLING_MANAGER_ORIGIN === origin ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Tag local document changes as 'update' type.\n\t\tupdateQueue.add( createSyncUpdate( update, SyncUpdateType.UPDATE ) );\n\t}\n\n\tfunction unregister(): void {\n\t\tdoc.off( 'update', onDocUpdate );\n\t\tawareness.off( 'change', onAwarenessUpdate );\n\t\tupdateQueue.clear();\n\t}\n\n\tconst roomState: RoomState = {\n\t\tclientId: doc.clientID,\n\t\tcreateCompactionUpdate: () =>\n\t\t\tcreateSyncUpdate(\n\t\t\t\tY.encodeStateAsUpdate( doc ),\n\t\t\t\tSyncUpdateType.COMPACTION\n\t\t\t),\n\t\tendCursor: 0,\n\t\tlocalAwarenessState: awareness.getLocalState() ?? {},\n\t\tlog,\n\t\tonStatusChange,\n\t\tprocessAwarenessUpdate: ( state: AwarenessState ) =>\n\t\t\tprocessAwarenessUpdate( state, awareness ),\n\t\tprocessDocUpdate: ( update: SyncUpdate ) =>\n\t\t\tprocessDocUpdate( update, doc, onSync ),\n\t\tunregister,\n\t\tupdateQueue,\n\t};\n\n\tdoc.on( 'update', onDocUpdate );\n\tawareness.on( 'change', onAwarenessUpdate );\n\troomStates.set( room, roomState );\n\n\tif ( ! areListenersRegistered ) {\n\t\twindow.addEventListener( 'beforeunload', handleBeforeUnload );\n\t\twindow.addEventListener( 'pagehide', handlePageHide );\n\t\tdocument.addEventListener( 'visibilitychange', handleVisibilityChange );\n\t\tareListenersRegistered = true;\n\t}\n\n\tif ( ! isPolling ) {\n\t\tpoll();\n\t}\n}\n\nfunction unregisterRoom( room: string ): void {\n\tconst state = roomStates.get( room );\n\tif ( state ) {\n\t\t// Send a disconnect signal so the server removes this client's\n\t\t// awareness entry immediately instead of waiting for the timeout.\n\t\tconst rooms = [\n\t\t\t{\n\t\t\t\tafter: 0,\n\t\t\t\tawareness: null,\n\t\t\t\tclient_id: state.clientId,\n\t\t\t\troom,\n\t\t\t\tupdates: [],\n\t\t\t},\n\t\t];\n\n\t\tpostSyncUpdateNonBlocking( { rooms } );\n\t\tstate.unregister();\n\t\troomStates.delete( room );\n\t}\n\n\tif ( 0 === roomStates.size && areListenersRegistered ) {\n\t\twindow.removeEventListener( 'beforeunload', handleBeforeUnload );\n\t\twindow.removeEventListener( 'pagehide', handlePageHide );\n\t\tdocument.removeEventListener(\n\t\t\t'visibilitychange',\n\t\t\thandleVisibilityChange\n\t\t);\n\t\tareListenersRegistered = false;\n\t}\n}\n\nexport const pollingManager: PollingManager = {\n\tregisterRoom,\n\tunregisterRoom,\n};\n"], | ||
| "mappings": "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", | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport * as encoding from 'lib0/encoding';\nimport * as decoding from 'lib0/decoding';\nimport type { Awareness } from 'y-protocols/awareness';\nimport { removeAwarenessStates } from 'y-protocols/awareness';\nimport * as syncProtocol from 'y-protocols/sync';\n\n/**\n * Internal dependencies\n */\nimport type { ConnectionStatus } from '../../types';\nimport {\n\ttype AwarenessState,\n\ttype LocalAwarenessState,\n\ttype SyncPayload,\n\ttype SyncUpdate,\n\tSyncUpdateType,\n\ttype UpdateQueue,\n} from './types';\nimport {\n\tbase64ToUint8Array,\n\tcreateSyncUpdate,\n\tcreateUpdateQueue,\n\tpostSyncUpdate,\n\tpostSyncUpdateNonBlocking,\n} from './utils';\n\nconst POLLING_INTERVAL_IN_MS = 1000; // 1 second or 1000 milliseconds\nconst POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS = 250; // 250 milliseconds\n// Must be less than the server-side AWARENESS_TIMEOUT (30 s) to avoid\n// false disconnects when the tab is in the background.\nconst POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 25 * 1000; // 25 seconds\nconst MAX_ERROR_BACKOFF_IN_MS = 30 * 1000; // 30 seconds\nconst POLLING_MANAGER_ORIGIN = 'polling-manager';\n\ntype LogFunction = ( message: string, debug?: object ) => void;\n\ninterface PollingManager {\n\tregisterRoom: ( options: RegisterRoomOptions ) => void;\n\tretryNow: () => void;\n\tunregisterRoom: ( room: string ) => void;\n}\n\ninterface RegisterRoomOptions {\n\troom: string;\n\tdoc: Y.Doc;\n\tawareness: Awareness;\n\tlog: LogFunction;\n\tonStatusChange: ( status: ConnectionStatus ) => void;\n\tonSync: () => void;\n}\n\ninterface RoomState {\n\tclientId: number;\n\tcreateCompactionUpdate: () => SyncUpdate;\n\tendCursor: number;\n\tlocalAwarenessState: LocalAwarenessState;\n\tlog: LogFunction;\n\tonStatusChange: ( status: ConnectionStatus ) => void;\n\tprocessAwarenessUpdate: ( state: AwarenessState ) => void;\n\tprocessDocUpdate: ( update: SyncUpdate ) => SyncUpdate | void;\n\tunregister: () => void;\n\tupdateQueue: UpdateQueue;\n}\n\nconst roomStates: Map< string, RoomState > = new Map();\n\n/**\n * Create a compaction update by merging existing updates. This preserves\n * the original operation metadata (client IDs, logical clocks) so that\n * Yjs deduplication works correctly when the compaction is applied.\n *\n * Deprecated: The server is moving towards full state updates for compaction.\n *\n * @param updates The updates to merge\n */\nfunction createDeprecatedCompactionUpdate( updates: SyncUpdate[] ): SyncUpdate {\n\t// Extract only compaction and update types for merging (skip sync-step updates).\n\t// Decode base64 updates to Uint8Array for merging.\n\tconst mergeable = updates\n\t\t.filter( ( u ) =>\n\t\t\t[ SyncUpdateType.COMPACTION, SyncUpdateType.UPDATE ].includes(\n\t\t\t\tu.type\n\t\t\t)\n\t\t)\n\t\t.map( ( u ) => base64ToUint8Array( u.data ) );\n\n\t// Merge all updates while preserving operation metadata.\n\treturn createSyncUpdate(\n\t\tY.mergeUpdates( mergeable ),\n\t\tSyncUpdateType.COMPACTION\n\t);\n}\n\n/**\n * Create sync step 1 update (announce our state vector).\n *\n * @param doc The Yjs document\n */\nfunction createSyncStep1Update( doc: Y.Doc ): SyncUpdate {\n\tconst encoder = encoding.createEncoder();\n\tsyncProtocol.writeSyncStep1( encoder, doc );\n\treturn createSyncUpdate(\n\t\tencoding.toUint8Array( encoder ),\n\t\tSyncUpdateType.SYNC_STEP_1\n\t);\n}\n\n/**\n * Create sync step 2 update (acknowledge sync step 1).\n *\n * @param doc The Yjs document\n * @param step1 The sync step 1 update received\n */\nfunction createSyncStep2Update( doc: Y.Doc, step1: Uint8Array ): SyncUpdate {\n\tconst decoder = decoding.createDecoder( step1 );\n\tconst encoder = encoding.createEncoder();\n\tsyncProtocol.readSyncMessage(\n\t\tdecoder,\n\t\tencoder,\n\t\tdoc,\n\t\tPOLLING_MANAGER_ORIGIN\n\t);\n\treturn createSyncUpdate(\n\t\tencoding.toUint8Array( encoder ),\n\t\tSyncUpdateType.SYNC_STEP_2\n\t);\n}\n\n/**\n * Process an incoming awareness update from the server.\n *\n * @param state The awareness state received\n * @param awareness The local Awareness instance\n */\nfunction processAwarenessUpdate(\n\tstate: AwarenessState,\n\tawareness: Awareness\n): void {\n\tconst currentStates = awareness.getStates();\n\tconst added = new Set< number >();\n\tconst updated = new Set< number >();\n\n\t// Removed clients are missing from the server state.\n\tconst removed = new Set< number >(\n\t\tcurrentStates.keys().filter( ( clientId ) => ! state[ clientId ] )\n\t);\n\n\tObject.entries( state ).forEach( ( [ clientIdString, awarenessState ] ) => {\n\t\tconst clientId = Number( clientIdString );\n\n\t\t// Skip our own state (we already have it locally).\n\t\tif ( clientId === awareness.clientID ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// A null state should be removed by the server, but handle it here just in case.\n\t\tif ( null === awarenessState ) {\n\t\t\tcurrentStates.delete( clientId );\n\t\t\tremoved.add( clientId );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( ! currentStates.has( clientId ) ) {\n\t\t\tcurrentStates.set( clientId, awarenessState );\n\t\t\tadded.add( clientId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentState = currentStates.get( clientId );\n\n\t\tif (\n\t\t\tJSON.stringify( currentState ) !== JSON.stringify( awarenessState )\n\t\t) {\n\t\t\tcurrentStates.set( clientId, awarenessState );\n\t\t\tupdated.add( clientId );\n\t\t}\n\t} );\n\n\tif ( added.size + updated.size > 0 ) {\n\t\tawareness.emit( 'change', [\n\t\t\t{\n\t\t\t\tadded: Array.from( added ),\n\t\t\t\tupdated: Array.from( updated ),\n\t\t\t\t// Left blank on purpose, as the removal of clients is handled in the if condition below.\n\t\t\t\tremoved: [],\n\t\t\t},\n\t\t] );\n\t}\n\n\tif ( removed.size > 0 ) {\n\t\tremoveAwarenessStates(\n\t\t\tawareness,\n\t\t\tArray.from( removed ),\n\t\t\tPOLLING_MANAGER_ORIGIN\n\t\t);\n\t}\n}\n\n/**\n * Process an incoming sync / document update based on its type.\n *\n * @param update The typed update received\n * @param doc The Yjs document\n * @param onSync Callback when sync is complete\n * @return A response update if needed (e.g., sync_step2 in response to sync_step1)\n */\nfunction processDocUpdate(\n\tupdate: SyncUpdate,\n\tdoc: Y.Doc,\n\tonSync: () => void\n): SyncUpdate | void {\n\tconst data = base64ToUint8Array( update.data );\n\n\tswitch ( update.type ) {\n\t\tcase SyncUpdateType.SYNC_STEP_1: {\n\t\t\t// Respond to sync step 1 with sync step 2.\n\t\t\treturn createSyncStep2Update( doc, data );\n\t\t}\n\n\t\tcase SyncUpdateType.SYNC_STEP_2: {\n\t\t\t// Apply sync step 2 (potentially contains missing updates).\n\t\t\tconst decoder = decoding.createDecoder( data );\n\t\t\tconst encoder = encoding.createEncoder();\n\t\t\tsyncProtocol.readSyncMessage(\n\t\t\t\tdecoder,\n\t\t\t\tencoder,\n\t\t\t\tdoc,\n\t\t\t\tPOLLING_MANAGER_ORIGIN\n\t\t\t);\n\t\t\tonSync();\n\t\t\treturn;\n\t\t}\n\n\t\tcase SyncUpdateType.COMPACTION:\n\t\tcase SyncUpdateType.UPDATE: {\n\t\t\t// Apply document update directly.\n\t\t\tY.applyUpdate( doc, data, POLLING_MANAGER_ORIGIN );\n\t\t}\n\t}\n}\n\nlet areListenersRegistered = false;\nlet hasCollaborators = false;\nlet isActiveBrowser = 'visible' === document.visibilityState;\nlet isPolling = false;\nlet isUnloadPending = false;\nlet pollInterval = POLLING_INTERVAL_IN_MS;\nlet pollingTimeoutId: ReturnType< typeof setTimeout > | null = null;\n\n/**\n * Mark that a page unload has been requested. This fires on\n * `beforeunload` which happens before the browser aborts in-flight\n * fetches, allowing us to distinguish poll failures caused by\n * navigation from genuine server errors in the catch block.\n *\n * If the user cancels the unload (e.g. by dismissing a \"Save Changes?\" dialog),\n * the flag is reset at the start of the next poll cycle so that polling can\n * resume.\n */\nfunction handleBeforeUnload(): void {\n\tisUnloadPending = true;\n}\n\n/**\n * Send a disconnect signal for all registered rooms when the page is\n * being unloaded. Uses `sendBeacon` so the request survives navigation.\n */\nfunction handlePageHide(): void {\n\tconst rooms = Array.from( roomStates.entries() ).map(\n\t\t( [ room, state ] ) => ( {\n\t\t\tafter: 0,\n\t\t\tawareness: null,\n\t\t\tclient_id: state.clientId,\n\t\t\troom,\n\t\t\tupdates: [],\n\t\t} )\n\t);\n\n\tpostSyncUpdateNonBlocking( { rooms } );\n}\n\n/**\n * Hangle change in visibility state of browser tab.\n *\n * Used to trigger a slow down of the collaboration syncs when the\n * browser tab becomes inactive (either the user switches tabs or the\n * screen saver comes on).\n *\n * Fires on the document's visibilitychange event.\n */\nfunction handleVisibilityChange() {\n\tconst wasActive = isActiveBrowser;\n\tisActiveBrowser = document.visibilityState === 'visible';\n\n\tif ( isActiveBrowser && ! wasActive ) {\n\t\t/*\n\t\t * Remove scheduled polling and repoll immediately when reactivated.\n\t\t *\n\t\t * This ensures that any updates by collaborators are immediately\n\t\t * reflected in the document once the browser tab becomes active.\n\t\t * Otherwise there would be a delay of up to 30 seconds before the\n\t\t * updates came through.\n\t\t *\n\t\t * Only repoll if we cleared a pending timeout, meaning the poll loop\n\t\t * was idle between cycles. If no timeout is pending, a poll request\n\t\t * is already in-flight and will pick up the updated isActiveBrowser\n\t\t * value when it schedules the next cycle.\n\t\t */\n\t\tif ( pollingTimeoutId ) {\n\t\t\tclearTimeout( pollingTimeoutId );\n\t\t\tpollingTimeoutId = null;\n\t\t\tpoll();\n\t\t}\n\t}\n}\n\nfunction poll(): void {\n\tisPolling = true;\n\tpollingTimeoutId = null;\n\n\tasync function start(): Promise< void > {\n\t\tif ( 0 === roomStates.size ) {\n\t\t\tisPolling = false;\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the unloading flag at the start of each poll cycle so\n\t\t// it doesn't permanently suppress disconnect after the user\n\t\t// cancels a beforeunload dialog.\n\t\tisUnloadPending = false;\n\n\t\t// Emit 'connecting' status.\n\t\troomStates.forEach( ( state ) => {\n\t\t\tstate.onStatusChange( { status: 'connecting' } );\n\t\t} );\n\n\t\t// Create a payload with all queued updates. We include rooms even if they\n\t\t// have no updates to ensure we receive any incoming updates. Note that we\n\t\t// withhold our own updates until we detect another collaborator using the\n\t\t// queue's pause / resume mechanism.\n\t\tconst payload: SyncPayload = {\n\t\t\trooms: Array.from( roomStates.entries() ).map(\n\t\t\t\t( [ room, state ] ) => ( {\n\t\t\t\t\tafter: state.endCursor ?? 0,\n\t\t\t\t\tawareness: state.localAwarenessState,\n\t\t\t\t\tclient_id: state.clientId,\n\t\t\t\t\troom,\n\t\t\t\t\tupdates: state.updateQueue.get(),\n\t\t\t\t} )\n\t\t\t),\n\t\t};\n\n\t\ttry {\n\t\t\tconst { rooms } = await postSyncUpdate( payload );\n\n\t\t\t// Emit 'connected' status.\n\t\t\troomStates.forEach( ( state ) => {\n\t\t\t\tstate.onStatusChange( { status: 'connected' } );\n\t\t\t} );\n\n\t\t\trooms.forEach( ( room ) => {\n\t\t\t\tif ( ! roomStates.has( room.room ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst roomState = roomStates.get( room.room )!;\n\t\t\t\troomState.endCursor = room.end_cursor;\n\n\t\t\t\t// Process awareness update.\n\t\t\t\troomState.processAwarenessUpdate( room.awareness );\n\n\t\t\t\t// If there is another collaborator, resume the queue for the next poll\n\t\t\t\t// and increase polling frequency.\n\t\t\t\tif ( Object.keys( room.awareness ).length > 1 ) {\n\t\t\t\t\thasCollaborators = true;\n\t\t\t\t\troomState.updateQueue.resume();\n\t\t\t\t}\n\n\t\t\t\t// Process each incoming update and collect any responses.\n\t\t\t\tconst responseUpdates = room.updates\n\t\t\t\t\t.map( ( update ) => roomState.processDocUpdate( update ) )\n\t\t\t\t\t.filter( ( update ): update is SyncUpdate =>\n\t\t\t\t\t\tBoolean( update )\n\t\t\t\t\t);\n\t\t\t\troomState.updateQueue.addBulk( responseUpdates );\n\n\t\t\t\t// Respond to compaction requests from server. The server asks only one\n\t\t\t\t// client at a time to compact (lowest active client ID). We encode our\n\t\t\t\t// full document state to replace all prior updates on the server.\n\t\t\t\tif ( room.should_compact ) {\n\t\t\t\t\troomState.log( 'Server requested compaction update' );\n\t\t\t\t\troomState.updateQueue.clear();\n\t\t\t\t\troomState.updateQueue.add(\n\t\t\t\t\t\troomState.createCompactionUpdate()\n\t\t\t\t\t);\n\t\t\t\t} else if ( room.compaction_request ) {\n\t\t\t\t\t// Deprecated\n\t\t\t\t\troomState.log( 'Server requested (old) compaction update' );\n\t\t\t\t\troomState.updateQueue.add(\n\t\t\t\t\t\tcreateDeprecatedCompactionUpdate(\n\t\t\t\t\t\t\troom.compaction_request\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Recalculate polling interval.\n\t\t\tif ( isActiveBrowser && hasCollaborators ) {\n\t\t\t\tpollInterval = POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS;\n\t\t\t} else if ( isActiveBrowser ) {\n\t\t\t\tpollInterval = POLLING_INTERVAL_IN_MS;\n\t\t\t} else {\n\t\t\t\tpollInterval = POLLING_INTERVAL_BACKGROUND_TAB_IN_MS;\n\t\t\t}\n\t\t} catch ( error ) {\n\t\t\t// Exponential backoff on error: double the backoff time, up to max\n\t\t\tpollInterval = Math.min(\n\t\t\t\tpollInterval * 2,\n\t\t\t\tMAX_ERROR_BACKOFF_IN_MS\n\t\t\t);\n\n\t\t\t// Restore updates to queues on failure so they can be retried.\n\t\t\tfor ( const room of payload.rooms ) {\n\t\t\t\tif ( ! roomStates.has( room.room ) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst state = roomStates.get( room.room )!;\n\t\t\t\tstate.updateQueue.restore( room.updates );\n\t\t\t\tstate.log(\n\t\t\t\t\t'Error posting sync update, will retry with backoff',\n\t\t\t\t\t{\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tnextPoll: pollInterval,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Don't report disconnected status when the request was aborted\n\t\t\t// due to page unload (e.g. during a refresh) to avoid briefly\n\t\t\t// flashing the disconnect dialog before the new page loads.\n\t\t\tif ( ! isUnloadPending ) {\n\t\t\t\troomStates.forEach( ( state ) => {\n\t\t\t\t\tstate.onStatusChange( {\n\t\t\t\t\t\tstatus: 'disconnected',\n\t\t\t\t\t\tretryInMs: pollInterval,\n\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\tpollingTimeoutId = setTimeout( poll, pollInterval );\n\t}\n\n\t// Start polling.\n\tvoid start();\n}\nfunction registerRoom( {\n\troom,\n\tdoc,\n\tawareness,\n\tlog,\n\tonSync,\n\tonStatusChange,\n}: RegisterRoomOptions ): void {\n\tif ( roomStates.has( room ) ) {\n\t\treturn;\n\t}\n\n\t// Note: Queue is initially paused. Call .resume() to unpause.\n\tconst updateQueue = createUpdateQueue( [ createSyncStep1Update( doc ) ] );\n\n\tfunction onAwarenessUpdate(): void {\n\t\troomState.localAwarenessState = awareness.getLocalState() ?? {};\n\t}\n\n\tfunction onDocUpdate( update: Uint8Array, origin: unknown ): void {\n\t\tif ( POLLING_MANAGER_ORIGIN === origin ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Tag local document changes as 'update' type.\n\t\tupdateQueue.add( createSyncUpdate( update, SyncUpdateType.UPDATE ) );\n\t}\n\n\tfunction unregister(): void {\n\t\tdoc.off( 'update', onDocUpdate );\n\t\tawareness.off( 'change', onAwarenessUpdate );\n\t\tupdateQueue.clear();\n\t}\n\n\tconst roomState: RoomState = {\n\t\tclientId: doc.clientID,\n\t\tcreateCompactionUpdate: () =>\n\t\t\tcreateSyncUpdate(\n\t\t\t\tY.encodeStateAsUpdate( doc ),\n\t\t\t\tSyncUpdateType.COMPACTION\n\t\t\t),\n\t\tendCursor: 0,\n\t\tlocalAwarenessState: awareness.getLocalState() ?? {},\n\t\tlog,\n\t\tonStatusChange,\n\t\tprocessAwarenessUpdate: ( state: AwarenessState ) =>\n\t\t\tprocessAwarenessUpdate( state, awareness ),\n\t\tprocessDocUpdate: ( update: SyncUpdate ) =>\n\t\t\tprocessDocUpdate( update, doc, onSync ),\n\t\tunregister,\n\t\tupdateQueue,\n\t};\n\n\tdoc.on( 'update', onDocUpdate );\n\tawareness.on( 'change', onAwarenessUpdate );\n\troomStates.set( room, roomState );\n\n\tif ( ! areListenersRegistered ) {\n\t\twindow.addEventListener( 'beforeunload', handleBeforeUnload );\n\t\twindow.addEventListener( 'pagehide', handlePageHide );\n\t\tdocument.addEventListener( 'visibilitychange', handleVisibilityChange );\n\t\tareListenersRegistered = true;\n\t}\n\n\tif ( ! isPolling ) {\n\t\tpoll();\n\t}\n}\n\nfunction unregisterRoom( room: string ): void {\n\tconst state = roomStates.get( room );\n\tif ( state ) {\n\t\t// Send a disconnect signal so the server removes this client's\n\t\t// awareness entry immediately instead of waiting for the timeout.\n\t\tconst rooms = [\n\t\t\t{\n\t\t\t\tafter: 0,\n\t\t\t\tawareness: null,\n\t\t\t\tclient_id: state.clientId,\n\t\t\t\troom,\n\t\t\t\tupdates: [],\n\t\t\t},\n\t\t];\n\n\t\tpostSyncUpdateNonBlocking( { rooms } );\n\t\tstate.unregister();\n\t\troomStates.delete( room );\n\t}\n\n\tif ( 0 === roomStates.size && areListenersRegistered ) {\n\t\twindow.removeEventListener( 'beforeunload', handleBeforeUnload );\n\t\twindow.removeEventListener( 'pagehide', handlePageHide );\n\t\tdocument.removeEventListener(\n\t\t\t'visibilitychange',\n\t\t\thandleVisibilityChange\n\t\t);\n\t\tareListenersRegistered = false;\n\t}\n}\n\n/**\n * Immediately retry the sync connection by cancelling any pending backoff\n * timeout and triggering a new poll. If a request is already in-flight,\n * the backoff interval is reset so the next scheduled poll fires sooner.\n */\nfunction retryNow(): void {\n\tpollInterval = POLLING_INTERVAL_IN_MS * 2;\n\n\tif ( pollingTimeoutId ) {\n\t\tclearTimeout( pollingTimeoutId );\n\t\tpollingTimeoutId = null;\n\t\tpoll();\n\t}\n}\n\nexport const pollingManager: PollingManager = {\n\tregisterRoom,\n\tretryNow,\n\tunregisterRoom,\n};\n"], | ||
| "mappings": "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", | ||
| "names": [] | ||
| } |
@@ -11,2 +11,8 @@ // packages/sync/src/quill-delta/Delta.ts | ||
| var NULL_CHARACTER = String.fromCharCode(0); | ||
| function normalizeChangeCounts(changes) { | ||
| return changes.map((change) => ({ | ||
| ...change, | ||
| count: change.value.length | ||
| })); | ||
| } | ||
| var getEmbedTypeAndData = (a, b) => { | ||
@@ -273,3 +279,5 @@ if (typeof a !== "object" || a === null) { | ||
| const strings = this.deltasToStrings(other); | ||
| const diffResult = diffChars(strings[0], strings[1]); | ||
| const diffResult = normalizeChangeCounts( | ||
| diffChars(strings[0], strings[1]) | ||
| ); | ||
| const thisIter = new OpIterator(this.ops); | ||
@@ -444,3 +452,5 @@ const otherIter = new OpIterator(other.ops); | ||
| const strings = this.deltasToStrings(other); | ||
| let diffs = diffChars(strings[0], strings[1]); | ||
| let diffs = normalizeChangeCounts( | ||
| diffChars(strings[0], strings[1]) | ||
| ); | ||
| let lastDiffPosition = 0; | ||
@@ -447,0 +457,0 @@ const adjustedDiffs = []; |
| { | ||
| "version": 3, | ||
| "sources": ["../../src/quill-delta/Delta.ts"], | ||
| "sourcesContent": ["// File copied https://github.com/slab/delta/blob/main/src/Delta.ts with changes:\n// - fast-diff swapped out for 'diff',\n// - lodash.clonedeep is replaced with JSON parse / stringify\n// - lodash.isequal is replaced with fast-deep-equal.\n\n// @ts-ignore\n/**\n * External dependencies\n */\nimport type { Change } from 'diff';\nimport { diffChars } from 'diff';\nimport { default as isEqual } from 'fast-deep-equal/es6';\n\n/**\n * Internal dependencies\n */\nimport AttributeMap from './AttributeMap';\nimport Op from './Op';\nimport OpIterator from './OpIterator';\n\nfunction cloneDeep< T >( value: T ): T {\n\treturn JSON.parse( JSON.stringify( value ) ) as T;\n}\n\nconst NULL_CHARACTER = String.fromCharCode( 0 ); // Placeholder char for embed in diff()\n\ninterface EmbedHandler< T > {\n\tcompose: ( a: T, b: T, keepNull: boolean ) => T;\n\tinvert: ( a: T, b: T ) => T;\n\ttransform: ( a: T, b: T, priority: boolean ) => T;\n}\n\nconst getEmbedTypeAndData = (\n\ta: Op[ 'insert' ] | Op[ 'retain' ],\n\tb: Op[ 'insert' ]\n): [ string, unknown, unknown ] => {\n\tif ( typeof a !== 'object' || a === null ) {\n\t\tthrow new Error( `cannot retain a ${ typeof a }` );\n\t}\n\tif ( typeof b !== 'object' || b === null ) {\n\t\tthrow new Error( `cannot retain a ${ typeof b }` );\n\t}\n\tconst embedType = Object.keys( a )[ 0 ];\n\tif ( ! embedType || embedType !== Object.keys( b )[ 0 ] ) {\n\t\tthrow new Error(\n\t\t\t`embed types not matched: ${ embedType } != ${\n\t\t\t\tObject.keys( b )[ 0 ]\n\t\t\t}`\n\t\t);\n\t}\n\treturn [ embedType, a[ embedType ], b[ embedType ] ];\n};\n\nclass Delta {\n\tstatic Op = Op;\n\tstatic OpIterator = OpIterator;\n\tstatic AttributeMap = AttributeMap;\n\tprivate static handlers: {\n\t\t[ embedType: string ]: EmbedHandler< unknown >;\n\t} = {};\n\n\tstatic registerEmbed< T >(\n\t\tembedType: string,\n\t\thandler: EmbedHandler< T >\n\t): void {\n\t\tthis.handlers[ embedType ] = handler as EmbedHandler< unknown >;\n\t}\n\n\tstatic unregisterEmbed( embedType: string ): void {\n\t\tdelete this.handlers[ embedType ];\n\t}\n\n\tprivate static getHandler( embedType: string ): EmbedHandler< unknown > {\n\t\tconst handler = this.handlers[ embedType ];\n\t\tif ( ! handler ) {\n\t\t\tthrow new Error( `no handlers for embed type \"${ embedType }\"` );\n\t\t}\n\t\treturn handler;\n\t}\n\n\tops: Op[];\n\tconstructor( ops?: Op[] | { ops: Op[] } ) {\n\t\t// Assume we are given a well formed ops\n\t\tif ( Array.isArray( ops ) ) {\n\t\t\tthis.ops = ops;\n\t\t} else if (\n\t\t\tops !== null &&\n\t\t\tops !== undefined &&\n\t\t\tArray.isArray( ops.ops )\n\t\t) {\n\t\t\tthis.ops = ops.ops;\n\t\t} else {\n\t\t\tthis.ops = [];\n\t\t}\n\t}\n\n\tinsert(\n\t\targ: string | Record< string, unknown >,\n\t\tattributes?: AttributeMap | null\n\t): this {\n\t\tconst newOp: Op = {};\n\t\tif ( typeof arg === 'string' && arg.length === 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\tnewOp.insert = arg;\n\t\tif (\n\t\t\tattributes !== null &&\n\t\t\tattributes !== undefined &&\n\t\t\ttypeof attributes === 'object' &&\n\t\t\tObject.keys( attributes ).length > 0\n\t\t) {\n\t\t\tnewOp.attributes = attributes;\n\t\t}\n\t\treturn this.push( newOp );\n\t}\n\n\tdelete( length: number ): this {\n\t\tif ( length <= 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\treturn this.push( { delete: length } );\n\t}\n\n\tretain(\n\t\tlength: number | Record< string, unknown >,\n\t\tattributes?: AttributeMap | null\n\t): this {\n\t\tif ( typeof length === 'number' && length <= 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\tconst newOp: Op = { retain: length };\n\t\tif (\n\t\t\tattributes !== null &&\n\t\t\tattributes !== undefined &&\n\t\t\ttypeof attributes === 'object' &&\n\t\t\tObject.keys( attributes ).length > 0\n\t\t) {\n\t\t\tnewOp.attributes = attributes;\n\t\t}\n\t\treturn this.push( newOp );\n\t}\n\n\tpush( newOp: Op ): this {\n\t\tlet index = this.ops.length;\n\t\tlet lastOp = this.ops[ index - 1 ];\n\t\tnewOp = cloneDeep( newOp );\n\t\tif ( typeof lastOp === 'object' ) {\n\t\t\tif (\n\t\t\t\ttypeof newOp.delete === 'number' &&\n\t\t\t\ttypeof lastOp.delete === 'number'\n\t\t\t) {\n\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\tdelete: lastOp.delete + newOp.delete,\n\t\t\t\t};\n\t\t\t\treturn this;\n\t\t\t}\n\t\t\t// Since it does not matter if we insert before or after deleting at the same index,\n\t\t\t// always prefer to insert first\n\t\t\tif (\n\t\t\t\ttypeof lastOp.delete === 'number' &&\n\t\t\t\tnewOp.insert !== null &&\n\t\t\t\tnewOp.insert !== undefined\n\t\t\t) {\n\t\t\t\tindex -= 1;\n\t\t\t\tlastOp = this.ops[ index - 1 ];\n\t\t\t\tif ( typeof lastOp !== 'object' ) {\n\t\t\t\t\tthis.ops.unshift( newOp );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( isEqual( newOp.attributes, lastOp.attributes ) ) {\n\t\t\t\tif (\n\t\t\t\t\ttypeof newOp.insert === 'string' &&\n\t\t\t\t\ttypeof lastOp.insert === 'string'\n\t\t\t\t) {\n\t\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\t\tinsert: lastOp.insert + newOp.insert,\n\t\t\t\t\t};\n\t\t\t\t\tif ( typeof newOp.attributes === 'object' ) {\n\t\t\t\t\t\tthis.ops[ index - 1 ].attributes = newOp.attributes;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof newOp.retain === 'number' &&\n\t\t\t\t\ttypeof lastOp.retain === 'number'\n\t\t\t\t) {\n\t\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\t\tretain: lastOp.retain + newOp.retain,\n\t\t\t\t\t};\n\t\t\t\t\tif ( typeof newOp.attributes === 'object' ) {\n\t\t\t\t\t\tthis.ops[ index - 1 ].attributes = newOp.attributes;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif ( index === this.ops.length ) {\n\t\t\tthis.ops.push( newOp );\n\t\t} else {\n\t\t\tthis.ops.splice( index, 0, newOp );\n\t\t}\n\t\treturn this;\n\t}\n\n\tchop(): this {\n\t\tconst lastOp = this.ops[ this.ops.length - 1 ];\n\t\tif (\n\t\t\tlastOp &&\n\t\t\ttypeof lastOp.retain === 'number' &&\n\t\t\t! lastOp.attributes\n\t\t) {\n\t\t\tthis.ops.pop();\n\t\t}\n\t\treturn this;\n\t}\n\n\tfilter( predicate: ( op: Op, index: number ) => boolean ): Op[] {\n\t\treturn this.ops.filter( predicate );\n\t}\n\n\tforEach( predicate: ( op: Op, index: number ) => void ): void {\n\t\tthis.ops.forEach( predicate );\n\t}\n\n\tmap< T >( predicate: ( op: Op, index: number ) => T ): T[] {\n\t\treturn this.ops.map( predicate );\n\t}\n\n\tpartition( predicate: ( op: Op ) => boolean ): [ Op[], Op[] ] {\n\t\tconst passed: Op[] = [];\n\t\tconst failed: Op[] = [];\n\t\tthis.forEach( ( op ) => {\n\t\t\tconst target = predicate( op ) ? passed : failed;\n\t\t\ttarget.push( op );\n\t\t} );\n\t\treturn [ passed, failed ];\n\t}\n\n\treduce< T >(\n\t\tpredicate: ( accum: T, curr: Op, index: number ) => T,\n\t\tinitialValue: T\n\t): T {\n\t\treturn this.ops.reduce( predicate, initialValue );\n\t}\n\n\tchangeLength(): number {\n\t\treturn this.reduce( ( length, elem ) => {\n\t\t\tif ( elem.insert ) {\n\t\t\t\treturn length + Op.length( elem );\n\t\t\t} else if ( elem.delete ) {\n\t\t\t\treturn length - elem.delete;\n\t\t\t}\n\t\t\treturn length;\n\t\t}, 0 );\n\t}\n\n\tlength(): number {\n\t\treturn this.reduce( ( length, elem ) => {\n\t\t\treturn length + Op.length( elem );\n\t\t}, 0 );\n\t}\n\n\tslice( start = 0, end = Infinity ): Delta {\n\t\tconst ops = [];\n\t\tconst iter = new OpIterator( this.ops );\n\t\tlet index = 0;\n\t\twhile ( index < end && iter.hasNext() ) {\n\t\t\tlet nextOp;\n\t\t\tif ( index < start ) {\n\t\t\t\tnextOp = iter.next( start - index );\n\t\t\t} else {\n\t\t\t\tnextOp = iter.next( end - index );\n\t\t\t\tops.push( nextOp );\n\t\t\t}\n\t\t\tindex += Op.length( nextOp );\n\t\t}\n\t\treturn new Delta( ops );\n\t}\n\n\tcompose( other: Delta ): Delta {\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst ops = [];\n\t\tconst firstOther = otherIter.peek();\n\t\tif (\n\t\t\tfirstOther !== null &&\n\t\t\tfirstOther !== undefined &&\n\t\t\ttypeof firstOther.retain === 'number' &&\n\t\t\t( firstOther.attributes === null ||\n\t\t\t\tfirstOther.attributes === undefined )\n\t\t) {\n\t\t\tlet firstLeft = firstOther.retain;\n\t\t\twhile (\n\t\t\t\tthisIter.peekType() === 'insert' &&\n\t\t\t\tthisIter.peekLength() <= firstLeft\n\t\t\t) {\n\t\t\t\tfirstLeft -= thisIter.peekLength();\n\t\t\t\tops.push( thisIter.next() );\n\t\t\t}\n\t\t\tif ( firstOther.retain - firstLeft > 0 ) {\n\t\t\t\totherIter.next( firstOther.retain - firstLeft );\n\t\t\t}\n\t\t}\n\t\tconst delta = new Delta( ops );\n\t\twhile ( thisIter.hasNext() || otherIter.hasNext() ) {\n\t\t\tif ( otherIter.peekType() === 'insert' ) {\n\t\t\t\tdelta.push( otherIter.next() );\n\t\t\t} else if ( thisIter.peekType() === 'delete' ) {\n\t\t\t\tdelta.push( thisIter.next() );\n\t\t\t} else {\n\t\t\t\tconst length = Math.min(\n\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\totherIter.peekLength()\n\t\t\t\t);\n\t\t\t\tconst thisOp = thisIter.next( length );\n\t\t\t\tconst otherOp = otherIter.next( length );\n\t\t\t\tif ( otherOp.retain ) {\n\t\t\t\t\tconst newOp: Op = {};\n\t\t\t\t\tif ( typeof thisOp.retain === 'number' ) {\n\t\t\t\t\t\tnewOp.retain =\n\t\t\t\t\t\t\ttypeof otherOp.retain === 'number'\n\t\t\t\t\t\t\t\t? length\n\t\t\t\t\t\t\t\t: otherOp.retain;\n\t\t\t\t\t} else if ( typeof otherOp.retain === 'number' ) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tthisOp.retain === null ||\n\t\t\t\t\t\t\tthisOp.retain === undefined\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tnewOp.insert = thisOp.insert;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnewOp.retain = thisOp.retain;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst action =\n\t\t\t\t\t\t\tthisOp.retain === null ||\n\t\t\t\t\t\t\tthisOp.retain === undefined\n\t\t\t\t\t\t\t\t? 'insert'\n\t\t\t\t\t\t\t\t: 'retain';\n\t\t\t\t\t\tconst [ embedType, thisData, otherData ] =\n\t\t\t\t\t\t\tgetEmbedTypeAndData(\n\t\t\t\t\t\t\t\tthisOp[ action ],\n\t\t\t\t\t\t\t\totherOp.retain\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\t\t\tnewOp[ action ] = {\n\t\t\t\t\t\t\t[ embedType ]: handler.compose(\n\t\t\t\t\t\t\t\tthisData,\n\t\t\t\t\t\t\t\totherData,\n\t\t\t\t\t\t\t\taction === 'retain'\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Preserve null when composing with a retain, otherwise remove it for inserts\n\t\t\t\t\tconst attributes = AttributeMap.compose(\n\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\totherOp.attributes,\n\t\t\t\t\t\ttypeof thisOp.retain === 'number'\n\t\t\t\t\t);\n\t\t\t\t\tif ( attributes ) {\n\t\t\t\t\t\tnewOp.attributes = attributes;\n\t\t\t\t\t}\n\t\t\t\t\tdelta.push( newOp );\n\n\t\t\t\t\t// Optimization if rest of other is just retain\n\t\t\t\t\tif (\n\t\t\t\t\t\t! otherIter.hasNext() &&\n\t\t\t\t\t\tisEqual( delta.ops[ delta.ops.length - 1 ], newOp )\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst rest = new Delta( thisIter.rest() );\n\t\t\t\t\t\treturn delta.concat( rest ).chop();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Other op should be delete, we could be an insert or retain\n\t\t\t\t\t// Insert + delete cancels out\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof otherOp.delete === 'number' &&\n\t\t\t\t\t( typeof thisOp.retain === 'number' ||\n\t\t\t\t\t\t( typeof thisOp.retain === 'object' &&\n\t\t\t\t\t\t\tthisOp.retain !== null ) )\n\t\t\t\t) {\n\t\t\t\t\tdelta.push( otherOp );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn delta.chop();\n\t}\n\n\tconcat( other: Delta ): Delta {\n\t\tconst delta = new Delta( this.ops.slice() );\n\t\tif ( other.ops.length > 0 ) {\n\t\t\tdelta.push( other.ops[ 0 ] );\n\t\t\tdelta.ops = delta.ops.concat( other.ops.slice( 1 ) );\n\t\t}\n\t\treturn delta;\n\t}\n\n\tdiff( other: Delta ): Delta {\n\t\tif ( this.ops === other.ops ) {\n\t\t\treturn new Delta();\n\t\t}\n\t\tconst strings = this.deltasToStrings( other );\n\t\tconst diffResult = diffChars( strings[ 0 ], strings[ 1 ] );\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst retDelta = this.convertChangesToDelta(\n\t\t\tdiffResult,\n\t\t\tthisIter,\n\t\t\totherIter\n\t\t);\n\n\t\treturn retDelta.chop();\n\t}\n\n\teachLine(\n\t\tpredicate: (\n\t\t\tline: Delta,\n\t\t\tattributes: AttributeMap,\n\t\t\tindex: number\n\t\t) => boolean | void,\n\t\tnewline = '\\n'\n\t): void {\n\t\tconst iter = new OpIterator( this.ops );\n\t\tlet line = new Delta();\n\t\tlet i = 0;\n\t\twhile ( iter.hasNext() ) {\n\t\t\tif ( iter.peekType() !== 'insert' ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst thisOp = iter.peek();\n\t\t\tconst start = Op.length( thisOp ) - iter.peekLength();\n\t\t\tconst index =\n\t\t\t\ttypeof thisOp.insert === 'string'\n\t\t\t\t\t? thisOp.insert.indexOf( newline, start ) - start\n\t\t\t\t\t: -1;\n\t\t\tif ( index < 0 ) {\n\t\t\t\tline.push( iter.next() );\n\t\t\t} else if ( index > 0 ) {\n\t\t\t\tline.push( iter.next( index ) );\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tpredicate( line, iter.next( 1 ).attributes || {}, i ) ===\n\t\t\t\t\tfalse\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ti += 1;\n\t\t\t\tline = new Delta();\n\t\t\t}\n\t\t}\n\t\tif ( line.length() > 0 ) {\n\t\t\tpredicate( line, {}, i );\n\t\t}\n\t}\n\n\tinvert( base: Delta ): Delta {\n\t\tconst inverted = new Delta();\n\t\tthis.reduce( ( baseIndex, op ) => {\n\t\t\tif ( op.insert ) {\n\t\t\t\tinverted.delete( Op.length( op ) );\n\t\t\t} else if (\n\t\t\t\ttypeof op.retain === 'number' &&\n\t\t\t\t( op.attributes === null || op.attributes === undefined )\n\t\t\t) {\n\t\t\t\tinverted.retain( op.retain );\n\t\t\t\treturn baseIndex + op.retain;\n\t\t\t} else if ( op.delete || typeof op.retain === 'number' ) {\n\t\t\t\tconst length = ( op.delete || op.retain ) as number;\n\t\t\t\tconst slice = base.slice( baseIndex, baseIndex + length );\n\t\t\t\tslice.forEach( ( baseOp ) => {\n\t\t\t\t\tif ( op.delete ) {\n\t\t\t\t\t\tinverted.push( baseOp );\n\t\t\t\t\t} else if ( op.retain && op.attributes ) {\n\t\t\t\t\t\tinverted.retain(\n\t\t\t\t\t\t\tOp.length( baseOp ),\n\t\t\t\t\t\t\tAttributeMap.invert(\n\t\t\t\t\t\t\t\top.attributes,\n\t\t\t\t\t\t\t\tbaseOp.attributes\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\treturn baseIndex + length;\n\t\t\t} else if ( typeof op.retain === 'object' && op.retain !== null ) {\n\t\t\t\tconst slice = base.slice( baseIndex, baseIndex + 1 );\n\t\t\t\tconst baseOp = new OpIterator( slice.ops ).next();\n\t\t\t\tconst [ embedType, opData, baseOpData ] = getEmbedTypeAndData(\n\t\t\t\t\top.retain,\n\t\t\t\t\tbaseOp.insert\n\t\t\t\t);\n\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\tinverted.retain(\n\t\t\t\t\t{ [ embedType ]: handler.invert( opData, baseOpData ) },\n\t\t\t\t\tAttributeMap.invert( op.attributes, baseOp.attributes )\n\t\t\t\t);\n\t\t\t\treturn baseIndex + 1;\n\t\t\t}\n\t\t\treturn baseIndex;\n\t\t}, 0 );\n\t\treturn inverted.chop();\n\t}\n\n\ttransform( index: number, priority?: boolean ): number;\n\ttransform( other: Delta, priority?: boolean ): Delta;\n\ttransform( arg: number | Delta, priority = false ): typeof arg {\n\t\tpriority = !! priority;\n\t\tif ( typeof arg === 'number' ) {\n\t\t\treturn this.transformPosition( arg, priority );\n\t\t}\n\t\tconst other: Delta = arg;\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst delta = new Delta();\n\t\twhile ( thisIter.hasNext() || otherIter.hasNext() ) {\n\t\t\tif (\n\t\t\t\tthisIter.peekType() === 'insert' &&\n\t\t\t\t( priority || otherIter.peekType() !== 'insert' )\n\t\t\t) {\n\t\t\t\tdelta.retain( Op.length( thisIter.next() ) );\n\t\t\t} else if ( otherIter.peekType() === 'insert' ) {\n\t\t\t\tdelta.push( otherIter.next() );\n\t\t\t} else {\n\t\t\t\tconst length = Math.min(\n\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\totherIter.peekLength()\n\t\t\t\t);\n\t\t\t\tconst thisOp = thisIter.next( length );\n\t\t\t\tconst otherOp = otherIter.next( length );\n\t\t\t\tif ( thisOp.delete ) {\n\t\t\t\t\t// Our delete either makes their delete redundant or removes their retain\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if ( otherOp.delete ) {\n\t\t\t\t\tdelta.push( otherOp );\n\t\t\t\t} else {\n\t\t\t\t\tconst thisData = thisOp.retain;\n\t\t\t\t\tconst otherData = otherOp.retain;\n\t\t\t\t\tlet transformedData: Op[ 'retain' ] =\n\t\t\t\t\t\ttypeof otherData === 'object' && otherData !== null\n\t\t\t\t\t\t\t? otherData\n\t\t\t\t\t\t\t: length;\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof thisData === 'object' &&\n\t\t\t\t\t\tthisData !== null &&\n\t\t\t\t\t\ttypeof otherData === 'object' &&\n\t\t\t\t\t\totherData !== null\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst embedType = Object.keys( thisData )[ 0 ];\n\t\t\t\t\t\tif ( embedType === Object.keys( otherData )[ 0 ] ) {\n\t\t\t\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\t\t\t\tif ( handler ) {\n\t\t\t\t\t\t\t\ttransformedData = {\n\t\t\t\t\t\t\t\t\t[ embedType ]: handler.transform(\n\t\t\t\t\t\t\t\t\t\tthisData[ embedType ],\n\t\t\t\t\t\t\t\t\t\totherData[ embedType ],\n\t\t\t\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// We retain either their retain or insert\n\t\t\t\t\tdelta.retain(\n\t\t\t\t\t\ttransformedData,\n\t\t\t\t\t\tAttributeMap.transform(\n\t\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\t\totherOp.attributes,\n\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn delta.chop();\n\t}\n\n\ttransformPosition( index: number, priority = false ): number {\n\t\tpriority = !! priority;\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tlet offset = 0;\n\t\twhile ( thisIter.hasNext() && offset <= index ) {\n\t\t\tconst length = thisIter.peekLength();\n\t\t\tconst nextType = thisIter.peekType();\n\t\t\tthisIter.next();\n\t\t\tif ( nextType === 'delete' ) {\n\t\t\t\tindex -= Math.min( length, index - offset );\n\t\t\t\tcontinue;\n\t\t\t} else if (\n\t\t\t\tnextType === 'insert' &&\n\t\t\t\t( offset < index || ! priority )\n\t\t\t) {\n\t\t\t\tindex += length;\n\t\t\t}\n\t\t\toffset += length;\n\t\t}\n\t\treturn index;\n\t}\n\n\t/**\n\t * Given a Delta and a cursor position, do a diff and attempt to adjust\n\t * the diff to place insertions or deletions at the cursor position.\n\t *\n\t * @param other - The other Delta to diff against.\n\t * @param cursorAfterChange - The cursor position index after the change.\n\t * @return A Delta that attempts to place insertions or deletions at the cursor position.\n\t */\n\tdiffWithCursor( other: Delta, cursorAfterChange: number | null ): Delta {\n\t\tif ( this.ops === other.ops ) {\n\t\t\treturn new Delta();\n\t\t} else if ( cursorAfterChange === null ) {\n\t\t\t// If no cursor position is provided, do a regular diff.\n\t\t\treturn this.diff( other );\n\t\t}\n\n\t\tconst strings = this.deltasToStrings( other );\n\t\tlet diffs = diffChars( strings[ 0 ], strings[ 1 ] );\n\t\tlet lastDiffPosition = 0;\n\t\tconst adjustedDiffs: Change[] = [];\n\n\t\tfor ( let i = 0; i < diffs.length; i++ ) {\n\t\t\tconst diff = diffs[ i ];\n\n\t\t\tconst segmentStart = lastDiffPosition;\n\t\t\tconst segmentEnd = lastDiffPosition + ( diff.count ?? 0 );\n\t\t\tconst isCursorInSegment =\n\t\t\t\tcursorAfterChange > segmentStart &&\n\t\t\t\tcursorAfterChange <= segmentEnd;\n\n\t\t\tconst isUnchangedSegment = ! diff.added && ! diff.removed;\n\t\t\tconst isRemovalSegment = diff.removed && ! diff.added;\n\n\t\t\tconst nextDiff = diffs[ i + 1 ];\n\t\t\tconst isNextDiffAnInsert =\n\t\t\t\tnextDiff && nextDiff.added && ! nextDiff.removed;\n\n\t\t\t// Path 1: Look-ahead strategy\n\t\t\t// If the position of the cursor is in an \"unchanged\" segment, but there's an insertion\n\t\t\t// right after this section, then the insertion has likely been placed in\n\t\t\t// the incorrect spot, and we can move the insertion to the position of the cursor.\n\t\t\tif (\n\t\t\t\tisUnchangedSegment &&\n\t\t\t\tisCursorInSegment &&\n\t\t\t\tisNextDiffAnInsert\n\t\t\t) {\n\t\t\t\tconst movedSegments = this.tryMoveInsertionToCursor(\n\t\t\t\t\tdiff,\n\t\t\t\t\tnextDiff,\n\t\t\t\t\tcursorAfterChange,\n\t\t\t\t\tsegmentStart\n\t\t\t\t);\n\n\t\t\t\tif ( movedSegments ) {\n\t\t\t\t\tadjustedDiffs.push( ...movedSegments );\n\t\t\t\t\t// Skip the next diff since we've already consumed it\n\t\t\t\t\ti++;\n\t\t\t\t\tlastDiffPosition = segmentEnd;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Path 2: Look-back strategy\n\t\t\t// Handle removals by checking if cursor was in the previous unchanged segment\n\t\t\tif ( isRemovalSegment ) {\n\t\t\t\tconst movedSegments = this.tryMoveDeletionToCursor(\n\t\t\t\t\tdiff,\n\t\t\t\t\tadjustedDiffs,\n\t\t\t\t\tcursorAfterChange,\n\t\t\t\t\tlastDiffPosition\n\t\t\t\t);\n\n\t\t\t\tif ( movedSegments ) {\n\t\t\t\t\t// Remove the previous unchanged segment from adjustedDiffs\n\t\t\t\t\tadjustedDiffs.pop();\n\t\t\t\t\tadjustedDiffs.push( ...movedSegments );\n\t\t\t\t\tlastDiffPosition += diff.count ?? 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Path 3: Do nothing - add diff as-is\n\t\t\tadjustedDiffs.push( diff );\n\t\t\tif ( ! diff.added ) {\n\t\t\t\tlastDiffPosition += diff.count ?? 0;\n\t\t\t}\n\t\t}\n\n\t\tdiffs = adjustedDiffs;\n\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst retDelta = this.convertChangesToDelta(\n\t\t\tdiffs,\n\t\t\tthisIter,\n\t\t\totherIter\n\t\t);\n\n\t\treturn retDelta.chop();\n\t}\n\n\t/**\n\t * Try to move an insertion operation from after an unchanged segment to the cursor position within it.\n\t * This is a \"look-ahead\" strategy.\n\t *\n\t * @param diff - The current unchanged diff segment.\n\t * @param nextDiff - The next diff segment (expected to be an insertion).\n\t * @param cursorAfterChange - The cursor position after the change.\n\t * @param segmentStart - The start position of the current segment.\n\t * @return An array of adjusted diff segments if the insertion was successfully moved, null otherwise.\n\t */\n\tprivate tryMoveInsertionToCursor(\n\t\tdiff: Change,\n\t\tnextDiff: Change,\n\t\tcursorAfterChange: number,\n\t\tsegmentStart: number\n\t): Change[] | null {\n\t\tconst nextDiffInsert = nextDiff.value;\n\t\tconst insertLength = nextDiffInsert.length;\n\t\tconst insertOffset = cursorAfterChange - segmentStart - insertLength;\n\n\t\t// Verify that the inserted text matches the text at the cursor position\n\t\tconst textAtCursor = diff.value.substring(\n\t\t\tinsertOffset,\n\t\t\tinsertOffset + nextDiffInsert.length\n\t\t);\n\t\tconst isInsertMoveable = textAtCursor === nextDiffInsert;\n\n\t\t// The insert text matches what's at the cursor position,\n\t\t// so we can safely move the insertion to the cursor position.\n\t\tif ( ! isInsertMoveable ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Split the current segment at the cursor\n\t\tconst beforeCursor = diff.value.substring( 0, insertOffset );\n\t\tconst afterCursor = diff.value.substring( insertOffset );\n\n\t\tconst result: Change[] = [];\n\n\t\t// Add before cursor part (if not empty)\n\t\tif ( beforeCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: beforeCursor,\n\t\t\t\tcount: beforeCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\t// Add the insertion in the middle\n\t\tresult.push( nextDiff );\n\n\t\t// Add after cursor part (if not empty)\n\t\tif ( afterCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: afterCursor,\n\t\t\t\tcount: afterCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Try to move a deletion operation to the cursor position by looking back at the previous unchanged segment.\n\t * This is a \"look-back\" strategy.\n\t *\n\t * @param diff - The current deletion diff segment.\n\t * @param adjustedDiffs - The array of previously processed diff segments.\n\t * @param cursorAfterChange - The cursor position after the change.\n\t * @param lastDiffPosition - The position in the document up to (but not including) the current diff.\n\t * @return An array of adjusted diff segments if the deletion was successfully moved, null otherwise.\n\t */\n\tprivate tryMoveDeletionToCursor(\n\t\tdiff: Change,\n\t\tadjustedDiffs: Change[],\n\t\tcursorAfterChange: number,\n\t\tlastDiffPosition: number\n\t): Change[] | null {\n\t\t// Check if there's a preceding unchanged segment where cursor falls\n\t\t// and the deleted characters match characters in that segment\n\t\tconst prevDiff = adjustedDiffs[ adjustedDiffs.length - 1 ];\n\n\t\tif ( ! prevDiff || prevDiff.added || prevDiff.removed ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst prevSegmentStart = lastDiffPosition - ( prevDiff.count ?? 0 );\n\t\tconst prevSegmentEnd = lastDiffPosition;\n\n\t\t// Check if cursor is within or at the end of the previous unchanged segment\n\t\tif (\n\t\t\tcursorAfterChange < prevSegmentStart ||\n\t\t\tcursorAfterChange >= prevSegmentEnd\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Check if the deleted characters match the text at the cursor position\n\t\tconst deletedChars = diff.value;\n\t\tconst deleteOffset = cursorAfterChange - prevSegmentStart;\n\t\tconst textAtCursor = prevDiff.value.substring(\n\t\t\tdeleteOffset,\n\t\t\tdeleteOffset + deletedChars.length\n\t\t);\n\t\tconst canBePlacedHere = textAtCursor === deletedChars;\n\n\t\tif ( ! canBePlacedHere ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Split the unchanged segment at the cursor and place deletion there\n\t\tconst beforeCursor = prevDiff.value.substring( 0, deleteOffset );\n\t\tconst atAndAfterCursor = prevDiff.value.substring( deleteOffset );\n\n\t\t// The deletion should consume characters starting at cursor\n\t\tconst deletionLength = diff.count ?? 0;\n\t\tconst afterDeletion = atAndAfterCursor.substring( deletionLength );\n\n\t\tconst result: Change[] = [];\n\n\t\t// Add before cursor part (if not empty)\n\t\tif ( beforeCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: beforeCursor,\n\t\t\t\tcount: beforeCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\t// Add the deletion\n\t\tresult.push( diff );\n\n\t\t// Add after deletion part (if not empty)\n\t\tif ( afterDeletion.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: afterDeletion,\n\t\t\t\tcount: afterDeletion.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Convert two Deltas to string representations for diffing.\n\t *\n\t * @param other - The other Delta to convert.\n\t * @return A tuple of [thisString, otherString].\n\t */\n\tprivate deltasToStrings( other: Delta ): [ string, string ] {\n\t\treturn [ this, other ].map( ( delta ) => {\n\t\t\treturn delta\n\t\t\t\t.map( ( op ) => {\n\t\t\t\t\tif ( op.insert !== null || op.insert !== undefined ) {\n\t\t\t\t\t\treturn typeof op.insert === 'string'\n\t\t\t\t\t\t\t? op.insert\n\t\t\t\t\t\t\t: NULL_CHARACTER;\n\t\t\t\t\t}\n\t\t\t\t\tconst prep = delta === other ? 'on' : 'with';\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t'diff() called ' + prep + ' non-document'\n\t\t\t\t\t);\n\t\t\t\t} )\n\t\t\t\t.join( '' );\n\t\t} ) as [ string, string ];\n\t}\n\n\t/**\n\t * Process diff changes and convert them to Delta operations.\n\t *\n\t * @param changes - The array of changes from the diff algorithm.\n\t * @param thisIter - Iterator for this Delta's operations.\n\t * @param otherIter - Iterator for the other Delta's operations.\n\t * @return A Delta containing the processed diff operations.\n\t */\n\tprivate convertChangesToDelta(\n\t\tchanges: Change[],\n\t\tthisIter: OpIterator,\n\t\totherIter: OpIterator\n\t): Delta {\n\t\tconst retDelta = new Delta();\n\t\tchanges.forEach( ( component: Change ) => {\n\t\t\tlet length = component.count ?? 0;\n\t\t\twhile ( length > 0 ) {\n\t\t\t\tlet opLength = 0;\n\t\t\t\tif ( component.added ) {\n\t\t\t\t\topLength = Math.min( otherIter.peekLength(), length );\n\t\t\t\t\tretDelta.push( otherIter.next( opLength ) );\n\t\t\t\t} else if ( component.removed ) {\n\t\t\t\t\topLength = Math.min( length, thisIter.peekLength() );\n\t\t\t\t\tthisIter.next( opLength );\n\t\t\t\t\tretDelta.delete( opLength );\n\t\t\t\t} else {\n\t\t\t\t\topLength = Math.min(\n\t\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\t\totherIter.peekLength(),\n\t\t\t\t\t\tlength\n\t\t\t\t\t);\n\t\t\t\t\tconst thisOp = thisIter.next( opLength );\n\t\t\t\t\tconst otherOp = otherIter.next( opLength );\n\t\t\t\t\tif ( isEqual( thisOp.insert, otherOp.insert ) ) {\n\t\t\t\t\t\tretDelta.retain(\n\t\t\t\t\t\t\topLength,\n\t\t\t\t\t\t\tAttributeMap.diff(\n\t\t\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\t\t\totherOp.attributes\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tretDelta.push( otherOp ).delete( opLength );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlength -= opLength;\n\t\t\t}\n\t\t} );\n\t\treturn retDelta;\n\t}\n}\n\nexport default Delta;\nexport { Op, OpIterator, AttributeMap };\n"], | ||
| "mappings": "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", | ||
| "sourcesContent": ["// File copied https://github.com/slab/delta/blob/main/src/Delta.ts with changes:\n// - fast-diff swapped out for 'diff',\n// - lodash.clonedeep is replaced with JSON parse / stringify\n// - lodash.isequal is replaced with fast-deep-equal.\n\n// @ts-ignore\n/**\n * External dependencies\n */\nimport type { Change } from 'diff';\nimport { diffChars } from 'diff';\nimport { default as isEqual } from 'fast-deep-equal/es6';\n\n/**\n * Internal dependencies\n */\nimport AttributeMap from './AttributeMap';\nimport Op from './Op';\nimport OpIterator from './OpIterator';\n\nfunction cloneDeep< T >( value: T ): T {\n\treturn JSON.parse( JSON.stringify( value ) ) as T;\n}\n\nconst NULL_CHARACTER = String.fromCharCode( 0 ); // Placeholder char for embed in diff()\n\n/**\n * Normalize diff changes so that `count` reflects UTF-16 code-unit length\n * rather than grapheme-cluster count (which diffChars may return when\n * Intl.Segmenter is available, e.g. diff v8+).\n *\n * @param changes - The array of changes from diffChars.\n * @return The changes with `count` normalized to UTF-16 code-unit length.\n */\nfunction normalizeChangeCounts( changes: Change[] ): Change[] {\n\treturn changes.map( ( change ) => ( {\n\t\t...change,\n\t\tcount: change.value.length,\n\t} ) );\n}\n\ninterface EmbedHandler< T > {\n\tcompose: ( a: T, b: T, keepNull: boolean ) => T;\n\tinvert: ( a: T, b: T ) => T;\n\ttransform: ( a: T, b: T, priority: boolean ) => T;\n}\n\nconst getEmbedTypeAndData = (\n\ta: Op[ 'insert' ] | Op[ 'retain' ],\n\tb: Op[ 'insert' ]\n): [ string, unknown, unknown ] => {\n\tif ( typeof a !== 'object' || a === null ) {\n\t\tthrow new Error( `cannot retain a ${ typeof a }` );\n\t}\n\tif ( typeof b !== 'object' || b === null ) {\n\t\tthrow new Error( `cannot retain a ${ typeof b }` );\n\t}\n\tconst embedType = Object.keys( a )[ 0 ];\n\tif ( ! embedType || embedType !== Object.keys( b )[ 0 ] ) {\n\t\tthrow new Error(\n\t\t\t`embed types not matched: ${ embedType } != ${\n\t\t\t\tObject.keys( b )[ 0 ]\n\t\t\t}`\n\t\t);\n\t}\n\treturn [ embedType, a[ embedType ], b[ embedType ] ];\n};\n\nclass Delta {\n\tstatic Op = Op;\n\tstatic OpIterator = OpIterator;\n\tstatic AttributeMap = AttributeMap;\n\tprivate static handlers: {\n\t\t[ embedType: string ]: EmbedHandler< unknown >;\n\t} = {};\n\n\tstatic registerEmbed< T >(\n\t\tembedType: string,\n\t\thandler: EmbedHandler< T >\n\t): void {\n\t\tthis.handlers[ embedType ] = handler as EmbedHandler< unknown >;\n\t}\n\n\tstatic unregisterEmbed( embedType: string ): void {\n\t\tdelete this.handlers[ embedType ];\n\t}\n\n\tprivate static getHandler( embedType: string ): EmbedHandler< unknown > {\n\t\tconst handler = this.handlers[ embedType ];\n\t\tif ( ! handler ) {\n\t\t\tthrow new Error( `no handlers for embed type \"${ embedType }\"` );\n\t\t}\n\t\treturn handler;\n\t}\n\n\tops: Op[];\n\tconstructor( ops?: Op[] | { ops: Op[] } ) {\n\t\t// Assume we are given a well formed ops\n\t\tif ( Array.isArray( ops ) ) {\n\t\t\tthis.ops = ops;\n\t\t} else if (\n\t\t\tops !== null &&\n\t\t\tops !== undefined &&\n\t\t\tArray.isArray( ops.ops )\n\t\t) {\n\t\t\tthis.ops = ops.ops;\n\t\t} else {\n\t\t\tthis.ops = [];\n\t\t}\n\t}\n\n\tinsert(\n\t\targ: string | Record< string, unknown >,\n\t\tattributes?: AttributeMap | null\n\t): this {\n\t\tconst newOp: Op = {};\n\t\tif ( typeof arg === 'string' && arg.length === 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\tnewOp.insert = arg;\n\t\tif (\n\t\t\tattributes !== null &&\n\t\t\tattributes !== undefined &&\n\t\t\ttypeof attributes === 'object' &&\n\t\t\tObject.keys( attributes ).length > 0\n\t\t) {\n\t\t\tnewOp.attributes = attributes;\n\t\t}\n\t\treturn this.push( newOp );\n\t}\n\n\tdelete( length: number ): this {\n\t\tif ( length <= 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\treturn this.push( { delete: length } );\n\t}\n\n\tretain(\n\t\tlength: number | Record< string, unknown >,\n\t\tattributes?: AttributeMap | null\n\t): this {\n\t\tif ( typeof length === 'number' && length <= 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\tconst newOp: Op = { retain: length };\n\t\tif (\n\t\t\tattributes !== null &&\n\t\t\tattributes !== undefined &&\n\t\t\ttypeof attributes === 'object' &&\n\t\t\tObject.keys( attributes ).length > 0\n\t\t) {\n\t\t\tnewOp.attributes = attributes;\n\t\t}\n\t\treturn this.push( newOp );\n\t}\n\n\tpush( newOp: Op ): this {\n\t\tlet index = this.ops.length;\n\t\tlet lastOp = this.ops[ index - 1 ];\n\t\tnewOp = cloneDeep( newOp );\n\t\tif ( typeof lastOp === 'object' ) {\n\t\t\tif (\n\t\t\t\ttypeof newOp.delete === 'number' &&\n\t\t\t\ttypeof lastOp.delete === 'number'\n\t\t\t) {\n\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\tdelete: lastOp.delete + newOp.delete,\n\t\t\t\t};\n\t\t\t\treturn this;\n\t\t\t}\n\t\t\t// Since it does not matter if we insert before or after deleting at the same index,\n\t\t\t// always prefer to insert first\n\t\t\tif (\n\t\t\t\ttypeof lastOp.delete === 'number' &&\n\t\t\t\tnewOp.insert !== null &&\n\t\t\t\tnewOp.insert !== undefined\n\t\t\t) {\n\t\t\t\tindex -= 1;\n\t\t\t\tlastOp = this.ops[ index - 1 ];\n\t\t\t\tif ( typeof lastOp !== 'object' ) {\n\t\t\t\t\tthis.ops.unshift( newOp );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( isEqual( newOp.attributes, lastOp.attributes ) ) {\n\t\t\t\tif (\n\t\t\t\t\ttypeof newOp.insert === 'string' &&\n\t\t\t\t\ttypeof lastOp.insert === 'string'\n\t\t\t\t) {\n\t\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\t\tinsert: lastOp.insert + newOp.insert,\n\t\t\t\t\t};\n\t\t\t\t\tif ( typeof newOp.attributes === 'object' ) {\n\t\t\t\t\t\tthis.ops[ index - 1 ].attributes = newOp.attributes;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof newOp.retain === 'number' &&\n\t\t\t\t\ttypeof lastOp.retain === 'number'\n\t\t\t\t) {\n\t\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\t\tretain: lastOp.retain + newOp.retain,\n\t\t\t\t\t};\n\t\t\t\t\tif ( typeof newOp.attributes === 'object' ) {\n\t\t\t\t\t\tthis.ops[ index - 1 ].attributes = newOp.attributes;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif ( index === this.ops.length ) {\n\t\t\tthis.ops.push( newOp );\n\t\t} else {\n\t\t\tthis.ops.splice( index, 0, newOp );\n\t\t}\n\t\treturn this;\n\t}\n\n\tchop(): this {\n\t\tconst lastOp = this.ops[ this.ops.length - 1 ];\n\t\tif (\n\t\t\tlastOp &&\n\t\t\ttypeof lastOp.retain === 'number' &&\n\t\t\t! lastOp.attributes\n\t\t) {\n\t\t\tthis.ops.pop();\n\t\t}\n\t\treturn this;\n\t}\n\n\tfilter( predicate: ( op: Op, index: number ) => boolean ): Op[] {\n\t\treturn this.ops.filter( predicate );\n\t}\n\n\tforEach( predicate: ( op: Op, index: number ) => void ): void {\n\t\tthis.ops.forEach( predicate );\n\t}\n\n\tmap< T >( predicate: ( op: Op, index: number ) => T ): T[] {\n\t\treturn this.ops.map( predicate );\n\t}\n\n\tpartition( predicate: ( op: Op ) => boolean ): [ Op[], Op[] ] {\n\t\tconst passed: Op[] = [];\n\t\tconst failed: Op[] = [];\n\t\tthis.forEach( ( op ) => {\n\t\t\tconst target = predicate( op ) ? passed : failed;\n\t\t\ttarget.push( op );\n\t\t} );\n\t\treturn [ passed, failed ];\n\t}\n\n\treduce< T >(\n\t\tpredicate: ( accum: T, curr: Op, index: number ) => T,\n\t\tinitialValue: T\n\t): T {\n\t\treturn this.ops.reduce( predicate, initialValue );\n\t}\n\n\tchangeLength(): number {\n\t\treturn this.reduce( ( length, elem ) => {\n\t\t\tif ( elem.insert ) {\n\t\t\t\treturn length + Op.length( elem );\n\t\t\t} else if ( elem.delete ) {\n\t\t\t\treturn length - elem.delete;\n\t\t\t}\n\t\t\treturn length;\n\t\t}, 0 );\n\t}\n\n\tlength(): number {\n\t\treturn this.reduce( ( length, elem ) => {\n\t\t\treturn length + Op.length( elem );\n\t\t}, 0 );\n\t}\n\n\tslice( start = 0, end = Infinity ): Delta {\n\t\tconst ops = [];\n\t\tconst iter = new OpIterator( this.ops );\n\t\tlet index = 0;\n\t\twhile ( index < end && iter.hasNext() ) {\n\t\t\tlet nextOp;\n\t\t\tif ( index < start ) {\n\t\t\t\tnextOp = iter.next( start - index );\n\t\t\t} else {\n\t\t\t\tnextOp = iter.next( end - index );\n\t\t\t\tops.push( nextOp );\n\t\t\t}\n\t\t\tindex += Op.length( nextOp );\n\t\t}\n\t\treturn new Delta( ops );\n\t}\n\n\tcompose( other: Delta ): Delta {\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst ops = [];\n\t\tconst firstOther = otherIter.peek();\n\t\tif (\n\t\t\tfirstOther !== null &&\n\t\t\tfirstOther !== undefined &&\n\t\t\ttypeof firstOther.retain === 'number' &&\n\t\t\t( firstOther.attributes === null ||\n\t\t\t\tfirstOther.attributes === undefined )\n\t\t) {\n\t\t\tlet firstLeft = firstOther.retain;\n\t\t\twhile (\n\t\t\t\tthisIter.peekType() === 'insert' &&\n\t\t\t\tthisIter.peekLength() <= firstLeft\n\t\t\t) {\n\t\t\t\tfirstLeft -= thisIter.peekLength();\n\t\t\t\tops.push( thisIter.next() );\n\t\t\t}\n\t\t\tif ( firstOther.retain - firstLeft > 0 ) {\n\t\t\t\totherIter.next( firstOther.retain - firstLeft );\n\t\t\t}\n\t\t}\n\t\tconst delta = new Delta( ops );\n\t\twhile ( thisIter.hasNext() || otherIter.hasNext() ) {\n\t\t\tif ( otherIter.peekType() === 'insert' ) {\n\t\t\t\tdelta.push( otherIter.next() );\n\t\t\t} else if ( thisIter.peekType() === 'delete' ) {\n\t\t\t\tdelta.push( thisIter.next() );\n\t\t\t} else {\n\t\t\t\tconst length = Math.min(\n\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\totherIter.peekLength()\n\t\t\t\t);\n\t\t\t\tconst thisOp = thisIter.next( length );\n\t\t\t\tconst otherOp = otherIter.next( length );\n\t\t\t\tif ( otherOp.retain ) {\n\t\t\t\t\tconst newOp: Op = {};\n\t\t\t\t\tif ( typeof thisOp.retain === 'number' ) {\n\t\t\t\t\t\tnewOp.retain =\n\t\t\t\t\t\t\ttypeof otherOp.retain === 'number'\n\t\t\t\t\t\t\t\t? length\n\t\t\t\t\t\t\t\t: otherOp.retain;\n\t\t\t\t\t} else if ( typeof otherOp.retain === 'number' ) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tthisOp.retain === null ||\n\t\t\t\t\t\t\tthisOp.retain === undefined\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tnewOp.insert = thisOp.insert;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnewOp.retain = thisOp.retain;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst action =\n\t\t\t\t\t\t\tthisOp.retain === null ||\n\t\t\t\t\t\t\tthisOp.retain === undefined\n\t\t\t\t\t\t\t\t? 'insert'\n\t\t\t\t\t\t\t\t: 'retain';\n\t\t\t\t\t\tconst [ embedType, thisData, otherData ] =\n\t\t\t\t\t\t\tgetEmbedTypeAndData(\n\t\t\t\t\t\t\t\tthisOp[ action ],\n\t\t\t\t\t\t\t\totherOp.retain\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\t\t\tnewOp[ action ] = {\n\t\t\t\t\t\t\t[ embedType ]: handler.compose(\n\t\t\t\t\t\t\t\tthisData,\n\t\t\t\t\t\t\t\totherData,\n\t\t\t\t\t\t\t\taction === 'retain'\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Preserve null when composing with a retain, otherwise remove it for inserts\n\t\t\t\t\tconst attributes = AttributeMap.compose(\n\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\totherOp.attributes,\n\t\t\t\t\t\ttypeof thisOp.retain === 'number'\n\t\t\t\t\t);\n\t\t\t\t\tif ( attributes ) {\n\t\t\t\t\t\tnewOp.attributes = attributes;\n\t\t\t\t\t}\n\t\t\t\t\tdelta.push( newOp );\n\n\t\t\t\t\t// Optimization if rest of other is just retain\n\t\t\t\t\tif (\n\t\t\t\t\t\t! otherIter.hasNext() &&\n\t\t\t\t\t\tisEqual( delta.ops[ delta.ops.length - 1 ], newOp )\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst rest = new Delta( thisIter.rest() );\n\t\t\t\t\t\treturn delta.concat( rest ).chop();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Other op should be delete, we could be an insert or retain\n\t\t\t\t\t// Insert + delete cancels out\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof otherOp.delete === 'number' &&\n\t\t\t\t\t( typeof thisOp.retain === 'number' ||\n\t\t\t\t\t\t( typeof thisOp.retain === 'object' &&\n\t\t\t\t\t\t\tthisOp.retain !== null ) )\n\t\t\t\t) {\n\t\t\t\t\tdelta.push( otherOp );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn delta.chop();\n\t}\n\n\tconcat( other: Delta ): Delta {\n\t\tconst delta = new Delta( this.ops.slice() );\n\t\tif ( other.ops.length > 0 ) {\n\t\t\tdelta.push( other.ops[ 0 ] );\n\t\t\tdelta.ops = delta.ops.concat( other.ops.slice( 1 ) );\n\t\t}\n\t\treturn delta;\n\t}\n\n\tdiff( other: Delta ): Delta {\n\t\tif ( this.ops === other.ops ) {\n\t\t\treturn new Delta();\n\t\t}\n\t\tconst strings = this.deltasToStrings( other );\n\t\tconst diffResult = normalizeChangeCounts(\n\t\t\tdiffChars( strings[ 0 ], strings[ 1 ] )\n\t\t);\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst retDelta = this.convertChangesToDelta(\n\t\t\tdiffResult,\n\t\t\tthisIter,\n\t\t\totherIter\n\t\t);\n\n\t\treturn retDelta.chop();\n\t}\n\n\teachLine(\n\t\tpredicate: (\n\t\t\tline: Delta,\n\t\t\tattributes: AttributeMap,\n\t\t\tindex: number\n\t\t) => boolean | void,\n\t\tnewline = '\\n'\n\t): void {\n\t\tconst iter = new OpIterator( this.ops );\n\t\tlet line = new Delta();\n\t\tlet i = 0;\n\t\twhile ( iter.hasNext() ) {\n\t\t\tif ( iter.peekType() !== 'insert' ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst thisOp = iter.peek();\n\t\t\tconst start = Op.length( thisOp ) - iter.peekLength();\n\t\t\tconst index =\n\t\t\t\ttypeof thisOp.insert === 'string'\n\t\t\t\t\t? thisOp.insert.indexOf( newline, start ) - start\n\t\t\t\t\t: -1;\n\t\t\tif ( index < 0 ) {\n\t\t\t\tline.push( iter.next() );\n\t\t\t} else if ( index > 0 ) {\n\t\t\t\tline.push( iter.next( index ) );\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tpredicate( line, iter.next( 1 ).attributes || {}, i ) ===\n\t\t\t\t\tfalse\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ti += 1;\n\t\t\t\tline = new Delta();\n\t\t\t}\n\t\t}\n\t\tif ( line.length() > 0 ) {\n\t\t\tpredicate( line, {}, i );\n\t\t}\n\t}\n\n\tinvert( base: Delta ): Delta {\n\t\tconst inverted = new Delta();\n\t\tthis.reduce( ( baseIndex, op ) => {\n\t\t\tif ( op.insert ) {\n\t\t\t\tinverted.delete( Op.length( op ) );\n\t\t\t} else if (\n\t\t\t\ttypeof op.retain === 'number' &&\n\t\t\t\t( op.attributes === null || op.attributes === undefined )\n\t\t\t) {\n\t\t\t\tinverted.retain( op.retain );\n\t\t\t\treturn baseIndex + op.retain;\n\t\t\t} else if ( op.delete || typeof op.retain === 'number' ) {\n\t\t\t\tconst length = ( op.delete || op.retain ) as number;\n\t\t\t\tconst slice = base.slice( baseIndex, baseIndex + length );\n\t\t\t\tslice.forEach( ( baseOp ) => {\n\t\t\t\t\tif ( op.delete ) {\n\t\t\t\t\t\tinverted.push( baseOp );\n\t\t\t\t\t} else if ( op.retain && op.attributes ) {\n\t\t\t\t\t\tinverted.retain(\n\t\t\t\t\t\t\tOp.length( baseOp ),\n\t\t\t\t\t\t\tAttributeMap.invert(\n\t\t\t\t\t\t\t\top.attributes,\n\t\t\t\t\t\t\t\tbaseOp.attributes\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\treturn baseIndex + length;\n\t\t\t} else if ( typeof op.retain === 'object' && op.retain !== null ) {\n\t\t\t\tconst slice = base.slice( baseIndex, baseIndex + 1 );\n\t\t\t\tconst baseOp = new OpIterator( slice.ops ).next();\n\t\t\t\tconst [ embedType, opData, baseOpData ] = getEmbedTypeAndData(\n\t\t\t\t\top.retain,\n\t\t\t\t\tbaseOp.insert\n\t\t\t\t);\n\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\tinverted.retain(\n\t\t\t\t\t{ [ embedType ]: handler.invert( opData, baseOpData ) },\n\t\t\t\t\tAttributeMap.invert( op.attributes, baseOp.attributes )\n\t\t\t\t);\n\t\t\t\treturn baseIndex + 1;\n\t\t\t}\n\t\t\treturn baseIndex;\n\t\t}, 0 );\n\t\treturn inverted.chop();\n\t}\n\n\ttransform( index: number, priority?: boolean ): number;\n\ttransform( other: Delta, priority?: boolean ): Delta;\n\ttransform( arg: number | Delta, priority = false ): typeof arg {\n\t\tpriority = !! priority;\n\t\tif ( typeof arg === 'number' ) {\n\t\t\treturn this.transformPosition( arg, priority );\n\t\t}\n\t\tconst other: Delta = arg;\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst delta = new Delta();\n\t\twhile ( thisIter.hasNext() || otherIter.hasNext() ) {\n\t\t\tif (\n\t\t\t\tthisIter.peekType() === 'insert' &&\n\t\t\t\t( priority || otherIter.peekType() !== 'insert' )\n\t\t\t) {\n\t\t\t\tdelta.retain( Op.length( thisIter.next() ) );\n\t\t\t} else if ( otherIter.peekType() === 'insert' ) {\n\t\t\t\tdelta.push( otherIter.next() );\n\t\t\t} else {\n\t\t\t\tconst length = Math.min(\n\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\totherIter.peekLength()\n\t\t\t\t);\n\t\t\t\tconst thisOp = thisIter.next( length );\n\t\t\t\tconst otherOp = otherIter.next( length );\n\t\t\t\tif ( thisOp.delete ) {\n\t\t\t\t\t// Our delete either makes their delete redundant or removes their retain\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if ( otherOp.delete ) {\n\t\t\t\t\tdelta.push( otherOp );\n\t\t\t\t} else {\n\t\t\t\t\tconst thisData = thisOp.retain;\n\t\t\t\t\tconst otherData = otherOp.retain;\n\t\t\t\t\tlet transformedData: Op[ 'retain' ] =\n\t\t\t\t\t\ttypeof otherData === 'object' && otherData !== null\n\t\t\t\t\t\t\t? otherData\n\t\t\t\t\t\t\t: length;\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof thisData === 'object' &&\n\t\t\t\t\t\tthisData !== null &&\n\t\t\t\t\t\ttypeof otherData === 'object' &&\n\t\t\t\t\t\totherData !== null\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst embedType = Object.keys( thisData )[ 0 ];\n\t\t\t\t\t\tif ( embedType === Object.keys( otherData )[ 0 ] ) {\n\t\t\t\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\t\t\t\tif ( handler ) {\n\t\t\t\t\t\t\t\ttransformedData = {\n\t\t\t\t\t\t\t\t\t[ embedType ]: handler.transform(\n\t\t\t\t\t\t\t\t\t\tthisData[ embedType ],\n\t\t\t\t\t\t\t\t\t\totherData[ embedType ],\n\t\t\t\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// We retain either their retain or insert\n\t\t\t\t\tdelta.retain(\n\t\t\t\t\t\ttransformedData,\n\t\t\t\t\t\tAttributeMap.transform(\n\t\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\t\totherOp.attributes,\n\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn delta.chop();\n\t}\n\n\ttransformPosition( index: number, priority = false ): number {\n\t\tpriority = !! priority;\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tlet offset = 0;\n\t\twhile ( thisIter.hasNext() && offset <= index ) {\n\t\t\tconst length = thisIter.peekLength();\n\t\t\tconst nextType = thisIter.peekType();\n\t\t\tthisIter.next();\n\t\t\tif ( nextType === 'delete' ) {\n\t\t\t\tindex -= Math.min( length, index - offset );\n\t\t\t\tcontinue;\n\t\t\t} else if (\n\t\t\t\tnextType === 'insert' &&\n\t\t\t\t( offset < index || ! priority )\n\t\t\t) {\n\t\t\t\tindex += length;\n\t\t\t}\n\t\t\toffset += length;\n\t\t}\n\t\treturn index;\n\t}\n\n\t/**\n\t * Given a Delta and a cursor position, do a diff and attempt to adjust\n\t * the diff to place insertions or deletions at the cursor position.\n\t *\n\t * @param other - The other Delta to diff against.\n\t * @param cursorAfterChange - The cursor position index after the change.\n\t * @return A Delta that attempts to place insertions or deletions at the cursor position.\n\t */\n\tdiffWithCursor( other: Delta, cursorAfterChange: number | null ): Delta {\n\t\tif ( this.ops === other.ops ) {\n\t\t\treturn new Delta();\n\t\t} else if ( cursorAfterChange === null ) {\n\t\t\t// If no cursor position is provided, do a regular diff.\n\t\t\treturn this.diff( other );\n\t\t}\n\n\t\tconst strings = this.deltasToStrings( other );\n\t\tlet diffs = normalizeChangeCounts(\n\t\t\tdiffChars( strings[ 0 ], strings[ 1 ] )\n\t\t);\n\t\tlet lastDiffPosition = 0;\n\t\tconst adjustedDiffs: Change[] = [];\n\n\t\tfor ( let i = 0; i < diffs.length; i++ ) {\n\t\t\tconst diff = diffs[ i ];\n\n\t\t\tconst segmentStart = lastDiffPosition;\n\t\t\tconst segmentEnd = lastDiffPosition + ( diff.count ?? 0 );\n\t\t\tconst isCursorInSegment =\n\t\t\t\tcursorAfterChange > segmentStart &&\n\t\t\t\tcursorAfterChange <= segmentEnd;\n\n\t\t\tconst isUnchangedSegment = ! diff.added && ! diff.removed;\n\t\t\tconst isRemovalSegment = diff.removed && ! diff.added;\n\n\t\t\tconst nextDiff = diffs[ i + 1 ];\n\t\t\tconst isNextDiffAnInsert =\n\t\t\t\tnextDiff && nextDiff.added && ! nextDiff.removed;\n\n\t\t\t// Path 1: Look-ahead strategy\n\t\t\t// If the position of the cursor is in an \"unchanged\" segment, but there's an insertion\n\t\t\t// right after this section, then the insertion has likely been placed in\n\t\t\t// the incorrect spot, and we can move the insertion to the position of the cursor.\n\t\t\tif (\n\t\t\t\tisUnchangedSegment &&\n\t\t\t\tisCursorInSegment &&\n\t\t\t\tisNextDiffAnInsert\n\t\t\t) {\n\t\t\t\tconst movedSegments = this.tryMoveInsertionToCursor(\n\t\t\t\t\tdiff,\n\t\t\t\t\tnextDiff,\n\t\t\t\t\tcursorAfterChange,\n\t\t\t\t\tsegmentStart\n\t\t\t\t);\n\n\t\t\t\tif ( movedSegments ) {\n\t\t\t\t\tadjustedDiffs.push( ...movedSegments );\n\t\t\t\t\t// Skip the next diff since we've already consumed it\n\t\t\t\t\ti++;\n\t\t\t\t\tlastDiffPosition = segmentEnd;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Path 2: Look-back strategy\n\t\t\t// Handle removals by checking if cursor was in the previous unchanged segment\n\t\t\tif ( isRemovalSegment ) {\n\t\t\t\tconst movedSegments = this.tryMoveDeletionToCursor(\n\t\t\t\t\tdiff,\n\t\t\t\t\tadjustedDiffs,\n\t\t\t\t\tcursorAfterChange,\n\t\t\t\t\tlastDiffPosition\n\t\t\t\t);\n\n\t\t\t\tif ( movedSegments ) {\n\t\t\t\t\t// Remove the previous unchanged segment from adjustedDiffs\n\t\t\t\t\tadjustedDiffs.pop();\n\t\t\t\t\tadjustedDiffs.push( ...movedSegments );\n\t\t\t\t\tlastDiffPosition += diff.count ?? 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Path 3: Do nothing - add diff as-is\n\t\t\tadjustedDiffs.push( diff );\n\t\t\tif ( ! diff.added ) {\n\t\t\t\tlastDiffPosition += diff.count ?? 0;\n\t\t\t}\n\t\t}\n\n\t\tdiffs = adjustedDiffs;\n\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst retDelta = this.convertChangesToDelta(\n\t\t\tdiffs,\n\t\t\tthisIter,\n\t\t\totherIter\n\t\t);\n\n\t\treturn retDelta.chop();\n\t}\n\n\t/**\n\t * Try to move an insertion operation from after an unchanged segment to the cursor position within it.\n\t * This is a \"look-ahead\" strategy.\n\t *\n\t * @param diff - The current unchanged diff segment.\n\t * @param nextDiff - The next diff segment (expected to be an insertion).\n\t * @param cursorAfterChange - The cursor position after the change.\n\t * @param segmentStart - The start position of the current segment.\n\t * @return An array of adjusted diff segments if the insertion was successfully moved, null otherwise.\n\t */\n\tprivate tryMoveInsertionToCursor(\n\t\tdiff: Change,\n\t\tnextDiff: Change,\n\t\tcursorAfterChange: number,\n\t\tsegmentStart: number\n\t): Change[] | null {\n\t\tconst nextDiffInsert = nextDiff.value;\n\t\tconst insertLength = nextDiffInsert.length;\n\t\tconst insertOffset = cursorAfterChange - segmentStart - insertLength;\n\n\t\t// Verify that the inserted text matches the text at the cursor position\n\t\tconst textAtCursor = diff.value.substring(\n\t\t\tinsertOffset,\n\t\t\tinsertOffset + nextDiffInsert.length\n\t\t);\n\t\tconst isInsertMoveable = textAtCursor === nextDiffInsert;\n\n\t\t// The insert text matches what's at the cursor position,\n\t\t// so we can safely move the insertion to the cursor position.\n\t\tif ( ! isInsertMoveable ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Split the current segment at the cursor\n\t\tconst beforeCursor = diff.value.substring( 0, insertOffset );\n\t\tconst afterCursor = diff.value.substring( insertOffset );\n\n\t\tconst result: Change[] = [];\n\n\t\t// Add before cursor part (if not empty)\n\t\tif ( beforeCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: beforeCursor,\n\t\t\t\tcount: beforeCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\t// Add the insertion in the middle\n\t\tresult.push( nextDiff );\n\n\t\t// Add after cursor part (if not empty)\n\t\tif ( afterCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: afterCursor,\n\t\t\t\tcount: afterCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Try to move a deletion operation to the cursor position by looking back at the previous unchanged segment.\n\t * This is a \"look-back\" strategy.\n\t *\n\t * @param diff - The current deletion diff segment.\n\t * @param adjustedDiffs - The array of previously processed diff segments.\n\t * @param cursorAfterChange - The cursor position after the change.\n\t * @param lastDiffPosition - The position in the document up to (but not including) the current diff.\n\t * @return An array of adjusted diff segments if the deletion was successfully moved, null otherwise.\n\t */\n\tprivate tryMoveDeletionToCursor(\n\t\tdiff: Change,\n\t\tadjustedDiffs: Change[],\n\t\tcursorAfterChange: number,\n\t\tlastDiffPosition: number\n\t): Change[] | null {\n\t\t// Check if there's a preceding unchanged segment where cursor falls\n\t\t// and the deleted characters match characters in that segment\n\t\tconst prevDiff = adjustedDiffs[ adjustedDiffs.length - 1 ];\n\n\t\tif ( ! prevDiff || prevDiff.added || prevDiff.removed ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst prevSegmentStart = lastDiffPosition - ( prevDiff.count ?? 0 );\n\t\tconst prevSegmentEnd = lastDiffPosition;\n\n\t\t// Check if cursor is within or at the end of the previous unchanged segment\n\t\tif (\n\t\t\tcursorAfterChange < prevSegmentStart ||\n\t\t\tcursorAfterChange >= prevSegmentEnd\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Check if the deleted characters match the text at the cursor position\n\t\tconst deletedChars = diff.value;\n\t\tconst deleteOffset = cursorAfterChange - prevSegmentStart;\n\t\tconst textAtCursor = prevDiff.value.substring(\n\t\t\tdeleteOffset,\n\t\t\tdeleteOffset + deletedChars.length\n\t\t);\n\t\tconst canBePlacedHere = textAtCursor === deletedChars;\n\n\t\tif ( ! canBePlacedHere ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Split the unchanged segment at the cursor and place deletion there\n\t\tconst beforeCursor = prevDiff.value.substring( 0, deleteOffset );\n\t\tconst atAndAfterCursor = prevDiff.value.substring( deleteOffset );\n\n\t\t// The deletion should consume characters starting at cursor\n\t\tconst deletionLength = diff.count ?? 0;\n\t\tconst afterDeletion = atAndAfterCursor.substring( deletionLength );\n\n\t\tconst result: Change[] = [];\n\n\t\t// Add before cursor part (if not empty)\n\t\tif ( beforeCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: beforeCursor,\n\t\t\t\tcount: beforeCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\t// Add the deletion\n\t\tresult.push( diff );\n\n\t\t// Add after deletion part (if not empty)\n\t\tif ( afterDeletion.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: afterDeletion,\n\t\t\t\tcount: afterDeletion.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Convert two Deltas to string representations for diffing.\n\t *\n\t * @param other - The other Delta to convert.\n\t * @return A tuple of [thisString, otherString].\n\t */\n\tprivate deltasToStrings( other: Delta ): [ string, string ] {\n\t\treturn [ this, other ].map( ( delta ) => {\n\t\t\treturn delta\n\t\t\t\t.map( ( op ) => {\n\t\t\t\t\tif ( op.insert !== null || op.insert !== undefined ) {\n\t\t\t\t\t\treturn typeof op.insert === 'string'\n\t\t\t\t\t\t\t? op.insert\n\t\t\t\t\t\t\t: NULL_CHARACTER;\n\t\t\t\t\t}\n\t\t\t\t\tconst prep = delta === other ? 'on' : 'with';\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t'diff() called ' + prep + ' non-document'\n\t\t\t\t\t);\n\t\t\t\t} )\n\t\t\t\t.join( '' );\n\t\t} ) as [ string, string ];\n\t}\n\n\t/**\n\t * Process diff changes and convert them to Delta operations.\n\t *\n\t * @param changes - The array of changes from the diff algorithm.\n\t * @param thisIter - Iterator for this Delta's operations.\n\t * @param otherIter - Iterator for the other Delta's operations.\n\t * @return A Delta containing the processed diff operations.\n\t */\n\tprivate convertChangesToDelta(\n\t\tchanges: Change[],\n\t\tthisIter: OpIterator,\n\t\totherIter: OpIterator\n\t): Delta {\n\t\tconst retDelta = new Delta();\n\t\tchanges.forEach( ( component: Change ) => {\n\t\t\tlet length = component.count ?? 0;\n\t\t\twhile ( length > 0 ) {\n\t\t\t\tlet opLength = 0;\n\t\t\t\tif ( component.added ) {\n\t\t\t\t\topLength = Math.min( otherIter.peekLength(), length );\n\t\t\t\t\tretDelta.push( otherIter.next( opLength ) );\n\t\t\t\t} else if ( component.removed ) {\n\t\t\t\t\topLength = Math.min( length, thisIter.peekLength() );\n\t\t\t\t\tthisIter.next( opLength );\n\t\t\t\t\tretDelta.delete( opLength );\n\t\t\t\t} else {\n\t\t\t\t\topLength = Math.min(\n\t\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\t\totherIter.peekLength(),\n\t\t\t\t\t\tlength\n\t\t\t\t\t);\n\t\t\t\t\tconst thisOp = thisIter.next( opLength );\n\t\t\t\t\tconst otherOp = otherIter.next( opLength );\n\t\t\t\t\tif ( isEqual( thisOp.insert, otherOp.insert ) ) {\n\t\t\t\t\t\tretDelta.retain(\n\t\t\t\t\t\t\topLength,\n\t\t\t\t\t\t\tAttributeMap.diff(\n\t\t\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\t\t\totherOp.attributes\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tretDelta.push( otherOp ).delete( opLength );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlength -= opLength;\n\t\t\t}\n\t\t} );\n\t\treturn retDelta;\n\t}\n}\n\nexport default Delta;\nexport { Op, OpIterator, AttributeMap };\n"], | ||
| "mappings": "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", | ||
| "names": [] | ||
| } |
@@ -16,6 +16,7 @@ // packages/sync/src/utils.ts | ||
| ); | ||
| const ydoc = new Y.Doc({ meta: metaMap }); | ||
| return new Y.Doc({ meta: metaMap }); | ||
| } | ||
| function initializeYjsDoc(ydoc) { | ||
| const stateMap = ydoc.getMap(CRDT_STATE_MAP_KEY); | ||
| stateMap.set(VERSION_KEY, CRDT_DOC_VERSION); | ||
| return ydoc; | ||
| } | ||
@@ -55,2 +56,3 @@ function markEntityAsSaved(ydoc) { | ||
| deserializeCrdtDoc, | ||
| initializeYjsDoc, | ||
| markEntityAsSaved, | ||
@@ -57,0 +59,0 @@ serializeCrdtDoc |
| { | ||
| "version": 3, | ||
| "sources": ["../src/utils.ts"], | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport * as buffer from 'lib0/buffer';\n\n/**\n * Internal dependencies\n */\nimport {\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_DOC_VERSION,\n\tCRDT_STATE_MAP_KEY,\n\tCRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,\n\tCRDT_STATE_MAP_SAVED_BY_KEY as SAVED_BY_KEY,\n\tCRDT_STATE_MAP_VERSION_KEY as VERSION_KEY,\n} from './config';\nimport type { CRDTDoc } from './types';\n\n// An object representation of CRDT document metadata.\ntype DocumentMeta = Record< string, DocumentMetaValue >;\ntype DocumentMetaValue = boolean | number | string;\n\nexport function createYjsDoc( documentMeta: DocumentMeta = {} ): Y.Doc {\n\t// Convert the object representation of CRDT document metadata to a map.\n\t// Document metadata is passed to the Y.Doc constructor and stored in its\n\t// `meta` property. It is not synced to peers or persisted with the document.\n\t// It is just a place to store transient information about this doc instance.\n\tconst metaMap = new Map< string, DocumentMetaValue >(\n\t\tObject.entries( documentMeta )\n\t);\n\n\tconst ydoc = new Y.Doc( { meta: metaMap } );\n\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\n\tstateMap.set( VERSION_KEY, CRDT_DOC_VERSION );\n\n\treturn ydoc;\n}\n\n/**\n * Record that the entity was saved (persisted to the database) in the CRDT\n * document record metadata.\n *\n * @param {CRDTDoc} ydoc CRDT document.\n */\nexport function markEntityAsSaved( ydoc: CRDTDoc ): void {\n\tconst recordMeta = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\trecordMeta.set( SAVED_AT_KEY, Date.now() );\n\trecordMeta.set( SAVED_BY_KEY, ydoc.clientID );\n}\n\nfunction pseudoRandomID(): number {\n\treturn Math.floor( Math.random() * 1000000000 );\n}\n\nexport function serializeCrdtDoc( crdtDoc: CRDTDoc ): string {\n\treturn JSON.stringify( {\n\t\tdocument: buffer.toBase64( Y.encodeStateAsUpdateV2( crdtDoc ) ),\n\t\tupdateId: pseudoRandomID(), // helps with debugging\n\t} );\n}\n\nexport function deserializeCrdtDoc(\n\tserializedCrdtDoc: string\n): CRDTDoc | null {\n\ttry {\n\t\tconst { document } = JSON.parse( serializedCrdtDoc );\n\n\t\t// Mark this document as from persistence.\n\t\tconst docMeta: DocumentMeta = {\n\t\t\t[ CRDT_DOC_META_PERSISTENCE_KEY ]: true,\n\t\t};\n\n\t\t// Apply the document as an update against a new (temporary) Y.Doc.\n\t\tconst ydoc = createYjsDoc( docMeta );\n\t\tconst yupdate = buffer.fromBase64( document );\n\t\tY.applyUpdateV2( ydoc, yupdate );\n\n\t\t// Overwrite the client ID (which is from a previous session) with a random\n\t\t// client ID. Deserialized documents should not be used directly. Instead,\n\t\t// their state should be applied to another in-use document.\n\t\tydoc.clientID = pseudoRandomID();\n\n\t\treturn ydoc;\n\t} catch ( e ) {\n\t\treturn null;\n\t}\n}\n"], | ||
| "mappings": ";AAGA,YAAY,OAAO;AACnB,YAAY,YAAY;AAKxB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA,+BAA+B;AAAA,EAC/B,+BAA+B;AAAA,EAC/B,8BAA8B;AAAA,OACxB;AAOA,SAAS,aAAc,eAA6B,CAAC,GAAW;AAKtE,QAAM,UAAU,IAAI;AAAA,IACnB,OAAO,QAAS,YAAa;AAAA,EAC9B;AAEA,QAAM,OAAO,IAAM,MAAK,EAAE,MAAM,QAAQ,CAAE;AAC1C,QAAM,WAAW,KAAK,OAAQ,kBAAmB;AAEjD,WAAS,IAAK,aAAa,gBAAiB;AAE5C,SAAO;AACR;AAQO,SAAS,kBAAmB,MAAsB;AACxD,QAAM,aAAa,KAAK,OAAQ,kBAAmB;AACnD,aAAW,IAAK,cAAc,KAAK,IAAI,CAAE;AACzC,aAAW,IAAK,cAAc,KAAK,QAAS;AAC7C;AAEA,SAAS,iBAAyB;AACjC,SAAO,KAAK,MAAO,KAAK,OAAO,IAAI,GAAW;AAC/C;AAEO,SAAS,iBAAkB,SAA2B;AAC5D,SAAO,KAAK,UAAW;AAAA,IACtB,UAAiB,gBAAY,wBAAuB,OAAQ,CAAE;AAAA,IAC9D,UAAU,eAAe;AAAA;AAAA,EAC1B,CAAE;AACH;AAEO,SAAS,mBACf,mBACiB;AACjB,MAAI;AACH,UAAM,EAAE,SAAS,IAAI,KAAK,MAAO,iBAAkB;AAGnD,UAAM,UAAwB;AAAA,MAC7B,CAAE,6BAA8B,GAAG;AAAA,IACpC;AAGA,UAAM,OAAO,aAAc,OAAQ;AACnC,UAAM,UAAiB,kBAAY,QAAS;AAC5C,IAAE,gBAAe,MAAM,OAAQ;AAK/B,SAAK,WAAW,eAAe;AAE/B,WAAO;AAAA,EACR,SAAU,GAAI;AACb,WAAO;AAAA,EACR;AACD;", | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport * as buffer from 'lib0/buffer';\n\n/**\n * Internal dependencies\n */\nimport {\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_DOC_VERSION,\n\tCRDT_STATE_MAP_KEY,\n\tCRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,\n\tCRDT_STATE_MAP_SAVED_BY_KEY as SAVED_BY_KEY,\n\tCRDT_STATE_MAP_VERSION_KEY as VERSION_KEY,\n} from './config';\nimport type { CRDTDoc } from './types';\n\n// An object representation of CRDT document metadata.\ntype DocumentMeta = Record< string, DocumentMetaValue >;\ntype DocumentMetaValue = boolean | number | string;\n\n/**\n * Creates a new Y.Doc instance with the given document metadata.\n *\n * @param {DocumentMeta} documentMeta Optional metadata to associate with the\n * document. Metadata is not persisted.\n */\nexport function createYjsDoc( documentMeta: DocumentMeta = {} ): CRDTDoc {\n\t// Convert the object representation of CRDT document metadata to a map.\n\t// Document metadata is passed to the Y.Doc constructor and stored in its\n\t// `meta` property. It is not synced to peers or persisted with the document.\n\t// It is just a place to store transient information about this doc instance.\n\tconst metaMap = new Map< string, DocumentMetaValue >(\n\t\tObject.entries( documentMeta )\n\t);\n\n\t// IMPORTANT: Do not add update the document itself to avoid generating updates\n\t// before observers are attached. Add initial updates in `initializeYjsDoc`.\n\treturn new Y.Doc( { meta: metaMap } );\n}\n\n/**\n * Initializes a Y.Doc instance with the necessary CRDT state for our use case.\n *\n * @param {Y.Doc} ydoc Y.Doc instance to initialize.\n */\nexport function initializeYjsDoc( ydoc: CRDTDoc ): void {\n\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\tstateMap.set( VERSION_KEY, CRDT_DOC_VERSION );\n}\n\n/**\n * Record that the entity was saved (persisted to the database) in the CRDT\n * document record metadata.\n *\n * @param {CRDTDoc} ydoc CRDT document.\n */\nexport function markEntityAsSaved( ydoc: CRDTDoc ): void {\n\tconst recordMeta = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\trecordMeta.set( SAVED_AT_KEY, Date.now() );\n\trecordMeta.set( SAVED_BY_KEY, ydoc.clientID );\n}\n\nfunction pseudoRandomID(): number {\n\treturn Math.floor( Math.random() * 1000000000 );\n}\n\nexport function serializeCrdtDoc( crdtDoc: CRDTDoc ): string {\n\treturn JSON.stringify( {\n\t\tdocument: buffer.toBase64( Y.encodeStateAsUpdateV2( crdtDoc ) ),\n\t\tupdateId: pseudoRandomID(), // helps with debugging\n\t} );\n}\n\nexport function deserializeCrdtDoc(\n\tserializedCrdtDoc: string\n): CRDTDoc | null {\n\ttry {\n\t\tconst { document } = JSON.parse( serializedCrdtDoc );\n\n\t\t// Mark this document as from persistence.\n\t\tconst docMeta: DocumentMeta = {\n\t\t\t[ CRDT_DOC_META_PERSISTENCE_KEY ]: true,\n\t\t};\n\n\t\t// Apply the document as an update against a new (temporary) Y.Doc.\n\t\tconst ydoc = createYjsDoc( docMeta );\n\t\tconst yupdate = buffer.fromBase64( document );\n\t\tY.applyUpdateV2( ydoc, yupdate );\n\n\t\t// Overwrite the client ID (which is from a previous session) with a random\n\t\t// client ID. Deserialized documents should not be used directly. Instead,\n\t\t// their state should be applied to another in-use document.\n\t\tydoc.clientID = pseudoRandomID();\n\n\t\treturn ydoc;\n\t} catch ( e ) {\n\t\treturn null;\n\t}\n}\n"], | ||
| "mappings": ";AAGA,YAAY,OAAO;AACnB,YAAY,YAAY;AAKxB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA,+BAA+B;AAAA,EAC/B,+BAA+B;AAAA,EAC/B,8BAA8B;AAAA,OACxB;AAaA,SAAS,aAAc,eAA6B,CAAC,GAAa;AAKxE,QAAM,UAAU,IAAI;AAAA,IACnB,OAAO,QAAS,YAAa;AAAA,EAC9B;AAIA,SAAO,IAAM,MAAK,EAAE,MAAM,QAAQ,CAAE;AACrC;AAOO,SAAS,iBAAkB,MAAsB;AACvD,QAAM,WAAW,KAAK,OAAQ,kBAAmB;AACjD,WAAS,IAAK,aAAa,gBAAiB;AAC7C;AAQO,SAAS,kBAAmB,MAAsB;AACxD,QAAM,aAAa,KAAK,OAAQ,kBAAmB;AACnD,aAAW,IAAK,cAAc,KAAK,IAAI,CAAE;AACzC,aAAW,IAAK,cAAc,KAAK,QAAS;AAC7C;AAEA,SAAS,iBAAyB;AACjC,SAAO,KAAK,MAAO,KAAK,OAAO,IAAI,GAAW;AAC/C;AAEO,SAAS,iBAAkB,SAA2B;AAC5D,SAAO,KAAK,UAAW;AAAA,IACtB,UAAiB,gBAAY,wBAAuB,OAAQ,CAAE;AAAA,IAC9D,UAAU,eAAe;AAAA;AAAA,EAC1B,CAAE;AACH;AAEO,SAAS,mBACf,mBACiB;AACjB,MAAI;AACH,UAAM,EAAE,SAAS,IAAI,KAAK,MAAO,iBAAkB;AAGnD,UAAM,UAAwB;AAAA,MAC7B,CAAE,6BAA8B,GAAG;AAAA,IACpC;AAGA,UAAM,OAAO,aAAc,OAAQ;AACnC,UAAM,UAAiB,kBAAY,QAAS;AAC5C,IAAE,gBAAe,MAAM,OAAQ;AAK/B,SAAK,WAAW,eAAe;AAE/B,WAAO;AAAA,EACR,SAAU,GAAI;AACb,WAAO;AAAA,EACR;AACD;", | ||
| "names": [] | ||
| } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAUX,WAAW,EAGX,MAAM,SAAS,CAAC;AAwCjB;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAE,KAAK,UAAQ,GAAI,WAAW,CAokB9D"} | ||
| {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAUX,WAAW,EAGX,MAAM,SAAS,CAAC;AAyCjB;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAE,KAAK,UAAQ,GAAI,WAAW,CA+kB9D"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"private-apis.d.ts","sourceRoot":"","sources":["../src/private-apis.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,WAAW,IAAK,CAAC"} | ||
| {"version":3,"file":"private-apis.d.ts","sourceRoot":"","sources":["../src/private-apis.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,WAAW,IAAK,CAAC"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"http-polling-provider.d.ts","sourceRoot":"","sources":["../../../src/providers/http-polling/http-polling-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,KAAK,CAAC,MAAM,KAAK,CAAC;AAE9B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;GAEG;AACH,OAAO,KAAK,EAEX,eAAe,EAEf,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,eAAe;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC;CACZ;AAgHD;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,CAyB3D"} | ||
| {"version":3,"file":"http-polling-provider.d.ts","sourceRoot":"","sources":["../../../src/providers/http-polling/http-polling-provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,KAAK,CAAC,MAAM,KAAK,CAAC;AAE9B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;GAEG;AACH,OAAO,KAAK,EAEX,eAAe,EAEf,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,eAAe;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC;CACZ;AAmHD;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,CAyB3D"} |
@@ -13,2 +13,3 @@ /** | ||
| registerRoom: (options: RegisterRoomOptions) => void; | ||
| retryNow: () => void; | ||
| unregisterRoom: (room: string) => void; | ||
@@ -15,0 +16,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"polling-manager.d.ts","sourceRoot":"","sources":["../../../src/providers/http-polling/polling-manager.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAGzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD;;GAEG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAuBpD,KAAK,WAAW,GAAG,CAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAM,IAAI,CAAC;AAE/D,UAAU,cAAc;IACvB,YAAY,EAAE,CAAE,OAAO,EAAE,mBAAmB,KAAM,IAAI,CAAC;IACvD,cAAc,EAAE,CAAE,IAAI,EAAE,MAAM,KAAM,IAAI,CAAC;CACzC;AAED,UAAU,mBAAmB;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC;IACX,SAAS,EAAE,SAAS,CAAC;IACrB,GAAG,EAAE,WAAW,CAAC;IACjB,cAAc,EAAE,CAAE,MAAM,EAAE,gBAAgB,KAAM,IAAI,CAAC;IACrD,MAAM,EAAE,MAAM,IAAI,CAAC;CACnB;AAqfD,eAAO,MAAM,cAAc,EAAE,cAG5B,CAAC"} | ||
| {"version":3,"file":"polling-manager.d.ts","sourceRoot":"","sources":["../../../src/providers/http-polling/polling-manager.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAGzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD;;GAEG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAyBpD,KAAK,WAAW,GAAG,CAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAM,IAAI,CAAC;AAE/D,UAAU,cAAc;IACvB,YAAY,EAAE,CAAE,OAAO,EAAE,mBAAmB,KAAM,IAAI,CAAC;IACvD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,cAAc,EAAE,CAAE,IAAI,EAAE,MAAM,KAAM,IAAI,CAAC;CACzC;AAED,UAAU,mBAAmB;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC;IACX,SAAS,EAAE,SAAS,CAAC;IACrB,GAAG,EAAE,WAAW,CAAC;IACjB,cAAc,EAAE,CAAE,MAAM,EAAE,gBAAgB,KAAM,IAAI,CAAC;IACrD,MAAM,EAAE,MAAM,IAAI,CAAC;CACnB;AA2gBD,eAAO,MAAM,cAAc,EAAE,cAI5B,CAAC"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"Delta.d.ts","sourceRoot":"","sources":["../../src/quill-delta/Delta.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"Delta.d.ts","sourceRoot":"","sources":["../../src/quill-delta/Delta.ts"],"names":[],"mappings":"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"} |
@@ -53,11 +53,18 @@ /** | ||
| /** | ||
| * Current connection status of a sync provider, including status and optional error information. | ||
| * Current connection status of a sync provider. | ||
| */ | ||
| export interface ConnectionStatus { | ||
| status: 'connected' | 'connecting' | 'disconnected'; | ||
| /** | ||
| * Optional error information when status is 'disconnected'. | ||
| */ | ||
| export interface ConnectionStatusConnected { | ||
| status: 'connected'; | ||
| } | ||
| export interface ConnectionStatusConnecting { | ||
| status: 'connecting'; | ||
| } | ||
| export interface ConnectionStatusDisconnected { | ||
| status: 'disconnected'; | ||
| /** Optional error information. */ | ||
| error?: ConnectionError; | ||
| /** Milliseconds until the next automatic retry attempt. */ | ||
| retryInMs?: number; | ||
| } | ||
| export type ConnectionStatus = ConnectionStatusConnected | ConnectionStatusConnecting | ConnectionStatusDisconnected; | ||
| export type OnStatusChangeCallback = (status: ConnectionStatus | null) => void; | ||
@@ -100,3 +107,3 @@ /** | ||
| export interface SyncManager { | ||
| createPersistedCRDTDoc: (objectType: ObjectType, objectId: ObjectID) => string | null; | ||
| createPersistedCRDTDoc: (objectType: ObjectType, objectId: ObjectID) => Promise<string | null>; | ||
| getAwareness: <State extends Awareness>(objectType: ObjectType, objectId: ObjectID) => State | undefined; | ||
@@ -103,0 +110,0 @@ load: (syncConfig: SyncConfig, objectType: ObjectType, objectId: ObjectID, record: ObjectData, handlers: RecordHandlers) => Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} |
@@ -1,10 +0,18 @@ | ||
| /** | ||
| * External dependencies | ||
| */ | ||
| import * as Y from 'yjs'; | ||
| import type { CRDTDoc } from './types'; | ||
| type DocumentMeta = Record<string, DocumentMetaValue>; | ||
| type DocumentMetaValue = boolean | number | string; | ||
| export declare function createYjsDoc(documentMeta?: DocumentMeta): Y.Doc; | ||
| /** | ||
| * Creates a new Y.Doc instance with the given document metadata. | ||
| * | ||
| * @param {DocumentMeta} documentMeta Optional metadata to associate with the | ||
| * document. Metadata is not persisted. | ||
| */ | ||
| export declare function createYjsDoc(documentMeta?: DocumentMeta): CRDTDoc; | ||
| /** | ||
| * Initializes a Y.Doc instance with the necessary CRDT state for our use case. | ||
| * | ||
| * @param {Y.Doc} ydoc Y.Doc instance to initialize. | ||
| */ | ||
| export declare function initializeYjsDoc(ydoc: CRDTDoc): void; | ||
| /** | ||
| * Record that the entity was saved (persisted to the database) in the CRDT | ||
@@ -11,0 +19,0 @@ * document record metadata. |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAczB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC,KAAK,YAAY,GAAG,MAAM,CAAE,MAAM,EAAE,iBAAiB,CAAE,CAAC;AACxD,KAAK,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,wBAAgB,YAAY,CAAE,YAAY,GAAE,YAAiB,GAAI,CAAC,CAAC,GAAG,CAerE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,OAAO,GAAI,IAAI,CAIvD;AAMD,wBAAgB,gBAAgB,CAAE,OAAO,EAAE,OAAO,GAAI,MAAM,CAK3D;AAED,wBAAgB,kBAAkB,CACjC,iBAAiB,EAAE,MAAM,GACvB,OAAO,GAAG,IAAI,CAuBhB"} | ||
| {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC,KAAK,YAAY,GAAG,MAAM,CAAE,MAAM,EAAE,iBAAiB,CAAE,CAAC;AACxD,KAAK,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAE,YAAY,GAAE,YAAiB,GAAI,OAAO,CAYvE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAE,IAAI,EAAE,OAAO,GAAI,IAAI,CAGtD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAE,IAAI,EAAE,OAAO,GAAI,IAAI,CAIvD;AAMD,wBAAgB,gBAAgB,CAAE,OAAO,EAAE,OAAO,GAAI,MAAM,CAK3D;AAED,wBAAgB,kBAAkB,CACjC,iBAAiB,EAAE,MAAM,GACvB,OAAO,GAAG,IAAI,CAuBhB"} |
@@ -150,2 +150,3 @@ "use strict"; | ||
| stateMap.observe(onStateMapUpdate); | ||
| (0, import_utils.initializeYjsDoc)(ydoc); | ||
| internal.applyPersistedCrdtDoc(objectType, objectId, record); | ||
@@ -215,2 +216,3 @@ } | ||
| stateMap.observe(onStateMapUpdate); | ||
| (0, import_utils.initializeYjsDoc)(ydoc); | ||
| } | ||
@@ -327,3 +329,3 @@ function unloadEntity(objectType, objectId) { | ||
| } | ||
| function createPersistedCRDTDoc(objectType, objectId) { | ||
| async function createPersistedCRDTDoc(objectType, objectId) { | ||
| const entityId = getEntityId(objectType, objectId); | ||
@@ -334,2 +336,3 @@ const entityState = entityStates.get(entityId); | ||
| } | ||
| await new Promise((resolve) => setTimeout(resolve, 0)); | ||
| return (0, import_utils.serializeCrdtDoc)(entityState.ydoc); | ||
@@ -336,0 +339,0 @@ } |
| { | ||
| "version": 3, | ||
| "sources": ["../src/manager.ts"], | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\n/**\n * Internal dependencies\n */\nimport {\n\tCRDT_RECORD_MAP_KEY,\n\tCRDT_STATE_MAP_KEY,\n\tCRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,\n\tLOCAL_SYNC_MANAGER_ORIGIN,\n} from './config';\nimport {\n\tlogPerformanceTiming,\n\tpassThru,\n\tyieldToEventLoop,\n} from './performance';\nimport { getProviderCreators } from './providers';\nimport type {\n\tCollectionHandlers,\n\tCRDTDoc,\n\tEntityID,\n\tObjectID,\n\tObjectData,\n\tObjectType,\n\tProviderCreator,\n\tRecordHandlers,\n\tSyncConfig,\n\tSyncManager,\n\tSyncManagerUpdateOptions,\n\tSyncUndoManager,\n} from './types';\nimport { createUndoManager } from './undo-manager';\nimport {\n\tcreateYjsDoc,\n\tdeserializeCrdtDoc,\n\tmarkEntityAsSaved,\n\tserializeCrdtDoc,\n} from './utils';\n\ninterface CollectionState {\n\tawareness?: Awareness;\n\thandlers: CollectionHandlers;\n\tsyncConfig: SyncConfig;\n\tunload: () => void;\n\tydoc: CRDTDoc;\n}\n\ninterface EntityState {\n\tawareness?: Awareness;\n\thandlers: RecordHandlers;\n\tobjectId: ObjectID;\n\tobjectType: ObjectType;\n\tsyncConfig: SyncConfig;\n\tunload: () => void;\n\tydoc: CRDTDoc;\n}\n\n/**\n * Get the entity ID for the given object type and object ID.\n *\n * @param {ObjectType} objectType Object type.\n * @param {ObjectID|null} objectId Object ID.\n */\nfunction getEntityId(\n\tobjectType: ObjectType,\n\tobjectId: ObjectID | null\n): EntityID {\n\treturn `${ objectType }_${ objectId }`;\n}\n\n/**\n * The sync manager orchestrates the lifecycle of syncing entity records. It\n * creates Yjs documents, connects to providers, creates awareness instances,\n * and coordinates with the `core-data` store.\n *\n * @param debug Whether to enable performance and debug logging.\n */\nexport function createSyncManager( debug = false ): SyncManager {\n\tconst debugWrap = debug ? logPerformanceTiming : passThru;\n\tconst collectionStates: Map< ObjectType, CollectionState > = new Map();\n\tconst entityStates: Map< EntityID, EntityState > = new Map();\n\n\t/**\n\t * A \"sync-aware\" undo manager for all synced entities. It is lazily created\n\t * when the first entity is loaded.\n\t *\n\t * IMPORTANT: In Gutenberg, the undo manager is effectively global and manages\n\t * undo/redo state for all entities. If the default WPUndoManager is used,\n\t * changes to entities are recorded in the `editEntityRecord` action:\n\t *\n\t * https://github.com/WordPress/gutenberg/blob/b63451e26e3c91b6bb291a2f9994722e3850417e/packages/core-data/src/actions.js#L428-L442\n\t *\n\t * In contrast, the `SyncUndoManager` only manages undo/redo for entities that\n\t * **are being synced by this sync manager**. The `addRecord` method is still\n\t * called in the code linked above, but it is a no-op. Yjs automatically tracks\n\t * changes to entities via the associated CRDT doc:\n\t *\n\t * https://github.com/WordPress/gutenberg/blob/b63451e26e3c91b6bb291a2f9994722e3850417e/packages/sync/src/undo-manager.ts#L42-L48\n\t *\n\t * This means that if at least one entity is being synced, then undo/redo\n\t * operations will be **restricted to synced entities only.**\n\t *\n\t * We could improve the `SyncUndoManager` to also track non-synced entities by\n\t * delegating to a secondary `WPUndoManager`, but this would add complexity\n\t * since we would need to maintain two separate undo/redo stacks and ensure\n\t * that they retain ordering and integrity.\n\t *\n\t * However, we also anticipate that most entities being edited in Gutenberg\n\t * will be synced entities (e.g. posts, pages, templates, template parts,\n\t * etc.), so this limitation may be temporary.\n\t */\n\tlet undoManager: SyncUndoManager | undefined;\n\n\t/**\n\t * Log debug messages if debugging is enabled.\n\t *\n\t * @param component The component or context related to the log message\n\t * @param message The debug message\n\t * @param entityId The entity ID related to the log message\n\t * @param context Additional debug context\n\t */\n\tfunction log(\n\t\tcomponent: string,\n\t\tmessage: string,\n\t\tentityId: string,\n\t\tcontext: object = {}\n\t): void {\n\t\tif ( ! debug ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log( `[SyncManager][${ component }]: ${ message }`, {\n\t\t\t...context,\n\t\t\tentityId,\n\t\t} );\n\t}\n\n\t/**\n\t * Load an entity for syncing and manage its lifecycle.\n\t *\n\t * @param {SyncConfig} syncConfig Sync configuration for the object type.\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {ObjectData} record Entity record representing this object type.\n\t * @param {RecordHandlers} handlers Handlers for updating and fetching the record.\n\t */\n\tasync function loadEntity(\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData,\n\t\thandlers: RecordHandlers\n\t): Promise< void > {\n\t\tconst providerCreators = getProviderCreators();\n\t\tconst entityId = getEntityId( objectType, objectId );\n\n\t\tif ( 0 === providerCreators.length ) {\n\t\t\tlog( 'loadEntity', 'no providers, skipping', entityId );\n\t\t\treturn; // No provider creators, so syncing is effectively disabled.\n\t\t}\n\n\t\tif ( entityStates.has( entityId ) ) {\n\t\t\tlog( 'loadEntity', 'already loaded', entityId );\n\t\t\treturn; // Already bootstrapped.\n\t\t}\n\n\t\tlog( 'loadEntity', 'loading', entityId );\n\n\t\thandlers = {\n\t\t\taddUndoMeta: debugWrap( handlers.addUndoMeta ),\n\t\t\teditRecord: debugWrap( handlers.editRecord ),\n\t\t\tgetEditedRecord: debugWrap( handlers.getEditedRecord ),\n\t\t\tonStatusChange: debugWrap( handlers.onStatusChange ),\n\t\t\trefetchRecord: debugWrap( handlers.refetchRecord ),\n\t\t\trestoreUndoMeta: debugWrap( handlers.restoreUndoMeta ),\n\t\t\tsaveRecord: debugWrap( handlers.saveRecord ),\n\t\t};\n\n\t\tconst ydoc = createYjsDoc( { objectType } );\n\t\tconst recordMap = ydoc.getMap( CRDT_RECORD_MAP_KEY );\n\t\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\t\tconst now = Date.now();\n\n\t\t// Clean up providers and in-memory state when the entity is unloaded.\n\t\tconst unload = (): void => {\n\t\t\tlog( 'loadEntity', 'unloading', entityId );\n\t\t\tproviderResults.forEach( ( result ) => result.destroy() );\n\t\t\thandlers.onStatusChange( null );\n\t\t\trecordMap.unobserveDeep( onRecordUpdate );\n\t\t\tstateMap.unobserve( onStateMapUpdate );\n\t\t\tydoc.destroy();\n\t\t\tentityStates.delete( entityId );\n\t\t};\n\n\t\t// If the sync config supports awareness, create it.\n\t\tconst awareness = syncConfig.createAwareness?.( ydoc, objectId );\n\n\t\t// When the CRDT document is updated by an UndoManager or a connection (not\n\t\t// a local origin), update the local store.\n\t\tconst onRecordUpdate = (\n\t\t\t_events: Y.YEvent< any >[],\n\t\t\ttransaction: Y.Transaction\n\t\t): void => {\n\t\t\tif (\n\t\t\t\ttransaction.local &&\n\t\t\t\t! ( transaction.origin instanceof Y.UndoManager )\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid internal.updateEntityRecord( objectType, objectId );\n\t\t};\n\n\t\tconst onStateMapUpdate = (\n\t\t\tevent: Y.YMapEvent< unknown >,\n\t\t\ttransaction: Y.Transaction\n\t\t) => {\n\t\t\tif ( transaction.local ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.keysChanged.forEach( ( key ) => {\n\t\t\t\tswitch ( key ) {\n\t\t\t\t\tcase SAVED_AT_KEY:\n\t\t\t\t\t\tconst newValue = stateMap.get( SAVED_AT_KEY );\n\t\t\t\t\t\tif ( 'number' === typeof newValue && newValue > now ) {\n\t\t\t\t\t\t\t// Another peer has saved the record. Refetch it so that we have\n\t\t\t\t\t\t\t// a correct understanding of our own unsaved edits.\n\t\t\t\t\t\t\tlog( 'loadEntity', 'refetching record', entityId );\n\t\t\t\t\t\t\tvoid handlers.refetchRecord().catch( () => {} );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\n\t\t// Lazily create the undo manager when the first entity is loaded.\n\t\tif ( ! undoManager ) {\n\t\t\tundoManager = createUndoManager();\n\t\t}\n\n\t\tconst { addUndoMeta, restoreUndoMeta } = handlers;\n\t\tundoManager.addToScope( recordMap, {\n\t\t\taddUndoMeta,\n\t\t\trestoreUndoMeta,\n\t\t} );\n\n\t\tconst entityState: EntityState = {\n\t\t\tawareness,\n\t\t\thandlers,\n\t\t\tobjectId,\n\t\t\tobjectType,\n\t\t\tsyncConfig,\n\t\t\tunload,\n\t\t\tydoc,\n\t\t};\n\n\t\tentityStates.set( entityId, entityState );\n\n\t\t// Create providers for the given entity and its Yjs document.\n\t\tlog( 'loadEntity', 'connecting', entityId );\n\t\tconst providerResults = await Promise.all(\n\t\t\tproviderCreators.map( async ( create ) => {\n\t\t\t\tconst provider = await create( {\n\t\t\t\t\tobjectType,\n\t\t\t\t\tobjectId,\n\t\t\t\t\tydoc,\n\t\t\t\t\tawareness,\n\t\t\t\t} );\n\n\t\t\t\t// Attach status listener after provider creation.\n\t\t\t\tprovider.on( 'status', handlers.onStatusChange );\n\n\t\t\t\treturn provider;\n\t\t\t} )\n\t\t);\n\n\t\t// Attach observers.\n\t\trecordMap.observeDeep( onRecordUpdate );\n\t\tstateMap.observe( onStateMapUpdate );\n\n\t\t// Get and apply the persisted CRDT document, if it exists.\n\t\tinternal.applyPersistedCrdtDoc( objectType, objectId, record );\n\t}\n\n\t/**\n\t * Load a collection for syncing and manage its lifecycle.\n\t *\n\t * @param {SyncConfig} syncConfig Sync configuration for the object type.\n\t * @param {ObjectType} objectType Object type.\n\t * @param {CollectionHandlers} handlers Handlers for updating the collection.\n\t */\n\tasync function loadCollection(\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\thandlers: CollectionHandlers\n\t): Promise< void > {\n\t\tconst providerCreators: ProviderCreator[] = getProviderCreators();\n\t\tconst entityId = getEntityId( objectType, null );\n\n\t\tif ( 0 === providerCreators.length ) {\n\t\t\tlog( 'loadCollection', 'no providers, skipping', entityId );\n\t\t\treturn; // No provider creators, so syncing is effectively disabled.\n\t\t}\n\n\t\tif ( collectionStates.has( objectType ) ) {\n\t\t\tlog( 'loadCollection', 'already loaded', entityId );\n\t\t\treturn; // Already loaded.\n\t\t}\n\n\t\tlog( 'loadCollection', 'loading', entityId );\n\n\t\tconst ydoc = createYjsDoc( { collection: true, objectType } );\n\t\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\t\tconst now = Date.now();\n\n\t\t// Clean up providers and in-memory state when the entity is unloaded.\n\t\tconst unload = (): void => {\n\t\t\tlog( 'loadCollection', 'unloading', entityId );\n\t\t\tproviderResults.forEach( ( result ) => result.destroy() );\n\t\t\thandlers.onStatusChange( null );\n\t\t\tstateMap.unobserve( onStateMapUpdate );\n\t\t\tydoc.destroy();\n\t\t\tcollectionStates.delete( objectType );\n\t\t};\n\n\t\tconst onStateMapUpdate = (\n\t\t\tevent: Y.YMapEvent< unknown >,\n\t\t\ttransaction: Y.Transaction\n\t\t) => {\n\t\t\tif ( transaction.local ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.keysChanged.forEach( ( key ) => {\n\t\t\t\tswitch ( key ) {\n\t\t\t\t\tcase SAVED_AT_KEY:\n\t\t\t\t\t\tconst newValue = stateMap.get( SAVED_AT_KEY );\n\t\t\t\t\t\tif ( 'number' === typeof newValue && newValue > now ) {\n\t\t\t\t\t\t\t// Another peer has mutated the collection. Refetch it so that we\n\t\t\t\t\t\t\t// obtain the updated records.\n\t\t\t\t\t\t\tvoid handlers.refetchRecords().catch( () => {} );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\n\t\t// If the sync config supports awareness, create it.\n\t\tconst awareness = syncConfig.createAwareness?.( ydoc );\n\n\t\tconst collectionState: CollectionState = {\n\t\t\tawareness,\n\t\t\thandlers,\n\t\t\tsyncConfig,\n\t\t\tunload,\n\t\t\tydoc,\n\t\t};\n\n\t\tcollectionStates.set( objectType, collectionState );\n\n\t\t// Create providers for the given entity and its Yjs document.\n\t\tlog( 'loadCollection', 'connecting', entityId );\n\t\tconst providerResults = await Promise.all(\n\t\t\tproviderCreators.map( async ( create ) => {\n\t\t\t\tconst provider = await create( {\n\t\t\t\t\tawareness,\n\t\t\t\t\tobjectType,\n\t\t\t\t\tobjectId: null,\n\t\t\t\t\tydoc,\n\t\t\t\t} );\n\n\t\t\t\t// Attach status listener after provider creation.\n\t\t\t\tprovider.on( 'status', handlers.onStatusChange );\n\n\t\t\t\treturn provider;\n\t\t\t} )\n\t\t);\n\n\t\t// Attach observers.\n\t\tstateMap.observe( onStateMapUpdate );\n\t}\n\n\t/**\n\t * Unload an entity, stop syncing, destroy its in-memory state, and trigger an\n\t * update of the collection.\n\t *\n\t * @param {ObjectType} objectType Object type to discard.\n\t * @param {ObjectID} objectId Object ID to discard, or null for collections.\n\t */\n\tfunction unloadEntity( objectType: ObjectType, objectId: ObjectID ): void {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tlog( 'unloadEntity', 'unloading', entityId );\n\t\tentityStates.get( entityId )?.unload();\n\t\tupdateCRDTDoc( objectType, null, {}, origin, { isSave: true } );\n\t}\n\n\t/**\n\t * Get the awareness instance for the given object type and object ID, if supported.\n\t *\n\t * @template {Awareness} State\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @return {State | undefined} The awareness instance, or undefined if not supported.\n\t */\n\tfunction getAwareness< State extends Awareness >(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): State | undefined {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState || ! entityState.awareness ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn entityState.awareness as State;\n\t}\n\n\t/**\n\t * Load and inspect the persisted CRDT document. If supported and it exists,\n\t * compare it against the current entity record. If there are differences,\n\t * apply the changes from the entity record.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {ObjectData} record Entity record representing this object type.\n\t */\n\tfunction _applyPersistedCrdtDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData\n\t): void {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'no entity state', entityId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst {\n\t\t\thandlers,\n\t\t\tsyncConfig: {\n\t\t\t\tapplyChangesToCRDTDoc,\n\t\t\t\tgetChangesFromCRDTDoc,\n\t\t\t\tgetPersistedCRDTDoc,\n\t\t\t},\n\t\t\tydoc: targetDoc,\n\t\t} = entityState;\n\n\t\t// Get the persisted CRDT document, if it exists.\n\t\tconst serialized = getPersistedCRDTDoc?.( record );\n\t\tconst tempDoc = serialized ? deserializeCrdtDoc( serialized ) : null;\n\n\t\tif ( ! tempDoc ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'no persisted doc', entityId );\n\t\t\t// Apply the current record as changes and trigger a save, which will\n\t\t\t// persist the CRDT document. (The entity should call `createPersistedCRDTDoc`\n\t\t\t// via its pre-persist hook.)\n\t\t\ttargetDoc.transact( () => {\n\t\t\t\tapplyChangesToCRDTDoc( targetDoc, record );\n\t\t\t\thandlers.saveRecord();\n\t\t\t}, LOCAL_SYNC_MANAGER_ORIGIN );\n\t\t\treturn;\n\t\t}\n\n\t\t// Apply the persisted document to the current document as a single update.\n\t\t// This is done even if the persisted document has been invalidated. This\n\t\t// prevents a newly joining peer (or refreshing user) from re-initializing\n\t\t// the CRDT document (the \"initialization problem\").\n\t\t//\n\t\t// IMPORTANT: Do not wrap this in a transaction with the local origin. It\n\t\t// effectively advances the state vector for the current client, which causes\n\t\t// Yjs to think that another client is using this client ID.\n\t\tconst update = Y.encodeStateAsUpdateV2( tempDoc );\n\t\tY.applyUpdateV2( targetDoc, update );\n\n\t\t// Compute the differences between the persisted doc and the current\n\t\t// record. This can happen when:\n\t\t//\n\t\t// 1. The server makes updates on save that mutate the entity. Example: On\n\t\t// initial save, the server adds the \"Uncategorized\" category to the\n\t\t// post.\n\t\t// 2. An \"out-of-band\" update occurs. Example: a WP-CLI command or direct\n\t\t// database update mutates the entity.\n\t\t// 3. Unsaved changes are synced from a peer _before_ this code runs. We\n\t\t// can't control when (or if) remote changes are synced, so this is a\n\t\t// race condition.\n\t\tconst invalidations = getChangesFromCRDTDoc( tempDoc, record );\n\t\tconst invalidatedKeys = Object.keys( invalidations );\n\n\t\t// Destroy the temporary document to prevent leaks.\n\t\ttempDoc.destroy();\n\n\t\tif ( 0 === invalidatedKeys.length ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'valid persisted doc', entityId );\n\t\t\t// The persisted CRDT document is valid. There are no updates to apply.\n\t\t\treturn;\n\t\t}\n\n\t\tlog( 'applyPersistedCrdtDoc', 'invalidated keys', entityId, {\n\t\t\tinvalidatedKeys,\n\t\t} );\n\n\t\t// Use the invalidated keys to get the updated values from the entity.\n\t\tconst changes = invalidatedKeys.reduce(\n\t\t\t( acc, key ) =>\n\t\t\t\tObject.assign( acc, {\n\t\t\t\t\t[ key ]: record[ key ],\n\t\t\t\t} ),\n\t\t\t{}\n\t\t);\n\n\t\t// Apply the changes and trigger a save, which will persist the CRDT\n\t\t// document. (The entity should call `createPersistedCRDTDoc` via its\n\t\t// pre-persist hook.)\n\t\ttargetDoc.transact( () => {\n\t\t\tapplyChangesToCRDTDoc( targetDoc, changes );\n\t\t\thandlers.saveRecord();\n\t\t}, LOCAL_SYNC_MANAGER_ORIGIN );\n\t}\n\n\t/**\n\t * Update CRDT document with changes from the local store.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {Partial< ObjectData >} changes Updates to make.\n\t * @param {string} origin The source of change.\n\t * @param {SyncManagerUpdateOptions} options Optional flags for the update.\n\t * @param {boolean} options.isSave Whether this update is part of a save operation. Defaults to false.\n\t * @param {boolean} options.isNewUndoLevel Whether to create a new undo level for this change. Defaults to false.\n\t */\n\tfunction updateCRDTDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID | null,\n\t\tchanges: Partial< ObjectData >,\n\t\torigin: string,\n\t\toptions: SyncManagerUpdateOptions = {}\n\t): void {\n\t\tconst { isSave = false, isNewUndoLevel = false } = options;\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\t\tconst collectionState = collectionStates.get( objectType );\n\n\t\tif ( entityState ) {\n\t\t\tconst { syncConfig, ydoc } = entityState;\n\n\t\t\t// If this is change should create a new undo level, tell the undo\n\t\t\t// manager to stop capturing and create a new undo group.\n\t\t\t// We can't do this in the undo manager itself, because addRecord() is\n\t\t\t// called after the CRDT changes have been applied, and we want to\n\t\t\t// ensure that the undo set is created before the changes are applied.\n\t\t\tif ( isNewUndoLevel && undoManager ) {\n\t\t\t\tundoManager.stopCapturing?.();\n\t\t\t}\n\n\t\t\tydoc.transact( () => {\n\t\t\t\tlog( 'updateCRDTDoc', 'applying changes', entityId, {\n\t\t\t\t\tchangedKeys: Object.keys( changes ),\n\t\t\t\t} );\n\t\t\t\tsyncConfig.applyChangesToCRDTDoc( ydoc, changes );\n\n\t\t\t\tif ( isSave ) {\n\t\t\t\t\tmarkEntityAsSaved( ydoc );\n\t\t\t\t}\n\t\t\t}, origin );\n\t\t}\n\n\t\tif ( collectionState && isSave ) {\n\t\t\tcollectionState.ydoc.transact( () => {\n\t\t\t\tmarkEntityAsSaved( collectionState.ydoc );\n\t\t\t}, origin );\n\t\t}\n\t}\n\n\t/**\n\t * Update the entity record in the local store with changes from the CRDT\n\t * document.\n\t *\n\t * @param {ObjectType} objectType Object type of record to update.\n\t * @param {ObjectID} objectId Object ID of record to update.\n\t */\n\tasync function _updateEntityRecord(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): Promise< void > {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState ) {\n\t\t\tlog( 'updateEntityRecord', 'no entity state', entityId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst { handlers, syncConfig, ydoc } = entityState;\n\n\t\t// Determine which synced properties have actually changed by comparing\n\t\t// them against the current edited entity record.\n\t\tconst changes = syncConfig.getChangesFromCRDTDoc(\n\t\t\tydoc,\n\t\t\tawait handlers.getEditedRecord()\n\t\t);\n\n\t\tconst changedKeys = Object.keys( changes );\n\n\t\tif ( 0 === changedKeys.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlog( 'updateEntityRecord', 'changes', entityId, {\n\t\t\tchangedKeys,\n\t\t} );\n\t\thandlers.editRecord( changes );\n\t}\n\n\t/**\n\t * Create object meta to persist the CRDT document in the entity record.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t */\n\tfunction createPersistedCRDTDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): string | null {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState?.ydoc ) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn serializeCrdtDoc( entityState.ydoc );\n\t}\n\n\t// Collect internal functions so that they can be wrapped before calling.\n\tconst internal = {\n\t\tapplyPersistedCrdtDoc: debugWrap( _applyPersistedCrdtDoc ),\n\t\tupdateEntityRecord: debugWrap( _updateEntityRecord ),\n\t};\n\n\t// Wrap and return the public API.\n\treturn {\n\t\tcreatePersistedCRDTDoc: debugWrap( createPersistedCRDTDoc ),\n\t\tgetAwareness,\n\t\tload: debugWrap( loadEntity ),\n\t\tloadCollection: debugWrap( loadCollection ),\n\t\t// Use getter to ensure we always return the current value of `undoManager`.\n\t\tget undoManager(): SyncUndoManager | undefined {\n\t\t\treturn undoManager;\n\t\t},\n\t\tunload: debugWrap( unloadEntity ),\n\t\tupdate: debugWrap( yieldToEventLoop( updateCRDTDoc ) ),\n\t};\n}\n"], | ||
| "mappings": "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", | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\n/**\n * Internal dependencies\n */\nimport {\n\tCRDT_RECORD_MAP_KEY,\n\tCRDT_STATE_MAP_KEY,\n\tCRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,\n\tLOCAL_SYNC_MANAGER_ORIGIN,\n} from './config';\nimport {\n\tlogPerformanceTiming,\n\tpassThru,\n\tyieldToEventLoop,\n} from './performance';\nimport { getProviderCreators } from './providers';\nimport type {\n\tCollectionHandlers,\n\tCRDTDoc,\n\tEntityID,\n\tObjectID,\n\tObjectData,\n\tObjectType,\n\tProviderCreator,\n\tRecordHandlers,\n\tSyncConfig,\n\tSyncManager,\n\tSyncManagerUpdateOptions,\n\tSyncUndoManager,\n} from './types';\nimport { createUndoManager } from './undo-manager';\nimport {\n\tcreateYjsDoc,\n\tdeserializeCrdtDoc,\n\tinitializeYjsDoc,\n\tmarkEntityAsSaved,\n\tserializeCrdtDoc,\n} from './utils';\n\ninterface CollectionState {\n\tawareness?: Awareness;\n\thandlers: CollectionHandlers;\n\tsyncConfig: SyncConfig;\n\tunload: () => void;\n\tydoc: CRDTDoc;\n}\n\ninterface EntityState {\n\tawareness?: Awareness;\n\thandlers: RecordHandlers;\n\tobjectId: ObjectID;\n\tobjectType: ObjectType;\n\tsyncConfig: SyncConfig;\n\tunload: () => void;\n\tydoc: CRDTDoc;\n}\n\n/**\n * Get the entity ID for the given object type and object ID.\n *\n * @param {ObjectType} objectType Object type.\n * @param {ObjectID|null} objectId Object ID.\n */\nfunction getEntityId(\n\tobjectType: ObjectType,\n\tobjectId: ObjectID | null\n): EntityID {\n\treturn `${ objectType }_${ objectId }`;\n}\n\n/**\n * The sync manager orchestrates the lifecycle of syncing entity records. It\n * creates Yjs documents, connects to providers, creates awareness instances,\n * and coordinates with the `core-data` store.\n *\n * @param debug Whether to enable performance and debug logging.\n */\nexport function createSyncManager( debug = false ): SyncManager {\n\tconst debugWrap = debug ? logPerformanceTiming : passThru;\n\tconst collectionStates: Map< ObjectType, CollectionState > = new Map();\n\tconst entityStates: Map< EntityID, EntityState > = new Map();\n\n\t/**\n\t * A \"sync-aware\" undo manager for all synced entities. It is lazily created\n\t * when the first entity is loaded.\n\t *\n\t * IMPORTANT: In Gutenberg, the undo manager is effectively global and manages\n\t * undo/redo state for all entities. If the default WPUndoManager is used,\n\t * changes to entities are recorded in the `editEntityRecord` action:\n\t *\n\t * https://github.com/WordPress/gutenberg/blob/b63451e26e3c91b6bb291a2f9994722e3850417e/packages/core-data/src/actions.js#L428-L442\n\t *\n\t * In contrast, the `SyncUndoManager` only manages undo/redo for entities that\n\t * **are being synced by this sync manager**. The `addRecord` method is still\n\t * called in the code linked above, but it is a no-op. Yjs automatically tracks\n\t * changes to entities via the associated CRDT doc:\n\t *\n\t * https://github.com/WordPress/gutenberg/blob/b63451e26e3c91b6bb291a2f9994722e3850417e/packages/sync/src/undo-manager.ts#L42-L48\n\t *\n\t * This means that if at least one entity is being synced, then undo/redo\n\t * operations will be **restricted to synced entities only.**\n\t *\n\t * We could improve the `SyncUndoManager` to also track non-synced entities by\n\t * delegating to a secondary `WPUndoManager`, but this would add complexity\n\t * since we would need to maintain two separate undo/redo stacks and ensure\n\t * that they retain ordering and integrity.\n\t *\n\t * However, we also anticipate that most entities being edited in Gutenberg\n\t * will be synced entities (e.g. posts, pages, templates, template parts,\n\t * etc.), so this limitation may be temporary.\n\t */\n\tlet undoManager: SyncUndoManager | undefined;\n\n\t/**\n\t * Log debug messages if debugging is enabled.\n\t *\n\t * @param component The component or context related to the log message\n\t * @param message The debug message\n\t * @param entityId The entity ID related to the log message\n\t * @param context Additional debug context\n\t */\n\tfunction log(\n\t\tcomponent: string,\n\t\tmessage: string,\n\t\tentityId: string,\n\t\tcontext: object = {}\n\t): void {\n\t\tif ( ! debug ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.log( `[SyncManager][${ component }]: ${ message }`, {\n\t\t\t...context,\n\t\t\tentityId,\n\t\t} );\n\t}\n\n\t/**\n\t * Load an entity for syncing and manage its lifecycle.\n\t *\n\t * @param {SyncConfig} syncConfig Sync configuration for the object type.\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {ObjectData} record Entity record representing this object type.\n\t * @param {RecordHandlers} handlers Handlers for updating and fetching the record.\n\t */\n\tasync function loadEntity(\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData,\n\t\thandlers: RecordHandlers\n\t): Promise< void > {\n\t\tconst providerCreators = getProviderCreators();\n\t\tconst entityId = getEntityId( objectType, objectId );\n\n\t\tif ( 0 === providerCreators.length ) {\n\t\t\tlog( 'loadEntity', 'no providers, skipping', entityId );\n\t\t\treturn; // No provider creators, so syncing is effectively disabled.\n\t\t}\n\n\t\tif ( entityStates.has( entityId ) ) {\n\t\t\tlog( 'loadEntity', 'already loaded', entityId );\n\t\t\treturn; // Already bootstrapped.\n\t\t}\n\n\t\tlog( 'loadEntity', 'loading', entityId );\n\n\t\thandlers = {\n\t\t\taddUndoMeta: debugWrap( handlers.addUndoMeta ),\n\t\t\teditRecord: debugWrap( handlers.editRecord ),\n\t\t\tgetEditedRecord: debugWrap( handlers.getEditedRecord ),\n\t\t\tonStatusChange: debugWrap( handlers.onStatusChange ),\n\t\t\trefetchRecord: debugWrap( handlers.refetchRecord ),\n\t\t\trestoreUndoMeta: debugWrap( handlers.restoreUndoMeta ),\n\t\t\tsaveRecord: debugWrap( handlers.saveRecord ),\n\t\t};\n\n\t\tconst ydoc = createYjsDoc( { objectType } );\n\t\tconst recordMap = ydoc.getMap( CRDT_RECORD_MAP_KEY );\n\t\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\t\tconst now = Date.now();\n\n\t\t// Clean up providers and in-memory state when the entity is unloaded.\n\t\tconst unload = (): void => {\n\t\t\tlog( 'loadEntity', 'unloading', entityId );\n\t\t\tproviderResults.forEach( ( result ) => result.destroy() );\n\t\t\thandlers.onStatusChange( null );\n\t\t\trecordMap.unobserveDeep( onRecordUpdate );\n\t\t\tstateMap.unobserve( onStateMapUpdate );\n\t\t\tydoc.destroy();\n\t\t\tentityStates.delete( entityId );\n\t\t};\n\n\t\t// If the sync config supports awareness, create it.\n\t\tconst awareness = syncConfig.createAwareness?.( ydoc, objectId );\n\n\t\t// When the CRDT document is updated by an UndoManager or a connection (not\n\t\t// a local origin), update the local store.\n\t\tconst onRecordUpdate = (\n\t\t\t_events: Y.YEvent< any >[],\n\t\t\ttransaction: Y.Transaction\n\t\t): void => {\n\t\t\tif (\n\t\t\t\ttransaction.local &&\n\t\t\t\t! ( transaction.origin instanceof Y.UndoManager )\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid internal.updateEntityRecord( objectType, objectId );\n\t\t};\n\n\t\tconst onStateMapUpdate = (\n\t\t\tevent: Y.YMapEvent< unknown >,\n\t\t\ttransaction: Y.Transaction\n\t\t) => {\n\t\t\tif ( transaction.local ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.keysChanged.forEach( ( key ) => {\n\t\t\t\tswitch ( key ) {\n\t\t\t\t\tcase SAVED_AT_KEY:\n\t\t\t\t\t\tconst newValue = stateMap.get( SAVED_AT_KEY );\n\t\t\t\t\t\tif ( 'number' === typeof newValue && newValue > now ) {\n\t\t\t\t\t\t\t// Another peer has saved the record. Refetch it so that we have\n\t\t\t\t\t\t\t// a correct understanding of our own unsaved edits.\n\t\t\t\t\t\t\tlog( 'loadEntity', 'refetching record', entityId );\n\t\t\t\t\t\t\tvoid handlers.refetchRecord().catch( () => {} );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\n\t\t// Lazily create the undo manager when the first entity is loaded.\n\t\tif ( ! undoManager ) {\n\t\t\tundoManager = createUndoManager();\n\t\t}\n\n\t\tconst { addUndoMeta, restoreUndoMeta } = handlers;\n\t\tundoManager.addToScope( recordMap, {\n\t\t\taddUndoMeta,\n\t\t\trestoreUndoMeta,\n\t\t} );\n\n\t\tconst entityState: EntityState = {\n\t\t\tawareness,\n\t\t\thandlers,\n\t\t\tobjectId,\n\t\t\tobjectType,\n\t\t\tsyncConfig,\n\t\t\tunload,\n\t\t\tydoc,\n\t\t};\n\n\t\tentityStates.set( entityId, entityState );\n\n\t\t// Create providers for the given entity and its Yjs document.\n\t\tlog( 'loadEntity', 'connecting', entityId );\n\t\tconst providerResults = await Promise.all(\n\t\t\tproviderCreators.map( async ( create ) => {\n\t\t\t\tconst provider = await create( {\n\t\t\t\t\tobjectType,\n\t\t\t\t\tobjectId,\n\t\t\t\t\tydoc,\n\t\t\t\t\tawareness,\n\t\t\t\t} );\n\n\t\t\t\t// Attach status listener after provider creation.\n\t\t\t\tprovider.on( 'status', handlers.onStatusChange );\n\n\t\t\t\treturn provider;\n\t\t\t} )\n\t\t);\n\n\t\t// Attach observers.\n\t\trecordMap.observeDeep( onRecordUpdate );\n\t\tstateMap.observe( onStateMapUpdate );\n\n\t\t// Initialize the Yjs document with the necessary CRDT state.\n\t\tinitializeYjsDoc( ydoc );\n\n\t\t// Get and apply the persisted CRDT document, if it exists.\n\t\tinternal.applyPersistedCrdtDoc( objectType, objectId, record );\n\t}\n\n\t/**\n\t * Load a collection for syncing and manage its lifecycle.\n\t *\n\t * @param {SyncConfig} syncConfig Sync configuration for the object type.\n\t * @param {ObjectType} objectType Object type.\n\t * @param {CollectionHandlers} handlers Handlers for updating the collection.\n\t */\n\tasync function loadCollection(\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\thandlers: CollectionHandlers\n\t): Promise< void > {\n\t\tconst providerCreators: ProviderCreator[] = getProviderCreators();\n\t\tconst entityId = getEntityId( objectType, null );\n\n\t\tif ( 0 === providerCreators.length ) {\n\t\t\tlog( 'loadCollection', 'no providers, skipping', entityId );\n\t\t\treturn; // No provider creators, so syncing is effectively disabled.\n\t\t}\n\n\t\tif ( collectionStates.has( objectType ) ) {\n\t\t\tlog( 'loadCollection', 'already loaded', entityId );\n\t\t\treturn; // Already loaded.\n\t\t}\n\n\t\tlog( 'loadCollection', 'loading', entityId );\n\n\t\tconst ydoc = createYjsDoc( { collection: true, objectType } );\n\t\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\t\tconst now = Date.now();\n\n\t\t// Clean up providers and in-memory state when the entity is unloaded.\n\t\tconst unload = (): void => {\n\t\t\tlog( 'loadCollection', 'unloading', entityId );\n\t\t\tproviderResults.forEach( ( result ) => result.destroy() );\n\t\t\thandlers.onStatusChange( null );\n\t\t\tstateMap.unobserve( onStateMapUpdate );\n\t\t\tydoc.destroy();\n\t\t\tcollectionStates.delete( objectType );\n\t\t};\n\n\t\tconst onStateMapUpdate = (\n\t\t\tevent: Y.YMapEvent< unknown >,\n\t\t\ttransaction: Y.Transaction\n\t\t) => {\n\t\t\tif ( transaction.local ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tevent.keysChanged.forEach( ( key ) => {\n\t\t\t\tswitch ( key ) {\n\t\t\t\t\tcase SAVED_AT_KEY:\n\t\t\t\t\t\tconst newValue = stateMap.get( SAVED_AT_KEY );\n\t\t\t\t\t\tif ( 'number' === typeof newValue && newValue > now ) {\n\t\t\t\t\t\t\t// Another peer has mutated the collection. Refetch it so that we\n\t\t\t\t\t\t\t// obtain the updated records.\n\t\t\t\t\t\t\tvoid handlers.refetchRecords().catch( () => {} );\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} );\n\t\t};\n\n\t\t// If the sync config supports awareness, create it.\n\t\tconst awareness = syncConfig.createAwareness?.( ydoc );\n\n\t\tconst collectionState: CollectionState = {\n\t\t\tawareness,\n\t\t\thandlers,\n\t\t\tsyncConfig,\n\t\t\tunload,\n\t\t\tydoc,\n\t\t};\n\n\t\tcollectionStates.set( objectType, collectionState );\n\n\t\t// Create providers for the given entity and its Yjs document.\n\t\tlog( 'loadCollection', 'connecting', entityId );\n\t\tconst providerResults = await Promise.all(\n\t\t\tproviderCreators.map( async ( create ) => {\n\t\t\t\tconst provider = await create( {\n\t\t\t\t\tawareness,\n\t\t\t\t\tobjectType,\n\t\t\t\t\tobjectId: null,\n\t\t\t\t\tydoc,\n\t\t\t\t} );\n\n\t\t\t\t// Attach status listener after provider creation.\n\t\t\t\tprovider.on( 'status', handlers.onStatusChange );\n\n\t\t\t\treturn provider;\n\t\t\t} )\n\t\t);\n\n\t\t// Attach observers.\n\t\tstateMap.observe( onStateMapUpdate );\n\n\t\t// Initialize the Yjs document with the necessary CRDT state.\n\t\tinitializeYjsDoc( ydoc );\n\t}\n\n\t/**\n\t * Unload an entity, stop syncing, destroy its in-memory state, and trigger an\n\t * update of the collection.\n\t *\n\t * @param {ObjectType} objectType Object type to discard.\n\t * @param {ObjectID} objectId Object ID to discard, or null for collections.\n\t */\n\tfunction unloadEntity( objectType: ObjectType, objectId: ObjectID ): void {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tlog( 'unloadEntity', 'unloading', entityId );\n\t\tentityStates.get( entityId )?.unload();\n\t\tupdateCRDTDoc( objectType, null, {}, origin, { isSave: true } );\n\t}\n\n\t/**\n\t * Get the awareness instance for the given object type and object ID, if supported.\n\t *\n\t * @template {Awareness} State\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @return {State | undefined} The awareness instance, or undefined if not supported.\n\t */\n\tfunction getAwareness< State extends Awareness >(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): State | undefined {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState || ! entityState.awareness ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn entityState.awareness as State;\n\t}\n\n\t/**\n\t * Load and inspect the persisted CRDT document. If supported and it exists,\n\t * compare it against the current entity record. If there are differences,\n\t * apply the changes from the entity record.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {ObjectData} record Entity record representing this object type.\n\t */\n\tfunction _applyPersistedCrdtDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData\n\t): void {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'no entity state', entityId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst {\n\t\t\thandlers,\n\t\t\tsyncConfig: {\n\t\t\t\tapplyChangesToCRDTDoc,\n\t\t\t\tgetChangesFromCRDTDoc,\n\t\t\t\tgetPersistedCRDTDoc,\n\t\t\t},\n\t\t\tydoc: targetDoc,\n\t\t} = entityState;\n\n\t\t// Get the persisted CRDT document, if it exists.\n\t\tconst serialized = getPersistedCRDTDoc?.( record );\n\t\tconst tempDoc = serialized ? deserializeCrdtDoc( serialized ) : null;\n\n\t\tif ( ! tempDoc ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'no persisted doc', entityId );\n\t\t\t// Apply the current record as changes and trigger a save, which will\n\t\t\t// persist the CRDT document. (The entity should call `createPersistedCRDTDoc`\n\t\t\t// via its pre-persist hook.)\n\t\t\ttargetDoc.transact( () => {\n\t\t\t\tapplyChangesToCRDTDoc( targetDoc, record );\n\t\t\t\thandlers.saveRecord();\n\t\t\t}, LOCAL_SYNC_MANAGER_ORIGIN );\n\t\t\treturn;\n\t\t}\n\n\t\t// Apply the persisted document to the current document as a single update.\n\t\t// This is done even if the persisted document has been invalidated. This\n\t\t// prevents a newly joining peer (or refreshing user) from re-initializing\n\t\t// the CRDT document (the \"initialization problem\").\n\t\t//\n\t\t// IMPORTANT: Do not wrap this in a transaction with the local origin. It\n\t\t// effectively advances the state vector for the current client, which causes\n\t\t// Yjs to think that another client is using this client ID.\n\t\tconst update = Y.encodeStateAsUpdateV2( tempDoc );\n\t\tY.applyUpdateV2( targetDoc, update );\n\n\t\t// Compute the differences between the persisted doc and the current\n\t\t// record. This can happen when:\n\t\t//\n\t\t// 1. The server makes updates on save that mutate the entity. Example: On\n\t\t// initial save, the server adds the \"Uncategorized\" category to the\n\t\t// post.\n\t\t// 2. An \"out-of-band\" update occurs. Example: a WP-CLI command or direct\n\t\t// database update mutates the entity.\n\t\t// 3. Unsaved changes are synced from a peer _before_ this code runs. We\n\t\t// can't control when (or if) remote changes are synced, so this is a\n\t\t// race condition.\n\t\tconst invalidations = getChangesFromCRDTDoc( tempDoc, record );\n\t\tconst invalidatedKeys = Object.keys( invalidations );\n\n\t\t// Destroy the temporary document to prevent leaks.\n\t\ttempDoc.destroy();\n\n\t\tif ( 0 === invalidatedKeys.length ) {\n\t\t\tlog( 'applyPersistedCrdtDoc', 'valid persisted doc', entityId );\n\t\t\t// The persisted CRDT document is valid. There are no updates to apply.\n\t\t\treturn;\n\t\t}\n\n\t\tlog( 'applyPersistedCrdtDoc', 'invalidated keys', entityId, {\n\t\t\tinvalidatedKeys,\n\t\t} );\n\n\t\t// Use the invalidated keys to get the updated values from the entity.\n\t\tconst changes = invalidatedKeys.reduce(\n\t\t\t( acc, key ) =>\n\t\t\t\tObject.assign( acc, {\n\t\t\t\t\t[ key ]: record[ key ],\n\t\t\t\t} ),\n\t\t\t{}\n\t\t);\n\n\t\t// Apply the changes and trigger a save, which will persist the CRDT\n\t\t// document. (The entity should call `createPersistedCRDTDoc` via its\n\t\t// pre-persist hook.)\n\t\ttargetDoc.transact( () => {\n\t\t\tapplyChangesToCRDTDoc( targetDoc, changes );\n\t\t\thandlers.saveRecord();\n\t\t}, LOCAL_SYNC_MANAGER_ORIGIN );\n\t}\n\n\t/**\n\t * Update CRDT document with changes from the local store.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t * @param {Partial< ObjectData >} changes Updates to make.\n\t * @param {string} origin The source of change.\n\t * @param {SyncManagerUpdateOptions} options Optional flags for the update.\n\t * @param {boolean} options.isSave Whether this update is part of a save operation. Defaults to false.\n\t * @param {boolean} options.isNewUndoLevel Whether to create a new undo level for this change. Defaults to false.\n\t */\n\tfunction updateCRDTDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID | null,\n\t\tchanges: Partial< ObjectData >,\n\t\torigin: string,\n\t\toptions: SyncManagerUpdateOptions = {}\n\t): void {\n\t\tconst { isSave = false, isNewUndoLevel = false } = options;\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\t\tconst collectionState = collectionStates.get( objectType );\n\n\t\tif ( entityState ) {\n\t\t\tconst { syncConfig, ydoc } = entityState;\n\n\t\t\t// If this is change should create a new undo level, tell the undo\n\t\t\t// manager to stop capturing and create a new undo group.\n\t\t\t// We can't do this in the undo manager itself, because addRecord() is\n\t\t\t// called after the CRDT changes have been applied, and we want to\n\t\t\t// ensure that the undo set is created before the changes are applied.\n\t\t\tif ( isNewUndoLevel && undoManager ) {\n\t\t\t\tundoManager.stopCapturing?.();\n\t\t\t}\n\n\t\t\tydoc.transact( () => {\n\t\t\t\tlog( 'updateCRDTDoc', 'applying changes', entityId, {\n\t\t\t\t\tchangedKeys: Object.keys( changes ),\n\t\t\t\t} );\n\t\t\t\tsyncConfig.applyChangesToCRDTDoc( ydoc, changes );\n\n\t\t\t\tif ( isSave ) {\n\t\t\t\t\tmarkEntityAsSaved( ydoc );\n\t\t\t\t}\n\t\t\t}, origin );\n\t\t}\n\n\t\tif ( collectionState && isSave ) {\n\t\t\tcollectionState.ydoc.transact( () => {\n\t\t\t\tmarkEntityAsSaved( collectionState.ydoc );\n\t\t\t}, origin );\n\t\t}\n\t}\n\n\t/**\n\t * Update the entity record in the local store with changes from the CRDT\n\t * document.\n\t *\n\t * @param {ObjectType} objectType Object type of record to update.\n\t * @param {ObjectID} objectId Object ID of record to update.\n\t */\n\tasync function _updateEntityRecord(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): Promise< void > {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState ) {\n\t\t\tlog( 'updateEntityRecord', 'no entity state', entityId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst { handlers, syncConfig, ydoc } = entityState;\n\n\t\t// Determine which synced properties have actually changed by comparing\n\t\t// them against the current edited entity record.\n\t\tconst changes = syncConfig.getChangesFromCRDTDoc(\n\t\t\tydoc,\n\t\t\tawait handlers.getEditedRecord()\n\t\t);\n\n\t\tconst changedKeys = Object.keys( changes );\n\n\t\tif ( 0 === changedKeys.length ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlog( 'updateEntityRecord', 'changes', entityId, {\n\t\t\tchangedKeys,\n\t\t} );\n\t\thandlers.editRecord( changes );\n\t}\n\n\t/**\n\t * Create object meta to persist the CRDT document in the entity record.\n\t *\n\t * @param {ObjectType} objectType Object type.\n\t * @param {ObjectID} objectId Object ID.\n\t */\n\tasync function createPersistedCRDTDoc(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t): Promise< string | null > {\n\t\tconst entityId = getEntityId( objectType, objectId );\n\t\tconst entityState = entityStates.get( entityId );\n\n\t\tif ( ! entityState?.ydoc ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Y.Doc updates are deferred via yieldToEventLoop. Await a promise that\n\t\t// resolves on the next tick of the event loop so pending updates are flushed\n\t\t// before we serialize the document.\n\t\tawait new Promise( ( resolve ) => setTimeout( resolve, 0 ) );\n\n\t\treturn serializeCrdtDoc( entityState.ydoc );\n\t}\n\n\t// Collect internal functions so that they can be wrapped before calling.\n\tconst internal = {\n\t\tapplyPersistedCrdtDoc: debugWrap( _applyPersistedCrdtDoc ),\n\t\tupdateEntityRecord: debugWrap( _updateEntityRecord ),\n\t};\n\n\t// Wrap and return the public API.\n\treturn {\n\t\tcreatePersistedCRDTDoc: debugWrap( createPersistedCRDTDoc ),\n\t\tgetAwareness,\n\t\tload: debugWrap( loadEntity ),\n\t\tloadCollection: debugWrap( loadCollection ),\n\t\t// Use getter to ensure we always return the current value of `undoManager`.\n\t\tget undoManager(): SyncUndoManager | undefined {\n\t\t\treturn undoManager;\n\t\t},\n\t\tunload: debugWrap( unloadEntity ),\n\t\tupdate: debugWrap( yieldToEventLoop( updateCRDTDoc ) ),\n\t};\n}\n"], | ||
| "mappings": "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", | ||
| "names": ["SAVED_AT_KEY", "origin"] | ||
| } |
@@ -39,2 +39,3 @@ "use strict"; | ||
| var import_manager = require("./manager.cjs"); | ||
| var import_polling_manager = require("./providers/http-polling/polling-manager.cjs"); | ||
| var import_Delta = __toESM(require("./quill-delta/Delta.cjs")); | ||
@@ -47,3 +48,4 @@ var privateApis = {}; | ||
| CRDT_RECORD_MAP_KEY: import_config.CRDT_RECORD_MAP_KEY, | ||
| LOCAL_EDITOR_ORIGIN: import_config.LOCAL_EDITOR_ORIGIN | ||
| LOCAL_EDITOR_ORIGIN: import_config.LOCAL_EDITOR_ORIGIN, | ||
| retrySyncConnection: () => import_polling_manager.pollingManager.retryNow() | ||
| }); | ||
@@ -50,0 +52,0 @@ // Annotate the CommonJS export names for ESM import in node: |
| { | ||
| "version": 3, | ||
| "sources": ["../src/private-apis.ts"], | ||
| "sourcesContent": ["/**\n * Internal dependencies\n */\nimport {\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_RECORD_MAP_KEY,\n\tLOCAL_EDITOR_ORIGIN,\n} from './config';\nimport { lock } from './lock-unlock';\nimport { createSyncManager } from './manager';\nimport { default as Delta } from './quill-delta/Delta';\n\nexport const privateApis = {};\n\nlock( privateApis, {\n\tcreateSyncManager,\n\tDelta,\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_RECORD_MAP_KEY,\n\tLOCAL_EDITOR_ORIGIN,\n} );\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAIO;AACP,yBAAqB;AACrB,qBAAkC;AAClC,mBAAiC;AAE1B,IAAM,cAAc,CAAC;AAAA,IAE5B,yBAAM,aAAa;AAAA,EAClB;AAAA,EACA,oBAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAE;", | ||
| "sourcesContent": ["/**\n * Internal dependencies\n */\nimport {\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_RECORD_MAP_KEY,\n\tLOCAL_EDITOR_ORIGIN,\n} from './config';\nimport { lock } from './lock-unlock';\nimport { createSyncManager } from './manager';\nimport { pollingManager } from './providers/http-polling/polling-manager';\nimport { default as Delta } from './quill-delta/Delta';\n\nexport const privateApis = {};\n\nlock( privateApis, {\n\tcreateSyncManager,\n\tDelta,\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_RECORD_MAP_KEY,\n\tLOCAL_EDITOR_ORIGIN,\n\tretrySyncConnection: () => pollingManager.retryNow(),\n} );\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,oBAIO;AACP,yBAAqB;AACrB,qBAAkC;AAClC,6BAA+B;AAC/B,mBAAiC;AAE1B,IAAM,cAAc,CAAC;AAAA,IAE5B,yBAAM,aAAa;AAAA,EAClB;AAAA,EACA,oBAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,qBAAqB,MAAM,sCAAe,SAAS;AACpD,CAAE;", | ||
| "names": ["Delta"] | ||
| } |
@@ -70,9 +70,10 @@ "use strict"; | ||
| /** | ||
| * Emit connection status. | ||
| * Emit connection status, passing the full object through so that | ||
| * additional fields (e.g. `retryInMs`) are preserved for consumers. | ||
| * | ||
| * @param status The connection status | ||
| * @param status.error Optional error information when status is 'disconnected' | ||
| * @param status.status The connection status ('connected', 'connecting', 'disconnected') | ||
| * @param connectionStatus The connection status object | ||
| */ | ||
| emitStatus = ({ error, status }) => { | ||
| emitStatus = (connectionStatus) => { | ||
| const { status } = connectionStatus; | ||
| const error = status === "disconnected" ? connectionStatus.error : void 0; | ||
| if (this.status === status && !error) { | ||
@@ -86,3 +87,3 @@ return; | ||
| this.status = status; | ||
| this.emit("status", [{ error, status }]); | ||
| this.emit("status", [connectionStatus]); | ||
| }; | ||
@@ -89,0 +90,0 @@ /** |
| { | ||
| "version": 3, | ||
| "sources": ["../../../src/providers/http-polling/http-polling-provider.ts"], | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport type * as Y from 'yjs';\nimport { ObservableV2 } from 'lib0/observable';\nimport { Awareness } from 'y-protocols/awareness';\n\n/**\n * Internal dependencies\n */\nimport type {\n\tConnectionStatus,\n\tProviderCreator,\n\tProviderCreatorResult,\n} from '../../types';\nimport { pollingManager } from './polling-manager';\n\nexport interface ProviderOptions {\n\tawareness?: Awareness;\n\tdebug?: boolean;\n\troom: string;\n\tydoc: Y.Doc;\n}\n\n/**\n * Event types for HttpPollingProvider.\n * ObservableV2 expects event handlers as functions.\n */\ntype HttpPollingEvents = {\n\tstatus: ( status: ConnectionStatus ) => void;\n};\n\n/**\n * Yjs provider that uses HTTP polling for real-time synchronization. It manages\n * document updates and awareness states through a central sync server.\n */\nclass HttpPollingProvider extends ObservableV2< HttpPollingEvents > {\n\tprotected awareness: Awareness;\n\tprotected status: ConnectionStatus[ 'status' ] = 'disconnected';\n\tprotected synced = false;\n\n\tpublic constructor( protected options: ProviderOptions ) {\n\t\tsuper();\n\t\tthis.log( 'Initializing', { room: options.room } );\n\n\t\tthis.awareness = options.awareness ?? new Awareness( options.ydoc );\n\t\tthis.connect();\n\t}\n\n\t/**\n\t * Connect to the endpoint and initialize sync.\n\t */\n\tpublic connect(): void {\n\t\tthis.log( 'Connecting' );\n\n\t\tpollingManager.registerRoom( {\n\t\t\troom: this.options.room,\n\t\t\tdoc: this.options.ydoc,\n\t\t\tawareness: this.awareness,\n\t\t\tlog: this.log,\n\t\t\tonStatusChange: this.emitStatus,\n\t\t\tonSync: this.onSync,\n\t\t} );\n\t}\n\n\t/**\n\t * Destroy the provider and cleanup resources.\n\t */\n\tpublic destroy(): void {\n\t\tthis.disconnect();\n\t\tsuper.destroy();\n\t}\n\n\t/**\n\t * Disconnect the provider and allow reconnection later.\n\t */\n\tpublic disconnect(): void {\n\t\tthis.log( 'Disconnecting' );\n\n\t\tpollingManager.unregisterRoom( this.options.room );\n\t\tthis.emitStatus( { status: 'disconnected' } );\n\t}\n\n\t/**\n\t * Emit connection status.\n\t *\n\t * @param status The connection status\n\t * @param status.error Optional error information when status is 'disconnected'\n\t * @param status.status The connection status ('connected', 'connecting', 'disconnected')\n\t */\n\tprotected emitStatus = ( { error, status }: ConnectionStatus ): void => {\n\t\tif ( this.status === status && ! error ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Only emit 'connecting' status if transitioning from 'disconnected'.\n\t\tif ( status === 'connecting' && this.status !== 'disconnected' ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.log( 'Status change', { status, error } );\n\n\t\t// ObservableV2 expects arguments as an array\n\t\tthis.status = status;\n\t\tthis.emit( 'status', [ { error, status } ] );\n\t};\n\n\t/**\n\t * Log debug messages if debugging is enabled.\n\t *\n\t * @param message The debug message\n\t * @param debug Additional debug information\n\t */\n\tprotected log = ( message: string, debug: object = {} ): void => {\n\t\tif ( this.options.debug ) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.log( `[${ this.constructor.name }]: ${ message }`, {\n\t\t\t\troom: this.options.room,\n\t\t\t\t...debug,\n\t\t\t} );\n\t\t}\n\t};\n\n\t/**\n\t * Handle synchronization events from the polling manager.\n\t */\n\tprotected onSync = (): void => {\n\t\tif ( ! this.synced ) {\n\t\t\tthis.synced = true;\n\t\t\tthis.log( 'Synced' );\n\t\t}\n\t};\n}\n\n/**\n * Create a provider creator function for the HttpPollingProvider\n */\nexport function createHttpPollingProvider(): ProviderCreator {\n\treturn async ( {\n\t\tawareness,\n\t\tobjectType,\n\t\tobjectId,\n\t\tydoc,\n\t} ): Promise< ProviderCreatorResult > => {\n\t\t// Generate room name from objectType and objectId\n\t\tconst room = objectId ? `${ objectType }:${ objectId }` : objectType;\n\t\tconst provider = new HttpPollingProvider( {\n\t\t\tawareness,\n\t\t\t// debug: true,\n\t\t\troom,\n\t\t\tydoc,\n\t\t} );\n\n\t\treturn {\n\t\t\tdestroy: () => provider.destroy(),\n\t\t\t// Adapter: ObservableV2.on is compatible with ProviderOn\n\t\t\t// The callback receives data as the first parameter\n\t\t\ton: ( event, callback ) => {\n\t\t\t\tprovider.on( event, callback );\n\t\t\t},\n\t\t};\n\t};\n}\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,wBAA6B;AAC7B,uBAA0B;AAU1B,6BAA+B;AAqB/B,IAAM,sBAAN,cAAkC,+BAAkC;AAAA,EAK5D,YAAuB,SAA2B;AACxD,UAAM;AADuB;AAE7B,SAAK,IAAK,gBAAgB,EAAE,MAAM,QAAQ,KAAK,CAAE;AAEjD,SAAK,YAAY,QAAQ,aAAa,IAAI,2BAAW,QAAQ,IAAK;AAClE,SAAK,QAAQ;AAAA,EACd;AAAA,EAVU;AAAA,EACA,SAAuC;AAAA,EACvC,SAAS;AAAA;AAAA;AAAA;AAAA,EAaZ,UAAgB;AACtB,SAAK,IAAK,YAAa;AAEvB,0CAAe,aAAc;AAAA,MAC5B,MAAM,KAAK,QAAQ;AAAA,MACnB,KAAK,KAAK,QAAQ;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,KAAK,KAAK;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,IACd,CAAE;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,UAAgB;AACtB,SAAK,WAAW;AAChB,UAAM,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKO,aAAmB;AACzB,SAAK,IAAK,eAAgB;AAE1B,0CAAe,eAAgB,KAAK,QAAQ,IAAK;AACjD,SAAK,WAAY,EAAE,QAAQ,eAAe,CAAE;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASU,aAAa,CAAE,EAAE,OAAO,OAAO,MAA+B;AACvE,QAAK,KAAK,WAAW,UAAU,CAAE,OAAQ;AACxC;AAAA,IACD;AAGA,QAAK,WAAW,gBAAgB,KAAK,WAAW,gBAAiB;AAChE;AAAA,IACD;AAEA,SAAK,IAAK,iBAAiB,EAAE,QAAQ,MAAM,CAAE;AAG7C,SAAK,SAAS;AACd,SAAK,KAAM,UAAU,CAAE,EAAE,OAAO,OAAO,CAAE,CAAE;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,MAAM,CAAE,SAAiB,QAAgB,CAAC,MAAa;AAChE,QAAK,KAAK,QAAQ,OAAQ;AAEzB,cAAQ,IAAK,IAAK,KAAK,YAAY,IAAK,MAAO,OAAQ,IAAI;AAAA,QAC1D,MAAM,KAAK,QAAQ;AAAA,QACnB,GAAG;AAAA,MACJ,CAAE;AAAA,IACH;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKU,SAAS,MAAY;AAC9B,QAAK,CAAE,KAAK,QAAS;AACpB,WAAK,SAAS;AACd,WAAK,IAAK,QAAS;AAAA,IACpB;AAAA,EACD;AACD;AAKO,SAAS,4BAA6C;AAC5D,SAAO,OAAQ;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,MAAyC;AAExC,UAAM,OAAO,WAAW,GAAI,UAAW,IAAK,QAAS,KAAK;AAC1D,UAAM,WAAW,IAAI,oBAAqB;AAAA,MACzC;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,IACD,CAAE;AAEF,WAAO;AAAA,MACN,SAAS,MAAM,SAAS,QAAQ;AAAA;AAAA;AAAA,MAGhC,IAAI,CAAE,OAAO,aAAc;AAC1B,iBAAS,GAAI,OAAO,QAAS;AAAA,MAC9B;AAAA,IACD;AAAA,EACD;AACD;", | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport type * as Y from 'yjs';\nimport { ObservableV2 } from 'lib0/observable';\nimport { Awareness } from 'y-protocols/awareness';\n\n/**\n * Internal dependencies\n */\nimport type {\n\tConnectionStatus,\n\tProviderCreator,\n\tProviderCreatorResult,\n} from '../../types';\nimport { pollingManager } from './polling-manager';\n\nexport interface ProviderOptions {\n\tawareness?: Awareness;\n\tdebug?: boolean;\n\troom: string;\n\tydoc: Y.Doc;\n}\n\n/**\n * Event types for HttpPollingProvider.\n * ObservableV2 expects event handlers as functions.\n */\ntype HttpPollingEvents = {\n\tstatus: ( status: ConnectionStatus ) => void;\n};\n\n/**\n * Yjs provider that uses HTTP polling for real-time synchronization. It manages\n * document updates and awareness states through a central sync server.\n */\nclass HttpPollingProvider extends ObservableV2< HttpPollingEvents > {\n\tprotected awareness: Awareness;\n\tprotected status: ConnectionStatus[ 'status' ] = 'disconnected';\n\tprotected synced = false;\n\n\tpublic constructor( protected options: ProviderOptions ) {\n\t\tsuper();\n\t\tthis.log( 'Initializing', { room: options.room } );\n\n\t\tthis.awareness = options.awareness ?? new Awareness( options.ydoc );\n\t\tthis.connect();\n\t}\n\n\t/**\n\t * Connect to the endpoint and initialize sync.\n\t */\n\tpublic connect(): void {\n\t\tthis.log( 'Connecting' );\n\n\t\tpollingManager.registerRoom( {\n\t\t\troom: this.options.room,\n\t\t\tdoc: this.options.ydoc,\n\t\t\tawareness: this.awareness,\n\t\t\tlog: this.log,\n\t\t\tonStatusChange: this.emitStatus,\n\t\t\tonSync: this.onSync,\n\t\t} );\n\t}\n\n\t/**\n\t * Destroy the provider and cleanup resources.\n\t */\n\tpublic destroy(): void {\n\t\tthis.disconnect();\n\t\tsuper.destroy();\n\t}\n\n\t/**\n\t * Disconnect the provider and allow reconnection later.\n\t */\n\tpublic disconnect(): void {\n\t\tthis.log( 'Disconnecting' );\n\n\t\tpollingManager.unregisterRoom( this.options.room );\n\t\tthis.emitStatus( { status: 'disconnected' } );\n\t}\n\n\t/**\n\t * Emit connection status, passing the full object through so that\n\t * additional fields (e.g. `retryInMs`) are preserved for consumers.\n\t *\n\t * @param connectionStatus The connection status object\n\t */\n\tprotected emitStatus = ( connectionStatus: ConnectionStatus ): void => {\n\t\tconst { status } = connectionStatus;\n\t\tconst error =\n\t\t\tstatus === 'disconnected' ? connectionStatus.error : undefined;\n\n\t\tif ( this.status === status && ! error ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Only emit 'connecting' status if transitioning from 'disconnected'.\n\t\tif ( status === 'connecting' && this.status !== 'disconnected' ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.log( 'Status change', { status, error } );\n\n\t\t// ObservableV2 expects arguments as an array\n\t\tthis.status = status;\n\t\tthis.emit( 'status', [ connectionStatus ] );\n\t};\n\n\t/**\n\t * Log debug messages if debugging is enabled.\n\t *\n\t * @param message The debug message\n\t * @param debug Additional debug information\n\t */\n\tprotected log = ( message: string, debug: object = {} ): void => {\n\t\tif ( this.options.debug ) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.log( `[${ this.constructor.name }]: ${ message }`, {\n\t\t\t\troom: this.options.room,\n\t\t\t\t...debug,\n\t\t\t} );\n\t\t}\n\t};\n\n\t/**\n\t * Handle synchronization events from the polling manager.\n\t */\n\tprotected onSync = (): void => {\n\t\tif ( ! this.synced ) {\n\t\t\tthis.synced = true;\n\t\t\tthis.log( 'Synced' );\n\t\t}\n\t};\n}\n\n/**\n * Create a provider creator function for the HttpPollingProvider\n */\nexport function createHttpPollingProvider(): ProviderCreator {\n\treturn async ( {\n\t\tawareness,\n\t\tobjectType,\n\t\tobjectId,\n\t\tydoc,\n\t} ): Promise< ProviderCreatorResult > => {\n\t\t// Generate room name from objectType and objectId\n\t\tconst room = objectId ? `${ objectType }:${ objectId }` : objectType;\n\t\tconst provider = new HttpPollingProvider( {\n\t\t\tawareness,\n\t\t\t// debug: true,\n\t\t\troom,\n\t\t\tydoc,\n\t\t} );\n\n\t\treturn {\n\t\t\tdestroy: () => provider.destroy(),\n\t\t\t// Adapter: ObservableV2.on is compatible with ProviderOn\n\t\t\t// The callback receives data as the first parameter\n\t\t\ton: ( event, callback ) => {\n\t\t\t\tprovider.on( event, callback );\n\t\t\t},\n\t\t};\n\t};\n}\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,wBAA6B;AAC7B,uBAA0B;AAU1B,6BAA+B;AAqB/B,IAAM,sBAAN,cAAkC,+BAAkC;AAAA,EAK5D,YAAuB,SAA2B;AACxD,UAAM;AADuB;AAE7B,SAAK,IAAK,gBAAgB,EAAE,MAAM,QAAQ,KAAK,CAAE;AAEjD,SAAK,YAAY,QAAQ,aAAa,IAAI,2BAAW,QAAQ,IAAK;AAClE,SAAK,QAAQ;AAAA,EACd;AAAA,EAVU;AAAA,EACA,SAAuC;AAAA,EACvC,SAAS;AAAA;AAAA;AAAA;AAAA,EAaZ,UAAgB;AACtB,SAAK,IAAK,YAAa;AAEvB,0CAAe,aAAc;AAAA,MAC5B,MAAM,KAAK,QAAQ;AAAA,MACnB,KAAK,KAAK,QAAQ;AAAA,MAClB,WAAW,KAAK;AAAA,MAChB,KAAK,KAAK;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,IACd,CAAE;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKO,UAAgB;AACtB,SAAK,WAAW;AAChB,UAAM,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKO,aAAmB;AACzB,SAAK,IAAK,eAAgB;AAE1B,0CAAe,eAAgB,KAAK,QAAQ,IAAK;AACjD,SAAK,WAAY,EAAE,QAAQ,eAAe,CAAE;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,aAAa,CAAE,qBAA8C;AACtE,UAAM,EAAE,OAAO,IAAI;AACnB,UAAM,QACL,WAAW,iBAAiB,iBAAiB,QAAQ;AAEtD,QAAK,KAAK,WAAW,UAAU,CAAE,OAAQ;AACxC;AAAA,IACD;AAGA,QAAK,WAAW,gBAAgB,KAAK,WAAW,gBAAiB;AAChE;AAAA,IACD;AAEA,SAAK,IAAK,iBAAiB,EAAE,QAAQ,MAAM,CAAE;AAG7C,SAAK,SAAS;AACd,SAAK,KAAM,UAAU,CAAE,gBAAiB,CAAE;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,MAAM,CAAE,SAAiB,QAAgB,CAAC,MAAa;AAChE,QAAK,KAAK,QAAQ,OAAQ;AAEzB,cAAQ,IAAK,IAAK,KAAK,YAAY,IAAK,MAAO,OAAQ,IAAI;AAAA,QAC1D,MAAM,KAAK,QAAQ;AAAA,QACnB,GAAG;AAAA,MACJ,CAAE;AAAA,IACH;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKU,SAAS,MAAY;AAC9B,QAAK,CAAE,KAAK,QAAS;AACpB,WAAK,SAAS;AACd,WAAK,IAAK,QAAS;AAAA,IACpB;AAAA,EACD;AACD;AAKO,SAAS,4BAA6C;AAC5D,SAAO,OAAQ;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,MAAyC;AAExC,UAAM,OAAO,WAAW,GAAI,UAAW,IAAK,QAAS,KAAK;AAC1D,UAAM,WAAW,IAAI,oBAAqB;AAAA,MACzC;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,IACD,CAAE;AAEF,WAAO;AAAA,MACN,SAAS,MAAM,SAAS,QAAQ;AAAA;AAAA;AAAA,MAGhC,IAAI,CAAE,OAAO,aAAc;AAC1B,iBAAS,GAAI,OAAO,QAAS;AAAA,MAC9B;AAAA,IACD;AAAA,EACD;AACD;", | ||
| "names": [] | ||
| } |
@@ -45,3 +45,3 @@ "use strict"; | ||
| var POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS = 250; | ||
| var POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 30 * 1e3; | ||
| var POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 25 * 1e3; | ||
| var MAX_ERROR_BACKOFF_IN_MS = 30 * 1e3; | ||
@@ -182,4 +182,2 @@ var POLLING_MANAGER_ORIGIN = "polling-manager"; | ||
| pollingTimeoutId = null; | ||
| } | ||
| if (isPolling) { | ||
| poll(); | ||
@@ -191,2 +189,3 @@ } | ||
| isPolling = true; | ||
| pollingTimeoutId = null; | ||
| async function start() { | ||
@@ -275,3 +274,6 @@ if (0 === roomStates.size) { | ||
| roomStates.forEach((state) => { | ||
| state.onStatusChange({ status: "disconnected" }); | ||
| state.onStatusChange({ | ||
| status: "disconnected", | ||
| retryInMs: pollInterval | ||
| }); | ||
| }); | ||
@@ -364,4 +366,13 @@ } | ||
| } | ||
| function retryNow() { | ||
| pollInterval = POLLING_INTERVAL_IN_MS * 2; | ||
| if (pollingTimeoutId) { | ||
| clearTimeout(pollingTimeoutId); | ||
| pollingTimeoutId = null; | ||
| poll(); | ||
| } | ||
| } | ||
| var pollingManager = { | ||
| registerRoom, | ||
| retryNow, | ||
| unregisterRoom | ||
@@ -368,0 +379,0 @@ }; |
| { | ||
| "version": 3, | ||
| "sources": ["../../../src/providers/http-polling/polling-manager.ts"], | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport * as encoding from 'lib0/encoding';\nimport * as decoding from 'lib0/decoding';\nimport type { Awareness } from 'y-protocols/awareness';\nimport { removeAwarenessStates } from 'y-protocols/awareness';\nimport * as syncProtocol from 'y-protocols/sync';\n\n/**\n * Internal dependencies\n */\nimport type { ConnectionStatus } from '../../types';\nimport {\n\ttype AwarenessState,\n\ttype LocalAwarenessState,\n\ttype SyncPayload,\n\ttype SyncUpdate,\n\tSyncUpdateType,\n\ttype UpdateQueue,\n} from './types';\nimport {\n\tbase64ToUint8Array,\n\tcreateSyncUpdate,\n\tcreateUpdateQueue,\n\tpostSyncUpdate,\n\tpostSyncUpdateNonBlocking,\n} from './utils';\n\nconst POLLING_INTERVAL_IN_MS = 1000; // 1 second or 1000 milliseconds\nconst POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS = 250; // 250 milliseconds\nconst POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 30 * 1000; // 30 seconds\nconst MAX_ERROR_BACKOFF_IN_MS = 30 * 1000; // 30 seconds\nconst POLLING_MANAGER_ORIGIN = 'polling-manager';\n\ntype LogFunction = ( message: string, debug?: object ) => void;\n\ninterface PollingManager {\n\tregisterRoom: ( options: RegisterRoomOptions ) => void;\n\tunregisterRoom: ( room: string ) => void;\n}\n\ninterface RegisterRoomOptions {\n\troom: string;\n\tdoc: Y.Doc;\n\tawareness: Awareness;\n\tlog: LogFunction;\n\tonStatusChange: ( status: ConnectionStatus ) => void;\n\tonSync: () => void;\n}\n\ninterface RoomState {\n\tclientId: number;\n\tcreateCompactionUpdate: () => SyncUpdate;\n\tendCursor: number;\n\tlocalAwarenessState: LocalAwarenessState;\n\tlog: LogFunction;\n\tonStatusChange: ( status: ConnectionStatus ) => void;\n\tprocessAwarenessUpdate: ( state: AwarenessState ) => void;\n\tprocessDocUpdate: ( update: SyncUpdate ) => SyncUpdate | void;\n\tunregister: () => void;\n\tupdateQueue: UpdateQueue;\n}\n\nconst roomStates: Map< string, RoomState > = new Map();\n\n/**\n * Create a compaction update by merging existing updates. This preserves\n * the original operation metadata (client IDs, logical clocks) so that\n * Yjs deduplication works correctly when the compaction is applied.\n *\n * Deprecated: The server is moving towards full state updates for compaction.\n *\n * @param updates The updates to merge\n */\nfunction createDeprecatedCompactionUpdate( updates: SyncUpdate[] ): SyncUpdate {\n\t// Extract only compaction and update types for merging (skip sync-step updates).\n\t// Decode base64 updates to Uint8Array for merging.\n\tconst mergeable = updates\n\t\t.filter( ( u ) =>\n\t\t\t[ SyncUpdateType.COMPACTION, SyncUpdateType.UPDATE ].includes(\n\t\t\t\tu.type\n\t\t\t)\n\t\t)\n\t\t.map( ( u ) => base64ToUint8Array( u.data ) );\n\n\t// Merge all updates while preserving operation metadata.\n\treturn createSyncUpdate(\n\t\tY.mergeUpdates( mergeable ),\n\t\tSyncUpdateType.COMPACTION\n\t);\n}\n\n/**\n * Create sync step 1 update (announce our state vector).\n *\n * @param doc The Yjs document\n */\nfunction createSyncStep1Update( doc: Y.Doc ): SyncUpdate {\n\tconst encoder = encoding.createEncoder();\n\tsyncProtocol.writeSyncStep1( encoder, doc );\n\treturn createSyncUpdate(\n\t\tencoding.toUint8Array( encoder ),\n\t\tSyncUpdateType.SYNC_STEP_1\n\t);\n}\n\n/**\n * Create sync step 2 update (acknowledge sync step 1).\n *\n * @param doc The Yjs document\n * @param step1 The sync step 1 update received\n */\nfunction createSyncStep2Update( doc: Y.Doc, step1: Uint8Array ): SyncUpdate {\n\tconst decoder = decoding.createDecoder( step1 );\n\tconst encoder = encoding.createEncoder();\n\tsyncProtocol.readSyncMessage(\n\t\tdecoder,\n\t\tencoder,\n\t\tdoc,\n\t\tPOLLING_MANAGER_ORIGIN\n\t);\n\treturn createSyncUpdate(\n\t\tencoding.toUint8Array( encoder ),\n\t\tSyncUpdateType.SYNC_STEP_2\n\t);\n}\n\n/**\n * Process an incoming awareness update from the server.\n *\n * @param state The awareness state received\n * @param awareness The local Awareness instance\n */\nfunction processAwarenessUpdate(\n\tstate: AwarenessState,\n\tawareness: Awareness\n): void {\n\tconst currentStates = awareness.getStates();\n\tconst added = new Set< number >();\n\tconst updated = new Set< number >();\n\n\t// Removed clients are missing from the server state.\n\tconst removed = new Set< number >(\n\t\tcurrentStates.keys().filter( ( clientId ) => ! state[ clientId ] )\n\t);\n\n\tObject.entries( state ).forEach( ( [ clientIdString, awarenessState ] ) => {\n\t\tconst clientId = Number( clientIdString );\n\n\t\t// Skip our own state (we already have it locally).\n\t\tif ( clientId === awareness.clientID ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// A null state should be removed by the server, but handle it here just in case.\n\t\tif ( null === awarenessState ) {\n\t\t\tcurrentStates.delete( clientId );\n\t\t\tremoved.add( clientId );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( ! currentStates.has( clientId ) ) {\n\t\t\tcurrentStates.set( clientId, awarenessState );\n\t\t\tadded.add( clientId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentState = currentStates.get( clientId );\n\n\t\tif (\n\t\t\tJSON.stringify( currentState ) !== JSON.stringify( awarenessState )\n\t\t) {\n\t\t\tcurrentStates.set( clientId, awarenessState );\n\t\t\tupdated.add( clientId );\n\t\t}\n\t} );\n\n\tif ( added.size + updated.size > 0 ) {\n\t\tawareness.emit( 'change', [\n\t\t\t{\n\t\t\t\tadded: Array.from( added ),\n\t\t\t\tupdated: Array.from( updated ),\n\t\t\t\t// Left blank on purpose, as the removal of clients is handled in the if condition below.\n\t\t\t\tremoved: [],\n\t\t\t},\n\t\t] );\n\t}\n\n\tif ( removed.size > 0 ) {\n\t\tremoveAwarenessStates(\n\t\t\tawareness,\n\t\t\tArray.from( removed ),\n\t\t\tPOLLING_MANAGER_ORIGIN\n\t\t);\n\t}\n}\n\n/**\n * Process an incoming sync / document update based on its type.\n *\n * @param update The typed update received\n * @param doc The Yjs document\n * @param onSync Callback when sync is complete\n * @return A response update if needed (e.g., sync_step2 in response to sync_step1)\n */\nfunction processDocUpdate(\n\tupdate: SyncUpdate,\n\tdoc: Y.Doc,\n\tonSync: () => void\n): SyncUpdate | void {\n\tconst data = base64ToUint8Array( update.data );\n\n\tswitch ( update.type ) {\n\t\tcase SyncUpdateType.SYNC_STEP_1: {\n\t\t\t// Respond to sync step 1 with sync step 2.\n\t\t\treturn createSyncStep2Update( doc, data );\n\t\t}\n\n\t\tcase SyncUpdateType.SYNC_STEP_2: {\n\t\t\t// Apply sync step 2 (potentially contains missing updates).\n\t\t\tconst decoder = decoding.createDecoder( data );\n\t\t\tconst encoder = encoding.createEncoder();\n\t\t\tsyncProtocol.readSyncMessage(\n\t\t\t\tdecoder,\n\t\t\t\tencoder,\n\t\t\t\tdoc,\n\t\t\t\tPOLLING_MANAGER_ORIGIN\n\t\t\t);\n\t\t\tonSync();\n\t\t\treturn;\n\t\t}\n\n\t\tcase SyncUpdateType.COMPACTION:\n\t\tcase SyncUpdateType.UPDATE: {\n\t\t\t// Apply document update directly.\n\t\t\tY.applyUpdate( doc, data, POLLING_MANAGER_ORIGIN );\n\t\t}\n\t}\n}\n\nlet areListenersRegistered = false;\nlet hasCollaborators = false;\nlet isActiveBrowser = 'visible' === document.visibilityState;\nlet isPolling = false;\nlet isUnloadPending = false;\nlet pollInterval = POLLING_INTERVAL_IN_MS;\nlet pollingTimeoutId: ReturnType< typeof setTimeout > | null = null;\n\n/**\n * Mark that a page unload has been requested. This fires on\n * `beforeunload` which happens before the browser aborts in-flight\n * fetches, allowing us to distinguish poll failures caused by\n * navigation from genuine server errors in the catch block.\n *\n * If the user cancels the unload (e.g. by dismissing a \"Save Changes?\" dialog),\n * the flag is reset at the start of the next poll cycle so that polling can\n * resume.\n */\nfunction handleBeforeUnload(): void {\n\tisUnloadPending = true;\n}\n\n/**\n * Send a disconnect signal for all registered rooms when the page is\n * being unloaded. Uses `sendBeacon` so the request survives navigation.\n */\nfunction handlePageHide(): void {\n\tconst rooms = Array.from( roomStates.entries() ).map(\n\t\t( [ room, state ] ) => ( {\n\t\t\tafter: 0,\n\t\t\tawareness: null,\n\t\t\tclient_id: state.clientId,\n\t\t\troom,\n\t\t\tupdates: [],\n\t\t} )\n\t);\n\n\tpostSyncUpdateNonBlocking( { rooms } );\n}\n\n/**\n * Hangle change in visibility state of browser tab.\n *\n * Used to trigger a slow down of the collaboration syncs when the\n * browser tab becomes inactive (either the user switches tabs or the\n * screen saver comes on).\n *\n * Fires on the document's visibilitychange event.\n */\nfunction handleVisibilityChange() {\n\tconst wasActive = isActiveBrowser;\n\tisActiveBrowser = document.visibilityState === 'visible';\n\n\tif ( isActiveBrowser && ! wasActive ) {\n\t\t/*\n\t\t * Remove scheduled polling and repoll immediately when reactivated.\n\t\t *\n\t\t * This ensures that any updates by collaborators are immediately reflected\n\t\t * in the document once the browser tab becomes active. Otherwise there would\n\t\t * be a delay of 30 seconds before the updates came through.\n\t\t */\n\t\tif ( pollingTimeoutId ) {\n\t\t\tclearTimeout( pollingTimeoutId );\n\t\t\tpollingTimeoutId = null;\n\t\t}\n\n\t\tif ( isPolling ) {\n\t\t\tpoll();\n\t\t}\n\t}\n}\n\nfunction poll(): void {\n\tisPolling = true;\n\n\tasync function start(): Promise< void > {\n\t\tif ( 0 === roomStates.size ) {\n\t\t\tisPolling = false;\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the unloading flag at the start of each poll cycle so\n\t\t// it doesn't permanently suppress disconnect after the user\n\t\t// cancels a beforeunload dialog.\n\t\tisUnloadPending = false;\n\n\t\t// Emit 'connecting' status.\n\t\troomStates.forEach( ( state ) => {\n\t\t\tstate.onStatusChange( { status: 'connecting' } );\n\t\t} );\n\n\t\t// Create a payload with all queued updates. We include rooms even if they\n\t\t// have no updates to ensure we receive any incoming updates. Note that we\n\t\t// withhold our own updates until we detect another collaborator using the\n\t\t// queue's pause / resume mechanism.\n\t\tconst payload: SyncPayload = {\n\t\t\trooms: Array.from( roomStates.entries() ).map(\n\t\t\t\t( [ room, state ] ) => ( {\n\t\t\t\t\tafter: state.endCursor ?? 0,\n\t\t\t\t\tawareness: state.localAwarenessState,\n\t\t\t\t\tclient_id: state.clientId,\n\t\t\t\t\troom,\n\t\t\t\t\tupdates: state.updateQueue.get(),\n\t\t\t\t} )\n\t\t\t),\n\t\t};\n\n\t\ttry {\n\t\t\tconst { rooms } = await postSyncUpdate( payload );\n\n\t\t\t// Emit 'connected' status.\n\t\t\troomStates.forEach( ( state ) => {\n\t\t\t\tstate.onStatusChange( { status: 'connected' } );\n\t\t\t} );\n\n\t\t\trooms.forEach( ( room ) => {\n\t\t\t\tif ( ! roomStates.has( room.room ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst roomState = roomStates.get( room.room )!;\n\t\t\t\troomState.endCursor = room.end_cursor;\n\n\t\t\t\t// Process awareness update.\n\t\t\t\troomState.processAwarenessUpdate( room.awareness );\n\n\t\t\t\t// If there is another collaborator, resume the queue for the next poll\n\t\t\t\t// and increase polling frequency.\n\t\t\t\tif ( Object.keys( room.awareness ).length > 1 ) {\n\t\t\t\t\thasCollaborators = true;\n\t\t\t\t\troomState.updateQueue.resume();\n\t\t\t\t}\n\n\t\t\t\t// Process each incoming update and collect any responses.\n\t\t\t\tconst responseUpdates = room.updates\n\t\t\t\t\t.map( ( update ) => roomState.processDocUpdate( update ) )\n\t\t\t\t\t.filter( ( update ): update is SyncUpdate =>\n\t\t\t\t\t\tBoolean( update )\n\t\t\t\t\t);\n\t\t\t\troomState.updateQueue.addBulk( responseUpdates );\n\n\t\t\t\t// Respond to compaction requests from server. The server asks only one\n\t\t\t\t// client at a time to compact (lowest active client ID). We encode our\n\t\t\t\t// full document state to replace all prior updates on the server.\n\t\t\t\tif ( room.should_compact ) {\n\t\t\t\t\troomState.log( 'Server requested compaction update' );\n\t\t\t\t\troomState.updateQueue.clear();\n\t\t\t\t\troomState.updateQueue.add(\n\t\t\t\t\t\troomState.createCompactionUpdate()\n\t\t\t\t\t);\n\t\t\t\t} else if ( room.compaction_request ) {\n\t\t\t\t\t// Deprecated\n\t\t\t\t\troomState.log( 'Server requested (old) compaction update' );\n\t\t\t\t\troomState.updateQueue.add(\n\t\t\t\t\t\tcreateDeprecatedCompactionUpdate(\n\t\t\t\t\t\t\troom.compaction_request\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Recalculate polling interval.\n\t\t\tif ( isActiveBrowser && hasCollaborators ) {\n\t\t\t\tpollInterval = POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS;\n\t\t\t} else if ( isActiveBrowser ) {\n\t\t\t\tpollInterval = POLLING_INTERVAL_IN_MS;\n\t\t\t} else {\n\t\t\t\tpollInterval = POLLING_INTERVAL_BACKGROUND_TAB_IN_MS;\n\t\t\t}\n\t\t} catch ( error ) {\n\t\t\t// Exponential backoff on error: double the backoff time, up to max\n\t\t\tpollInterval = Math.min(\n\t\t\t\tpollInterval * 2,\n\t\t\t\tMAX_ERROR_BACKOFF_IN_MS\n\t\t\t);\n\n\t\t\t// Restore updates to queues on failure so they can be retried.\n\t\t\tfor ( const room of payload.rooms ) {\n\t\t\t\tif ( ! roomStates.has( room.room ) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst state = roomStates.get( room.room )!;\n\t\t\t\tstate.updateQueue.restore( room.updates );\n\t\t\t\tstate.log(\n\t\t\t\t\t'Error posting sync update, will retry with backoff',\n\t\t\t\t\t{\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tnextPoll: pollInterval,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Don't report disconnected status when the request was aborted\n\t\t\t// due to page unload (e.g. during a refresh) to avoid briefly\n\t\t\t// flashing the disconnect dialog before the new page loads.\n\t\t\tif ( ! isUnloadPending ) {\n\t\t\t\troomStates.forEach( ( state ) => {\n\t\t\t\t\tstate.onStatusChange( { status: 'disconnected' } );\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\tpollingTimeoutId = setTimeout( poll, pollInterval );\n\t}\n\n\t// Start polling.\n\tvoid start();\n}\nfunction registerRoom( {\n\troom,\n\tdoc,\n\tawareness,\n\tlog,\n\tonSync,\n\tonStatusChange,\n}: RegisterRoomOptions ): void {\n\tif ( roomStates.has( room ) ) {\n\t\treturn;\n\t}\n\n\t// Note: Queue is initially paused. Call .resume() to unpause.\n\tconst updateQueue = createUpdateQueue( [ createSyncStep1Update( doc ) ] );\n\n\tfunction onAwarenessUpdate(): void {\n\t\troomState.localAwarenessState = awareness.getLocalState() ?? {};\n\t}\n\n\tfunction onDocUpdate( update: Uint8Array, origin: unknown ): void {\n\t\tif ( POLLING_MANAGER_ORIGIN === origin ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Tag local document changes as 'update' type.\n\t\tupdateQueue.add( createSyncUpdate( update, SyncUpdateType.UPDATE ) );\n\t}\n\n\tfunction unregister(): void {\n\t\tdoc.off( 'update', onDocUpdate );\n\t\tawareness.off( 'change', onAwarenessUpdate );\n\t\tupdateQueue.clear();\n\t}\n\n\tconst roomState: RoomState = {\n\t\tclientId: doc.clientID,\n\t\tcreateCompactionUpdate: () =>\n\t\t\tcreateSyncUpdate(\n\t\t\t\tY.encodeStateAsUpdate( doc ),\n\t\t\t\tSyncUpdateType.COMPACTION\n\t\t\t),\n\t\tendCursor: 0,\n\t\tlocalAwarenessState: awareness.getLocalState() ?? {},\n\t\tlog,\n\t\tonStatusChange,\n\t\tprocessAwarenessUpdate: ( state: AwarenessState ) =>\n\t\t\tprocessAwarenessUpdate( state, awareness ),\n\t\tprocessDocUpdate: ( update: SyncUpdate ) =>\n\t\t\tprocessDocUpdate( update, doc, onSync ),\n\t\tunregister,\n\t\tupdateQueue,\n\t};\n\n\tdoc.on( 'update', onDocUpdate );\n\tawareness.on( 'change', onAwarenessUpdate );\n\troomStates.set( room, roomState );\n\n\tif ( ! areListenersRegistered ) {\n\t\twindow.addEventListener( 'beforeunload', handleBeforeUnload );\n\t\twindow.addEventListener( 'pagehide', handlePageHide );\n\t\tdocument.addEventListener( 'visibilitychange', handleVisibilityChange );\n\t\tareListenersRegistered = true;\n\t}\n\n\tif ( ! isPolling ) {\n\t\tpoll();\n\t}\n}\n\nfunction unregisterRoom( room: string ): void {\n\tconst state = roomStates.get( room );\n\tif ( state ) {\n\t\t// Send a disconnect signal so the server removes this client's\n\t\t// awareness entry immediately instead of waiting for the timeout.\n\t\tconst rooms = [\n\t\t\t{\n\t\t\t\tafter: 0,\n\t\t\t\tawareness: null,\n\t\t\t\tclient_id: state.clientId,\n\t\t\t\troom,\n\t\t\t\tupdates: [],\n\t\t\t},\n\t\t];\n\n\t\tpostSyncUpdateNonBlocking( { rooms } );\n\t\tstate.unregister();\n\t\troomStates.delete( room );\n\t}\n\n\tif ( 0 === roomStates.size && areListenersRegistered ) {\n\t\twindow.removeEventListener( 'beforeunload', handleBeforeUnload );\n\t\twindow.removeEventListener( 'pagehide', handlePageHide );\n\t\tdocument.removeEventListener(\n\t\t\t'visibilitychange',\n\t\t\thandleVisibilityChange\n\t\t);\n\t\tareListenersRegistered = false;\n\t}\n}\n\nexport const pollingManager: PollingManager = {\n\tregisterRoom,\n\tunregisterRoom,\n};\n"], | ||
| "mappings": "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", | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport * as encoding from 'lib0/encoding';\nimport * as decoding from 'lib0/decoding';\nimport type { Awareness } from 'y-protocols/awareness';\nimport { removeAwarenessStates } from 'y-protocols/awareness';\nimport * as syncProtocol from 'y-protocols/sync';\n\n/**\n * Internal dependencies\n */\nimport type { ConnectionStatus } from '../../types';\nimport {\n\ttype AwarenessState,\n\ttype LocalAwarenessState,\n\ttype SyncPayload,\n\ttype SyncUpdate,\n\tSyncUpdateType,\n\ttype UpdateQueue,\n} from './types';\nimport {\n\tbase64ToUint8Array,\n\tcreateSyncUpdate,\n\tcreateUpdateQueue,\n\tpostSyncUpdate,\n\tpostSyncUpdateNonBlocking,\n} from './utils';\n\nconst POLLING_INTERVAL_IN_MS = 1000; // 1 second or 1000 milliseconds\nconst POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS = 250; // 250 milliseconds\n// Must be less than the server-side AWARENESS_TIMEOUT (30 s) to avoid\n// false disconnects when the tab is in the background.\nconst POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 25 * 1000; // 25 seconds\nconst MAX_ERROR_BACKOFF_IN_MS = 30 * 1000; // 30 seconds\nconst POLLING_MANAGER_ORIGIN = 'polling-manager';\n\ntype LogFunction = ( message: string, debug?: object ) => void;\n\ninterface PollingManager {\n\tregisterRoom: ( options: RegisterRoomOptions ) => void;\n\tretryNow: () => void;\n\tunregisterRoom: ( room: string ) => void;\n}\n\ninterface RegisterRoomOptions {\n\troom: string;\n\tdoc: Y.Doc;\n\tawareness: Awareness;\n\tlog: LogFunction;\n\tonStatusChange: ( status: ConnectionStatus ) => void;\n\tonSync: () => void;\n}\n\ninterface RoomState {\n\tclientId: number;\n\tcreateCompactionUpdate: () => SyncUpdate;\n\tendCursor: number;\n\tlocalAwarenessState: LocalAwarenessState;\n\tlog: LogFunction;\n\tonStatusChange: ( status: ConnectionStatus ) => void;\n\tprocessAwarenessUpdate: ( state: AwarenessState ) => void;\n\tprocessDocUpdate: ( update: SyncUpdate ) => SyncUpdate | void;\n\tunregister: () => void;\n\tupdateQueue: UpdateQueue;\n}\n\nconst roomStates: Map< string, RoomState > = new Map();\n\n/**\n * Create a compaction update by merging existing updates. This preserves\n * the original operation metadata (client IDs, logical clocks) so that\n * Yjs deduplication works correctly when the compaction is applied.\n *\n * Deprecated: The server is moving towards full state updates for compaction.\n *\n * @param updates The updates to merge\n */\nfunction createDeprecatedCompactionUpdate( updates: SyncUpdate[] ): SyncUpdate {\n\t// Extract only compaction and update types for merging (skip sync-step updates).\n\t// Decode base64 updates to Uint8Array for merging.\n\tconst mergeable = updates\n\t\t.filter( ( u ) =>\n\t\t\t[ SyncUpdateType.COMPACTION, SyncUpdateType.UPDATE ].includes(\n\t\t\t\tu.type\n\t\t\t)\n\t\t)\n\t\t.map( ( u ) => base64ToUint8Array( u.data ) );\n\n\t// Merge all updates while preserving operation metadata.\n\treturn createSyncUpdate(\n\t\tY.mergeUpdates( mergeable ),\n\t\tSyncUpdateType.COMPACTION\n\t);\n}\n\n/**\n * Create sync step 1 update (announce our state vector).\n *\n * @param doc The Yjs document\n */\nfunction createSyncStep1Update( doc: Y.Doc ): SyncUpdate {\n\tconst encoder = encoding.createEncoder();\n\tsyncProtocol.writeSyncStep1( encoder, doc );\n\treturn createSyncUpdate(\n\t\tencoding.toUint8Array( encoder ),\n\t\tSyncUpdateType.SYNC_STEP_1\n\t);\n}\n\n/**\n * Create sync step 2 update (acknowledge sync step 1).\n *\n * @param doc The Yjs document\n * @param step1 The sync step 1 update received\n */\nfunction createSyncStep2Update( doc: Y.Doc, step1: Uint8Array ): SyncUpdate {\n\tconst decoder = decoding.createDecoder( step1 );\n\tconst encoder = encoding.createEncoder();\n\tsyncProtocol.readSyncMessage(\n\t\tdecoder,\n\t\tencoder,\n\t\tdoc,\n\t\tPOLLING_MANAGER_ORIGIN\n\t);\n\treturn createSyncUpdate(\n\t\tencoding.toUint8Array( encoder ),\n\t\tSyncUpdateType.SYNC_STEP_2\n\t);\n}\n\n/**\n * Process an incoming awareness update from the server.\n *\n * @param state The awareness state received\n * @param awareness The local Awareness instance\n */\nfunction processAwarenessUpdate(\n\tstate: AwarenessState,\n\tawareness: Awareness\n): void {\n\tconst currentStates = awareness.getStates();\n\tconst added = new Set< number >();\n\tconst updated = new Set< number >();\n\n\t// Removed clients are missing from the server state.\n\tconst removed = new Set< number >(\n\t\tcurrentStates.keys().filter( ( clientId ) => ! state[ clientId ] )\n\t);\n\n\tObject.entries( state ).forEach( ( [ clientIdString, awarenessState ] ) => {\n\t\tconst clientId = Number( clientIdString );\n\n\t\t// Skip our own state (we already have it locally).\n\t\tif ( clientId === awareness.clientID ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// A null state should be removed by the server, but handle it here just in case.\n\t\tif ( null === awarenessState ) {\n\t\t\tcurrentStates.delete( clientId );\n\t\t\tremoved.add( clientId );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( ! currentStates.has( clientId ) ) {\n\t\t\tcurrentStates.set( clientId, awarenessState );\n\t\t\tadded.add( clientId );\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentState = currentStates.get( clientId );\n\n\t\tif (\n\t\t\tJSON.stringify( currentState ) !== JSON.stringify( awarenessState )\n\t\t) {\n\t\t\tcurrentStates.set( clientId, awarenessState );\n\t\t\tupdated.add( clientId );\n\t\t}\n\t} );\n\n\tif ( added.size + updated.size > 0 ) {\n\t\tawareness.emit( 'change', [\n\t\t\t{\n\t\t\t\tadded: Array.from( added ),\n\t\t\t\tupdated: Array.from( updated ),\n\t\t\t\t// Left blank on purpose, as the removal of clients is handled in the if condition below.\n\t\t\t\tremoved: [],\n\t\t\t},\n\t\t] );\n\t}\n\n\tif ( removed.size > 0 ) {\n\t\tremoveAwarenessStates(\n\t\t\tawareness,\n\t\t\tArray.from( removed ),\n\t\t\tPOLLING_MANAGER_ORIGIN\n\t\t);\n\t}\n}\n\n/**\n * Process an incoming sync / document update based on its type.\n *\n * @param update The typed update received\n * @param doc The Yjs document\n * @param onSync Callback when sync is complete\n * @return A response update if needed (e.g., sync_step2 in response to sync_step1)\n */\nfunction processDocUpdate(\n\tupdate: SyncUpdate,\n\tdoc: Y.Doc,\n\tonSync: () => void\n): SyncUpdate | void {\n\tconst data = base64ToUint8Array( update.data );\n\n\tswitch ( update.type ) {\n\t\tcase SyncUpdateType.SYNC_STEP_1: {\n\t\t\t// Respond to sync step 1 with sync step 2.\n\t\t\treturn createSyncStep2Update( doc, data );\n\t\t}\n\n\t\tcase SyncUpdateType.SYNC_STEP_2: {\n\t\t\t// Apply sync step 2 (potentially contains missing updates).\n\t\t\tconst decoder = decoding.createDecoder( data );\n\t\t\tconst encoder = encoding.createEncoder();\n\t\t\tsyncProtocol.readSyncMessage(\n\t\t\t\tdecoder,\n\t\t\t\tencoder,\n\t\t\t\tdoc,\n\t\t\t\tPOLLING_MANAGER_ORIGIN\n\t\t\t);\n\t\t\tonSync();\n\t\t\treturn;\n\t\t}\n\n\t\tcase SyncUpdateType.COMPACTION:\n\t\tcase SyncUpdateType.UPDATE: {\n\t\t\t// Apply document update directly.\n\t\t\tY.applyUpdate( doc, data, POLLING_MANAGER_ORIGIN );\n\t\t}\n\t}\n}\n\nlet areListenersRegistered = false;\nlet hasCollaborators = false;\nlet isActiveBrowser = 'visible' === document.visibilityState;\nlet isPolling = false;\nlet isUnloadPending = false;\nlet pollInterval = POLLING_INTERVAL_IN_MS;\nlet pollingTimeoutId: ReturnType< typeof setTimeout > | null = null;\n\n/**\n * Mark that a page unload has been requested. This fires on\n * `beforeunload` which happens before the browser aborts in-flight\n * fetches, allowing us to distinguish poll failures caused by\n * navigation from genuine server errors in the catch block.\n *\n * If the user cancels the unload (e.g. by dismissing a \"Save Changes?\" dialog),\n * the flag is reset at the start of the next poll cycle so that polling can\n * resume.\n */\nfunction handleBeforeUnload(): void {\n\tisUnloadPending = true;\n}\n\n/**\n * Send a disconnect signal for all registered rooms when the page is\n * being unloaded. Uses `sendBeacon` so the request survives navigation.\n */\nfunction handlePageHide(): void {\n\tconst rooms = Array.from( roomStates.entries() ).map(\n\t\t( [ room, state ] ) => ( {\n\t\t\tafter: 0,\n\t\t\tawareness: null,\n\t\t\tclient_id: state.clientId,\n\t\t\troom,\n\t\t\tupdates: [],\n\t\t} )\n\t);\n\n\tpostSyncUpdateNonBlocking( { rooms } );\n}\n\n/**\n * Hangle change in visibility state of browser tab.\n *\n * Used to trigger a slow down of the collaboration syncs when the\n * browser tab becomes inactive (either the user switches tabs or the\n * screen saver comes on).\n *\n * Fires on the document's visibilitychange event.\n */\nfunction handleVisibilityChange() {\n\tconst wasActive = isActiveBrowser;\n\tisActiveBrowser = document.visibilityState === 'visible';\n\n\tif ( isActiveBrowser && ! wasActive ) {\n\t\t/*\n\t\t * Remove scheduled polling and repoll immediately when reactivated.\n\t\t *\n\t\t * This ensures that any updates by collaborators are immediately\n\t\t * reflected in the document once the browser tab becomes active.\n\t\t * Otherwise there would be a delay of up to 30 seconds before the\n\t\t * updates came through.\n\t\t *\n\t\t * Only repoll if we cleared a pending timeout, meaning the poll loop\n\t\t * was idle between cycles. If no timeout is pending, a poll request\n\t\t * is already in-flight and will pick up the updated isActiveBrowser\n\t\t * value when it schedules the next cycle.\n\t\t */\n\t\tif ( pollingTimeoutId ) {\n\t\t\tclearTimeout( pollingTimeoutId );\n\t\t\tpollingTimeoutId = null;\n\t\t\tpoll();\n\t\t}\n\t}\n}\n\nfunction poll(): void {\n\tisPolling = true;\n\tpollingTimeoutId = null;\n\n\tasync function start(): Promise< void > {\n\t\tif ( 0 === roomStates.size ) {\n\t\t\tisPolling = false;\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the unloading flag at the start of each poll cycle so\n\t\t// it doesn't permanently suppress disconnect after the user\n\t\t// cancels a beforeunload dialog.\n\t\tisUnloadPending = false;\n\n\t\t// Emit 'connecting' status.\n\t\troomStates.forEach( ( state ) => {\n\t\t\tstate.onStatusChange( { status: 'connecting' } );\n\t\t} );\n\n\t\t// Create a payload with all queued updates. We include rooms even if they\n\t\t// have no updates to ensure we receive any incoming updates. Note that we\n\t\t// withhold our own updates until we detect another collaborator using the\n\t\t// queue's pause / resume mechanism.\n\t\tconst payload: SyncPayload = {\n\t\t\trooms: Array.from( roomStates.entries() ).map(\n\t\t\t\t( [ room, state ] ) => ( {\n\t\t\t\t\tafter: state.endCursor ?? 0,\n\t\t\t\t\tawareness: state.localAwarenessState,\n\t\t\t\t\tclient_id: state.clientId,\n\t\t\t\t\troom,\n\t\t\t\t\tupdates: state.updateQueue.get(),\n\t\t\t\t} )\n\t\t\t),\n\t\t};\n\n\t\ttry {\n\t\t\tconst { rooms } = await postSyncUpdate( payload );\n\n\t\t\t// Emit 'connected' status.\n\t\t\troomStates.forEach( ( state ) => {\n\t\t\t\tstate.onStatusChange( { status: 'connected' } );\n\t\t\t} );\n\n\t\t\trooms.forEach( ( room ) => {\n\t\t\t\tif ( ! roomStates.has( room.room ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst roomState = roomStates.get( room.room )!;\n\t\t\t\troomState.endCursor = room.end_cursor;\n\n\t\t\t\t// Process awareness update.\n\t\t\t\troomState.processAwarenessUpdate( room.awareness );\n\n\t\t\t\t// If there is another collaborator, resume the queue for the next poll\n\t\t\t\t// and increase polling frequency.\n\t\t\t\tif ( Object.keys( room.awareness ).length > 1 ) {\n\t\t\t\t\thasCollaborators = true;\n\t\t\t\t\troomState.updateQueue.resume();\n\t\t\t\t}\n\n\t\t\t\t// Process each incoming update and collect any responses.\n\t\t\t\tconst responseUpdates = room.updates\n\t\t\t\t\t.map( ( update ) => roomState.processDocUpdate( update ) )\n\t\t\t\t\t.filter( ( update ): update is SyncUpdate =>\n\t\t\t\t\t\tBoolean( update )\n\t\t\t\t\t);\n\t\t\t\troomState.updateQueue.addBulk( responseUpdates );\n\n\t\t\t\t// Respond to compaction requests from server. The server asks only one\n\t\t\t\t// client at a time to compact (lowest active client ID). We encode our\n\t\t\t\t// full document state to replace all prior updates on the server.\n\t\t\t\tif ( room.should_compact ) {\n\t\t\t\t\troomState.log( 'Server requested compaction update' );\n\t\t\t\t\troomState.updateQueue.clear();\n\t\t\t\t\troomState.updateQueue.add(\n\t\t\t\t\t\troomState.createCompactionUpdate()\n\t\t\t\t\t);\n\t\t\t\t} else if ( room.compaction_request ) {\n\t\t\t\t\t// Deprecated\n\t\t\t\t\troomState.log( 'Server requested (old) compaction update' );\n\t\t\t\t\troomState.updateQueue.add(\n\t\t\t\t\t\tcreateDeprecatedCompactionUpdate(\n\t\t\t\t\t\t\troom.compaction_request\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t// Recalculate polling interval.\n\t\t\tif ( isActiveBrowser && hasCollaborators ) {\n\t\t\t\tpollInterval = POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS;\n\t\t\t} else if ( isActiveBrowser ) {\n\t\t\t\tpollInterval = POLLING_INTERVAL_IN_MS;\n\t\t\t} else {\n\t\t\t\tpollInterval = POLLING_INTERVAL_BACKGROUND_TAB_IN_MS;\n\t\t\t}\n\t\t} catch ( error ) {\n\t\t\t// Exponential backoff on error: double the backoff time, up to max\n\t\t\tpollInterval = Math.min(\n\t\t\t\tpollInterval * 2,\n\t\t\t\tMAX_ERROR_BACKOFF_IN_MS\n\t\t\t);\n\n\t\t\t// Restore updates to queues on failure so they can be retried.\n\t\t\tfor ( const room of payload.rooms ) {\n\t\t\t\tif ( ! roomStates.has( room.room ) ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst state = roomStates.get( room.room )!;\n\t\t\t\tstate.updateQueue.restore( room.updates );\n\t\t\t\tstate.log(\n\t\t\t\t\t'Error posting sync update, will retry with backoff',\n\t\t\t\t\t{\n\t\t\t\t\t\terror,\n\t\t\t\t\t\tnextPoll: pollInterval,\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Don't report disconnected status when the request was aborted\n\t\t\t// due to page unload (e.g. during a refresh) to avoid briefly\n\t\t\t// flashing the disconnect dialog before the new page loads.\n\t\t\tif ( ! isUnloadPending ) {\n\t\t\t\troomStates.forEach( ( state ) => {\n\t\t\t\t\tstate.onStatusChange( {\n\t\t\t\t\t\tstatus: 'disconnected',\n\t\t\t\t\t\tretryInMs: pollInterval,\n\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\tpollingTimeoutId = setTimeout( poll, pollInterval );\n\t}\n\n\t// Start polling.\n\tvoid start();\n}\nfunction registerRoom( {\n\troom,\n\tdoc,\n\tawareness,\n\tlog,\n\tonSync,\n\tonStatusChange,\n}: RegisterRoomOptions ): void {\n\tif ( roomStates.has( room ) ) {\n\t\treturn;\n\t}\n\n\t// Note: Queue is initially paused. Call .resume() to unpause.\n\tconst updateQueue = createUpdateQueue( [ createSyncStep1Update( doc ) ] );\n\n\tfunction onAwarenessUpdate(): void {\n\t\troomState.localAwarenessState = awareness.getLocalState() ?? {};\n\t}\n\n\tfunction onDocUpdate( update: Uint8Array, origin: unknown ): void {\n\t\tif ( POLLING_MANAGER_ORIGIN === origin ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Tag local document changes as 'update' type.\n\t\tupdateQueue.add( createSyncUpdate( update, SyncUpdateType.UPDATE ) );\n\t}\n\n\tfunction unregister(): void {\n\t\tdoc.off( 'update', onDocUpdate );\n\t\tawareness.off( 'change', onAwarenessUpdate );\n\t\tupdateQueue.clear();\n\t}\n\n\tconst roomState: RoomState = {\n\t\tclientId: doc.clientID,\n\t\tcreateCompactionUpdate: () =>\n\t\t\tcreateSyncUpdate(\n\t\t\t\tY.encodeStateAsUpdate( doc ),\n\t\t\t\tSyncUpdateType.COMPACTION\n\t\t\t),\n\t\tendCursor: 0,\n\t\tlocalAwarenessState: awareness.getLocalState() ?? {},\n\t\tlog,\n\t\tonStatusChange,\n\t\tprocessAwarenessUpdate: ( state: AwarenessState ) =>\n\t\t\tprocessAwarenessUpdate( state, awareness ),\n\t\tprocessDocUpdate: ( update: SyncUpdate ) =>\n\t\t\tprocessDocUpdate( update, doc, onSync ),\n\t\tunregister,\n\t\tupdateQueue,\n\t};\n\n\tdoc.on( 'update', onDocUpdate );\n\tawareness.on( 'change', onAwarenessUpdate );\n\troomStates.set( room, roomState );\n\n\tif ( ! areListenersRegistered ) {\n\t\twindow.addEventListener( 'beforeunload', handleBeforeUnload );\n\t\twindow.addEventListener( 'pagehide', handlePageHide );\n\t\tdocument.addEventListener( 'visibilitychange', handleVisibilityChange );\n\t\tareListenersRegistered = true;\n\t}\n\n\tif ( ! isPolling ) {\n\t\tpoll();\n\t}\n}\n\nfunction unregisterRoom( room: string ): void {\n\tconst state = roomStates.get( room );\n\tif ( state ) {\n\t\t// Send a disconnect signal so the server removes this client's\n\t\t// awareness entry immediately instead of waiting for the timeout.\n\t\tconst rooms = [\n\t\t\t{\n\t\t\t\tafter: 0,\n\t\t\t\tawareness: null,\n\t\t\t\tclient_id: state.clientId,\n\t\t\t\troom,\n\t\t\t\tupdates: [],\n\t\t\t},\n\t\t];\n\n\t\tpostSyncUpdateNonBlocking( { rooms } );\n\t\tstate.unregister();\n\t\troomStates.delete( room );\n\t}\n\n\tif ( 0 === roomStates.size && areListenersRegistered ) {\n\t\twindow.removeEventListener( 'beforeunload', handleBeforeUnload );\n\t\twindow.removeEventListener( 'pagehide', handlePageHide );\n\t\tdocument.removeEventListener(\n\t\t\t'visibilitychange',\n\t\t\thandleVisibilityChange\n\t\t);\n\t\tareListenersRegistered = false;\n\t}\n}\n\n/**\n * Immediately retry the sync connection by cancelling any pending backoff\n * timeout and triggering a new poll. If a request is already in-flight,\n * the backoff interval is reset so the next scheduled poll fires sooner.\n */\nfunction retryNow(): void {\n\tpollInterval = POLLING_INTERVAL_IN_MS * 2;\n\n\tif ( pollingTimeoutId ) {\n\t\tclearTimeout( pollingTimeoutId );\n\t\tpollingTimeoutId = null;\n\t\tpoll();\n\t}\n}\n\nexport const pollingManager: PollingManager = {\n\tregisterRoom,\n\tretryNow,\n\tunregisterRoom,\n};\n"], | ||
| "mappings": "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", | ||
| "names": [] | ||
| } |
@@ -48,2 +48,8 @@ "use strict"; | ||
| var NULL_CHARACTER = String.fromCharCode(0); | ||
| function normalizeChangeCounts(changes) { | ||
| return changes.map((change) => ({ | ||
| ...change, | ||
| count: change.value.length | ||
| })); | ||
| } | ||
| var getEmbedTypeAndData = (a, b) => { | ||
@@ -310,3 +316,5 @@ if (typeof a !== "object" || a === null) { | ||
| const strings = this.deltasToStrings(other); | ||
| const diffResult = (0, import_diff.diffChars)(strings[0], strings[1]); | ||
| const diffResult = normalizeChangeCounts( | ||
| (0, import_diff.diffChars)(strings[0], strings[1]) | ||
| ); | ||
| const thisIter = new import_OpIterator.default(this.ops); | ||
@@ -481,3 +489,5 @@ const otherIter = new import_OpIterator.default(other.ops); | ||
| const strings = this.deltasToStrings(other); | ||
| let diffs = (0, import_diff.diffChars)(strings[0], strings[1]); | ||
| let diffs = normalizeChangeCounts( | ||
| (0, import_diff.diffChars)(strings[0], strings[1]) | ||
| ); | ||
| let lastDiffPosition = 0; | ||
@@ -484,0 +494,0 @@ const adjustedDiffs = []; |
| { | ||
| "version": 3, | ||
| "sources": ["../../src/quill-delta/Delta.ts"], | ||
| "sourcesContent": ["// File copied https://github.com/slab/delta/blob/main/src/Delta.ts with changes:\n// - fast-diff swapped out for 'diff',\n// - lodash.clonedeep is replaced with JSON parse / stringify\n// - lodash.isequal is replaced with fast-deep-equal.\n\n// @ts-ignore\n/**\n * External dependencies\n */\nimport type { Change } from 'diff';\nimport { diffChars } from 'diff';\nimport { default as isEqual } from 'fast-deep-equal/es6';\n\n/**\n * Internal dependencies\n */\nimport AttributeMap from './AttributeMap';\nimport Op from './Op';\nimport OpIterator from './OpIterator';\n\nfunction cloneDeep< T >( value: T ): T {\n\treturn JSON.parse( JSON.stringify( value ) ) as T;\n}\n\nconst NULL_CHARACTER = String.fromCharCode( 0 ); // Placeholder char for embed in diff()\n\ninterface EmbedHandler< T > {\n\tcompose: ( a: T, b: T, keepNull: boolean ) => T;\n\tinvert: ( a: T, b: T ) => T;\n\ttransform: ( a: T, b: T, priority: boolean ) => T;\n}\n\nconst getEmbedTypeAndData = (\n\ta: Op[ 'insert' ] | Op[ 'retain' ],\n\tb: Op[ 'insert' ]\n): [ string, unknown, unknown ] => {\n\tif ( typeof a !== 'object' || a === null ) {\n\t\tthrow new Error( `cannot retain a ${ typeof a }` );\n\t}\n\tif ( typeof b !== 'object' || b === null ) {\n\t\tthrow new Error( `cannot retain a ${ typeof b }` );\n\t}\n\tconst embedType = Object.keys( a )[ 0 ];\n\tif ( ! embedType || embedType !== Object.keys( b )[ 0 ] ) {\n\t\tthrow new Error(\n\t\t\t`embed types not matched: ${ embedType } != ${\n\t\t\t\tObject.keys( b )[ 0 ]\n\t\t\t}`\n\t\t);\n\t}\n\treturn [ embedType, a[ embedType ], b[ embedType ] ];\n};\n\nclass Delta {\n\tstatic Op = Op;\n\tstatic OpIterator = OpIterator;\n\tstatic AttributeMap = AttributeMap;\n\tprivate static handlers: {\n\t\t[ embedType: string ]: EmbedHandler< unknown >;\n\t} = {};\n\n\tstatic registerEmbed< T >(\n\t\tembedType: string,\n\t\thandler: EmbedHandler< T >\n\t): void {\n\t\tthis.handlers[ embedType ] = handler as EmbedHandler< unknown >;\n\t}\n\n\tstatic unregisterEmbed( embedType: string ): void {\n\t\tdelete this.handlers[ embedType ];\n\t}\n\n\tprivate static getHandler( embedType: string ): EmbedHandler< unknown > {\n\t\tconst handler = this.handlers[ embedType ];\n\t\tif ( ! handler ) {\n\t\t\tthrow new Error( `no handlers for embed type \"${ embedType }\"` );\n\t\t}\n\t\treturn handler;\n\t}\n\n\tops: Op[];\n\tconstructor( ops?: Op[] | { ops: Op[] } ) {\n\t\t// Assume we are given a well formed ops\n\t\tif ( Array.isArray( ops ) ) {\n\t\t\tthis.ops = ops;\n\t\t} else if (\n\t\t\tops !== null &&\n\t\t\tops !== undefined &&\n\t\t\tArray.isArray( ops.ops )\n\t\t) {\n\t\t\tthis.ops = ops.ops;\n\t\t} else {\n\t\t\tthis.ops = [];\n\t\t}\n\t}\n\n\tinsert(\n\t\targ: string | Record< string, unknown >,\n\t\tattributes?: AttributeMap | null\n\t): this {\n\t\tconst newOp: Op = {};\n\t\tif ( typeof arg === 'string' && arg.length === 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\tnewOp.insert = arg;\n\t\tif (\n\t\t\tattributes !== null &&\n\t\t\tattributes !== undefined &&\n\t\t\ttypeof attributes === 'object' &&\n\t\t\tObject.keys( attributes ).length > 0\n\t\t) {\n\t\t\tnewOp.attributes = attributes;\n\t\t}\n\t\treturn this.push( newOp );\n\t}\n\n\tdelete( length: number ): this {\n\t\tif ( length <= 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\treturn this.push( { delete: length } );\n\t}\n\n\tretain(\n\t\tlength: number | Record< string, unknown >,\n\t\tattributes?: AttributeMap | null\n\t): this {\n\t\tif ( typeof length === 'number' && length <= 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\tconst newOp: Op = { retain: length };\n\t\tif (\n\t\t\tattributes !== null &&\n\t\t\tattributes !== undefined &&\n\t\t\ttypeof attributes === 'object' &&\n\t\t\tObject.keys( attributes ).length > 0\n\t\t) {\n\t\t\tnewOp.attributes = attributes;\n\t\t}\n\t\treturn this.push( newOp );\n\t}\n\n\tpush( newOp: Op ): this {\n\t\tlet index = this.ops.length;\n\t\tlet lastOp = this.ops[ index - 1 ];\n\t\tnewOp = cloneDeep( newOp );\n\t\tif ( typeof lastOp === 'object' ) {\n\t\t\tif (\n\t\t\t\ttypeof newOp.delete === 'number' &&\n\t\t\t\ttypeof lastOp.delete === 'number'\n\t\t\t) {\n\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\tdelete: lastOp.delete + newOp.delete,\n\t\t\t\t};\n\t\t\t\treturn this;\n\t\t\t}\n\t\t\t// Since it does not matter if we insert before or after deleting at the same index,\n\t\t\t// always prefer to insert first\n\t\t\tif (\n\t\t\t\ttypeof lastOp.delete === 'number' &&\n\t\t\t\tnewOp.insert !== null &&\n\t\t\t\tnewOp.insert !== undefined\n\t\t\t) {\n\t\t\t\tindex -= 1;\n\t\t\t\tlastOp = this.ops[ index - 1 ];\n\t\t\t\tif ( typeof lastOp !== 'object' ) {\n\t\t\t\t\tthis.ops.unshift( newOp );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( isEqual( newOp.attributes, lastOp.attributes ) ) {\n\t\t\t\tif (\n\t\t\t\t\ttypeof newOp.insert === 'string' &&\n\t\t\t\t\ttypeof lastOp.insert === 'string'\n\t\t\t\t) {\n\t\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\t\tinsert: lastOp.insert + newOp.insert,\n\t\t\t\t\t};\n\t\t\t\t\tif ( typeof newOp.attributes === 'object' ) {\n\t\t\t\t\t\tthis.ops[ index - 1 ].attributes = newOp.attributes;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof newOp.retain === 'number' &&\n\t\t\t\t\ttypeof lastOp.retain === 'number'\n\t\t\t\t) {\n\t\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\t\tretain: lastOp.retain + newOp.retain,\n\t\t\t\t\t};\n\t\t\t\t\tif ( typeof newOp.attributes === 'object' ) {\n\t\t\t\t\t\tthis.ops[ index - 1 ].attributes = newOp.attributes;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif ( index === this.ops.length ) {\n\t\t\tthis.ops.push( newOp );\n\t\t} else {\n\t\t\tthis.ops.splice( index, 0, newOp );\n\t\t}\n\t\treturn this;\n\t}\n\n\tchop(): this {\n\t\tconst lastOp = this.ops[ this.ops.length - 1 ];\n\t\tif (\n\t\t\tlastOp &&\n\t\t\ttypeof lastOp.retain === 'number' &&\n\t\t\t! lastOp.attributes\n\t\t) {\n\t\t\tthis.ops.pop();\n\t\t}\n\t\treturn this;\n\t}\n\n\tfilter( predicate: ( op: Op, index: number ) => boolean ): Op[] {\n\t\treturn this.ops.filter( predicate );\n\t}\n\n\tforEach( predicate: ( op: Op, index: number ) => void ): void {\n\t\tthis.ops.forEach( predicate );\n\t}\n\n\tmap< T >( predicate: ( op: Op, index: number ) => T ): T[] {\n\t\treturn this.ops.map( predicate );\n\t}\n\n\tpartition( predicate: ( op: Op ) => boolean ): [ Op[], Op[] ] {\n\t\tconst passed: Op[] = [];\n\t\tconst failed: Op[] = [];\n\t\tthis.forEach( ( op ) => {\n\t\t\tconst target = predicate( op ) ? passed : failed;\n\t\t\ttarget.push( op );\n\t\t} );\n\t\treturn [ passed, failed ];\n\t}\n\n\treduce< T >(\n\t\tpredicate: ( accum: T, curr: Op, index: number ) => T,\n\t\tinitialValue: T\n\t): T {\n\t\treturn this.ops.reduce( predicate, initialValue );\n\t}\n\n\tchangeLength(): number {\n\t\treturn this.reduce( ( length, elem ) => {\n\t\t\tif ( elem.insert ) {\n\t\t\t\treturn length + Op.length( elem );\n\t\t\t} else if ( elem.delete ) {\n\t\t\t\treturn length - elem.delete;\n\t\t\t}\n\t\t\treturn length;\n\t\t}, 0 );\n\t}\n\n\tlength(): number {\n\t\treturn this.reduce( ( length, elem ) => {\n\t\t\treturn length + Op.length( elem );\n\t\t}, 0 );\n\t}\n\n\tslice( start = 0, end = Infinity ): Delta {\n\t\tconst ops = [];\n\t\tconst iter = new OpIterator( this.ops );\n\t\tlet index = 0;\n\t\twhile ( index < end && iter.hasNext() ) {\n\t\t\tlet nextOp;\n\t\t\tif ( index < start ) {\n\t\t\t\tnextOp = iter.next( start - index );\n\t\t\t} else {\n\t\t\t\tnextOp = iter.next( end - index );\n\t\t\t\tops.push( nextOp );\n\t\t\t}\n\t\t\tindex += Op.length( nextOp );\n\t\t}\n\t\treturn new Delta( ops );\n\t}\n\n\tcompose( other: Delta ): Delta {\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst ops = [];\n\t\tconst firstOther = otherIter.peek();\n\t\tif (\n\t\t\tfirstOther !== null &&\n\t\t\tfirstOther !== undefined &&\n\t\t\ttypeof firstOther.retain === 'number' &&\n\t\t\t( firstOther.attributes === null ||\n\t\t\t\tfirstOther.attributes === undefined )\n\t\t) {\n\t\t\tlet firstLeft = firstOther.retain;\n\t\t\twhile (\n\t\t\t\tthisIter.peekType() === 'insert' &&\n\t\t\t\tthisIter.peekLength() <= firstLeft\n\t\t\t) {\n\t\t\t\tfirstLeft -= thisIter.peekLength();\n\t\t\t\tops.push( thisIter.next() );\n\t\t\t}\n\t\t\tif ( firstOther.retain - firstLeft > 0 ) {\n\t\t\t\totherIter.next( firstOther.retain - firstLeft );\n\t\t\t}\n\t\t}\n\t\tconst delta = new Delta( ops );\n\t\twhile ( thisIter.hasNext() || otherIter.hasNext() ) {\n\t\t\tif ( otherIter.peekType() === 'insert' ) {\n\t\t\t\tdelta.push( otherIter.next() );\n\t\t\t} else if ( thisIter.peekType() === 'delete' ) {\n\t\t\t\tdelta.push( thisIter.next() );\n\t\t\t} else {\n\t\t\t\tconst length = Math.min(\n\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\totherIter.peekLength()\n\t\t\t\t);\n\t\t\t\tconst thisOp = thisIter.next( length );\n\t\t\t\tconst otherOp = otherIter.next( length );\n\t\t\t\tif ( otherOp.retain ) {\n\t\t\t\t\tconst newOp: Op = {};\n\t\t\t\t\tif ( typeof thisOp.retain === 'number' ) {\n\t\t\t\t\t\tnewOp.retain =\n\t\t\t\t\t\t\ttypeof otherOp.retain === 'number'\n\t\t\t\t\t\t\t\t? length\n\t\t\t\t\t\t\t\t: otherOp.retain;\n\t\t\t\t\t} else if ( typeof otherOp.retain === 'number' ) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tthisOp.retain === null ||\n\t\t\t\t\t\t\tthisOp.retain === undefined\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tnewOp.insert = thisOp.insert;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnewOp.retain = thisOp.retain;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst action =\n\t\t\t\t\t\t\tthisOp.retain === null ||\n\t\t\t\t\t\t\tthisOp.retain === undefined\n\t\t\t\t\t\t\t\t? 'insert'\n\t\t\t\t\t\t\t\t: 'retain';\n\t\t\t\t\t\tconst [ embedType, thisData, otherData ] =\n\t\t\t\t\t\t\tgetEmbedTypeAndData(\n\t\t\t\t\t\t\t\tthisOp[ action ],\n\t\t\t\t\t\t\t\totherOp.retain\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\t\t\tnewOp[ action ] = {\n\t\t\t\t\t\t\t[ embedType ]: handler.compose(\n\t\t\t\t\t\t\t\tthisData,\n\t\t\t\t\t\t\t\totherData,\n\t\t\t\t\t\t\t\taction === 'retain'\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Preserve null when composing with a retain, otherwise remove it for inserts\n\t\t\t\t\tconst attributes = AttributeMap.compose(\n\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\totherOp.attributes,\n\t\t\t\t\t\ttypeof thisOp.retain === 'number'\n\t\t\t\t\t);\n\t\t\t\t\tif ( attributes ) {\n\t\t\t\t\t\tnewOp.attributes = attributes;\n\t\t\t\t\t}\n\t\t\t\t\tdelta.push( newOp );\n\n\t\t\t\t\t// Optimization if rest of other is just retain\n\t\t\t\t\tif (\n\t\t\t\t\t\t! otherIter.hasNext() &&\n\t\t\t\t\t\tisEqual( delta.ops[ delta.ops.length - 1 ], newOp )\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst rest = new Delta( thisIter.rest() );\n\t\t\t\t\t\treturn delta.concat( rest ).chop();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Other op should be delete, we could be an insert or retain\n\t\t\t\t\t// Insert + delete cancels out\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof otherOp.delete === 'number' &&\n\t\t\t\t\t( typeof thisOp.retain === 'number' ||\n\t\t\t\t\t\t( typeof thisOp.retain === 'object' &&\n\t\t\t\t\t\t\tthisOp.retain !== null ) )\n\t\t\t\t) {\n\t\t\t\t\tdelta.push( otherOp );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn delta.chop();\n\t}\n\n\tconcat( other: Delta ): Delta {\n\t\tconst delta = new Delta( this.ops.slice() );\n\t\tif ( other.ops.length > 0 ) {\n\t\t\tdelta.push( other.ops[ 0 ] );\n\t\t\tdelta.ops = delta.ops.concat( other.ops.slice( 1 ) );\n\t\t}\n\t\treturn delta;\n\t}\n\n\tdiff( other: Delta ): Delta {\n\t\tif ( this.ops === other.ops ) {\n\t\t\treturn new Delta();\n\t\t}\n\t\tconst strings = this.deltasToStrings( other );\n\t\tconst diffResult = diffChars( strings[ 0 ], strings[ 1 ] );\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst retDelta = this.convertChangesToDelta(\n\t\t\tdiffResult,\n\t\t\tthisIter,\n\t\t\totherIter\n\t\t);\n\n\t\treturn retDelta.chop();\n\t}\n\n\teachLine(\n\t\tpredicate: (\n\t\t\tline: Delta,\n\t\t\tattributes: AttributeMap,\n\t\t\tindex: number\n\t\t) => boolean | void,\n\t\tnewline = '\\n'\n\t): void {\n\t\tconst iter = new OpIterator( this.ops );\n\t\tlet line = new Delta();\n\t\tlet i = 0;\n\t\twhile ( iter.hasNext() ) {\n\t\t\tif ( iter.peekType() !== 'insert' ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst thisOp = iter.peek();\n\t\t\tconst start = Op.length( thisOp ) - iter.peekLength();\n\t\t\tconst index =\n\t\t\t\ttypeof thisOp.insert === 'string'\n\t\t\t\t\t? thisOp.insert.indexOf( newline, start ) - start\n\t\t\t\t\t: -1;\n\t\t\tif ( index < 0 ) {\n\t\t\t\tline.push( iter.next() );\n\t\t\t} else if ( index > 0 ) {\n\t\t\t\tline.push( iter.next( index ) );\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tpredicate( line, iter.next( 1 ).attributes || {}, i ) ===\n\t\t\t\t\tfalse\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ti += 1;\n\t\t\t\tline = new Delta();\n\t\t\t}\n\t\t}\n\t\tif ( line.length() > 0 ) {\n\t\t\tpredicate( line, {}, i );\n\t\t}\n\t}\n\n\tinvert( base: Delta ): Delta {\n\t\tconst inverted = new Delta();\n\t\tthis.reduce( ( baseIndex, op ) => {\n\t\t\tif ( op.insert ) {\n\t\t\t\tinverted.delete( Op.length( op ) );\n\t\t\t} else if (\n\t\t\t\ttypeof op.retain === 'number' &&\n\t\t\t\t( op.attributes === null || op.attributes === undefined )\n\t\t\t) {\n\t\t\t\tinverted.retain( op.retain );\n\t\t\t\treturn baseIndex + op.retain;\n\t\t\t} else if ( op.delete || typeof op.retain === 'number' ) {\n\t\t\t\tconst length = ( op.delete || op.retain ) as number;\n\t\t\t\tconst slice = base.slice( baseIndex, baseIndex + length );\n\t\t\t\tslice.forEach( ( baseOp ) => {\n\t\t\t\t\tif ( op.delete ) {\n\t\t\t\t\t\tinverted.push( baseOp );\n\t\t\t\t\t} else if ( op.retain && op.attributes ) {\n\t\t\t\t\t\tinverted.retain(\n\t\t\t\t\t\t\tOp.length( baseOp ),\n\t\t\t\t\t\t\tAttributeMap.invert(\n\t\t\t\t\t\t\t\top.attributes,\n\t\t\t\t\t\t\t\tbaseOp.attributes\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\treturn baseIndex + length;\n\t\t\t} else if ( typeof op.retain === 'object' && op.retain !== null ) {\n\t\t\t\tconst slice = base.slice( baseIndex, baseIndex + 1 );\n\t\t\t\tconst baseOp = new OpIterator( slice.ops ).next();\n\t\t\t\tconst [ embedType, opData, baseOpData ] = getEmbedTypeAndData(\n\t\t\t\t\top.retain,\n\t\t\t\t\tbaseOp.insert\n\t\t\t\t);\n\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\tinverted.retain(\n\t\t\t\t\t{ [ embedType ]: handler.invert( opData, baseOpData ) },\n\t\t\t\t\tAttributeMap.invert( op.attributes, baseOp.attributes )\n\t\t\t\t);\n\t\t\t\treturn baseIndex + 1;\n\t\t\t}\n\t\t\treturn baseIndex;\n\t\t}, 0 );\n\t\treturn inverted.chop();\n\t}\n\n\ttransform( index: number, priority?: boolean ): number;\n\ttransform( other: Delta, priority?: boolean ): Delta;\n\ttransform( arg: number | Delta, priority = false ): typeof arg {\n\t\tpriority = !! priority;\n\t\tif ( typeof arg === 'number' ) {\n\t\t\treturn this.transformPosition( arg, priority );\n\t\t}\n\t\tconst other: Delta = arg;\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst delta = new Delta();\n\t\twhile ( thisIter.hasNext() || otherIter.hasNext() ) {\n\t\t\tif (\n\t\t\t\tthisIter.peekType() === 'insert' &&\n\t\t\t\t( priority || otherIter.peekType() !== 'insert' )\n\t\t\t) {\n\t\t\t\tdelta.retain( Op.length( thisIter.next() ) );\n\t\t\t} else if ( otherIter.peekType() === 'insert' ) {\n\t\t\t\tdelta.push( otherIter.next() );\n\t\t\t} else {\n\t\t\t\tconst length = Math.min(\n\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\totherIter.peekLength()\n\t\t\t\t);\n\t\t\t\tconst thisOp = thisIter.next( length );\n\t\t\t\tconst otherOp = otherIter.next( length );\n\t\t\t\tif ( thisOp.delete ) {\n\t\t\t\t\t// Our delete either makes their delete redundant or removes their retain\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if ( otherOp.delete ) {\n\t\t\t\t\tdelta.push( otherOp );\n\t\t\t\t} else {\n\t\t\t\t\tconst thisData = thisOp.retain;\n\t\t\t\t\tconst otherData = otherOp.retain;\n\t\t\t\t\tlet transformedData: Op[ 'retain' ] =\n\t\t\t\t\t\ttypeof otherData === 'object' && otherData !== null\n\t\t\t\t\t\t\t? otherData\n\t\t\t\t\t\t\t: length;\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof thisData === 'object' &&\n\t\t\t\t\t\tthisData !== null &&\n\t\t\t\t\t\ttypeof otherData === 'object' &&\n\t\t\t\t\t\totherData !== null\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst embedType = Object.keys( thisData )[ 0 ];\n\t\t\t\t\t\tif ( embedType === Object.keys( otherData )[ 0 ] ) {\n\t\t\t\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\t\t\t\tif ( handler ) {\n\t\t\t\t\t\t\t\ttransformedData = {\n\t\t\t\t\t\t\t\t\t[ embedType ]: handler.transform(\n\t\t\t\t\t\t\t\t\t\tthisData[ embedType ],\n\t\t\t\t\t\t\t\t\t\totherData[ embedType ],\n\t\t\t\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// We retain either their retain or insert\n\t\t\t\t\tdelta.retain(\n\t\t\t\t\t\ttransformedData,\n\t\t\t\t\t\tAttributeMap.transform(\n\t\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\t\totherOp.attributes,\n\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn delta.chop();\n\t}\n\n\ttransformPosition( index: number, priority = false ): number {\n\t\tpriority = !! priority;\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tlet offset = 0;\n\t\twhile ( thisIter.hasNext() && offset <= index ) {\n\t\t\tconst length = thisIter.peekLength();\n\t\t\tconst nextType = thisIter.peekType();\n\t\t\tthisIter.next();\n\t\t\tif ( nextType === 'delete' ) {\n\t\t\t\tindex -= Math.min( length, index - offset );\n\t\t\t\tcontinue;\n\t\t\t} else if (\n\t\t\t\tnextType === 'insert' &&\n\t\t\t\t( offset < index || ! priority )\n\t\t\t) {\n\t\t\t\tindex += length;\n\t\t\t}\n\t\t\toffset += length;\n\t\t}\n\t\treturn index;\n\t}\n\n\t/**\n\t * Given a Delta and a cursor position, do a diff and attempt to adjust\n\t * the diff to place insertions or deletions at the cursor position.\n\t *\n\t * @param other - The other Delta to diff against.\n\t * @param cursorAfterChange - The cursor position index after the change.\n\t * @return A Delta that attempts to place insertions or deletions at the cursor position.\n\t */\n\tdiffWithCursor( other: Delta, cursorAfterChange: number | null ): Delta {\n\t\tif ( this.ops === other.ops ) {\n\t\t\treturn new Delta();\n\t\t} else if ( cursorAfterChange === null ) {\n\t\t\t// If no cursor position is provided, do a regular diff.\n\t\t\treturn this.diff( other );\n\t\t}\n\n\t\tconst strings = this.deltasToStrings( other );\n\t\tlet diffs = diffChars( strings[ 0 ], strings[ 1 ] );\n\t\tlet lastDiffPosition = 0;\n\t\tconst adjustedDiffs: Change[] = [];\n\n\t\tfor ( let i = 0; i < diffs.length; i++ ) {\n\t\t\tconst diff = diffs[ i ];\n\n\t\t\tconst segmentStart = lastDiffPosition;\n\t\t\tconst segmentEnd = lastDiffPosition + ( diff.count ?? 0 );\n\t\t\tconst isCursorInSegment =\n\t\t\t\tcursorAfterChange > segmentStart &&\n\t\t\t\tcursorAfterChange <= segmentEnd;\n\n\t\t\tconst isUnchangedSegment = ! diff.added && ! diff.removed;\n\t\t\tconst isRemovalSegment = diff.removed && ! diff.added;\n\n\t\t\tconst nextDiff = diffs[ i + 1 ];\n\t\t\tconst isNextDiffAnInsert =\n\t\t\t\tnextDiff && nextDiff.added && ! nextDiff.removed;\n\n\t\t\t// Path 1: Look-ahead strategy\n\t\t\t// If the position of the cursor is in an \"unchanged\" segment, but there's an insertion\n\t\t\t// right after this section, then the insertion has likely been placed in\n\t\t\t// the incorrect spot, and we can move the insertion to the position of the cursor.\n\t\t\tif (\n\t\t\t\tisUnchangedSegment &&\n\t\t\t\tisCursorInSegment &&\n\t\t\t\tisNextDiffAnInsert\n\t\t\t) {\n\t\t\t\tconst movedSegments = this.tryMoveInsertionToCursor(\n\t\t\t\t\tdiff,\n\t\t\t\t\tnextDiff,\n\t\t\t\t\tcursorAfterChange,\n\t\t\t\t\tsegmentStart\n\t\t\t\t);\n\n\t\t\t\tif ( movedSegments ) {\n\t\t\t\t\tadjustedDiffs.push( ...movedSegments );\n\t\t\t\t\t// Skip the next diff since we've already consumed it\n\t\t\t\t\ti++;\n\t\t\t\t\tlastDiffPosition = segmentEnd;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Path 2: Look-back strategy\n\t\t\t// Handle removals by checking if cursor was in the previous unchanged segment\n\t\t\tif ( isRemovalSegment ) {\n\t\t\t\tconst movedSegments = this.tryMoveDeletionToCursor(\n\t\t\t\t\tdiff,\n\t\t\t\t\tadjustedDiffs,\n\t\t\t\t\tcursorAfterChange,\n\t\t\t\t\tlastDiffPosition\n\t\t\t\t);\n\n\t\t\t\tif ( movedSegments ) {\n\t\t\t\t\t// Remove the previous unchanged segment from adjustedDiffs\n\t\t\t\t\tadjustedDiffs.pop();\n\t\t\t\t\tadjustedDiffs.push( ...movedSegments );\n\t\t\t\t\tlastDiffPosition += diff.count ?? 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Path 3: Do nothing - add diff as-is\n\t\t\tadjustedDiffs.push( diff );\n\t\t\tif ( ! diff.added ) {\n\t\t\t\tlastDiffPosition += diff.count ?? 0;\n\t\t\t}\n\t\t}\n\n\t\tdiffs = adjustedDiffs;\n\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst retDelta = this.convertChangesToDelta(\n\t\t\tdiffs,\n\t\t\tthisIter,\n\t\t\totherIter\n\t\t);\n\n\t\treturn retDelta.chop();\n\t}\n\n\t/**\n\t * Try to move an insertion operation from after an unchanged segment to the cursor position within it.\n\t * This is a \"look-ahead\" strategy.\n\t *\n\t * @param diff - The current unchanged diff segment.\n\t * @param nextDiff - The next diff segment (expected to be an insertion).\n\t * @param cursorAfterChange - The cursor position after the change.\n\t * @param segmentStart - The start position of the current segment.\n\t * @return An array of adjusted diff segments if the insertion was successfully moved, null otherwise.\n\t */\n\tprivate tryMoveInsertionToCursor(\n\t\tdiff: Change,\n\t\tnextDiff: Change,\n\t\tcursorAfterChange: number,\n\t\tsegmentStart: number\n\t): Change[] | null {\n\t\tconst nextDiffInsert = nextDiff.value;\n\t\tconst insertLength = nextDiffInsert.length;\n\t\tconst insertOffset = cursorAfterChange - segmentStart - insertLength;\n\n\t\t// Verify that the inserted text matches the text at the cursor position\n\t\tconst textAtCursor = diff.value.substring(\n\t\t\tinsertOffset,\n\t\t\tinsertOffset + nextDiffInsert.length\n\t\t);\n\t\tconst isInsertMoveable = textAtCursor === nextDiffInsert;\n\n\t\t// The insert text matches what's at the cursor position,\n\t\t// so we can safely move the insertion to the cursor position.\n\t\tif ( ! isInsertMoveable ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Split the current segment at the cursor\n\t\tconst beforeCursor = diff.value.substring( 0, insertOffset );\n\t\tconst afterCursor = diff.value.substring( insertOffset );\n\n\t\tconst result: Change[] = [];\n\n\t\t// Add before cursor part (if not empty)\n\t\tif ( beforeCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: beforeCursor,\n\t\t\t\tcount: beforeCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\t// Add the insertion in the middle\n\t\tresult.push( nextDiff );\n\n\t\t// Add after cursor part (if not empty)\n\t\tif ( afterCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: afterCursor,\n\t\t\t\tcount: afterCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Try to move a deletion operation to the cursor position by looking back at the previous unchanged segment.\n\t * This is a \"look-back\" strategy.\n\t *\n\t * @param diff - The current deletion diff segment.\n\t * @param adjustedDiffs - The array of previously processed diff segments.\n\t * @param cursorAfterChange - The cursor position after the change.\n\t * @param lastDiffPosition - The position in the document up to (but not including) the current diff.\n\t * @return An array of adjusted diff segments if the deletion was successfully moved, null otherwise.\n\t */\n\tprivate tryMoveDeletionToCursor(\n\t\tdiff: Change,\n\t\tadjustedDiffs: Change[],\n\t\tcursorAfterChange: number,\n\t\tlastDiffPosition: number\n\t): Change[] | null {\n\t\t// Check if there's a preceding unchanged segment where cursor falls\n\t\t// and the deleted characters match characters in that segment\n\t\tconst prevDiff = adjustedDiffs[ adjustedDiffs.length - 1 ];\n\n\t\tif ( ! prevDiff || prevDiff.added || prevDiff.removed ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst prevSegmentStart = lastDiffPosition - ( prevDiff.count ?? 0 );\n\t\tconst prevSegmentEnd = lastDiffPosition;\n\n\t\t// Check if cursor is within or at the end of the previous unchanged segment\n\t\tif (\n\t\t\tcursorAfterChange < prevSegmentStart ||\n\t\t\tcursorAfterChange >= prevSegmentEnd\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Check if the deleted characters match the text at the cursor position\n\t\tconst deletedChars = diff.value;\n\t\tconst deleteOffset = cursorAfterChange - prevSegmentStart;\n\t\tconst textAtCursor = prevDiff.value.substring(\n\t\t\tdeleteOffset,\n\t\t\tdeleteOffset + deletedChars.length\n\t\t);\n\t\tconst canBePlacedHere = textAtCursor === deletedChars;\n\n\t\tif ( ! canBePlacedHere ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Split the unchanged segment at the cursor and place deletion there\n\t\tconst beforeCursor = prevDiff.value.substring( 0, deleteOffset );\n\t\tconst atAndAfterCursor = prevDiff.value.substring( deleteOffset );\n\n\t\t// The deletion should consume characters starting at cursor\n\t\tconst deletionLength = diff.count ?? 0;\n\t\tconst afterDeletion = atAndAfterCursor.substring( deletionLength );\n\n\t\tconst result: Change[] = [];\n\n\t\t// Add before cursor part (if not empty)\n\t\tif ( beforeCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: beforeCursor,\n\t\t\t\tcount: beforeCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\t// Add the deletion\n\t\tresult.push( diff );\n\n\t\t// Add after deletion part (if not empty)\n\t\tif ( afterDeletion.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: afterDeletion,\n\t\t\t\tcount: afterDeletion.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Convert two Deltas to string representations for diffing.\n\t *\n\t * @param other - The other Delta to convert.\n\t * @return A tuple of [thisString, otherString].\n\t */\n\tprivate deltasToStrings( other: Delta ): [ string, string ] {\n\t\treturn [ this, other ].map( ( delta ) => {\n\t\t\treturn delta\n\t\t\t\t.map( ( op ) => {\n\t\t\t\t\tif ( op.insert !== null || op.insert !== undefined ) {\n\t\t\t\t\t\treturn typeof op.insert === 'string'\n\t\t\t\t\t\t\t? op.insert\n\t\t\t\t\t\t\t: NULL_CHARACTER;\n\t\t\t\t\t}\n\t\t\t\t\tconst prep = delta === other ? 'on' : 'with';\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t'diff() called ' + prep + ' non-document'\n\t\t\t\t\t);\n\t\t\t\t} )\n\t\t\t\t.join( '' );\n\t\t} ) as [ string, string ];\n\t}\n\n\t/**\n\t * Process diff changes and convert them to Delta operations.\n\t *\n\t * @param changes - The array of changes from the diff algorithm.\n\t * @param thisIter - Iterator for this Delta's operations.\n\t * @param otherIter - Iterator for the other Delta's operations.\n\t * @return A Delta containing the processed diff operations.\n\t */\n\tprivate convertChangesToDelta(\n\t\tchanges: Change[],\n\t\tthisIter: OpIterator,\n\t\totherIter: OpIterator\n\t): Delta {\n\t\tconst retDelta = new Delta();\n\t\tchanges.forEach( ( component: Change ) => {\n\t\t\tlet length = component.count ?? 0;\n\t\t\twhile ( length > 0 ) {\n\t\t\t\tlet opLength = 0;\n\t\t\t\tif ( component.added ) {\n\t\t\t\t\topLength = Math.min( otherIter.peekLength(), length );\n\t\t\t\t\tretDelta.push( otherIter.next( opLength ) );\n\t\t\t\t} else if ( component.removed ) {\n\t\t\t\t\topLength = Math.min( length, thisIter.peekLength() );\n\t\t\t\t\tthisIter.next( opLength );\n\t\t\t\t\tretDelta.delete( opLength );\n\t\t\t\t} else {\n\t\t\t\t\topLength = Math.min(\n\t\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\t\totherIter.peekLength(),\n\t\t\t\t\t\tlength\n\t\t\t\t\t);\n\t\t\t\t\tconst thisOp = thisIter.next( opLength );\n\t\t\t\t\tconst otherOp = otherIter.next( opLength );\n\t\t\t\t\tif ( isEqual( thisOp.insert, otherOp.insert ) ) {\n\t\t\t\t\t\tretDelta.retain(\n\t\t\t\t\t\t\topLength,\n\t\t\t\t\t\t\tAttributeMap.diff(\n\t\t\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\t\t\totherOp.attributes\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tretDelta.push( otherOp ).delete( opLength );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlength -= opLength;\n\t\t\t}\n\t\t} );\n\t\treturn retDelta;\n\t}\n}\n\nexport default Delta;\nexport { Op, OpIterator, AttributeMap };\n"], | ||
| "mappings": "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", | ||
| "sourcesContent": ["// File copied https://github.com/slab/delta/blob/main/src/Delta.ts with changes:\n// - fast-diff swapped out for 'diff',\n// - lodash.clonedeep is replaced with JSON parse / stringify\n// - lodash.isequal is replaced with fast-deep-equal.\n\n// @ts-ignore\n/**\n * External dependencies\n */\nimport type { Change } from 'diff';\nimport { diffChars } from 'diff';\nimport { default as isEqual } from 'fast-deep-equal/es6';\n\n/**\n * Internal dependencies\n */\nimport AttributeMap from './AttributeMap';\nimport Op from './Op';\nimport OpIterator from './OpIterator';\n\nfunction cloneDeep< T >( value: T ): T {\n\treturn JSON.parse( JSON.stringify( value ) ) as T;\n}\n\nconst NULL_CHARACTER = String.fromCharCode( 0 ); // Placeholder char for embed in diff()\n\n/**\n * Normalize diff changes so that `count` reflects UTF-16 code-unit length\n * rather than grapheme-cluster count (which diffChars may return when\n * Intl.Segmenter is available, e.g. diff v8+).\n *\n * @param changes - The array of changes from diffChars.\n * @return The changes with `count` normalized to UTF-16 code-unit length.\n */\nfunction normalizeChangeCounts( changes: Change[] ): Change[] {\n\treturn changes.map( ( change ) => ( {\n\t\t...change,\n\t\tcount: change.value.length,\n\t} ) );\n}\n\ninterface EmbedHandler< T > {\n\tcompose: ( a: T, b: T, keepNull: boolean ) => T;\n\tinvert: ( a: T, b: T ) => T;\n\ttransform: ( a: T, b: T, priority: boolean ) => T;\n}\n\nconst getEmbedTypeAndData = (\n\ta: Op[ 'insert' ] | Op[ 'retain' ],\n\tb: Op[ 'insert' ]\n): [ string, unknown, unknown ] => {\n\tif ( typeof a !== 'object' || a === null ) {\n\t\tthrow new Error( `cannot retain a ${ typeof a }` );\n\t}\n\tif ( typeof b !== 'object' || b === null ) {\n\t\tthrow new Error( `cannot retain a ${ typeof b }` );\n\t}\n\tconst embedType = Object.keys( a )[ 0 ];\n\tif ( ! embedType || embedType !== Object.keys( b )[ 0 ] ) {\n\t\tthrow new Error(\n\t\t\t`embed types not matched: ${ embedType } != ${\n\t\t\t\tObject.keys( b )[ 0 ]\n\t\t\t}`\n\t\t);\n\t}\n\treturn [ embedType, a[ embedType ], b[ embedType ] ];\n};\n\nclass Delta {\n\tstatic Op = Op;\n\tstatic OpIterator = OpIterator;\n\tstatic AttributeMap = AttributeMap;\n\tprivate static handlers: {\n\t\t[ embedType: string ]: EmbedHandler< unknown >;\n\t} = {};\n\n\tstatic registerEmbed< T >(\n\t\tembedType: string,\n\t\thandler: EmbedHandler< T >\n\t): void {\n\t\tthis.handlers[ embedType ] = handler as EmbedHandler< unknown >;\n\t}\n\n\tstatic unregisterEmbed( embedType: string ): void {\n\t\tdelete this.handlers[ embedType ];\n\t}\n\n\tprivate static getHandler( embedType: string ): EmbedHandler< unknown > {\n\t\tconst handler = this.handlers[ embedType ];\n\t\tif ( ! handler ) {\n\t\t\tthrow new Error( `no handlers for embed type \"${ embedType }\"` );\n\t\t}\n\t\treturn handler;\n\t}\n\n\tops: Op[];\n\tconstructor( ops?: Op[] | { ops: Op[] } ) {\n\t\t// Assume we are given a well formed ops\n\t\tif ( Array.isArray( ops ) ) {\n\t\t\tthis.ops = ops;\n\t\t} else if (\n\t\t\tops !== null &&\n\t\t\tops !== undefined &&\n\t\t\tArray.isArray( ops.ops )\n\t\t) {\n\t\t\tthis.ops = ops.ops;\n\t\t} else {\n\t\t\tthis.ops = [];\n\t\t}\n\t}\n\n\tinsert(\n\t\targ: string | Record< string, unknown >,\n\t\tattributes?: AttributeMap | null\n\t): this {\n\t\tconst newOp: Op = {};\n\t\tif ( typeof arg === 'string' && arg.length === 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\tnewOp.insert = arg;\n\t\tif (\n\t\t\tattributes !== null &&\n\t\t\tattributes !== undefined &&\n\t\t\ttypeof attributes === 'object' &&\n\t\t\tObject.keys( attributes ).length > 0\n\t\t) {\n\t\t\tnewOp.attributes = attributes;\n\t\t}\n\t\treturn this.push( newOp );\n\t}\n\n\tdelete( length: number ): this {\n\t\tif ( length <= 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\treturn this.push( { delete: length } );\n\t}\n\n\tretain(\n\t\tlength: number | Record< string, unknown >,\n\t\tattributes?: AttributeMap | null\n\t): this {\n\t\tif ( typeof length === 'number' && length <= 0 ) {\n\t\t\treturn this;\n\t\t}\n\t\tconst newOp: Op = { retain: length };\n\t\tif (\n\t\t\tattributes !== null &&\n\t\t\tattributes !== undefined &&\n\t\t\ttypeof attributes === 'object' &&\n\t\t\tObject.keys( attributes ).length > 0\n\t\t) {\n\t\t\tnewOp.attributes = attributes;\n\t\t}\n\t\treturn this.push( newOp );\n\t}\n\n\tpush( newOp: Op ): this {\n\t\tlet index = this.ops.length;\n\t\tlet lastOp = this.ops[ index - 1 ];\n\t\tnewOp = cloneDeep( newOp );\n\t\tif ( typeof lastOp === 'object' ) {\n\t\t\tif (\n\t\t\t\ttypeof newOp.delete === 'number' &&\n\t\t\t\ttypeof lastOp.delete === 'number'\n\t\t\t) {\n\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\tdelete: lastOp.delete + newOp.delete,\n\t\t\t\t};\n\t\t\t\treturn this;\n\t\t\t}\n\t\t\t// Since it does not matter if we insert before or after deleting at the same index,\n\t\t\t// always prefer to insert first\n\t\t\tif (\n\t\t\t\ttypeof lastOp.delete === 'number' &&\n\t\t\t\tnewOp.insert !== null &&\n\t\t\t\tnewOp.insert !== undefined\n\t\t\t) {\n\t\t\t\tindex -= 1;\n\t\t\t\tlastOp = this.ops[ index - 1 ];\n\t\t\t\tif ( typeof lastOp !== 'object' ) {\n\t\t\t\t\tthis.ops.unshift( newOp );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( isEqual( newOp.attributes, lastOp.attributes ) ) {\n\t\t\t\tif (\n\t\t\t\t\ttypeof newOp.insert === 'string' &&\n\t\t\t\t\ttypeof lastOp.insert === 'string'\n\t\t\t\t) {\n\t\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\t\tinsert: lastOp.insert + newOp.insert,\n\t\t\t\t\t};\n\t\t\t\t\tif ( typeof newOp.attributes === 'object' ) {\n\t\t\t\t\t\tthis.ops[ index - 1 ].attributes = newOp.attributes;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof newOp.retain === 'number' &&\n\t\t\t\t\ttypeof lastOp.retain === 'number'\n\t\t\t\t) {\n\t\t\t\t\tthis.ops[ index - 1 ] = {\n\t\t\t\t\t\tretain: lastOp.retain + newOp.retain,\n\t\t\t\t\t};\n\t\t\t\t\tif ( typeof newOp.attributes === 'object' ) {\n\t\t\t\t\t\tthis.ops[ index - 1 ].attributes = newOp.attributes;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif ( index === this.ops.length ) {\n\t\t\tthis.ops.push( newOp );\n\t\t} else {\n\t\t\tthis.ops.splice( index, 0, newOp );\n\t\t}\n\t\treturn this;\n\t}\n\n\tchop(): this {\n\t\tconst lastOp = this.ops[ this.ops.length - 1 ];\n\t\tif (\n\t\t\tlastOp &&\n\t\t\ttypeof lastOp.retain === 'number' &&\n\t\t\t! lastOp.attributes\n\t\t) {\n\t\t\tthis.ops.pop();\n\t\t}\n\t\treturn this;\n\t}\n\n\tfilter( predicate: ( op: Op, index: number ) => boolean ): Op[] {\n\t\treturn this.ops.filter( predicate );\n\t}\n\n\tforEach( predicate: ( op: Op, index: number ) => void ): void {\n\t\tthis.ops.forEach( predicate );\n\t}\n\n\tmap< T >( predicate: ( op: Op, index: number ) => T ): T[] {\n\t\treturn this.ops.map( predicate );\n\t}\n\n\tpartition( predicate: ( op: Op ) => boolean ): [ Op[], Op[] ] {\n\t\tconst passed: Op[] = [];\n\t\tconst failed: Op[] = [];\n\t\tthis.forEach( ( op ) => {\n\t\t\tconst target = predicate( op ) ? passed : failed;\n\t\t\ttarget.push( op );\n\t\t} );\n\t\treturn [ passed, failed ];\n\t}\n\n\treduce< T >(\n\t\tpredicate: ( accum: T, curr: Op, index: number ) => T,\n\t\tinitialValue: T\n\t): T {\n\t\treturn this.ops.reduce( predicate, initialValue );\n\t}\n\n\tchangeLength(): number {\n\t\treturn this.reduce( ( length, elem ) => {\n\t\t\tif ( elem.insert ) {\n\t\t\t\treturn length + Op.length( elem );\n\t\t\t} else if ( elem.delete ) {\n\t\t\t\treturn length - elem.delete;\n\t\t\t}\n\t\t\treturn length;\n\t\t}, 0 );\n\t}\n\n\tlength(): number {\n\t\treturn this.reduce( ( length, elem ) => {\n\t\t\treturn length + Op.length( elem );\n\t\t}, 0 );\n\t}\n\n\tslice( start = 0, end = Infinity ): Delta {\n\t\tconst ops = [];\n\t\tconst iter = new OpIterator( this.ops );\n\t\tlet index = 0;\n\t\twhile ( index < end && iter.hasNext() ) {\n\t\t\tlet nextOp;\n\t\t\tif ( index < start ) {\n\t\t\t\tnextOp = iter.next( start - index );\n\t\t\t} else {\n\t\t\t\tnextOp = iter.next( end - index );\n\t\t\t\tops.push( nextOp );\n\t\t\t}\n\t\t\tindex += Op.length( nextOp );\n\t\t}\n\t\treturn new Delta( ops );\n\t}\n\n\tcompose( other: Delta ): Delta {\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst ops = [];\n\t\tconst firstOther = otherIter.peek();\n\t\tif (\n\t\t\tfirstOther !== null &&\n\t\t\tfirstOther !== undefined &&\n\t\t\ttypeof firstOther.retain === 'number' &&\n\t\t\t( firstOther.attributes === null ||\n\t\t\t\tfirstOther.attributes === undefined )\n\t\t) {\n\t\t\tlet firstLeft = firstOther.retain;\n\t\t\twhile (\n\t\t\t\tthisIter.peekType() === 'insert' &&\n\t\t\t\tthisIter.peekLength() <= firstLeft\n\t\t\t) {\n\t\t\t\tfirstLeft -= thisIter.peekLength();\n\t\t\t\tops.push( thisIter.next() );\n\t\t\t}\n\t\t\tif ( firstOther.retain - firstLeft > 0 ) {\n\t\t\t\totherIter.next( firstOther.retain - firstLeft );\n\t\t\t}\n\t\t}\n\t\tconst delta = new Delta( ops );\n\t\twhile ( thisIter.hasNext() || otherIter.hasNext() ) {\n\t\t\tif ( otherIter.peekType() === 'insert' ) {\n\t\t\t\tdelta.push( otherIter.next() );\n\t\t\t} else if ( thisIter.peekType() === 'delete' ) {\n\t\t\t\tdelta.push( thisIter.next() );\n\t\t\t} else {\n\t\t\t\tconst length = Math.min(\n\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\totherIter.peekLength()\n\t\t\t\t);\n\t\t\t\tconst thisOp = thisIter.next( length );\n\t\t\t\tconst otherOp = otherIter.next( length );\n\t\t\t\tif ( otherOp.retain ) {\n\t\t\t\t\tconst newOp: Op = {};\n\t\t\t\t\tif ( typeof thisOp.retain === 'number' ) {\n\t\t\t\t\t\tnewOp.retain =\n\t\t\t\t\t\t\ttypeof otherOp.retain === 'number'\n\t\t\t\t\t\t\t\t? length\n\t\t\t\t\t\t\t\t: otherOp.retain;\n\t\t\t\t\t} else if ( typeof otherOp.retain === 'number' ) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tthisOp.retain === null ||\n\t\t\t\t\t\t\tthisOp.retain === undefined\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tnewOp.insert = thisOp.insert;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnewOp.retain = thisOp.retain;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst action =\n\t\t\t\t\t\t\tthisOp.retain === null ||\n\t\t\t\t\t\t\tthisOp.retain === undefined\n\t\t\t\t\t\t\t\t? 'insert'\n\t\t\t\t\t\t\t\t: 'retain';\n\t\t\t\t\t\tconst [ embedType, thisData, otherData ] =\n\t\t\t\t\t\t\tgetEmbedTypeAndData(\n\t\t\t\t\t\t\t\tthisOp[ action ],\n\t\t\t\t\t\t\t\totherOp.retain\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\t\t\tnewOp[ action ] = {\n\t\t\t\t\t\t\t[ embedType ]: handler.compose(\n\t\t\t\t\t\t\t\tthisData,\n\t\t\t\t\t\t\t\totherData,\n\t\t\t\t\t\t\t\taction === 'retain'\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\t// Preserve null when composing with a retain, otherwise remove it for inserts\n\t\t\t\t\tconst attributes = AttributeMap.compose(\n\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\totherOp.attributes,\n\t\t\t\t\t\ttypeof thisOp.retain === 'number'\n\t\t\t\t\t);\n\t\t\t\t\tif ( attributes ) {\n\t\t\t\t\t\tnewOp.attributes = attributes;\n\t\t\t\t\t}\n\t\t\t\t\tdelta.push( newOp );\n\n\t\t\t\t\t// Optimization if rest of other is just retain\n\t\t\t\t\tif (\n\t\t\t\t\t\t! otherIter.hasNext() &&\n\t\t\t\t\t\tisEqual( delta.ops[ delta.ops.length - 1 ], newOp )\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst rest = new Delta( thisIter.rest() );\n\t\t\t\t\t\treturn delta.concat( rest ).chop();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Other op should be delete, we could be an insert or retain\n\t\t\t\t\t// Insert + delete cancels out\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof otherOp.delete === 'number' &&\n\t\t\t\t\t( typeof thisOp.retain === 'number' ||\n\t\t\t\t\t\t( typeof thisOp.retain === 'object' &&\n\t\t\t\t\t\t\tthisOp.retain !== null ) )\n\t\t\t\t) {\n\t\t\t\t\tdelta.push( otherOp );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn delta.chop();\n\t}\n\n\tconcat( other: Delta ): Delta {\n\t\tconst delta = new Delta( this.ops.slice() );\n\t\tif ( other.ops.length > 0 ) {\n\t\t\tdelta.push( other.ops[ 0 ] );\n\t\t\tdelta.ops = delta.ops.concat( other.ops.slice( 1 ) );\n\t\t}\n\t\treturn delta;\n\t}\n\n\tdiff( other: Delta ): Delta {\n\t\tif ( this.ops === other.ops ) {\n\t\t\treturn new Delta();\n\t\t}\n\t\tconst strings = this.deltasToStrings( other );\n\t\tconst diffResult = normalizeChangeCounts(\n\t\t\tdiffChars( strings[ 0 ], strings[ 1 ] )\n\t\t);\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst retDelta = this.convertChangesToDelta(\n\t\t\tdiffResult,\n\t\t\tthisIter,\n\t\t\totherIter\n\t\t);\n\n\t\treturn retDelta.chop();\n\t}\n\n\teachLine(\n\t\tpredicate: (\n\t\t\tline: Delta,\n\t\t\tattributes: AttributeMap,\n\t\t\tindex: number\n\t\t) => boolean | void,\n\t\tnewline = '\\n'\n\t): void {\n\t\tconst iter = new OpIterator( this.ops );\n\t\tlet line = new Delta();\n\t\tlet i = 0;\n\t\twhile ( iter.hasNext() ) {\n\t\t\tif ( iter.peekType() !== 'insert' ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst thisOp = iter.peek();\n\t\t\tconst start = Op.length( thisOp ) - iter.peekLength();\n\t\t\tconst index =\n\t\t\t\ttypeof thisOp.insert === 'string'\n\t\t\t\t\t? thisOp.insert.indexOf( newline, start ) - start\n\t\t\t\t\t: -1;\n\t\t\tif ( index < 0 ) {\n\t\t\t\tline.push( iter.next() );\n\t\t\t} else if ( index > 0 ) {\n\t\t\t\tline.push( iter.next( index ) );\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tpredicate( line, iter.next( 1 ).attributes || {}, i ) ===\n\t\t\t\t\tfalse\n\t\t\t\t) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ti += 1;\n\t\t\t\tline = new Delta();\n\t\t\t}\n\t\t}\n\t\tif ( line.length() > 0 ) {\n\t\t\tpredicate( line, {}, i );\n\t\t}\n\t}\n\n\tinvert( base: Delta ): Delta {\n\t\tconst inverted = new Delta();\n\t\tthis.reduce( ( baseIndex, op ) => {\n\t\t\tif ( op.insert ) {\n\t\t\t\tinverted.delete( Op.length( op ) );\n\t\t\t} else if (\n\t\t\t\ttypeof op.retain === 'number' &&\n\t\t\t\t( op.attributes === null || op.attributes === undefined )\n\t\t\t) {\n\t\t\t\tinverted.retain( op.retain );\n\t\t\t\treturn baseIndex + op.retain;\n\t\t\t} else if ( op.delete || typeof op.retain === 'number' ) {\n\t\t\t\tconst length = ( op.delete || op.retain ) as number;\n\t\t\t\tconst slice = base.slice( baseIndex, baseIndex + length );\n\t\t\t\tslice.forEach( ( baseOp ) => {\n\t\t\t\t\tif ( op.delete ) {\n\t\t\t\t\t\tinverted.push( baseOp );\n\t\t\t\t\t} else if ( op.retain && op.attributes ) {\n\t\t\t\t\t\tinverted.retain(\n\t\t\t\t\t\t\tOp.length( baseOp ),\n\t\t\t\t\t\t\tAttributeMap.invert(\n\t\t\t\t\t\t\t\top.attributes,\n\t\t\t\t\t\t\t\tbaseOp.attributes\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\treturn baseIndex + length;\n\t\t\t} else if ( typeof op.retain === 'object' && op.retain !== null ) {\n\t\t\t\tconst slice = base.slice( baseIndex, baseIndex + 1 );\n\t\t\t\tconst baseOp = new OpIterator( slice.ops ).next();\n\t\t\t\tconst [ embedType, opData, baseOpData ] = getEmbedTypeAndData(\n\t\t\t\t\top.retain,\n\t\t\t\t\tbaseOp.insert\n\t\t\t\t);\n\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\tinverted.retain(\n\t\t\t\t\t{ [ embedType ]: handler.invert( opData, baseOpData ) },\n\t\t\t\t\tAttributeMap.invert( op.attributes, baseOp.attributes )\n\t\t\t\t);\n\t\t\t\treturn baseIndex + 1;\n\t\t\t}\n\t\t\treturn baseIndex;\n\t\t}, 0 );\n\t\treturn inverted.chop();\n\t}\n\n\ttransform( index: number, priority?: boolean ): number;\n\ttransform( other: Delta, priority?: boolean ): Delta;\n\ttransform( arg: number | Delta, priority = false ): typeof arg {\n\t\tpriority = !! priority;\n\t\tif ( typeof arg === 'number' ) {\n\t\t\treturn this.transformPosition( arg, priority );\n\t\t}\n\t\tconst other: Delta = arg;\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst delta = new Delta();\n\t\twhile ( thisIter.hasNext() || otherIter.hasNext() ) {\n\t\t\tif (\n\t\t\t\tthisIter.peekType() === 'insert' &&\n\t\t\t\t( priority || otherIter.peekType() !== 'insert' )\n\t\t\t) {\n\t\t\t\tdelta.retain( Op.length( thisIter.next() ) );\n\t\t\t} else if ( otherIter.peekType() === 'insert' ) {\n\t\t\t\tdelta.push( otherIter.next() );\n\t\t\t} else {\n\t\t\t\tconst length = Math.min(\n\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\totherIter.peekLength()\n\t\t\t\t);\n\t\t\t\tconst thisOp = thisIter.next( length );\n\t\t\t\tconst otherOp = otherIter.next( length );\n\t\t\t\tif ( thisOp.delete ) {\n\t\t\t\t\t// Our delete either makes their delete redundant or removes their retain\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if ( otherOp.delete ) {\n\t\t\t\t\tdelta.push( otherOp );\n\t\t\t\t} else {\n\t\t\t\t\tconst thisData = thisOp.retain;\n\t\t\t\t\tconst otherData = otherOp.retain;\n\t\t\t\t\tlet transformedData: Op[ 'retain' ] =\n\t\t\t\t\t\ttypeof otherData === 'object' && otherData !== null\n\t\t\t\t\t\t\t? otherData\n\t\t\t\t\t\t\t: length;\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof thisData === 'object' &&\n\t\t\t\t\t\tthisData !== null &&\n\t\t\t\t\t\ttypeof otherData === 'object' &&\n\t\t\t\t\t\totherData !== null\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst embedType = Object.keys( thisData )[ 0 ];\n\t\t\t\t\t\tif ( embedType === Object.keys( otherData )[ 0 ] ) {\n\t\t\t\t\t\t\tconst handler = Delta.getHandler( embedType );\n\t\t\t\t\t\t\tif ( handler ) {\n\t\t\t\t\t\t\t\ttransformedData = {\n\t\t\t\t\t\t\t\t\t[ embedType ]: handler.transform(\n\t\t\t\t\t\t\t\t\t\tthisData[ embedType ],\n\t\t\t\t\t\t\t\t\t\totherData[ embedType ],\n\t\t\t\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// We retain either their retain or insert\n\t\t\t\t\tdelta.retain(\n\t\t\t\t\t\ttransformedData,\n\t\t\t\t\t\tAttributeMap.transform(\n\t\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\t\totherOp.attributes,\n\t\t\t\t\t\t\tpriority\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn delta.chop();\n\t}\n\n\ttransformPosition( index: number, priority = false ): number {\n\t\tpriority = !! priority;\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tlet offset = 0;\n\t\twhile ( thisIter.hasNext() && offset <= index ) {\n\t\t\tconst length = thisIter.peekLength();\n\t\t\tconst nextType = thisIter.peekType();\n\t\t\tthisIter.next();\n\t\t\tif ( nextType === 'delete' ) {\n\t\t\t\tindex -= Math.min( length, index - offset );\n\t\t\t\tcontinue;\n\t\t\t} else if (\n\t\t\t\tnextType === 'insert' &&\n\t\t\t\t( offset < index || ! priority )\n\t\t\t) {\n\t\t\t\tindex += length;\n\t\t\t}\n\t\t\toffset += length;\n\t\t}\n\t\treturn index;\n\t}\n\n\t/**\n\t * Given a Delta and a cursor position, do a diff and attempt to adjust\n\t * the diff to place insertions or deletions at the cursor position.\n\t *\n\t * @param other - The other Delta to diff against.\n\t * @param cursorAfterChange - The cursor position index after the change.\n\t * @return A Delta that attempts to place insertions or deletions at the cursor position.\n\t */\n\tdiffWithCursor( other: Delta, cursorAfterChange: number | null ): Delta {\n\t\tif ( this.ops === other.ops ) {\n\t\t\treturn new Delta();\n\t\t} else if ( cursorAfterChange === null ) {\n\t\t\t// If no cursor position is provided, do a regular diff.\n\t\t\treturn this.diff( other );\n\t\t}\n\n\t\tconst strings = this.deltasToStrings( other );\n\t\tlet diffs = normalizeChangeCounts(\n\t\t\tdiffChars( strings[ 0 ], strings[ 1 ] )\n\t\t);\n\t\tlet lastDiffPosition = 0;\n\t\tconst adjustedDiffs: Change[] = [];\n\n\t\tfor ( let i = 0; i < diffs.length; i++ ) {\n\t\t\tconst diff = diffs[ i ];\n\n\t\t\tconst segmentStart = lastDiffPosition;\n\t\t\tconst segmentEnd = lastDiffPosition + ( diff.count ?? 0 );\n\t\t\tconst isCursorInSegment =\n\t\t\t\tcursorAfterChange > segmentStart &&\n\t\t\t\tcursorAfterChange <= segmentEnd;\n\n\t\t\tconst isUnchangedSegment = ! diff.added && ! diff.removed;\n\t\t\tconst isRemovalSegment = diff.removed && ! diff.added;\n\n\t\t\tconst nextDiff = diffs[ i + 1 ];\n\t\t\tconst isNextDiffAnInsert =\n\t\t\t\tnextDiff && nextDiff.added && ! nextDiff.removed;\n\n\t\t\t// Path 1: Look-ahead strategy\n\t\t\t// If the position of the cursor is in an \"unchanged\" segment, but there's an insertion\n\t\t\t// right after this section, then the insertion has likely been placed in\n\t\t\t// the incorrect spot, and we can move the insertion to the position of the cursor.\n\t\t\tif (\n\t\t\t\tisUnchangedSegment &&\n\t\t\t\tisCursorInSegment &&\n\t\t\t\tisNextDiffAnInsert\n\t\t\t) {\n\t\t\t\tconst movedSegments = this.tryMoveInsertionToCursor(\n\t\t\t\t\tdiff,\n\t\t\t\t\tnextDiff,\n\t\t\t\t\tcursorAfterChange,\n\t\t\t\t\tsegmentStart\n\t\t\t\t);\n\n\t\t\t\tif ( movedSegments ) {\n\t\t\t\t\tadjustedDiffs.push( ...movedSegments );\n\t\t\t\t\t// Skip the next diff since we've already consumed it\n\t\t\t\t\ti++;\n\t\t\t\t\tlastDiffPosition = segmentEnd;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Path 2: Look-back strategy\n\t\t\t// Handle removals by checking if cursor was in the previous unchanged segment\n\t\t\tif ( isRemovalSegment ) {\n\t\t\t\tconst movedSegments = this.tryMoveDeletionToCursor(\n\t\t\t\t\tdiff,\n\t\t\t\t\tadjustedDiffs,\n\t\t\t\t\tcursorAfterChange,\n\t\t\t\t\tlastDiffPosition\n\t\t\t\t);\n\n\t\t\t\tif ( movedSegments ) {\n\t\t\t\t\t// Remove the previous unchanged segment from adjustedDiffs\n\t\t\t\t\tadjustedDiffs.pop();\n\t\t\t\t\tadjustedDiffs.push( ...movedSegments );\n\t\t\t\t\tlastDiffPosition += diff.count ?? 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Path 3: Do nothing - add diff as-is\n\t\t\tadjustedDiffs.push( diff );\n\t\t\tif ( ! diff.added ) {\n\t\t\t\tlastDiffPosition += diff.count ?? 0;\n\t\t\t}\n\t\t}\n\n\t\tdiffs = adjustedDiffs;\n\n\t\tconst thisIter = new OpIterator( this.ops );\n\t\tconst otherIter = new OpIterator( other.ops );\n\t\tconst retDelta = this.convertChangesToDelta(\n\t\t\tdiffs,\n\t\t\tthisIter,\n\t\t\totherIter\n\t\t);\n\n\t\treturn retDelta.chop();\n\t}\n\n\t/**\n\t * Try to move an insertion operation from after an unchanged segment to the cursor position within it.\n\t * This is a \"look-ahead\" strategy.\n\t *\n\t * @param diff - The current unchanged diff segment.\n\t * @param nextDiff - The next diff segment (expected to be an insertion).\n\t * @param cursorAfterChange - The cursor position after the change.\n\t * @param segmentStart - The start position of the current segment.\n\t * @return An array of adjusted diff segments if the insertion was successfully moved, null otherwise.\n\t */\n\tprivate tryMoveInsertionToCursor(\n\t\tdiff: Change,\n\t\tnextDiff: Change,\n\t\tcursorAfterChange: number,\n\t\tsegmentStart: number\n\t): Change[] | null {\n\t\tconst nextDiffInsert = nextDiff.value;\n\t\tconst insertLength = nextDiffInsert.length;\n\t\tconst insertOffset = cursorAfterChange - segmentStart - insertLength;\n\n\t\t// Verify that the inserted text matches the text at the cursor position\n\t\tconst textAtCursor = diff.value.substring(\n\t\t\tinsertOffset,\n\t\t\tinsertOffset + nextDiffInsert.length\n\t\t);\n\t\tconst isInsertMoveable = textAtCursor === nextDiffInsert;\n\n\t\t// The insert text matches what's at the cursor position,\n\t\t// so we can safely move the insertion to the cursor position.\n\t\tif ( ! isInsertMoveable ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Split the current segment at the cursor\n\t\tconst beforeCursor = diff.value.substring( 0, insertOffset );\n\t\tconst afterCursor = diff.value.substring( insertOffset );\n\n\t\tconst result: Change[] = [];\n\n\t\t// Add before cursor part (if not empty)\n\t\tif ( beforeCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: beforeCursor,\n\t\t\t\tcount: beforeCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\t// Add the insertion in the middle\n\t\tresult.push( nextDiff );\n\n\t\t// Add after cursor part (if not empty)\n\t\tif ( afterCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: afterCursor,\n\t\t\t\tcount: afterCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Try to move a deletion operation to the cursor position by looking back at the previous unchanged segment.\n\t * This is a \"look-back\" strategy.\n\t *\n\t * @param diff - The current deletion diff segment.\n\t * @param adjustedDiffs - The array of previously processed diff segments.\n\t * @param cursorAfterChange - The cursor position after the change.\n\t * @param lastDiffPosition - The position in the document up to (but not including) the current diff.\n\t * @return An array of adjusted diff segments if the deletion was successfully moved, null otherwise.\n\t */\n\tprivate tryMoveDeletionToCursor(\n\t\tdiff: Change,\n\t\tadjustedDiffs: Change[],\n\t\tcursorAfterChange: number,\n\t\tlastDiffPosition: number\n\t): Change[] | null {\n\t\t// Check if there's a preceding unchanged segment where cursor falls\n\t\t// and the deleted characters match characters in that segment\n\t\tconst prevDiff = adjustedDiffs[ adjustedDiffs.length - 1 ];\n\n\t\tif ( ! prevDiff || prevDiff.added || prevDiff.removed ) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst prevSegmentStart = lastDiffPosition - ( prevDiff.count ?? 0 );\n\t\tconst prevSegmentEnd = lastDiffPosition;\n\n\t\t// Check if cursor is within or at the end of the previous unchanged segment\n\t\tif (\n\t\t\tcursorAfterChange < prevSegmentStart ||\n\t\t\tcursorAfterChange >= prevSegmentEnd\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Check if the deleted characters match the text at the cursor position\n\t\tconst deletedChars = diff.value;\n\t\tconst deleteOffset = cursorAfterChange - prevSegmentStart;\n\t\tconst textAtCursor = prevDiff.value.substring(\n\t\t\tdeleteOffset,\n\t\t\tdeleteOffset + deletedChars.length\n\t\t);\n\t\tconst canBePlacedHere = textAtCursor === deletedChars;\n\n\t\tif ( ! canBePlacedHere ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Split the unchanged segment at the cursor and place deletion there\n\t\tconst beforeCursor = prevDiff.value.substring( 0, deleteOffset );\n\t\tconst atAndAfterCursor = prevDiff.value.substring( deleteOffset );\n\n\t\t// The deletion should consume characters starting at cursor\n\t\tconst deletionLength = diff.count ?? 0;\n\t\tconst afterDeletion = atAndAfterCursor.substring( deletionLength );\n\n\t\tconst result: Change[] = [];\n\n\t\t// Add before cursor part (if not empty)\n\t\tif ( beforeCursor.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: beforeCursor,\n\t\t\t\tcount: beforeCursor.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\t// Add the deletion\n\t\tresult.push( diff );\n\n\t\t// Add after deletion part (if not empty)\n\t\tif ( afterDeletion.length > 0 ) {\n\t\t\tresult.push( {\n\t\t\t\tvalue: afterDeletion,\n\t\t\t\tcount: afterDeletion.length,\n\t\t\t\tadded: false,\n\t\t\t\tremoved: false,\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Convert two Deltas to string representations for diffing.\n\t *\n\t * @param other - The other Delta to convert.\n\t * @return A tuple of [thisString, otherString].\n\t */\n\tprivate deltasToStrings( other: Delta ): [ string, string ] {\n\t\treturn [ this, other ].map( ( delta ) => {\n\t\t\treturn delta\n\t\t\t\t.map( ( op ) => {\n\t\t\t\t\tif ( op.insert !== null || op.insert !== undefined ) {\n\t\t\t\t\t\treturn typeof op.insert === 'string'\n\t\t\t\t\t\t\t? op.insert\n\t\t\t\t\t\t\t: NULL_CHARACTER;\n\t\t\t\t\t}\n\t\t\t\t\tconst prep = delta === other ? 'on' : 'with';\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t'diff() called ' + prep + ' non-document'\n\t\t\t\t\t);\n\t\t\t\t} )\n\t\t\t\t.join( '' );\n\t\t} ) as [ string, string ];\n\t}\n\n\t/**\n\t * Process diff changes and convert them to Delta operations.\n\t *\n\t * @param changes - The array of changes from the diff algorithm.\n\t * @param thisIter - Iterator for this Delta's operations.\n\t * @param otherIter - Iterator for the other Delta's operations.\n\t * @return A Delta containing the processed diff operations.\n\t */\n\tprivate convertChangesToDelta(\n\t\tchanges: Change[],\n\t\tthisIter: OpIterator,\n\t\totherIter: OpIterator\n\t): Delta {\n\t\tconst retDelta = new Delta();\n\t\tchanges.forEach( ( component: Change ) => {\n\t\t\tlet length = component.count ?? 0;\n\t\t\twhile ( length > 0 ) {\n\t\t\t\tlet opLength = 0;\n\t\t\t\tif ( component.added ) {\n\t\t\t\t\topLength = Math.min( otherIter.peekLength(), length );\n\t\t\t\t\tretDelta.push( otherIter.next( opLength ) );\n\t\t\t\t} else if ( component.removed ) {\n\t\t\t\t\topLength = Math.min( length, thisIter.peekLength() );\n\t\t\t\t\tthisIter.next( opLength );\n\t\t\t\t\tretDelta.delete( opLength );\n\t\t\t\t} else {\n\t\t\t\t\topLength = Math.min(\n\t\t\t\t\t\tthisIter.peekLength(),\n\t\t\t\t\t\totherIter.peekLength(),\n\t\t\t\t\t\tlength\n\t\t\t\t\t);\n\t\t\t\t\tconst thisOp = thisIter.next( opLength );\n\t\t\t\t\tconst otherOp = otherIter.next( opLength );\n\t\t\t\t\tif ( isEqual( thisOp.insert, otherOp.insert ) ) {\n\t\t\t\t\t\tretDelta.retain(\n\t\t\t\t\t\t\topLength,\n\t\t\t\t\t\t\tAttributeMap.diff(\n\t\t\t\t\t\t\t\tthisOp.attributes,\n\t\t\t\t\t\t\t\totherOp.attributes\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tretDelta.push( otherOp ).delete( opLength );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlength -= opLength;\n\t\t\t}\n\t\t} );\n\t\treturn retDelta;\n\t}\n}\n\nexport default Delta;\nexport { Op, OpIterator, AttributeMap };\n"], | ||
| "mappings": "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", | ||
| "names": ["AttributeMap", "Op", "OpIterator", "isEqual"] | ||
| } |
| { | ||
| "version": 3, | ||
| "sources": ["../src/types.ts"], | ||
| "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport type { UndoManager as WPUndoManager } from '@wordpress/undo-manager';\n\n/**\n * External dependencies\n */\nimport type * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\n/* globalThis */\ndeclare global {\n\tinterface Window {\n\t\t_wpCollaborationEnabled?: string;\n\t}\n}\n\nexport type CRDTDoc = Y.Doc;\nexport type AwarenessID = string;\nexport type EntityID = string;\nexport type ObjectID = string;\nexport type ObjectType = string;\n\n// An origin is a value passed by the transactor to identify the source of a\n// change. It can be any value, and is not used internally by Yjs. Origins are\n// preserved locally, while a remote change will have the provider instance as\n// its origin.\nexport type Origin = any;\n\n// Object data represents any entity record. There are not any expectations that\n// can hold on its shape, beyond a record with string keys and unknown values.\nexport type ObjectData = Record< string, unknown >;\n\n/**\n * Event map for provider events.\n * Add new event types here as needed.\n */\nexport interface ProviderEventMap {\n\tstatus: ConnectionStatus;\n}\n\n/**\n * Generic event listener type for providers.\n * Providers should call registered callbacks when events occur like connection status changes.\n * Providers are responsible for cleaning up listeners in their destroy() method.\n */\nexport type ProviderOn = < K extends keyof ProviderEventMap >(\n\tevent: K,\n\tcallback: ( data: ProviderEventMap[ K ] ) => void\n) => void;\n\nexport interface ProviderCreatorResult {\n\tdestroy: () => void;\n\ton: ProviderOn;\n}\n\n/**\n * Error codes for connection errors that can occur in sync providers.\n */\nexport type ConnectionErrorCode =\n\t| 'authentication-error'\n\t| 'connection-expired'\n\t| 'connection-limit-exceeded'\n\t| 'unknown-error';\n\n/**\n * Sync connection error object.\n */\nexport interface ConnectionError extends Error {\n\t/**\n\t * Error code identifier for programmatic handling and default message lookup.\n\t */\n\tcode: ConnectionErrorCode;\n}\n\n/**\n * Current connection status of a sync provider, including status and optional error information.\n */\nexport interface ConnectionStatus {\n\tstatus: 'connected' | 'connecting' | 'disconnected';\n\n\t/**\n\t * Optional error information when status is 'disconnected'.\n\t */\n\terror?: ConnectionError;\n}\n\nexport type OnStatusChangeCallback = (\n\tstatus: ConnectionStatus | null\n) => void;\n\n/**\n * Options passed to a provider creator function when initializing a sync provider.\n */\nexport interface ProviderCreatorOptions {\n\tobjectType: ObjectType;\n\tobjectId: ObjectID | null;\n\tydoc: Y.Doc;\n\tawareness?: Awareness;\n}\n\nexport type ProviderCreator = (\n\toptions: ProviderCreatorOptions\n) => Promise< ProviderCreatorResult >;\n\nexport interface CollectionHandlers {\n\tonStatusChange: OnStatusChangeCallback;\n\trefetchRecords: () => Promise< void >;\n}\n\nexport interface SyncManagerUpdateOptions {\n\tisSave?: boolean;\n\tisNewUndoLevel?: boolean;\n}\n\nexport interface RecordHandlers {\n\taddUndoMeta: ( ydoc: Y.Doc, meta: Map< string, any > ) => void;\n\teditRecord: (\n\t\tdata: Partial< ObjectData >,\n\t\toptions?: { undoIgnore?: boolean }\n\t) => void;\n\tgetEditedRecord: () => Promise< ObjectData >;\n\tonStatusChange: OnStatusChangeCallback;\n\trefetchRecord: () => Promise< void >;\n\trestoreUndoMeta: ( ydoc: Y.Doc, meta: Map< string, any > ) => void;\n\tsaveRecord: () => void;\n}\n\nexport interface SyncConfig {\n\tapplyChangesToCRDTDoc: (\n\t\tydoc: Y.Doc,\n\t\tchanges: Partial< ObjectData >\n\t) => void;\n\tcreateAwareness?: (\n\t\tydoc: Y.Doc,\n\t\tobjectId?: ObjectID\n\t) => Awareness | undefined;\n\tgetChangesFromCRDTDoc: (\n\t\tydoc: Y.Doc,\n\t\teditedRecord: ObjectData\n\t) => ObjectData;\n\tgetPersistedCRDTDoc?: ( record: ObjectData ) => string | null;\n}\n\nexport interface SyncManager {\n\tcreatePersistedCRDTDoc: (\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t) => string | null;\n\tgetAwareness: < State extends Awareness >(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t) => State | undefined;\n\tload: (\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData,\n\t\thandlers: RecordHandlers\n\t) => Promise< void >;\n\tloadCollection: (\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\thandlers: CollectionHandlers\n\t) => Promise< void >;\n\t// undoManager is undefined until the first entity is loaded.\n\tundoManager: SyncUndoManager | undefined;\n\tunload: ( objectType: ObjectType, objectId: ObjectID ) => void;\n\tupdate: (\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID | null,\n\t\tchanges: Partial< ObjectData >,\n\t\torigin: string,\n\t\toptions?: SyncManagerUpdateOptions\n\t) => void;\n}\n\nexport interface SyncUndoManager extends WPUndoManager< ObjectData > {\n\taddToScope: (\n\t\tymap: Y.Map< any >,\n\t\thandlers: Pick< RecordHandlers, 'addUndoMeta' | 'restoreUndoMeta' >\n\t) => void;\n\tstopCapturing: () => void;\n}\n"], | ||
| "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport type { UndoManager as WPUndoManager } from '@wordpress/undo-manager';\n\n/**\n * External dependencies\n */\nimport type * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\n/* globalThis */\ndeclare global {\n\tinterface Window {\n\t\t_wpCollaborationEnabled?: string;\n\t}\n}\n\nexport type CRDTDoc = Y.Doc;\nexport type AwarenessID = string;\nexport type EntityID = string;\nexport type ObjectID = string;\nexport type ObjectType = string;\n\n// An origin is a value passed by the transactor to identify the source of a\n// change. It can be any value, and is not used internally by Yjs. Origins are\n// preserved locally, while a remote change will have the provider instance as\n// its origin.\nexport type Origin = any;\n\n// Object data represents any entity record. There are not any expectations that\n// can hold on its shape, beyond a record with string keys and unknown values.\nexport type ObjectData = Record< string, unknown >;\n\n/**\n * Event map for provider events.\n * Add new event types here as needed.\n */\nexport interface ProviderEventMap {\n\tstatus: ConnectionStatus;\n}\n\n/**\n * Generic event listener type for providers.\n * Providers should call registered callbacks when events occur like connection status changes.\n * Providers are responsible for cleaning up listeners in their destroy() method.\n */\nexport type ProviderOn = < K extends keyof ProviderEventMap >(\n\tevent: K,\n\tcallback: ( data: ProviderEventMap[ K ] ) => void\n) => void;\n\nexport interface ProviderCreatorResult {\n\tdestroy: () => void;\n\ton: ProviderOn;\n}\n\n/**\n * Error codes for connection errors that can occur in sync providers.\n */\nexport type ConnectionErrorCode =\n\t| 'authentication-error'\n\t| 'connection-expired'\n\t| 'connection-limit-exceeded'\n\t| 'unknown-error';\n\n/**\n * Sync connection error object.\n */\nexport interface ConnectionError extends Error {\n\t/**\n\t * Error code identifier for programmatic handling and default message lookup.\n\t */\n\tcode: ConnectionErrorCode;\n}\n\n/**\n * Current connection status of a sync provider.\n */\nexport interface ConnectionStatusConnected {\n\tstatus: 'connected';\n}\n\nexport interface ConnectionStatusConnecting {\n\tstatus: 'connecting';\n}\n\nexport interface ConnectionStatusDisconnected {\n\tstatus: 'disconnected';\n\t/** Optional error information. */\n\terror?: ConnectionError;\n\t/** Milliseconds until the next automatic retry attempt. */\n\tretryInMs?: number;\n}\n\nexport type ConnectionStatus =\n\t| ConnectionStatusConnected\n\t| ConnectionStatusConnecting\n\t| ConnectionStatusDisconnected;\n\nexport type OnStatusChangeCallback = (\n\tstatus: ConnectionStatus | null\n) => void;\n\n/**\n * Options passed to a provider creator function when initializing a sync provider.\n */\nexport interface ProviderCreatorOptions {\n\tobjectType: ObjectType;\n\tobjectId: ObjectID | null;\n\tydoc: Y.Doc;\n\tawareness?: Awareness;\n}\n\nexport type ProviderCreator = (\n\toptions: ProviderCreatorOptions\n) => Promise< ProviderCreatorResult >;\n\nexport interface CollectionHandlers {\n\tonStatusChange: OnStatusChangeCallback;\n\trefetchRecords: () => Promise< void >;\n}\n\nexport interface SyncManagerUpdateOptions {\n\tisSave?: boolean;\n\tisNewUndoLevel?: boolean;\n}\n\nexport interface RecordHandlers {\n\taddUndoMeta: ( ydoc: Y.Doc, meta: Map< string, any > ) => void;\n\teditRecord: (\n\t\tdata: Partial< ObjectData >,\n\t\toptions?: { undoIgnore?: boolean }\n\t) => void;\n\tgetEditedRecord: () => Promise< ObjectData >;\n\tonStatusChange: OnStatusChangeCallback;\n\trefetchRecord: () => Promise< void >;\n\trestoreUndoMeta: ( ydoc: Y.Doc, meta: Map< string, any > ) => void;\n\tsaveRecord: () => void;\n}\n\nexport interface SyncConfig {\n\tapplyChangesToCRDTDoc: (\n\t\tydoc: Y.Doc,\n\t\tchanges: Partial< ObjectData >\n\t) => void;\n\tcreateAwareness?: (\n\t\tydoc: Y.Doc,\n\t\tobjectId?: ObjectID\n\t) => Awareness | undefined;\n\tgetChangesFromCRDTDoc: (\n\t\tydoc: Y.Doc,\n\t\teditedRecord: ObjectData\n\t) => ObjectData;\n\tgetPersistedCRDTDoc?: ( record: ObjectData ) => string | null;\n}\n\nexport interface SyncManager {\n\tcreatePersistedCRDTDoc: (\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t) => Promise< string | null >;\n\tgetAwareness: < State extends Awareness >(\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID\n\t) => State | undefined;\n\tload: (\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID,\n\t\trecord: ObjectData,\n\t\thandlers: RecordHandlers\n\t) => Promise< void >;\n\tloadCollection: (\n\t\tsyncConfig: SyncConfig,\n\t\tobjectType: ObjectType,\n\t\thandlers: CollectionHandlers\n\t) => Promise< void >;\n\t// undoManager is undefined until the first entity is loaded.\n\tundoManager: SyncUndoManager | undefined;\n\tunload: ( objectType: ObjectType, objectId: ObjectID ) => void;\n\tupdate: (\n\t\tobjectType: ObjectType,\n\t\tobjectId: ObjectID | null,\n\t\tchanges: Partial< ObjectData >,\n\t\torigin: string,\n\t\toptions?: SyncManagerUpdateOptions\n\t) => void;\n}\n\nexport interface SyncUndoManager extends WPUndoManager< ObjectData > {\n\taddToScope: (\n\t\tymap: Y.Map< any >,\n\t\thandlers: Pick< RecordHandlers, 'addUndoMeta' | 'restoreUndoMeta' >\n\t) => void;\n\tstopCapturing: () => void;\n}\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;", | ||
| "names": [] | ||
| } |
+5
-2
@@ -35,2 +35,3 @@ "use strict"; | ||
| deserializeCrdtDoc: () => deserializeCrdtDoc, | ||
| initializeYjsDoc: () => initializeYjsDoc, | ||
| markEntityAsSaved: () => markEntityAsSaved, | ||
@@ -47,6 +48,7 @@ serializeCrdtDoc: () => serializeCrdtDoc | ||
| ); | ||
| const ydoc = new Y.Doc({ meta: metaMap }); | ||
| return new Y.Doc({ meta: metaMap }); | ||
| } | ||
| function initializeYjsDoc(ydoc) { | ||
| const stateMap = ydoc.getMap(import_config.CRDT_STATE_MAP_KEY); | ||
| stateMap.set(import_config.CRDT_STATE_MAP_VERSION_KEY, import_config.CRDT_DOC_VERSION); | ||
| return ydoc; | ||
| } | ||
@@ -87,2 +89,3 @@ function markEntityAsSaved(ydoc) { | ||
| deserializeCrdtDoc, | ||
| initializeYjsDoc, | ||
| markEntityAsSaved, | ||
@@ -89,0 +92,0 @@ serializeCrdtDoc |
| { | ||
| "version": 3, | ||
| "sources": ["../src/utils.ts"], | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport * as buffer from 'lib0/buffer';\n\n/**\n * Internal dependencies\n */\nimport {\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_DOC_VERSION,\n\tCRDT_STATE_MAP_KEY,\n\tCRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,\n\tCRDT_STATE_MAP_SAVED_BY_KEY as SAVED_BY_KEY,\n\tCRDT_STATE_MAP_VERSION_KEY as VERSION_KEY,\n} from './config';\nimport type { CRDTDoc } from './types';\n\n// An object representation of CRDT document metadata.\ntype DocumentMeta = Record< string, DocumentMetaValue >;\ntype DocumentMetaValue = boolean | number | string;\n\nexport function createYjsDoc( documentMeta: DocumentMeta = {} ): Y.Doc {\n\t// Convert the object representation of CRDT document metadata to a map.\n\t// Document metadata is passed to the Y.Doc constructor and stored in its\n\t// `meta` property. It is not synced to peers or persisted with the document.\n\t// It is just a place to store transient information about this doc instance.\n\tconst metaMap = new Map< string, DocumentMetaValue >(\n\t\tObject.entries( documentMeta )\n\t);\n\n\tconst ydoc = new Y.Doc( { meta: metaMap } );\n\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\n\tstateMap.set( VERSION_KEY, CRDT_DOC_VERSION );\n\n\treturn ydoc;\n}\n\n/**\n * Record that the entity was saved (persisted to the database) in the CRDT\n * document record metadata.\n *\n * @param {CRDTDoc} ydoc CRDT document.\n */\nexport function markEntityAsSaved( ydoc: CRDTDoc ): void {\n\tconst recordMeta = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\trecordMeta.set( SAVED_AT_KEY, Date.now() );\n\trecordMeta.set( SAVED_BY_KEY, ydoc.clientID );\n}\n\nfunction pseudoRandomID(): number {\n\treturn Math.floor( Math.random() * 1000000000 );\n}\n\nexport function serializeCrdtDoc( crdtDoc: CRDTDoc ): string {\n\treturn JSON.stringify( {\n\t\tdocument: buffer.toBase64( Y.encodeStateAsUpdateV2( crdtDoc ) ),\n\t\tupdateId: pseudoRandomID(), // helps with debugging\n\t} );\n}\n\nexport function deserializeCrdtDoc(\n\tserializedCrdtDoc: string\n): CRDTDoc | null {\n\ttry {\n\t\tconst { document } = JSON.parse( serializedCrdtDoc );\n\n\t\t// Mark this document as from persistence.\n\t\tconst docMeta: DocumentMeta = {\n\t\t\t[ CRDT_DOC_META_PERSISTENCE_KEY ]: true,\n\t\t};\n\n\t\t// Apply the document as an update against a new (temporary) Y.Doc.\n\t\tconst ydoc = createYjsDoc( docMeta );\n\t\tconst yupdate = buffer.fromBase64( document );\n\t\tY.applyUpdateV2( ydoc, yupdate );\n\n\t\t// Overwrite the client ID (which is from a previous session) with a random\n\t\t// client ID. Deserialized documents should not be used directly. Instead,\n\t\t// their state should be applied to another in-use document.\n\t\tydoc.clientID = pseudoRandomID();\n\n\t\treturn ydoc;\n\t} catch ( e ) {\n\t\treturn null;\n\t}\n}\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,QAAmB;AACnB,aAAwB;AAKxB,oBAOO;AAOA,SAAS,aAAc,eAA6B,CAAC,GAAW;AAKtE,QAAM,UAAU,IAAI;AAAA,IACnB,OAAO,QAAS,YAAa;AAAA,EAC9B;AAEA,QAAM,OAAO,IAAM,MAAK,EAAE,MAAM,QAAQ,CAAE;AAC1C,QAAM,WAAW,KAAK,OAAQ,gCAAmB;AAEjD,WAAS,IAAK,cAAAA,4BAAa,8BAAiB;AAE5C,SAAO;AACR;AAQO,SAAS,kBAAmB,MAAsB;AACxD,QAAM,aAAa,KAAK,OAAQ,gCAAmB;AACnD,aAAW,IAAK,cAAAC,6BAAc,KAAK,IAAI,CAAE;AACzC,aAAW,IAAK,cAAAC,6BAAc,KAAK,QAAS;AAC7C;AAEA,SAAS,iBAAyB;AACjC,SAAO,KAAK,MAAO,KAAK,OAAO,IAAI,GAAW;AAC/C;AAEO,SAAS,iBAAkB,SAA2B;AAC5D,SAAO,KAAK,UAAW;AAAA,IACtB,UAAiB,gBAAY,wBAAuB,OAAQ,CAAE;AAAA,IAC9D,UAAU,eAAe;AAAA;AAAA,EAC1B,CAAE;AACH;AAEO,SAAS,mBACf,mBACiB;AACjB,MAAI;AACH,UAAM,EAAE,SAAS,IAAI,KAAK,MAAO,iBAAkB;AAGnD,UAAM,UAAwB;AAAA,MAC7B,CAAE,2CAA8B,GAAG;AAAA,IACpC;AAGA,UAAM,OAAO,aAAc,OAAQ;AACnC,UAAM,UAAiB,kBAAY,QAAS;AAC5C,IAAE,gBAAe,MAAM,OAAQ;AAK/B,SAAK,WAAW,eAAe;AAE/B,WAAO;AAAA,EACR,SAAU,GAAI;AACb,WAAO;AAAA,EACR;AACD;", | ||
| "sourcesContent": ["/**\n * External dependencies\n */\nimport * as Y from 'yjs';\nimport * as buffer from 'lib0/buffer';\n\n/**\n * Internal dependencies\n */\nimport {\n\tCRDT_DOC_META_PERSISTENCE_KEY,\n\tCRDT_DOC_VERSION,\n\tCRDT_STATE_MAP_KEY,\n\tCRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,\n\tCRDT_STATE_MAP_SAVED_BY_KEY as SAVED_BY_KEY,\n\tCRDT_STATE_MAP_VERSION_KEY as VERSION_KEY,\n} from './config';\nimport type { CRDTDoc } from './types';\n\n// An object representation of CRDT document metadata.\ntype DocumentMeta = Record< string, DocumentMetaValue >;\ntype DocumentMetaValue = boolean | number | string;\n\n/**\n * Creates a new Y.Doc instance with the given document metadata.\n *\n * @param {DocumentMeta} documentMeta Optional metadata to associate with the\n * document. Metadata is not persisted.\n */\nexport function createYjsDoc( documentMeta: DocumentMeta = {} ): CRDTDoc {\n\t// Convert the object representation of CRDT document metadata to a map.\n\t// Document metadata is passed to the Y.Doc constructor and stored in its\n\t// `meta` property. It is not synced to peers or persisted with the document.\n\t// It is just a place to store transient information about this doc instance.\n\tconst metaMap = new Map< string, DocumentMetaValue >(\n\t\tObject.entries( documentMeta )\n\t);\n\n\t// IMPORTANT: Do not add update the document itself to avoid generating updates\n\t// before observers are attached. Add initial updates in `initializeYjsDoc`.\n\treturn new Y.Doc( { meta: metaMap } );\n}\n\n/**\n * Initializes a Y.Doc instance with the necessary CRDT state for our use case.\n *\n * @param {Y.Doc} ydoc Y.Doc instance to initialize.\n */\nexport function initializeYjsDoc( ydoc: CRDTDoc ): void {\n\tconst stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\tstateMap.set( VERSION_KEY, CRDT_DOC_VERSION );\n}\n\n/**\n * Record that the entity was saved (persisted to the database) in the CRDT\n * document record metadata.\n *\n * @param {CRDTDoc} ydoc CRDT document.\n */\nexport function markEntityAsSaved( ydoc: CRDTDoc ): void {\n\tconst recordMeta = ydoc.getMap( CRDT_STATE_MAP_KEY );\n\trecordMeta.set( SAVED_AT_KEY, Date.now() );\n\trecordMeta.set( SAVED_BY_KEY, ydoc.clientID );\n}\n\nfunction pseudoRandomID(): number {\n\treturn Math.floor( Math.random() * 1000000000 );\n}\n\nexport function serializeCrdtDoc( crdtDoc: CRDTDoc ): string {\n\treturn JSON.stringify( {\n\t\tdocument: buffer.toBase64( Y.encodeStateAsUpdateV2( crdtDoc ) ),\n\t\tupdateId: pseudoRandomID(), // helps with debugging\n\t} );\n}\n\nexport function deserializeCrdtDoc(\n\tserializedCrdtDoc: string\n): CRDTDoc | null {\n\ttry {\n\t\tconst { document } = JSON.parse( serializedCrdtDoc );\n\n\t\t// Mark this document as from persistence.\n\t\tconst docMeta: DocumentMeta = {\n\t\t\t[ CRDT_DOC_META_PERSISTENCE_KEY ]: true,\n\t\t};\n\n\t\t// Apply the document as an update against a new (temporary) Y.Doc.\n\t\tconst ydoc = createYjsDoc( docMeta );\n\t\tconst yupdate = buffer.fromBase64( document );\n\t\tY.applyUpdateV2( ydoc, yupdate );\n\n\t\t// Overwrite the client ID (which is from a previous session) with a random\n\t\t// client ID. Deserialized documents should not be used directly. Instead,\n\t\t// their state should be applied to another in-use document.\n\t\tydoc.clientID = pseudoRandomID();\n\n\t\treturn ydoc;\n\t} catch ( e ) {\n\t\treturn null;\n\t}\n}\n"], | ||
| "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,QAAmB;AACnB,aAAwB;AAKxB,oBAOO;AAaA,SAAS,aAAc,eAA6B,CAAC,GAAa;AAKxE,QAAM,UAAU,IAAI;AAAA,IACnB,OAAO,QAAS,YAAa;AAAA,EAC9B;AAIA,SAAO,IAAM,MAAK,EAAE,MAAM,QAAQ,CAAE;AACrC;AAOO,SAAS,iBAAkB,MAAsB;AACvD,QAAM,WAAW,KAAK,OAAQ,gCAAmB;AACjD,WAAS,IAAK,cAAAA,4BAAa,8BAAiB;AAC7C;AAQO,SAAS,kBAAmB,MAAsB;AACxD,QAAM,aAAa,KAAK,OAAQ,gCAAmB;AACnD,aAAW,IAAK,cAAAC,6BAAc,KAAK,IAAI,CAAE;AACzC,aAAW,IAAK,cAAAC,6BAAc,KAAK,QAAS;AAC7C;AAEA,SAAS,iBAAyB;AACjC,SAAO,KAAK,MAAO,KAAK,OAAO,IAAI,GAAW;AAC/C;AAEO,SAAS,iBAAkB,SAA2B;AAC5D,SAAO,KAAK,UAAW;AAAA,IACtB,UAAiB,gBAAY,wBAAuB,OAAQ,CAAE;AAAA,IAC9D,UAAU,eAAe;AAAA;AAAA,EAC1B,CAAE;AACH;AAEO,SAAS,mBACf,mBACiB;AACjB,MAAI;AACH,UAAM,EAAE,SAAS,IAAI,KAAK,MAAO,iBAAkB;AAGnD,UAAM,UAAwB;AAAA,MAC7B,CAAE,2CAA8B,GAAG;AAAA,IACpC;AAGA,UAAM,OAAO,aAAc,OAAQ;AACnC,UAAM,UAAiB,kBAAY,QAAS;AAC5C,IAAE,gBAAe,MAAM,OAAQ;AAK/B,SAAK,WAAW,eAAe;AAE/B,WAAO;AAAA,EACR,SAAU,GAAI;AACb,WAAO;AAAA,EACR;AACD;", | ||
| "names": ["VERSION_KEY", "SAVED_AT_KEY", "SAVED_BY_KEY"] | ||
| } |
+2
-0
@@ -5,2 +5,4 @@ <!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. --> | ||
| ## 1.41.0 (2026-03-04) | ||
| ## 1.40.0 (2026-02-18) | ||
@@ -7,0 +9,0 @@ |
+6
-6
| { | ||
| "name": "@wordpress/sync", | ||
| "version": "1.40.1-next.v.202602271551.0+464abe399", | ||
| "version": "1.41.0", | ||
| "description": "Sync Data.", | ||
@@ -47,6 +47,6 @@ "author": "The WordPress Contributors", | ||
| "dependencies": { | ||
| "@wordpress/api-fetch": "^7.40.1-next.v.202602271551.0+464abe399", | ||
| "@wordpress/hooks": "^4.40.1-next.v.202602271551.0+464abe399", | ||
| "@wordpress/private-apis": "^1.40.1-next.v.202602271551.0+464abe399", | ||
| "@wordpress/undo-manager": "^1.40.1-next.v.202602271551.0+464abe399", | ||
| "@wordpress/api-fetch": "^7.41.0", | ||
| "@wordpress/hooks": "^4.41.0", | ||
| "@wordpress/private-apis": "^1.41.0", | ||
| "@wordpress/undo-manager": "^1.41.0", | ||
| "diff": "^8.0.3", | ||
@@ -65,3 +65,3 @@ "fast-deep-equal": "^3.1.3", | ||
| }, | ||
| "gitHead": "95aa7055a5757219e2d96a91efc69f7dd1b2d4c3" | ||
| "gitHead": "8bfc179b9aed74c0a6dd6e8edf7a49e40e4f87cc" | ||
| } |
+14
-2
@@ -40,2 +40,3 @@ /** | ||
| deserializeCrdtDoc, | ||
| initializeYjsDoc, | ||
| markEntityAsSaved, | ||
@@ -288,2 +289,5 @@ serializeCrdtDoc, | ||
| // Initialize the Yjs document with the necessary CRDT state. | ||
| initializeYjsDoc( ydoc ); | ||
| // Get and apply the persisted CRDT document, if it exists. | ||
@@ -389,2 +393,5 @@ internal.applyPersistedCrdtDoc( objectType, objectId, record ); | ||
| stateMap.observe( onStateMapUpdate ); | ||
| // Initialize the Yjs document with the necessary CRDT state. | ||
| initializeYjsDoc( ydoc ); | ||
| } | ||
@@ -632,6 +639,6 @@ | ||
| */ | ||
| function createPersistedCRDTDoc( | ||
| async function createPersistedCRDTDoc( | ||
| objectType: ObjectType, | ||
| objectId: ObjectID | ||
| ): string | null { | ||
| ): Promise< string | null > { | ||
| const entityId = getEntityId( objectType, objectId ); | ||
@@ -644,2 +651,7 @@ const entityState = entityStates.get( entityId ); | ||
| // Y.Doc updates are deferred via yieldToEventLoop. Await a promise that | ||
| // resolves on the next tick of the event loop so pending updates are flushed | ||
| // before we serialize the document. | ||
| await new Promise( ( resolve ) => setTimeout( resolve, 0 ) ); | ||
| return serializeCrdtDoc( entityState.ydoc ); | ||
@@ -646,0 +658,0 @@ } |
@@ -11,2 +11,3 @@ /** | ||
| import { createSyncManager } from './manager'; | ||
| import { pollingManager } from './providers/http-polling/polling-manager'; | ||
| import { default as Delta } from './quill-delta/Delta'; | ||
@@ -22,2 +23,3 @@ | ||
| LOCAL_EDITOR_ORIGIN, | ||
| retrySyncConnection: () => pollingManager.retryNow(), | ||
| } ); |
@@ -85,9 +85,12 @@ /** | ||
| /** | ||
| * Emit connection status. | ||
| * Emit connection status, passing the full object through so that | ||
| * additional fields (e.g. `retryInMs`) are preserved for consumers. | ||
| * | ||
| * @param status The connection status | ||
| * @param status.error Optional error information when status is 'disconnected' | ||
| * @param status.status The connection status ('connected', 'connecting', 'disconnected') | ||
| * @param connectionStatus The connection status object | ||
| */ | ||
| protected emitStatus = ( { error, status }: ConnectionStatus ): void => { | ||
| protected emitStatus = ( connectionStatus: ConnectionStatus ): void => { | ||
| const { status } = connectionStatus; | ||
| const error = | ||
| status === 'disconnected' ? connectionStatus.error : undefined; | ||
| if ( this.status === status && ! error ) { | ||
@@ -106,3 +109,3 @@ return; | ||
| this.status = status; | ||
| this.emit( 'status', [ { error, status } ] ); | ||
| this.emit( 'status', [ connectionStatus ] ); | ||
| }; | ||
@@ -109,0 +112,0 @@ |
@@ -33,3 +33,5 @@ /** | ||
| const POLLING_INTERVAL_WITH_COLLABORATORS_IN_MS = 250; // 250 milliseconds | ||
| const POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 30 * 1000; // 30 seconds | ||
| // Must be less than the server-side AWARENESS_TIMEOUT (30 s) to avoid | ||
| // false disconnects when the tab is in the background. | ||
| const POLLING_INTERVAL_BACKGROUND_TAB_IN_MS = 25 * 1000; // 25 seconds | ||
| const MAX_ERROR_BACKOFF_IN_MS = 30 * 1000; // 30 seconds | ||
@@ -42,2 +44,3 @@ const POLLING_MANAGER_ORIGIN = 'polling-manager'; | ||
| registerRoom: ( options: RegisterRoomOptions ) => void; | ||
| retryNow: () => void; | ||
| unregisterRoom: ( room: string ) => void; | ||
@@ -302,5 +305,11 @@ } | ||
| * | ||
| * This ensures that any updates by collaborators are immediately reflected | ||
| * in the document once the browser tab becomes active. Otherwise there would | ||
| * be a delay of 30 seconds before the updates came through. | ||
| * This ensures that any updates by collaborators are immediately | ||
| * reflected in the document once the browser tab becomes active. | ||
| * Otherwise there would be a delay of up to 30 seconds before the | ||
| * updates came through. | ||
| * | ||
| * Only repoll if we cleared a pending timeout, meaning the poll loop | ||
| * was idle between cycles. If no timeout is pending, a poll request | ||
| * is already in-flight and will pick up the updated isActiveBrowser | ||
| * value when it schedules the next cycle. | ||
| */ | ||
@@ -310,5 +319,2 @@ if ( pollingTimeoutId ) { | ||
| pollingTimeoutId = null; | ||
| } | ||
| if ( isPolling ) { | ||
| poll(); | ||
@@ -321,2 +327,3 @@ } | ||
| isPolling = true; | ||
| pollingTimeoutId = null; | ||
@@ -446,3 +453,6 @@ async function start(): Promise< void > { | ||
| roomStates.forEach( ( state ) => { | ||
| state.onStatusChange( { status: 'disconnected' } ); | ||
| state.onStatusChange( { | ||
| status: 'disconnected', | ||
| retryInMs: pollInterval, | ||
| } ); | ||
| } ); | ||
@@ -558,5 +568,21 @@ } | ||
| /** | ||
| * Immediately retry the sync connection by cancelling any pending backoff | ||
| * timeout and triggering a new poll. If a request is already in-flight, | ||
| * the backoff interval is reset so the next scheduled poll fires sooner. | ||
| */ | ||
| function retryNow(): void { | ||
| pollInterval = POLLING_INTERVAL_IN_MS * 2; | ||
| if ( pollingTimeoutId ) { | ||
| clearTimeout( pollingTimeoutId ); | ||
| pollingTimeoutId = null; | ||
| poll(); | ||
| } | ||
| } | ||
| export const pollingManager: PollingManager = { | ||
| registerRoom, | ||
| retryNow, | ||
| unregisterRoom, | ||
| }; |
@@ -27,2 +27,17 @@ // File copied https://github.com/slab/delta/blob/main/src/Delta.ts with changes: | ||
| /** | ||
| * Normalize diff changes so that `count` reflects UTF-16 code-unit length | ||
| * rather than grapheme-cluster count (which diffChars may return when | ||
| * Intl.Segmenter is available, e.g. diff v8+). | ||
| * | ||
| * @param changes - The array of changes from diffChars. | ||
| * @return The changes with `count` normalized to UTF-16 code-unit length. | ||
| */ | ||
| function normalizeChangeCounts( changes: Change[] ): Change[] { | ||
| return changes.map( ( change ) => ( { | ||
| ...change, | ||
| count: change.value.length, | ||
| } ) ); | ||
| } | ||
| interface EmbedHandler< T > { | ||
@@ -403,3 +418,5 @@ compose: ( a: T, b: T, keepNull: boolean ) => T; | ||
| const strings = this.deltasToStrings( other ); | ||
| const diffResult = diffChars( strings[ 0 ], strings[ 1 ] ); | ||
| const diffResult = normalizeChangeCounts( | ||
| diffChars( strings[ 0 ], strings[ 1 ] ) | ||
| ); | ||
| const thisIter = new OpIterator( this.ops ); | ||
@@ -617,3 +634,5 @@ const otherIter = new OpIterator( other.ops ); | ||
| const strings = this.deltasToStrings( other ); | ||
| let diffs = diffChars( strings[ 0 ], strings[ 1 ] ); | ||
| let diffs = normalizeChangeCounts( | ||
| diffChars( strings[ 0 ], strings[ 1 ] ) | ||
| ); | ||
| let lastDiffPosition = 0; | ||
@@ -620,0 +639,0 @@ const adjustedDiffs: Change[] = []; |
@@ -461,2 +461,233 @@ /** | ||
| } ); | ||
| describe( 'emoji and surrogate pair handling', () => { | ||
| // Emoji like 😀 (U+1F600) are represented as surrogate pairs in UTF-16, | ||
| // so '😀'.length === 2 in JavaScript. The diff engine must handle these | ||
| // correctly without splitting surrogate pairs. | ||
| it( 'should handle diff with emoji in unchanged prefix', () => { | ||
| // '😀' -> '😀x' — append 'x' after emoji | ||
| const oldDelta = new Delta().insert( '😀' ); | ||
| const newDelta = new Delta().insert( '😀x' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // 😀 is 2 UTF-16 code units, so retain 2, insert 'x' | ||
| expect( diff.ops ).toEqual( [ { retain: 2 }, { insert: 'x' } ] ); | ||
| } ); | ||
| it( 'should handle diff replacing text after emoji', () => { | ||
| // 'a😀b' -> 'a😀c' | ||
| const oldDelta = new Delta().insert( 'a😀b' ); | ||
| const newDelta = new Delta().insert( 'a😀c' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // retain 3 (a + 😀 = 1 + 2), then replace b with c | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 3 }, | ||
| { insert: 'c' }, | ||
| { delete: 1 }, | ||
| ] ); | ||
| } ); | ||
| it( 'should handle diffWithCursor inserting text after emoji', () => { | ||
| // 'a😀b' -> 'a😀xb' with cursor at 4 (a=1, 😀=2, x=1) | ||
| const oldDelta = new Delta().insert( 'a😀b' ); | ||
| const newDelta = new Delta().insert( 'a😀xb' ); | ||
| const cursor = 4; | ||
| const diff = oldDelta.diffWithCursor( newDelta, cursor ); | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 3 }, // a(1) + 😀(2) | ||
| { insert: 'x' }, | ||
| ] ); | ||
| } ); | ||
| it( 'should handle diffWithCursor inserting an emoji', () => { | ||
| // 'ab' -> 'a😀b' with cursor at 3 (a=1, 😀=2) | ||
| const oldDelta = new Delta().insert( 'ab' ); | ||
| const newDelta = new Delta().insert( 'a😀b' ); | ||
| const cursor = 3; | ||
| const diff = oldDelta.diffWithCursor( newDelta, cursor ); | ||
| expect( diff.ops ).toEqual( [ { retain: 1 }, { insert: '😀' } ] ); | ||
| } ); | ||
| it( 'should handle diffWithCursor deleting an emoji', () => { | ||
| // 'a😀b' -> 'ab' with cursor at 1 (after 'a', emoji deleted) | ||
| const oldDelta = new Delta().insert( 'a😀b' ); | ||
| const newDelta = new Delta().insert( 'ab' ); | ||
| const cursor = 1; | ||
| const diff = oldDelta.diffWithCursor( newDelta, cursor ); | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 1 }, | ||
| { delete: 2 }, // 😀 is 2 UTF-16 code units | ||
| ] ); | ||
| } ); | ||
| it( 'should handle inserting between two emoji', () => { | ||
| // '😀😀' -> '😀x😀' with cursor at 3 (😀=2, x=1) | ||
| const oldDelta = new Delta().insert( '😀😀' ); | ||
| const newDelta = new Delta().insert( '😀x😀' ); | ||
| const cursor = 3; | ||
| const diff = oldDelta.diffWithCursor( newDelta, cursor ); | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 2 }, // first 😀 | ||
| { insert: 'x' }, | ||
| ] ); | ||
| } ); | ||
| it( 'should handle diff with emoji-only strings', () => { | ||
| // '😀🎉' -> '😀🚀🎉' — insert 🚀 between two emoji | ||
| const oldDelta = new Delta().insert( '😀🎉' ); | ||
| const newDelta = new Delta().insert( '😀🚀🎉' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 2 }, // 😀 | ||
| { insert: '🚀' }, | ||
| ] ); | ||
| } ); | ||
| it( 'should preserve emoji when diffing identical strings', () => { | ||
| const oldDelta = new Delta().insert( 'Hello 😀 World' ); | ||
| const newDelta = new Delta().insert( 'Hello 😀 World' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| expect( diff.ops ).toEqual( [] ); | ||
| } ); | ||
| it( 'should handle diff with mixed emoji and regular text changes', () => { | ||
| // 'Hello 😀 World' -> 'Hello 😀 Beautiful World' | ||
| const oldDelta = new Delta().insert( 'Hello 😀 World' ); | ||
| const newDelta = new Delta().insert( 'Hello 😀 Beautiful World' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // 'Hello 😀 ' is 6+2+1 = 9 UTF-16 code units | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 9 }, | ||
| { insert: 'Beautiful ' }, | ||
| ] ); | ||
| } ); | ||
| it( 'should handle compound emoji (flag emoji)', () => { | ||
| // Flag emoji like 🏳️🌈 are compound and has .length === 6 in JavaScript | ||
| const oldDelta = new Delta().insert( 'a🏳️🌈b' ); | ||
| const newDelta = new Delta().insert( 'a🏳️🌈xb' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // a(1) + 🏳️🌈 (6) = 7 code units to retain | ||
| expect( diff.ops ).toEqual( [ { retain: 7 }, { insert: 'x' } ] ); | ||
| } ); | ||
| it( 'should handle emoji with skin tone modifier', () => { | ||
| // 👋🏽 is base emoji + skin tone modifier, .length === 4 | ||
| const oldDelta = new Delta().insert( 'Hi 👋🏽' ); | ||
| const newDelta = new Delta().insert( 'Hi 👋🏽!' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // 'Hi '(3) + '👋🏽'(4) = 7 code units to retain | ||
| expect( diff.ops ).toEqual( [ { retain: 7 }, { insert: '!' } ] ); | ||
| } ); | ||
| } ); | ||
| describe( 'supplementary plane characters (non-emoji)', () => { | ||
| // Characters in the supplementary Unicode planes (U+10000+) are stored | ||
| // as surrogate pairs in UTF-16, so .length === 2 per character. The | ||
| // diff library v8 counts them as 1 grapheme cluster via Intl.Segmenter, | ||
| // causing the same mismatch as emoji. | ||
| it( 'should handle CJK Extension B characters (rare kanji)', () => { | ||
| // 𠮷 (U+20BB7) is a real kanji used in Japanese names (𠮷野家). | ||
| // It's in the supplementary plane: .length === 2 (surrogate pair). | ||
| const oldDelta = new Delta().insert( '𠮷野家' ); | ||
| const newDelta = new Delta().insert( '𠮷野家は美味しい' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // '𠮷'(2) + '野'(1) + '家'(1) = 4 code units to retain | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 4 }, | ||
| { insert: 'は美味しい' }, | ||
| ] ); | ||
| } ); | ||
| it( 'should handle diffWithCursor inserting after CJK Extension B character', () => { | ||
| // 'a𠮷b' -> 'a𠮷xb' | ||
| const oldDelta = new Delta().insert( 'a𠮷b' ); | ||
| const newDelta = new Delta().insert( 'a𠮷xb' ); | ||
| const cursor = 4; // a(1) + 𠮷(2) + x(1) | ||
| const diff = oldDelta.diffWithCursor( newDelta, cursor ); | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 3 }, // a(1) + 𠮷(2) | ||
| { insert: 'x' }, | ||
| ] ); | ||
| } ); | ||
| it( 'should handle mathematical symbols from supplementary plane', () => { | ||
| // 𝐀 (U+1D400, Mathematical Bold Capital A) — .length === 2 | ||
| const oldDelta = new Delta().insert( 'Let 𝐀 be a matrix' ); | ||
| const newDelta = new Delta().insert( 'Let 𝐀 be a square matrix' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // 'Let 𝐀 be a ' = L(1)+e(1)+t(1)+' '(1)+𝐀(2)+' '(1)+b(1)+e(1)+' '(1)+a(1)+' '(1) = 12 | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 12 }, | ||
| { insert: 'square ' }, | ||
| ] ); | ||
| } ); | ||
| it( 'should handle mixed surrogate pairs and BMP text', () => { | ||
| // Mix of supplementary plane characters in one string. | ||
| // '𠮷' (CJK Ext B, surrogate pair) + '😀' (emoji, surrogate pair) | ||
| const oldDelta = new Delta().insert( '𠮷😀' ); | ||
| const newDelta = new Delta().insert( '𠮷😀!' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // 𠮷(2) + 😀(2) = 4 code units | ||
| expect( diff.ops ).toEqual( [ { retain: 4 }, { insert: '!' } ] ); | ||
| } ); | ||
| it( 'should handle musical symbols', () => { | ||
| // 𝄞 (U+1D11E, Musical Symbol G Clef) — .length === 2 | ||
| const oldDelta = new Delta().insert( 'Play 𝄞 in C' ); | ||
| const newDelta = new Delta().insert( 'Play 𝄞 in D' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // 'Play 𝄞 in ' = P(1)+l(1)+a(1)+y(1)+' '(1)+𝄞(2)+' '(1)+i(1)+n(1)+' '(1) = 11 | ||
| expect( diff.ops ).toEqual( [ | ||
| { retain: 11 }, | ||
| { insert: 'D' }, | ||
| { delete: 1 }, | ||
| ] ); | ||
| } ); | ||
| it( 'should handle ancient script characters (Egyptian hieroglyphs)', () => { | ||
| // 𓀀 (U+13000, Egyptian Hieroglyph A001) — .length === 2 | ||
| const oldDelta = new Delta().insert( 'a𓀀b' ); | ||
| const newDelta = new Delta().insert( 'a𓀀xb' ); | ||
| const diff = oldDelta.diff( newDelta ); | ||
| // a(1) + 𓀀(2) = 3 code units to retain | ||
| expect( diff.ops ).toEqual( [ { retain: 3 }, { insert: 'x' } ] ); | ||
| } ); | ||
| } ); | ||
| } ); |
+82
-2
@@ -11,7 +11,15 @@ /** | ||
| */ | ||
| import { createYjsDoc, serializeCrdtDoc, deserializeCrdtDoc } from '../utils'; | ||
| import { | ||
| createYjsDoc, | ||
| initializeYjsDoc, | ||
| markEntityAsSaved, | ||
| serializeCrdtDoc, | ||
| deserializeCrdtDoc, | ||
| } from '../utils'; | ||
| import { | ||
| CRDT_DOC_META_PERSISTENCE_KEY, | ||
| CRDT_DOC_VERSION, | ||
| CRDT_STATE_MAP_KEY, | ||
| CRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY, | ||
| CRDT_STATE_MAP_SAVED_BY_KEY as SAVED_BY_KEY, | ||
| CRDT_STATE_MAP_VERSION_KEY as VERSION_KEY, | ||
@@ -44,10 +52,80 @@ } from '../config'; | ||
| it( 'does not advance the state vector when creating the doc', () => { | ||
| const ydoc = createYjsDoc(); | ||
| const sv = Y.decodeStateVector( Y.encodeStateVector( ydoc ) ); | ||
| // createYjsDoc does not make any updates to the doc, so the state | ||
| // vector for the doc's client ID should be undefined. | ||
| expect( sv.get( ydoc.clientID ) ).toBeUndefined(); | ||
| } ); | ||
| } ); | ||
| describe( 'initializeYjsDoc', () => { | ||
| it( 'sets the CRDT document version in the state map', () => { | ||
| const ydoc = createYjsDoc( {} ); | ||
| const ydoc = new Y.Doc( {} ); | ||
| const stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY ); | ||
| initializeYjsDoc( ydoc ); | ||
| expect( stateMap.get( VERSION_KEY ) ).toBe( CRDT_DOC_VERSION ); | ||
| } ); | ||
| it( 'advances the state vector when setting the version key', () => { | ||
| const ydoc = new Y.Doc(); | ||
| initializeYjsDoc( ydoc ); | ||
| const sv = Y.decodeStateVector( Y.encodeStateVector( ydoc ) ); | ||
| // createYjsDoc sets VERSION_KEY via stateMap.set(), which creates | ||
| // a Yjs operation. This advances the state vector for the doc's | ||
| // client ID to clock 1. | ||
| expect( sv.get( ydoc.clientID ) ).toBe( 1 ); | ||
| } ); | ||
| it( 'fires an update event when setting the version key', () => { | ||
| const updates: Uint8Array[] = []; | ||
| const ydoc = new Y.Doc(); | ||
| ydoc.on( 'update', ( update: Uint8Array ) => { | ||
| updates.push( update ); | ||
| } ); | ||
| initializeYjsDoc( ydoc ); | ||
| expect( updates ).toHaveLength( 1 ); | ||
| } ); | ||
| } ); | ||
| describe( 'markEntityAsSaved', () => { | ||
| it( 'sets the saved-at timestamp and saved-by client ID', () => { | ||
| const ydoc = createYjsDoc(); | ||
| const before = Date.now(); | ||
| markEntityAsSaved( ydoc ); | ||
| const stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY ); | ||
| const savedAt = stateMap.get( SAVED_AT_KEY ) as number; | ||
| const savedBy = stateMap.get( SAVED_BY_KEY ); | ||
| expect( savedAt ).toBeGreaterThanOrEqual( before ); | ||
| expect( savedAt ).toBeLessThanOrEqual( Date.now() ); | ||
| expect( savedBy ).toBe( ydoc.clientID ); | ||
| } ); | ||
| it( 'updates the timestamp on subsequent calls', () => { | ||
| const ydoc = createYjsDoc(); | ||
| markEntityAsSaved( ydoc ); | ||
| const stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY ); | ||
| const firstSavedAt = stateMap.get( SAVED_AT_KEY ) as number; | ||
| // Small delay to ensure timestamp changes. | ||
| markEntityAsSaved( ydoc ); | ||
| const secondSavedAt = stateMap.get( SAVED_AT_KEY ) as number; | ||
| expect( secondSavedAt ).toBeGreaterThanOrEqual( firstSavedAt ); | ||
| } ); | ||
| } ); | ||
| describe( 'serializeCrdtDoc', () => { | ||
@@ -80,2 +158,4 @@ let testDoc: Y.Doc; | ||
| originalDoc = createYjsDoc(); | ||
| initializeYjsDoc( originalDoc ); | ||
| const ymap = originalDoc.getMap( 'testMap' ); | ||
@@ -82,0 +162,0 @@ ymap.set( 'title', 'Test Title' ); |
+19
-7
@@ -78,13 +78,25 @@ /** | ||
| /** | ||
| * Current connection status of a sync provider, including status and optional error information. | ||
| * Current connection status of a sync provider. | ||
| */ | ||
| export interface ConnectionStatus { | ||
| status: 'connected' | 'connecting' | 'disconnected'; | ||
| export interface ConnectionStatusConnected { | ||
| status: 'connected'; | ||
| } | ||
| /** | ||
| * Optional error information when status is 'disconnected'. | ||
| */ | ||
| export interface ConnectionStatusConnecting { | ||
| status: 'connecting'; | ||
| } | ||
| export interface ConnectionStatusDisconnected { | ||
| status: 'disconnected'; | ||
| /** Optional error information. */ | ||
| error?: ConnectionError; | ||
| /** Milliseconds until the next automatic retry attempt. */ | ||
| retryInMs?: number; | ||
| } | ||
| export type ConnectionStatus = | ||
| | ConnectionStatusConnected | ||
| | ConnectionStatusConnecting | ||
| | ConnectionStatusDisconnected; | ||
| export type OnStatusChangeCallback = ( | ||
@@ -151,3 +163,3 @@ status: ConnectionStatus | null | ||
| objectId: ObjectID | ||
| ) => string | null; | ||
| ) => Promise< string | null >; | ||
| getAwareness: < State extends Awareness >( | ||
@@ -154,0 +166,0 @@ objectType: ObjectType, |
+18
-5
@@ -24,3 +24,9 @@ /** | ||
| export function createYjsDoc( documentMeta: DocumentMeta = {} ): Y.Doc { | ||
| /** | ||
| * Creates a new Y.Doc instance with the given document metadata. | ||
| * | ||
| * @param {DocumentMeta} documentMeta Optional metadata to associate with the | ||
| * document. Metadata is not persisted. | ||
| */ | ||
| export function createYjsDoc( documentMeta: DocumentMeta = {} ): CRDTDoc { | ||
| // Convert the object representation of CRDT document metadata to a map. | ||
@@ -34,8 +40,15 @@ // Document metadata is passed to the Y.Doc constructor and stored in its | ||
| const ydoc = new Y.Doc( { meta: metaMap } ); | ||
| // IMPORTANT: Do not add update the document itself to avoid generating updates | ||
| // before observers are attached. Add initial updates in `initializeYjsDoc`. | ||
| return new Y.Doc( { meta: metaMap } ); | ||
| } | ||
| /** | ||
| * Initializes a Y.Doc instance with the necessary CRDT state for our use case. | ||
| * | ||
| * @param {Y.Doc} ydoc Y.Doc instance to initialize. | ||
| */ | ||
| export function initializeYjsDoc( ydoc: CRDTDoc ): void { | ||
| const stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY ); | ||
| stateMap.set( VERSION_KEY, CRDT_DOC_VERSION ); | ||
| return ydoc; | ||
| } | ||
@@ -42,0 +55,0 @@ |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
754944
3.82%163
1.88%11348
5.19%0
-100%6
-14.29%