{"version":3,"file":"index.cjs","names":["records: Uint8Array[]","EloRecordKind","VersionVector","requester: VersionVector | undefined","key: number | bigint | `${number}`","kept: EloDeltaSpanIndexEntry[]","CrdtType","response: JoinResponseOk","MessageType","broadcastUpdates: Uint8Array[]","defaultDescriptors: ServerAdaptorDescriptor[]","LoroServerAdaptor","LoroEphemeralServerAdaptor","LoroPersistentStoreServerAdaptor","FlockServerAdaptor","options: { port: number; host?: string }","WebSocketServer","client: ClientConnection","ws","WebSocket","MessageType","UpdateStatusCode","permission: Permission","JoinErrorCode","response: JoinResponseOk","MAX_MESSAGE_SIZE","outgoing: DocUpdate","fragMsg: DocUpdateFragment","error: JoinError","ack: Ack","CrdtType"],"sources":["../../src/server/elo-doc.ts","../../src/server/elo-server-adaptor.ts","../../src/server/crdt-doc.ts","../../src/server/simple-server.ts"],"sourcesContent":["import { VersionVector } from \"loro-crdt\";\nimport {\n  bytesToHex,\n  decodeEloContainer,\n  encodeEloContainer,\n  parseEloRecordHeader,\n  EloRecordKind,\n} from \"loro-protocol\";\n\nexport interface EloDeltaSpanIndexEntry {\n  start: number; // inclusive\n  end: number; // exclusive\n  keyId: string;\n  record: Uint8Array; // exact record bytes; server must not mutate\n}\n\n// An in-memory, server-side index for %ELO encrypted updates.\n// The server never decrypts; it only validates headers and indexes\n// delta-span coverage for backfill selection.\nexport class EloDoc {\n  // Keyed by decoded peerId string; values kept sorted by start (ascending)\n  private spansByPeer: Map<string, EloDeltaSpanIndexEntry[]> = new Map();\n  private verbose: boolean;\n\n  constructor(opts?: { verbose?: boolean }) {\n    const envVerbose =\n      typeof process !== \"undefined\" &&\n      (process as unknown as { env?: Record<string, string | undefined> }).env &&\n      (((process as unknown as { env?: Record<string, string | undefined> }).env!.ELO_LOG === \"1\") ||\n        ((process as unknown as { env?: Record<string, string | undefined> }).env!.ELO_LOG === \"true\"));\n    this.verbose = !!(opts?.verbose ?? envVerbose);\n  }\n\n  indexBatch(batch: Uint8Array): { ok: true } | { ok: false; error: string } {\n    let records: Uint8Array[];\n    try {\n      records = decodeEloContainer(batch);\n    } catch (e) {\n      return { ok: false, error: e instanceof Error ? e.message : String(e) };\n    }\n\n    for (const rec of records) {\n      let parsed;\n      try {\n        parsed = parseEloRecordHeader(rec);\n      } catch (e) {\n        return { ok: false, error: e instanceof Error ? e.message : String(e) };\n      }\n\n      if (parsed.kind === EloRecordKind.DeltaSpan) {\n        const hdr = parsed.header as import(\"loro-protocol\").EloDeltaHeader;\n        // Validate span and IV per spec\n        const { start, end, iv } = hdr;\n        if (!(end > start)) {\n          return { ok: false, error: \"Invalid ELO delta span: end must be > start\" };\n        }\n        if (iv.length !== 12) {\n          return { ok: false, error: \"Invalid ELO delta span: IV must be 12 bytes\" };\n        }\n        if (hdr.peerId.length > 64) {\n          return { ok: false, error: \"Invalid ELO delta span: peerId must be ≤ 64 bytes\" };\n        }\n        if (parsed.keyId && new TextEncoder().encode(parsed.keyId).length > 64) {\n          return { ok: false, error: \"Invalid ELO delta span: keyId must be ≤ 64 bytes\" };\n        }\n\n        const peerKey = this.peerKeyFromBytes(hdr.peerId);\n        const list = this.spansByPeer.get(peerKey) ?? [];\n\n        // Fast path: append at end if ordered and no need to prune\n        if (list.length === 0 || list[list.length - 1]!.start <= start) {\n          // Remove any fully covered spans with start >= start\n          const kept = this.filterCoveredFromIndex(list, start, end);\n          kept.push({ start, end, keyId: parsed.keyId, record: rec });\n          this.spansByPeer.set(peerKey, kept);\n          if (this.verbose) {\n            console.info(\"[ELO] indexed-delta\", {\n              peerId: peerKey,\n              start,\n              end,\n              keyId: parsed.keyId,\n            });\n          }\n          continue;\n        }\n\n        // General path: binary insert by start; prune covered entries [start, end]\n        const left = this.lowerBound(list, start);\n        const before = left > 0 ? list.slice(0, left) : [];\n        const after = this.filterCoveredFromIndex(list.slice(left), start, end);\n        const next = before;\n        next.push({ start, end, keyId: parsed.keyId, record: rec });\n        for (const e of after) next.push(e);\n        this.spansByPeer.set(peerKey, next);\n        if (this.verbose) {\n          console.info(\"[ELO] indexed-delta\", {\n            peerId: peerKey,\n            start,\n            end,\n            keyId: parsed.keyId,\n          });\n        }\n      } else if (parsed.kind === EloRecordKind.Snapshot) {\n        // Snapshot: validate IV length; do not store; server does not need to backfill snapshots\n        if (parsed.iv.length !== 12) {\n          return { ok: false, error: \"Invalid ELO snapshot: IV must be 12 bytes\" };\n        }\n        if (parsed.keyId && new TextEncoder().encode(parsed.keyId).length > 64) {\n          return { ok: false, error: \"Invalid ELO snapshot: keyId must be ≤ 64 bytes\" };\n        }\n        // no-op\n        if (this.verbose) {\n          console.info(\"[ELO] received-snapshot\", { keyId: parsed.keyId });\n        }\n      }\n    }\n\n    return { ok: true };\n  }\n\n  getVersionBytes(): Uint8Array {\n    // Derive VV as max end per peer from indexed spans\n    const vvMap = new Map<`${number}`, number>();\n    for (const [peer, spans] of this.spansByPeer.entries()) {\n      let maxEnd = 0;\n      for (const s of spans) if (s.end > maxEnd) maxEnd = s.end;\n      if (maxEnd > 0 && /^\\d+$/.test(peer)) vvMap.set(peer as `${number}`, maxEnd);\n    }\n    const vv = new VersionVector(vvMap);\n    return vv.encode();\n  }\n\n  selectBackfillBatches(requesterVersion: Uint8Array): Uint8Array[] {\n    // Decode requester VV; unknown/invalid => treat as empty\n    let requester: VersionVector | undefined;\n    try {\n      if (requesterVersion && requesterVersion.length > 0) {\n        requester = VersionVector.decode(requesterVersion);\n      }\n    } catch {\n      requester = undefined;\n    }\n\n    // Build a flat list of records to send where end > known counter for that peer\n    const records: Uint8Array[] = [];\n    for (const [peer, spans] of this.spansByPeer.entries()) {\n      const key: number | bigint | `${number}` =\n        /^\\d+$/.test(peer) ? (peer as `${number}`) : 0;\n      const known = requester ? Number(requester.get(key) ?? 0) : 0;\n      for (const s of spans) {\n        if (s.end > known) records.push(s.record);\n      }\n    }\n    if (records.length === 0) return [];\n    // Package into a single batch (protocol calls this a container)\n    if (this.verbose) {\n      const vvEntries = requester ? [...requester.toJSON().entries()].length : 0;\n      console.info(\"[ELO] select-backfill\", {\n        requesterVvEntries: vvEntries,\n        recordCount: records.length,\n      });\n    }\n    return [encodeEloContainer(records)];\n  }\n\n  reset(): void {\n    this.spansByPeer.clear();\n  }\n\n  loadFromEncodedState(\n    data: Uint8Array\n  ): { ok: true } | { ok: false; error: string } {\n    this.reset();\n    if (!data.length) {\n      return { ok: true };\n    }\n    return this.indexBatch(data);\n  }\n\n  exportIndexedRecords(): Uint8Array {\n    const records: Uint8Array[] = [];\n    for (const spans of this.spansByPeer.values()) {\n      for (const entry of spans) {\n        records.push(entry.record);\n      }\n    }\n    if (records.length === 0) {\n      return new Uint8Array();\n    }\n    return encodeEloContainer(records);\n  }\n\n  private peerKeyFromBytes(peerId: Uint8Array): string {\n    try {\n      // Prefer UTF-8 for Loro peer IDs (numeric strings)\n      return new TextDecoder().decode(peerId);\n    } catch {\n      // Fallback to hex if not valid UTF-8\n      return bytesToHex(peerId);\n    }\n  }\n\n  private lowerBound(list: EloDeltaSpanIndexEntry[], start: number): number {\n    // First index with e.start >= start\n    let lo = 0,\n      hi = list.length;\n    while (lo < hi) {\n      const mid = (lo + hi) >>> 1;\n      if (list[mid]!.start < start) lo = mid + 1;\n      else hi = mid;\n    }\n    return lo;\n  }\n\n  private filterCoveredFromIndex(\n    list: EloDeltaSpanIndexEntry[],\n    start: number,\n    end: number\n  ): EloDeltaSpanIndexEntry[] {\n    // Keep entries not fully covered by [start, end]\n    const kept: EloDeltaSpanIndexEntry[] = [];\n    for (const e of list) {\n      const covered = e.start >= start && e.end <= end;\n      if (!covered) kept.push(e);\n    }\n    return kept;\n  }\n}\n","import type { CrdtServerAdaptor } from \"loro-adaptors\";\nimport {\n  CrdtType,\n  JoinResponseOk,\n  MessageType,\n} from \"loro-protocol\";\nimport { EloDoc } from \"./elo-doc\";\n\n\nexport class EloServerAdaptor implements CrdtServerAdaptor {\n  readonly crdtType = CrdtType.Elo;\n\n  createEmpty(): Uint8Array {\n    return new Uint8Array();\n  }\n\n  handleJoinRequest(\n    documentData: Uint8Array,\n    clientVersion: Uint8Array,\n  ): {\n    response: JoinResponseOk;\n    updates?: Uint8Array[];\n  } {\n    const doc = new EloDoc();\n    const load = doc.loadFromEncodedState(documentData);\n    if (!load.ok) {\n      console.warn(\"[ELO] failed to load indexed state:\", load.error);\n    }\n\n    const response: JoinResponseOk = {\n      type: MessageType.JoinResponseOk,\n      crdt: this.crdtType,\n      roomId: \"\",\n      permission: \"write\",\n      version: doc.getVersionBytes(),\n    };\n\n    const updates = doc.selectBackfillBatches(clientVersion);\n\n    return {\n      response,\n      updates: updates.length ? updates : undefined,\n    };\n  }\n\n  applyUpdates(\n    documentData: Uint8Array,\n    updates: Uint8Array[],\n  ): Uint8Array {\n    const doc = new EloDoc();\n    const load = doc.loadFromEncodedState(documentData);\n    if (!load.ok) {\n      throw new Error(load.error, { cause: load.error });\n    }\n\n    const broadcastUpdates: Uint8Array[] = [];\n\n    for (const update of updates) {\n      if (!update.length) continue;\n      const res = doc.indexBatch(update);\n      if (!res.ok) {\n        throw new Error(res.error, { cause: res.error });\n      }\n      broadcastUpdates.push(update);\n    }\n\n    const newDocumentData = doc.exportIndexedRecords();\n    return newDocumentData;\n  }\n\n  getVersion(documentData: Uint8Array): Uint8Array {\n    const doc = new EloDoc();\n    const load = doc.loadFromEncodedState(documentData);\n    if (!load.ok) {\n      console.warn(\"[ELO] failed to load indexed state:\", load.error);\n      return new Uint8Array();\n    }\n    return doc.getVersionBytes();\n  }\n\n  merge(documents: Uint8Array[]): Uint8Array {\n    const doc = new EloDoc();\n    for (const data of documents) {\n      if (!data.length) continue;\n      const res = doc.indexBatch(data);\n      if (!res.ok) {\n        console.warn(\"[ELO] failed to merge indexed state:\", res.error);\n      }\n    }\n    return doc.exportIndexedRecords();\n  }\n}\n","import type { CrdtServerAdaptor } from \"loro-adaptors\";\nimport { LoroServerAdaptor, LoroEphemeralServerAdaptor, LoroPersistentStoreServerAdaptor } from \"loro-adaptors/loro\";\nimport { FlockServerAdaptor } from \"loro-adaptors/flock\";\nimport { CrdtType } from \"loro-protocol\";\nimport { EloServerAdaptor } from \"./elo-server-adaptor\";\n\nexport interface ServerAdaptorDescriptor {\n  adaptor: CrdtServerAdaptor;\n  shouldPersist: boolean;\n  allowBackfillWhenNoOtherClients: boolean;\n}\n\nconst descriptors = new Map<CrdtType, ServerAdaptorDescriptor>();\n\nfunction registerDescriptor(descriptor: ServerAdaptorDescriptor): void {\n  descriptors.set(descriptor.adaptor.crdtType, descriptor);\n}\n\nconst defaultDescriptors: ServerAdaptorDescriptor[] = [\n  {\n    adaptor: new LoroServerAdaptor(),\n    shouldPersist: true,\n    allowBackfillWhenNoOtherClients: true,\n  },\n  {\n    adaptor: new LoroEphemeralServerAdaptor(),\n    shouldPersist: false,\n    allowBackfillWhenNoOtherClients: false,\n  },\n  {\n    adaptor: new LoroPersistentStoreServerAdaptor(),\n    shouldPersist: true,\n    allowBackfillWhenNoOtherClients: true,\n  },\n  {\n    adaptor: new FlockServerAdaptor(),\n    shouldPersist: true,\n    allowBackfillWhenNoOtherClients: false,\n  },\n  {\n    adaptor: new EloServerAdaptor(),\n    shouldPersist: false,\n    allowBackfillWhenNoOtherClients: true,\n  },\n];\n\nfor (const descriptor of defaultDescriptors) {\n  registerDescriptor(descriptor);\n}\n\nexport function registerServerAdaptorDescriptor(\n  descriptor: ServerAdaptorDescriptor\n): void {\n  registerDescriptor(descriptor);\n}\n\nexport function getServerAdaptorDescriptor(\n  crdtType: CrdtType\n): ServerAdaptorDescriptor | undefined {\n  return descriptors.get(crdtType);\n}\n","import { WebSocketServer, WebSocket } from \"ws\";\nimport { randomBytes } from \"node:crypto\";\nimport type { RawData } from \"ws\";\n// no direct CRDT imports here; handled by CrdtDoc implementations\nimport {\n  encode,\n  decode,\n  CrdtType,\n  MessageType,\n  JoinRequest,\n  JoinResponseOk,\n  JoinError,\n  JoinErrorCode,\n  DocUpdate,\n  Ack,\n  UpdateStatusCode,\n  Leave,\n  ProtocolMessage,\n  Permission,\n  RoomId,\n  HexString,\n  DocUpdateFragmentHeader,\n  DocUpdateFragment,\n  MAX_MESSAGE_SIZE,\n} from \"loro-protocol\";\nimport {\n  getServerAdaptorDescriptor,\n  type ServerAdaptorDescriptor,\n} from \"./crdt-doc\";\n\nexport interface SimpleServerConfig {\n  port: number;\n  host?: string; // default 127.0.0.1 to avoid sandbox binding errors\n  saveInterval?: number;\n  onLoadDocument?: (\n    roomId: string,\n    crdtType: CrdtType\n  ) => Promise<Uint8Array | null>;\n  onSaveDocument?: (\n    roomId: string,\n    crdtType: CrdtType,\n    data: Uint8Array\n  ) => Promise<void>;\n  /** Map join payload (`auth`) to permission; return null to reject. */\n  authenticate?: (\n    roomId: string,\n    crdtType: CrdtType,\n    auth: Uint8Array\n  ) => Promise<Permission | null>;\n}\n\ninterface RoomDocument {\n  data: Uint8Array;\n  descriptor: ServerAdaptorDescriptor;\n  lastSaved: number;\n  dirty: boolean;\n}\n\ninterface ClientConnection {\n  ws: WebSocket;\n  rooms: Set<string>;\n  fragments: Map<\n    HexString,\n    {\n      data: Uint8Array[];\n      totalSize: number;\n      received: number;\n      header: DocUpdateFragmentHeader;\n      timeoutId?: NodeJS.Timeout;\n    }\n  >;\n  permissions: Map<string, Permission>; // roomKey -> permission\n}\n\nexport class SimpleServer {\n  private wss?: WebSocketServer;\n  private rooms = new Map<string, RoomDocument>();\n  private clients = new WeakMap<WebSocket, ClientConnection>();\n  private saveTimer?: NodeJS.Timeout;\n  private config: SimpleServerConfig;\n  private static readonly DEFAULT_SAVE_INTERVAL = 60000; // 1 minute\n\n  constructor(config: SimpleServerConfig) {\n    this.config = config;\n  }\n\n  start(): Promise<void> {\n    return new Promise(resolve => {\n      const options: { port: number; host?: string } = {\n        port: this.config.port,\n      };\n      if (this.config.host) {\n        options.host = this.config.host;\n      }\n      this.wss = new WebSocketServer(options);\n\n      this.wss.on(\"connection\", ws => {\n        const client: ClientConnection = {\n          ws,\n          rooms: new Set(),\n          fragments: new Map(),\n          permissions: new Map(),\n        };\n        this.clients.set(ws, client);\n\n        ws.on(\"message\", (data: RawData, isBinary: boolean) => {\n          try {\n            const bytes = this.toUint8Array(data);\n            // TODO: REVIEW keepalive handling (app-level ping/pong per protocol.md)\n            // Keepalive: reply to text \"ping\", ignore text \"pong\"\n            if (!isBinary) {\n              const str = new TextDecoder().decode(bytes);\n              if (str === \"ping\") {\n                if (ws.readyState === WebSocket.OPEN) ws.send(\"pong\");\n                return;\n              }\n              if (str === \"pong\") return;\n              // Ignore other text frames\n              return;\n            }\n\n            const message = decode(bytes);\n            void this.handleMessage(client, message);\n          } catch (error) {\n            console.error(\"Failed to decode message:\", error);\n            ws.close(1002, \"Protocol error\");\n          }\n        });\n\n        ws.on(\"close\", () => {\n          this.handleDisconnect(client);\n        });\n      });\n\n      if (this.config.onSaveDocument) {\n        const saveInterval =\n          this.config.saveInterval ?? SimpleServer.DEFAULT_SAVE_INTERVAL;\n        this.saveTimer = setInterval(() => {\n          void this.saveAllDirtyDocuments();\n        }, saveInterval);\n      }\n\n      this.wss.on(\"listening\", resolve);\n    });\n  }\n\n  stop(): Promise<void> {\n    return new Promise(resolve => {\n      if (this.saveTimer) {\n        clearInterval(this.saveTimer);\n      }\n\n      void this.saveAllDirtyDocuments();\n\n      const wss = this.wss;\n      if (wss) {\n        const clients = Array.from(wss.clients);\n        const closers = Promise.all(\n          clients.map(ws => this.gracefulCloseWebSocket(ws))\n        );\n\n        void closers\n          .catch(() => { })\n          .finally(() => {\n            try {\n              wss.close(() => {\n                resolve();\n              });\n            } catch {\n              resolve();\n            }\n            this.wss = undefined;\n          });\n        return;\n      }\n\n      resolve();\n    });\n  }\n\n  private async gracefulCloseWebSocket(ws: WebSocket): Promise<void> {\n    try {\n      await this.waitForSocketDrain(ws);\n    } catch { }\n\n    try {\n      ws.close(1001, \"Server stopping\");\n    } catch { }\n\n    setTimeout(() => {\n      try {\n        if (ws.readyState !== WebSocket.CLOSED) ws.terminate();\n      } catch { }\n    }, 50);\n  }\n\n  private waitForSocketDrain(ws: WebSocket, timeoutMs = 2000): Promise<void> {\n    const readBufferedAmount = (): number | undefined => {\n      const raw = Reflect.get(ws, \"bufferedAmount\") as unknown;\n      return typeof raw === \"number\" ? raw : undefined;\n    };\n\n    if (readBufferedAmount() == null) return Promise.resolve();\n\n    return new Promise(resolve => {\n      const start = Date.now();\n      const poll = () => {\n        const state = ws.readyState;\n        if (state === WebSocket.CLOSING || state === WebSocket.CLOSED) {\n          resolve();\n          return;\n        }\n\n        const buffered = readBufferedAmount();\n        if (buffered == null || buffered <= 0 || Date.now() - start >= timeoutMs) {\n          resolve();\n          return;\n        }\n\n        setTimeout(poll, 25);\n      };\n\n      poll();\n    });\n  }\n\n  private toUint8Array(data: RawData): Uint8Array {\n    if (data instanceof ArrayBuffer) return new Uint8Array(data);\n    if (ArrayBuffer.isView(data))\n      return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n    if (typeof Buffer !== \"undefined\" && Buffer.isBuffer(data))\n      return new Uint8Array(data);\n    if (Array.isArray(data)) {\n      // Buffer[]\n      const total = data.reduce((s, b) => s + b.length, 0);\n      const out = new Uint8Array(total);\n      let off = 0;\n      for (const b of data) {\n        out.set(b, off);\n        off += b.length;\n      }\n      return out;\n    }\n    throw new Error(\"Unsupported message data type\");\n  }\n\n  private async handleMessage(\n    client: ClientConnection,\n    message: ProtocolMessage\n  ): Promise<void> {\n    switch (message.type) {\n      case MessageType.JoinRequest:\n        await this.handleJoinRequest(client, message);\n        break;\n      case MessageType.DocUpdate:\n        await this.handleDocUpdate(client, message);\n        break;\n      case MessageType.DocUpdateFragmentHeader:\n        this.handleFragmentHeader(client, message);\n        break;\n      case MessageType.DocUpdateFragment:\n        await this.handleFragment(client, message);\n        break;\n      case MessageType.Leave:\n        this.handleLeave(client, message);\n        break;\n      case MessageType.Ack:\n        // Clients may report failures when they cannot apply a server update.\n        if (message.status !== UpdateStatusCode.Ok) {\n          console.warn(\n            `Client reported update failure for ${message.crdt}:${message.roomId} ref ${message.refId} status ${message.status}`\n          );\n        }\n        break;\n      case MessageType.RoomError:\n        // Server does not expect these from clients; ignore.\n        break;\n      default:\n        throw new Error(`Unsupported message type: ${message.type}`);\n    }\n  }\n\n  private async handleJoinRequest(\n    client: ClientConnection,\n    message: JoinRequest\n  ): Promise<void> {\n    try {\n      // Authenticate if configured\n      let permission: Permission = \"write\";\n      if (this.config.authenticate) {\n        const authResult = await this.config.authenticate(\n          message.roomId,\n          message.crdt,\n          message.auth\n        );\n        if (!authResult) {\n          this.sendJoinError(\n            client,\n            message,\n            JoinErrorCode.AuthFailed,\n            \"Authentication failed\"\n          );\n          return;\n        }\n        permission = authResult;\n      }\n\n      const roomDoc = await this.getOrCreateRoomDocument(\n        message.roomId,\n        message.crdt\n      );\n      const roomKey = this.getRoomKey(message.roomId, message.crdt);\n      client.rooms.add(roomKey);\n      client.permissions.set(roomKey, permission);\n\n      const joinResult = roomDoc.descriptor.adaptor.handleJoinRequest(\n        roomDoc.data,\n        message.version,\n      );\n\n      // Send join response with current document version\n      const response: JoinResponseOk = {\n        ...joinResult.response,\n        permission,\n        crdt: message.crdt,\n        roomId: message.roomId,\n      };\n\n      this.sendMessage(client.ws, response);\n      // Backfill: send the updates the client is missing so it can\n      // catch up from its known version to the room’s current state.\n      // For Loro this is a delta from the VersionVector; for ELO this\n      // is an encrypted container selection; for Ephemeral it's current state.\n      const hasOthers = this.hasOtherClientsInRoom(\n        message.roomId,\n        message.crdt,\n        client\n      );\n      const shouldBackfill =\n        (hasOthers ||\n          roomDoc.descriptor.allowBackfillWhenNoOtherClients) &&\n        joinResult.updates &&\n        joinResult.updates.length;\n\n      if (shouldBackfill && joinResult.updates) {\n        this.sendMessage(client.ws, {\n          type: MessageType.DocUpdate,\n          crdt: message.crdt,\n          roomId: message.roomId,\n          updates: joinResult.updates,\n          batchId: this.newBatchId(),\n        });\n      }\n    } catch (error) {\n      this.sendJoinError(\n        client,\n        message,\n        JoinErrorCode.Unknown,\n        error instanceof Error ? error.message : \"Unknown error\"\n      );\n    }\n  }\n\n  private async handleDocUpdate(\n    client: ClientConnection,\n    message: DocUpdate\n  ): Promise<void> {\n    try {\n      // Guard: reject payloads that exceed max update size\n      // (Clients fragment large updates; this is a safety net.)\n      const oversized = message.updates.some(u => u.length > MAX_MESSAGE_SIZE);\n      if (oversized) {\n        this.sendAck(\n          client.ws,\n          message.batchId,\n          UpdateStatusCode.PayloadTooLarge,\n          message.crdt,\n          message.roomId,\n        );\n        return;\n      }\n\n      const roomKey = this.getRoomKey(message.roomId, message.crdt);\n\n      // Check if client has joined this room\n      if (!client.rooms.has(roomKey)) {\n        this.sendAck(\n          client.ws,\n          message.batchId,\n          UpdateStatusCode.PermissionDenied,\n          message.crdt,\n          message.roomId,\n        );\n        client.fragments.delete(message.batchId);\n        return;\n      }\n\n      const permission = client.permissions.get(roomKey);\n\n      // Check if client has write permission\n      if (permission !== \"write\") {\n        this.sendAck(\n          client.ws,\n          message.batchId,\n          UpdateStatusCode.PermissionDenied,\n          message.crdt,\n          message.roomId,\n        );\n        return;\n      }\n\n      const roomDoc = await this.getOrCreateRoomDocument(\n        message.roomId,\n        message.crdt\n      );\n\n      try {\n        const newDocumentData = roomDoc.descriptor.adaptor.applyUpdates(\n          roomDoc.data,\n          message.updates,\n        );\n        roomDoc.data = newDocumentData;\n      } catch (error) {\n        console.warn(\"applyUpdates failed for DocUpdate\", error);\n        this.sendAck(\n          client.ws,\n          message.batchId,\n          UpdateStatusCode.InvalidUpdate,\n          message.crdt,\n          message.roomId,\n        );\n        return;\n      }\n\n\n      if (roomDoc.descriptor.shouldPersist) {\n        roomDoc.dirty = true;\n      }\n\n      const updatesForBroadcast = message.updates;\n      // Notify sender\n      this.sendAck(\n        client.ws,\n        message.batchId,\n        UpdateStatusCode.Ok,\n        message.crdt,\n        message.roomId,\n      );\n\n      if (updatesForBroadcast.length > 0) {\n        const outgoing: DocUpdate = {\n          type: MessageType.DocUpdate,\n          crdt: message.crdt,\n          roomId: message.roomId,\n          updates: updatesForBroadcast,\n          batchId: message.batchId,\n        };\n        this.broadcastToRoom(\n          message.roomId,\n          message.crdt,\n          outgoing,\n          client\n        );\n      }\n    } catch (error) {\n      console.error(error);\n      this.sendAck(\n        client.ws,\n        message.batchId,\n        UpdateStatusCode.Unknown,\n        message.crdt,\n        message.roomId,\n      );\n    }\n  }\n\n  private handleFragmentHeader(\n    client: ClientConnection,\n    message: DocUpdateFragmentHeader\n  ): void {\n    const roomKey = this.getRoomKey(message.roomId, message.crdt);\n\n    // Check if client has joined this room\n    if (!client.rooms.has(roomKey)) {\n      this.sendAck(\n        client.ws,\n        message.batchId,\n        UpdateStatusCode.PermissionDenied,\n        message.crdt,\n        message.roomId,\n      );\n      return;\n    }\n\n    const batch = {\n      data: Array.from({ length: message.fragmentCount }, () => new Uint8Array()),\n      totalSize: message.totalSizeBytes,\n      received: 0,\n      header: message,\n      timeoutId: undefined as NodeJS.Timeout | undefined,\n    };\n\n    batch.timeoutId = setTimeout(() => {\n      client.fragments.delete(message.batchId);\n      this.sendAck(\n        client.ws,\n        message.batchId,\n        UpdateStatusCode.FragmentTimeout,\n        message.crdt,\n        message.roomId,\n      );\n    }, 10000);\n\n    client.fragments.set(message.batchId, batch);\n  }\n\n  private async handleFragment(\n    client: ClientConnection,\n    message: DocUpdateFragment\n  ): Promise<void> {\n    const batch = client.fragments.get(message.batchId);\n    if (!batch) {\n      this.sendAck(\n        client.ws,\n        message.batchId,\n        UpdateStatusCode.FragmentTimeout,\n        message.crdt,\n        message.roomId,\n      );\n      return;\n    }\n\n    batch.data[message.index] = message.fragment;\n    batch.received++;\n\n    // Check if all fragments received\n    if (batch.received === batch.data.length) {\n      if (batch.timeoutId) clearTimeout(batch.timeoutId);\n      // Reconstruct the complete update\n      const totalData = new Uint8Array(batch.totalSize);\n      let offset = 0;\n\n      for (const fragment of batch.data) {\n        totalData.set(fragment, offset);\n        offset += fragment.length;\n      }\n\n      // Apply updates with permission checks, then broadcast the original\n      // fragment header and fragments to other clients to avoid oversize\n      // DocUpdate messages.\n      const roomKey = this.getRoomKey(message.roomId, message.crdt);\n      if (!client.rooms.has(roomKey)) {\n        this.sendAck(\n          client.ws,\n          message.batchId,\n          UpdateStatusCode.PermissionDenied,\n          message.crdt,\n          message.roomId,\n        );\n        return;\n      }\n\n      const permission = client.permissions.get(roomKey);\n      if (permission !== \"write\") {\n        this.sendAck(\n          client.ws,\n          message.batchId,\n          UpdateStatusCode.PermissionDenied,\n          message.crdt,\n          message.roomId,\n        );\n        client.fragments.delete(message.batchId);\n        return;\n      }\n\n      // Apply to server-side CRDT state\n      const roomDoc = await this.getOrCreateRoomDocument(\n        message.roomId,\n        message.crdt\n      );\n      try {\n        const newDocumentData = roomDoc.descriptor.adaptor.applyUpdates(\n          roomDoc.data,\n          [totalData],\n        );\n        roomDoc.data = newDocumentData;\n      } catch (error) {\n        console.warn(\"applyUpdates failed for fragmented DocUpdate\", error);\n        this.sendAck(\n          client.ws,\n          message.batchId,\n          UpdateStatusCode.InvalidUpdate,\n          message.crdt,\n          message.roomId,\n        );\n        client.fragments.delete(message.batchId);\n        return;\n      }\n      if (roomDoc.descriptor.shouldPersist) {\n        roomDoc.dirty = true;\n      }\n\n      // Notify sender\n      this.sendAck(\n        client.ws,\n        message.batchId,\n        UpdateStatusCode.Ok,\n        message.crdt,\n        message.roomId,\n      );\n\n      // Broadcast original fragments to other clients in the room\n      const header = client.fragments.get(message.batchId)!.header;\n      this.wss?.clients.forEach(ws => {\n        const c = this.clients.get(ws);\n        if (!c || c === client) return;\n        if (!c.rooms.has(roomKey)) return;\n        this.sendMessage(ws, header);\n        for (let i = 0; i < batch.data.length; i++) {\n          const fragMsg: DocUpdateFragment = {\n            type: MessageType.DocUpdateFragment,\n            crdt: message.crdt,\n            roomId: message.roomId,\n            batchId: message.batchId,\n            index: i,\n            fragment: batch.data[i]!,\n          };\n          this.sendMessage(ws, fragMsg);\n        }\n      });\n\n      client.fragments.delete(message.batchId);\n    }\n  }\n\n  private handleLeave(client: ClientConnection, message: Leave): void {\n    const roomKey = this.getRoomKey(message.roomId, message.crdt);\n    client.rooms.delete(roomKey);\n    client.permissions.delete(roomKey);\n  }\n\n  private handleDisconnect(client: ClientConnection): void {\n    client.rooms.clear();\n    for (const [, frag] of client.fragments) {\n      if (frag.timeoutId) clearTimeout(frag.timeoutId);\n    }\n    client.fragments.clear();\n    client.permissions.clear();\n  }\n\n  private async getOrCreateRoomDocument(\n    roomId: RoomId,\n    crdtType: CrdtType\n  ): Promise<RoomDocument> {\n    const roomKey = this.getRoomKey(roomId, crdtType);\n\n    let roomDoc = this.rooms.get(roomKey);\n    if (roomDoc) return roomDoc;\n\n    const descriptor = getServerAdaptorDescriptor(crdtType);\n    if (!descriptor) throw new Error(\"Unsupported CRDT type\");\n\n    let data = descriptor.adaptor.createEmpty();\n\n    if (descriptor.shouldPersist && this.config.onLoadDocument) {\n      try {\n        const loaded = await this.config.onLoadDocument(\n          roomId,\n          crdtType\n        );\n        if (loaded) {\n          data = loaded;\n        }\n      } catch (error) {\n        console.warn(\"Failed to load document:\", error);\n        throw error;\n      }\n    }\n\n    roomDoc = {\n      data,\n      descriptor,\n      lastSaved: Date.now(),\n      dirty: false,\n    };\n    this.rooms.set(roomKey, roomDoc);\n    return roomDoc;\n  }\n\n  private sendJoinError(\n    client: ClientConnection,\n    message: JoinRequest,\n    code: JoinErrorCode,\n    errorMessage: string\n  ): void {\n    const error: JoinError = {\n      type: MessageType.JoinError,\n      crdt: message.crdt,\n      roomId: message.roomId,\n      code,\n      message: errorMessage,\n    };\n    this.sendMessage(client.ws, error);\n  }\n\n  private sendMessage(ws: WebSocket, message: ProtocolMessage): void {\n    if (ws.readyState === WebSocket.OPEN) {\n      const data = encode(message);\n      ws.send(data);\n    }\n  }\n\n  private newBatchId(): HexString {\n    return `0x${randomBytes(8).toString(\"hex\")}`;\n  }\n\n  private sendAck(\n    ws: WebSocket,\n    refId: HexString,\n    status: UpdateStatusCode,\n    crdt: CrdtType,\n    roomId: RoomId\n  ): void {\n    const ack: Ack = {\n      type: MessageType.Ack,\n      crdt,\n      roomId,\n      refId,\n      status,\n    };\n    this.sendMessage(ws, ack);\n  }\n\n  private broadcastToRoom(\n    roomId: RoomId,\n    crdtType: CrdtType,\n    message: ProtocolMessage,\n    excludeClient?: ClientConnection\n  ): void {\n    const roomKey = this.getRoomKey(roomId, crdtType);\n\n    this.wss?.clients.forEach(ws => {\n      const client = this.clients.get(ws);\n      if (client && client !== excludeClient && client.rooms.has(roomKey)) {\n        // console.log(\"send message to client\", JSON.stringify(message))\n        this.sendMessage(ws, message);\n      }\n    });\n  }\n\n  private hasOtherClientsInRoom(\n    roomId: RoomId,\n    crdtType: CrdtType,\n    excludeClient?: ClientConnection\n  ): boolean {\n    const roomKey = this.getRoomKey(roomId, crdtType);\n    let count = 0;\n    this.wss?.clients.forEach(ws => {\n      const c = this.clients.get(ws);\n      if (!c) return;\n      if (excludeClient && c === excludeClient) return;\n      if (c.rooms.has(roomKey)) count++;\n    });\n    return count > 0;\n  }\n\n  private getRoomKey(roomId: RoomId, crdtType: CrdtType): string {\n    return `${roomId}:${crdtType}`;\n  }\n\n  private parseRoomKey(\n    roomKey: string\n  ): { roomId: string; crdtType: CrdtType } {\n    const sep = roomKey.lastIndexOf(\":\");\n    if (sep === -1) {\n      return { roomId: roomKey, crdtType: CrdtType.Loro };\n    }\n    const roomId = roomKey.slice(0, sep);\n    const crdtType = Number(roomKey.slice(sep + 1)) as unknown as CrdtType;\n    return { roomId, crdtType };\n  }\n\n  private async saveAllDirtyDocuments(): Promise<void> {\n    if (!this.config.onSaveDocument) return;\n\n    for (const [roomKey, roomDoc] of this.rooms) {\n      if (!roomDoc.dirty || !roomDoc.descriptor.shouldPersist) continue;\n      try {\n        const { roomId, crdtType } = this.parseRoomKey(roomKey);\n        await this.config.onSaveDocument(roomId, crdtType, roomDoc.data);\n        roomDoc.dirty = false;\n        roomDoc.lastSaved = Date.now();\n      } catch (error) {\n        console.error(\"Failed to save document:\", error);\n      }\n    }\n  }\n}\n"],"mappings":"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"}