@tldraw/sync-core 4.2.0-next.b7f56801f23f → 4.2.0-next.bff7e3992d58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/TLSyncRoom.ts"],
4
- "sourcesContent": ["import { transact, transaction } from '@tldraw/state'\nimport {\n\tAtomMap,\n\tIdOf,\n\tMigrationFailureReason,\n\tRecordType,\n\tSerializedSchema,\n\tStoreSchema,\n\tUnknownRecord,\n} from '@tldraw/store'\nimport { DocumentRecordType, PageRecordType, TLDOCUMENT_ID } from '@tldraw/tlschema'\nimport {\n\tIndexKey,\n\tResult,\n\tassert,\n\tassertExists,\n\texhaustiveSwitchError,\n\tgetOwnProperty,\n\thasOwnProperty,\n\tisEqual,\n\tisNativeStructuredClone,\n\tobjectMapEntriesIterable,\n\tstructuredClone,\n} from '@tldraw/utils'\nimport { createNanoEvents } from 'nanoevents'\nimport {\n\tRoomSession,\n\tRoomSessionState,\n\tSESSION_IDLE_TIMEOUT,\n\tSESSION_REMOVAL_WAIT_TIME,\n\tSESSION_START_WAIT_TIME,\n} from './RoomSession'\nimport { TLSyncLog } from './TLSocketRoom'\nimport { TLSyncErrorCloseEventCode, TLSyncErrorCloseEventReason } from './TLSyncClient'\nimport {\n\tNetworkDiff,\n\tObjectDiff,\n\tRecordOp,\n\tRecordOpType,\n\tValueOpType,\n\tapplyObjectDiff,\n\tdiffRecord,\n} from './diff'\nimport { findMin } from './findMin'\nimport { interval } from './interval'\nimport {\n\tTLIncompatibilityReason,\n\tTLSocketClientSentEvent,\n\tTLSocketServerSentDataEvent,\n\tTLSocketServerSentEvent,\n\tgetTlsyncProtocolVersion,\n} from './protocol'\n\n/**\n * WebSocket interface for server-side room connections. This defines the contract\n * that socket implementations must follow to work with TLSyncRoom.\n *\n * @internal\n */\nexport interface TLRoomSocket<R extends UnknownRecord> {\n\t/**\n\t * Whether the socket connection is currently open and ready to send messages.\n\t */\n\tisOpen: boolean\n\t/**\n\t * Send a message to the connected client through this socket.\n\t *\n\t * @param msg - The server-sent event message to transmit\n\t */\n\tsendMessage(msg: TLSocketServerSentEvent<R>): void\n\t/**\n\t * Close the socket connection with optional status code and reason.\n\t *\n\t * @param code - WebSocket close code (optional)\n\t * @param reason - Human-readable close reason (optional)\n\t */\n\tclose(code?: number, reason?: string): void\n}\n\n/**\n * The maximum number of tombstone records to keep in memory. Tombstones track\n * deleted records to prevent resurrection during sync operations.\n * @public\n */\nexport const MAX_TOMBSTONES = 3000\n\n/**\n * The number of tombstones to delete when pruning occurs after reaching MAX_TOMBSTONES.\n * This buffer prevents frequent pruning operations.\n * @public\n */\nexport const TOMBSTONE_PRUNE_BUFFER_SIZE = 300\n\n/**\n * The minimum time interval (in milliseconds) between sending batched data messages\n * to clients. This debouncing prevents overwhelming clients with rapid updates.\n * @public\n */\nexport const DATA_MESSAGE_DEBOUNCE_INTERVAL = 1000 / 60\n\nconst timeSince = (time: number) => Date.now() - time\n\n/**\n * Represents the state of a document record within a sync room, including\n * its current data and the clock value when it was last modified.\n *\n * @internal\n */\nexport class DocumentState<R extends UnknownRecord> {\n\t/**\n\t * Create a DocumentState instance without validating the record data.\n\t * Used for performance when validation has already been performed.\n\t *\n\t * @param state - The record data\n\t * @param lastChangedClock - Clock value when this record was last modified\n\t * @param recordType - The record type definition for validation\n\t * @returns A new DocumentState instance\n\t */\n\tstatic createWithoutValidating<R extends UnknownRecord>(\n\t\tstate: R,\n\t\tlastChangedClock: number,\n\t\trecordType: RecordType<R, any>\n\t): DocumentState<R> {\n\t\treturn new DocumentState(state, lastChangedClock, recordType)\n\t}\n\n\t/**\n\t * Create a DocumentState instance with validation of the record data.\n\t *\n\t * @param state - The record data to validate\n\t * @param lastChangedClock - Clock value when this record was last modified\n\t * @param recordType - The record type definition for validation\n\t * @returns Result containing the DocumentState or validation error\n\t */\n\tstatic createAndValidate<R extends UnknownRecord>(\n\t\tstate: R,\n\t\tlastChangedClock: number,\n\t\trecordType: RecordType<R, any>\n\t): Result<DocumentState<R>, Error> {\n\t\ttry {\n\t\t\trecordType.validate(state)\n\t\t} catch (error: any) {\n\t\t\treturn Result.err(error)\n\t\t}\n\t\treturn Result.ok(new DocumentState(state, lastChangedClock, recordType))\n\t}\n\n\tprivate constructor(\n\t\tpublic readonly state: R,\n\t\tpublic readonly lastChangedClock: number,\n\t\tprivate readonly recordType: RecordType<R, any>\n\t) {}\n\n\t/**\n\t * Replace the current state with new state and calculate the diff.\n\t *\n\t * @param state - The new record state\n\t * @param clock - The new clock value\n\t * @returns Result containing the diff and new DocumentState, or null if no changes, or validation error\n\t */\n\treplaceState(state: R, clock: number): Result<[ObjectDiff, DocumentState<R>] | null, Error> {\n\t\tconst diff = diffRecord(this.state, state)\n\t\tif (!diff) return Result.ok(null)\n\t\ttry {\n\t\t\tthis.recordType.validate(state)\n\t\t} catch (error: any) {\n\t\t\treturn Result.err(error)\n\t\t}\n\t\treturn Result.ok([diff, new DocumentState(state, clock, this.recordType)])\n\t}\n\t/**\n\t * Apply a diff to the current state and return the resulting changes.\n\t *\n\t * @param diff - The object diff to apply\n\t * @param clock - The new clock value\n\t * @returns Result containing the final diff and new DocumentState, or null if no changes, or validation error\n\t */\n\tmergeDiff(diff: ObjectDiff, clock: number): Result<[ObjectDiff, DocumentState<R>] | null, Error> {\n\t\tconst newState = applyObjectDiff(this.state, diff)\n\t\treturn this.replaceState(newState, clock)\n\t}\n}\n\n/**\n * Snapshot of a room's complete state that can be persisted and restored.\n * Contains all documents, tombstones, and metadata needed to reconstruct the room.\n *\n * @public\n */\nexport interface RoomSnapshot {\n\t/**\n\t * The current logical clock value for the room\n\t */\n\tclock: number\n\t/**\n\t * Clock value when document data was last changed (optional for backwards compatibility)\n\t */\n\tdocumentClock?: number\n\t/**\n\t * Array of all document records with their last modification clocks\n\t */\n\tdocuments: Array<{ state: UnknownRecord; lastChangedClock: number }>\n\t/**\n\t * Map of deleted record IDs to their deletion clock values (optional)\n\t */\n\ttombstones?: Record<string, number>\n\t/**\n\t * Clock value where tombstone history begins - older deletions are not tracked (optional)\n\t */\n\ttombstoneHistoryStartsAtClock?: number\n\t/**\n\t * Serialized schema used when creating this snapshot (optional)\n\t */\n\tschema?: SerializedSchema\n}\n\nfunction getDocumentClock(snapshot: RoomSnapshot) {\n\tif (typeof snapshot.documentClock === 'number') {\n\t\treturn snapshot.documentClock\n\t}\n\tlet max = 0\n\tfor (const doc of snapshot.documents) {\n\t\tmax = Math.max(max, doc.lastChangedClock)\n\t}\n\tfor (const tombstone of Object.values(snapshot.tombstones ?? {})) {\n\t\tmax = Math.max(max, tombstone)\n\t}\n\treturn max\n}\n\n/**\n * A collaborative workspace that manages multiple client sessions and synchronizes\n * document changes between them. The room serves as the authoritative source for\n * all document state and handles conflict resolution, schema migrations, and\n * real-time data distribution.\n *\n * @example\n * ```ts\n * const room = new TLSyncRoom({\n * schema: mySchema,\n * onDataChange: () => saveToDatabase(room.getSnapshot()),\n * onPresenceChange: () => updateLiveCursors()\n * })\n *\n * // Handle new client connections\n * room.handleNewSession({\n * sessionId: 'user-123',\n * socket: webSocketAdapter,\n * meta: { userId: '123', name: 'Alice' },\n * isReadonly: false\n * })\n * ```\n *\n * @internal\n */\nexport class TLSyncRoom<R extends UnknownRecord, SessionMeta> {\n\t// A table of connected clients\n\treadonly sessions = new Map<string, RoomSession<R, SessionMeta>>()\n\n\t// eslint-disable-next-line local/prefer-class-methods\n\tpruneSessions = () => {\n\t\tfor (const client of this.sessions.values()) {\n\t\t\tswitch (client.state) {\n\t\t\t\tcase RoomSessionState.Connected: {\n\t\t\t\t\tconst hasTimedOut = timeSince(client.lastInteractionTime) > SESSION_IDLE_TIMEOUT\n\t\t\t\t\tif (hasTimedOut || !client.socket.isOpen) {\n\t\t\t\t\t\tthis.cancelSession(client.sessionId)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase RoomSessionState.AwaitingConnectMessage: {\n\t\t\t\t\tconst hasTimedOut = timeSince(client.sessionStartTime) > SESSION_START_WAIT_TIME\n\t\t\t\t\tif (hasTimedOut || !client.socket.isOpen) {\n\t\t\t\t\t\t// remove immediately\n\t\t\t\t\t\tthis.removeSession(client.sessionId)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase RoomSessionState.AwaitingRemoval: {\n\t\t\t\t\tconst hasTimedOut = timeSince(client.cancellationTime) > SESSION_REMOVAL_WAIT_TIME\n\t\t\t\t\tif (hasTimedOut) {\n\t\t\t\t\t\tthis.removeSession(client.sessionId)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\texhaustiveSwitchError(client)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate disposables: Array<() => void> = [interval(this.pruneSessions, 2000)]\n\n\tprivate _isClosed = false\n\n\t/**\n\t * Close the room and clean up all resources. Disconnects all sessions\n\t * and stops background processes.\n\t */\n\tclose() {\n\t\tthis.disposables.forEach((d) => d())\n\t\tthis.sessions.forEach((session) => {\n\t\t\tsession.socket.close()\n\t\t})\n\t\tthis._isClosed = true\n\t}\n\n\t/**\n\t * Check if the room has been closed and is no longer accepting connections.\n\t *\n\t * @returns True if the room is closed\n\t */\n\tisClosed() {\n\t\treturn this._isClosed\n\t}\n\n\treadonly events = createNanoEvents<{\n\t\troom_became_empty(): void\n\t\tsession_removed(args: { sessionId: string; meta: SessionMeta }): void\n\t}>()\n\n\t// Values associated with each uid (must be serializable).\n\t/** @internal */\n\tdocuments: AtomMap<string, DocumentState<R>>\n\ttombstones: AtomMap<string, number>\n\n\t// this clock should start higher than the client, to make sure that clients who sync with their\n\t// initial lastServerClock value get the full state\n\t// in this case clients will start with 0, and the server will start with 1\n\tclock: number\n\tdocumentClock: number\n\ttombstoneHistoryStartsAtClock: number\n\t// map from record id to clock upon deletion\n\n\treadonly serializedSchema: SerializedSchema\n\n\treadonly documentTypes: Set<string>\n\treadonly presenceType: RecordType<R, any> | null\n\tprivate log?: TLSyncLog\n\tpublic readonly schema: StoreSchema<R, any>\n\tprivate onDataChange?(): void\n\tprivate onPresenceChange?(): void\n\n\tconstructor(opts: {\n\t\tlog?: TLSyncLog\n\t\tschema: StoreSchema<R, any>\n\t\tsnapshot?: RoomSnapshot\n\t\tonDataChange?(): void\n\t\tonPresenceChange?(): void\n\t}) {\n\t\tthis.schema = opts.schema\n\t\tlet snapshot = opts.snapshot\n\t\tthis.log = opts.log\n\t\tthis.onDataChange = opts.onDataChange\n\t\tthis.onPresenceChange = opts.onPresenceChange\n\n\t\tassert(\n\t\t\tisNativeStructuredClone,\n\t\t\t'TLSyncRoom is supposed to run either on Cloudflare Workers' +\n\t\t\t\t'or on a 18+ version of Node.js, which both support the native structuredClone API'\n\t\t)\n\n\t\t// do a json serialization cycle to make sure the schema has no 'undefined' values\n\t\tthis.serializedSchema = JSON.parse(JSON.stringify(this.schema.serialize()))\n\n\t\tthis.documentTypes = new Set(\n\t\t\tObject.values<RecordType<R, any>>(this.schema.types)\n\t\t\t\t.filter((t) => t.scope === 'document')\n\t\t\t\t.map((t) => t.typeName)\n\t\t)\n\n\t\tconst presenceTypes = new Set(\n\t\t\tObject.values<RecordType<R, any>>(this.schema.types).filter((t) => t.scope === 'presence')\n\t\t)\n\n\t\tif (presenceTypes.size > 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`TLSyncRoom: exactly zero or one presence type is expected, but found ${presenceTypes.size}`\n\t\t\t)\n\t\t}\n\n\t\tthis.presenceType = presenceTypes.values().next()?.value ?? null\n\n\t\tif (!snapshot) {\n\t\t\tsnapshot = {\n\t\t\t\tclock: 0,\n\t\t\t\tdocumentClock: 0,\n\t\t\t\tdocuments: [\n\t\t\t\t\t{\n\t\t\t\t\t\tstate: DocumentRecordType.create({ id: TLDOCUMENT_ID }),\n\t\t\t\t\t\tlastChangedClock: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tstate: PageRecordType.create({ name: 'Page 1', index: 'a1' as IndexKey }),\n\t\t\t\t\t\tlastChangedClock: 0,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\t\t}\n\n\t\tthis.clock = snapshot.clock\n\n\t\tlet didIncrementClock = false\n\t\tconst ensureClockDidIncrement = (_reason: string) => {\n\t\t\tif (!didIncrementClock) {\n\t\t\t\tdidIncrementClock = true\n\t\t\t\tthis.clock++\n\t\t\t}\n\t\t}\n\n\t\tthis.tombstones = new AtomMap(\n\t\t\t'room tombstones',\n\t\t\tobjectMapEntriesIterable(snapshot.tombstones ?? {})\n\t\t)\n\t\tthis.documents = new AtomMap(\n\t\t\t'room documents',\n\t\t\tfunction* (this: TLSyncRoom<R, SessionMeta>) {\n\t\t\t\tfor (const doc of snapshot.documents) {\n\t\t\t\t\tif (this.documentTypes.has(doc.state.typeName)) {\n\t\t\t\t\t\tyield [\n\t\t\t\t\t\t\tdoc.state.id,\n\t\t\t\t\t\t\tDocumentState.createWithoutValidating<R>(\n\t\t\t\t\t\t\t\tdoc.state as R,\n\t\t\t\t\t\t\t\tdoc.lastChangedClock,\n\t\t\t\t\t\t\t\tassertExists(getOwnProperty(this.schema.types, doc.state.typeName))\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t] as const\n\t\t\t\t\t} else {\n\t\t\t\t\t\tensureClockDidIncrement('doc type was not doc type')\n\t\t\t\t\t\tthis.tombstones.set(doc.state.id, this.clock)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}.call(this)\n\t\t)\n\n\t\tthis.tombstoneHistoryStartsAtClock =\n\t\t\tsnapshot.tombstoneHistoryStartsAtClock ?? findMin(this.tombstones.values()) ?? this.clock\n\n\t\tif (this.tombstoneHistoryStartsAtClock === 0) {\n\t\t\t// Before this comment was added, new clients would send '0' as their 'lastServerClock'\n\t\t\t// which was technically an error because clocks start at 0, but the error didn't manifest\n\t\t\t// because we initialized tombstoneHistoryStartsAtClock to 1 and then never updated it.\n\t\t\t// Now that we handle tombstoneHistoryStartsAtClock properly we need to increment it here to make sure old\n\t\t\t// clients still get data when they connect. This if clause can be deleted after a few months.\n\t\t\tthis.tombstoneHistoryStartsAtClock++\n\t\t}\n\n\t\ttransact(() => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\tconst schema = snapshot.schema ?? this.schema.serializeEarliestVersion()\n\n\t\t\tconst migrationsToApply = this.schema.getMigrationsSince(schema)\n\t\t\tassert(migrationsToApply.ok, 'Failed to get migrations')\n\n\t\t\tif (migrationsToApply.value.length > 0) {\n\t\t\t\t// only bother allocating a snapshot if there are migrations to apply\n\t\t\t\tconst store = {} as Record<IdOf<R>, R>\n\t\t\t\tfor (const [k, v] of this.documents.entries()) {\n\t\t\t\t\tstore[k as IdOf<R>] = v.state\n\t\t\t\t}\n\n\t\t\t\tconst migrationResult = this.schema.migrateStoreSnapshot(\n\t\t\t\t\t{ store, schema },\n\t\t\t\t\t{ mutateInputStore: true }\n\t\t\t\t)\n\n\t\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\t\t// TODO: Fault tolerance\n\t\t\t\t\tthrow new Error('Failed to migrate: ' + migrationResult.reason)\n\t\t\t\t}\n\n\t\t\t\t// use for..in to iterate over the keys of the object because it consumes less memory than\n\t\t\t\t// Object.entries\n\t\t\t\tfor (const id in migrationResult.value) {\n\t\t\t\t\tif (!Object.prototype.hasOwnProperty.call(migrationResult.value, id)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst r = migrationResult.value[id as keyof typeof migrationResult.value]\n\t\t\t\t\tconst existing = this.documents.get(id)\n\t\t\t\t\tif (!existing || !isEqual(existing.state, r)) {\n\t\t\t\t\t\t// record was added or updated during migration\n\t\t\t\t\t\tensureClockDidIncrement('record was added or updated during migration')\n\t\t\t\t\t\tthis.documents.set(\n\t\t\t\t\t\t\tr.id,\n\t\t\t\t\t\t\tDocumentState.createWithoutValidating(\n\t\t\t\t\t\t\t\tr,\n\t\t\t\t\t\t\t\tthis.clock,\n\t\t\t\t\t\t\t\tassertExists(getOwnProperty(this.schema.types, r.typeName)) as any\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\n\t\t\t\tfor (const id of this.documents.keys()) {\n\t\t\t\t\tif (!migrationResult.value[id as keyof typeof migrationResult.value]) {\n\t\t\t\t\t\t// record was removed during migration\n\t\t\t\t\t\tensureClockDidIncrement('record was removed during migration')\n\t\t\t\t\t\tthis.tombstones.set(id, this.clock)\n\t\t\t\t\t\tthis.documents.delete(id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pruneTombstones()\n\t\t})\n\n\t\tif (didIncrementClock) {\n\t\t\tthis.documentClock = this.clock\n\t\t\topts.onDataChange?.()\n\t\t} else {\n\t\t\tthis.documentClock = getDocumentClock(snapshot)\n\t\t}\n\t}\n\n\tprivate didSchedulePrune = true\n\t// eslint-disable-next-line local/prefer-class-methods\n\tprivate pruneTombstones = () => {\n\t\tthis.didSchedulePrune = false\n\t\t// avoid blocking any pending responses\n\t\tif (this.tombstones.size > MAX_TOMBSTONES) {\n\t\t\tconst entries = Array.from(this.tombstones.entries())\n\t\t\t// sort entries in ascending order by clock\n\t\t\tentries.sort((a, b) => a[1] - b[1])\n\t\t\tlet idx = entries.length - 1 - MAX_TOMBSTONES + TOMBSTONE_PRUNE_BUFFER_SIZE\n\t\t\tconst cullClock = entries[idx++][1]\n\t\t\twhile (idx < entries.length && entries[idx][1] === cullClock) {\n\t\t\t\tidx++\n\t\t\t}\n\t\t\t// trim off the first bunch\n\t\t\tconst keysToDelete = entries.slice(0, idx).map(([key]) => key)\n\n\t\t\tthis.tombstoneHistoryStartsAtClock = cullClock + 1\n\t\t\tthis.tombstones.deleteMany(keysToDelete)\n\t\t}\n\t}\n\n\tprivate getDocument(id: string) {\n\t\treturn this.documents.get(id)\n\t}\n\n\tprivate addDocument(id: string, state: R, clock: number): Result<void, Error> {\n\t\tif (this.tombstones.has(id)) {\n\t\t\tthis.tombstones.delete(id)\n\t\t}\n\t\tconst createResult = DocumentState.createAndValidate(\n\t\t\tstate,\n\t\t\tclock,\n\t\t\tassertExists(getOwnProperty(this.schema.types, state.typeName))\n\t\t)\n\t\tif (!createResult.ok) return createResult\n\t\tthis.documents.set(id, createResult.value)\n\t\treturn Result.ok(undefined)\n\t}\n\n\tprivate removeDocument(id: string, clock: number) {\n\t\tthis.documents.delete(id)\n\t\tthis.tombstones.set(id, clock)\n\t\tif (!this.didSchedulePrune) {\n\t\t\tthis.didSchedulePrune = true\n\t\t\tsetTimeout(this.pruneTombstones, 0)\n\t\t}\n\t}\n\n\t/**\n\t * Get a complete snapshot of the current room state that can be persisted\n\t * and later used to restore the room.\n\t *\n\t * @returns Room snapshot containing all documents, tombstones, and metadata\n\t * @example\n\t * ```ts\n\t * const snapshot = room.getSnapshot()\n\t * await database.saveRoomSnapshot(roomId, snapshot)\n\t *\n\t * // Later, restore from snapshot\n\t * const restoredRoom = new TLSyncRoom({\n\t * schema: mySchema,\n\t * snapshot: snapshot\n\t * })\n\t * ```\n\t */\n\tgetSnapshot(): RoomSnapshot {\n\t\tconst tombstones = Object.fromEntries(this.tombstones.entries())\n\t\tconst documents = []\n\t\tfor (const doc of this.documents.values()) {\n\t\t\tif (this.documentTypes.has(doc.state.typeName)) {\n\t\t\t\tdocuments.push({\n\t\t\t\t\tstate: doc.state,\n\t\t\t\t\tlastChangedClock: doc.lastChangedClock,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tclock: this.clock,\n\t\t\tdocumentClock: this.documentClock,\n\t\t\ttombstones,\n\t\t\ttombstoneHistoryStartsAtClock: this.tombstoneHistoryStartsAtClock,\n\t\t\tschema: this.serializedSchema,\n\t\t\tdocuments,\n\t\t}\n\t}\n\n\t/**\n\t * Send a message to a particular client. Debounces data events\n\t *\n\t * @param sessionId - The id of the session to send the message to.\n\t * @param message - The message to send.\n\t */\n\tprivate sendMessage(\n\t\tsessionId: string,\n\t\tmessage: TLSocketServerSentEvent<R> | TLSocketServerSentDataEvent<R>\n\t) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) {\n\t\t\tthis.log?.warn?.('Tried to send message to unknown session', message.type)\n\t\t\treturn\n\t\t}\n\t\tif (session.state !== RoomSessionState.Connected) {\n\t\t\tthis.log?.warn?.('Tried to send message to disconnected client', message.type)\n\t\t\treturn\n\t\t}\n\t\tif (session.socket.isOpen) {\n\t\t\tif (message.type !== 'patch' && message.type !== 'push_result') {\n\t\t\t\t// this is not a data message\n\t\t\t\tif (message.type !== 'pong') {\n\t\t\t\t\t// non-data messages like \"connect\" might still need to be ordered correctly with\n\t\t\t\t\t// respect to data messages, so it's better to flush just in case\n\t\t\t\t\tthis._flushDataMessages(sessionId)\n\t\t\t\t}\n\t\t\t\tsession.socket.sendMessage(message)\n\t\t\t} else {\n\t\t\t\tif (session.debounceTimer === null) {\n\t\t\t\t\t// this is the first message since the last flush, don't delay it\n\t\t\t\t\tsession.socket.sendMessage({ type: 'data', data: [message] })\n\n\t\t\t\t\tsession.debounceTimer = setTimeout(\n\t\t\t\t\t\t() => this._flushDataMessages(sessionId),\n\t\t\t\t\t\tDATA_MESSAGE_DEBOUNCE_INTERVAL\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tsession.outstandingDataMessages.push(message)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.cancelSession(session.sessionId)\n\t\t}\n\t}\n\n\t// needs to accept sessionId and not a session because the session might be dead by the time\n\t// the timer fires\n\t_flushDataMessages(sessionId: string) {\n\t\tconst session = this.sessions.get(sessionId)\n\n\t\tif (!session || session.state !== RoomSessionState.Connected) {\n\t\t\treturn\n\t\t}\n\n\t\tsession.debounceTimer = null\n\n\t\tif (session.outstandingDataMessages.length > 0) {\n\t\t\tsession.socket.sendMessage({ type: 'data', data: session.outstandingDataMessages })\n\t\t\tsession.outstandingDataMessages.length = 0\n\t\t}\n\t}\n\n\t/** @internal */\n\tprivate removeSession(sessionId: string, fatalReason?: string) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) {\n\t\t\tthis.log?.warn?.('Tried to remove unknown session')\n\t\t\treturn\n\t\t}\n\n\t\tthis.sessions.delete(sessionId)\n\n\t\tconst presence = this.getDocument(session.presenceId ?? '')\n\n\t\ttry {\n\t\t\tif (fatalReason) {\n\t\t\t\tsession.socket.close(TLSyncErrorCloseEventCode, fatalReason)\n\t\t\t} else {\n\t\t\t\tsession.socket.close()\n\t\t\t}\n\t\t} catch {\n\t\t\t// noop, calling .close() multiple times is fine\n\t\t}\n\n\t\tif (presence) {\n\t\t\tthis.documents.delete(session.presenceId!)\n\n\t\t\tthis.broadcastPatch({\n\t\t\t\tdiff: { [session.presenceId!]: [RecordOpType.Remove] },\n\t\t\t\tsourceSessionId: sessionId,\n\t\t\t})\n\t\t}\n\n\t\tthis.events.emit('session_removed', { sessionId, meta: session.meta })\n\t\tif (this.sessions.size === 0) {\n\t\t\tthis.events.emit('room_became_empty')\n\t\t}\n\t}\n\n\tprivate cancelSession(sessionId: string) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) {\n\t\t\treturn\n\t\t}\n\n\t\tif (session.state === RoomSessionState.AwaitingRemoval) {\n\t\t\tthis.log?.warn?.('Tried to cancel session that is already awaiting removal')\n\t\t\treturn\n\t\t}\n\n\t\tthis.sessions.set(sessionId, {\n\t\t\tstate: RoomSessionState.AwaitingRemoval,\n\t\t\tsessionId,\n\t\t\tpresenceId: session.presenceId,\n\t\t\tsocket: session.socket,\n\t\t\tcancellationTime: Date.now(),\n\t\t\tmeta: session.meta,\n\t\t\tisReadonly: session.isReadonly,\n\t\t\trequiresLegacyRejection: session.requiresLegacyRejection,\n\t\t})\n\n\t\ttry {\n\t\t\tsession.socket.close()\n\t\t} catch {\n\t\t\t// noop, calling .close() multiple times is fine\n\t\t}\n\t}\n\n\t/**\n\t * Broadcast a patch to all connected clients except the one with the sessionId provided.\n\t * Automatically handles schema migration for clients on different versions.\n\t *\n\t * @param message - The broadcast message\n\t * - diff - The network diff to broadcast to all clients\n\t * - sourceSessionId - Optional ID of the session that originated this change (excluded from broadcast)\n\t * @returns This room instance for method chaining\n\t * @example\n\t * ```ts\n\t * room.broadcastPatch({\n\t * diff: { 'shape:123': [RecordOpType.Put, newShapeData] },\n\t * sourceSessionId: 'user-456' // This user won't receive the broadcast\n\t * })\n\t * ```\n\t */\n\tbroadcastPatch(message: { diff: NetworkDiff<R>; sourceSessionId?: string }) {\n\t\tconst { diff, sourceSessionId } = message\n\t\tthis.sessions.forEach((session) => {\n\t\t\tif (session.state !== RoomSessionState.Connected) return\n\t\t\tif (sourceSessionId === session.sessionId) return\n\t\t\tif (!session.socket.isOpen) {\n\t\t\t\tthis.cancelSession(session.sessionId)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst res = this.migrateDiffForSession(session.serializedSchema, diff)\n\n\t\t\tif (!res.ok) {\n\t\t\t\t// disconnect client and send incompatibility error\n\t\t\t\tthis.rejectSession(\n\t\t\t\t\tsession.sessionId,\n\t\t\t\t\tres.error === MigrationFailureReason.TargetVersionTooNew\n\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.sendMessage(session.sessionId, {\n\t\t\t\ttype: 'patch',\n\t\t\t\tdiff: res.value,\n\t\t\t\tserverClock: this.clock,\n\t\t\t})\n\t\t})\n\t\treturn this\n\t}\n\n\t/**\n\t * Send a custom message to a connected client. Useful for application-specific\n\t * communication that doesn't involve document synchronization.\n\t *\n\t * @param sessionId - The ID of the session to send the message to\n\t * @param data - The custom payload to send (will be JSON serialized)\n\t * @example\n\t * ```ts\n\t * // Send a custom notification\n\t * room.sendCustomMessage('user-123', {\n\t * type: 'notification',\n\t * message: 'Document saved successfully'\n\t * })\n\t *\n\t * // Send user-specific data\n\t * room.sendCustomMessage('user-456', {\n\t * type: 'user_permissions',\n\t * canEdit: true,\n\t * canDelete: false\n\t * })\n\t * ```\n\t */\n\tsendCustomMessage(sessionId: string, data: any): void {\n\t\tthis.sendMessage(sessionId, { type: 'custom', data })\n\t}\n\n\t/**\n\t * Register a new client session with the room. The session will be in an awaiting\n\t * state until it sends a connect message with protocol handshake.\n\t *\n\t * @param opts - Session configuration\n\t * - sessionId - Unique identifier for this session\n\t * - socket - WebSocket adapter for communication\n\t * - meta - Application-specific metadata for this session\n\t * - isReadonly - Whether this session can modify documents\n\t * @returns This room instance for method chaining\n\t * @example\n\t * ```ts\n\t * room.handleNewSession({\n\t * sessionId: crypto.randomUUID(),\n\t * socket: new WebSocketAdapter(ws),\n\t * meta: { userId: '123', name: 'Alice', avatar: 'url' },\n\t * isReadonly: !hasEditPermission\n\t * })\n\t * ```\n\t *\n\t * @internal\n\t */\n\thandleNewSession(opts: {\n\t\tsessionId: string\n\t\tsocket: TLRoomSocket<R>\n\t\tmeta: SessionMeta\n\t\tisReadonly: boolean\n\t}) {\n\t\tconst { sessionId, socket, meta, isReadonly } = opts\n\t\tconst existing = this.sessions.get(sessionId)\n\t\tthis.sessions.set(sessionId, {\n\t\t\tstate: RoomSessionState.AwaitingConnectMessage,\n\t\t\tsessionId,\n\t\t\tsocket,\n\t\t\tpresenceId: existing?.presenceId ?? this.presenceType?.createId() ?? null,\n\t\t\tsessionStartTime: Date.now(),\n\t\t\tmeta,\n\t\t\tisReadonly: isReadonly ?? false,\n\t\t\t// this gets set later during handleConnectMessage\n\t\t\trequiresLegacyRejection: false,\n\t\t})\n\t\treturn this\n\t}\n\n\t/**\n\t * When we send a diff to a client, if that client is on a lower version than us, we need to make\n\t * the diff compatible with their version. At the moment this means migrating each affected record\n\t * to the client's version and sending the whole record again. We can optimize this later by\n\t * keeping the previous versions of records around long enough to recalculate these diffs for\n\t * older client versions.\n\t */\n\tprivate migrateDiffForSession(\n\t\tserializedSchema: SerializedSchema,\n\t\tdiff: NetworkDiff<R>\n\t): Result<NetworkDiff<R>, MigrationFailureReason> {\n\t\t// TODO: optimize this by recalculating patches using the previous versions of records\n\n\t\t// when the client connects we check whether the schema is identical and make sure\n\t\t// to use the same object reference so that === works on this line\n\t\tif (serializedSchema === this.serializedSchema) {\n\t\t\treturn Result.ok(diff)\n\t\t}\n\n\t\tconst result: NetworkDiff<R> = {}\n\t\tfor (const [id, op] of objectMapEntriesIterable(diff)) {\n\t\t\tif (op[0] === RecordOpType.Remove) {\n\t\t\t\tresult[id] = op\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst doc = this.getDocument(id)\n\t\t\tif (!doc) {\n\t\t\t\treturn Result.err(MigrationFailureReason.TargetVersionTooNew)\n\t\t\t}\n\t\t\tconst migrationResult = this.schema.migratePersistedRecord(\n\t\t\t\tdoc.state,\n\t\t\t\tserializedSchema,\n\t\t\t\t'down'\n\t\t\t)\n\n\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\treturn Result.err(migrationResult.reason)\n\t\t\t}\n\n\t\t\tresult[id] = [RecordOpType.Put, migrationResult.value]\n\t\t}\n\n\t\treturn Result.ok(result)\n\t}\n\n\t/**\n\t * Process an incoming message from a client session. Handles connection requests,\n\t * data synchronization pushes, and ping/pong for connection health.\n\t *\n\t * @param sessionId - The ID of the session that sent the message\n\t * @param message - The client message to process\n\t * @example\n\t * ```ts\n\t * // Typically called by WebSocket message handlers\n\t * websocket.onMessage((data) => {\n\t * const message = JSON.parse(data)\n\t * room.handleMessage(sessionId, message)\n\t * })\n\t * ```\n\t */\n\tasync handleMessage(sessionId: string, message: TLSocketClientSentEvent<R>) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) {\n\t\t\tthis.log?.warn?.('Received message from unknown session')\n\t\t\treturn\n\t\t}\n\t\tswitch (message.type) {\n\t\t\tcase 'connect': {\n\t\t\t\treturn this.handleConnectRequest(session, message)\n\t\t\t}\n\t\t\tcase 'push': {\n\t\t\t\treturn this.handlePushRequest(session, message)\n\t\t\t}\n\t\t\tcase 'ping': {\n\t\t\t\tif (session.state === RoomSessionState.Connected) {\n\t\t\t\t\tsession.lastInteractionTime = Date.now()\n\t\t\t\t}\n\t\t\t\treturn this.sendMessage(session.sessionId, { type: 'pong' })\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\texhaustiveSwitchError(message)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Reject and disconnect a session due to incompatibility or other fatal errors.\n\t * Sends appropriate error messages before closing the connection.\n\t *\n\t * @param sessionId - The session to reject\n\t * @param fatalReason - The reason for rejection (optional)\n\t * @example\n\t * ```ts\n\t * // Reject due to version mismatch\n\t * room.rejectSession('user-123', TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t *\n\t * // Reject due to permission issue\n\t * room.rejectSession('user-456', 'Insufficient permissions')\n\t * ```\n\t */\n\trejectSession(sessionId: string, fatalReason?: TLSyncErrorCloseEventReason | string) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) return\n\t\tif (!fatalReason) {\n\t\t\tthis.removeSession(sessionId)\n\t\t\treturn\n\t\t}\n\t\tif (session.requiresLegacyRejection) {\n\t\t\ttry {\n\t\t\t\tif (session.socket.isOpen) {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\tlet legacyReason: TLIncompatibilityReason\n\t\t\t\t\tswitch (fatalReason) {\n\t\t\t\t\t\tcase TLSyncErrorCloseEventReason.CLIENT_TOO_OLD:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\t\t\tlegacyReason = TLIncompatibilityReason.ClientTooOld\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase TLSyncErrorCloseEventReason.SERVER_TOO_OLD:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\t\t\tlegacyReason = TLIncompatibilityReason.ServerTooOld\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase TLSyncErrorCloseEventReason.INVALID_RECORD:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\t\t\tlegacyReason = TLIncompatibilityReason.InvalidRecord\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\t\t\tlegacyReason = TLIncompatibilityReason.InvalidOperation\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tsession.socket.sendMessage({\n\t\t\t\t\t\ttype: 'incompatibility_error',\n\t\t\t\t\t\treason: legacyReason,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// noop\n\t\t\t} finally {\n\t\t\t\tthis.removeSession(sessionId)\n\t\t\t}\n\t\t} else {\n\t\t\tthis.removeSession(sessionId, fatalReason)\n\t\t}\n\t}\n\n\tprivate handleConnectRequest(\n\t\tsession: RoomSession<R, SessionMeta>,\n\t\tmessage: Extract<TLSocketClientSentEvent<R>, { type: 'connect' }>\n\t) {\n\t\t// if the protocol versions don't match, disconnect the client\n\t\t// we will eventually want to try to make our protocol backwards compatible to some degree\n\t\t// and have a MIN_PROTOCOL_VERSION constant that the TLSyncRoom implements support for\n\t\tlet theirProtocolVersion = message.protocolVersion\n\t\t// 5 is the same as 6\n\t\tif (theirProtocolVersion === 5) {\n\t\t\ttheirProtocolVersion = 6\n\t\t}\n\t\t// 6 is almost the same as 7\n\t\tsession.requiresLegacyRejection = theirProtocolVersion === 6\n\t\tif (theirProtocolVersion === 6) {\n\t\t\ttheirProtocolVersion++\n\t\t}\n\n\t\tif (theirProtocolVersion == null || theirProtocolVersion < getTlsyncProtocolVersion()) {\n\t\t\tthis.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\treturn\n\t\t} else if (theirProtocolVersion > getTlsyncProtocolVersion()) {\n\t\t\tthis.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.SERVER_TOO_OLD)\n\t\t\treturn\n\t\t}\n\t\t// If the client's store is at a different version to ours, it could cause corruption.\n\t\t// We should disconnect the client and ask them to refresh.\n\t\tif (message.schema == null) {\n\t\t\tthis.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\treturn\n\t\t}\n\t\tconst migrations = this.schema.getMigrationsSince(message.schema)\n\t\t// if the client's store is at a different version to ours, we can't support them\n\t\tif (!migrations.ok || migrations.value.some((m) => m.scope === 'store' || !m.down)) {\n\t\t\tthis.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\treturn\n\t\t}\n\n\t\tconst sessionSchema = isEqual(message.schema, this.serializedSchema)\n\t\t\t? this.serializedSchema\n\t\t\t: message.schema\n\n\t\tconst connect = async (msg: Extract<TLSocketServerSentEvent<R>, { type: 'connect' }>) => {\n\t\t\tthis.sessions.set(session.sessionId, {\n\t\t\t\tstate: RoomSessionState.Connected,\n\t\t\t\tsessionId: session.sessionId,\n\t\t\t\tpresenceId: session.presenceId,\n\t\t\t\tsocket: session.socket,\n\t\t\t\tserializedSchema: sessionSchema,\n\t\t\t\tlastInteractionTime: Date.now(),\n\t\t\t\tdebounceTimer: null,\n\t\t\t\toutstandingDataMessages: [],\n\t\t\t\tmeta: session.meta,\n\t\t\t\tisReadonly: session.isReadonly,\n\t\t\t\trequiresLegacyRejection: session.requiresLegacyRejection,\n\t\t\t})\n\t\t\tthis.sendMessage(session.sessionId, msg)\n\t\t}\n\n\t\ttransaction((rollback) => {\n\t\t\tif (\n\t\t\t\t// if the client requests changes since a time before we have tombstone history, send them the full state\n\t\t\t\tmessage.lastServerClock < this.tombstoneHistoryStartsAtClock ||\n\t\t\t\t// similarly, if they ask for a time we haven't reached yet, send them the full state\n\t\t\t\t// this will only happen if the DB is reset (or there is no db) and the server restarts\n\t\t\t\t// or if the server exits/crashes with unpersisted changes\n\t\t\t\tmessage.lastServerClock > this.clock\n\t\t\t) {\n\t\t\t\tconst diff: NetworkDiff<R> = {}\n\t\t\t\tfor (const [id, doc] of this.documents.entries()) {\n\t\t\t\t\tif (id !== session.presenceId) {\n\t\t\t\t\t\tdiff[id] = [RecordOpType.Put, doc.state]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst migrated = this.migrateDiffForSession(sessionSchema, diff)\n\t\t\t\tif (!migrated.ok) {\n\t\t\t\t\trollback()\n\t\t\t\t\tthis.rejectSession(\n\t\t\t\t\t\tsession.sessionId,\n\t\t\t\t\t\tmigrated.error === MigrationFailureReason.TargetVersionTooNew\n\t\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconnect({\n\t\t\t\t\ttype: 'connect',\n\t\t\t\t\tconnectRequestId: message.connectRequestId,\n\t\t\t\t\thydrationType: 'wipe_all',\n\t\t\t\t\tprotocolVersion: getTlsyncProtocolVersion(),\n\t\t\t\t\tschema: this.schema.serialize(),\n\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\tdiff: migrated.value,\n\t\t\t\t\tisReadonly: session.isReadonly,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\t// calculate the changes since the time the client last saw\n\t\t\t\tconst diff: NetworkDiff<R> = {}\n\t\t\t\tfor (const doc of this.documents.values()) {\n\t\t\t\t\tif (doc.lastChangedClock > message.lastServerClock) {\n\t\t\t\t\t\tdiff[doc.state.id] = [RecordOpType.Put, doc.state]\n\t\t\t\t\t} else if (this.presenceType?.isId(doc.state.id) && doc.state.id !== session.presenceId) {\n\t\t\t\t\t\tdiff[doc.state.id] = [RecordOpType.Put, doc.state]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [id, deletedAtClock] of this.tombstones.entries()) {\n\t\t\t\t\tif (deletedAtClock > message.lastServerClock) {\n\t\t\t\t\t\tdiff[id] = [RecordOpType.Remove]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst migrated = this.migrateDiffForSession(sessionSchema, diff)\n\t\t\t\tif (!migrated.ok) {\n\t\t\t\t\trollback()\n\t\t\t\t\tthis.rejectSession(\n\t\t\t\t\t\tsession.sessionId,\n\t\t\t\t\t\tmigrated.error === MigrationFailureReason.TargetVersionTooNew\n\t\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconnect({\n\t\t\t\t\ttype: 'connect',\n\t\t\t\t\tconnectRequestId: message.connectRequestId,\n\t\t\t\t\thydrationType: 'wipe_presence',\n\t\t\t\t\tschema: this.schema.serialize(),\n\t\t\t\t\tprotocolVersion: getTlsyncProtocolVersion(),\n\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\tdiff: migrated.value,\n\t\t\t\t\tisReadonly: session.isReadonly,\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate handlePushRequest(\n\t\tsession: RoomSession<R, SessionMeta> | null,\n\t\tmessage: Extract<TLSocketClientSentEvent<R>, { type: 'push' }>\n\t) {\n\t\t// We must be connected to handle push requests\n\t\tif (session && session.state !== RoomSessionState.Connected) {\n\t\t\treturn\n\t\t}\n\n\t\t// update the last interaction time\n\t\tif (session) {\n\t\t\tsession.lastInteractionTime = Date.now()\n\t\t}\n\n\t\t// increment the clock for this push\n\t\tthis.clock++\n\n\t\tconst initialDocumentClock = this.documentClock\n\t\tlet didPresenceChange = false\n\t\ttransaction((rollback) => {\n\t\t\t// collect actual ops that resulted from the push\n\t\t\t// these will be broadcast to other users\n\t\t\tinterface ActualChanges {\n\t\t\t\tdiff: NetworkDiff<R> | null\n\t\t\t}\n\t\t\tconst docChanges: ActualChanges = { diff: null }\n\t\t\tconst presenceChanges: ActualChanges = { diff: null }\n\n\t\t\tconst propagateOp = (changes: ActualChanges, id: string, op: RecordOp<R>) => {\n\t\t\t\tif (!changes.diff) changes.diff = {}\n\t\t\t\tchanges.diff[id] = op\n\t\t\t}\n\n\t\t\tconst fail = (\n\t\t\t\treason: TLSyncErrorCloseEventReason,\n\t\t\t\tunderlyingError?: Error\n\t\t\t): Result<void, void> => {\n\t\t\t\trollback()\n\t\t\t\tif (session) {\n\t\t\t\t\tthis.rejectSession(session.sessionId, reason)\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error('failed to apply changes: ' + reason, underlyingError)\n\t\t\t\t}\n\t\t\t\tif (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') {\n\t\t\t\t\tthis.log?.error?.('failed to apply push', reason, message, underlyingError)\n\t\t\t\t}\n\t\t\t\treturn Result.err(undefined)\n\t\t\t}\n\n\t\t\tconst addDocument = (changes: ActualChanges, id: string, _state: R): Result<void, void> => {\n\t\t\t\tconst res = session\n\t\t\t\t\t? this.schema.migratePersistedRecord(_state, session.serializedSchema, 'up')\n\t\t\t\t\t: { type: 'success' as const, value: _state }\n\t\t\t\tif (res.type === 'error') {\n\t\t\t\t\treturn fail(\n\t\t\t\t\t\tres.reason === MigrationFailureReason.TargetVersionTooOld // target version is our version\n\t\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst { value: state } = res\n\n\t\t\t\t// Get the existing document, if any\n\t\t\t\tconst doc = this.getDocument(id)\n\n\t\t\t\tif (doc) {\n\t\t\t\t\t// If there's an existing document, replace it with the new state\n\t\t\t\t\t// but propagate a diff rather than the entire value\n\t\t\t\t\tconst diff = doc.replaceState(state, this.clock)\n\t\t\t\t\tif (!diff.ok) {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t}\n\t\t\t\t\tif (diff.value) {\n\t\t\t\t\t\tthis.documents.set(id, diff.value[1])\n\t\t\t\t\t\tpropagateOp(changes, id, [RecordOpType.Patch, diff.value[0]])\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Otherwise, if we don't already have a document with this id\n\t\t\t\t\t// create the document and propagate the put op\n\t\t\t\t\tconst result = this.addDocument(id, state, this.clock)\n\t\t\t\t\tif (!result.ok) {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t}\n\t\t\t\t\tpropagateOp(changes, id, [RecordOpType.Put, state])\n\t\t\t\t}\n\n\t\t\t\treturn Result.ok(undefined)\n\t\t\t}\n\n\t\t\tconst patchDocument = (\n\t\t\t\tchanges: ActualChanges,\n\t\t\t\tid: string,\n\t\t\t\tpatch: ObjectDiff\n\t\t\t): Result<void, void> => {\n\t\t\t\t// if it was already deleted, there's no need to apply the patch\n\t\t\t\tconst doc = this.getDocument(id)\n\t\t\t\tif (!doc) return Result.ok(undefined)\n\t\t\t\t// If the client's version of the record is older than ours,\n\t\t\t\t// we apply the patch to the downgraded version of the record\n\t\t\t\tconst downgraded = session\n\t\t\t\t\t? this.schema.migratePersistedRecord(doc.state, session.serializedSchema, 'down')\n\t\t\t\t\t: { type: 'success' as const, value: doc.state }\n\t\t\t\tif (downgraded.type === 'error') {\n\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\t\t}\n\n\t\t\t\tif (downgraded.value === doc.state) {\n\t\t\t\t\t// If the versions are compatible, apply the patch and propagate the patch op\n\t\t\t\t\tconst diff = doc.mergeDiff(patch, this.clock)\n\t\t\t\t\tif (!diff.ok) {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t}\n\t\t\t\t\tif (diff.value) {\n\t\t\t\t\t\tthis.documents.set(id, diff.value[1])\n\t\t\t\t\t\tpropagateOp(changes, id, [RecordOpType.Patch, diff.value[0]])\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// need to apply the patch to the downgraded version and then upgrade it\n\n\t\t\t\t\t// apply the patch to the downgraded version\n\t\t\t\t\tconst patched = applyObjectDiff(downgraded.value, patch)\n\t\t\t\t\t// then upgrade the patched version and use that as the new state\n\t\t\t\t\tconst upgraded = session\n\t\t\t\t\t\t? this.schema.migratePersistedRecord(patched, session.serializedSchema, 'up')\n\t\t\t\t\t\t: { type: 'success' as const, value: patched }\n\t\t\t\t\t// If the client's version is too old, we'll hit an error\n\t\t\t\t\tif (upgraded.type === 'error') {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\t\t\t}\n\t\t\t\t\t// replace the state with the upgraded version and propagate the patch op\n\t\t\t\t\tconst diff = doc.replaceState(upgraded.value, this.clock)\n\t\t\t\t\tif (!diff.ok) {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t}\n\t\t\t\t\tif (diff.value) {\n\t\t\t\t\t\tthis.documents.set(id, diff.value[1])\n\t\t\t\t\t\tpropagateOp(changes, id, [RecordOpType.Patch, diff.value[0]])\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn Result.ok(undefined)\n\t\t\t}\n\n\t\t\tconst { clientClock } = message\n\n\t\t\tif (this.presenceType && session?.presenceId && 'presence' in message && message.presence) {\n\t\t\t\tif (!session) throw new Error('session is required for presence pushes')\n\t\t\t\t// The push request was for the presence scope.\n\t\t\t\tconst id = session.presenceId\n\t\t\t\tconst [type, val] = message.presence\n\t\t\t\tconst { typeName } = this.presenceType\n\t\t\t\tswitch (type) {\n\t\t\t\t\tcase RecordOpType.Put: {\n\t\t\t\t\t\t// Try to put the document. If it fails, stop here.\n\t\t\t\t\t\tconst res = addDocument(presenceChanges, id, { ...val, id, typeName })\n\t\t\t\t\t\t// if res.ok is false here then we already called `fail` and we should stop immediately\n\t\t\t\t\t\tif (!res.ok) return\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase RecordOpType.Patch: {\n\t\t\t\t\t\t// Try to patch the document. If it fails, stop here.\n\t\t\t\t\t\tconst res = patchDocument(presenceChanges, id, {\n\t\t\t\t\t\t\t...val,\n\t\t\t\t\t\t\tid: [ValueOpType.Put, id],\n\t\t\t\t\t\t\ttypeName: [ValueOpType.Put, typeName],\n\t\t\t\t\t\t})\n\t\t\t\t\t\t// if res.ok is false here then we already called `fail` and we should stop immediately\n\t\t\t\t\t\tif (!res.ok) return\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (message.diff && !session?.isReadonly) {\n\t\t\t\t// The push request was for the document scope.\n\t\t\t\tfor (const [id, op] of objectMapEntriesIterable(message.diff!)) {\n\t\t\t\t\tswitch (op[0]) {\n\t\t\t\t\t\tcase RecordOpType.Put: {\n\t\t\t\t\t\t\t// Try to add the document.\n\t\t\t\t\t\t\t// If we're putting a record with a type that we don't recognize, fail\n\t\t\t\t\t\t\tif (!this.documentTypes.has(op[1].typeName)) {\n\t\t\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst res = addDocument(docChanges, id, op[1])\n\t\t\t\t\t\t\t// if res.ok is false here then we already called `fail` and we should stop immediately\n\t\t\t\t\t\t\tif (!res.ok) return\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase RecordOpType.Patch: {\n\t\t\t\t\t\t\t// Try to patch the document. If it fails, stop here.\n\t\t\t\t\t\t\tconst res = patchDocument(docChanges, id, op[1])\n\t\t\t\t\t\t\t// if res.ok is false here then we already called `fail` and we should stop immediately\n\t\t\t\t\t\t\tif (!res.ok) return\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase RecordOpType.Remove: {\n\t\t\t\t\t\t\tconst doc = this.getDocument(id)\n\t\t\t\t\t\t\tif (!doc) {\n\t\t\t\t\t\t\t\t// If the doc was already deleted, don't do anything, no need to propagate a delete op\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Delete the document and propagate the delete op\n\t\t\t\t\t\t\tthis.removeDocument(id, this.clock)\n\t\t\t\t\t\t\t// Schedule a pruneTombstones call to happen on the next call stack\n\t\t\t\t\t\t\tpropagateOp(docChanges, id, op)\n\t\t\t\t\t\t\tbreak\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// Let the client know what action to take based on the results of the push\n\t\t\tif (\n\t\t\t\t// if there was only a presence push, the client doesn't need to do anything aside from\n\t\t\t\t// shift the push request.\n\t\t\t\t!message.diff ||\n\t\t\t\tisEqual(docChanges.diff, message.diff)\n\t\t\t) {\n\t\t\t\t// COMMIT\n\t\t\t\t// Applying the client's changes had the exact same effect on the server as\n\t\t\t\t// they had on the client, so the client should keep the diff\n\t\t\t\tif (session) {\n\t\t\t\t\tthis.sendMessage(session.sessionId, {\n\t\t\t\t\t\ttype: 'push_result',\n\t\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\t\tclientClock,\n\t\t\t\t\t\taction: 'commit',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if (!docChanges.diff) {\n\t\t\t\t// DISCARD\n\t\t\t\t// Applying the client's changes had no effect, so the client should drop the diff\n\t\t\t\tif (session) {\n\t\t\t\t\tthis.sendMessage(session.sessionId, {\n\t\t\t\t\t\ttype: 'push_result',\n\t\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\t\tclientClock,\n\t\t\t\t\t\taction: 'discard',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// REBASE\n\t\t\t\t// Applying the client's changes had a different non-empty effect on the server,\n\t\t\t\t// so the client should rebase with our gold-standard / authoritative diff.\n\t\t\t\t// First we need to migrate the diff to the client's version\n\t\t\t\tif (session) {\n\t\t\t\t\tconst migrateResult = this.migrateDiffForSession(\n\t\t\t\t\t\tsession.serializedSchema,\n\t\t\t\t\t\tdocChanges.diff\n\t\t\t\t\t)\n\t\t\t\t\tif (!migrateResult.ok) {\n\t\t\t\t\t\treturn fail(\n\t\t\t\t\t\t\tmigrateResult.error === MigrationFailureReason.TargetVersionTooNew\n\t\t\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\t// If the migration worked, send the rebased diff to the client\n\t\t\t\t\tthis.sendMessage(session.sessionId, {\n\t\t\t\t\t\ttype: 'push_result',\n\t\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\t\tclientClock,\n\t\t\t\t\t\taction: { rebaseWithDiff: migrateResult.value },\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If there are merged changes, broadcast them to all other clients\n\t\t\tif (docChanges.diff || presenceChanges.diff) {\n\t\t\t\tthis.broadcastPatch({\n\t\t\t\t\tsourceSessionId: session?.sessionId,\n\t\t\t\t\tdiff: {\n\t\t\t\t\t\t...docChanges.diff,\n\t\t\t\t\t\t...presenceChanges.diff,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif (docChanges.diff) {\n\t\t\t\tthis.documentClock = this.clock\n\t\t\t}\n\t\t\tif (presenceChanges.diff) {\n\t\t\t\tdidPresenceChange = true\n\t\t\t}\n\n\t\t\treturn\n\t\t})\n\n\t\t// if it threw the changes will have been rolled back and the document clock will not have been incremented\n\t\tif (this.documentClock !== initialDocumentClock) {\n\t\t\tthis.onDataChange?.()\n\t\t}\n\n\t\tif (didPresenceChange) {\n\t\t\tthis.onPresenceChange?.()\n\t\t}\n\t}\n\n\t/**\n\t * Handle the event when a client disconnects. Cleans up the session and\n\t * removes any presence information.\n\t *\n\t * @param sessionId - The session that disconnected\n\t * @example\n\t * ```ts\n\t * websocket.onClose(() => {\n\t * room.handleClose(sessionId)\n\t * })\n\t * ```\n\t */\n\thandleClose(sessionId: string) {\n\t\tthis.cancelSession(sessionId)\n\t}\n\n\t/**\n\t * Apply changes to the room's store in a transactional way. Changes are\n\t * automatically synchronized to all connected clients.\n\t *\n\t * @param updater - Function that receives store methods to make changes\n\t * @returns Promise that resolves when the transaction is complete\n\t * @example\n\t * ```ts\n\t * // Add multiple shapes atomically\n\t * await room.updateStore((store) => {\n\t * store.put(createShape({ type: 'geo', x: 100, y: 100 }))\n\t * store.put(createShape({ type: 'text', x: 200, y: 200 }))\n\t * })\n\t *\n\t * // Async operations are supported\n\t * await room.updateStore(async (store) => {\n\t * const template = await loadTemplate()\n\t * template.shapes.forEach(shape => store.put(shape))\n\t * })\n\t * ```\n\t */\n\tasync updateStore(updater: (store: RoomStoreMethods<R>) => void | Promise<void>) {\n\t\tif (this._isClosed) {\n\t\t\tthrow new Error('Cannot update store on a closed room')\n\t\t}\n\t\tconst context = new StoreUpdateContext<R>(\n\t\t\tObject.fromEntries(this.getSnapshot().documents.map((d) => [d.state.id, d.state]))\n\t\t)\n\t\ttry {\n\t\t\tawait updater(context)\n\t\t} finally {\n\t\t\tcontext.close()\n\t\t}\n\n\t\tconst diff = context.toDiff()\n\t\tif (Object.keys(diff).length === 0) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.handlePushRequest(null, { type: 'push', diff, clientClock: 0 })\n\t}\n}\n\n/**\n * Interface for making transactional changes to room store data. Used within\n * updateStore transactions to modify documents atomically.\n *\n * @example\n * ```ts\n * await room.updateStore((store) => {\n * const shape = store.get('shape:123')\n * if (shape) {\n * store.put({ ...shape, x: shape.x + 10 })\n * }\n * store.delete('shape:456')\n * })\n * ```\n *\n * @public\n */\nexport interface RoomStoreMethods<R extends UnknownRecord = UnknownRecord> {\n\t/**\n\t * Add or update a record in the store.\n\t *\n\t * @param record - The record to store\n\t */\n\tput(record: R): void\n\t/**\n\t * Delete a record from the store.\n\t *\n\t * @param recordOrId - The record or record ID to delete\n\t */\n\tdelete(recordOrId: R | string): void\n\t/**\n\t * Get a record by its ID.\n\t *\n\t * @param id - The record ID\n\t * @returns The record or null if not found\n\t */\n\tget(id: string): R | null\n\t/**\n\t * Get all records in the store.\n\t *\n\t * @returns Array of all records\n\t */\n\tgetAll(): R[]\n}\n\nclass StoreUpdateContext<R extends UnknownRecord> implements RoomStoreMethods<R> {\n\tconstructor(private readonly snapshot: Record<string, UnknownRecord>) {}\n\tprivate readonly updates = {\n\t\tputs: {} as Record<string, UnknownRecord>,\n\t\tdeletes: new Set<string>(),\n\t}\n\tput(record: R): void {\n\t\tif (this._isClosed) throw new Error('StoreUpdateContext is closed')\n\t\tif (record.id in this.snapshot && isEqual(this.snapshot[record.id], record)) {\n\t\t\tdelete this.updates.puts[record.id]\n\t\t} else {\n\t\t\tthis.updates.puts[record.id] = structuredClone(record)\n\t\t}\n\t\tthis.updates.deletes.delete(record.id)\n\t}\n\tdelete(recordOrId: R | string): void {\n\t\tif (this._isClosed) throw new Error('StoreUpdateContext is closed')\n\t\tconst id = typeof recordOrId === 'string' ? recordOrId : recordOrId.id\n\t\tdelete this.updates.puts[id]\n\t\tif (this.snapshot[id]) {\n\t\t\tthis.updates.deletes.add(id)\n\t\t}\n\t}\n\tget(id: string): R | null {\n\t\tif (this._isClosed) throw new Error('StoreUpdateContext is closed')\n\t\tif (hasOwnProperty(this.updates.puts, id)) {\n\t\t\treturn structuredClone(this.updates.puts[id]) as R\n\t\t}\n\t\tif (this.updates.deletes.has(id)) {\n\t\t\treturn null\n\t\t}\n\t\treturn structuredClone(this.snapshot[id] ?? null) as R\n\t}\n\n\tgetAll(): R[] {\n\t\tif (this._isClosed) throw new Error('StoreUpdateContext is closed')\n\t\tconst result = Object.values(this.updates.puts)\n\t\tfor (const [id, record] of Object.entries(this.snapshot)) {\n\t\t\tif (!this.updates.deletes.has(id) && !hasOwnProperty(this.updates.puts, id)) {\n\t\t\t\tresult.push(record)\n\t\t\t}\n\t\t}\n\t\treturn structuredClone(result) as R[]\n\t}\n\n\ttoDiff(): NetworkDiff<any> {\n\t\tconst diff: NetworkDiff<R> = {}\n\t\tfor (const [id, record] of Object.entries(this.updates.puts)) {\n\t\t\tdiff[id] = [RecordOpType.Put, record as R]\n\t\t}\n\t\tfor (const id of this.updates.deletes) {\n\t\t\tdiff[id] = [RecordOpType.Remove]\n\t\t}\n\t\treturn diff\n\t}\n\n\tprivate _isClosed = false\n\tclose() {\n\t\tthis._isClosed = true\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { transact, transaction } from '@tldraw/state'\nimport {\n\tAtomMap,\n\tIdOf,\n\tMigrationFailureReason,\n\tRecordType,\n\tSerializedSchema,\n\tStoreSchema,\n\tUnknownRecord,\n} from '@tldraw/store'\nimport { DocumentRecordType, PageRecordType, TLDOCUMENT_ID } from '@tldraw/tlschema'\nimport {\n\tIndexKey,\n\tResult,\n\tassert,\n\tassertExists,\n\texhaustiveSwitchError,\n\tgetOwnProperty,\n\thasOwnProperty,\n\tisEqual,\n\tisNativeStructuredClone,\n\tobjectMapEntriesIterable,\n\tstructuredClone,\n} from '@tldraw/utils'\nimport { createNanoEvents } from 'nanoevents'\nimport {\n\tRoomSession,\n\tRoomSessionState,\n\tSESSION_IDLE_TIMEOUT,\n\tSESSION_REMOVAL_WAIT_TIME,\n\tSESSION_START_WAIT_TIME,\n} from './RoomSession'\nimport { TLSyncLog } from './TLSocketRoom'\nimport { TLSyncErrorCloseEventCode, TLSyncErrorCloseEventReason } from './TLSyncClient'\nimport {\n\tNetworkDiff,\n\tObjectDiff,\n\tRecordOp,\n\tRecordOpType,\n\tValueOpType,\n\tapplyObjectDiff,\n\tdiffRecord,\n} from './diff'\nimport { findMin } from './findMin'\nimport { interval } from './interval'\nimport {\n\tTLIncompatibilityReason,\n\tTLSocketClientSentEvent,\n\tTLSocketServerSentDataEvent,\n\tTLSocketServerSentEvent,\n\tgetTlsyncProtocolVersion,\n} from './protocol'\n\n/**\n * WebSocket interface for server-side room connections. This defines the contract\n * that socket implementations must follow to work with TLSyncRoom.\n *\n * @internal\n */\nexport interface TLRoomSocket<R extends UnknownRecord> {\n\t/**\n\t * Whether the socket connection is currently open and ready to send messages.\n\t */\n\tisOpen: boolean\n\t/**\n\t * Send a message to the connected client through this socket.\n\t *\n\t * @param msg - The server-sent event message to transmit\n\t */\n\tsendMessage(msg: TLSocketServerSentEvent<R>): void\n\t/**\n\t * Close the socket connection with optional status code and reason.\n\t *\n\t * @param code - WebSocket close code (optional)\n\t * @param reason - Human-readable close reason (optional)\n\t */\n\tclose(code?: number, reason?: string): void\n}\n\n/**\n * The maximum number of tombstone records to keep in memory. Tombstones track\n * deleted records to prevent resurrection during sync operations.\n * @public\n */\nexport const MAX_TOMBSTONES = 3000\n\n/**\n * The number of tombstones to delete when pruning occurs after reaching MAX_TOMBSTONES.\n * This buffer prevents frequent pruning operations.\n * @public\n */\nexport const TOMBSTONE_PRUNE_BUFFER_SIZE = 300\n\n/**\n * The minimum time interval (in milliseconds) between sending batched data messages\n * to clients. This debouncing prevents overwhelming clients with rapid updates.\n * @public\n */\nexport const DATA_MESSAGE_DEBOUNCE_INTERVAL = 1000 / 60\n\nconst timeSince = (time: number) => Date.now() - time\n\n/**\n * Represents the state of a document record within a sync room, including\n * its current data and the clock value when it was last modified.\n *\n * @internal\n */\nexport class DocumentState<R extends UnknownRecord> {\n\t/**\n\t * Create a DocumentState instance without validating the record data.\n\t * Used for performance when validation has already been performed.\n\t *\n\t * @param state - The record data\n\t * @param lastChangedClock - Clock value when this record was last modified\n\t * @param recordType - The record type definition for validation\n\t * @returns A new DocumentState instance\n\t */\n\tstatic createWithoutValidating<R extends UnknownRecord>(\n\t\tstate: R,\n\t\tlastChangedClock: number,\n\t\trecordType: RecordType<R, any>\n\t): DocumentState<R> {\n\t\treturn new DocumentState(state, lastChangedClock, recordType)\n\t}\n\n\t/**\n\t * Create a DocumentState instance with validation of the record data.\n\t *\n\t * @param state - The record data to validate\n\t * @param lastChangedClock - Clock value when this record was last modified\n\t * @param recordType - The record type definition for validation\n\t * @returns Result containing the DocumentState or validation error\n\t */\n\tstatic createAndValidate<R extends UnknownRecord>(\n\t\tstate: R,\n\t\tlastChangedClock: number,\n\t\trecordType: RecordType<R, any>\n\t): Result<DocumentState<R>, Error> {\n\t\ttry {\n\t\t\trecordType.validate(state)\n\t\t} catch (error: any) {\n\t\t\treturn Result.err(error)\n\t\t}\n\t\treturn Result.ok(new DocumentState(state, lastChangedClock, recordType))\n\t}\n\n\tprivate constructor(\n\t\tpublic readonly state: R,\n\t\tpublic readonly lastChangedClock: number,\n\t\tprivate readonly recordType: RecordType<R, any>\n\t) {}\n\n\t/**\n\t * Replace the current state with new state and calculate the diff.\n\t *\n\t * @param state - The new record state\n\t * @param clock - The new clock value\n\t * @param legacyAppendMode - If true, string append operations will be converted to Put operations\n\t * @returns Result containing the diff and new DocumentState, or null if no changes, or validation error\n\t */\n\treplaceState(\n\t\tstate: R,\n\t\tclock: number,\n\t\tlegacyAppendMode = false\n\t): Result<[ObjectDiff, DocumentState<R>] | null, Error> {\n\t\tconst diff = diffRecord(this.state, state, legacyAppendMode)\n\t\tif (!diff) return Result.ok(null)\n\t\ttry {\n\t\t\tthis.recordType.validate(state)\n\t\t} catch (error: any) {\n\t\t\treturn Result.err(error)\n\t\t}\n\t\treturn Result.ok([diff, new DocumentState(state, clock, this.recordType)])\n\t}\n\t/**\n\t * Apply a diff to the current state and return the resulting changes.\n\t *\n\t * @param diff - The object diff to apply\n\t * @param clock - The new clock value\n\t * @param legacyAppendMode - If true, string append operations will be converted to Put operations\n\t * @returns Result containing the final diff and new DocumentState, or null if no changes, or validation error\n\t */\n\tmergeDiff(\n\t\tdiff: ObjectDiff,\n\t\tclock: number,\n\t\tlegacyAppendMode = false\n\t): Result<[ObjectDiff, DocumentState<R>] | null, Error> {\n\t\tconst newState = applyObjectDiff(this.state, diff)\n\t\treturn this.replaceState(newState, clock, legacyAppendMode)\n\t}\n}\n\n/**\n * Snapshot of a room's complete state that can be persisted and restored.\n * Contains all documents, tombstones, and metadata needed to reconstruct the room.\n *\n * @public\n */\nexport interface RoomSnapshot {\n\t/**\n\t * The current logical clock value for the room\n\t */\n\tclock: number\n\t/**\n\t * Clock value when document data was last changed (optional for backwards compatibility)\n\t */\n\tdocumentClock?: number\n\t/**\n\t * Array of all document records with their last modification clocks\n\t */\n\tdocuments: Array<{ state: UnknownRecord; lastChangedClock: number }>\n\t/**\n\t * Map of deleted record IDs to their deletion clock values (optional)\n\t */\n\ttombstones?: Record<string, number>\n\t/**\n\t * Clock value where tombstone history begins - older deletions are not tracked (optional)\n\t */\n\ttombstoneHistoryStartsAtClock?: number\n\t/**\n\t * Serialized schema used when creating this snapshot (optional)\n\t */\n\tschema?: SerializedSchema\n}\n\nfunction getDocumentClock(snapshot: RoomSnapshot) {\n\tif (typeof snapshot.documentClock === 'number') {\n\t\treturn snapshot.documentClock\n\t}\n\tlet max = 0\n\tfor (const doc of snapshot.documents) {\n\t\tmax = Math.max(max, doc.lastChangedClock)\n\t}\n\tfor (const tombstone of Object.values(snapshot.tombstones ?? {})) {\n\t\tmax = Math.max(max, tombstone)\n\t}\n\treturn max\n}\n\n/**\n * A collaborative workspace that manages multiple client sessions and synchronizes\n * document changes between them. The room serves as the authoritative source for\n * all document state and handles conflict resolution, schema migrations, and\n * real-time data distribution.\n *\n * @example\n * ```ts\n * const room = new TLSyncRoom({\n * schema: mySchema,\n * onDataChange: () => saveToDatabase(room.getSnapshot()),\n * onPresenceChange: () => updateLiveCursors()\n * })\n *\n * // Handle new client connections\n * room.handleNewSession({\n * sessionId: 'user-123',\n * socket: webSocketAdapter,\n * meta: { userId: '123', name: 'Alice' },\n * isReadonly: false\n * })\n * ```\n *\n * @internal\n */\nexport class TLSyncRoom<R extends UnknownRecord, SessionMeta> {\n\t// A table of connected clients\n\treadonly sessions = new Map<string, RoomSession<R, SessionMeta>>()\n\n\t// eslint-disable-next-line local/prefer-class-methods\n\tpruneSessions = () => {\n\t\tfor (const client of this.sessions.values()) {\n\t\t\tswitch (client.state) {\n\t\t\t\tcase RoomSessionState.Connected: {\n\t\t\t\t\tconst hasTimedOut = timeSince(client.lastInteractionTime) > SESSION_IDLE_TIMEOUT\n\t\t\t\t\tif (hasTimedOut || !client.socket.isOpen) {\n\t\t\t\t\t\tthis.cancelSession(client.sessionId)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase RoomSessionState.AwaitingConnectMessage: {\n\t\t\t\t\tconst hasTimedOut = timeSince(client.sessionStartTime) > SESSION_START_WAIT_TIME\n\t\t\t\t\tif (hasTimedOut || !client.socket.isOpen) {\n\t\t\t\t\t\t// remove immediately\n\t\t\t\t\t\tthis.removeSession(client.sessionId)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase RoomSessionState.AwaitingRemoval: {\n\t\t\t\t\tconst hasTimedOut = timeSince(client.cancellationTime) > SESSION_REMOVAL_WAIT_TIME\n\t\t\t\t\tif (hasTimedOut) {\n\t\t\t\t\t\tthis.removeSession(client.sessionId)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\texhaustiveSwitchError(client)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate disposables: Array<() => void> = [interval(this.pruneSessions, 2000)]\n\n\tprivate _isClosed = false\n\n\t/**\n\t * Close the room and clean up all resources. Disconnects all sessions\n\t * and stops background processes.\n\t */\n\tclose() {\n\t\tthis.disposables.forEach((d) => d())\n\t\tthis.sessions.forEach((session) => {\n\t\t\tsession.socket.close()\n\t\t})\n\t\tthis._isClosed = true\n\t}\n\n\t/**\n\t * Check if the room has been closed and is no longer accepting connections.\n\t *\n\t * @returns True if the room is closed\n\t */\n\tisClosed() {\n\t\treturn this._isClosed\n\t}\n\n\treadonly events = createNanoEvents<{\n\t\troom_became_empty(): void\n\t\tsession_removed(args: { sessionId: string; meta: SessionMeta }): void\n\t}>()\n\n\t// Values associated with each uid (must be serializable).\n\t/** @internal */\n\tdocuments: AtomMap<string, DocumentState<R>>\n\ttombstones: AtomMap<string, number>\n\n\t// this clock should start higher than the client, to make sure that clients who sync with their\n\t// initial lastServerClock value get the full state\n\t// in this case clients will start with 0, and the server will start with 1\n\tclock: number\n\tdocumentClock: number\n\ttombstoneHistoryStartsAtClock: number\n\t// map from record id to clock upon deletion\n\n\treadonly serializedSchema: SerializedSchema\n\n\treadonly documentTypes: Set<string>\n\treadonly presenceType: RecordType<R, any> | null\n\tprivate log?: TLSyncLog\n\tpublic readonly schema: StoreSchema<R, any>\n\tprivate onDataChange?(): void\n\tprivate onPresenceChange?(): void\n\n\tconstructor(opts: {\n\t\tlog?: TLSyncLog\n\t\tschema: StoreSchema<R, any>\n\t\tsnapshot?: RoomSnapshot\n\t\tonDataChange?(): void\n\t\tonPresenceChange?(): void\n\t}) {\n\t\tthis.schema = opts.schema\n\t\tlet snapshot = opts.snapshot\n\t\tthis.log = opts.log\n\t\tthis.onDataChange = opts.onDataChange\n\t\tthis.onPresenceChange = opts.onPresenceChange\n\n\t\tassert(\n\t\t\tisNativeStructuredClone,\n\t\t\t'TLSyncRoom is supposed to run either on Cloudflare Workers' +\n\t\t\t\t'or on a 18+ version of Node.js, which both support the native structuredClone API'\n\t\t)\n\n\t\t// do a json serialization cycle to make sure the schema has no 'undefined' values\n\t\tthis.serializedSchema = JSON.parse(JSON.stringify(this.schema.serialize()))\n\n\t\tthis.documentTypes = new Set(\n\t\t\tObject.values<RecordType<R, any>>(this.schema.types)\n\t\t\t\t.filter((t) => t.scope === 'document')\n\t\t\t\t.map((t) => t.typeName)\n\t\t)\n\n\t\tconst presenceTypes = new Set(\n\t\t\tObject.values<RecordType<R, any>>(this.schema.types).filter((t) => t.scope === 'presence')\n\t\t)\n\n\t\tif (presenceTypes.size > 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`TLSyncRoom: exactly zero or one presence type is expected, but found ${presenceTypes.size}`\n\t\t\t)\n\t\t}\n\n\t\tthis.presenceType = presenceTypes.values().next()?.value ?? null\n\n\t\tif (!snapshot) {\n\t\t\tsnapshot = {\n\t\t\t\tclock: 0,\n\t\t\t\tdocumentClock: 0,\n\t\t\t\tdocuments: [\n\t\t\t\t\t{\n\t\t\t\t\t\tstate: DocumentRecordType.create({ id: TLDOCUMENT_ID }),\n\t\t\t\t\t\tlastChangedClock: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tstate: PageRecordType.create({ name: 'Page 1', index: 'a1' as IndexKey }),\n\t\t\t\t\t\tlastChangedClock: 0,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\t\t}\n\n\t\tthis.clock = snapshot.clock\n\n\t\tlet didIncrementClock = false\n\t\tconst ensureClockDidIncrement = (_reason: string) => {\n\t\t\tif (!didIncrementClock) {\n\t\t\t\tdidIncrementClock = true\n\t\t\t\tthis.clock++\n\t\t\t}\n\t\t}\n\n\t\tthis.tombstones = new AtomMap(\n\t\t\t'room tombstones',\n\t\t\tobjectMapEntriesIterable(snapshot.tombstones ?? {})\n\t\t)\n\t\tthis.documents = new AtomMap(\n\t\t\t'room documents',\n\t\t\tfunction* (this: TLSyncRoom<R, SessionMeta>) {\n\t\t\t\tfor (const doc of snapshot.documents) {\n\t\t\t\t\tif (this.documentTypes.has(doc.state.typeName)) {\n\t\t\t\t\t\tyield [\n\t\t\t\t\t\t\tdoc.state.id,\n\t\t\t\t\t\t\tDocumentState.createWithoutValidating<R>(\n\t\t\t\t\t\t\t\tdoc.state as R,\n\t\t\t\t\t\t\t\tdoc.lastChangedClock,\n\t\t\t\t\t\t\t\tassertExists(getOwnProperty(this.schema.types, doc.state.typeName))\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t] as const\n\t\t\t\t\t} else {\n\t\t\t\t\t\tensureClockDidIncrement('doc type was not doc type')\n\t\t\t\t\t\tthis.tombstones.set(doc.state.id, this.clock)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}.call(this)\n\t\t)\n\n\t\tthis.tombstoneHistoryStartsAtClock =\n\t\t\tsnapshot.tombstoneHistoryStartsAtClock ?? findMin(this.tombstones.values()) ?? this.clock\n\n\t\tif (this.tombstoneHistoryStartsAtClock === 0) {\n\t\t\t// Before this comment was added, new clients would send '0' as their 'lastServerClock'\n\t\t\t// which was technically an error because clocks start at 0, but the error didn't manifest\n\t\t\t// because we initialized tombstoneHistoryStartsAtClock to 1 and then never updated it.\n\t\t\t// Now that we handle tombstoneHistoryStartsAtClock properly we need to increment it here to make sure old\n\t\t\t// clients still get data when they connect. This if clause can be deleted after a few months.\n\t\t\tthis.tombstoneHistoryStartsAtClock++\n\t\t}\n\n\t\ttransact(() => {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\tconst schema = snapshot.schema ?? this.schema.serializeEarliestVersion()\n\n\t\t\tconst migrationsToApply = this.schema.getMigrationsSince(schema)\n\t\t\tassert(migrationsToApply.ok, 'Failed to get migrations')\n\n\t\t\tif (migrationsToApply.value.length > 0) {\n\t\t\t\t// only bother allocating a snapshot if there are migrations to apply\n\t\t\t\tconst store = {} as Record<IdOf<R>, R>\n\t\t\t\tfor (const [k, v] of this.documents.entries()) {\n\t\t\t\t\tstore[k as IdOf<R>] = v.state\n\t\t\t\t}\n\n\t\t\t\tconst migrationResult = this.schema.migrateStoreSnapshot(\n\t\t\t\t\t{ store, schema },\n\t\t\t\t\t{ mutateInputStore: true }\n\t\t\t\t)\n\n\t\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\t\t// TODO: Fault tolerance\n\t\t\t\t\tthrow new Error('Failed to migrate: ' + migrationResult.reason)\n\t\t\t\t}\n\n\t\t\t\t// use for..in to iterate over the keys of the object because it consumes less memory than\n\t\t\t\t// Object.entries\n\t\t\t\tfor (const id in migrationResult.value) {\n\t\t\t\t\tif (!Object.prototype.hasOwnProperty.call(migrationResult.value, id)) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst r = migrationResult.value[id as keyof typeof migrationResult.value]\n\t\t\t\t\tconst existing = this.documents.get(id)\n\t\t\t\t\tif (!existing || !isEqual(existing.state, r)) {\n\t\t\t\t\t\t// record was added or updated during migration\n\t\t\t\t\t\tensureClockDidIncrement('record was added or updated during migration')\n\t\t\t\t\t\tthis.documents.set(\n\t\t\t\t\t\t\tr.id,\n\t\t\t\t\t\t\tDocumentState.createWithoutValidating(\n\t\t\t\t\t\t\t\tr,\n\t\t\t\t\t\t\t\tthis.clock,\n\t\t\t\t\t\t\t\tassertExists(getOwnProperty(this.schema.types, r.typeName)) as any\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\n\t\t\t\tfor (const id of this.documents.keys()) {\n\t\t\t\t\tif (!migrationResult.value[id as keyof typeof migrationResult.value]) {\n\t\t\t\t\t\t// record was removed during migration\n\t\t\t\t\t\tensureClockDidIncrement('record was removed during migration')\n\t\t\t\t\t\tthis.tombstones.set(id, this.clock)\n\t\t\t\t\t\tthis.documents.delete(id)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pruneTombstones()\n\t\t})\n\n\t\tif (didIncrementClock) {\n\t\t\tthis.documentClock = this.clock\n\t\t\topts.onDataChange?.()\n\t\t} else {\n\t\t\tthis.documentClock = getDocumentClock(snapshot)\n\t\t}\n\t}\n\n\tprivate didSchedulePrune = true\n\t// eslint-disable-next-line local/prefer-class-methods\n\tprivate pruneTombstones = () => {\n\t\tthis.didSchedulePrune = false\n\t\t// avoid blocking any pending responses\n\t\tif (this.tombstones.size > MAX_TOMBSTONES) {\n\t\t\tconst entries = Array.from(this.tombstones.entries())\n\t\t\t// sort entries in ascending order by clock\n\t\t\tentries.sort((a, b) => a[1] - b[1])\n\t\t\tlet idx = entries.length - 1 - MAX_TOMBSTONES + TOMBSTONE_PRUNE_BUFFER_SIZE\n\t\t\tconst cullClock = entries[idx++][1]\n\t\t\twhile (idx < entries.length && entries[idx][1] === cullClock) {\n\t\t\t\tidx++\n\t\t\t}\n\t\t\t// trim off the first bunch\n\t\t\tconst keysToDelete = entries.slice(0, idx).map(([key]) => key)\n\n\t\t\tthis.tombstoneHistoryStartsAtClock = cullClock + 1\n\t\t\tthis.tombstones.deleteMany(keysToDelete)\n\t\t}\n\t}\n\n\tprivate getDocument(id: string) {\n\t\treturn this.documents.get(id)\n\t}\n\n\tprivate addDocument(id: string, state: R, clock: number): Result<void, Error> {\n\t\tif (this.tombstones.has(id)) {\n\t\t\tthis.tombstones.delete(id)\n\t\t}\n\t\tconst createResult = DocumentState.createAndValidate(\n\t\t\tstate,\n\t\t\tclock,\n\t\t\tassertExists(getOwnProperty(this.schema.types, state.typeName))\n\t\t)\n\t\tif (!createResult.ok) return createResult\n\t\tthis.documents.set(id, createResult.value)\n\t\treturn Result.ok(undefined)\n\t}\n\n\tprivate removeDocument(id: string, clock: number) {\n\t\tthis.documents.delete(id)\n\t\tthis.tombstones.set(id, clock)\n\t\tif (!this.didSchedulePrune) {\n\t\t\tthis.didSchedulePrune = true\n\t\t\tsetTimeout(this.pruneTombstones, 0)\n\t\t}\n\t}\n\n\t/**\n\t * Get a complete snapshot of the current room state that can be persisted\n\t * and later used to restore the room.\n\t *\n\t * @returns Room snapshot containing all documents, tombstones, and metadata\n\t * @example\n\t * ```ts\n\t * const snapshot = room.getSnapshot()\n\t * await database.saveRoomSnapshot(roomId, snapshot)\n\t *\n\t * // Later, restore from snapshot\n\t * const restoredRoom = new TLSyncRoom({\n\t * schema: mySchema,\n\t * snapshot: snapshot\n\t * })\n\t * ```\n\t */\n\tgetSnapshot(): RoomSnapshot {\n\t\tconst tombstones = Object.fromEntries(this.tombstones.entries())\n\t\tconst documents = []\n\t\tfor (const doc of this.documents.values()) {\n\t\t\tif (this.documentTypes.has(doc.state.typeName)) {\n\t\t\t\tdocuments.push({\n\t\t\t\t\tstate: doc.state,\n\t\t\t\t\tlastChangedClock: doc.lastChangedClock,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tclock: this.clock,\n\t\t\tdocumentClock: this.documentClock,\n\t\t\ttombstones,\n\t\t\ttombstoneHistoryStartsAtClock: this.tombstoneHistoryStartsAtClock,\n\t\t\tschema: this.serializedSchema,\n\t\t\tdocuments,\n\t\t}\n\t}\n\n\t/**\n\t * Send a message to a particular client. Debounces data events\n\t *\n\t * @param sessionId - The id of the session to send the message to.\n\t * @param message - The message to send.\n\t */\n\tprivate sendMessage(\n\t\tsessionId: string,\n\t\tmessage: TLSocketServerSentEvent<R> | TLSocketServerSentDataEvent<R>\n\t) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) {\n\t\t\tthis.log?.warn?.('Tried to send message to unknown session', message.type)\n\t\t\treturn\n\t\t}\n\t\tif (session.state !== RoomSessionState.Connected) {\n\t\t\tthis.log?.warn?.('Tried to send message to disconnected client', message.type)\n\t\t\treturn\n\t\t}\n\t\tif (session.socket.isOpen) {\n\t\t\tif (message.type !== 'patch' && message.type !== 'push_result') {\n\t\t\t\t// this is not a data message\n\t\t\t\tif (message.type !== 'pong') {\n\t\t\t\t\t// non-data messages like \"connect\" might still need to be ordered correctly with\n\t\t\t\t\t// respect to data messages, so it's better to flush just in case\n\t\t\t\t\tthis._flushDataMessages(sessionId)\n\t\t\t\t}\n\t\t\t\tsession.socket.sendMessage(message)\n\t\t\t} else {\n\t\t\t\tif (session.debounceTimer === null) {\n\t\t\t\t\t// this is the first message since the last flush, don't delay it\n\t\t\t\t\tsession.socket.sendMessage({ type: 'data', data: [message] })\n\n\t\t\t\t\tsession.debounceTimer = setTimeout(\n\t\t\t\t\t\t() => this._flushDataMessages(sessionId),\n\t\t\t\t\t\tDATA_MESSAGE_DEBOUNCE_INTERVAL\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tsession.outstandingDataMessages.push(message)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.cancelSession(session.sessionId)\n\t\t}\n\t}\n\n\t// needs to accept sessionId and not a session because the session might be dead by the time\n\t// the timer fires\n\t_flushDataMessages(sessionId: string) {\n\t\tconst session = this.sessions.get(sessionId)\n\n\t\tif (!session || session.state !== RoomSessionState.Connected) {\n\t\t\treturn\n\t\t}\n\n\t\tsession.debounceTimer = null\n\n\t\tif (session.outstandingDataMessages.length > 0) {\n\t\t\tsession.socket.sendMessage({ type: 'data', data: session.outstandingDataMessages })\n\t\t\tsession.outstandingDataMessages.length = 0\n\t\t}\n\t}\n\n\t/** @internal */\n\tprivate removeSession(sessionId: string, fatalReason?: string) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) {\n\t\t\tthis.log?.warn?.('Tried to remove unknown session')\n\t\t\treturn\n\t\t}\n\n\t\tthis.sessions.delete(sessionId)\n\n\t\tconst presence = this.getDocument(session.presenceId ?? '')\n\n\t\ttry {\n\t\t\tif (fatalReason) {\n\t\t\t\tsession.socket.close(TLSyncErrorCloseEventCode, fatalReason)\n\t\t\t} else {\n\t\t\t\tsession.socket.close()\n\t\t\t}\n\t\t} catch {\n\t\t\t// noop, calling .close() multiple times is fine\n\t\t}\n\n\t\tif (presence) {\n\t\t\tthis.documents.delete(session.presenceId!)\n\n\t\t\tthis.broadcastPatch({\n\t\t\t\tdiff: { [session.presenceId!]: [RecordOpType.Remove] },\n\t\t\t\tsourceSessionId: sessionId,\n\t\t\t})\n\t\t}\n\n\t\tthis.events.emit('session_removed', { sessionId, meta: session.meta })\n\t\tif (this.sessions.size === 0) {\n\t\t\tthis.events.emit('room_became_empty')\n\t\t}\n\t}\n\n\tprivate cancelSession(sessionId: string) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) {\n\t\t\treturn\n\t\t}\n\n\t\tif (session.state === RoomSessionState.AwaitingRemoval) {\n\t\t\tthis.log?.warn?.('Tried to cancel session that is already awaiting removal')\n\t\t\treturn\n\t\t}\n\n\t\tthis.sessions.set(sessionId, {\n\t\t\tstate: RoomSessionState.AwaitingRemoval,\n\t\t\tsessionId,\n\t\t\tpresenceId: session.presenceId,\n\t\t\tsocket: session.socket,\n\t\t\tcancellationTime: Date.now(),\n\t\t\tmeta: session.meta,\n\t\t\tisReadonly: session.isReadonly,\n\t\t\trequiresLegacyRejection: session.requiresLegacyRejection,\n\t\t\tsupportsStringAppend: session.supportsStringAppend,\n\t\t})\n\n\t\ttry {\n\t\t\tsession.socket.close()\n\t\t} catch {\n\t\t\t// noop, calling .close() multiple times is fine\n\t\t}\n\t}\n\n\t/**\n\t * Broadcast a patch to all connected clients except the one with the sessionId provided.\n\t * Automatically handles schema migration for clients on different versions.\n\t *\n\t * @param message - The broadcast message\n\t * - diff - The network diff to broadcast to all clients\n\t * - sourceSessionId - Optional ID of the session that originated this change (excluded from broadcast)\n\t * @returns This room instance for method chaining\n\t * @example\n\t * ```ts\n\t * room.broadcastPatch({\n\t * diff: { 'shape:123': [RecordOpType.Put, newShapeData] },\n\t * sourceSessionId: 'user-456' // This user won't receive the broadcast\n\t * })\n\t * ```\n\t */\n\tbroadcastPatch(message: { diff: NetworkDiff<R>; sourceSessionId?: string }) {\n\t\tconst { diff, sourceSessionId } = message\n\t\tthis.sessions.forEach((session) => {\n\t\t\tif (session.state !== RoomSessionState.Connected) return\n\t\t\tif (sourceSessionId === session.sessionId) return\n\t\t\tif (!session.socket.isOpen) {\n\t\t\t\tthis.cancelSession(session.sessionId)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst res = this.migrateDiffForSession(session.serializedSchema, diff)\n\n\t\t\tif (!res.ok) {\n\t\t\t\t// disconnect client and send incompatibility error\n\t\t\t\tthis.rejectSession(\n\t\t\t\t\tsession.sessionId,\n\t\t\t\t\tres.error === MigrationFailureReason.TargetVersionTooNew\n\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.sendMessage(session.sessionId, {\n\t\t\t\ttype: 'patch',\n\t\t\t\tdiff: res.value,\n\t\t\t\tserverClock: this.clock,\n\t\t\t})\n\t\t})\n\t\treturn this\n\t}\n\n\t/**\n\t * Send a custom message to a connected client. Useful for application-specific\n\t * communication that doesn't involve document synchronization.\n\t *\n\t * @param sessionId - The ID of the session to send the message to\n\t * @param data - The custom payload to send (will be JSON serialized)\n\t * @example\n\t * ```ts\n\t * // Send a custom notification\n\t * room.sendCustomMessage('user-123', {\n\t * type: 'notification',\n\t * message: 'Document saved successfully'\n\t * })\n\t *\n\t * // Send user-specific data\n\t * room.sendCustomMessage('user-456', {\n\t * type: 'user_permissions',\n\t * canEdit: true,\n\t * canDelete: false\n\t * })\n\t * ```\n\t */\n\tsendCustomMessage(sessionId: string, data: any): void {\n\t\tthis.sendMessage(sessionId, { type: 'custom', data })\n\t}\n\n\t/**\n\t * Register a new client session with the room. The session will be in an awaiting\n\t * state until it sends a connect message with protocol handshake.\n\t *\n\t * @param opts - Session configuration\n\t * - sessionId - Unique identifier for this session\n\t * - socket - WebSocket adapter for communication\n\t * - meta - Application-specific metadata for this session\n\t * - isReadonly - Whether this session can modify documents\n\t * @returns This room instance for method chaining\n\t * @example\n\t * ```ts\n\t * room.handleNewSession({\n\t * sessionId: crypto.randomUUID(),\n\t * socket: new WebSocketAdapter(ws),\n\t * meta: { userId: '123', name: 'Alice', avatar: 'url' },\n\t * isReadonly: !hasEditPermission\n\t * })\n\t * ```\n\t *\n\t * @internal\n\t */\n\thandleNewSession(opts: {\n\t\tsessionId: string\n\t\tsocket: TLRoomSocket<R>\n\t\tmeta: SessionMeta\n\t\tisReadonly: boolean\n\t}) {\n\t\tconst { sessionId, socket, meta, isReadonly } = opts\n\t\tconst existing = this.sessions.get(sessionId)\n\t\tthis.sessions.set(sessionId, {\n\t\t\tstate: RoomSessionState.AwaitingConnectMessage,\n\t\t\tsessionId,\n\t\t\tsocket,\n\t\t\tpresenceId: existing?.presenceId ?? this.presenceType?.createId() ?? null,\n\t\t\tsessionStartTime: Date.now(),\n\t\t\tmeta,\n\t\t\tisReadonly: isReadonly ?? false,\n\t\t\t// this gets set later during handleConnectMessage\n\t\t\trequiresLegacyRejection: false,\n\t\t\tsupportsStringAppend: true,\n\t\t})\n\t\treturn this\n\t}\n\n\t/**\n\t * Checks if all connected sessions support string append operations (protocol version 8+).\n\t * If any client is on an older version, returns false to enable legacy append mode.\n\t *\n\t * @returns True if all connected sessions are on protocol version 8 or higher\n\t */\n\tgetCanEmitStringAppend(): boolean {\n\t\tfor (const session of this.sessions.values()) {\n\t\t\tif (session.state === RoomSessionState.Connected) {\n\t\t\t\tif (!session.supportsStringAppend) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\t/**\n\t * When we send a diff to a client, if that client is on a lower version than us, we need to make\n\t * the diff compatible with their version. At the moment this means migrating each affected record\n\t * to the client's version and sending the whole record again. We can optimize this later by\n\t * keeping the previous versions of records around long enough to recalculate these diffs for\n\t * older client versions.\n\t */\n\tprivate migrateDiffForSession(\n\t\tserializedSchema: SerializedSchema,\n\t\tdiff: NetworkDiff<R>\n\t): Result<NetworkDiff<R>, MigrationFailureReason> {\n\t\t// TODO: optimize this by recalculating patches using the previous versions of records\n\n\t\t// when the client connects we check whether the schema is identical and make sure\n\t\t// to use the same object reference so that === works on this line\n\t\tif (serializedSchema === this.serializedSchema) {\n\t\t\treturn Result.ok(diff)\n\t\t}\n\n\t\tconst result: NetworkDiff<R> = {}\n\t\tfor (const [id, op] of objectMapEntriesIterable(diff)) {\n\t\t\tif (op[0] === RecordOpType.Remove) {\n\t\t\t\tresult[id] = op\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst doc = this.getDocument(id)\n\t\t\tif (!doc) {\n\t\t\t\treturn Result.err(MigrationFailureReason.TargetVersionTooNew)\n\t\t\t}\n\t\t\tconst migrationResult = this.schema.migratePersistedRecord(\n\t\t\t\tdoc.state,\n\t\t\t\tserializedSchema,\n\t\t\t\t'down'\n\t\t\t)\n\n\t\t\tif (migrationResult.type === 'error') {\n\t\t\t\treturn Result.err(migrationResult.reason)\n\t\t\t}\n\n\t\t\tresult[id] = [RecordOpType.Put, migrationResult.value]\n\t\t}\n\n\t\treturn Result.ok(result)\n\t}\n\n\t/**\n\t * Process an incoming message from a client session. Handles connection requests,\n\t * data synchronization pushes, and ping/pong for connection health.\n\t *\n\t * @param sessionId - The ID of the session that sent the message\n\t * @param message - The client message to process\n\t * @example\n\t * ```ts\n\t * // Typically called by WebSocket message handlers\n\t * websocket.onMessage((data) => {\n\t * const message = JSON.parse(data)\n\t * room.handleMessage(sessionId, message)\n\t * })\n\t * ```\n\t */\n\tasync handleMessage(sessionId: string, message: TLSocketClientSentEvent<R>) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) {\n\t\t\tthis.log?.warn?.('Received message from unknown session')\n\t\t\treturn\n\t\t}\n\t\tswitch (message.type) {\n\t\t\tcase 'connect': {\n\t\t\t\treturn this.handleConnectRequest(session, message)\n\t\t\t}\n\t\t\tcase 'push': {\n\t\t\t\treturn this.handlePushRequest(session, message)\n\t\t\t}\n\t\t\tcase 'ping': {\n\t\t\t\tif (session.state === RoomSessionState.Connected) {\n\t\t\t\t\tsession.lastInteractionTime = Date.now()\n\t\t\t\t}\n\t\t\t\treturn this.sendMessage(session.sessionId, { type: 'pong' })\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\texhaustiveSwitchError(message)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Reject and disconnect a session due to incompatibility or other fatal errors.\n\t * Sends appropriate error messages before closing the connection.\n\t *\n\t * @param sessionId - The session to reject\n\t * @param fatalReason - The reason for rejection (optional)\n\t * @example\n\t * ```ts\n\t * // Reject due to version mismatch\n\t * room.rejectSession('user-123', TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t *\n\t * // Reject due to permission issue\n\t * room.rejectSession('user-456', 'Insufficient permissions')\n\t * ```\n\t */\n\trejectSession(sessionId: string, fatalReason?: TLSyncErrorCloseEventReason | string) {\n\t\tconst session = this.sessions.get(sessionId)\n\t\tif (!session) return\n\t\tif (!fatalReason) {\n\t\t\tthis.removeSession(sessionId)\n\t\t\treturn\n\t\t}\n\t\tif (session.requiresLegacyRejection) {\n\t\t\ttry {\n\t\t\t\tif (session.socket.isOpen) {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\tlet legacyReason: TLIncompatibilityReason\n\t\t\t\t\tswitch (fatalReason) {\n\t\t\t\t\t\tcase TLSyncErrorCloseEventReason.CLIENT_TOO_OLD:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\t\t\tlegacyReason = TLIncompatibilityReason.ClientTooOld\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase TLSyncErrorCloseEventReason.SERVER_TOO_OLD:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\t\t\tlegacyReason = TLIncompatibilityReason.ServerTooOld\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase TLSyncErrorCloseEventReason.INVALID_RECORD:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\t\t\tlegacyReason = TLIncompatibilityReason.InvalidRecord\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\t\t\t\t\t\tlegacyReason = TLIncompatibilityReason.InvalidOperation\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tsession.socket.sendMessage({\n\t\t\t\t\t\ttype: 'incompatibility_error',\n\t\t\t\t\t\treason: legacyReason,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// noop\n\t\t\t} finally {\n\t\t\t\tthis.removeSession(sessionId)\n\t\t\t}\n\t\t} else {\n\t\t\tthis.removeSession(sessionId, fatalReason)\n\t\t}\n\t}\n\n\tprivate handleConnectRequest(\n\t\tsession: RoomSession<R, SessionMeta>,\n\t\tmessage: Extract<TLSocketClientSentEvent<R>, { type: 'connect' }>\n\t) {\n\t\t// if the protocol versions don't match, disconnect the client\n\t\t// we will eventually want to try to make our protocol backwards compatible to some degree\n\t\t// and have a MIN_PROTOCOL_VERSION constant that the TLSyncRoom implements support for\n\t\tlet theirProtocolVersion = message.protocolVersion\n\t\t// 5 is the same as 6\n\t\tif (theirProtocolVersion === 5) {\n\t\t\ttheirProtocolVersion = 6\n\t\t}\n\t\t// 6 is almost the same as 7\n\t\tsession.requiresLegacyRejection = theirProtocolVersion === 6\n\t\tif (theirProtocolVersion === 6) {\n\t\t\ttheirProtocolVersion++\n\t\t}\n\t\tif (theirProtocolVersion === 7) {\n\t\t\ttheirProtocolVersion++\n\t\t\tsession.supportsStringAppend = false\n\t\t}\n\n\t\tif (theirProtocolVersion == null || theirProtocolVersion < getTlsyncProtocolVersion()) {\n\t\t\tthis.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\treturn\n\t\t} else if (theirProtocolVersion > getTlsyncProtocolVersion()) {\n\t\t\tthis.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.SERVER_TOO_OLD)\n\t\t\treturn\n\t\t}\n\t\t// If the client's store is at a different version to ours, it could cause corruption.\n\t\t// We should disconnect the client and ask them to refresh.\n\t\tif (message.schema == null) {\n\t\t\tthis.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\treturn\n\t\t}\n\t\tconst migrations = this.schema.getMigrationsSince(message.schema)\n\t\t// if the client's store is at a different version to ours, we can't support them\n\t\tif (!migrations.ok || migrations.value.some((m) => m.scope === 'store' || !m.down)) {\n\t\t\tthis.rejectSession(session.sessionId, TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\treturn\n\t\t}\n\n\t\tconst sessionSchema = isEqual(message.schema, this.serializedSchema)\n\t\t\t? this.serializedSchema\n\t\t\t: message.schema\n\n\t\tconst connect = async (msg: Extract<TLSocketServerSentEvent<R>, { type: 'connect' }>) => {\n\t\t\tthis.sessions.set(session.sessionId, {\n\t\t\t\tstate: RoomSessionState.Connected,\n\t\t\t\tsessionId: session.sessionId,\n\t\t\t\tpresenceId: session.presenceId,\n\t\t\t\tsocket: session.socket,\n\t\t\t\tserializedSchema: sessionSchema,\n\t\t\t\tlastInteractionTime: Date.now(),\n\t\t\t\tdebounceTimer: null,\n\t\t\t\toutstandingDataMessages: [],\n\t\t\t\tsupportsStringAppend: session.supportsStringAppend,\n\t\t\t\tmeta: session.meta,\n\t\t\t\tisReadonly: session.isReadonly,\n\t\t\t\trequiresLegacyRejection: session.requiresLegacyRejection,\n\t\t\t})\n\t\t\tthis.sendMessage(session.sessionId, msg)\n\t\t}\n\n\t\ttransaction((rollback) => {\n\t\t\tif (\n\t\t\t\t// if the client requests changes since a time before we have tombstone history, send them the full state\n\t\t\t\tmessage.lastServerClock < this.tombstoneHistoryStartsAtClock ||\n\t\t\t\t// similarly, if they ask for a time we haven't reached yet, send them the full state\n\t\t\t\t// this will only happen if the DB is reset (or there is no db) and the server restarts\n\t\t\t\t// or if the server exits/crashes with unpersisted changes\n\t\t\t\tmessage.lastServerClock > this.clock\n\t\t\t) {\n\t\t\t\tconst diff: NetworkDiff<R> = {}\n\t\t\t\tfor (const [id, doc] of this.documents.entries()) {\n\t\t\t\t\tif (id !== session.presenceId) {\n\t\t\t\t\t\tdiff[id] = [RecordOpType.Put, doc.state]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst migrated = this.migrateDiffForSession(sessionSchema, diff)\n\t\t\t\tif (!migrated.ok) {\n\t\t\t\t\trollback()\n\t\t\t\t\tthis.rejectSession(\n\t\t\t\t\t\tsession.sessionId,\n\t\t\t\t\t\tmigrated.error === MigrationFailureReason.TargetVersionTooNew\n\t\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconnect({\n\t\t\t\t\ttype: 'connect',\n\t\t\t\t\tconnectRequestId: message.connectRequestId,\n\t\t\t\t\thydrationType: 'wipe_all',\n\t\t\t\t\tprotocolVersion: getTlsyncProtocolVersion(),\n\t\t\t\t\tschema: this.schema.serialize(),\n\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\tdiff: migrated.value,\n\t\t\t\t\tisReadonly: session.isReadonly,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\t// calculate the changes since the time the client last saw\n\t\t\t\tconst diff: NetworkDiff<R> = {}\n\t\t\t\tfor (const doc of this.documents.values()) {\n\t\t\t\t\tif (doc.lastChangedClock > message.lastServerClock) {\n\t\t\t\t\t\tdiff[doc.state.id] = [RecordOpType.Put, doc.state]\n\t\t\t\t\t} else if (this.presenceType?.isId(doc.state.id) && doc.state.id !== session.presenceId) {\n\t\t\t\t\t\tdiff[doc.state.id] = [RecordOpType.Put, doc.state]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const [id, deletedAtClock] of this.tombstones.entries()) {\n\t\t\t\t\tif (deletedAtClock > message.lastServerClock) {\n\t\t\t\t\t\tdiff[id] = [RecordOpType.Remove]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst migrated = this.migrateDiffForSession(sessionSchema, diff)\n\t\t\t\tif (!migrated.ok) {\n\t\t\t\t\trollback()\n\t\t\t\t\tthis.rejectSession(\n\t\t\t\t\t\tsession.sessionId,\n\t\t\t\t\t\tmigrated.error === MigrationFailureReason.TargetVersionTooNew\n\t\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconnect({\n\t\t\t\t\ttype: 'connect',\n\t\t\t\t\tconnectRequestId: message.connectRequestId,\n\t\t\t\t\thydrationType: 'wipe_presence',\n\t\t\t\t\tschema: this.schema.serialize(),\n\t\t\t\t\tprotocolVersion: getTlsyncProtocolVersion(),\n\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\tdiff: migrated.value,\n\t\t\t\t\tisReadonly: session.isReadonly,\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n\n\tprivate handlePushRequest(\n\t\tsession: RoomSession<R, SessionMeta> | null,\n\t\tmessage: Extract<TLSocketClientSentEvent<R>, { type: 'push' }>\n\t) {\n\t\t// We must be connected to handle push requests\n\t\tif (session && session.state !== RoomSessionState.Connected) {\n\t\t\treturn\n\t\t}\n\n\t\t// update the last interaction time\n\t\tif (session) {\n\t\t\tsession.lastInteractionTime = Date.now()\n\t\t}\n\n\t\t// increment the clock for this push\n\t\tthis.clock++\n\n\t\tconst initialDocumentClock = this.documentClock\n\t\tlet didPresenceChange = false\n\t\ttransaction((rollback) => {\n\t\t\tconst legacyAppendMode = !this.getCanEmitStringAppend()\n\t\t\t// collect actual ops that resulted from the push\n\t\t\t// these will be broadcast to other users\n\t\t\tinterface ActualChanges {\n\t\t\t\tdiff: NetworkDiff<R> | null\n\t\t\t}\n\t\t\tconst docChanges: ActualChanges = { diff: null }\n\t\t\tconst presenceChanges: ActualChanges = { diff: null }\n\n\t\t\tconst propagateOp = (changes: ActualChanges, id: string, op: RecordOp<R>) => {\n\t\t\t\tif (!changes.diff) changes.diff = {}\n\t\t\t\tchanges.diff[id] = op\n\t\t\t}\n\n\t\t\tconst fail = (\n\t\t\t\treason: TLSyncErrorCloseEventReason,\n\t\t\t\tunderlyingError?: Error\n\t\t\t): Result<void, void> => {\n\t\t\t\trollback()\n\t\t\t\tif (session) {\n\t\t\t\t\tthis.rejectSession(session.sessionId, reason)\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error('failed to apply changes: ' + reason, underlyingError)\n\t\t\t\t}\n\t\t\t\tif (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') {\n\t\t\t\t\tthis.log?.error?.('failed to apply push', reason, message, underlyingError)\n\t\t\t\t}\n\t\t\t\treturn Result.err(undefined)\n\t\t\t}\n\n\t\t\tconst addDocument = (changes: ActualChanges, id: string, _state: R): Result<void, void> => {\n\t\t\t\tconst res = session\n\t\t\t\t\t? this.schema.migratePersistedRecord(_state, session.serializedSchema, 'up')\n\t\t\t\t\t: { type: 'success' as const, value: _state }\n\t\t\t\tif (res.type === 'error') {\n\t\t\t\t\treturn fail(\n\t\t\t\t\t\tres.reason === MigrationFailureReason.TargetVersionTooOld // target version is our version\n\t\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst { value: state } = res\n\n\t\t\t\t// Get the existing document, if any\n\t\t\t\tconst doc = this.getDocument(id)\n\n\t\t\t\tif (doc) {\n\t\t\t\t\t// If there's an existing document, replace it with the new state\n\t\t\t\t\t// but propagate a diff rather than the entire value\n\t\t\t\t\tconst diff = doc.replaceState(state, this.clock, legacyAppendMode)\n\t\t\t\t\tif (!diff.ok) {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t}\n\t\t\t\t\tif (diff.value) {\n\t\t\t\t\t\tthis.documents.set(id, diff.value[1])\n\t\t\t\t\t\tpropagateOp(changes, id, [RecordOpType.Patch, diff.value[0]])\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Otherwise, if we don't already have a document with this id\n\t\t\t\t\t// create the document and propagate the put op\n\t\t\t\t\tconst result = this.addDocument(id, state, this.clock)\n\t\t\t\t\tif (!result.ok) {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t}\n\t\t\t\t\tpropagateOp(changes, id, [RecordOpType.Put, state])\n\t\t\t\t}\n\n\t\t\t\treturn Result.ok(undefined)\n\t\t\t}\n\n\t\t\tconst patchDocument = (\n\t\t\t\tchanges: ActualChanges,\n\t\t\t\tid: string,\n\t\t\t\tpatch: ObjectDiff\n\t\t\t): Result<void, void> => {\n\t\t\t\t// if it was already deleted, there's no need to apply the patch\n\t\t\t\tconst doc = this.getDocument(id)\n\t\t\t\tif (!doc) return Result.ok(undefined)\n\t\t\t\t// If the client's version of the record is older than ours,\n\t\t\t\t// we apply the patch to the downgraded version of the record\n\t\t\t\tconst downgraded = session\n\t\t\t\t\t? this.schema.migratePersistedRecord(doc.state, session.serializedSchema, 'down')\n\t\t\t\t\t: { type: 'success' as const, value: doc.state }\n\t\t\t\tif (downgraded.type === 'error') {\n\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\t\t}\n\n\t\t\t\tif (downgraded.value === doc.state) {\n\t\t\t\t\t// If the versions are compatible, apply the patch and propagate the patch op\n\t\t\t\t\tconst diff = doc.mergeDiff(patch, this.clock, legacyAppendMode)\n\t\t\t\t\tif (!diff.ok) {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t}\n\t\t\t\t\tif (diff.value) {\n\t\t\t\t\t\tthis.documents.set(id, diff.value[1])\n\t\t\t\t\t\tpropagateOp(changes, id, [RecordOpType.Patch, diff.value[0]])\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// need to apply the patch to the downgraded version and then upgrade it\n\n\t\t\t\t\t// apply the patch to the downgraded version\n\t\t\t\t\tconst patched = applyObjectDiff(downgraded.value, patch)\n\t\t\t\t\t// then upgrade the patched version and use that as the new state\n\t\t\t\t\tconst upgraded = session\n\t\t\t\t\t\t? this.schema.migratePersistedRecord(patched, session.serializedSchema, 'up')\n\t\t\t\t\t\t: { type: 'success' as const, value: patched }\n\t\t\t\t\t// If the client's version is too old, we'll hit an error\n\t\t\t\t\tif (upgraded.type === 'error') {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.CLIENT_TOO_OLD)\n\t\t\t\t\t}\n\t\t\t\t\t// replace the state with the upgraded version and propagate the patch op\n\t\t\t\t\tconst diff = doc.replaceState(upgraded.value, this.clock, legacyAppendMode)\n\t\t\t\t\tif (!diff.ok) {\n\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t}\n\t\t\t\t\tif (diff.value) {\n\t\t\t\t\t\tthis.documents.set(id, diff.value[1])\n\t\t\t\t\t\tpropagateOp(changes, id, [RecordOpType.Patch, diff.value[0]])\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn Result.ok(undefined)\n\t\t\t}\n\n\t\t\tconst { clientClock } = message\n\n\t\t\tif (this.presenceType && session?.presenceId && 'presence' in message && message.presence) {\n\t\t\t\tif (!session) throw new Error('session is required for presence pushes')\n\t\t\t\t// The push request was for the presence scope.\n\t\t\t\tconst id = session.presenceId\n\t\t\t\tconst [type, val] = message.presence\n\t\t\t\tconst { typeName } = this.presenceType\n\t\t\t\tswitch (type) {\n\t\t\t\t\tcase RecordOpType.Put: {\n\t\t\t\t\t\t// Try to put the document. If it fails, stop here.\n\t\t\t\t\t\tconst res = addDocument(presenceChanges, id, { ...val, id, typeName })\n\t\t\t\t\t\t// if res.ok is false here then we already called `fail` and we should stop immediately\n\t\t\t\t\t\tif (!res.ok) return\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase RecordOpType.Patch: {\n\t\t\t\t\t\t// Try to patch the document. If it fails, stop here.\n\t\t\t\t\t\tconst res = patchDocument(presenceChanges, id, {\n\t\t\t\t\t\t\t...val,\n\t\t\t\t\t\t\tid: [ValueOpType.Put, id],\n\t\t\t\t\t\t\ttypeName: [ValueOpType.Put, typeName],\n\t\t\t\t\t\t})\n\t\t\t\t\t\t// if res.ok is false here then we already called `fail` and we should stop immediately\n\t\t\t\t\t\tif (!res.ok) return\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (message.diff && !session?.isReadonly) {\n\t\t\t\t// The push request was for the document scope.\n\t\t\t\tfor (const [id, op] of objectMapEntriesIterable(message.diff!)) {\n\t\t\t\t\tswitch (op[0]) {\n\t\t\t\t\t\tcase RecordOpType.Put: {\n\t\t\t\t\t\t\t// Try to add the document.\n\t\t\t\t\t\t\t// If we're putting a record with a type that we don't recognize, fail\n\t\t\t\t\t\t\tif (!this.documentTypes.has(op[1].typeName)) {\n\t\t\t\t\t\t\t\treturn fail(TLSyncErrorCloseEventReason.INVALID_RECORD)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst res = addDocument(docChanges, id, op[1])\n\t\t\t\t\t\t\t// if res.ok is false here then we already called `fail` and we should stop immediately\n\t\t\t\t\t\t\tif (!res.ok) return\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase RecordOpType.Patch: {\n\t\t\t\t\t\t\t// Try to patch the document. If it fails, stop here.\n\t\t\t\t\t\t\tconst res = patchDocument(docChanges, id, op[1])\n\t\t\t\t\t\t\t// if res.ok is false here then we already called `fail` and we should stop immediately\n\t\t\t\t\t\t\tif (!res.ok) return\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase RecordOpType.Remove: {\n\t\t\t\t\t\t\tconst doc = this.getDocument(id)\n\t\t\t\t\t\t\tif (!doc) {\n\t\t\t\t\t\t\t\t// If the doc was already deleted, don't do anything, no need to propagate a delete op\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Delete the document and propagate the delete op\n\t\t\t\t\t\t\tthis.removeDocument(id, this.clock)\n\t\t\t\t\t\t\t// Schedule a pruneTombstones call to happen on the next call stack\n\t\t\t\t\t\t\tpropagateOp(docChanges, id, op)\n\t\t\t\t\t\t\tbreak\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// Let the client know what action to take based on the results of the push\n\t\t\tif (\n\t\t\t\t// if there was only a presence push, the client doesn't need to do anything aside from\n\t\t\t\t// shift the push request.\n\t\t\t\t!message.diff ||\n\t\t\t\tisEqual(docChanges.diff, message.diff)\n\t\t\t) {\n\t\t\t\t// COMMIT\n\t\t\t\t// Applying the client's changes had the exact same effect on the server as\n\t\t\t\t// they had on the client, so the client should keep the diff\n\t\t\t\tif (session) {\n\t\t\t\t\tthis.sendMessage(session.sessionId, {\n\t\t\t\t\t\ttype: 'push_result',\n\t\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\t\tclientClock,\n\t\t\t\t\t\taction: 'commit',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if (!docChanges.diff) {\n\t\t\t\t// DISCARD\n\t\t\t\t// Applying the client's changes had no effect, so the client should drop the diff\n\t\t\t\tif (session) {\n\t\t\t\t\tthis.sendMessage(session.sessionId, {\n\t\t\t\t\t\ttype: 'push_result',\n\t\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\t\tclientClock,\n\t\t\t\t\t\taction: 'discard',\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// REBASE\n\t\t\t\t// Applying the client's changes had a different non-empty effect on the server,\n\t\t\t\t// so the client should rebase with our gold-standard / authoritative diff.\n\t\t\t\t// First we need to migrate the diff to the client's version\n\t\t\t\tif (session) {\n\t\t\t\t\tconst migrateResult = this.migrateDiffForSession(\n\t\t\t\t\t\tsession.serializedSchema,\n\t\t\t\t\t\tdocChanges.diff\n\t\t\t\t\t)\n\t\t\t\t\tif (!migrateResult.ok) {\n\t\t\t\t\t\treturn fail(\n\t\t\t\t\t\t\tmigrateResult.error === MigrationFailureReason.TargetVersionTooNew\n\t\t\t\t\t\t\t\t? TLSyncErrorCloseEventReason.SERVER_TOO_OLD\n\t\t\t\t\t\t\t\t: TLSyncErrorCloseEventReason.CLIENT_TOO_OLD\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\t// If the migration worked, send the rebased diff to the client\n\t\t\t\t\tthis.sendMessage(session.sessionId, {\n\t\t\t\t\t\ttype: 'push_result',\n\t\t\t\t\t\tserverClock: this.clock,\n\t\t\t\t\t\tclientClock,\n\t\t\t\t\t\taction: { rebaseWithDiff: migrateResult.value },\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If there are merged changes, broadcast them to all other clients\n\t\t\tif (docChanges.diff || presenceChanges.diff) {\n\t\t\t\tthis.broadcastPatch({\n\t\t\t\t\tsourceSessionId: session?.sessionId,\n\t\t\t\t\tdiff: {\n\t\t\t\t\t\t...docChanges.diff,\n\t\t\t\t\t\t...presenceChanges.diff,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif (docChanges.diff) {\n\t\t\t\tthis.documentClock = this.clock\n\t\t\t}\n\t\t\tif (presenceChanges.diff) {\n\t\t\t\tdidPresenceChange = true\n\t\t\t}\n\n\t\t\treturn\n\t\t})\n\n\t\t// if it threw the changes will have been rolled back and the document clock will not have been incremented\n\t\tif (this.documentClock !== initialDocumentClock) {\n\t\t\tthis.onDataChange?.()\n\t\t}\n\n\t\tif (didPresenceChange) {\n\t\t\tthis.onPresenceChange?.()\n\t\t}\n\t}\n\n\t/**\n\t * Handle the event when a client disconnects. Cleans up the session and\n\t * removes any presence information.\n\t *\n\t * @param sessionId - The session that disconnected\n\t * @example\n\t * ```ts\n\t * websocket.onClose(() => {\n\t * room.handleClose(sessionId)\n\t * })\n\t * ```\n\t */\n\thandleClose(sessionId: string) {\n\t\tthis.cancelSession(sessionId)\n\t}\n\n\t/**\n\t * Apply changes to the room's store in a transactional way. Changes are\n\t * automatically synchronized to all connected clients.\n\t *\n\t * @param updater - Function that receives store methods to make changes\n\t * @returns Promise that resolves when the transaction is complete\n\t * @example\n\t * ```ts\n\t * // Add multiple shapes atomically\n\t * await room.updateStore((store) => {\n\t * store.put(createShape({ type: 'geo', x: 100, y: 100 }))\n\t * store.put(createShape({ type: 'text', x: 200, y: 200 }))\n\t * })\n\t *\n\t * // Async operations are supported\n\t * await room.updateStore(async (store) => {\n\t * const template = await loadTemplate()\n\t * template.shapes.forEach(shape => store.put(shape))\n\t * })\n\t * ```\n\t */\n\tasync updateStore(updater: (store: RoomStoreMethods<R>) => void | Promise<void>) {\n\t\tif (this._isClosed) {\n\t\t\tthrow new Error('Cannot update store on a closed room')\n\t\t}\n\t\tconst context = new StoreUpdateContext<R>(\n\t\t\tObject.fromEntries(this.getSnapshot().documents.map((d) => [d.state.id, d.state]))\n\t\t)\n\t\ttry {\n\t\t\tawait updater(context)\n\t\t} finally {\n\t\t\tcontext.close()\n\t\t}\n\n\t\tconst diff = context.toDiff()\n\t\tif (Object.keys(diff).length === 0) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.handlePushRequest(null, { type: 'push', diff, clientClock: 0 })\n\t}\n}\n\n/**\n * Interface for making transactional changes to room store data. Used within\n * updateStore transactions to modify documents atomically.\n *\n * @example\n * ```ts\n * await room.updateStore((store) => {\n * const shape = store.get('shape:123')\n * if (shape) {\n * store.put({ ...shape, x: shape.x + 10 })\n * }\n * store.delete('shape:456')\n * })\n * ```\n *\n * @public\n */\nexport interface RoomStoreMethods<R extends UnknownRecord = UnknownRecord> {\n\t/**\n\t * Add or update a record in the store.\n\t *\n\t * @param record - The record to store\n\t */\n\tput(record: R): void\n\t/**\n\t * Delete a record from the store.\n\t *\n\t * @param recordOrId - The record or record ID to delete\n\t */\n\tdelete(recordOrId: R | string): void\n\t/**\n\t * Get a record by its ID.\n\t *\n\t * @param id - The record ID\n\t * @returns The record or null if not found\n\t */\n\tget(id: string): R | null\n\t/**\n\t * Get all records in the store.\n\t *\n\t * @returns Array of all records\n\t */\n\tgetAll(): R[]\n}\n\nclass StoreUpdateContext<R extends UnknownRecord> implements RoomStoreMethods<R> {\n\tconstructor(private readonly snapshot: Record<string, UnknownRecord>) {}\n\tprivate readonly updates = {\n\t\tputs: {} as Record<string, UnknownRecord>,\n\t\tdeletes: new Set<string>(),\n\t}\n\tput(record: R): void {\n\t\tif (this._isClosed) throw new Error('StoreUpdateContext is closed')\n\t\tif (record.id in this.snapshot && isEqual(this.snapshot[record.id], record)) {\n\t\t\tdelete this.updates.puts[record.id]\n\t\t} else {\n\t\t\tthis.updates.puts[record.id] = structuredClone(record)\n\t\t}\n\t\tthis.updates.deletes.delete(record.id)\n\t}\n\tdelete(recordOrId: R | string): void {\n\t\tif (this._isClosed) throw new Error('StoreUpdateContext is closed')\n\t\tconst id = typeof recordOrId === 'string' ? recordOrId : recordOrId.id\n\t\tdelete this.updates.puts[id]\n\t\tif (this.snapshot[id]) {\n\t\t\tthis.updates.deletes.add(id)\n\t\t}\n\t}\n\tget(id: string): R | null {\n\t\tif (this._isClosed) throw new Error('StoreUpdateContext is closed')\n\t\tif (hasOwnProperty(this.updates.puts, id)) {\n\t\t\treturn structuredClone(this.updates.puts[id]) as R\n\t\t}\n\t\tif (this.updates.deletes.has(id)) {\n\t\t\treturn null\n\t\t}\n\t\treturn structuredClone(this.snapshot[id] ?? null) as R\n\t}\n\n\tgetAll(): R[] {\n\t\tif (this._isClosed) throw new Error('StoreUpdateContext is closed')\n\t\tconst result = Object.values(this.updates.puts)\n\t\tfor (const [id, record] of Object.entries(this.snapshot)) {\n\t\t\tif (!this.updates.deletes.has(id) && !hasOwnProperty(this.updates.puts, id)) {\n\t\t\t\tresult.push(record)\n\t\t\t}\n\t\t}\n\t\treturn structuredClone(result) as R[]\n\t}\n\n\ttoDiff(): NetworkDiff<any> {\n\t\tconst diff: NetworkDiff<R> = {}\n\t\tfor (const [id, record] of Object.entries(this.updates.puts)) {\n\t\t\tdiff[id] = [RecordOpType.Put, record as R]\n\t\t}\n\t\tfor (const id of this.updates.deletes) {\n\t\t\tdiff[id] = [RecordOpType.Remove]\n\t\t}\n\t\treturn diff\n\t}\n\n\tprivate _isClosed = false\n\tclose() {\n\t\tthis._isClosed = true\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -29,10 +29,10 @@ const ValueOpType = {
29
29
  Append: "append",
30
30
  Patch: "patch"
31
31
  };
