@fieldnotes/sync-server 0.17.1 → 0.18.0
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.
- package/README.md +2 -2
- package/dist/index.cjs +142 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -20
- package/dist/index.d.ts +45 -20
- package/dist/index.js +150 -40
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/sync-hub.ts","../src/memory-hub-backend.ts","../src/hub-fanout.ts","../src/sync-plugin.ts","../src/resource-limits.ts","../src/create-sync-server.ts","../src/heartbeat.ts","../src/shutdown.ts"],"sourcesContent":["export { SyncHub } from './sync-hub';\nexport type { SyncHubOptions, Connection } from './sync-hub';\nexport { MemoryHubBackend } from './memory-hub-backend';\nexport type { HubBackend } from './hub-backend';\nexport { createSyncServer } from './create-sync-server';\nexport type { CreateSyncServerOptions } from './create-sync-server';\nexport { InMemoryHubFanout } from './hub-fanout';\nexport type { HubFanout } from './hub-fanout';\nexport type { AuthInfo, AuthResult, Authenticate } from './authenticate';\nexport type {\n Authorize,\n AuthorizeContext,\n AuthorizeLayer,\n AuthorizeLayerContext,\n OwnedElement,\n ReadContext,\n CanRead,\n} from './authorize';\nexport { startHeartbeat } from './heartbeat';\nexport type { Heartbeat, HeartbeatSocket, HeartbeatServer } from './heartbeat';\nexport type {\n ServerSyncPlugin,\n ApplyResult,\n ServerOpContext,\n ServerNext,\n PluginSnapshot,\n ServerExtensionRegistry,\n} from './sync-plugin';\n","import {\n parseEnvelope,\n isValidElement,\n isNewerLayerRecord,\n type LayerRecord,\n type SyncOp,\n type SyncEnvelope,\n type PluginSnapshot,\n} from '@fieldnotes/sync';\nimport { MemoryHubBackend } from './memory-hub-backend';\nimport { InMemoryHubFanout, type HubFanout } from './hub-fanout';\nimport type { HubBackend } from './hub-backend';\nimport type { Authorize, AuthorizeLayer, CanRead, OwnedElement } from './authorize';\nimport { ServerPluginRegistry } from './sync-plugin';\nimport type { ApplyResult, ServerOpContext, ServerSyncPlugin } from './sync-plugin';\nimport {\n DEFAULT_MAX_JSON_DEPTH,\n DEFAULT_MAX_PRESENCE_LANES,\n DEFAULT_PRESENCE_THROTTLE_MS,\n hasJsonDepthAtMost,\n} from './resource-limits';\n\nexport interface Connection {\n id: string;\n room: string;\n userId?: string;\n role?: string;\n send(message: string): void;\n}\n\nexport interface SyncHubOptions {\n backend?: HubBackend;\n fanout?: HubFanout;\n instanceId?: string;\n authorize?: Authorize;\n authorizeLayer?: AuthorizeLayer;\n plugins?: readonly ServerSyncPlugin[];\n canRead?: CanRead;\n maxJsonDepth?: number;\n presenceThrottleMs?: number;\n maxPresenceLanes?: number;\n}\n\nconst HUB_FROM = 'hub';\n\nfunction generateInstanceId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n return `i-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isFanoutOp(op: unknown): op is Extract<SyncOp, { kind: 'upsert' | 'remove' | 'clear' }> {\n if (typeof op !== 'object' || op === null) return false;\n const o = op as { kind?: unknown; element?: unknown; id?: unknown };\n if (o.kind === 'upsert') return isValidElement(o.element);\n if (o.kind === 'remove') return typeof o.id === 'string';\n return o.kind === 'clear';\n}\n\ntype LayerOp = Extract<SyncOp, { kind: 'layer-upsert' | 'layer-remove' }>;\n\ninterface PresenceLane {\n lastSentAt: number | undefined;\n pending: { data: unknown; timer: ReturnType<typeof setTimeout> } | null;\n}\n\nconst FALLBACK_PRESENCE_LANE = '';\nconst MAX_PRESENCE_LANE_LENGTH = 64;\n\nfunction presenceLaneOf(data: unknown): string {\n if (typeof data !== 'object' || data === null) return FALLBACK_PRESENCE_LANE;\n const kind = (data as { kind?: unknown }).kind;\n if (typeof kind !== 'string' || kind.length === 0 || kind.length > MAX_PRESENCE_LANE_LENGTH) {\n return FALLBACK_PRESENCE_LANE;\n }\n return kind;\n}\n\nfunction isLayerOp(op: unknown): op is LayerOp {\n if (typeof op !== 'object' || op === null) return false;\n const k = (op as { kind?: unknown }).kind;\n // Shape is re-validated by parseEnvelope on the serial path; fanout payloads\n // come from a sibling hub that already validated them.\n return k === 'layer-upsert' || k === 'layer-remove';\n}\n\nfunction layerOpToRecord(op: LayerOp): LayerRecord {\n return op.kind === 'layer-upsert'\n ? { id: op.layer.id, version: op.version, editor: op.editor, definition: op.layer }\n : { id: op.id, version: op.version, editor: op.editor };\n}\n\nfunction layerRecordToOp(record: LayerRecord): LayerOp {\n return record.definition\n ? {\n kind: 'layer-upsert',\n layer: record.definition,\n version: record.version,\n editor: record.editor,\n }\n : { kind: 'layer-remove', id: record.id, version: record.version, editor: record.editor };\n}\n\nfunction isPresenceOp(\n op: unknown,\n): op is { kind: 'presence'; data: unknown } | { kind: 'presence-leave' } {\n if (typeof op !== 'object' || op === null) return false;\n const k = (op as { kind?: unknown }).kind;\n return k === 'presence' || k === 'presence-leave';\n}\n\nexport class SyncHub {\n private readonly backend: HubBackend;\n private readonly conns = new Map<string, Connection>();\n private readonly rooms = new Map<string, Set<string>>(); // room → connIds\n private readonly roomQueues = new Map<string, Promise<void>>(); // room → serial tail\n private readonly presenceConnections = new Set<string>();\n private readonly instanceId: string;\n private readonly fanout: HubFanout;\n private readonly fanoutUnsub: () => void;\n private readonly authorize?: Authorize;\n private readonly authorizeLayer?: AuthorizeLayer;\n private readonly pluginRegistry: ServerPluginRegistry;\n private readonly canRead?: CanRead;\n private readonly memoryLayers = new Map<string, Map<string, LayerRecord>>();\n private readonly maxJsonDepth: number;\n private readonly presenceThrottleMs: number;\n private readonly maxPresenceLanes: number;\n /**\n * Presence throttle state keyed by connection, then by lane. A lane is the\n * payload's `kind` (a non-empty string of at most 64 chars) or the reserved\n * fallback lane `''`, so a rapid stream of one kind (awareness cursors) can\n * never replace a pending frame of another kind (a ping, a path `cleared`).\n * Within a lane the newest payload wins. The lane count per connection is\n * capped by `maxPresenceLanes`, counting the fallback lane, so a client\n * cannot mint timers by varying `kind`.\n */\n private readonly presenceLanes = new Map<string, Map<string, PresenceLane>>();\n\n constructor(options: SyncHubOptions = {}) {\n this.backend = options.backend ?? new MemoryHubBackend();\n this.pluginRegistry = new ServerPluginRegistry(options.plugins ?? []);\n this.instanceId = options.instanceId ?? generateInstanceId();\n this.fanout = options.fanout ?? new InMemoryHubFanout();\n this.authorize = options.authorize;\n this.authorizeLayer = options.authorizeLayer;\n this.canRead = options.canRead;\n this.maxJsonDepth = options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH;\n this.presenceThrottleMs = options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS;\n const maxPresenceLanes = options.maxPresenceLanes ?? DEFAULT_MAX_PRESENCE_LANES;\n if (!Number.isFinite(maxPresenceLanes) || maxPresenceLanes < 1) {\n throw new RangeError('maxPresenceLanes must be a finite number of at least 1');\n }\n this.maxPresenceLanes = Math.floor(maxPresenceLanes);\n this.fanoutUnsub = this.fanout.subscribe((payload) => this.onFanout(payload));\n }\n\n addConnection(conn: Connection): void {\n this.conns.set(conn.id, conn);\n let set = this.rooms.get(conn.room);\n if (!set) {\n set = new Set();\n this.rooms.set(conn.room, set);\n }\n set.add(conn.id);\n }\n\n removeConnection(connId: string): void {\n const conn = this.conns.get(connId);\n if (!conn) return;\n this.conns.delete(connId);\n const room = conn.room;\n const hadPresence = this.presenceConnections.delete(connId);\n this.clearPresenceLanes(connId);\n const members = this.rooms.get(room);\n if (members) {\n members.delete(connId);\n if (members.size === 0) {\n this.rooms.delete(room);\n this.roomQueues.delete(room);\n }\n }\n if (hadPresence) this.broadcastLeave(room, conn.id);\n }\n\n roomCount(): number {\n return this.rooms.size;\n }\n\n /**\n * Broadcasts ephemeral server-owned presence data to every connection in a room.\n * The returned count covers successful delivery on this hub instance only; configured fan-out\n * forwards the same event to other instances on a best-effort basis.\n */\n broadcastPresence<T>(room: string, data: T): number {\n const op = { kind: 'presence' as const, data };\n const sent = this.relayToRoom(room, undefined, JSON.stringify({ from: HUB_FROM, op }));\n this.safePublish(JSON.stringify({ o: this.instanceId, room, from: HUB_FROM, op }));\n return sent;\n }\n\n handleMessage(connId: string, message: string): Promise<void> {\n const conn = this.conns.get(connId);\n if (!conn) return Promise.resolve();\n if (!hasJsonDepthAtMost(message, this.maxJsonDepth)) return Promise.resolve();\n const env = parseEnvelope(message);\n if (!env) return Promise.resolve();\n if (env.op.kind === 'presence') {\n this.schedulePresence(conn, env.op.data); // off-queue, throttled independently\n return Promise.resolve();\n }\n const room = conn.room;\n // The per-room serial queue is the single total-order authority: ops apply in arrival order\n // (arrival-order LWW — no per-element seq; see D3 / TD-12). Different rooms run independently.\n const prev = this.roomQueues.get(room) ?? Promise.resolve();\n const operation = prev.then(() => this.process(conn, env));\n this.roomQueues.set(\n room,\n operation.catch(() => {\n // Recover only the internal tail so one failed message never wedges the room queue.\n // The caller still receives the operation rejection for observability.\n }),\n );\n return operation;\n }\n\n private async process(conn: Connection, env: SyncEnvelope): Promise<void> {\n const op = env.op;\n if (op.kind === 'request-snapshot') {\n const all = (await this.backend.snapshot(conn.room)) as OwnedElement[];\n const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;\n // Layer records are presentation-only and carry no element bytes, so no\n // audience filter applies; the field is omitted while a room has never\n // used layer sync, keeping snapshot frames byte-identical to before.\n const layers = await this.getLayerRecords(conn.room);\n const snapshotOp: Record<string, unknown> = {\n kind: 'snapshot',\n to: env.from,\n elements,\n };\n if (layers.length > 0) snapshotOp['layers'] = layers;\n const extensions: Record<string, PluginSnapshot> = {};\n for (const plugin of this.pluginRegistry.plugins) {\n let snapshot = await plugin.snapshot?.(conn.room, this.backend);\n if (!snapshot) continue;\n if (plugin.filterSnapshot) {\n snapshot =\n plugin.filterSnapshot(snapshot, { userId: conn.userId, role: conn.role }) ?? undefined;\n }\n if (!snapshot) continue;\n if (plugin.legacySnapshotKey) snapshotOp[plugin.legacySnapshotKey] = snapshot.data;\n else extensions[plugin.name] = snapshot;\n }\n if (Object.keys(extensions).length > 0) snapshotOp['extensions'] = extensions;\n conn.send(JSON.stringify({ from: HUB_FROM, op: snapshotOp }));\n } else if (op.kind === 'layer-upsert' || op.kind === 'layer-remove') {\n await this.processLayerOp(conn, op);\n } else if (op.kind === 'extension') {\n const entry = this.pluginRegistry.extension(op.extensionKind);\n if (!entry || !entry.kind.codec.validate(op.payload)) return;\n await this.deliverPluginResult(conn, await entry.handler(op, this.pluginContext(conn)));\n } else if (this.pluginRegistry.ownerOf(op.kind)) {\n const owner = this.pluginRegistry.ownerOf(op.kind);\n if (!owner?.process) return;\n const result = await owner.process(op, this.pluginContext(conn), async () => ({\n accepted: null,\n corrections: [],\n }));\n await this.deliverPluginResult(conn, result);\n } else if (op.kind === 'upsert' || op.kind === 'remove' || op.kind === 'clear') {\n const id = op.kind === 'upsert' ? op.element.id : op.kind === 'remove' ? op.id : undefined;\n const needCurrent = (this.authorize || this.canRead) && id !== undefined;\n const current: OwnedElement | undefined = needCurrent\n ? await this.backend.get(conn.room, id)\n : undefined;\n\n let outboundOp: SyncOp = op;\n if (this.authorize) {\n const allowed = await this.authorize({\n userId: conn.userId,\n role: conn.role,\n room: conn.room,\n op,\n currentElement: current,\n });\n if (!allowed) {\n await this.sendCorrection(conn, env.from, op, current);\n return;\n }\n if (op.kind === 'upsert') {\n const ownerId = current?.ownerId ?? conn.userId;\n const stampedElement: OwnedElement = { ...op.element, ownerId };\n outboundOp = { kind: 'upsert', element: stampedElement };\n }\n }\n\n const prevExisted = current !== undefined;\n const prevAudience = current?.audience;\n const result = await this.runCorePlugins(conn, outboundOp);\n for (const correction of result.corrections) {\n conn.send(JSON.stringify({ from: HUB_FROM, op: correction }));\n }\n const accepted = result.accepted;\n if (\n accepted &&\n (accepted.kind === 'upsert' || accepted.kind === 'remove' || accepted.kind === 'clear')\n ) {\n if (result.locality !== 'local') {\n await this.fanout.publish(\n JSON.stringify({\n o: this.instanceId,\n room: conn.room,\n from: conn.id,\n op: accepted,\n prev: prevAudience,\n existed: prevExisted,\n }),\n );\n }\n this.deliverToRoom(conn.room, conn.id, conn.id, accepted, prevAudience, prevExisted);\n }\n for (const broadcast of result.broadcast ?? []) {\n await this.publishPluginOp(conn, broadcast, result.locality);\n }\n }\n // 'snapshot' from a client → ignored\n }\n\n private pluginContext(conn: Connection): ServerOpContext {\n return {\n room: conn.room,\n connectionId: conn.id,\n userId: conn.userId,\n role: conn.role,\n backend: this.backend,\n backendPlugin: (key) => this.backend.getService?.(key),\n };\n }\n\n private async runCorePlugins(conn: Connection, op: SyncOp): Promise<ApplyResult> {\n const middleware = this.pluginRegistry.plugins.filter((plugin) => plugin.process);\n const context = this.pluginContext(conn);\n const dispatch = async (index: number, current: SyncOp): Promise<ApplyResult> => {\n const plugin = middleware[index];\n if (!plugin?.process) {\n await this.backend.apply(conn.room, current);\n return { accepted: current, corrections: [] };\n }\n let called = false;\n return plugin.process(current, context, async (nextOp, nextContext) => {\n if (called) throw new Error(`Server plugin \"${plugin.name}\" called next() more than once`);\n if (nextContext !== context) {\n throw new Error(`Server plugin \"${plugin.name}\" replaced the operation context`);\n }\n called = true;\n return dispatch(index + 1, nextOp);\n });\n };\n return dispatch(0, op);\n }\n\n private async deliverPluginResult(conn: Connection, result: ApplyResult): Promise<void> {\n for (const correction of result.corrections) {\n conn.send(JSON.stringify({ from: HUB_FROM, op: correction }));\n }\n if (result.accepted) await this.publishPluginOp(conn, result.accepted, result.locality);\n for (const broadcast of result.broadcast ?? []) {\n await this.publishPluginOp(conn, broadcast, result.locality);\n }\n }\n\n private async publishPluginOp(\n conn: Connection,\n op: SyncOp,\n locality: ApplyResult['locality'],\n ): Promise<void> {\n if (locality !== 'local') {\n await this.fanout.publish(\n JSON.stringify({ o: this.instanceId, room: conn.room, from: conn.id, op }),\n );\n }\n this.relayToRoom(conn.room, conn.id, JSON.stringify({ from: conn.id, op }));\n }\n\n /**\n * Applies a layer-definition edit on the room's serial queue. Convergence is\n * last-writer-wins under the deterministic (version, editor) ordering — the\n * same rule every client applies — so arrival order never decides a race. A\n * stale or denied edit is answered with an authoritative correction to the\n * sender only.\n */\n private async processLayerOp(conn: Connection, op: LayerOp): Promise<void> {\n const record = layerOpToRecord(op);\n const current = await this.getLayerRecord(conn.room, record.id);\n if (this.authorizeLayer) {\n const allowed = await this.authorizeLayer({\n userId: conn.userId,\n role: conn.role,\n room: conn.room,\n op,\n currentRecord: current,\n });\n if (!allowed) {\n // Revert the sender to the room's record; a tombstone when there is\n // none, so the denied local edit disappears everywhere consistently.\n const correction = current ?? { id: record.id, version: record.version, editor: HUB_FROM };\n conn.send(JSON.stringify({ from: HUB_FROM, op: layerRecordToOp(correction) }));\n return;\n }\n }\n if (current && !isNewerLayerRecord(record, current)) {\n // Stale under (version, editor): converge the sender, do not broadcast.\n conn.send(JSON.stringify({ from: HUB_FROM, op: layerRecordToOp(current) }));\n return;\n }\n await this.applyLayerRecord(conn.room, record);\n await this.fanout.publish(\n JSON.stringify({ o: this.instanceId, room: conn.room, from: conn.id, op }),\n );\n this.relayToRoom(conn.room, conn.id, JSON.stringify({ from: conn.id, op }));\n }\n\n private layerBackend(): Required<\n Pick<HubBackend, 'layerRecords' | 'getLayerRecord' | 'applyLayerRecord'>\n > | null {\n const { layerRecords, getLayerRecord, applyLayerRecord } = this.backend;\n if (!layerRecords || !getLayerRecord || !applyLayerRecord) return null;\n return {\n layerRecords: layerRecords.bind(this.backend),\n getLayerRecord: getLayerRecord.bind(this.backend),\n applyLayerRecord: applyLayerRecord.bind(this.backend),\n };\n }\n\n private async getLayerRecords(room: string): Promise<LayerRecord[]> {\n const backend = this.layerBackend();\n if (backend) return backend.layerRecords(room);\n return [...(this.memoryLayers.get(room)?.values() ?? [])];\n }\n\n private async getLayerRecord(room: string, id: string): Promise<LayerRecord | undefined> {\n const backend = this.layerBackend();\n if (backend) return backend.getLayerRecord(room, id);\n return this.memoryLayers.get(room)?.get(id);\n }\n\n private async applyLayerRecord(room: string, record: LayerRecord): Promise<void> {\n const backend = this.layerBackend();\n if (backend) {\n await backend.applyLayerRecord(room, record);\n return;\n }\n let map = this.memoryLayers.get(room);\n if (!map) {\n map = new Map();\n this.memoryLayers.set(room, map);\n }\n map.set(record.id, record);\n }\n\n private mayRead(conn: Connection, audience: string | undefined): boolean {\n if (!this.canRead) return true;\n return this.canRead({ userId: conn.userId, role: conn.role, room: conn.room, audience });\n }\n\n private safePublish(payload: string): void {\n try {\n void Promise.resolve(this.fanout.publish(payload)).catch(() => {\n /* presence is ephemeral; a broken publisher must not create an unhandled rejection */\n });\n } catch {\n /* a broken fanout publisher must not break the un-queued presence relay */\n }\n }\n\n private relayToRoom(room: string, excludeId: string | undefined, message: string): number {\n const members = this.rooms.get(room);\n if (!members) return 0;\n let sent = 0;\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n try {\n conn.send(message);\n sent += 1;\n } catch {\n /* a throwing socket must not break the relay loop */\n }\n }\n return sent;\n }\n\n private broadcastClientPresence(conn: Connection, data: unknown): void {\n this.presenceConnections.add(conn.id);\n const message = JSON.stringify({ from: conn.id, op: { kind: 'presence', data } });\n this.relayToRoom(conn.room, conn.id, message);\n this.safePublish(\n JSON.stringify({\n o: this.instanceId,\n room: conn.room,\n from: conn.id,\n op: { kind: 'presence', data },\n }),\n );\n }\n\n private clearPresenceLanes(connId: string): void {\n const lanes = this.presenceLanes.get(connId);\n if (!lanes) return;\n for (const lane of lanes.values()) {\n if (lane.pending) clearTimeout(lane.pending.timer);\n }\n this.presenceLanes.delete(connId);\n }\n\n private schedulePresence(conn: Connection, data: unknown): void {\n if (this.presenceThrottleMs <= 0) {\n this.broadcastClientPresence(conn, data);\n return;\n }\n const lane = this.presenceLaneFor(conn.id, presenceLaneOf(data));\n const now = Date.now();\n if (lane.lastSentAt === undefined || now - lane.lastSentAt >= this.presenceThrottleMs) {\n if (lane.pending) {\n clearTimeout(lane.pending.timer);\n lane.pending = null;\n }\n lane.lastSentAt = now;\n this.broadcastClientPresence(conn, data);\n return;\n }\n if (lane.pending) {\n lane.pending.data = data;\n return;\n }\n const timer = setTimeout(\n () => {\n const pending = lane.pending;\n lane.pending = null;\n if (!pending || !this.conns.has(conn.id)) return;\n lane.lastSentAt = Date.now();\n this.broadcastClientPresence(conn, pending.data);\n },\n this.presenceThrottleMs - (now - lane.lastSentAt),\n );\n lane.pending = { data, timer };\n }\n\n private presenceLaneFor(connId: string, requested: string): PresenceLane {\n let lanes = this.presenceLanes.get(connId);\n if (!lanes) {\n lanes = new Map();\n this.presenceLanes.set(connId, lanes);\n }\n let key = requested;\n if (key !== FALLBACK_PRESENCE_LANE && !lanes.has(key)) {\n const named = lanes.size - (lanes.has(FALLBACK_PRESENCE_LANE) ? 1 : 0);\n if (named >= this.maxPresenceLanes - 1) key = FALLBACK_PRESENCE_LANE;\n }\n let lane = lanes.get(key);\n if (!lane) {\n lane = { lastSentAt: undefined, pending: null };\n lanes.set(key, lane);\n }\n return lane;\n }\n\n private broadcastLeave(room: string, from: string): void {\n const message = JSON.stringify({ from, op: { kind: 'presence-leave' } });\n this.relayToRoom(room, undefined, message);\n this.safePublish(\n JSON.stringify({ o: this.instanceId, room, from, op: { kind: 'presence-leave' } }),\n );\n }\n\n private deliverToRoom(\n room: string,\n excludeId: string | undefined,\n from: string,\n op: SyncOp,\n prevAudience: string | undefined,\n prevExisted: boolean,\n ): void {\n const members = this.rooms.get(room);\n if (!members) return;\n const send = (conn: Connection, msg: string): void => {\n try {\n conn.send(msg);\n } catch {\n /* a throwing socket must not break the delivery loop */\n }\n };\n if (op.kind === 'upsert') {\n const audience = (op.element as OwnedElement).audience;\n const upsertMsg = JSON.stringify({ from, op });\n const removeMsg = JSON.stringify({\n from: HUB_FROM,\n op: { kind: 'remove', id: op.element.id },\n });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n if (this.mayRead(conn, audience)) send(conn, upsertMsg);\n else if (prevExisted && this.mayRead(conn, prevAudience)) send(conn, removeMsg);\n }\n } else if (op.kind === 'remove') {\n const removeMsg = JSON.stringify({ from, op });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n // No read filter → forward to all (today's behavior; current/prevExisted aren't fetched without\n // a hook). With canRead, only recipients who could see the removed element get it.\n const wasVisible = !this.canRead || (prevExisted && this.mayRead(conn, prevAudience));\n if (wasVisible) send(conn, removeMsg);\n }\n } else if (op.kind === 'clear') {\n const clearMsg = JSON.stringify({ from, op });\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (conn) send(conn, clearMsg);\n }\n }\n }\n\n private async sendCorrection(\n conn: Connection,\n from: string,\n op: SyncOp,\n current: OwnedElement | undefined,\n ): Promise<void> {\n let correction: SyncOp | undefined;\n if (op.kind === 'upsert') {\n correction = current\n ? this.mayRead(conn, current.audience)\n ? { kind: 'upsert', element: current }\n : { kind: 'remove', id: current.id }\n : { kind: 'remove', id: op.element.id };\n } else if (op.kind === 'remove') {\n correction = current\n ? this.mayRead(conn, current.audience)\n ? { kind: 'upsert', element: current }\n : { kind: 'remove', id: current.id }\n : undefined;\n } else if (op.kind === 'clear') {\n const all = (await this.backend.snapshot(conn.room)) as OwnedElement[];\n const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;\n correction = { kind: 'snapshot', to: from, elements };\n }\n if (correction) conn.send(JSON.stringify({ from: HUB_FROM, op: correction }));\n }\n\n private onFanout(payload: string): void {\n // Off the serial queue on purpose: forward-only (the origin already applied to the SHARED backend),\n // and delivery is already ordered. Re-filter per local member (canRead runs on EVERY instance).\n let env: {\n o?: unknown;\n room?: unknown;\n from?: unknown;\n op?: unknown;\n prev?: unknown;\n existed?: unknown;\n };\n try {\n env = JSON.parse(payload);\n } catch {\n return;\n }\n if (typeof env.o !== 'string' || typeof env.room !== 'string' || typeof env.from !== 'string')\n return;\n if (env.o === this.instanceId) return; // our own publish — already delivered locally\n const op = env.op;\n if (isPresenceOp(op)) {\n // presence/leave: raw forward to all local members (the sender lives on the origin instance),\n // no backend, no canRead filter.\n this.relayToRoom(env.room, undefined, JSON.stringify({ from: env.from, op }));\n return;\n }\n if (isLayerOp(op)) {\n void this.applyFanoutLayerOp(env.room, op).catch(() => {\n /* a broken backend must not break the fanout relay */\n });\n this.relayToRoom(env.room, undefined, JSON.stringify({ from: env.from, op }));\n return;\n }\n const plugin =\n typeof op === 'object' && op !== null && (op as { kind?: unknown }).kind === 'extension'\n ? this.pluginRegistry.extension((op as { extensionKind?: string }).extensionKind ?? '')\n ?.plugin\n : typeof op === 'object' &&\n op !== null &&\n typeof (op as { kind?: unknown }).kind === 'string'\n ? this.pluginRegistry.ownerOf((op as { kind: string }).kind)\n : undefined;\n if (plugin && typeof op === 'object' && op !== null) {\n const previous = this.roomQueues.get(env.room) ?? Promise.resolve();\n const operation = previous.then(async () => {\n const context: ServerOpContext = {\n room: env.room as string,\n connectionId: env.from as string,\n backend: this.backend,\n backendPlugin: (key) => this.backend.getService?.(key),\n };\n const accepted = plugin.applyFanout\n ? await plugin.applyFanout(op as SyncOp, context)\n : (op as SyncOp);\n if (accepted) {\n this.relayToRoom(\n env.room as string,\n undefined,\n JSON.stringify({ from: env.from, op: accepted }),\n );\n }\n });\n this.roomQueues.set(\n env.room,\n operation.catch(() => {\n /* a broken backend must not wedge the room queue */\n }),\n );\n return;\n }\n if (!isFanoutOp(op)) return;\n const prevAudience = typeof env.prev === 'string' ? env.prev : undefined;\n const prevExisted = env.existed === true;\n this.deliverToRoom(env.room, undefined, env.from, op, prevAudience, prevExisted);\n }\n\n private async applyFanoutLayerOp(room: string, op: LayerOp): Promise<void> {\n const record = layerOpToRecord(op);\n const current = await this.getLayerRecord(room, record.id);\n if (current && !isNewerLayerRecord(record, current)) return;\n await this.applyLayerRecord(room, record);\n }\n\n close(): void {\n for (const connId of [...this.presenceLanes.keys()]) this.clearPresenceLanes(connId);\n this.fanoutUnsub();\n }\n}\n","import { applyOpToMap, type SyncOp, type LayerRecord } from '@fieldnotes/sync';\nimport type { CanvasElement } from '@fieldnotes/core';\nimport type { HubBackend } from './hub-backend';\n\nexport class MemoryHubBackend implements HubBackend {\n private rooms = new Map<string, Map<string, CanvasElement>>();\n private roomLayers = new Map<string, Map<string, LayerRecord>>();\n\n private room(id: string): Map<string, CanvasElement> {\n let r = this.rooms.get(id);\n if (!r) {\n r = new Map();\n this.rooms.set(id, r);\n }\n return r;\n }\n\n private layers(room: string): Map<string, LayerRecord> {\n let r = this.roomLayers.get(room);\n if (!r) {\n r = new Map();\n this.roomLayers.set(room, r);\n }\n return r;\n }\n\n async snapshot(room: string): Promise<CanvasElement[]> {\n return [...this.room(room).values()];\n }\n\n async get(room: string, id: string): Promise<CanvasElement | undefined> {\n return this.room(room).get(id);\n }\n\n async apply(room: string, op: SyncOp): Promise<void> {\n if (op.kind === 'clear') {\n // Clears elements only; layer records are a separate, longer-lived ledger.\n this.rooms.delete(room);\n return;\n }\n applyOpToMap(this.room(room), op);\n }\n\n async layerRecords(room: string): Promise<LayerRecord[]> {\n return [...this.layers(room).values()];\n }\n\n async getLayerRecord(room: string, id: string): Promise<LayerRecord | undefined> {\n return this.layers(room).get(id);\n }\n\n async applyLayerRecord(room: string, record: LayerRecord): Promise<void> {\n this.layers(room).set(record.id, record);\n }\n}\n","export interface HubFanout {\n publish(payload: string): void | Promise<void>;\n subscribe(handler: (payload: string) => void): () => void;\n close?(): void;\n}\n\nexport class InMemoryHubFanout implements HubFanout {\n private readonly handlers = new Set<(payload: string) => void>();\n\n publish(payload: string): void {\n for (const h of this.handlers) {\n try {\n h(payload);\n } catch {\n /* one throwing subscriber must not break the publish loop */\n }\n }\n }\n\n subscribe(handler: (payload: string) => void): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler);\n }\n}\n","import type { ServiceKey } from '@fieldnotes/core';\nimport type { ExtensionKind, PluginSnapshot, SyncOp, TypedExtensionOp } from '@fieldnotes/sync';\nimport type { HubBackend } from './hub-backend';\n\nexport interface ApplyResult {\n readonly accepted: SyncOp | null;\n readonly corrections: SyncOp[];\n readonly broadcast?: SyncOp[];\n readonly locality?: 'shared' | 'local';\n}\n\nexport interface ServerOpContext {\n readonly room: string;\n readonly connectionId: string;\n readonly userId?: string;\n readonly role?: string;\n readonly backend: HubBackend;\n backendPlugin<T>(key: ServiceKey<T>): T | undefined;\n}\n\nexport type ServerNext = (op: SyncOp, context: ServerOpContext) => Promise<ApplyResult>;\n\nexport interface ServerExtensionRegistry {\n register<TPayload>(\n kind: ExtensionKind<TPayload>,\n handler: (op: TypedExtensionOp<TPayload>, context: ServerOpContext) => Promise<ApplyResult>,\n ): void;\n}\n\nexport interface ServerSyncPlugin {\n readonly name: string;\n readonly ownedLegacyKinds?: readonly string[];\n readonly legacySnapshotKey?: string;\n process?(op: SyncOp, context: ServerOpContext, next: ServerNext): Promise<ApplyResult>;\n applyFanout?(op: SyncOp, context: ServerOpContext): Promise<SyncOp | null>;\n registerExtensionKinds?(registry: ServerExtensionRegistry): void;\n snapshot?(room: string, backend: HubBackend): Promise<PluginSnapshot | undefined>;\n filterSnapshot?(\n snapshot: PluginSnapshot,\n viewer: { userId?: string; role?: string },\n ): PluginSnapshot | null;\n}\n\ninterface ServerExtensionEntry {\n readonly kind: ExtensionKind<unknown>;\n readonly plugin: ServerSyncPlugin;\n readonly handler: (\n op: TypedExtensionOp<unknown>,\n context: ServerOpContext,\n ) => Promise<ApplyResult>;\n}\n\nexport class ServerPluginRegistry {\n private readonly byName = new Map<string, ServerSyncPlugin>();\n private readonly legacyOwners = new Map<string, ServerSyncPlugin>();\n private readonly extensions = new Map<string, ServerExtensionEntry>();\n\n constructor(plugins: readonly ServerSyncPlugin[]) {\n for (const plugin of plugins) this.register(plugin);\n }\n\n private register(plugin: ServerSyncPlugin): void {\n if (this.byName.has(plugin.name))\n throw new Error(`Server plugin \"${plugin.name}\" is duplicated`);\n this.byName.set(plugin.name, plugin);\n for (const kind of plugin.ownedLegacyKinds ?? []) {\n if (this.legacyOwners.has(kind)) throw new Error(`Sync op kind \"${kind}\" has two owners`);\n this.legacyOwners.set(kind, plugin);\n }\n plugin.registerExtensionKinds?.({\n register: <TPayload>(\n kind: ExtensionKind<TPayload>,\n handler: (op: TypedExtensionOp<TPayload>, context: ServerOpContext) => Promise<ApplyResult>,\n ) => {\n if (this.extensions.has(kind.extensionKind)) {\n throw new Error(`Extension kind \"${kind.extensionKind}\" has two owners`);\n }\n this.extensions.set(kind.extensionKind, {\n kind: kind as ExtensionKind<unknown>,\n plugin,\n handler: handler as ServerExtensionEntry['handler'],\n });\n },\n });\n }\n\n get plugins(): readonly ServerSyncPlugin[] {\n return [...this.byName.values()];\n }\n\n ownerOf(kind: string): ServerSyncPlugin | undefined {\n return this.legacyOwners.get(kind);\n }\n\n extension(extensionKind: string): ServerExtensionEntry | undefined {\n return this.extensions.get(extensionKind);\n }\n}\n\nexport type { PluginSnapshot } from '@fieldnotes/sync';\n","export const DEFAULT_MAX_MESSAGE_BYTES = 1024 * 1024;\nexport const DEFAULT_MAX_JSON_DEPTH = 32;\nexport const DEFAULT_MAX_PENDING_AUTH_MESSAGES = 100;\nexport const DEFAULT_MAX_PENDING_AUTH_BYTES = 2 * 1024 * 1024;\nexport const DEFAULT_MESSAGES_PER_SECOND = 120;\nexport const DEFAULT_MESSAGE_BURST = 240;\nexport const DEFAULT_PRESENCE_THROTTLE_MS = 50;\n/** Per-connection presence throttle lanes, INCLUDING the reserved fallback lane. */\nexport const DEFAULT_MAX_PRESENCE_LANES = 16;\n\nexport function hasJsonDepthAtMost(message: string, maxDepth: number): boolean {\n let depth = 0;\n let inString = false;\n let escaped = false;\n\n for (const char of message) {\n if (inString) {\n if (escaped) escaped = false;\n else if (char === '\\\\') escaped = true;\n else if (char === '\"') inString = false;\n continue;\n }\n if (char === '\"') inString = true;\n else if (char === '{' || char === '[') {\n depth += 1;\n if (depth > maxDepth) return false;\n } else if (char === '}' || char === ']') {\n depth -= 1;\n if (depth < 0) return false;\n }\n }\n\n return depth === 0 && !inString;\n}\n\nexport class MessageRateLimiter {\n private tokens: number;\n private updatedAt: number;\n\n constructor(\n private readonly ratePerSecond: number,\n private readonly burst: number,\n now = Date.now(),\n ) {\n this.tokens = burst;\n this.updatedAt = now;\n }\n\n take(now = Date.now()): boolean {\n const elapsedSeconds = Math.max(0, now - this.updatedAt) / 1000;\n this.tokens = Math.min(this.burst, this.tokens + elapsedSeconds * this.ratePerSecond);\n this.updatedAt = now;\n if (this.tokens < 1) return false;\n this.tokens -= 1;\n return true;\n }\n}\n","import { WebSocketServer, type RawData } from 'ws';\nimport type { Server } from 'http';\nimport { SyncHub } from './sync-hub';\nimport type { HubBackend } from './hub-backend';\nimport type { HubFanout } from './hub-fanout';\nimport type { Authenticate } from './authenticate';\nimport type { Authorize, AuthorizeLayer, CanRead } from './authorize';\nimport type { ServerSyncPlugin } from './sync-plugin';\nimport { startHeartbeat } from './heartbeat';\nimport {\n DEFAULT_MAX_JSON_DEPTH,\n DEFAULT_MAX_MESSAGE_BYTES,\n DEFAULT_MAX_PENDING_AUTH_BYTES,\n DEFAULT_MAX_PENDING_AUTH_MESSAGES,\n DEFAULT_MESSAGE_BURST,\n DEFAULT_MESSAGES_PER_SECOND,\n DEFAULT_PRESENCE_THROTTLE_MS,\n MessageRateLimiter,\n} from './resource-limits';\nimport { DEFAULT_SHUTDOWN_GRACE_MS, drainWebSocketServer } from './shutdown';\n\nexport interface CreateSyncServerOptions {\n port?: number;\n server?: Server;\n backend?: HubBackend;\n fanout?: HubFanout;\n instanceId?: string;\n authenticate?: Authenticate;\n authorize?: Authorize;\n authorizeLayer?: AuthorizeLayer;\n plugins?: readonly ServerSyncPlugin[];\n canRead?: CanRead;\n heartbeatIntervalMs?: number;\n maxMessageBytes?: number;\n maxJsonDepth?: number;\n maxPendingAuthMessages?: number;\n maxPendingAuthBytes?: number;\n messagesPerSecond?: number;\n messageBurst?: number;\n presenceThrottleMs?: number;\n maxPresenceLanes?: number;\n shutdownGraceMs?: number;\n}\n\nfunction rawDataByteLength(data: RawData): number {\n if (Array.isArray(data)) return data.reduce((total, chunk) => total + chunk.byteLength, 0);\n return data.byteLength;\n}\n\nexport function createSyncServer(options: CreateSyncServerOptions = {}): {\n hub: SyncHub;\n wss: WebSocketServer;\n close: () => Promise<void>;\n} {\n const shutdownGraceMs = options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;\n if (!Number.isFinite(shutdownGraceMs) || shutdownGraceMs < 0) {\n throw new RangeError('shutdownGraceMs must be a non-negative finite number');\n }\n const hub = new SyncHub({\n backend: options.backend,\n fanout: options.fanout,\n instanceId: options.instanceId,\n authorize: options.authorize,\n authorizeLayer: options.authorizeLayer,\n plugins: options.plugins,\n canRead: options.canRead,\n maxJsonDepth: options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH,\n presenceThrottleMs: options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS,\n maxPresenceLanes: options.maxPresenceLanes,\n });\n const maxMessageBytes = options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;\n const wss = options.server\n ? new WebSocketServer({ server: options.server, maxPayload: maxMessageBytes })\n : new WebSocketServer({ port: options.port ?? 0, maxPayload: maxMessageBytes });\n const heartbeat = startHeartbeat(wss, options.heartbeatIntervalMs ?? 30000);\n let shuttingDown = false;\n let closePromise: Promise<void> | undefined;\n let counter = 0;\n wss.on('connection', (ws, req) => {\n if (shuttingDown) {\n ws.close(1001, 'server shutting down');\n return;\n }\n heartbeat.track(ws);\n // Protocol/parser failures (including maxPayload) close the peer. Consuming the socket-level\n // error keeps hostile input from becoming an uncaught process error.\n ws.on('error', () => undefined);\n const url = new URL(req.url ?? '', 'http://localhost');\n const room = url.searchParams.get('room');\n if (!room) {\n ws.close(4400, 'room required');\n return;\n }\n const connId = `c${++counter}-${Math.random().toString(36).slice(2, 8)}`;\n\n let state: 'pending' | 'ready' | 'rejected' = 'pending';\n let closed = false;\n let admitted = false;\n const queue: string[] = [];\n let queuedBytes = 0;\n const maxPendingAuthMessages =\n options.maxPendingAuthMessages ?? DEFAULT_MAX_PENDING_AUTH_MESSAGES;\n const maxPendingAuthBytes = options.maxPendingAuthBytes ?? DEFAULT_MAX_PENDING_AUTH_BYTES;\n const limiter = new MessageRateLimiter(\n options.messagesPerSecond ?? DEFAULT_MESSAGES_PER_SECOND,\n options.messageBurst ?? DEFAULT_MESSAGE_BURST,\n );\n\n const send = (m: string) => {\n try {\n ws.send(m);\n } catch {\n /* socket closed mid-send */\n }\n };\n\n ws.on('message', (data) => {\n if (state === 'rejected') return;\n const messageBytes = rawDataByteLength(data);\n if (messageBytes > maxMessageBytes) {\n state = 'rejected';\n ws.close(1009, 'message too large');\n return;\n }\n if (!limiter.take()) {\n state = 'rejected';\n ws.close(4408, 'rate limit exceeded');\n return;\n }\n const msg = String(data);\n if (state === 'pending') {\n if (\n queue.length >= maxPendingAuthMessages ||\n queuedBytes + messageBytes > maxPendingAuthBytes\n ) {\n state = 'rejected';\n ws.close(4408, 'authentication queue limit exceeded');\n return;\n }\n queue.push(msg);\n queuedBytes += messageBytes;\n return;\n }\n void hub.handleMessage(connId, msg).catch((err) => console.error('[sync-server]', err));\n });\n ws.on('close', () => {\n closed = true;\n if (admitted) hub.removeConnection(connId);\n });\n\n Promise.resolve(options.authenticate ? options.authenticate({ req, room }) : { userId: connId })\n .then((result) => {\n if (closed || state === 'rejected' || shuttingDown) return;\n if (!result) {\n state = 'rejected';\n ws.close(4401, 'unauthorized');\n return;\n }\n state = 'ready';\n admitted = true;\n hub.addConnection({ id: connId, room, userId: result.userId, role: result.role, send });\n for (const m of queue) {\n void hub.handleMessage(connId, m).catch((err) => console.error('[sync-server]', err));\n }\n queue.length = 0;\n queuedBytes = 0;\n })\n .catch(() => {\n if (!closed && !shuttingDown) {\n state = 'rejected';\n ws.close(4401, 'unauthorized');\n }\n });\n });\n const close = (): Promise<void> => {\n if (closePromise) return closePromise;\n shuttingDown = true;\n closePromise = drainWebSocketServer(wss, shutdownGraceMs).then(() => {\n heartbeat.stop();\n hub.close();\n });\n return closePromise;\n };\n\n return {\n hub,\n wss,\n close,\n };\n}\n","export interface HeartbeatSocket {\n ping(): void;\n terminate(): void;\n on(event: 'pong', listener: () => void): void;\n}\nexport interface HeartbeatServer {\n clients: Set<HeartbeatSocket>;\n}\nexport interface Heartbeat {\n track(ws: HeartbeatSocket): void;\n stop(): void;\n}\n\nexport function startHeartbeat(wss: HeartbeatServer, intervalMs: number): Heartbeat {\n if (intervalMs <= 0) {\n return {\n track: () => {\n /* disabled */\n },\n stop: () => {\n /* disabled */\n },\n };\n }\n const alive = new WeakMap<HeartbeatSocket, boolean>();\n const track = (ws: HeartbeatSocket): void => {\n alive.set(ws, true);\n ws.on('pong', () => alive.set(ws, true));\n };\n const interval = setInterval(() => {\n for (const ws of wss.clients) {\n try {\n if (alive.get(ws) === false) {\n ws.terminate();\n continue;\n }\n alive.set(ws, false);\n ws.ping();\n } catch {\n /* socket dying mid-tick — skip it */\n }\n }\n }, intervalMs);\n return { track, stop: () => clearInterval(interval) };\n}\n","export const DEFAULT_SHUTDOWN_GRACE_MS = 5000;\n\nexport interface ShutdownSocket {\n close(code?: number, reason?: string): void;\n terminate(): void;\n}\n\nexport interface ShutdownServer {\n clients: Set<ShutdownSocket>;\n close(callback: (error?: Error) => void): void;\n}\n\n/** Stop admission, ask connected peers to leave, then forcefully reap stragglers. */\nexport function drainWebSocketServer(wss: ShutdownServer, graceMs: number): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (): void => {\n if (settled) return;\n settled = true;\n clearTimeout(forceTimer);\n resolve();\n };\n const forceTimer = setTimeout(() => {\n for (const ws of wss.clients) {\n try {\n ws.terminate();\n } catch {\n /* socket already failed while shutdown was in progress */\n }\n }\n finish();\n }, graceMs);\n\n // Calling close first prevents new connections. Its callback runs once all clients have left.\n wss.close(() => finish());\n for (const ws of wss.clients) {\n try {\n ws.close(1001, 'server shutting down');\n } catch {\n try {\n ws.terminate();\n } catch {\n /* socket already closed */\n }\n }\n }\n });\n}\n"],"mappings":"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","names":["import_sync"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/sync-hub.ts","../src/memory-hub-backend.ts","../src/hub-fanout.ts","../src/sync-plugin.ts","../src/resource-limits.ts","../src/create-sync-server.ts","../src/heartbeat.ts","../src/shutdown.ts"],"sourcesContent":["export { SyncHub } from './sync-hub';\nexport type { SyncHubOptions, Connection } from './sync-hub';\nexport { MemoryHubBackend } from './memory-hub-backend';\nexport type { HubBackend } from './hub-backend';\nexport { createSyncServer } from './create-sync-server';\nexport type { CreateSyncServerOptions } from './create-sync-server';\nexport { InMemoryHubFanout } from './hub-fanout';\nexport type { HubFanout } from './hub-fanout';\nexport type { AuthInfo, AuthResult, Authenticate } from './authenticate';\nexport type {\n Authorize,\n AuthorizeContext,\n AuthorizeLayer,\n AuthorizeLayerContext,\n OwnedElement,\n ReadContext,\n CanRead,\n} from './authorize';\nexport { startHeartbeat } from './heartbeat';\nexport type { Heartbeat, HeartbeatSocket, HeartbeatServer } from './heartbeat';\nexport type {\n ServerSyncPlugin,\n ApplyResult,\n ServerOpContext,\n ServerNext,\n PluginSnapshot,\n ServerExtensionRegistry,\n} from './sync-plugin';\n","import {\n createCurrentCapabilities,\n createLegacyCapabilities,\n parseEnvelope,\n isValidElement,\n isValidWireElement,\n isNewerLayerRecord,\n translateOpForPeer,\n type LayerRecord,\n type WireSyncElement,\n type WireSyncOp,\n type WireSyncEnvelope,\n type SyncCapabilities,\n type PluginSnapshot,\n} from '@fieldnotes/sync';\nimport { getDefaultElementRegistry } from '@fieldnotes/core';\nimport type { ElementRegistry } from '@fieldnotes/core';\nimport { MemoryHubBackend } from './memory-hub-backend';\nimport { InMemoryHubFanout, type HubFanout } from './hub-fanout';\nimport type { HubBackend } from './hub-backend';\nimport type { Authorize, AuthorizeLayer, CanRead, OwnedElement } from './authorize';\nimport { ServerPluginRegistry } from './sync-plugin';\nimport type { ApplyResult, ServerOpContext, ServerSyncPlugin } from './sync-plugin';\nimport {\n DEFAULT_MAX_JSON_DEPTH,\n DEFAULT_MAX_PRESENCE_LANES,\n DEFAULT_PRESENCE_THROTTLE_MS,\n hasJsonDepthAtMost,\n} from './resource-limits';\n\nexport interface Connection {\n id: string;\n room: string;\n userId?: string;\n role?: string;\n send(message: string): void;\n}\n\nexport interface SyncHubOptions {\n backend?: HubBackend;\n fanout?: HubFanout;\n instanceId?: string;\n authorize?: Authorize;\n authorizeLayer?: AuthorizeLayer;\n plugins?: readonly ServerSyncPlugin[];\n canRead?: CanRead;\n maxJsonDepth?: number;\n presenceThrottleMs?: number;\n maxPresenceLanes?: number;\n /** Registry used to translate extension elements for legacy peers. */\n elementRegistry?: ElementRegistry;\n}\n\nconst HUB_FROM = 'hub';\nfunction generateInstanceId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n return `i-${Math.random().toString(36).slice(2)}`;\n}\n\nfunction isFanoutOp(\n op: unknown,\n): op is Extract<WireSyncOp, { kind: 'upsert' | 'remove' | 'clear' }> {\n if (typeof op !== 'object' || op === null) return false;\n const o = op as { kind?: unknown; element?: unknown; id?: unknown };\n if (o.kind === 'upsert') return isValidWireElement(o.element);\n if (o.kind === 'remove') return typeof o.id === 'string';\n return o.kind === 'clear';\n}\n\ntype LayerOp = Extract<WireSyncOp, { kind: 'layer-upsert' | 'layer-remove' }>;\n\ninterface PresenceLane {\n lastSentAt: number | undefined;\n pending: { data: unknown; timer: ReturnType<typeof setTimeout> } | null;\n}\n\nconst FALLBACK_PRESENCE_LANE = '';\nconst MAX_PRESENCE_LANE_LENGTH = 64;\n\nfunction presenceLaneOf(data: unknown): string {\n if (typeof data !== 'object' || data === null) return FALLBACK_PRESENCE_LANE;\n const kind = (data as { kind?: unknown }).kind;\n if (typeof kind !== 'string' || kind.length === 0 || kind.length > MAX_PRESENCE_LANE_LENGTH) {\n return FALLBACK_PRESENCE_LANE;\n }\n return kind;\n}\n\nfunction isLayerOp(op: unknown): op is LayerOp {\n if (typeof op !== 'object' || op === null) return false;\n const k = (op as { kind?: unknown }).kind;\n // Shape is re-validated by parseEnvelope on the serial path; fanout payloads\n // come from a sibling hub that already validated them.\n return k === 'layer-upsert' || k === 'layer-remove';\n}\n\nfunction layerOpToRecord(op: LayerOp): LayerRecord {\n return op.kind === 'layer-upsert'\n ? { id: op.layer.id, version: op.version, editor: op.editor, definition: op.layer }\n : { id: op.id, version: op.version, editor: op.editor };\n}\n\nfunction layerRecordToOp(record: LayerRecord): LayerOp {\n return record.definition\n ? {\n kind: 'layer-upsert',\n layer: record.definition,\n version: record.version,\n editor: record.editor,\n }\n : { kind: 'layer-remove', id: record.id, version: record.version, editor: record.editor };\n}\n\n/**\n * Peers sharing a capability profile receive byte-identical frames, so a\n * relay encodes each (sender, op) once per profile instead of once per recipient.\n */\ntype EncodedFrames = Map<string, string | null>;\n\nfunction capabilityProfile(capabilities: SyncCapabilities): string {\n return JSON.stringify([capabilities.elementEnvelope, capabilities.extensionKinds]);\n}\n\nfunction isPresenceOp(\n op: unknown,\n): op is { kind: 'presence'; data: unknown } | { kind: 'presence-leave' } {\n if (typeof op !== 'object' || op === null) return false;\n const k = (op as { kind?: unknown }).kind;\n return k === 'presence' || k === 'presence-leave';\n}\n\nexport class SyncHub {\n private readonly backend: HubBackend;\n private readonly conns = new Map<string, Connection>();\n private readonly rooms = new Map<string, Set<string>>(); // room → connIds\n private readonly roomQueues = new Map<string, Promise<void>>(); // room → serial tail\n private readonly presenceConnections = new Set<string>();\n private readonly instanceId: string;\n private readonly fanout: HubFanout;\n private readonly fanoutUnsub: () => void;\n private readonly authorize?: Authorize;\n private readonly authorizeLayer?: AuthorizeLayer;\n private readonly pluginRegistry: ServerPluginRegistry;\n private readonly elementRegistry: ElementRegistry;\n private readonly peerCapabilities = new Map<string, SyncCapabilities>();\n private readonly canRead?: CanRead;\n private readonly memoryLayers = new Map<string, Map<string, LayerRecord>>();\n private readonly maxJsonDepth: number;\n private readonly presenceThrottleMs: number;\n private readonly maxPresenceLanes: number;\n /**\n * Presence throttle state keyed by connection, then by lane. A lane is the\n * payload's `kind` (a non-empty string of at most 64 chars) or the reserved\n * fallback lane `''`, so a rapid stream of one kind (awareness cursors) can\n * never replace a pending frame of another kind (a ping, a path `cleared`).\n * Within a lane the newest payload wins. The lane count per connection is\n * capped by `maxPresenceLanes`, counting the fallback lane, so a client\n * cannot mint timers by varying `kind`.\n */\n private readonly presenceLanes = new Map<string, Map<string, PresenceLane>>();\n\n constructor(options: SyncHubOptions = {}) {\n this.backend = options.backend ?? new MemoryHubBackend();\n this.pluginRegistry = new ServerPluginRegistry(options.plugins ?? []);\n this.elementRegistry = options.elementRegistry ?? getDefaultElementRegistry();\n this.instanceId = options.instanceId ?? generateInstanceId();\n this.fanout = options.fanout ?? new InMemoryHubFanout();\n this.authorize = options.authorize;\n this.authorizeLayer = options.authorizeLayer;\n this.canRead = options.canRead;\n this.maxJsonDepth = options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH;\n this.presenceThrottleMs = options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS;\n const maxPresenceLanes = options.maxPresenceLanes ?? DEFAULT_MAX_PRESENCE_LANES;\n if (!Number.isFinite(maxPresenceLanes) || maxPresenceLanes < 1) {\n throw new RangeError('maxPresenceLanes must be a finite number of at least 1');\n }\n this.maxPresenceLanes = Math.floor(maxPresenceLanes);\n this.fanoutUnsub = this.fanout.subscribe((payload) => this.onFanout(payload));\n }\n\n addConnection(conn: Connection): void {\n this.conns.set(conn.id, conn);\n let set = this.rooms.get(conn.room);\n if (!set) {\n set = new Set();\n this.rooms.set(conn.room, set);\n }\n set.add(conn.id);\n }\n\n removeConnection(connId: string): void {\n const conn = this.conns.get(connId);\n if (!conn) return;\n this.conns.delete(connId);\n this.peerCapabilities.delete(connId);\n const room = conn.room;\n const hadPresence = this.presenceConnections.delete(connId);\n this.clearPresenceLanes(connId);\n const members = this.rooms.get(room);\n if (members) {\n members.delete(connId);\n if (members.size === 0) {\n this.rooms.delete(room);\n this.roomQueues.delete(room);\n }\n }\n if (hadPresence) this.broadcastLeave(room, conn.id);\n }\n\n roomCount(): number {\n return this.rooms.size;\n }\n\n /**\n * Broadcasts ephemeral server-owned presence data to every connection in a room.\n * The returned count covers successful delivery on this hub instance only; configured fan-out\n * forwards the same event to other instances on a best-effort basis.\n */\n broadcastPresence<T>(room: string, data: T): number {\n const op = { kind: 'presence' as const, data };\n const sent = this.relayToRoom(room, undefined, JSON.stringify({ from: HUB_FROM, op }));\n this.safePublish(JSON.stringify({ o: this.instanceId, room, from: HUB_FROM, op }));\n return sent;\n }\n\n handleMessage(connId: string, message: string): Promise<void> {\n const conn = this.conns.get(connId);\n if (!conn) return Promise.resolve();\n if (!hasJsonDepthAtMost(message, this.maxJsonDepth)) return Promise.resolve();\n const env = parseEnvelope(message);\n if (!env) return Promise.resolve();\n if (env.op.kind === 'capabilities') {\n this.peerCapabilities.set(conn.id, env.op.capabilities);\n this.sendToConnection(conn, HUB_FROM, {\n kind: 'capabilities',\n capabilities: createCurrentCapabilities(this.pluginRegistry.extensionKinds),\n });\n return Promise.resolve();\n }\n if (env.op.kind === 'presence') {\n this.schedulePresence(conn, env.op.data); // off-queue, throttled independently\n return Promise.resolve();\n }\n const room = conn.room;\n // The per-room serial queue is the single total-order authority: ops apply in arrival order\n // (arrival-order LWW — no per-element seq; see D3 / TD-12). Different rooms run independently.\n const prev = this.roomQueues.get(room) ?? Promise.resolve();\n const operation = prev.then(() => this.process(conn, env));\n this.roomQueues.set(\n room,\n operation.catch(() => {\n // Recover only the internal tail so one failed message never wedges the room queue.\n // The caller still receives the operation rejection for observability.\n }),\n );\n return operation;\n }\n\n private async process(conn: Connection, env: WireSyncEnvelope): Promise<void> {\n let op = env.op;\n if (op.kind === 'upsert') {\n const element = this.normalizeElement(op.element);\n if (!element) return;\n op = { ...op, element };\n }\n if (op.kind === 'request-snapshot') {\n const all = this.normalizeElements(await this.backend.snapshot(conn.room));\n const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;\n // Layer records are presentation-only and carry no element bytes, so no\n // audience filter applies; the field is omitted while a room has never\n // used layer sync, keeping snapshot frames byte-identical to before.\n const layers = await this.getLayerRecords(conn.room);\n const snapshotOp: Record<string, unknown> = {\n kind: 'snapshot',\n to: env.from,\n elements,\n };\n if (layers.length > 0) snapshotOp['layers'] = layers;\n const extensions: Record<string, PluginSnapshot> = {};\n for (const plugin of this.pluginRegistry.plugins) {\n let snapshot = await plugin.snapshot?.(conn.room, this.backend);\n if (!snapshot) continue;\n if (plugin.filterSnapshot) {\n snapshot =\n plugin.filterSnapshot(snapshot, { userId: conn.userId, role: conn.role }) ?? undefined;\n }\n if (!snapshot) continue;\n if (plugin.legacySnapshotKey) snapshotOp[plugin.legacySnapshotKey] = snapshot.data;\n else extensions[plugin.name] = snapshot;\n }\n if (Object.keys(extensions).length > 0) snapshotOp['extensions'] = extensions;\n this.sendToConnection(conn, HUB_FROM, snapshotOp as WireSyncOp);\n } else if (op.kind === 'layer-upsert' || op.kind === 'layer-remove') {\n await this.processLayerOp(conn, op);\n } else if (op.kind === 'extension') {\n const entry = this.pluginRegistry.extension(op.extensionKind);\n if (!entry || !entry.kind.codec.validate(op.payload)) return;\n await this.deliverPluginResult(conn, await entry.handler(op, this.pluginContext(conn)));\n } else if (this.pluginRegistry.ownerOf(op.kind)) {\n const owner = this.pluginRegistry.ownerOf(op.kind);\n if (!owner?.process) return;\n const result = await owner.process(op, this.pluginContext(conn), async () => ({\n accepted: null,\n corrections: [],\n }));\n await this.deliverPluginResult(conn, result);\n } else if (op.kind === 'upsert' || op.kind === 'remove' || op.kind === 'clear') {\n const id = op.kind === 'upsert' ? op.element.id : op.kind === 'remove' ? op.id : undefined;\n const needCurrent = (this.authorize || this.canRead) && id !== undefined;\n const storedCurrent = needCurrent ? await this.backend.get(conn.room, id) : undefined;\n const current = storedCurrent\n ? (this.normalizeElement(storedCurrent) ?? undefined)\n : undefined;\n\n let outboundOp: WireSyncOp = op;\n if (this.authorize) {\n const allowed = await this.authorize({\n userId: conn.userId,\n role: conn.role,\n room: conn.room,\n op,\n currentElement: current,\n });\n if (!allowed) {\n await this.sendCorrection(conn, env.from, op, current);\n return;\n }\n if (op.kind === 'upsert') {\n const ownerId = current?.ownerId ?? conn.userId;\n const stampedElement: OwnedElement = { ...op.element, ownerId };\n outboundOp = { kind: 'upsert', element: stampedElement };\n }\n }\n\n const prevExisted = current !== undefined;\n const prevAudience = current?.audience;\n const result = await this.runCorePlugins(conn, outboundOp);\n for (const correction of result.corrections) {\n this.sendToConnection(conn, HUB_FROM, correction);\n }\n const accepted = result.accepted;\n if (\n accepted &&\n (accepted.kind === 'upsert' || accepted.kind === 'remove' || accepted.kind === 'clear')\n ) {\n if (result.locality !== 'local') {\n await this.fanout.publish(\n JSON.stringify({\n o: this.instanceId,\n room: conn.room,\n from: conn.id,\n op: accepted,\n prev: prevAudience,\n existed: prevExisted,\n }),\n );\n }\n this.deliverToRoom(conn.room, conn.id, conn.id, accepted, prevAudience, prevExisted);\n }\n for (const broadcast of result.broadcast ?? []) {\n await this.publishPluginOp(conn, broadcast, result.locality);\n }\n }\n // 'snapshot' from a client → ignored\n }\n\n private pluginContext(conn: Connection): ServerOpContext {\n return {\n room: conn.room,\n connectionId: conn.id,\n userId: conn.userId,\n role: conn.role,\n backend: this.backend,\n backendPlugin: (key) => this.backend.getService?.(key),\n };\n }\n\n private async runCorePlugins(conn: Connection, op: WireSyncOp): Promise<ApplyResult> {\n const middleware = this.pluginRegistry.plugins.filter((plugin) => plugin.process);\n const context = this.pluginContext(conn);\n const dispatch = async (index: number, current: WireSyncOp): Promise<ApplyResult> => {\n const plugin = middleware[index];\n if (!plugin?.process) {\n await this.backend.apply(conn.room, current);\n return { accepted: current, corrections: [] };\n }\n let called = false;\n return plugin.process(current, context, async (nextOp, nextContext) => {\n if (called) throw new Error(`Server plugin \"${plugin.name}\" called next() more than once`);\n if (nextContext !== context) {\n throw new Error(`Server plugin \"${plugin.name}\" replaced the operation context`);\n }\n called = true;\n return dispatch(index + 1, nextOp);\n });\n };\n return dispatch(0, op);\n }\n\n private async deliverPluginResult(conn: Connection, result: ApplyResult): Promise<void> {\n for (const correction of result.corrections) {\n this.sendToConnection(conn, HUB_FROM, correction);\n }\n if (result.accepted) await this.publishPluginOp(conn, result.accepted, result.locality);\n for (const broadcast of result.broadcast ?? []) {\n await this.publishPluginOp(conn, broadcast, result.locality);\n }\n }\n\n private async publishPluginOp(\n conn: Connection,\n op: WireSyncOp,\n locality: ApplyResult['locality'],\n ): Promise<void> {\n if (locality !== 'local') {\n await this.fanout.publish(\n JSON.stringify({ o: this.instanceId, room: conn.room, from: conn.id, op }),\n );\n }\n this.relayOpToRoom(conn.room, conn.id, conn.id, op);\n }\n\n /**\n * Applies a layer-definition edit on the room's serial queue. Convergence is\n * last-writer-wins under the deterministic (version, editor) ordering — the\n * same rule every client applies — so arrival order never decides a race. A\n * stale or denied edit is answered with an authoritative correction to the\n * sender only.\n */\n private async processLayerOp(conn: Connection, op: LayerOp): Promise<void> {\n const record = layerOpToRecord(op);\n const current = await this.getLayerRecord(conn.room, record.id);\n if (this.authorizeLayer) {\n const allowed = await this.authorizeLayer({\n userId: conn.userId,\n role: conn.role,\n room: conn.room,\n op,\n currentRecord: current,\n });\n if (!allowed) {\n // Revert the sender to the room's record; a tombstone when there is\n // none, so the denied local edit disappears everywhere consistently.\n const correction = current ?? { id: record.id, version: record.version, editor: HUB_FROM };\n this.sendToConnection(conn, HUB_FROM, layerRecordToOp(correction));\n return;\n }\n }\n if (current && !isNewerLayerRecord(record, current)) {\n // Stale under (version, editor): converge the sender, do not broadcast.\n this.sendToConnection(conn, HUB_FROM, layerRecordToOp(current));\n return;\n }\n await this.applyLayerRecord(conn.room, record);\n await this.fanout.publish(\n JSON.stringify({ o: this.instanceId, room: conn.room, from: conn.id, op }),\n );\n this.relayOpToRoom(conn.room, conn.id, conn.id, op);\n }\n\n private layerBackend(): Required<\n Pick<HubBackend, 'layerRecords' | 'getLayerRecord' | 'applyLayerRecord'>\n > | null {\n const { layerRecords, getLayerRecord, applyLayerRecord } = this.backend;\n if (!layerRecords || !getLayerRecord || !applyLayerRecord) return null;\n return {\n layerRecords: layerRecords.bind(this.backend),\n getLayerRecord: getLayerRecord.bind(this.backend),\n applyLayerRecord: applyLayerRecord.bind(this.backend),\n };\n }\n\n private async getLayerRecords(room: string): Promise<LayerRecord[]> {\n const backend = this.layerBackend();\n if (backend) return backend.layerRecords(room);\n return [...(this.memoryLayers.get(room)?.values() ?? [])];\n }\n\n private async getLayerRecord(room: string, id: string): Promise<LayerRecord | undefined> {\n const backend = this.layerBackend();\n if (backend) return backend.getLayerRecord(room, id);\n return this.memoryLayers.get(room)?.get(id);\n }\n\n private async applyLayerRecord(room: string, record: LayerRecord): Promise<void> {\n const backend = this.layerBackend();\n if (backend) {\n await backend.applyLayerRecord(room, record);\n return;\n }\n let map = this.memoryLayers.get(room);\n if (!map) {\n map = new Map();\n this.memoryLayers.set(room, map);\n }\n map.set(record.id, record);\n }\n\n private mayRead(conn: Connection, audience: string | undefined): boolean {\n if (!this.canRead) return true;\n return this.canRead({ userId: conn.userId, role: conn.role, room: conn.room, audience });\n }\n\n private safePublish(payload: string): void {\n try {\n void Promise.resolve(this.fanout.publish(payload)).catch(() => {\n /* presence is ephemeral; a broken publisher must not create an unhandled rejection */\n });\n } catch {\n /* a broken fanout publisher must not break the un-queued presence relay */\n }\n }\n\n private relayToRoom(room: string, excludeId: string | undefined, message: string): number {\n const members = this.rooms.get(room);\n if (!members) return 0;\n let sent = 0;\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n try {\n conn.send(message);\n sent += 1;\n } catch {\n /* a throwing socket must not break the relay loop */\n }\n }\n return sent;\n }\n\n /**\n * Translates `op` for the peer and sends it. Returns false when the op is\n * lossy for this peer (no legacy encoding) or the socket throws; neither\n * may reject the room operation that produced it.\n */\n private sendToConnection(\n conn: Connection,\n from: string,\n op: WireSyncOp,\n encoded?: EncodedFrames,\n ): boolean {\n const capabilities = this.peerCapabilities.get(conn.id) ?? createLegacyCapabilities();\n const profile = encoded ? capabilityProfile(capabilities) : undefined;\n let message = profile === undefined ? undefined : encoded?.get(profile);\n if (message === undefined) {\n message = this.encodeForPeer(from, op, capabilities);\n if (profile !== undefined) encoded?.set(profile, message);\n }\n if (message === null) return false;\n try {\n conn.send(message);\n return true;\n } catch {\n return false;\n }\n }\n\n /** Returns the wire frame for `op` translated for `capabilities`, or null when lossy. */\n private encodeForPeer(\n from: string,\n op: WireSyncOp,\n capabilities: SyncCapabilities,\n ): string | null {\n try {\n const translated = translateOpForPeer(\n op,\n capabilities,\n this.elementRegistry,\n this.pluginRegistry.extensionDefinitions,\n );\n return JSON.stringify({ from, op: translated });\n } catch {\n return null;\n }\n }\n\n /**\n * Normalize registered legacy wire elements before authorization, storage,\n * and relay. A legacy type this hub has no adapter for is forwarded and\n * stored verbatim — the hub is a relay, and the peers decide whether they\n * understand it — so a hub deployed without domain adapters never erases\n * the room's existing elements. Only a malformed registered element is dropped.\n */\n private normalizeElement(element: WireSyncElement): OwnedElement | null {\n if (isValidElement(element)) return element;\n const adapter = this.elementRegistry.getAdapterByLegacyType(element.type);\n if (!adapter) return element;\n try {\n const raw = Object.fromEntries(Object.entries(element));\n const envelope = adapter.decodeLegacy(raw);\n if (!adapter.validateEnvelope(envelope)) return null;\n return {\n ...envelope,\n ...(typeof raw['audience'] === 'string' ? { audience: raw['audience'] } : {}),\n ...(typeof raw['ownerId'] === 'string' ? { ownerId: raw['ownerId'] } : {}),\n };\n } catch {\n return null;\n }\n }\n\n private normalizeElements(elements: readonly WireSyncElement[]): OwnedElement[] {\n const normalized: OwnedElement[] = [];\n for (const element of elements) {\n const runtime = this.normalizeElement(element);\n if (runtime) normalized.push(runtime);\n }\n return normalized;\n }\n\n private relayOpToRoom(\n room: string,\n excludeId: string | undefined,\n from: string,\n op: WireSyncOp,\n ): number {\n const members = this.rooms.get(room);\n if (!members) return 0;\n let sent = 0;\n const encoded: EncodedFrames = new Map();\n for (const connectionId of members) {\n if (connectionId === excludeId) continue;\n const conn = this.conns.get(connectionId);\n if (!conn) continue;\n // A lossy translation is skipped for this peer without blocking compatible peers.\n if (this.sendToConnection(conn, from, op, encoded)) sent += 1;\n }\n return sent;\n }\n\n private broadcastClientPresence(conn: Connection, data: unknown): void {\n this.presenceConnections.add(conn.id);\n const message = JSON.stringify({ from: conn.id, op: { kind: 'presence', data } });\n this.relayToRoom(conn.room, conn.id, message);\n this.safePublish(\n JSON.stringify({\n o: this.instanceId,\n room: conn.room,\n from: conn.id,\n op: { kind: 'presence', data },\n }),\n );\n }\n\n private clearPresenceLanes(connId: string): void {\n const lanes = this.presenceLanes.get(connId);\n if (!lanes) return;\n for (const lane of lanes.values()) {\n if (lane.pending) clearTimeout(lane.pending.timer);\n }\n this.presenceLanes.delete(connId);\n }\n\n private schedulePresence(conn: Connection, data: unknown): void {\n if (this.presenceThrottleMs <= 0) {\n this.broadcastClientPresence(conn, data);\n return;\n }\n const lane = this.presenceLaneFor(conn.id, presenceLaneOf(data));\n const now = Date.now();\n if (lane.lastSentAt === undefined || now - lane.lastSentAt >= this.presenceThrottleMs) {\n if (lane.pending) {\n clearTimeout(lane.pending.timer);\n lane.pending = null;\n }\n lane.lastSentAt = now;\n this.broadcastClientPresence(conn, data);\n return;\n }\n if (lane.pending) {\n lane.pending.data = data;\n return;\n }\n const timer = setTimeout(\n () => {\n const pending = lane.pending;\n lane.pending = null;\n if (!pending || !this.conns.has(conn.id)) return;\n lane.lastSentAt = Date.now();\n this.broadcastClientPresence(conn, pending.data);\n },\n this.presenceThrottleMs - (now - lane.lastSentAt),\n );\n lane.pending = { data, timer };\n }\n\n private presenceLaneFor(connId: string, requested: string): PresenceLane {\n let lanes = this.presenceLanes.get(connId);\n if (!lanes) {\n lanes = new Map();\n this.presenceLanes.set(connId, lanes);\n }\n let key = requested;\n if (key !== FALLBACK_PRESENCE_LANE && !lanes.has(key)) {\n const named = lanes.size - (lanes.has(FALLBACK_PRESENCE_LANE) ? 1 : 0);\n if (named >= this.maxPresenceLanes - 1) key = FALLBACK_PRESENCE_LANE;\n }\n let lane = lanes.get(key);\n if (!lane) {\n lane = { lastSentAt: undefined, pending: null };\n lanes.set(key, lane);\n }\n return lane;\n }\n\n private broadcastLeave(room: string, from: string): void {\n const message = JSON.stringify({ from, op: { kind: 'presence-leave' } });\n this.relayToRoom(room, undefined, message);\n this.safePublish(\n JSON.stringify({ o: this.instanceId, room, from, op: { kind: 'presence-leave' } }),\n );\n }\n\n private deliverToRoom(\n room: string,\n excludeId: string | undefined,\n from: string,\n op: WireSyncOp,\n prevAudience: string | undefined,\n prevExisted: boolean,\n ): void {\n const members = this.rooms.get(room);\n if (!members) return;\n const encoded: EncodedFrames = new Map();\n if (op.kind === 'upsert') {\n const audience = (op.element as OwnedElement).audience;\n const removeOp: WireSyncOp = { kind: 'remove', id: op.element.id };\n const encodedRemove: EncodedFrames = new Map();\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n if (this.mayRead(conn, audience)) this.sendToConnection(conn, from, op, encoded);\n else if (prevExisted && this.mayRead(conn, prevAudience)) {\n this.sendToConnection(conn, HUB_FROM, removeOp, encodedRemove);\n }\n }\n } else if (op.kind === 'remove') {\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (!conn) continue;\n // No read filter → forward to all (today's behavior; current/prevExisted aren't fetched without\n // a hook). With canRead, only recipients who could see the removed element get it.\n const wasVisible = !this.canRead || (prevExisted && this.mayRead(conn, prevAudience));\n if (wasVisible) this.sendToConnection(conn, from, op, encoded);\n }\n } else if (op.kind === 'clear') {\n for (const cid of members) {\n if (cid === excludeId) continue;\n const conn = this.conns.get(cid);\n if (conn) this.sendToConnection(conn, from, op, encoded);\n }\n }\n }\n\n private async sendCorrection(\n conn: Connection,\n from: string,\n op: WireSyncOp,\n current: OwnedElement | undefined,\n ): Promise<void> {\n let correction: WireSyncOp | undefined;\n if (op.kind === 'upsert') {\n correction = current\n ? this.mayRead(conn, current.audience)\n ? { kind: 'upsert', element: current }\n : { kind: 'remove', id: current.id }\n : { kind: 'remove', id: op.element.id };\n } else if (op.kind === 'remove') {\n correction = current\n ? this.mayRead(conn, current.audience)\n ? { kind: 'upsert', element: current }\n : { kind: 'remove', id: current.id }\n : undefined;\n } else if (op.kind === 'clear') {\n const all = this.normalizeElements(await this.backend.snapshot(conn.room));\n const elements = this.canRead ? all.filter((el) => this.mayRead(conn, el.audience)) : all;\n correction = { kind: 'snapshot', to: from, elements };\n }\n if (correction) this.sendToConnection(conn, HUB_FROM, correction);\n }\n\n private onFanout(payload: string): void {\n // Off the serial queue on purpose: forward-only (the origin already applied to the SHARED backend),\n // and delivery is already ordered. Re-filter per local member (canRead runs on EVERY instance).\n let env: {\n o?: unknown;\n room?: unknown;\n from?: unknown;\n op?: unknown;\n prev?: unknown;\n existed?: unknown;\n };\n try {\n env = JSON.parse(payload);\n } catch {\n return;\n }\n if (typeof env.o !== 'string' || typeof env.room !== 'string' || typeof env.from !== 'string')\n return;\n if (env.o === this.instanceId) return; // our own publish — already delivered locally\n const op = env.op;\n if (isPresenceOp(op)) {\n // presence/leave: raw forward to all local members (the sender lives on the origin instance),\n // no backend, no canRead filter.\n this.relayToRoom(env.room, undefined, JSON.stringify({ from: env.from, op }));\n return;\n }\n if (isLayerOp(op)) {\n void this.applyFanoutLayerOp(env.room, op).catch(() => {\n /* a broken backend must not break the fanout relay */\n });\n this.relayOpToRoom(env.room, undefined, env.from, op);\n return;\n }\n const plugin =\n typeof op === 'object' && op !== null && (op as { kind?: unknown }).kind === 'extension'\n ? this.pluginRegistry.extension((op as { extensionKind?: string }).extensionKind ?? '')\n ?.plugin\n : typeof op === 'object' &&\n op !== null &&\n typeof (op as { kind?: unknown }).kind === 'string'\n ? this.pluginRegistry.ownerOf((op as { kind: string }).kind)\n : undefined;\n if (plugin && typeof op === 'object' && op !== null) {\n const previous = this.roomQueues.get(env.room) ?? Promise.resolve();\n const operation = previous.then(async () => {\n const context: ServerOpContext = {\n room: env.room as string,\n connectionId: env.from as string,\n backend: this.backend,\n backendPlugin: (key) => this.backend.getService?.(key),\n };\n const accepted = plugin.applyFanout\n ? await plugin.applyFanout(op as WireSyncOp, context)\n : (op as WireSyncOp);\n if (accepted) {\n this.relayOpToRoom(env.room as string, undefined, env.from as string, accepted);\n }\n });\n this.roomQueues.set(\n env.room,\n operation.catch(() => {\n /* a broken backend must not wedge the room queue */\n }),\n );\n return;\n }\n if (!isFanoutOp(op)) return;\n const runtimeOp =\n op.kind === 'upsert'\n ? (() => {\n const element = this.normalizeElement(op.element);\n return element ? ({ ...op, element } satisfies WireSyncOp) : null;\n })()\n : op;\n if (!runtimeOp) return;\n const prevAudience = typeof env.prev === 'string' ? env.prev : undefined;\n const prevExisted = env.existed === true;\n this.deliverToRoom(env.room, undefined, env.from, runtimeOp, prevAudience, prevExisted);\n }\n\n private async applyFanoutLayerOp(room: string, op: LayerOp): Promise<void> {\n const record = layerOpToRecord(op);\n const current = await this.getLayerRecord(room, record.id);\n if (current && !isNewerLayerRecord(record, current)) return;\n await this.applyLayerRecord(room, record);\n }\n\n close(): void {\n for (const connId of [...this.presenceLanes.keys()]) this.clearPresenceLanes(connId);\n this.fanoutUnsub();\n }\n}\n","import {\n applyOpToMap,\n type LayerRecord,\n type WireSyncElement,\n type WireSyncOp,\n} from '@fieldnotes/sync';\nimport type { HubBackend } from './hub-backend';\n\nexport class MemoryHubBackend implements HubBackend {\n private rooms = new Map<string, Map<string, WireSyncElement>>();\n private roomLayers = new Map<string, Map<string, LayerRecord>>();\n\n private room(id: string): Map<string, WireSyncElement> {\n let r = this.rooms.get(id);\n if (!r) {\n r = new Map();\n this.rooms.set(id, r);\n }\n return r;\n }\n\n private layers(room: string): Map<string, LayerRecord> {\n let r = this.roomLayers.get(room);\n if (!r) {\n r = new Map();\n this.roomLayers.set(room, r);\n }\n return r;\n }\n\n async snapshot(room: string): Promise<WireSyncElement[]> {\n return [...this.room(room).values()];\n }\n\n async get(room: string, id: string): Promise<WireSyncElement | undefined> {\n return this.room(room).get(id);\n }\n\n async apply(room: string, op: WireSyncOp): Promise<void> {\n if (op.kind === 'clear') {\n // Clears elements only; layer records are a separate, longer-lived ledger.\n this.rooms.delete(room);\n return;\n }\n applyOpToMap(this.room(room), op);\n }\n\n async layerRecords(room: string): Promise<LayerRecord[]> {\n return [...this.layers(room).values()];\n }\n\n async getLayerRecord(room: string, id: string): Promise<LayerRecord | undefined> {\n return this.layers(room).get(id);\n }\n\n async applyLayerRecord(room: string, record: LayerRecord): Promise<void> {\n this.layers(room).set(record.id, record);\n }\n}\n","export interface HubFanout {\n publish(payload: string): void | Promise<void>;\n subscribe(handler: (payload: string) => void): () => void;\n close?(): void;\n}\n\nexport class InMemoryHubFanout implements HubFanout {\n private readonly handlers = new Set<(payload: string) => void>();\n\n publish(payload: string): void {\n for (const h of this.handlers) {\n try {\n h(payload);\n } catch {\n /* one throwing subscriber must not break the publish loop */\n }\n }\n }\n\n subscribe(handler: (payload: string) => void): () => void {\n this.handlers.add(handler);\n return () => this.handlers.delete(handler);\n }\n}\n","import type { ServiceKey } from '@fieldnotes/core';\nimport type { ExtensionKind, PluginSnapshot, TypedExtensionOp, WireSyncOp } from '@fieldnotes/sync';\nimport type { HubBackend } from './hub-backend';\n\nexport interface ApplyResult {\n readonly accepted: WireSyncOp | null;\n readonly corrections: WireSyncOp[];\n readonly broadcast?: WireSyncOp[];\n readonly locality?: 'shared' | 'local';\n}\n\nexport interface ServerOpContext {\n readonly room: string;\n readonly connectionId: string;\n readonly userId?: string;\n readonly role?: string;\n readonly backend: HubBackend;\n backendPlugin<T>(key: ServiceKey<T>): T | undefined;\n}\n\nexport type ServerNext = (op: WireSyncOp, context: ServerOpContext) => Promise<ApplyResult>;\n\nexport interface ServerExtensionRegistry {\n register<TPayload>(\n kind: ExtensionKind<TPayload>,\n handler: (op: TypedExtensionOp<TPayload>, context: ServerOpContext) => Promise<ApplyResult>,\n ): void;\n}\n\nexport interface ServerSyncPlugin {\n readonly name: string;\n readonly ownedLegacyKinds?: readonly string[];\n readonly legacySnapshotKey?: string;\n process?(op: WireSyncOp, context: ServerOpContext, next: ServerNext): Promise<ApplyResult>;\n applyFanout?(op: WireSyncOp, context: ServerOpContext): Promise<WireSyncOp | null>;\n registerExtensionKinds?(registry: ServerExtensionRegistry): void;\n snapshot?(room: string, backend: HubBackend): Promise<PluginSnapshot | undefined>;\n filterSnapshot?(\n snapshot: PluginSnapshot,\n viewer: { userId?: string; role?: string },\n ): PluginSnapshot | null;\n}\n\ninterface ServerExtensionEntry {\n readonly kind: ExtensionKind<unknown>;\n readonly plugin: ServerSyncPlugin;\n readonly handler: (\n op: TypedExtensionOp<unknown>,\n context: ServerOpContext,\n ) => Promise<ApplyResult>;\n}\n\nexport class ServerPluginRegistry {\n private readonly byName = new Map<string, ServerSyncPlugin>();\n private readonly legacyOwners = new Map<string, ServerSyncPlugin>();\n private readonly extensions = new Map<string, ServerExtensionEntry>();\n private readonly definitions: ReadonlyMap<string, ExtensionKind<unknown>>;\n\n constructor(plugins: readonly ServerSyncPlugin[]) {\n for (const plugin of plugins) this.register(plugin);\n // Registration is constructor-only, so the translation view is fixed here\n // instead of rebuilt on every relayed frame.\n this.definitions = new Map([...this.extensions].map(([name, entry]) => [name, entry.kind]));\n }\n\n private register(plugin: ServerSyncPlugin): void {\n if (this.byName.has(plugin.name))\n throw new Error(`Server plugin \"${plugin.name}\" is duplicated`);\n this.byName.set(plugin.name, plugin);\n for (const kind of plugin.ownedLegacyKinds ?? []) {\n if (this.legacyOwners.has(kind)) throw new Error(`Sync op kind \"${kind}\" has two owners`);\n this.legacyOwners.set(kind, plugin);\n }\n plugin.registerExtensionKinds?.({\n register: <TPayload>(\n kind: ExtensionKind<TPayload>,\n handler: (op: TypedExtensionOp<TPayload>, context: ServerOpContext) => Promise<ApplyResult>,\n ) => {\n if (this.extensions.has(kind.extensionKind)) {\n throw new Error(`Extension kind \"${kind.extensionKind}\" has two owners`);\n }\n this.extensions.set(kind.extensionKind, {\n kind: kind as ExtensionKind<unknown>,\n plugin,\n handler: handler as ServerExtensionEntry['handler'],\n });\n },\n });\n }\n\n get plugins(): readonly ServerSyncPlugin[] {\n return [...this.byName.values()];\n }\n\n ownerOf(kind: string): ServerSyncPlugin | undefined {\n return this.legacyOwners.get(kind);\n }\n\n extension(extensionKind: string): ServerExtensionEntry | undefined {\n return this.extensions.get(extensionKind);\n }\n\n get extensionKinds(): readonly string[] {\n return [...this.extensions.keys()];\n }\n\n get extensionDefinitions(): ReadonlyMap<string, ExtensionKind<unknown>> {\n return this.definitions;\n }\n}\n\nexport type { PluginSnapshot } from '@fieldnotes/sync';\n","export const DEFAULT_MAX_MESSAGE_BYTES = 1024 * 1024;\nexport const DEFAULT_MAX_JSON_DEPTH = 32;\nexport const DEFAULT_MAX_PENDING_AUTH_MESSAGES = 100;\nexport const DEFAULT_MAX_PENDING_AUTH_BYTES = 2 * 1024 * 1024;\nexport const DEFAULT_MESSAGES_PER_SECOND = 120;\nexport const DEFAULT_MESSAGE_BURST = 240;\nexport const DEFAULT_PRESENCE_THROTTLE_MS = 50;\n/** Per-connection presence throttle lanes, INCLUDING the reserved fallback lane. */\nexport const DEFAULT_MAX_PRESENCE_LANES = 16;\n\nexport function hasJsonDepthAtMost(message: string, maxDepth: number): boolean {\n let depth = 0;\n let inString = false;\n let escaped = false;\n\n for (const char of message) {\n if (inString) {\n if (escaped) escaped = false;\n else if (char === '\\\\') escaped = true;\n else if (char === '\"') inString = false;\n continue;\n }\n if (char === '\"') inString = true;\n else if (char === '{' || char === '[') {\n depth += 1;\n if (depth > maxDepth) return false;\n } else if (char === '}' || char === ']') {\n depth -= 1;\n if (depth < 0) return false;\n }\n }\n\n return depth === 0 && !inString;\n}\n\nexport class MessageRateLimiter {\n private tokens: number;\n private updatedAt: number;\n\n constructor(\n private readonly ratePerSecond: number,\n private readonly burst: number,\n now = Date.now(),\n ) {\n this.tokens = burst;\n this.updatedAt = now;\n }\n\n take(now = Date.now()): boolean {\n const elapsedSeconds = Math.max(0, now - this.updatedAt) / 1000;\n this.tokens = Math.min(this.burst, this.tokens + elapsedSeconds * this.ratePerSecond);\n this.updatedAt = now;\n if (this.tokens < 1) return false;\n this.tokens -= 1;\n return true;\n }\n}\n","import { WebSocketServer, type RawData } from 'ws';\nimport type { Server } from 'http';\nimport { SyncHub } from './sync-hub';\nimport type { HubBackend } from './hub-backend';\nimport type { HubFanout } from './hub-fanout';\nimport type { Authenticate } from './authenticate';\nimport type { Authorize, AuthorizeLayer, CanRead } from './authorize';\nimport type { ServerSyncPlugin } from './sync-plugin';\nimport type { ElementRegistry } from '@fieldnotes/core';\nimport { startHeartbeat } from './heartbeat';\nimport {\n DEFAULT_MAX_JSON_DEPTH,\n DEFAULT_MAX_MESSAGE_BYTES,\n DEFAULT_MAX_PENDING_AUTH_BYTES,\n DEFAULT_MAX_PENDING_AUTH_MESSAGES,\n DEFAULT_MESSAGE_BURST,\n DEFAULT_MESSAGES_PER_SECOND,\n DEFAULT_PRESENCE_THROTTLE_MS,\n MessageRateLimiter,\n} from './resource-limits';\nimport { DEFAULT_SHUTDOWN_GRACE_MS, drainWebSocketServer } from './shutdown';\n\nexport interface CreateSyncServerOptions {\n port?: number;\n server?: Server;\n backend?: HubBackend;\n fanout?: HubFanout;\n instanceId?: string;\n authenticate?: Authenticate;\n authorize?: Authorize;\n authorizeLayer?: AuthorizeLayer;\n plugins?: readonly ServerSyncPlugin[];\n canRead?: CanRead;\n heartbeatIntervalMs?: number;\n maxMessageBytes?: number;\n maxJsonDepth?: number;\n maxPendingAuthMessages?: number;\n maxPendingAuthBytes?: number;\n messagesPerSecond?: number;\n messageBurst?: number;\n presenceThrottleMs?: number;\n maxPresenceLanes?: number;\n shutdownGraceMs?: number;\n /**\n * Registry used to translate extension envelopes for legacy peers. Without\n * it the hub relays unknown legacy element types verbatim and cannot encode\n * envelopes for pre-envelope clients.\n */\n elementRegistry?: ElementRegistry;\n}\n\nfunction rawDataByteLength(data: RawData): number {\n if (Array.isArray(data)) return data.reduce((total, chunk) => total + chunk.byteLength, 0);\n return data.byteLength;\n}\n\nexport function createSyncServer(options: CreateSyncServerOptions = {}): {\n hub: SyncHub;\n wss: WebSocketServer;\n close: () => Promise<void>;\n} {\n const shutdownGraceMs = options.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;\n if (!Number.isFinite(shutdownGraceMs) || shutdownGraceMs < 0) {\n throw new RangeError('shutdownGraceMs must be a non-negative finite number');\n }\n const hub = new SyncHub({\n backend: options.backend,\n fanout: options.fanout,\n instanceId: options.instanceId,\n authorize: options.authorize,\n authorizeLayer: options.authorizeLayer,\n plugins: options.plugins,\n canRead: options.canRead,\n maxJsonDepth: options.maxJsonDepth ?? DEFAULT_MAX_JSON_DEPTH,\n presenceThrottleMs: options.presenceThrottleMs ?? DEFAULT_PRESENCE_THROTTLE_MS,\n maxPresenceLanes: options.maxPresenceLanes,\n elementRegistry: options.elementRegistry,\n });\n const maxMessageBytes = options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;\n const wss = options.server\n ? new WebSocketServer({ server: options.server, maxPayload: maxMessageBytes })\n : new WebSocketServer({ port: options.port ?? 0, maxPayload: maxMessageBytes });\n const heartbeat = startHeartbeat(wss, options.heartbeatIntervalMs ?? 30000);\n let shuttingDown = false;\n let closePromise: Promise<void> | undefined;\n let counter = 0;\n wss.on('connection', (ws, req) => {\n if (shuttingDown) {\n ws.close(1001, 'server shutting down');\n return;\n }\n heartbeat.track(ws);\n // Protocol/parser failures (including maxPayload) close the peer. Consuming the socket-level\n // error keeps hostile input from becoming an uncaught process error.\n ws.on('error', () => undefined);\n const url = new URL(req.url ?? '', 'http://localhost');\n const room = url.searchParams.get('room');\n if (!room) {\n ws.close(4400, 'room required');\n return;\n }\n const connId = `c${++counter}-${Math.random().toString(36).slice(2, 8)}`;\n\n let state: 'pending' | 'ready' | 'rejected' = 'pending';\n let closed = false;\n let admitted = false;\n const queue: string[] = [];\n let queuedBytes = 0;\n const maxPendingAuthMessages =\n options.maxPendingAuthMessages ?? DEFAULT_MAX_PENDING_AUTH_MESSAGES;\n const maxPendingAuthBytes = options.maxPendingAuthBytes ?? DEFAULT_MAX_PENDING_AUTH_BYTES;\n const limiter = new MessageRateLimiter(\n options.messagesPerSecond ?? DEFAULT_MESSAGES_PER_SECOND,\n options.messageBurst ?? DEFAULT_MESSAGE_BURST,\n );\n\n const send = (m: string) => {\n try {\n ws.send(m);\n } catch {\n /* socket closed mid-send */\n }\n };\n\n ws.on('message', (data) => {\n if (state === 'rejected') return;\n const messageBytes = rawDataByteLength(data);\n if (messageBytes > maxMessageBytes) {\n state = 'rejected';\n ws.close(1009, 'message too large');\n return;\n }\n if (!limiter.take()) {\n state = 'rejected';\n ws.close(4408, 'rate limit exceeded');\n return;\n }\n const msg = String(data);\n if (state === 'pending') {\n if (\n queue.length >= maxPendingAuthMessages ||\n queuedBytes + messageBytes > maxPendingAuthBytes\n ) {\n state = 'rejected';\n ws.close(4408, 'authentication queue limit exceeded');\n return;\n }\n queue.push(msg);\n queuedBytes += messageBytes;\n return;\n }\n void hub.handleMessage(connId, msg).catch((err) => console.error('[sync-server]', err));\n });\n ws.on('close', () => {\n closed = true;\n if (admitted) hub.removeConnection(connId);\n });\n\n Promise.resolve(options.authenticate ? options.authenticate({ req, room }) : { userId: connId })\n .then((result) => {\n if (closed || state === 'rejected' || shuttingDown) return;\n if (!result) {\n state = 'rejected';\n ws.close(4401, 'unauthorized');\n return;\n }\n state = 'ready';\n admitted = true;\n hub.addConnection({ id: connId, room, userId: result.userId, role: result.role, send });\n for (const m of queue) {\n void hub.handleMessage(connId, m).catch((err) => console.error('[sync-server]', err));\n }\n queue.length = 0;\n queuedBytes = 0;\n })\n .catch(() => {\n if (!closed && !shuttingDown) {\n state = 'rejected';\n ws.close(4401, 'unauthorized');\n }\n });\n });\n const close = (): Promise<void> => {\n if (closePromise) return closePromise;\n shuttingDown = true;\n closePromise = drainWebSocketServer(wss, shutdownGraceMs).then(() => {\n heartbeat.stop();\n hub.close();\n });\n return closePromise;\n };\n\n return {\n hub,\n wss,\n close,\n };\n}\n","export interface HeartbeatSocket {\n ping(): void;\n terminate(): void;\n on(event: 'pong', listener: () => void): void;\n}\nexport interface HeartbeatServer {\n clients: Set<HeartbeatSocket>;\n}\nexport interface Heartbeat {\n track(ws: HeartbeatSocket): void;\n stop(): void;\n}\n\nexport function startHeartbeat(wss: HeartbeatServer, intervalMs: number): Heartbeat {\n if (intervalMs <= 0) {\n return {\n track: () => {\n /* disabled */\n },\n stop: () => {\n /* disabled */\n },\n };\n }\n const alive = new WeakMap<HeartbeatSocket, boolean>();\n const track = (ws: HeartbeatSocket): void => {\n alive.set(ws, true);\n ws.on('pong', () => alive.set(ws, true));\n };\n const interval = setInterval(() => {\n for (const ws of wss.clients) {\n try {\n if (alive.get(ws) === false) {\n ws.terminate();\n continue;\n }\n alive.set(ws, false);\n ws.ping();\n } catch {\n /* socket dying mid-tick — skip it */\n }\n }\n }, intervalMs);\n return { track, stop: () => clearInterval(interval) };\n}\n","export const DEFAULT_SHUTDOWN_GRACE_MS = 5000;\n\nexport interface ShutdownSocket {\n close(code?: number, reason?: string): void;\n terminate(): void;\n}\n\nexport interface ShutdownServer {\n clients: Set<ShutdownSocket>;\n close(callback: (error?: Error) => void): void;\n}\n\n/** Stop admission, ask connected peers to leave, then forcefully reap stragglers. */\nexport function drainWebSocketServer(wss: ShutdownServer, graceMs: number): Promise<void> {\n return new Promise((resolve) => {\n let settled = false;\n const finish = (): void => {\n if (settled) return;\n settled = true;\n clearTimeout(forceTimer);\n resolve();\n };\n const forceTimer = setTimeout(() => {\n for (const ws of wss.clients) {\n try {\n ws.terminate();\n } catch {\n /* socket already failed while shutdown was in progress */\n }\n }\n finish();\n }, graceMs);\n\n // Calling close first prevents new connections. Its callback runs once all clients have left.\n wss.close(() => finish());\n for (const ws of wss.clients) {\n try {\n ws.close(1001, 'server shutting down');\n } catch {\n try {\n ws.terminate();\n } catch {\n /* socket already closed */\n }\n }\n }\n });\n}\n"],"mappings":"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","names":["import_sync"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ServiceKey, ElementRegistry } from '@fieldnotes/core';
|
|
2
|
+
import { WireSyncElement, WireSyncOp, LayerRecord, ExtensionKind, TypedExtensionOp, PluginSnapshot } from '@fieldnotes/sync';
|
|
3
3
|
export { PluginSnapshot } from '@fieldnotes/sync';
|
|
4
4
|
import { WebSocketServer } from 'ws';
|
|
5
5
|
import { IncomingMessage, Server } from 'http';
|
|
@@ -18,24 +18,21 @@ declare class InMemoryHubFanout implements HubFanout {
|
|
|
18
18
|
interface HubBackend {
|
|
19
19
|
/** True when every hub instance addresses the same atomic backing state (for example Redis). */
|
|
20
20
|
readonly sharedAcrossInstances?: boolean;
|
|
21
|
-
snapshot(room: string): Promise<
|
|
22
|
-
get(room: string, id: string): Promise<
|
|
23
|
-
apply(room: string, op:
|
|
21
|
+
snapshot(room: string): Promise<WireSyncElement[]>;
|
|
22
|
+
get(room: string, id: string): Promise<WireSyncElement | undefined>;
|
|
23
|
+
apply(room: string, op: WireSyncOp): Promise<void>;
|
|
24
24
|
layerRecords?(room: string): Promise<LayerRecord[]>;
|
|
25
25
|
getLayerRecord?(room: string, id: string): Promise<LayerRecord | undefined>;
|
|
26
26
|
applyLayerRecord?(room: string, record: LayerRecord): Promise<void>;
|
|
27
27
|
getService?<T>(key: ServiceKey<T>): T | undefined;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
type OwnedElement =
|
|
31
|
-
ownerId?: string;
|
|
32
|
-
audience?: string;
|
|
33
|
-
};
|
|
30
|
+
type OwnedElement = WireSyncElement;
|
|
34
31
|
interface AuthorizeContext {
|
|
35
32
|
userId?: string;
|
|
36
33
|
role?: string;
|
|
37
34
|
room: string;
|
|
38
|
-
op:
|
|
35
|
+
op: WireSyncOp;
|
|
39
36
|
currentElement?: OwnedElement;
|
|
40
37
|
}
|
|
41
38
|
type Authorize = (ctx: AuthorizeContext) => boolean | Promise<boolean>;
|
|
@@ -43,7 +40,7 @@ interface AuthorizeLayerContext {
|
|
|
43
40
|
userId?: string;
|
|
44
41
|
role?: string;
|
|
45
42
|
room: string;
|
|
46
|
-
op: Extract<
|
|
43
|
+
op: Extract<WireSyncOp, {
|
|
47
44
|
kind: 'layer-upsert' | 'layer-remove';
|
|
48
45
|
}>;
|
|
49
46
|
/** The hub's current record for the target layer, tombstones included. */
|
|
@@ -65,9 +62,9 @@ interface ReadContext {
|
|
|
65
62
|
type CanRead = (ctx: ReadContext) => boolean;
|
|
66
63
|
|
|
67
64
|
interface ApplyResult {
|
|
68
|
-
readonly accepted:
|
|
69
|
-
readonly corrections:
|
|
70
|
-
readonly broadcast?:
|
|
65
|
+
readonly accepted: WireSyncOp | null;
|
|
66
|
+
readonly corrections: WireSyncOp[];
|
|
67
|
+
readonly broadcast?: WireSyncOp[];
|
|
71
68
|
readonly locality?: 'shared' | 'local';
|
|
72
69
|
}
|
|
73
70
|
interface ServerOpContext {
|
|
@@ -78,7 +75,7 @@ interface ServerOpContext {
|
|
|
78
75
|
readonly backend: HubBackend;
|
|
79
76
|
backendPlugin<T>(key: ServiceKey<T>): T | undefined;
|
|
80
77
|
}
|
|
81
|
-
type ServerNext = (op:
|
|
78
|
+
type ServerNext = (op: WireSyncOp, context: ServerOpContext) => Promise<ApplyResult>;
|
|
82
79
|
interface ServerExtensionRegistry {
|
|
83
80
|
register<TPayload>(kind: ExtensionKind<TPayload>, handler: (op: TypedExtensionOp<TPayload>, context: ServerOpContext) => Promise<ApplyResult>): void;
|
|
84
81
|
}
|
|
@@ -86,8 +83,8 @@ interface ServerSyncPlugin {
|
|
|
86
83
|
readonly name: string;
|
|
87
84
|
readonly ownedLegacyKinds?: readonly string[];
|
|
88
85
|
readonly legacySnapshotKey?: string;
|
|
89
|
-
process?(op:
|
|
90
|
-
applyFanout?(op:
|
|
86
|
+
process?(op: WireSyncOp, context: ServerOpContext, next: ServerNext): Promise<ApplyResult>;
|
|
87
|
+
applyFanout?(op: WireSyncOp, context: ServerOpContext): Promise<WireSyncOp | null>;
|
|
91
88
|
registerExtensionKinds?(registry: ServerExtensionRegistry): void;
|
|
92
89
|
snapshot?(room: string, backend: HubBackend): Promise<PluginSnapshot | undefined>;
|
|
93
90
|
filterSnapshot?(snapshot: PluginSnapshot, viewer: {
|
|
@@ -114,6 +111,8 @@ interface SyncHubOptions {
|
|
|
114
111
|
maxJsonDepth?: number;
|
|
115
112
|
presenceThrottleMs?: number;
|
|
116
113
|
maxPresenceLanes?: number;
|
|
114
|
+
/** Registry used to translate extension elements for legacy peers. */
|
|
115
|
+
elementRegistry?: ElementRegistry;
|
|
117
116
|
}
|
|
118
117
|
declare class SyncHub {
|
|
119
118
|
private readonly backend;
|
|
@@ -127,6 +126,8 @@ declare class SyncHub {
|
|
|
127
126
|
private readonly authorize?;
|
|
128
127
|
private readonly authorizeLayer?;
|
|
129
128
|
private readonly pluginRegistry;
|
|
129
|
+
private readonly elementRegistry;
|
|
130
|
+
private readonly peerCapabilities;
|
|
130
131
|
private readonly canRead?;
|
|
131
132
|
private readonly memoryLayers;
|
|
132
133
|
private readonly maxJsonDepth;
|
|
@@ -173,6 +174,24 @@ declare class SyncHub {
|
|
|
173
174
|
private mayRead;
|
|
174
175
|
private safePublish;
|
|
175
176
|
private relayToRoom;
|
|
177
|
+
/**
|
|
178
|
+
* Translates `op` for the peer and sends it. Returns false when the op is
|
|
179
|
+
* lossy for this peer (no legacy encoding) or the socket throws; neither
|
|
180
|
+
* may reject the room operation that produced it.
|
|
181
|
+
*/
|
|
182
|
+
private sendToConnection;
|
|
183
|
+
/** Returns the wire frame for `op` translated for `capabilities`, or null when lossy. */
|
|
184
|
+
private encodeForPeer;
|
|
185
|
+
/**
|
|
186
|
+
* Normalize registered legacy wire elements before authorization, storage,
|
|
187
|
+
* and relay. A legacy type this hub has no adapter for is forwarded and
|
|
188
|
+
* stored verbatim — the hub is a relay, and the peers decide whether they
|
|
189
|
+
* understand it — so a hub deployed without domain adapters never erases
|
|
190
|
+
* the room's existing elements. Only a malformed registered element is dropped.
|
|
191
|
+
*/
|
|
192
|
+
private normalizeElement;
|
|
193
|
+
private normalizeElements;
|
|
194
|
+
private relayOpToRoom;
|
|
176
195
|
private broadcastClientPresence;
|
|
177
196
|
private clearPresenceLanes;
|
|
178
197
|
private schedulePresence;
|
|
@@ -190,9 +209,9 @@ declare class MemoryHubBackend implements HubBackend {
|
|
|
190
209
|
private roomLayers;
|
|
191
210
|
private room;
|
|
192
211
|
private layers;
|
|
193
|
-
snapshot(room: string): Promise<
|
|
194
|
-
get(room: string, id: string): Promise<
|
|
195
|
-
apply(room: string, op:
|
|
212
|
+
snapshot(room: string): Promise<WireSyncElement[]>;
|
|
213
|
+
get(room: string, id: string): Promise<WireSyncElement | undefined>;
|
|
214
|
+
apply(room: string, op: WireSyncOp): Promise<void>;
|
|
196
215
|
layerRecords(room: string): Promise<LayerRecord[]>;
|
|
197
216
|
getLayerRecord(room: string, id: string): Promise<LayerRecord | undefined>;
|
|
198
217
|
applyLayerRecord(room: string, record: LayerRecord): Promise<void>;
|
|
@@ -229,6 +248,12 @@ interface CreateSyncServerOptions {
|
|
|
229
248
|
presenceThrottleMs?: number;
|
|
230
249
|
maxPresenceLanes?: number;
|
|
231
250
|
shutdownGraceMs?: number;
|
|
251
|
+
/**
|
|
252
|
+
* Registry used to translate extension envelopes for legacy peers. Without
|
|
253
|
+
* it the hub relays unknown legacy element types verbatim and cannot encode
|
|
254
|
+
* envelopes for pre-envelope clients.
|
|
255
|
+
*/
|
|
256
|
+
elementRegistry?: ElementRegistry;
|
|
232
257
|
}
|
|
233
258
|
declare function createSyncServer(options?: CreateSyncServerOptions): {
|
|
234
259
|
hub: SyncHub;
|