{
  "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\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\tif ( false === syncConfig.shouldSync?.( objectType, objectId ) ) {\n\t\t\tlog( 'loadEntity', 'shouldSync false, skipping', entityId );\n\t\t\treturn; // Sync config indicates that this entity should not be synced.\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\tpersistCRDTDoc: debugWrap( handlers.persistCRDTDoc ),\n\t\t\trefetchRecord: debugWrap( handlers.refetchRecord ),\n\t\t\trestoreUndoMeta: debugWrap( handlers.restoreUndoMeta ),\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\tif ( false === syncConfig.shouldSync?.( objectType, null ) ) {\n\t\t\tlog( 'loadCollection', 'shouldSync false, skipping', entityId );\n\t\t\treturn; // Sync config indicates that this entity should not be synced.\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 request that the CRDT doc be\n\t\t\t// persisted with the entity. The persisted CRDT doc can be created by\n\t\t\t// calling `syncManager.createPersistedCRDTDoc`.\n\t\t\ttargetDoc.transact( () => {\n\t\t\t\tapplyChangesToCRDTDoc( targetDoc, record );\n\t\t\t\thandlers.persistCRDTDoc();\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 request that the updated CRDT doc be persisted with\n\t\t// the entity. The persisted CRDT doc can be created by calling\n\t\t// `syncManager.createPersistedCRDTDoc`.\n\t\ttargetDoc.transact( () => {\n\t\t\tapplyChangesToCRDTDoc( targetDoc, changes );\n\t\t\thandlers.persistCRDTDoc();\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"]
}