32
- function diffRecord(prev, next) {
33
- return diffObject(prev, next, /* @__PURE__ */ new Set(["props"]));
32
+ function diffRecord(prev, next, legacyAppendMode = false) {
33
+ return diffObject(prev, next, /* @__PURE__ */ new Set(["props", "meta"]), legacyAppendMode);
34
34
  }
35
- function diffObject(prev, next, nestedKeys) {
35
+ function diffObject(prev, next, nestedKeys, legacyAppendMode) {
36
36
  if (prev === next) {
37
37
  return null;
38
38
  }
@@ -43,25 +43,17 @@ function diffObject(prev, next, nestedKeys) {
43
43
  result[key] = [ValueOpType.Delete];
44
44
  continue;
45
45
  }
46
- const prevVal = prev[key];
47
- const nextVal = next[key];
48
- if (!isEqual(prevVal, nextVal)) {
49
- if (nestedKeys?.has(key) && prevVal && nextVal) {
50
- const diff = diffObject(prevVal, nextVal);
51
- if (diff) {
52
- if (!result) result = {};
53
- result[key] = [ValueOpType.Patch, diff];
54
- }
55
- } else if (Array.isArray(nextVal) && Array.isArray(prevVal)) {
56
- const op = diffArray(prevVal, nextVal);
57
- if (op) {
58
- if (!result) result = {};
59
- result[key] = op;
60
- }
61
- } else {
46
+ const prevValue = prev[key];
47
+ const nextValue = next[key];
48
+ if (nestedKeys?.has(key) || Array.isArray(prevValue) && Array.isArray(nextValue) || typeof prevValue === "string" && typeof nextValue === "string") {
49
+ const diff = diffValue(prevValue, nextValue, legacyAppendMode);
50
+ if (diff) {
62
51
  if (!result) result = {};
63
- result[key] = [ValueOpType.Put, nextVal];
52
+ result[key] = diff;
64
53
  }
54
+ } else if (!isEqual(prevValue, nextValue)) {
55
+ if (!result) result = {};
56
+ result[key] = [ValueOpType.Put, nextValue];
65
57
  }
66
58
  }
67
59
  for (const key of Object.keys(next)) {
@@ -72,18 +64,24 @@ function diffObject(prev, next, nestedKeys) {
72
64
  }
73
65
  return result;
74
66
  }
75
- function diffValue(valueA, valueB) {
67
+ function diffValue(valueA, valueB, legacyAppendMode) {
76
68
  if (Object.is(valueA, valueB)) return null;
77
69
  if (Array.isArray(valueA) && Array.isArray(valueB)) {
78
- return diffArray(valueA, valueB);
70
+ return diffArray(valueA, valueB, legacyAppendMode);
71
+ } else if (typeof valueA === "string" && typeof valueB === "string") {
72
+ if (!legacyAppendMode && valueB.startsWith(valueA)) {
73
+ const appendedText = valueB.slice(valueA.length);
74
+ return [ValueOpType.Append, appendedText, valueA.length];
75
+ }
76
+ return [ValueOpType.Put, valueB];
79
77
  } else if (!valueA || !valueB || typeof valueA !== "object" || typeof valueB !== "object") {
80
78
  return isEqual(valueA, valueB) ? null : [ValueOpType.Put, valueB];
81
79
  } else {
82
- const diff = diffObject(valueA, valueB);
80
+ const diff = diffObject(valueA, valueB, void 0, legacyAppendMode);
83
81
  return diff ? [ValueOpType.Patch, diff] : null;
84
82
  }
85
83
  }
86
- function diffArray(prevArray, nextArray) {
84
+ function diffArray(prevArray, nextArray, legacyAppendMode) {
87
85
  if (Object.is(prevArray, nextArray)) return null;
88
86
  if (prevArray.length === nextArray.length) {
89
87
  const maxPatchIndexes = Math.max(prevArray.length / 5, 1);
@@ -106,7 +104,7 @@ function diffArray(prevArray, nextArray) {
106
104
  if (!prevItem || !nextItem) {
107
105
  diff[i] = [ValueOpType.Put, nextItem];
108
106
  } else if (typeof prevItem === "object" && typeof nextItem === "object") {
109
- const op = diffValue(prevItem, nextItem);
107
+ const op = diffValue(prevItem, nextItem, legacyAppendMode);
110
108
  if (op) {
111
109
  diff[i] = op;
112
110
  }
@@ -151,11 +149,13 @@ function applyObjectDiff(object, objectDiff) {
151
149
  break;
152
150
  }
153
151
  case ValueOpType.Append: {
154
- const values = op[1];
152
+ const value = op[1];
155
153
  const offset = op[2];
156
- const arr = object[key];
157
- if (Array.isArray(arr) && arr.length === offset) {
158
- set(key, [...arr, ...values]);
154
+ const currentValue = object[key];
155
+ if (Array.isArray(currentValue) && Array.isArray(value) && currentValue.length === offset) {
156
+ set(key, [...currentValue, ...value]);
157
+ } else if (typeof currentValue === "string" && typeof value === "string" && currentValue.length === offset) {
158
+ set(key, currentValue + value);
159
159
  }
160
160
  break;
161
161
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/diff.ts"],
4
- "sourcesContent": ["import { RecordsDiff, UnknownRecord } from '@tldraw/store'\nimport { isEqual, objectMapEntries, objectMapValues } from '@tldraw/utils'\n\n/**\n * Constants representing the types of operations that can be applied to records in network diffs.\n * These operations describe how a record has been modified during synchronization.\n *\n * @internal\n */\nexport const RecordOpType = {\n\tPut: 'put',\n\tPatch: 'patch',\n\tRemove: 'remove',\n} as const\n\n/**\n * Union type of all possible record operation types.\n *\n * @internal\n */\nexport type RecordOpType = (typeof RecordOpType)[keyof typeof RecordOpType]\n\n/**\n * Represents a single operation to be applied to a record during synchronization.\n *\n * @param R - The record type being operated on\n *\n * @internal\n */\nexport type RecordOp<R extends UnknownRecord> =\n\t| [typeof RecordOpType.Put, R]\n\t| [typeof RecordOpType.Patch, ObjectDiff]\n\t| [typeof RecordOpType.Remove]\n\n/**\n * A one-way (non-reversible) diff designed for small json footprint. These are mainly intended to\n * be sent over the wire. Either as push requests from the client to the server, or as patch\n * operations in the opposite direction.\n *\n * Each key in this object is the id of a record that has been added, updated, or removed.\n *\n * @internal\n */\nexport interface NetworkDiff<R extends UnknownRecord> {\n\t[id: string]: RecordOp<R>\n}\n\n/**\n * Converts a (reversible, verbose) RecordsDiff into a (non-reversible, concise) NetworkDiff\n * suitable for transmission over the network. This function optimizes the diff representation\n * for minimal bandwidth usage while maintaining all necessary change information.\n *\n * @param diff - The RecordsDiff containing added, updated, and removed records\n * @returns A compact NetworkDiff for network transmission, or null if no changes exist\n *\n * @example\n * ```ts\n * const recordsDiff = {\n * added: { 'shape:1': newShape },\n * updated: { 'shape:2': [oldShape, updatedShape] },\n * removed: { 'shape:3': removedShape }\n * }\n *\n * const networkDiff = getNetworkDiff(recordsDiff)\n * // Returns: {\n * // 'shape:1': ['put', newShape],\n * // 'shape:2': ['patch', { x: ['put', 100] }],\n * // 'shape:3': ['remove']\n * // }\n * ```\n *\n * @internal\n */\nexport function getNetworkDiff<R extends UnknownRecord>(\n\tdiff: RecordsDiff<R>\n): NetworkDiff<R> | null {\n\tlet res: NetworkDiff<R> | null = null\n\n\tfor (const [k, v] of objectMapEntries(diff.added)) {\n\t\tif (!res) res = {}\n\t\tres[k] = [RecordOpType.Put, v]\n\t}\n\n\tfor (const [from, to] of objectMapValues(diff.updated)) {\n\t\tconst diff = diffRecord(from, to)\n\t\tif (diff) {\n\t\t\tif (!res) res = {}\n\t\t\tres[to.id] = [RecordOpType.Patch, diff]\n\t\t}\n\t}\n\n\tfor (const removed of Object.keys(diff.removed)) {\n\t\tif (!res) res = {}\n\t\tres[removed] = [RecordOpType.Remove]\n\t}\n\n\treturn res\n}\n\n/**\n * Constants representing the types of operations that can be applied to individual values\n * within object diffs. These operations describe how object properties have changed.\n *\n * @internal\n */\nexport const ValueOpType = {\n\tPut: 'put',\n\tDelete: 'delete',\n\tAppend: 'append',\n\tPatch: 'patch',\n} as const\n/**\n * Union type of all possible value operation types.\n *\n * @internal\n */\nexport type ValueOpType = (typeof ValueOpType)[keyof typeof ValueOpType]\n\n/**\n * Operation that replaces a value entirely with a new value.\n *\n * @internal\n */\nexport type PutOp = [type: typeof ValueOpType.Put, value: unknown]\n/**\n * Operation that appends new values to the end of an array.\n *\n * @internal\n */\nexport type AppendOp = [type: typeof ValueOpType.Append, values: unknown[], offset: number]\n/**\n * Operation that applies a nested diff to an object or array.\n *\n * @internal\n */\nexport type PatchOp = [type: typeof ValueOpType.Patch, diff: ObjectDiff]\n/**\n * Operation that removes a property from an object.\n *\n * @internal\n */\nexport type DeleteOp = [type: typeof ValueOpType.Delete]\n\n/**\n * Union type representing any value operation that can be applied during diffing.\n *\n * @internal\n */\nexport type ValueOp = PutOp | AppendOp | PatchOp | DeleteOp\n\n/**\n * Represents the differences between two objects as a mapping of property names\n * to the operations needed to transform one object into another.\n *\n * @internal\n */\nexport interface ObjectDiff {\n\t[k: string]: ValueOp\n}\n\n/**\n * Computes the difference between two record objects, generating an ObjectDiff\n * that describes how to transform the previous record into the next record.\n * This function is optimized for tldraw records and treats 'props' as a nested object.\n *\n * @param prev - The previous version of the record\n * @param next - The next version of the record\n * @returns An ObjectDiff describing the changes, or null if no changes exist\n *\n * @example\n * ```ts\n * const oldShape = { id: 'shape:1', x: 100, y: 200, props: { color: 'red' } }\n * const newShape = { id: 'shape:1', x: 150, y: 200, props: { color: 'blue' } }\n *\n * const diff = diffRecord(oldShape, newShape)\n * // Returns: {\n * // x: ['put', 150],\n * // props: ['patch', { color: ['put', 'blue'] }]\n * // }\n * ```\n *\n * @internal\n */\nexport function diffRecord(prev: object, next: object): ObjectDiff | null {\n\treturn diffObject(prev, next, new Set(['props']))\n}\n\nfunction diffObject(prev: object, next: object, nestedKeys?: Set<string>): ObjectDiff | null {\n\tif (prev === next) {\n\t\treturn null\n\t}\n\tlet result: ObjectDiff | null = null\n\tfor (const key of Object.keys(prev)) {\n\t\t// if key is not in next then it was deleted\n\t\tif (!(key in next)) {\n\t\t\tif (!result) result = {}\n\t\t\tresult[key] = [ValueOpType.Delete]\n\t\t\tcontinue\n\t\t}\n\t\t// if key is in both places, then compare values\n\t\tconst prevVal = (prev as any)[key]\n\t\tconst nextVal = (next as any)[key]\n\t\tif (!isEqual(prevVal, nextVal)) {\n\t\t\tif (nestedKeys?.has(key) && prevVal && nextVal) {\n\t\t\t\tconst diff = diffObject(prevVal, nextVal)\n\t\t\t\tif (diff) {\n\t\t\t\t\tif (!result) result = {}\n\t\t\t\t\tresult[key] = [ValueOpType.Patch, diff]\n\t\t\t\t}\n\t\t\t} else if (Array.isArray(nextVal) && Array.isArray(prevVal)) {\n\t\t\t\tconst op = diffArray(prevVal, nextVal)\n\t\t\t\tif (op) {\n\t\t\t\t\tif (!result) result = {}\n\t\t\t\t\tresult[key] = op\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (!result) result = {}\n\t\t\t\tresult[key] = [ValueOpType.Put, nextVal]\n\t\t\t}\n\t\t}\n\t}\n\tfor (const key of Object.keys(next)) {\n\t\t// if key is in next but not in prev then it was added\n\t\tif (!(key in prev)) {\n\t\t\tif (!result) result = {}\n\t\t\tresult[key] = [ValueOpType.Put, (next as any)[key]]\n\t\t}\n\t}\n\treturn result\n}\n\nfunction diffValue(valueA: unknown, valueB: unknown): ValueOp | null {\n\tif (Object.is(valueA, valueB)) return null\n\tif (Array.isArray(valueA) && Array.isArray(valueB)) {\n\t\treturn diffArray(valueA, valueB)\n\t} else if (!valueA || !valueB || typeof valueA !== 'object' || typeof valueB !== 'object') {\n\t\treturn isEqual(valueA, valueB) ? null : [ValueOpType.Put, valueB]\n\t} else {\n\t\tconst diff = diffObject(valueA, valueB)\n\t\treturn diff ? [ValueOpType.Patch, diff] : null\n\t}\n}\n\nfunction diffArray(prevArray: unknown[], nextArray: unknown[]): PutOp | AppendOp | PatchOp | null {\n\tif (Object.is(prevArray, nextArray)) return null\n\t// if lengths are equal, check for patch operation\n\tif (prevArray.length === nextArray.length) {\n\t\t// bail out if more than len/5 items need patching\n\t\tconst maxPatchIndexes = Math.max(prevArray.length / 5, 1)\n\t\tconst toPatchIndexes = []\n\t\tfor (let i = 0; i < prevArray.length; i++) {\n\t\t\tif (!isEqual(prevArray[i], nextArray[i])) {\n\t\t\t\ttoPatchIndexes.push(i)\n\t\t\t\tif (toPatchIndexes.length > maxPatchIndexes) {\n\t\t\t\t\treturn [ValueOpType.Put, nextArray]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (toPatchIndexes.length === 0) {\n\t\t\t// same length and no items changed, so no diff\n\t\t\treturn null\n\t\t}\n\t\tconst diff: ObjectDiff = {}\n\t\tfor (const i of toPatchIndexes) {\n\t\t\tconst prevItem = prevArray[i]\n\t\t\tconst nextItem = nextArray[i]\n\t\t\tif (!prevItem || !nextItem) {\n\t\t\t\tdiff[i] = [ValueOpType.Put, nextItem]\n\t\t\t} else if (typeof prevItem === 'object' && typeof nextItem === 'object') {\n\t\t\t\tconst op = diffValue(prevItem, nextItem)\n\t\t\t\tif (op) {\n\t\t\t\t\tdiff[i] = op\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff[i] = [ValueOpType.Put, nextItem]\n\t\t\t}\n\t\t}\n\t\treturn [ValueOpType.Patch, diff]\n\t}\n\n\t// if lengths are not equal, check for append operation, and bail out\n\t// to replace whole array if any shared elems changed\n\tfor (let i = 0; i < prevArray.length; i++) {\n\t\tif (!isEqual(prevArray[i], nextArray[i])) {\n\t\t\treturn [ValueOpType.Put, nextArray]\n\t\t}\n\t}\n\n\treturn [ValueOpType.Append, nextArray.slice(prevArray.length), prevArray.length]\n}\n\n/**\n * Applies an ObjectDiff to an object, returning a new object with the changes applied.\n * This function handles all value operation types and creates a shallow copy when modifications\n * are needed. If no changes are required, the original object is returned.\n *\n * @param object - The object to apply the diff to\n * @param objectDiff - The ObjectDiff containing the operations to apply\n * @returns A new object with the diff applied, or the original object if no changes were needed\n *\n * @example\n * ```ts\n * const original = { x: 100, y: 200, props: { color: 'red' } }\n * const diff = {\n * x: ['put', 150],\n * props: ['patch', { color: ['put', 'blue'] }]\n * }\n *\n * const updated = applyObjectDiff(original, diff)\n * // Returns: { x: 150, y: 200, props: { color: 'blue' } }\n * ```\n *\n * @internal\n */\nexport function applyObjectDiff<T extends object>(object: T, objectDiff: ObjectDiff): T {\n\t// don't patch nulls\n\tif (!object || typeof object !== 'object') return object\n\tconst isArray = Array.isArray(object)\n\tlet newObject: any | undefined = undefined\n\tconst set = (k: any, v: any) => {\n\t\tif (!newObject) {\n\t\t\tif (isArray) {\n\t\t\t\tnewObject = [...object]\n\t\t\t} else {\n\t\t\t\tnewObject = { ...object }\n\t\t\t}\n\t\t}\n\t\tif (isArray) {\n\t\t\tnewObject[Number(k)] = v\n\t\t} else {\n\t\t\tnewObject[k] = v\n\t\t}\n\t}\n\tfor (const [key, op] of Object.entries(objectDiff)) {\n\t\tswitch (op[0]) {\n\t\t\tcase ValueOpType.Put: {\n\t\t\t\tconst value = op[1]\n\t\t\t\tif (!isEqual(object[key as keyof T], value)) {\n\t\t\t\t\tset(key, value)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase ValueOpType.Append: {\n\t\t\t\tconst values = op[1]\n\t\t\t\tconst offset = op[2]\n\t\t\t\tconst arr = object[key as keyof T]\n\t\t\t\tif (Array.isArray(arr) && arr.length === offset) {\n\t\t\t\t\tset(key, [...arr, ...values])\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase ValueOpType.Patch: {\n\t\t\t\tif (object[key as keyof T] && typeof object[key as keyof T] === 'object') {\n\t\t\t\t\tconst diff = op[1]\n\t\t\t\t\tconst patched = applyObjectDiff(object[key as keyof T] as object, diff)\n\t\t\t\t\tif (patched !== object[key as keyof T]) {\n\t\t\t\t\t\tset(key, patched)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase ValueOpType.Delete: {\n\t\t\t\tif (key in object) {\n\t\t\t\t\tif (!newObject) {\n\t\t\t\t\t\tif (isArray) {\n\t\t\t\t\t\t\tconsole.error(\"Can't delete array item yet (this should never happen)\")\n\t\t\t\t\t\t\tnewObject = [...object]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnewObject = { ...object }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdelete newObject[key]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newObject ?? object\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { RecordsDiff, UnknownRecord } from '@tldraw/store'\nimport { isEqual, objectMapEntries, objectMapValues } from '@tldraw/utils'\n\n/**\n * Constants representing the types of operations that can be applied to records in network diffs.\n * These operations describe how a record has been modified during synchronization.\n *\n * @internal\n */\nexport const RecordOpType = {\n\tPut: 'put',\n\tPatch: 'patch',\n\tRemove: 'remove',\n} as const\n\n/**\n * Union type of all possible record operation types.\n *\n * @internal\n */\nexport type RecordOpType = (typeof RecordOpType)[keyof typeof RecordOpType]\n\n/**\n * Represents a single operation to be applied to a record during synchronization.\n *\n * @param R - The record type being operated on\n *\n * @internal\n */\nexport type RecordOp<R extends UnknownRecord> =\n\t| [typeof RecordOpType.Put, R]\n\t| [typeof RecordOpType.Patch, ObjectDiff]\n\t| [typeof RecordOpType.Remove]\n\n/**\n * A one-way (non-reversible) diff designed for small json footprint. These are mainly intended to\n * be sent over the wire. Either as push requests from the client to the server, or as patch\n * operations in the opposite direction.\n *\n * Each key in this object is the id of a record that has been added, updated, or removed.\n *\n * @internal\n */\nexport interface NetworkDiff<R extends UnknownRecord> {\n\t[id: string]: RecordOp<R>\n}\n\n/**\n * Converts a (reversible, verbose) RecordsDiff into a (non-reversible, concise) NetworkDiff\n * suitable for transmission over the network. This function optimizes the diff representation\n * for minimal bandwidth usage while maintaining all necessary change information.\n *\n * @param diff - The RecordsDiff containing added, updated, and removed records\n * @returns A compact NetworkDiff for network transmission, or null if no changes exist\n *\n * @example\n * ```ts\n * const recordsDiff = {\n * added: { 'shape:1': newShape },\n * updated: { 'shape:2': [oldShape, updatedShape] },\n * removed: { 'shape:3': removedShape }\n * }\n *\n * const networkDiff = getNetworkDiff(recordsDiff)\n * // Returns: {\n * // 'shape:1': ['put', newShape],\n * // 'shape:2': ['patch', { x: ['put', 100] }],\n * // 'shape:3': ['remove']\n * // }\n * ```\n *\n * @internal\n */\nexport function getNetworkDiff<R extends UnknownRecord>(\n\tdiff: RecordsDiff<R>\n): NetworkDiff<R> | null {\n\tlet res: NetworkDiff<R> | null = null\n\n\tfor (const [k, v] of objectMapEntries(diff.added)) {\n\t\tif (!res) res = {}\n\t\tres[k] = [RecordOpType.Put, v]\n\t}\n\n\tfor (const [from, to] of objectMapValues(diff.updated)) {\n\t\tconst diff = diffRecord(from, to)\n\t\tif (diff) {\n\t\t\tif (!res) res = {}\n\t\t\tres[to.id] = [RecordOpType.Patch, diff]\n\t\t}\n\t}\n\n\tfor (const removed of Object.keys(diff.removed)) {\n\t\tif (!res) res = {}\n\t\tres[removed] = [RecordOpType.Remove]\n\t}\n\n\treturn res\n}\n\n/**\n * Constants representing the types of operations that can be applied to individual values\n * within object diffs. These operations describe how object properties have changed.\n *\n * @internal\n */\nexport const ValueOpType = {\n\tPut: 'put',\n\tDelete: 'delete',\n\tAppend: 'append',\n\tPatch: 'patch',\n} as const\n/**\n * Union type of all possible value operation types.\n *\n * @internal\n */\nexport type ValueOpType = (typeof ValueOpType)[keyof typeof ValueOpType]\n\n/**\n * Operation that replaces a value entirely with a new value.\n *\n * @internal\n */\nexport type PutOp = [type: typeof ValueOpType.Put, value: unknown]\n/**\n * Operation that appends new values to the end of an array or string.\n *\n * @internal\n */\nexport type AppendOp = [type: typeof ValueOpType.Append, value: unknown[] | string, offset: number]\n/**\n * Operation that applies a nested diff to an object or array.\n *\n * @internal\n */\nexport type PatchOp = [type: typeof ValueOpType.Patch, diff: ObjectDiff]\n/**\n * Operation that removes a property from an object.\n *\n * @internal\n */\nexport type DeleteOp = [type: typeof ValueOpType.Delete]\n\n/**\n * Union type representing any value operation that can be applied during diffing.\n *\n * @internal\n */\nexport type ValueOp = PutOp | AppendOp | PatchOp | DeleteOp\n\n/**\n * Represents the differences between two objects as a mapping of property names\n * to the operations needed to transform one object into another.\n *\n * @internal\n */\nexport interface ObjectDiff {\n\t[k: string]: ValueOp\n}\n\n/**\n * Computes the difference between two record objects, generating an ObjectDiff\n * that describes how to transform the previous record into the next record.\n * This function is optimized for tldraw records and treats 'props' as a nested object.\n *\n * @param prev - The previous version of the record\n * @param next - The next version of the record\n * @param legacyAppendMode - If true, string append operations will be converted to Put operations\n * @returns An ObjectDiff describing the changes, or null if no changes exist\n *\n * @example\n * ```ts\n * const oldShape = { id: 'shape:1', x: 100, y: 200, props: { color: 'red' } }\n * const newShape = { id: 'shape:1', x: 150, y: 200, props: { color: 'blue' } }\n *\n * const diff = diffRecord(oldShape, newShape)\n * // Returns: {\n * // x: ['put', 150],\n * // props: ['patch', { color: ['put', 'blue'] }]\n * // }\n * ```\n *\n * @internal\n */\nexport function diffRecord(\n\tprev: object,\n\tnext: object,\n\tlegacyAppendMode = false\n): ObjectDiff | null {\n\treturn diffObject(prev, next, new Set(['props', 'meta']), legacyAppendMode)\n}\n\nfunction diffObject(\n\tprev: object,\n\tnext: object,\n\tnestedKeys: Set<string> | undefined,\n\tlegacyAppendMode: boolean\n): ObjectDiff | null {\n\tif (prev === next) {\n\t\treturn null\n\t}\n\tlet result: ObjectDiff | null = null\n\tfor (const key of Object.keys(prev)) {\n\t\t// if key is not in next then it was deleted\n\t\tif (!(key in next)) {\n\t\t\tif (!result) result = {}\n\t\t\tresult[key] = [ValueOpType.Delete]\n\t\t\tcontinue\n\t\t}\n\t\tconst prevValue = (prev as any)[key]\n\t\tconst nextValue = (next as any)[key]\n\t\tif (\n\t\t\tnestedKeys?.has(key) ||\n\t\t\t(Array.isArray(prevValue) && Array.isArray(nextValue)) ||\n\t\t\t(typeof prevValue === 'string' && typeof nextValue === 'string')\n\t\t) {\n\t\t\t// if key is in both places, then compare values\n\t\t\tconst diff = diffValue(prevValue, nextValue, legacyAppendMode)\n\t\t\tif (diff) {\n\t\t\t\tif (!result) result = {}\n\t\t\t\tresult[key] = diff\n\t\t\t}\n\t\t} else if (!isEqual(prevValue, nextValue)) {\n\t\t\tif (!result) result = {}\n\t\t\tresult[key] = [ValueOpType.Put, nextValue]\n\t\t}\n\t}\n\tfor (const key of Object.keys(next)) {\n\t\t// if key is in next but not in prev then it was added\n\t\tif (!(key in prev)) {\n\t\t\tif (!result) result = {}\n\t\t\tresult[key] = [ValueOpType.Put, (next as any)[key]]\n\t\t}\n\t}\n\treturn result\n}\n\nfunction diffValue(valueA: unknown, valueB: unknown, legacyAppendMode: boolean): ValueOp | null {\n\tif (Object.is(valueA, valueB)) return null\n\tif (Array.isArray(valueA) && Array.isArray(valueB)) {\n\t\treturn diffArray(valueA, valueB, legacyAppendMode)\n\t} else if (typeof valueA === 'string' && typeof valueB === 'string') {\n\t\tif (!legacyAppendMode && valueB.startsWith(valueA)) {\n\t\t\tconst appendedText = valueB.slice(valueA.length)\n\t\t\treturn [ValueOpType.Append, appendedText, valueA.length]\n\t\t}\n\t\treturn [ValueOpType.Put, valueB]\n\t} else if (!valueA || !valueB || typeof valueA !== 'object' || typeof valueB !== 'object') {\n\t\treturn isEqual(valueA, valueB) ? null : [ValueOpType.Put, valueB]\n\t} else {\n\t\tconst diff = diffObject(valueA, valueB, undefined, legacyAppendMode)\n\t\treturn diff ? [ValueOpType.Patch, diff] : null\n\t}\n}\n\nfunction diffArray(\n\tprevArray: unknown[],\n\tnextArray: unknown[],\n\tlegacyAppendMode: boolean\n): PutOp | AppendOp | PatchOp | null {\n\tif (Object.is(prevArray, nextArray)) return null\n\t// if lengths are equal, check for patch operation\n\tif (prevArray.length === nextArray.length) {\n\t\t// bail out if more than len/5 items need patching\n\t\tconst maxPatchIndexes = Math.max(prevArray.length / 5, 1)\n\t\tconst toPatchIndexes = []\n\t\tfor (let i = 0; i < prevArray.length; i++) {\n\t\t\tif (!isEqual(prevArray[i], nextArray[i])) {\n\t\t\t\ttoPatchIndexes.push(i)\n\t\t\t\tif (toPatchIndexes.length > maxPatchIndexes) {\n\t\t\t\t\treturn [ValueOpType.Put, nextArray]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (toPatchIndexes.length === 0) {\n\t\t\t// same length and no items changed, so no diff\n\t\t\treturn null\n\t\t}\n\t\tconst diff: ObjectDiff = {}\n\t\tfor (const i of toPatchIndexes) {\n\t\t\tconst prevItem = prevArray[i]\n\t\t\tconst nextItem = nextArray[i]\n\t\t\tif (!prevItem || !nextItem) {\n\t\t\t\tdiff[i] = [ValueOpType.Put, nextItem]\n\t\t\t} else if (typeof prevItem === 'object' && typeof nextItem === 'object') {\n\t\t\t\tconst op = diffValue(prevItem, nextItem, legacyAppendMode)\n\t\t\t\tif (op) {\n\t\t\t\t\tdiff[i] = op\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff[i] = [ValueOpType.Put, nextItem]\n\t\t\t}\n\t\t}\n\t\treturn [ValueOpType.Patch, diff]\n\t}\n\n\t// if lengths are not equal, check for append operation, and bail out\n\t// to replace whole array if any shared elems changed\n\tfor (let i = 0; i < prevArray.length; i++) {\n\t\tif (!isEqual(prevArray[i], nextArray[i])) {\n\t\t\treturn [ValueOpType.Put, nextArray]\n\t\t}\n\t}\n\n\treturn [ValueOpType.Append, nextArray.slice(prevArray.length), prevArray.length]\n}\n\n/**\n * Applies an ObjectDiff to an object, returning a new object with the changes applied.\n * This function handles all value operation types and creates a shallow copy when modifications\n * are needed. If no changes are required, the original object is returned.\n *\n * @param object - The object to apply the diff to\n * @param objectDiff - The ObjectDiff containing the operations to apply\n * @returns A new object with the diff applied, or the original object if no changes were needed\n *\n * @example\n * ```ts\n * const original = { x: 100, y: 200, props: { color: 'red' } }\n * const diff = {\n * x: ['put', 150],\n * props: ['patch', { color: ['put', 'blue'] }]\n * }\n *\n * const updated = applyObjectDiff(original, diff)\n * // Returns: { x: 150, y: 200, props: { color: 'blue' } }\n * ```\n *\n * @internal\n */\nexport function applyObjectDiff<T extends object>(object: T, objectDiff: ObjectDiff): T {\n\t// don't patch nulls\n\tif (!object || typeof object !== 'object') return object\n\tconst isArray = Array.isArray(object)\n\tlet newObject: any | undefined = undefined\n\tconst set = (k: any, v: any) => {\n\t\tif (!newObject) {\n\t\t\tif (isArray) {\n\t\t\t\tnewObject = [...object]\n\t\t\t} else {\n\t\t\t\tnewObject = { ...object }\n\t\t\t}\n\t\t}\n\t\tif (isArray) {\n\t\t\tnewObject[Number(k)] = v\n\t\t} else {\n\t\t\tnewObject[k] = v\n\t\t}\n\t}\n\tfor (const [key, op] of Object.entries(objectDiff)) {\n\t\tswitch (op[0]) {\n\t\t\tcase ValueOpType.Put: {\n\t\t\t\tconst value = op[1]\n\t\t\t\tif (!isEqual(object[key as keyof T], value)) {\n\t\t\t\t\tset(key, value)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase ValueOpType.Append: {\n\t\t\t\tconst value = op[1]\n\t\t\t\tconst offset = op[2]\n\t\t\t\tconst currentValue = object[key as keyof T]\n\t\t\t\tif (Array.isArray(currentValue) && Array.isArray(value) && currentValue.length === offset) {\n\t\t\t\t\tset(key, [...currentValue, ...value])\n\t\t\t\t} else if (\n\t\t\t\t\ttypeof currentValue === 'string' &&\n\t\t\t\t\ttypeof value === 'string' &&\n\t\t\t\t\tcurrentValue.length === offset\n\t\t\t\t) {\n\t\t\t\t\tset(key, currentValue + value)\n\t\t\t\t}\n\t\t\t\t// If validation fails (type mismatch or length mismatch), silently ignore\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase ValueOpType.Patch: {\n\t\t\t\tif (object[key as keyof T] && typeof object[key as keyof T] === 'object') {\n\t\t\t\t\tconst diff = op[1]\n\t\t\t\t\tconst patched = applyObjectDiff(object[key as keyof T] as object, diff)\n\t\t\t\t\tif (patched !== object[key as keyof T]) {\n\t\t\t\t\t\tset(key, patched)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase ValueOpType.Delete: {\n\t\t\t\tif (key in object) {\n\t\t\t\t\tif (!newObject) {\n\t\t\t\t\t\tif (isArray) {\n\t\t\t\t\t\t\tconsole.error(\"Can't delete array item yet (this should never happen)\")\n\t\t\t\t\t\t\tnewObject = [...object]\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tnewObject = { ...object }\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdelete newObject[key]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newObject ?? object\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["diff"]
7
7
  }
@@ -1,4 +1,4 @@
1
- const TLSYNC_PROTOCOL_VERSION = 7;
1
+ const TLSYNC_PROTOCOL_VERSION = 8;
2
2
  function getTlsyncProtocolVersion() {
3
3
  return TLSYNC_PROTOCOL_VERSION;
4
4
  }