@parsrun/realtime 0.1.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 +176 -0
- package/dist/adapters/durable-objects.d.ts +89 -0
- package/dist/adapters/durable-objects.js +414 -0
- package/dist/adapters/durable-objects.js.map +1 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.js +740 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/sse.d.ts +69 -0
- package/dist/adapters/sse.js +330 -0
- package/dist/adapters/sse.js.map +1 -0
- package/dist/hono.d.ts +76 -0
- package/dist/hono.js +551 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +1084 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +317 -0
- package/dist/types.js +75 -0
- package/dist/types.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types.ts","../../src/adapters/durable-objects.ts"],"sourcesContent":["/**\n * @parsrun/realtime - Type Definitions\n * Realtime communication types and interfaces\n */\n\n// ============================================================================\n// Core Types\n// ============================================================================\n\n/**\n * Realtime adapter type\n */\nexport type RealtimeAdapterType = \"sse\" | \"durable-objects\" | \"memory\";\n\n/**\n * Realtime message\n */\nexport interface RealtimeMessage<T = unknown> {\n /** Unique message ID */\n id: string;\n /** Event type/name */\n event: string;\n /** Channel name */\n channel: string;\n /** Message payload */\n data: T;\n /** Sender ID (optional) */\n senderId?: string | undefined;\n /** Timestamp */\n timestamp: number;\n /** Metadata */\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Presence user data\n */\nexport interface PresenceUser<T = unknown> {\n /** User ID */\n userId: string;\n /** Session ID */\n sessionId: string;\n /** Custom presence data */\n data: T;\n /** Join timestamp */\n joinedAt: number;\n /** Last activity timestamp */\n lastSeenAt: number;\n}\n\n/**\n * Channel info\n */\nexport interface ChannelInfo {\n /** Channel name */\n name: string;\n /** Number of subscribers */\n subscriberCount: number;\n /** Presence users */\n presence: PresenceUser[];\n /** Channel metadata */\n metadata?: Record<string, unknown> | undefined;\n}\n\n// ============================================================================\n// Events\n// ============================================================================\n\n/**\n * System event types\n */\nexport type SystemEventType =\n | \"connection:open\"\n | \"connection:close\"\n | \"connection:error\"\n | \"presence:join\"\n | \"presence:leave\"\n | \"presence:update\"\n | \"channel:subscribe\"\n | \"channel:unsubscribe\"\n | \"ping\"\n | \"pong\";\n\n/**\n * Message handler function\n */\nexport type MessageHandler<T = unknown> = (\n message: RealtimeMessage<T>\n) => void | Promise<void>;\n\n/**\n * Connection event handler\n */\nexport type ConnectionHandler = (\n event: ConnectionEvent\n) => void | Promise<void>;\n\n/**\n * Connection event\n */\nexport interface ConnectionEvent {\n type: \"open\" | \"close\" | \"error\";\n sessionId: string;\n userId?: string | undefined;\n error?: Error | undefined;\n timestamp: number;\n}\n\n/**\n * Presence event\n */\nexport interface PresenceEvent<T = unknown> {\n type: \"join\" | \"leave\" | \"update\";\n channel: string;\n user: PresenceUser<T>;\n timestamp: number;\n}\n\n// ============================================================================\n// Channel Interface\n// ============================================================================\n\n/**\n * Channel interface for realtime communication\n */\nexport interface Channel {\n /** Channel name */\n readonly name: string;\n\n /**\n * Broadcast message to all subscribers\n */\n broadcast<T = unknown>(event: string, data: T): Promise<void>;\n\n /**\n * Send message to specific user\n */\n send<T = unknown>(userId: string, event: string, data: T): Promise<void>;\n\n /**\n * Get current presence users\n */\n getPresence<T = unknown>(): Promise<PresenceUser<T>[]>;\n\n /**\n * Subscribe to channel messages\n */\n subscribe<T = unknown>(handler: MessageHandler<T>): () => void;\n\n /**\n * Subscribe to presence events\n */\n onPresence<T = unknown>(\n handler: (event: PresenceEvent<T>) => void\n ): () => void;\n\n /**\n * Get channel info\n */\n getInfo(): Promise<ChannelInfo>;\n}\n\n// ============================================================================\n// Realtime Service Interface\n// ============================================================================\n\n/**\n * Realtime service interface\n */\nexport interface RealtimeService {\n /** Adapter type */\n readonly adapterType: RealtimeAdapterType;\n\n /**\n * Get or create a channel\n */\n channel(name: string): Channel;\n\n /**\n * Broadcast to a channel\n */\n broadcast<T = unknown>(channel: string, event: string, data: T): Promise<void>;\n\n /**\n * Get channel info\n */\n getChannelInfo(channel: string): Promise<ChannelInfo | null>;\n\n /**\n * List active channels\n */\n listChannels(): Promise<string[]>;\n\n /**\n * Subscribe to connection events\n */\n onConnection(handler: ConnectionHandler): () => void;\n\n /**\n * Close all connections and cleanup\n */\n close(): Promise<void>;\n}\n\n// ============================================================================\n// Adapter Interface\n// ============================================================================\n\n/**\n * Realtime adapter interface (low-level)\n */\nexport interface RealtimeAdapter {\n /** Adapter type */\n readonly type: RealtimeAdapterType;\n\n /**\n * Add a subscriber to channel\n */\n subscribe(\n channel: string,\n sessionId: string,\n handler: MessageHandler\n ): Promise<void>;\n\n /**\n * Remove a subscriber from channel\n */\n unsubscribe(channel: string, sessionId: string): Promise<void>;\n\n /**\n * Publish message to channel\n */\n publish<T = unknown>(channel: string, message: RealtimeMessage<T>): Promise<void>;\n\n /**\n * Send to specific session\n */\n sendToSession<T = unknown>(\n sessionId: string,\n message: RealtimeMessage<T>\n ): Promise<boolean>;\n\n /**\n * Get subscribers for channel\n */\n getSubscribers(channel: string): Promise<string[]>;\n\n /**\n * Set presence for user in channel\n */\n setPresence<T = unknown>(\n channel: string,\n sessionId: string,\n userId: string,\n data: T\n ): Promise<void>;\n\n /**\n * Remove presence\n */\n removePresence(channel: string, sessionId: string): Promise<void>;\n\n /**\n * Get presence for channel\n */\n getPresence<T = unknown>(channel: string): Promise<PresenceUser<T>[]>;\n\n /**\n * Cleanup and close adapter\n */\n close(): Promise<void>;\n}\n\n// ============================================================================\n// SSE Types\n// ============================================================================\n\n/**\n * SSE connection\n */\nexport interface SSEConnection {\n /** Session ID */\n sessionId: string;\n /** User ID */\n userId?: string | undefined;\n /** Subscribed channels */\n channels: Set<string>;\n /** Response writer */\n writer: WritableStreamDefaultWriter<Uint8Array>;\n /** Connection state */\n state: \"open\" | \"closed\";\n /** Created timestamp */\n createdAt: number;\n /** Last ping timestamp */\n lastPingAt: number;\n}\n\n/**\n * SSE adapter options\n */\nexport interface SSEAdapterOptions {\n /** Ping interval in milliseconds (default: 30000) */\n pingInterval?: number | undefined;\n /** Connection timeout in milliseconds (default: 0 = no timeout) */\n connectionTimeout?: number | undefined;\n /** Max connections per channel (default: 1000) */\n maxConnectionsPerChannel?: number | undefined;\n /** Retry delay for client reconnection (default: 3000) */\n retryDelay?: number | undefined;\n}\n\n// ============================================================================\n// Durable Objects Types\n// ============================================================================\n\n/**\n * Durable Objects adapter options\n */\nexport interface DurableObjectsAdapterOptions {\n /** Durable Object namespace binding */\n namespace: DurableObjectNamespace;\n /** Channel prefix (default: \"channel:\") */\n channelPrefix?: string | undefined;\n}\n\n/**\n * Durable Object WebSocket session\n */\nexport interface DOWebSocketSession {\n /** Session ID */\n sessionId: string;\n /** User ID */\n userId?: string | undefined;\n /** WebSocket connection */\n webSocket: WebSocket;\n /** Presence data */\n presence?: unknown;\n /** Connection state */\n state: \"connecting\" | \"open\" | \"closing\" | \"closed\";\n /** Created timestamp */\n createdAt: number;\n}\n\n/**\n * Durable Object state stored in storage\n */\nexport interface DOChannelState {\n /** Channel name */\n name: string;\n /** Channel metadata */\n metadata?: Record<string, unknown> | undefined;\n /** Created timestamp */\n createdAt: number;\n}\n\n// ============================================================================\n// Config Types\n// ============================================================================\n\n/**\n * Realtime config\n */\nexport interface RealtimeConfig {\n /** Adapter type */\n adapter: RealtimeAdapterType;\n\n /** SSE adapter options */\n sse?: SSEAdapterOptions | undefined;\n\n /** Durable Objects adapter options */\n durableObjects?: DurableObjectsAdapterOptions | undefined;\n\n /** Enable debug logging */\n debug?: boolean | undefined;\n}\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Realtime error\n */\nexport class RealtimeError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = \"RealtimeError\";\n }\n}\n\n/**\n * Common error codes\n */\nexport const RealtimeErrorCodes = {\n CONNECTION_FAILED: \"CONNECTION_FAILED\",\n CHANNEL_NOT_FOUND: \"CHANNEL_NOT_FOUND\",\n UNAUTHORIZED: \"UNAUTHORIZED\",\n MESSAGE_TOO_LARGE: \"MESSAGE_TOO_LARGE\",\n RATE_LIMITED: \"RATE_LIMITED\",\n ADAPTER_ERROR: \"ADAPTER_ERROR\",\n INVALID_MESSAGE: \"INVALID_MESSAGE\",\n} as const;\n\n// ============================================================================\n// Utility Types\n// ============================================================================\n\n/**\n * Create message options\n */\nexport interface CreateMessageOptions<T = unknown> {\n event: string;\n channel: string;\n data: T;\n senderId?: string | undefined;\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Create a realtime message\n */\nexport function createMessage<T = unknown>(\n options: CreateMessageOptions<T>\n): RealtimeMessage<T> {\n return {\n id: crypto.randomUUID(),\n event: options.event,\n channel: options.channel,\n data: options.data,\n senderId: options.senderId,\n timestamp: Date.now(),\n metadata: options.metadata,\n };\n}\n\n/**\n * Parse SSE event string\n */\nexport function parseSSEEvent(eventString: string): RealtimeMessage | null {\n try {\n const lines = eventString.split(\"\\n\");\n let event = \"message\";\n let data = \"\";\n let id = \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event:\")) {\n event = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n data = line.slice(5).trim();\n } else if (line.startsWith(\"id:\")) {\n id = line.slice(3).trim();\n }\n }\n\n if (!data) return null;\n\n const parsed = JSON.parse(data);\n return {\n id: id || parsed.id || crypto.randomUUID(),\n event,\n channel: parsed.channel || \"\",\n data: parsed.data ?? parsed,\n senderId: parsed.senderId,\n timestamp: parsed.timestamp || Date.now(),\n metadata: parsed.metadata,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Format message as SSE event string\n */\nexport function formatSSEEvent(message: RealtimeMessage): string {\n const lines: string[] = [];\n\n lines.push(`id:${message.id}`);\n lines.push(`event:${message.event}`);\n lines.push(`data:${JSON.stringify(message)}`);\n lines.push(\"\"); // Empty line to end event\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","/**\n * @parsrun/realtime - Durable Objects Adapter\n * Cloudflare Durable Objects adapter for WebSocket-based realtime\n */\n\nimport type {\n DOWebSocketSession,\n DurableObjectsAdapterOptions,\n MessageHandler,\n PresenceUser,\n RealtimeAdapter,\n RealtimeMessage,\n} from \"../types.js\";\nimport { createMessage } from \"../types.js\";\n\n// ============================================================================\n// Durable Object Class (Export for wrangler.toml binding)\n// ============================================================================\n\n/**\n * RealtimeChannel Durable Object\n * Manages a single realtime channel with WebSocket connections\n *\n * Usage in wrangler.toml:\n * ```toml\n * [durable_objects]\n * bindings = [{ name = \"REALTIME_CHANNELS\", class_name = \"RealtimeChannelDO\" }]\n *\n * [[migrations]]\n * tag = \"v1\"\n * new_classes = [\"RealtimeChannelDO\"]\n * ```\n */\nexport class RealtimeChannelDO implements DurableObject {\n private sessions: Map<string, DOWebSocketSession> = new Map();\n private presence: Map<string, PresenceUser> = new Map();\n private channelName: string = \"\";\n private state: DurableObjectState;\n\n constructor(\n state: DurableObjectState,\n _env: unknown\n ) {\n this.state = state;\n // Restore hibernated WebSocket sessions\n this.state.getWebSockets().forEach((ws) => {\n const meta = ws.deserializeAttachment() as {\n sessionId: string;\n userId?: string;\n } | null;\n if (meta) {\n this.sessions.set(meta.sessionId, {\n sessionId: meta.sessionId,\n userId: meta.userId,\n webSocket: ws,\n state: \"open\",\n createdAt: Date.now(),\n });\n }\n });\n }\n\n async fetch(request: Request): Promise<Response> {\n const url = new URL(request.url);\n\n // Extract channel name from path\n const pathParts = url.pathname.split(\"/\").filter(Boolean);\n const lastPart = pathParts[pathParts.length - 1];\n if (lastPart) {\n this.channelName = lastPart;\n }\n\n // WebSocket upgrade\n if (request.headers.get(\"Upgrade\") === \"websocket\") {\n return this.handleWebSocketUpgrade(request);\n }\n\n // REST API endpoints\n switch (url.pathname.split(\"/\").pop()) {\n case \"broadcast\":\n return this.handleBroadcast(request);\n case \"presence\":\n return this.handleGetPresence();\n case \"info\":\n return this.handleGetInfo();\n case \"send\":\n return this.handleSendToUser(request);\n default:\n return new Response(\"Not found\", { status: 404 });\n }\n }\n\n // ============================================================================\n // WebSocket Handling\n // ============================================================================\n\n private handleWebSocketUpgrade(request: Request): Response {\n const url = new URL(request.url);\n const sessionId = url.searchParams.get(\"sessionId\") || crypto.randomUUID();\n const userId = url.searchParams.get(\"userId\") || undefined;\n\n const pair = new WebSocketPair();\n const client = pair[0];\n const server = pair[1];\n\n // Store session metadata for hibernation\n server.serializeAttachment({ sessionId, userId });\n\n this.state.acceptWebSocket(server);\n\n const session: DOWebSocketSession = {\n sessionId,\n userId,\n webSocket: server,\n state: \"open\",\n createdAt: Date.now(),\n };\n\n this.sessions.set(sessionId, session);\n\n // Send connection confirmation\n server.send(\n JSON.stringify(\n createMessage({\n event: \"connection:open\",\n channel: this.channelName,\n data: { sessionId, userId },\n })\n )\n );\n\n return new Response(null, { status: 101, webSocket: client });\n }\n\n async webSocketMessage(\n ws: WebSocket,\n message: string | ArrayBuffer\n ): Promise<void> {\n const session = this.getSessionByWebSocket(ws);\n if (!session) return;\n\n try {\n const data =\n typeof message === \"string\"\n ? message\n : new TextDecoder().decode(message);\n\n const parsed = JSON.parse(data) as RealtimeMessage;\n await this.handleMessage(session, parsed);\n } catch {\n // Invalid message format, ignore\n }\n }\n\n async webSocketClose(\n ws: WebSocket,\n code: number,\n reason: string,\n _wasClean: boolean\n ): Promise<void> {\n const session = this.getSessionByWebSocket(ws);\n if (!session) return;\n\n // Remove presence\n this.presence.delete(session.sessionId);\n await this.broadcastPresenceUpdate();\n\n // Remove session\n this.sessions.delete(session.sessionId);\n\n // Broadcast leave event\n await this.broadcastToAll({\n event: \"connection:close\",\n channel: this.channelName,\n data: {\n sessionId: session.sessionId,\n userId: session.userId,\n code,\n reason,\n },\n });\n }\n\n async webSocketError(ws: WebSocket, _error: unknown): Promise<void> {\n const session = this.getSessionByWebSocket(ws);\n if (session) {\n session.state = \"closed\";\n this.sessions.delete(session.sessionId);\n this.presence.delete(session.sessionId);\n }\n }\n\n // ============================================================================\n // Message Handling\n // ============================================================================\n\n private async handleMessage(\n session: DOWebSocketSession,\n message: RealtimeMessage\n ): Promise<void> {\n switch (message.event) {\n case \"ping\":\n session.webSocket.send(\n JSON.stringify(\n createMessage({\n event: \"pong\",\n channel: this.channelName,\n data: { timestamp: Date.now() },\n })\n )\n );\n break;\n\n case \"presence:join\":\n await this.handlePresenceJoin(session, message.data);\n break;\n\n case \"presence:update\":\n await this.handlePresenceUpdate(session, message.data);\n break;\n\n case \"presence:leave\":\n await this.handlePresenceLeave(session);\n break;\n\n case \"broadcast\":\n // Broadcast to all except sender\n await this.broadcastToAll(\n {\n event: message.event,\n channel: this.channelName,\n data: message.data,\n senderId: session.sessionId,\n },\n session.sessionId\n );\n break;\n\n default:\n // Forward custom events\n await this.broadcastToAll(\n {\n event: message.event,\n channel: this.channelName,\n data: message.data,\n senderId: session.sessionId,\n },\n session.sessionId\n );\n }\n }\n\n // ============================================================================\n // Presence Handling\n // ============================================================================\n\n private async handlePresenceJoin(\n session: DOWebSocketSession,\n data: unknown\n ): Promise<void> {\n const now = Date.now();\n const user: PresenceUser = {\n userId: session.userId || session.sessionId,\n sessionId: session.sessionId,\n data,\n joinedAt: now,\n lastSeenAt: now,\n };\n\n this.presence.set(session.sessionId, user);\n session.presence = data;\n\n await this.broadcastPresenceUpdate();\n }\n\n private async handlePresenceUpdate(\n session: DOWebSocketSession,\n data: unknown\n ): Promise<void> {\n const existing = this.presence.get(session.sessionId);\n if (existing) {\n existing.data = data;\n existing.lastSeenAt = Date.now();\n session.presence = data;\n await this.broadcastPresenceUpdate();\n }\n }\n\n private async handlePresenceLeave(session: DOWebSocketSession): Promise<void> {\n this.presence.delete(session.sessionId);\n session.presence = undefined;\n await this.broadcastPresenceUpdate();\n }\n\n private async broadcastPresenceUpdate(): Promise<void> {\n const presenceList = Array.from(this.presence.values());\n\n await this.broadcastToAll({\n event: \"presence:sync\",\n channel: this.channelName,\n data: presenceList,\n });\n }\n\n // ============================================================================\n // REST API Handlers\n // ============================================================================\n\n private async handleBroadcast(request: Request): Promise<Response> {\n try {\n const body = (await request.json()) as { event: string; data: unknown };\n\n await this.broadcastToAll({\n event: body.event,\n channel: this.channelName,\n data: body.data,\n });\n\n return new Response(JSON.stringify({ success: true }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (err) {\n return new Response(\n JSON.stringify({ success: false, error: String(err) }),\n { status: 400, headers: { \"Content-Type\": \"application/json\" } }\n );\n }\n }\n\n private handleGetPresence(): Response {\n const presenceList = Array.from(this.presence.values());\n return new Response(JSON.stringify(presenceList), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n private handleGetInfo(): Response {\n return new Response(\n JSON.stringify({\n channel: this.channelName,\n connections: this.sessions.size,\n presence: this.presence.size,\n }),\n { headers: { \"Content-Type\": \"application/json\" } }\n );\n }\n\n private async handleSendToUser(request: Request): Promise<Response> {\n try {\n const body = (await request.json()) as {\n userId: string;\n event: string;\n data: unknown;\n };\n\n let sent = false;\n for (const session of this.sessions.values()) {\n if (session.userId === body.userId && session.state === \"open\") {\n session.webSocket.send(\n JSON.stringify(\n createMessage({\n event: body.event,\n channel: this.channelName,\n data: body.data,\n })\n )\n );\n sent = true;\n }\n }\n\n return new Response(JSON.stringify({ success: true, sent }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (err) {\n return new Response(\n JSON.stringify({ success: false, error: String(err) }),\n { status: 400, headers: { \"Content-Type\": \"application/json\" } }\n );\n }\n }\n\n // ============================================================================\n // Helpers\n // ============================================================================\n\n private getSessionByWebSocket(ws: WebSocket): DOWebSocketSession | undefined {\n for (const session of this.sessions.values()) {\n if (session.webSocket === ws) {\n return session;\n }\n }\n return undefined;\n }\n\n private async broadcastToAll(\n messageData: {\n event: string;\n channel: string;\n data: unknown;\n senderId?: string;\n },\n excludeSessionId?: string\n ): Promise<void> {\n const message = createMessage(messageData);\n const payload = JSON.stringify(message);\n\n for (const session of this.sessions.values()) {\n if (session.sessionId === excludeSessionId) continue;\n if (session.state !== \"open\") continue;\n\n try {\n session.webSocket.send(payload);\n } catch {\n // Connection might be closed\n session.state = \"closed\";\n }\n }\n }\n}\n\n// ============================================================================\n// Durable Objects Adapter (Client-side)\n// ============================================================================\n\n/**\n * Durable Objects Adapter\n * Uses RealtimeChannelDO for channel management\n */\nexport class DurableObjectsAdapter implements RealtimeAdapter {\n readonly type = \"durable-objects\" as const;\n\n private namespace: DurableObjectNamespace;\n private channelPrefix: string;\n private localHandlers: Map<string, Map<string, MessageHandler>> = new Map();\n\n constructor(options: DurableObjectsAdapterOptions) {\n this.namespace = options.namespace;\n this.channelPrefix = options.channelPrefix ?? \"channel:\";\n }\n\n /**\n * Get Durable Object stub for a channel\n */\n getChannelStub(channel: string): DurableObjectStub {\n const id = this.namespace.idFromName(`${this.channelPrefix}${channel}`);\n return this.namespace.get(id);\n }\n\n /**\n * Get WebSocket URL for a channel\n */\n getWebSocketUrl(\n channel: string,\n sessionId: string,\n userId?: string,\n baseUrl?: string\n ): string {\n const base = baseUrl || \"wss://your-worker.your-subdomain.workers.dev\";\n const params = new URLSearchParams({ sessionId });\n if (userId) params.set(\"userId\", userId);\n return `${base}/realtime/${channel}?${params}`;\n }\n\n // ============================================================================\n // RealtimeAdapter Implementation\n // ============================================================================\n\n async subscribe(\n channel: string,\n sessionId: string,\n handler: MessageHandler\n ): Promise<void> {\n // Store handler locally for callback\n if (!this.localHandlers.has(channel)) {\n this.localHandlers.set(channel, new Map());\n }\n this.localHandlers.get(channel)!.set(sessionId, handler);\n\n // Note: Actual WebSocket subscription happens via WebSocket connection\n // This is used for server-side handler registration\n }\n\n async unsubscribe(channel: string, sessionId: string): Promise<void> {\n const handlers = this.localHandlers.get(channel);\n if (handlers) {\n handlers.delete(sessionId);\n if (handlers.size === 0) {\n this.localHandlers.delete(channel);\n }\n }\n }\n\n async publish<T = unknown>(\n channel: string,\n message: RealtimeMessage<T>\n ): Promise<void> {\n const stub = this.getChannelStub(channel);\n\n await stub.fetch(new Request(\"https://do/broadcast\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n event: message.event,\n data: message.data,\n }),\n }));\n\n // Call local handlers\n const handlers = this.localHandlers.get(channel);\n if (handlers) {\n for (const handler of handlers.values()) {\n try {\n await handler(message);\n } catch {\n // Ignore handler errors\n }\n }\n }\n }\n\n async sendToSession<T = unknown>(\n _sessionId: string,\n _message: RealtimeMessage<T>\n ): Promise<boolean> {\n // For DO adapter, we send via the REST API to a specific user\n // This requires knowing which channel the session is in\n // For simplicity, we broadcast with target metadata\n return false;\n }\n\n async getSubscribers(channel: string): Promise<string[]> {\n const handlers = this.localHandlers.get(channel);\n return handlers ? Array.from(handlers.keys()) : [];\n }\n\n async setPresence<T = unknown>(\n _channel: string,\n _sessionId: string,\n _userId: string,\n _data: T\n ): Promise<void> {\n // Presence is managed via WebSocket messages in DO\n // This is a no-op for the adapter\n }\n\n async removePresence(_channel: string, _sessionId: string): Promise<void> {\n // Presence is managed via WebSocket messages in DO\n }\n\n async getPresence<T = unknown>(channel: string): Promise<PresenceUser<T>[]> {\n const stub = this.getChannelStub(channel);\n const response = await stub.fetch(new Request(\"https://do/presence\"));\n return response.json();\n }\n\n async close(): Promise<void> {\n this.localHandlers.clear();\n }\n\n /**\n * Get channel info from Durable Object\n */\n async getChannelInfo(\n channel: string\n ): Promise<{ connections: number; presence: number }> {\n const stub = this.getChannelStub(channel);\n const response = await stub.fetch(new Request(\"https://do/info\"));\n return response.json();\n }\n\n /**\n * Send message to specific user in a channel\n */\n async sendToUser<T = unknown>(\n channel: string,\n userId: string,\n event: string,\n data: T\n ): Promise<boolean> {\n const stub = this.getChannelStub(channel);\n\n const response = await stub.fetch(\n new Request(\"https://do/send\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ userId, event, data }),\n })\n );\n\n const result = (await response.json()) as { sent: boolean };\n return result.sent;\n }\n}\n\n/**\n * Create Durable Objects adapter\n */\nexport function createDurableObjectsAdapter(\n options: DurableObjectsAdapterOptions\n): DurableObjectsAdapter {\n return new DurableObjectsAdapter(options);\n}\n"],"mappings":";AAyaO,SAAS,cACd,SACoB;AACpB,SAAO;AAAA,IACL,IAAI,OAAO,WAAW;AAAA,IACtB,OAAO,QAAQ;AAAA,IACf,SAAS,QAAQ;AAAA,IACjB,MAAM,QAAQ;AAAA,IACd,UAAU,QAAQ;AAAA,IAClB,WAAW,KAAK,IAAI;AAAA,IACpB,UAAU,QAAQ;AAAA,EACpB;AACF;;;ACpZO,IAAM,oBAAN,MAAiD;AAAA,EAC9C,WAA4C,oBAAI,IAAI;AAAA,EACpD,WAAsC,oBAAI,IAAI;AAAA,EAC9C,cAAsB;AAAA,EACtB;AAAA,EAER,YACE,OACA,MACA;AACA,SAAK,QAAQ;AAEb,SAAK,MAAM,cAAc,EAAE,QAAQ,CAAC,OAAO;AACzC,YAAM,OAAO,GAAG,sBAAsB;AAItC,UAAI,MAAM;AACR,aAAK,SAAS,IAAI,KAAK,WAAW;AAAA,UAChC,WAAW,KAAK;AAAA,UAChB,QAAQ,KAAK;AAAA,UACb,WAAW;AAAA,UACX,OAAO;AAAA,UACP,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,SAAqC;AAC/C,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAG/B,UAAM,YAAY,IAAI,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACxD,UAAM,WAAW,UAAU,UAAU,SAAS,CAAC;AAC/C,QAAI,UAAU;AACZ,WAAK,cAAc;AAAA,IACrB;AAGA,QAAI,QAAQ,QAAQ,IAAI,SAAS,MAAM,aAAa;AAClD,aAAO,KAAK,uBAAuB,OAAO;AAAA,IAC5C;AAGA,YAAQ,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,GAAG;AAAA,MACrC,KAAK;AACH,eAAO,KAAK,gBAAgB,OAAO;AAAA,MACrC,KAAK;AACH,eAAO,KAAK,kBAAkB;AAAA,MAChC,KAAK;AACH,eAAO,KAAK,cAAc;AAAA,MAC5B,KAAK;AACH,eAAO,KAAK,iBAAiB,OAAO;AAAA,MACtC;AACE,eAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,uBAAuB,SAA4B;AACzD,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,YAAY,IAAI,aAAa,IAAI,WAAW,KAAK,OAAO,WAAW;AACzE,UAAM,SAAS,IAAI,aAAa,IAAI,QAAQ,KAAK;AAEjD,UAAM,OAAO,IAAI,cAAc;AAC/B,UAAM,SAAS,KAAK,CAAC;AACrB,UAAM,SAAS,KAAK,CAAC;AAGrB,WAAO,oBAAoB,EAAE,WAAW,OAAO,CAAC;AAEhD,SAAK,MAAM,gBAAgB,MAAM;AAEjC,UAAM,UAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX,OAAO;AAAA,MACP,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,SAAK,SAAS,IAAI,WAAW,OAAO;AAGpC,WAAO;AAAA,MACL,KAAK;AAAA,QACH,cAAc;AAAA,UACZ,OAAO;AAAA,UACP,SAAS,KAAK;AAAA,UACd,MAAM,EAAE,WAAW,OAAO;AAAA,QAC5B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,WAAW,OAAO,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAM,iBACJ,IACA,SACe;AACf,UAAM,UAAU,KAAK,sBAAsB,EAAE;AAC7C,QAAI,CAAC,QAAS;AAEd,QAAI;AACF,YAAM,OACJ,OAAO,YAAY,WACf,UACA,IAAI,YAAY,EAAE,OAAO,OAAO;AAEtC,YAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,YAAM,KAAK,cAAc,SAAS,MAAM;AAAA,IAC1C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,eACJ,IACA,MACA,QACA,WACe;AACf,UAAM,UAAU,KAAK,sBAAsB,EAAE;AAC7C,QAAI,CAAC,QAAS;AAGd,SAAK,SAAS,OAAO,QAAQ,SAAS;AACtC,UAAM,KAAK,wBAAwB;AAGnC,SAAK,SAAS,OAAO,QAAQ,SAAS;AAGtC,UAAM,KAAK,eAAe;AAAA,MACxB,OAAO;AAAA,MACP,SAAS,KAAK;AAAA,MACd,MAAM;AAAA,QACJ,WAAW,QAAQ;AAAA,QACnB,QAAQ,QAAQ;AAAA,QAChB;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,eAAe,IAAe,QAAgC;AAClE,UAAM,UAAU,KAAK,sBAAsB,EAAE;AAC7C,QAAI,SAAS;AACX,cAAQ,QAAQ;AAChB,WAAK,SAAS,OAAO,QAAQ,SAAS;AACtC,WAAK,SAAS,OAAO,QAAQ,SAAS;AAAA,IACxC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,cACZ,SACA,SACe;AACf,YAAQ,QAAQ,OAAO;AAAA,MACrB,KAAK;AACH,gBAAQ,UAAU;AAAA,UAChB,KAAK;AAAA,YACH,cAAc;AAAA,cACZ,OAAO;AAAA,cACP,SAAS,KAAK;AAAA,cACd,MAAM,EAAE,WAAW,KAAK,IAAI,EAAE;AAAA,YAChC,CAAC;AAAA,UACH;AAAA,QACF;AACA;AAAA,MAEF,KAAK;AACH,cAAM,KAAK,mBAAmB,SAAS,QAAQ,IAAI;AACnD;AAAA,MAEF,KAAK;AACH,cAAM,KAAK,qBAAqB,SAAS,QAAQ,IAAI;AACrD;AAAA,MAEF,KAAK;AACH,cAAM,KAAK,oBAAoB,OAAO;AACtC;AAAA,MAEF,KAAK;AAEH,cAAM,KAAK;AAAA,UACT;AAAA,YACE,OAAO,QAAQ;AAAA,YACf,SAAS,KAAK;AAAA,YACd,MAAM,QAAQ;AAAA,YACd,UAAU,QAAQ;AAAA,UACpB;AAAA,UACA,QAAQ;AAAA,QACV;AACA;AAAA,MAEF;AAEE,cAAM,KAAK;AAAA,UACT;AAAA,YACE,OAAO,QAAQ;AAAA,YACf,SAAS,KAAK;AAAA,YACd,MAAM,QAAQ;AAAA,YACd,UAAU,QAAQ;AAAA,UACpB;AAAA,UACA,QAAQ;AAAA,QACV;AAAA,IACJ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,mBACZ,SACA,MACe;AACf,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,OAAqB;AAAA,MACzB,QAAQ,QAAQ,UAAU,QAAQ;AAAA,MAClC,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA,UAAU;AAAA,MACV,YAAY;AAAA,IACd;AAEA,SAAK,SAAS,IAAI,QAAQ,WAAW,IAAI;AACzC,YAAQ,WAAW;AAEnB,UAAM,KAAK,wBAAwB;AAAA,EACrC;AAAA,EAEA,MAAc,qBACZ,SACA,MACe;AACf,UAAM,WAAW,KAAK,SAAS,IAAI,QAAQ,SAAS;AACpD,QAAI,UAAU;AACZ,eAAS,OAAO;AAChB,eAAS,aAAa,KAAK,IAAI;AAC/B,cAAQ,WAAW;AACnB,YAAM,KAAK,wBAAwB;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,MAAc,oBAAoB,SAA4C;AAC5E,SAAK,SAAS,OAAO,QAAQ,SAAS;AACtC,YAAQ,WAAW;AACnB,UAAM,KAAK,wBAAwB;AAAA,EACrC;AAAA,EAEA,MAAc,0BAAyC;AACrD,UAAM,eAAe,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC;AAEtD,UAAM,KAAK,eAAe;AAAA,MACxB,OAAO;AAAA,MACP,SAAS,KAAK;AAAA,MACd,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,gBAAgB,SAAqC;AACjE,QAAI;AACF,YAAM,OAAQ,MAAM,QAAQ,KAAK;AAEjC,YAAM,KAAK,eAAe;AAAA,QACxB,OAAO,KAAK;AAAA,QACZ,SAAS,KAAK;AAAA,QACd,MAAM,KAAK;AAAA,MACb,CAAC;AAED,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,SAAS,KAAK,CAAC,GAAG;AAAA,QACrD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,QACrD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,oBAA8B;AACpC,UAAM,eAAe,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC;AACtD,WAAO,IAAI,SAAS,KAAK,UAAU,YAAY,GAAG;AAAA,MAChD,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AAAA,EACH;AAAA,EAEQ,gBAA0B;AAChC,WAAO,IAAI;AAAA,MACT,KAAK,UAAU;AAAA,QACb,SAAS,KAAK;AAAA,QACd,aAAa,KAAK,SAAS;AAAA,QAC3B,UAAU,KAAK,SAAS;AAAA,MAC1B,CAAC;AAAA,MACD,EAAE,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAc,iBAAiB,SAAqC;AAClE,QAAI;AACF,YAAM,OAAQ,MAAM,QAAQ,KAAK;AAMjC,UAAI,OAAO;AACX,iBAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,YAAI,QAAQ,WAAW,KAAK,UAAU,QAAQ,UAAU,QAAQ;AAC9D,kBAAQ,UAAU;AAAA,YAChB,KAAK;AAAA,cACH,cAAc;AAAA,gBACZ,OAAO,KAAK;AAAA,gBACZ,SAAS,KAAK;AAAA,gBACd,MAAM,KAAK;AAAA,cACb,CAAC;AAAA,YACH;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,aAAO,IAAI,SAAS,KAAK,UAAU,EAAE,SAAS,MAAM,KAAK,CAAC,GAAG;AAAA,QAC3D,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAChD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,EAAE,SAAS,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,QACrD,EAAE,QAAQ,KAAK,SAAS,EAAE,gBAAgB,mBAAmB,EAAE;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAAsB,IAA+C;AAC3E,eAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,UAAI,QAAQ,cAAc,IAAI;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,eACZ,aAMA,kBACe;AACf,UAAM,UAAU,cAAc,WAAW;AACzC,UAAM,UAAU,KAAK,UAAU,OAAO;AAEtC,eAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,UAAI,QAAQ,cAAc,iBAAkB;AAC5C,UAAI,QAAQ,UAAU,OAAQ;AAE9B,UAAI;AACF,gBAAQ,UAAU,KAAK,OAAO;AAAA,MAChC,QAAQ;AAEN,gBAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AACF;AAUO,IAAM,wBAAN,MAAuD;AAAA,EACnD,OAAO;AAAA,EAER;AAAA,EACA;AAAA,EACA,gBAA0D,oBAAI,IAAI;AAAA,EAE1E,YAAY,SAAuC;AACjD,SAAK,YAAY,QAAQ;AACzB,SAAK,gBAAgB,QAAQ,iBAAiB;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,SAAoC;AACjD,UAAM,KAAK,KAAK,UAAU,WAAW,GAAG,KAAK,aAAa,GAAG,OAAO,EAAE;AACtE,WAAO,KAAK,UAAU,IAAI,EAAE;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,gBACE,SACA,WACA,QACA,SACQ;AACR,UAAM,OAAO,WAAW;AACxB,UAAM,SAAS,IAAI,gBAAgB,EAAE,UAAU,CAAC;AAChD,QAAI,OAAQ,QAAO,IAAI,UAAU,MAAM;AACvC,WAAO,GAAG,IAAI,aAAa,OAAO,IAAI,MAAM;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UACJ,SACA,WACA,SACe;AAEf,QAAI,CAAC,KAAK,cAAc,IAAI,OAAO,GAAG;AACpC,WAAK,cAAc,IAAI,SAAS,oBAAI,IAAI,CAAC;AAAA,IAC3C;AACA,SAAK,cAAc,IAAI,OAAO,EAAG,IAAI,WAAW,OAAO;AAAA,EAIzD;AAAA,EAEA,MAAM,YAAY,SAAiB,WAAkC;AACnE,UAAM,WAAW,KAAK,cAAc,IAAI,OAAO;AAC/C,QAAI,UAAU;AACZ,eAAS,OAAO,SAAS;AACzB,UAAI,SAAS,SAAS,GAAG;AACvB,aAAK,cAAc,OAAO,OAAO;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,SACA,SACe;AACf,UAAM,OAAO,KAAK,eAAe,OAAO;AAExC,UAAM,KAAK,MAAM,IAAI,QAAQ,wBAAwB;AAAA,MACnD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,OAAO,QAAQ;AAAA,QACf,MAAM,QAAQ;AAAA,MAChB,CAAC;AAAA,IACH,CAAC,CAAC;AAGF,UAAM,WAAW,KAAK,cAAc,IAAI,OAAO;AAC/C,QAAI,UAAU;AACZ,iBAAW,WAAW,SAAS,OAAO,GAAG;AACvC,YAAI;AACF,gBAAM,QAAQ,OAAO;AAAA,QACvB,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cACJ,YACA,UACkB;AAIlB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAe,SAAoC;AACvD,UAAM,WAAW,KAAK,cAAc,IAAI,OAAO;AAC/C,WAAO,WAAW,MAAM,KAAK,SAAS,KAAK,CAAC,IAAI,CAAC;AAAA,EACnD;AAAA,EAEA,MAAM,YACJ,UACA,YACA,SACA,OACe;AAAA,EAGjB;AAAA,EAEA,MAAM,eAAe,UAAkB,YAAmC;AAAA,EAE1E;AAAA,EAEA,MAAM,YAAyB,SAA6C;AAC1E,UAAM,OAAO,KAAK,eAAe,OAAO;AACxC,UAAM,WAAW,MAAM,KAAK,MAAM,IAAI,QAAQ,qBAAqB,CAAC;AACpE,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,cAAc,MAAM;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eACJ,SACoD;AACpD,UAAM,OAAO,KAAK,eAAe,OAAO;AACxC,UAAM,WAAW,MAAM,KAAK,MAAM,IAAI,QAAQ,iBAAiB,CAAC;AAChE,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WACJ,SACA,QACA,OACA,MACkB;AAClB,UAAM,OAAO,KAAK,eAAe,OAAO;AAExC,UAAM,WAAW,MAAM,KAAK;AAAA,MAC1B,IAAI,QAAQ,mBAAmB;AAAA,QAC7B,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,OAAO,KAAK,CAAC;AAAA,MAC9C,CAAC;AAAA,IACH;AAEA,UAAM,SAAU,MAAM,SAAS,KAAK;AACpC,WAAO,OAAO;AAAA,EAChB;AACF;AAKO,SAAS,4BACd,SACuB;AACvB,SAAO,IAAI,sBAAsB,OAAO;AAC1C;","names":[]}
|