@derivation/rpc 0.6.0 → 0.8.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/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/iso.cjs +9 -0
- package/dist/iso.cjs.map +1 -1
- package/dist/iso.d.cts +6 -1
- package/dist/iso.d.ts +6 -1
- package/dist/iso.js +8 -0
- package/dist/iso.js.map +1 -1
- package/dist/shared-worker.cjs +20 -0
- package/dist/shared-worker.cjs.map +1 -1
- package/dist/shared-worker.d.cts +2 -1
- package/dist/shared-worker.d.ts +2 -1
- package/dist/shared-worker.js +20 -0
- package/dist/shared-worker.js.map +1 -1
- package/dist/{stream-types-Q_EqNLtO.d.cts → stream-types-Cn-LsIb9.d.cts} +1 -0
- package/dist/{stream-types-Q_EqNLtO.d.ts → stream-types-Cn-LsIb9.d.ts} +1 -0
- package/dist/websocket-client.cjs +258 -44
- package/dist/websocket-client.cjs.map +1 -1
- package/dist/websocket-client.d.cts +26 -4
- package/dist/websocket-client.d.ts +26 -4
- package/dist/websocket-client.js +258 -44
- package/dist/websocket-client.js.map +1 -1
- package/dist/websocket-server.cjs +45 -5
- package/dist/websocket-server.cjs.map +1 -1
- package/dist/websocket-server.d.cts +8 -1
- package/dist/websocket-server.d.ts +8 -1
- package/dist/websocket-server.js +45 -5
- package/dist/websocket-server.js.map +1 -1
- package/package.json +6 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client-message.ts","../src/websocket/web-socket-client.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const SubscribeMessageSchema = z.object({\n type: z.literal(\"subscribe\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type SubscribeMessage = z.infer<typeof SubscribeMessageSchema>;\n\nexport const UnsubscribeMessageSchema = z.object({\n type: z.literal(\"unsubscribe\"),\n id: z.number(),\n});\nexport type UnsubscribeMessage = z.infer<typeof UnsubscribeMessageSchema>;\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const CallMessageSchema = z.object({\n type: z.literal(\"call\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type CallMessage = z.infer<typeof CallMessageSchema>;\n\nexport const PresenceMessageSchema = z.object({\n type: z.literal(\"presence\"),\n data: z.looseObject({}),\n});\nexport type PresenceMessage = z.infer<typeof PresenceMessageSchema>;\n\nexport const ClientMessageSchema = z.discriminatedUnion(\"type\", [\n SubscribeMessageSchema,\n UnsubscribeMessageSchema,\n HeartbeatMessageSchema,\n CallMessageSchema,\n PresenceMessageSchema,\n]);\nexport type ClientMessage = z.infer<typeof ClientMessageSchema>;\n\nexport function parseClientMessage(data: unknown): ClientMessage {\n return ClientMessageSchema.parse(data);\n}\n\nexport const ClientMessage = {\n subscribe: (\n id: number,\n name: string,\n args: object,\n ): SubscribeMessage => ({\n type: \"subscribe\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n unsubscribe: (id: number): UnsubscribeMessage => ({\n type: \"unsubscribe\",\n id,\n }),\n call: (\n id: number,\n name: string,\n args: object,\n ): CallMessage => ({\n type: \"call\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n presence: (data: object): PresenceMessage => ({\n type: \"presence\",\n data: data as Record<string, unknown>,\n }),\n};\n","import { ClientMessage } from \"../client-message.js\";\nimport { ServerMessage } from \"../server-message.js\";\nimport type { Graph } from \"derivation\";\nimport type {\n Sink,\n StreamSinks,\n RPCDefinition,\n MutationResult,\n} from \"../stream-types.js\";\n\nfunction changer<T extends object, I extends object>(\n sink: Sink<T, I>,\n input: WeakRef<I>,\n): (change: object) => void {\n return (change) => {\n const i = input.deref();\n if (i) {\n sink.apply(change, i);\n }\n };\n}\n\nexport class WebSocketClient<Defs extends RPCDefinition> {\n private nextId = 1;\n private pendingStreams = new Map<number, (snapshot: object) => void>();\n private pendingMutations = new Map<\n number,\n (result: MutationResult<unknown>) => void\n >();\n private activeStreams = new Map<number, (change: object) => void>();\n private heartbeatTimeout: NodeJS.Timeout | undefined;\n private inactivityTimeout: NodeJS.Timeout | undefined;\n\n private registry = new FinalizationRegistry<[number, string]>(\n ([id, name]) => {\n console.log(`🧹 Stream ${id} (${name}) collected — unsubscribing`);\n this.sendMessage(ClientMessage.unsubscribe(id));\n this.activeStreams.delete(id);\n },\n );\n\n private resetHeartbeat() {\n if (this.heartbeatTimeout) {\n clearTimeout(this.heartbeatTimeout);\n }\n\n this.heartbeatTimeout = setTimeout(() => {\n this.sendMessage(ClientMessage.heartbeat());\n }, 10_000);\n }\n\n private resetInactivity() {\n if (this.inactivityTimeout) {\n clearTimeout(this.inactivityTimeout);\n }\n\n this.inactivityTimeout = setTimeout(() => {\n this.close();\n }, 30_000);\n }\n\n constructor(\n private ws: globalThis.WebSocket,\n private sinks: StreamSinks<Defs[\"streams\"]>,\n private graph: Graph,\n ) {\n this.ws.onmessage = (event: MessageEvent) => {\n const message = JSON.parse(event.data as string) as ServerMessage;\n this.handleMessage(message);\n };\n this.resetHeartbeat();\n this.resetInactivity();\n }\n\n private handleMessage(message: ServerMessage) {\n this.resetInactivity();\n\n switch (message.type) {\n case \"snapshot\": {\n console.log(`[WebSocketClient] Received snapshot for stream ${message.id}`);\n const resolve = this.pendingStreams.get(message.id);\n if (resolve) {\n resolve(message.snapshot as object);\n this.pendingStreams.delete(message.id);\n }\n break;\n }\n case \"delta\": {\n const changeCount = Object.keys(message.changes).length;\n console.log(`[WebSocketClient] Received delta with ${changeCount} changes for streams: ${Object.keys(message.changes).join(\", \")}`);\n for (const [idStr, change] of Object.entries(message.changes)) {\n const id = Number(idStr);\n const sink = this.activeStreams.get(id);\n\n if (sink && change && typeof change === \"object\") {\n sink(change);\n } else if (!sink) {\n console.log(`🧹 Sink ${id} GC'd — auto-unsubscribing`);\n this.sendMessage(ClientMessage.unsubscribe(id));\n this.activeStreams.delete(id);\n }\n }\n console.log(\"[WebSocketClient] Stepping graph\");\n this.graph.step();\n break;\n }\n case \"result\": {\n console.log(`[WebSocketClient] Received mutation result for call ${message.id}:`, message.success ? \"success\" : \"error\");\n const resolve = this.pendingMutations.get(message.id);\n if (resolve) {\n if (message.success) {\n resolve({ success: true, value: message.value });\n } else {\n resolve({\n success: false,\n error: message.error || \"Unknown error\",\n });\n }\n this.pendingMutations.delete(message.id);\n }\n break;\n }\n case \"heartbeat\":\n console.log(\"[WebSocketClient] Received heartbeat\");\n break;\n }\n }\n\n private sendMessage(message: ClientMessage) {\n this.resetHeartbeat();\n this.ws.send(JSON.stringify(message));\n }\n\n async run<Key extends keyof Defs[\"streams\"]>(\n key: Key,\n args: Defs[\"streams\"][Key][\"args\"],\n ): Promise<Defs[\"streams\"][Key][\"returnType\"]> {\n console.log(\n `Running stream ${String(key)} with args ${JSON.stringify(args)}`,\n );\n const id = this.nextId++;\n\n this.sendMessage(ClientMessage.subscribe(id, String(key), args));\n\n const snapshot = await new Promise<object>((resolve) => {\n this.pendingStreams.set(id, resolve);\n });\n\n const endpoint = this.sinks[key];\n const sinkAdapter = endpoint(snapshot);\n const { stream, input } = sinkAdapter.build();\n const inputRef = new WeakRef(input);\n this.activeStreams.set(id, changer(sinkAdapter, inputRef));\n this.registry.register(input, [id, String(key)]);\n\n return stream;\n }\n\n async call<Key extends keyof Defs[\"mutations\"]>(\n key: Key,\n args: Defs[\"mutations\"][Key][\"args\"],\n ): Promise<MutationResult<Defs[\"mutations\"][Key][\"result\"]>> {\n console.log(\n `Calling mutation ${String(key)} with args ${JSON.stringify(args)}`,\n );\n const id = this.nextId++;\n\n this.sendMessage(\n ClientMessage.call(id, String(key), args as Record<string, unknown>),\n );\n\n const result = await new Promise<\n MutationResult<Defs[\"mutations\"][Key][\"result\"]>\n >((resolve) => {\n this.pendingMutations.set(\n id,\n resolve as (result: MutationResult<unknown>) => void,\n );\n });\n\n return result;\n }\n\n close() {\n clearTimeout(this.heartbeatTimeout);\n clearTimeout(this.inactivityTimeout);\n try {\n this.ws.close();\n } catch {}\n }\n\n setPresence(value: Record<string, unknown>): void {\n this.sendMessage(ClientMessage.presence(value));\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client-message.ts","../src/websocket/web-socket-client.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const SubscribeMessageSchema = z.object({\n type: z.literal(\"subscribe\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type SubscribeMessage = z.infer<typeof SubscribeMessageSchema>;\n\nexport const UnsubscribeMessageSchema = z.object({\n type: z.literal(\"unsubscribe\"),\n id: z.number(),\n});\nexport type UnsubscribeMessage = z.infer<typeof UnsubscribeMessageSchema>;\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const CallMessageSchema = z.object({\n type: z.literal(\"call\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type CallMessage = z.infer<typeof CallMessageSchema>;\n\nexport const PresenceMessageSchema = z.object({\n type: z.literal(\"presence\"),\n data: z.looseObject({}),\n});\nexport type PresenceMessage = z.infer<typeof PresenceMessageSchema>;\n\nexport const ClientMessageSchema = z.discriminatedUnion(\"type\", [\n SubscribeMessageSchema,\n UnsubscribeMessageSchema,\n HeartbeatMessageSchema,\n CallMessageSchema,\n PresenceMessageSchema,\n]);\nexport type ClientMessage = z.infer<typeof ClientMessageSchema>;\n\nexport function parseClientMessage(data: unknown): ClientMessage {\n return ClientMessageSchema.parse(data);\n}\n\nexport const ClientMessage = {\n subscribe: (\n id: number,\n name: string,\n args: object,\n ): SubscribeMessage => ({\n type: \"subscribe\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n unsubscribe: (id: number): UnsubscribeMessage => ({\n type: \"unsubscribe\",\n id,\n }),\n call: (\n id: number,\n name: string,\n args: object,\n ): CallMessage => ({\n type: \"call\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n presence: (data: object): PresenceMessage => ({\n type: \"presence\",\n data: data as Record<string, unknown>,\n }),\n};\n","import { ClientMessage } from \"../client-message.js\";\nimport { ServerMessage } from \"../server-message.js\";\nimport type { Graph } from \"derivation\";\nimport type {\n Sink,\n StreamSinks,\n RPCDefinition,\n MutationResult,\n} from \"../stream-types.js\";\n\nconst DISCONNECTED_ERROR = \"WebSocket is disconnected\";\nconst CONNECTION_CLOSED_ERROR = \"WebSocket connection closed\";\n\ntype PendingStream = {\n name: string;\n args: Record<string, unknown>;\n resolve: (snapshot: object) => void;\n reject: (error: Error) => void;\n};\n\ntype ActiveStream = {\n name: string;\n args: Record<string, unknown>;\n sink: Sink<object, object>;\n input: WeakRef<object>;\n};\n\nexport type WebSocketReconnectOptions = {\n baseDelayMs?: number;\n maxDelayMs?: number;\n jitterRatio?: number;\n maxReconnectAttempts?: number;\n};\n\nfunction randomJitter(range: number): number {\n if (range <= 0) return 0;\n return (Math.random() * 2 - 1) * range;\n}\n\nexport class WebSocketClient<Defs extends RPCDefinition> {\n private ws: globalThis.WebSocket;\n private nextId = 1;\n private pendingStreams = new Map<number, PendingStream>();\n private pendingMutations = new Map<\n number,\n (result: MutationResult<unknown>) => void\n >();\n private activeStreams = new Map<number, ActiveStream>();\n private heartbeatTimeout: NodeJS.Timeout | undefined;\n private inactivityTimeout: NodeJS.Timeout | undefined;\n private reconnectTimeout: NodeJS.Timeout | undefined;\n private reconnectAttempts = 0;\n private connected = false;\n private closed = false;\n private currentPresence: Record<string, unknown> | undefined;\n\n private readonly reconnectOptions: {\n baseDelayMs: number;\n maxDelayMs: number;\n jitterRatio: number;\n maxReconnectAttempts: number;\n };\n\n private registry = new FinalizationRegistry<[number, string]>(\n ([id, name]) => {\n console.log(`🧹 Stream ${id} (${name}) collected — unsubscribing`);\n this.sendMessage(ClientMessage.unsubscribe(id));\n this.activeStreams.delete(id);\n },\n );\n\n constructor(\n private url: string,\n private sinks: StreamSinks<Defs[\"streams\"]>,\n private graph: Graph,\n reconnectOptions: WebSocketReconnectOptions = {},\n ) {\n this.reconnectOptions = {\n baseDelayMs: reconnectOptions.baseDelayMs ?? 500,\n maxDelayMs: reconnectOptions.maxDelayMs ?? 15_000,\n jitterRatio: reconnectOptions.jitterRatio ?? 0.2,\n maxReconnectAttempts:\n reconnectOptions.maxReconnectAttempts ?? Number.POSITIVE_INFINITY,\n };\n\n this.ws = this.createSocket();\n this.bindSocket(this.ws);\n this.connected = this.isSocketOpen();\n if (this.connected) {\n this.resetHeartbeat();\n this.resetInactivity();\n }\n }\n\n private createSocket(): globalThis.WebSocket {\n return new globalThis.WebSocket(this.url);\n }\n\n private bindSocket(socket: globalThis.WebSocket) {\n this.ws = socket;\n\n socket.onopen = () => {\n if (this.closed || this.ws !== socket) {\n return;\n }\n this.handleSocketOpen();\n };\n\n socket.onmessage = (event: MessageEvent) => {\n if (this.closed || this.ws !== socket) {\n return;\n }\n const message = JSON.parse(event.data as string) as ServerMessage;\n this.handleMessage(message);\n };\n\n socket.onerror = () => {\n if (this.closed || this.ws !== socket) {\n return;\n }\n this.handleTransportClose();\n };\n\n socket.onclose = () => {\n if (this.closed || this.ws !== socket) {\n return;\n }\n this.handleTransportClose();\n };\n }\n\n private handleSocketOpen() {\n this.connected = true;\n this.reconnectAttempts = 0;\n\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = undefined;\n }\n\n this.resetHeartbeat();\n this.resetInactivity();\n this.resubscribeAll();\n\n if (this.currentPresence) {\n this.sendMessage(ClientMessage.presence(this.currentPresence));\n }\n }\n\n private handleTransportClose() {\n if (!this.connected && this.reconnectTimeout) {\n return;\n }\n\n this.connected = false;\n\n if (this.heartbeatTimeout) {\n clearTimeout(this.heartbeatTimeout);\n this.heartbeatTimeout = undefined;\n }\n if (this.inactivityTimeout) {\n clearTimeout(this.inactivityTimeout);\n this.inactivityTimeout = undefined;\n }\n\n this.failPendingMutations(DISCONNECTED_ERROR);\n\n this.scheduleReconnect();\n }\n\n private scheduleReconnect() {\n if (this.closed || this.reconnectTimeout) {\n return;\n }\n\n if (this.reconnectAttempts >= this.reconnectOptions.maxReconnectAttempts) {\n this.failPendingStreams(CONNECTION_CLOSED_ERROR);\n return;\n }\n\n const exponent = Math.min(this.reconnectAttempts, 30);\n const baseDelay = this.reconnectOptions.baseDelayMs * 2 ** exponent;\n const cappedDelay = Math.min(baseDelay, this.reconnectOptions.maxDelayMs);\n const jitter = cappedDelay * this.reconnectOptions.jitterRatio;\n const delay = Math.max(0, Math.round(cappedDelay + randomJitter(jitter)));\n\n this.reconnectAttempts += 1;\n\n this.reconnectTimeout = setTimeout(() => {\n this.reconnectTimeout = undefined;\n if (this.closed) {\n return;\n }\n\n try {\n const socket = this.createSocket();\n this.bindSocket(socket);\n this.connected = this.isSocketOpen();\n if (this.connected) {\n this.handleSocketOpen();\n }\n } catch (err) {\n console.error(\"Failed to create reconnect socket:\", err);\n this.scheduleReconnect();\n }\n }, delay);\n }\n\n private resubscribeAll() {\n for (const [id, pending] of this.pendingStreams) {\n this.sendMessage(ClientMessage.subscribe(id, pending.name, pending.args));\n }\n\n for (const [id, active] of this.activeStreams) {\n if (!active.input.deref()) {\n this.activeStreams.delete(id);\n continue;\n }\n this.sendMessage(ClientMessage.subscribe(id, active.name, active.args));\n }\n }\n\n private failPendingStreams(message: string) {\n for (const [id, pending] of this.pendingStreams) {\n pending.reject(new Error(message));\n this.pendingStreams.delete(id);\n }\n }\n\n private failPendingMutations(message: string) {\n for (const [id, resolve] of this.pendingMutations) {\n resolve({ success: false, error: message });\n this.pendingMutations.delete(id);\n }\n }\n\n private resetHeartbeat() {\n if (!this.connected || this.closed) {\n return;\n }\n\n if (this.heartbeatTimeout) {\n clearTimeout(this.heartbeatTimeout);\n }\n\n this.heartbeatTimeout = setTimeout(() => {\n this.sendMessage(ClientMessage.heartbeat());\n }, 10_000);\n }\n\n private resetInactivity() {\n if (!this.connected || this.closed) {\n return;\n }\n\n if (this.inactivityTimeout) {\n clearTimeout(this.inactivityTimeout);\n }\n\n this.inactivityTimeout = setTimeout(() => {\n try {\n this.ws.close();\n } catch {\n this.handleTransportClose();\n }\n }, 30_000);\n }\n\n private handleMessage(message: ServerMessage) {\n this.resetInactivity();\n\n switch (message.type) {\n case \"snapshot\": {\n console.log(`[WebSocketClient] Received snapshot for stream ${message.id}`);\n\n const pending = this.pendingStreams.get(message.id);\n if (pending) {\n pending.resolve(message.snapshot as object);\n this.pendingStreams.delete(message.id);\n break;\n }\n\n const active = this.activeStreams.get(message.id);\n if (!active) {\n break;\n }\n\n const input = active.input.deref();\n if (!input) {\n this.sendMessage(ClientMessage.unsubscribe(message.id));\n this.activeStreams.delete(message.id);\n break;\n }\n\n active.sink.reset(message.snapshot as object, input);\n this.graph.step();\n break;\n }\n case \"delta\": {\n const changeCount = Object.keys(message.changes).length;\n console.log(\n `[WebSocketClient] Received delta with ${changeCount} changes for streams: ${Object.keys(message.changes).join(\", \")}`,\n );\n\n for (const [idStr, change] of Object.entries(message.changes)) {\n const id = Number(idStr);\n const active = this.activeStreams.get(id);\n\n if (!active) {\n this.sendMessage(ClientMessage.unsubscribe(id));\n continue;\n }\n\n const input = active.input.deref();\n if (!input) {\n this.sendMessage(ClientMessage.unsubscribe(id));\n this.activeStreams.delete(id);\n continue;\n }\n\n if (change && typeof change === \"object\") {\n active.sink.apply(change, input);\n }\n }\n\n console.log(\"[WebSocketClient] Stepping graph\");\n this.graph.step();\n break;\n }\n case \"result\": {\n console.log(\n `[WebSocketClient] Received mutation result for call ${message.id}:`,\n message.success ? \"success\" : \"error\",\n );\n\n const resolve = this.pendingMutations.get(message.id);\n if (!resolve) {\n break;\n }\n\n if (message.success) {\n resolve({ success: true, value: message.value });\n } else {\n resolve({\n success: false,\n error: message.error || \"Unknown error\",\n });\n }\n this.pendingMutations.delete(message.id);\n break;\n }\n case \"heartbeat\":\n console.log(\"[WebSocketClient] Received heartbeat\");\n break;\n }\n }\n\n private isSocketOpen() {\n return this.ws.readyState === this.ws.OPEN;\n }\n\n private sendMessage(message: ClientMessage): boolean {\n if (this.closed) {\n return false;\n }\n\n if (!this.isSocketOpen()) {\n return false;\n }\n\n this.resetHeartbeat();\n\n try {\n this.ws.send(JSON.stringify(message));\n return true;\n } catch {\n this.handleTransportClose();\n return false;\n }\n }\n\n async run<Key extends keyof Defs[\"streams\"]>(\n key: Key,\n args: Defs[\"streams\"][Key][\"args\"],\n ): Promise<Defs[\"streams\"][Key][\"returnType\"]> {\n console.log(\n `Running stream ${String(key)} with args ${JSON.stringify(args)}`,\n );\n\n const id = this.nextId++;\n const name = String(key);\n const typedArgs = args as Record<string, unknown>;\n\n const snapshot = await new Promise<object>((resolve, reject) => {\n this.pendingStreams.set(id, { name, args: typedArgs, resolve, reject });\n this.sendMessage(ClientMessage.subscribe(id, name, typedArgs));\n });\n\n const endpoint = this.sinks[key];\n const sinkAdapter = endpoint(snapshot);\n const { stream, input } = sinkAdapter.build();\n const inputRef = new WeakRef(input);\n\n this.activeStreams.set(id, {\n name,\n args: typedArgs,\n sink: sinkAdapter as Sink<object, object>,\n input: inputRef as WeakRef<object>,\n });\n\n this.registry.register(input, [id, name]);\n return stream;\n }\n\n async call<Key extends keyof Defs[\"mutations\"]>(\n key: Key,\n args: Defs[\"mutations\"][Key][\"args\"],\n ): Promise<MutationResult<Defs[\"mutations\"][Key][\"result\"]>> {\n console.log(\n `Calling mutation ${String(key)} with args ${JSON.stringify(args)}`,\n );\n\n if (!this.connected || !this.isSocketOpen()) {\n return {\n success: false,\n error: DISCONNECTED_ERROR,\n };\n }\n\n const id = this.nextId++;\n\n const resultPromise = new Promise<\n MutationResult<Defs[\"mutations\"][Key][\"result\"]>\n >((resolve) => {\n this.pendingMutations.set(\n id,\n resolve as (result: MutationResult<unknown>) => void,\n );\n });\n\n const sent = this.sendMessage(\n ClientMessage.call(id, String(key), args as Record<string, unknown>),\n );\n\n if (!sent) {\n this.pendingMutations.delete(id);\n return {\n success: false,\n error: DISCONNECTED_ERROR,\n };\n }\n\n return resultPromise;\n }\n\n close() {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.connected = false;\n\n if (this.heartbeatTimeout) {\n clearTimeout(this.heartbeatTimeout);\n this.heartbeatTimeout = undefined;\n }\n if (this.inactivityTimeout) {\n clearTimeout(this.inactivityTimeout);\n this.inactivityTimeout = undefined;\n }\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = undefined;\n }\n\n this.failPendingStreams(CONNECTION_CLOSED_ERROR);\n this.failPendingMutations(CONNECTION_CLOSED_ERROR);\n\n try {\n this.ws.close();\n } catch {}\n }\n\n setPresence(value: Record<string, unknown>): void {\n this.currentPresence = value;\n this.sendMessage(ClientMessage.presence(value));\n }\n}\n"],"mappings":";AAAA,SAAS,SAAS;AAEX,IAAM,yBAAyB,EAAE,OAAO;AAAA,EAC7C,MAAM,EAAE,QAAQ,WAAW;AAAA,EAC3B,IAAI,EAAE,OAAO;AAAA,EACb,MAAM,EAAE,OAAO;AAAA,EACf,MAAM,EAAE,YAAY,CAAC,CAAC;AACxB,CAAC;AAGM,IAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,MAAM,EAAE,QAAQ,aAAa;AAAA,EAC7B,IAAI,EAAE,OAAO;AACf,CAAC;AAGM,IAAM,yBAAyB,EAAE,OAAO;AAAA,EAC7C,MAAM,EAAE,QAAQ,WAAW;AAC7B,CAAC;AAGM,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,MAAM,EAAE,QAAQ,MAAM;AAAA,EACtB,IAAI,EAAE,OAAO;AAAA,EACb,MAAM,EAAE,OAAO;AAAA,EACf,MAAM,EAAE,YAAY,CAAC,CAAC;AACxB,CAAC;AAGM,IAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,MAAM,EAAE,QAAQ,UAAU;AAAA,EAC1B,MAAM,EAAE,YAAY,CAAC,CAAC;AACxB,CAAC;AAGM,IAAM,sBAAsB,EAAE,mBAAmB,QAAQ;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,IAAM,gBAAgB;AAAA,EAC3B,WAAW,CACT,IACA,MACA,UACsB;AAAA,IACtB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,aAAa,CAAC,QAAoC;AAAA,IAChD,MAAM;AAAA,IACN;AAAA,EACF;AAAA,EACA,MAAM,CACJ,IACA,MACA,UACiB;AAAA,IACjB,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,WAAW,OAAyB;AAAA,IAClC,MAAM;AAAA,EACR;AAAA,EACA,UAAU,CAAC,UAAmC;AAAA,IAC5C,MAAM;AAAA,IACN;AAAA,EACF;AACF;;;ACtEA,IAAM,qBAAqB;AAC3B,IAAM,0BAA0B;AAuBhC,SAAS,aAAa,OAAuB;AAC3C,MAAI,SAAS,EAAG,QAAO;AACvB,UAAQ,KAAK,OAAO,IAAI,IAAI,KAAK;AACnC;AAEO,IAAM,kBAAN,MAAkD;AAAA,EAgCvD,YACU,KACA,OACA,OACR,mBAA8C,CAAC,GAC/C;AAJQ;AACA;AACA;AAjCV,SAAQ,SAAS;AACjB,SAAQ,iBAAiB,oBAAI,IAA2B;AACxD,SAAQ,mBAAmB,oBAAI,IAG7B;AACF,SAAQ,gBAAgB,oBAAI,IAA0B;AAItD,SAAQ,oBAAoB;AAC5B,SAAQ,YAAY;AACpB,SAAQ,SAAS;AAUjB,SAAQ,WAAW,IAAI;AAAA,MACrB,CAAC,CAAC,IAAI,IAAI,MAAM;AACd,gBAAQ,IAAI,oBAAa,EAAE,KAAK,IAAI,kCAA6B;AACjE,aAAK,YAAY,cAAc,YAAY,EAAE,CAAC;AAC9C,aAAK,cAAc,OAAO,EAAE;AAAA,MAC9B;AAAA,IACF;AArEF;AA6EI,SAAK,mBAAmB;AAAA,MACtB,cAAa,sBAAiB,gBAAjB,YAAgC;AAAA,MAC7C,aAAY,sBAAiB,eAAjB,YAA+B;AAAA,MAC3C,cAAa,sBAAiB,gBAAjB,YAAgC;AAAA,MAC7C,uBACE,sBAAiB,yBAAjB,YAAyC,OAAO;AAAA,IACpD;AAEA,SAAK,KAAK,KAAK,aAAa;AAC5B,SAAK,WAAW,KAAK,EAAE;AACvB,SAAK,YAAY,KAAK,aAAa;AACnC,QAAI,KAAK,WAAW;AAClB,WAAK,eAAe;AACpB,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA,EAEQ,eAAqC;AAC3C,WAAO,IAAI,WAAW,UAAU,KAAK,GAAG;AAAA,EAC1C;AAAA,EAEQ,WAAW,QAA8B;AAC/C,SAAK,KAAK;AAEV,WAAO,SAAS,MAAM;AACpB,UAAI,KAAK,UAAU,KAAK,OAAO,QAAQ;AACrC;AAAA,MACF;AACA,WAAK,iBAAiB;AAAA,IACxB;AAEA,WAAO,YAAY,CAAC,UAAwB;AAC1C,UAAI,KAAK,UAAU,KAAK,OAAO,QAAQ;AACrC;AAAA,MACF;AACA,YAAM,UAAU,KAAK,MAAM,MAAM,IAAc;AAC/C,WAAK,cAAc,OAAO;AAAA,IAC5B;AAEA,WAAO,UAAU,MAAM;AACrB,UAAI,KAAK,UAAU,KAAK,OAAO,QAAQ;AACrC;AAAA,MACF;AACA,WAAK,qBAAqB;AAAA,IAC5B;AAEA,WAAO,UAAU,MAAM;AACrB,UAAI,KAAK,UAAU,KAAK,OAAO,QAAQ;AACrC;AAAA,MACF;AACA,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AAAA,EAEQ,mBAAmB;AACzB,SAAK,YAAY;AACjB,SAAK,oBAAoB;AAEzB,QAAI,KAAK,kBAAkB;AACzB,mBAAa,KAAK,gBAAgB;AAClC,WAAK,mBAAmB;AAAA,IAC1B;AAEA,SAAK,eAAe;AACpB,SAAK,gBAAgB;AACrB,SAAK,eAAe;AAEpB,QAAI,KAAK,iBAAiB;AACxB,WAAK,YAAY,cAAc,SAAS,KAAK,eAAe,CAAC;AAAA,IAC/D;AAAA,EACF;AAAA,EAEQ,uBAAuB;AAC7B,QAAI,CAAC,KAAK,aAAa,KAAK,kBAAkB;AAC5C;AAAA,IACF;AAEA,SAAK,YAAY;AAEjB,QAAI,KAAK,kBAAkB;AACzB,mBAAa,KAAK,gBAAgB;AAClC,WAAK,mBAAmB;AAAA,IAC1B;AACA,QAAI,KAAK,mBAAmB;AAC1B,mBAAa,KAAK,iBAAiB;AACnC,WAAK,oBAAoB;AAAA,IAC3B;AAEA,SAAK,qBAAqB,kBAAkB;AAE5C,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEQ,oBAAoB;AAC1B,QAAI,KAAK,UAAU,KAAK,kBAAkB;AACxC;AAAA,IACF;AAEA,QAAI,KAAK,qBAAqB,KAAK,iBAAiB,sBAAsB;AACxE,WAAK,mBAAmB,uBAAuB;AAC/C;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,IAAI,KAAK,mBAAmB,EAAE;AACpD,UAAM,YAAY,KAAK,iBAAiB,cAAc,KAAK;AAC3D,UAAM,cAAc,KAAK,IAAI,WAAW,KAAK,iBAAiB,UAAU;AACxE,UAAM,SAAS,cAAc,KAAK,iBAAiB;AACnD,UAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,cAAc,aAAa,MAAM,CAAC,CAAC;AAExE,SAAK,qBAAqB;AAE1B,SAAK,mBAAmB,WAAW,MAAM;AACvC,WAAK,mBAAmB;AACxB,UAAI,KAAK,QAAQ;AACf;AAAA,MACF;AAEA,UAAI;AACF,cAAM,SAAS,KAAK,aAAa;AACjC,aAAK,WAAW,MAAM;AACtB,aAAK,YAAY,KAAK,aAAa;AACnC,YAAI,KAAK,WAAW;AAClB,eAAK,iBAAiB;AAAA,QACxB;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,MAAM,sCAAsC,GAAG;AACvD,aAAK,kBAAkB;AAAA,MACzB;AAAA,IACF,GAAG,KAAK;AAAA,EACV;AAAA,EAEQ,iBAAiB;AACvB,eAAW,CAAC,IAAI,OAAO,KAAK,KAAK,gBAAgB;AAC/C,WAAK,YAAY,cAAc,UAAU,IAAI,QAAQ,MAAM,QAAQ,IAAI,CAAC;AAAA,IAC1E;AAEA,eAAW,CAAC,IAAI,MAAM,KAAK,KAAK,eAAe;AAC7C,UAAI,CAAC,OAAO,MAAM,MAAM,GAAG;AACzB,aAAK,cAAc,OAAO,EAAE;AAC5B;AAAA,MACF;AACA,WAAK,YAAY,cAAc,UAAU,IAAI,OAAO,MAAM,OAAO,IAAI,CAAC;AAAA,IACxE;AAAA,EACF;AAAA,EAEQ,mBAAmB,SAAiB;AAC1C,eAAW,CAAC,IAAI,OAAO,KAAK,KAAK,gBAAgB;AAC/C,cAAQ,OAAO,IAAI,MAAM,OAAO,CAAC;AACjC,WAAK,eAAe,OAAO,EAAE;AAAA,IAC/B;AAAA,EACF;AAAA,EAEQ,qBAAqB,SAAiB;AAC5C,eAAW,CAAC,IAAI,OAAO,KAAK,KAAK,kBAAkB;AACjD,cAAQ,EAAE,SAAS,OAAO,OAAO,QAAQ,CAAC;AAC1C,WAAK,iBAAiB,OAAO,EAAE;AAAA,IACjC;AAAA,EACF;AAAA,EAEQ,iBAAiB;AACvB,QAAI,CAAC,KAAK,aAAa,KAAK,QAAQ;AAClC;AAAA,IACF;AAEA,QAAI,KAAK,kBAAkB;AACzB,mBAAa,KAAK,gBAAgB;AAAA,IACpC;AAEA,SAAK,mBAAmB,WAAW,MAAM;AACvC,WAAK,YAAY,cAAc,UAAU,CAAC;AAAA,IAC5C,GAAG,GAAM;AAAA,EACX;AAAA,EAEQ,kBAAkB;AACxB,QAAI,CAAC,KAAK,aAAa,KAAK,QAAQ;AAClC;AAAA,IACF;AAEA,QAAI,KAAK,mBAAmB;AAC1B,mBAAa,KAAK,iBAAiB;AAAA,IACrC;AAEA,SAAK,oBAAoB,WAAW,MAAM;AACxC,UAAI;AACF,aAAK,GAAG,MAAM;AAAA,MAChB,SAAQ;AACN,aAAK,qBAAqB;AAAA,MAC5B;AAAA,IACF,GAAG,GAAM;AAAA,EACX;AAAA,EAEQ,cAAc,SAAwB;AAC5C,SAAK,gBAAgB;AAErB,YAAQ,QAAQ,MAAM;AAAA,MACpB,KAAK,YAAY;AACf,gBAAQ,IAAI,kDAAkD,QAAQ,EAAE,EAAE;AAE1E,cAAM,UAAU,KAAK,eAAe,IAAI,QAAQ,EAAE;AAClD,YAAI,SAAS;AACX,kBAAQ,QAAQ,QAAQ,QAAkB;AAC1C,eAAK,eAAe,OAAO,QAAQ,EAAE;AACrC;AAAA,QACF;AAEA,cAAM,SAAS,KAAK,cAAc,IAAI,QAAQ,EAAE;AAChD,YAAI,CAAC,QAAQ;AACX;AAAA,QACF;AAEA,cAAM,QAAQ,OAAO,MAAM,MAAM;AACjC,YAAI,CAAC,OAAO;AACV,eAAK,YAAY,cAAc,YAAY,QAAQ,EAAE,CAAC;AACtD,eAAK,cAAc,OAAO,QAAQ,EAAE;AACpC;AAAA,QACF;AAEA,eAAO,KAAK,MAAM,QAAQ,UAAoB,KAAK;AACnD,aAAK,MAAM,KAAK;AAChB;AAAA,MACF;AAAA,MACA,KAAK,SAAS;AACZ,cAAM,cAAc,OAAO,KAAK,QAAQ,OAAO,EAAE;AACjD,gBAAQ;AAAA,UACN,yCAAyC,WAAW,yBAAyB,OAAO,KAAK,QAAQ,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,QACtH;AAEA,mBAAW,CAAC,OAAO,MAAM,KAAK,OAAO,QAAQ,QAAQ,OAAO,GAAG;AAC7D,gBAAM,KAAK,OAAO,KAAK;AACvB,gBAAM,SAAS,KAAK,cAAc,IAAI,EAAE;AAExC,cAAI,CAAC,QAAQ;AACX,iBAAK,YAAY,cAAc,YAAY,EAAE,CAAC;AAC9C;AAAA,UACF;AAEA,gBAAM,QAAQ,OAAO,MAAM,MAAM;AACjC,cAAI,CAAC,OAAO;AACV,iBAAK,YAAY,cAAc,YAAY,EAAE,CAAC;AAC9C,iBAAK,cAAc,OAAO,EAAE;AAC5B;AAAA,UACF;AAEA,cAAI,UAAU,OAAO,WAAW,UAAU;AACxC,mBAAO,KAAK,MAAM,QAAQ,KAAK;AAAA,UACjC;AAAA,QACF;AAEA,gBAAQ,IAAI,kCAAkC;AAC9C,aAAK,MAAM,KAAK;AAChB;AAAA,MACF;AAAA,MACA,KAAK,UAAU;AACb,gBAAQ;AAAA,UACN,uDAAuD,QAAQ,EAAE;AAAA,UACjE,QAAQ,UAAU,YAAY;AAAA,QAChC;AAEA,cAAM,UAAU,KAAK,iBAAiB,IAAI,QAAQ,EAAE;AACpD,YAAI,CAAC,SAAS;AACZ;AAAA,QACF;AAEA,YAAI,QAAQ,SAAS;AACnB,kBAAQ,EAAE,SAAS,MAAM,OAAO,QAAQ,MAAM,CAAC;AAAA,QACjD,OAAO;AACL,kBAAQ;AAAA,YACN,SAAS;AAAA,YACT,OAAO,QAAQ,SAAS;AAAA,UAC1B,CAAC;AAAA,QACH;AACA,aAAK,iBAAiB,OAAO,QAAQ,EAAE;AACvC;AAAA,MACF;AAAA,MACA,KAAK;AACH,gBAAQ,IAAI,sCAAsC;AAClD;AAAA,IACJ;AAAA,EACF;AAAA,EAEQ,eAAe;AACrB,WAAO,KAAK,GAAG,eAAe,KAAK,GAAG;AAAA,EACxC;AAAA,EAEQ,YAAY,SAAiC;AACnD,QAAI,KAAK,QAAQ;AACf,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,KAAK,aAAa,GAAG;AACxB,aAAO;AAAA,IACT;AAEA,SAAK,eAAe;AAEpB,QAAI;AACF,WAAK,GAAG,KAAK,KAAK,UAAU,OAAO,CAAC;AACpC,aAAO;AAAA,IACT,SAAQ;AACN,WAAK,qBAAqB;AAC1B,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,IACJ,KACA,MAC6C;AAC7C,YAAQ;AAAA,MACN,kBAAkB,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,IAAI,CAAC;AAAA,IACjE;AAEA,UAAM,KAAK,KAAK;AAChB,UAAM,OAAO,OAAO,GAAG;AACvB,UAAM,YAAY;AAElB,UAAM,WAAW,MAAM,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9D,WAAK,eAAe,IAAI,IAAI,EAAE,MAAM,MAAM,WAAW,SAAS,OAAO,CAAC;AACtE,WAAK,YAAY,cAAc,UAAU,IAAI,MAAM,SAAS,CAAC;AAAA,IAC/D,CAAC;AAED,UAAM,WAAW,KAAK,MAAM,GAAG;AAC/B,UAAM,cAAc,SAAS,QAAQ;AACrC,UAAM,EAAE,QAAQ,MAAM,IAAI,YAAY,MAAM;AAC5C,UAAM,WAAW,IAAI,QAAQ,KAAK;AAElC,SAAK,cAAc,IAAI,IAAI;AAAA,MACzB;AAAA,MACA,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,IACT,CAAC;AAED,SAAK,SAAS,SAAS,OAAO,CAAC,IAAI,IAAI,CAAC;AACxC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,KACJ,KACA,MAC2D;AAC3D,YAAQ;AAAA,MACN,oBAAoB,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,IAAI,CAAC;AAAA,IACnE;AAEA,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,aAAa,GAAG;AAC3C,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,KAAK,KAAK;AAEhB,UAAM,gBAAgB,IAAI,QAExB,CAAC,YAAY;AACb,WAAK,iBAAiB;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAED,UAAM,OAAO,KAAK;AAAA,MAChB,cAAc,KAAK,IAAI,OAAO,GAAG,GAAG,IAA+B;AAAA,IACrE;AAEA,QAAI,CAAC,MAAM;AACT,WAAK,iBAAiB,OAAO,EAAE;AAC/B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ;AACN,QAAI,KAAK,QAAQ;AACf;AAAA,IACF;AAEA,SAAK,SAAS;AACd,SAAK,YAAY;AAEjB,QAAI,KAAK,kBAAkB;AACzB,mBAAa,KAAK,gBAAgB;AAClC,WAAK,mBAAmB;AAAA,IAC1B;AACA,QAAI,KAAK,mBAAmB;AAC1B,mBAAa,KAAK,iBAAiB;AACnC,WAAK,oBAAoB;AAAA,IAC3B;AACA,QAAI,KAAK,kBAAkB;AACzB,mBAAa,KAAK,gBAAgB;AAClC,WAAK,mBAAmB;AAAA,IAC1B;AAEA,SAAK,mBAAmB,uBAAuB;AAC/C,SAAK,qBAAqB,uBAAuB;AAEjD,QAAI;AACF,WAAK,GAAG,MAAM;AAAA,IAChB,SAAQ;AAAA,IAAC;AAAA,EACX;AAAA,EAEA,YAAY,OAAsC;AAChD,SAAK,kBAAkB;AACvB,SAAK,YAAY,cAAc,SAAS,KAAK,CAAC;AAAA,EAChD;AACF;","names":[]}
|
|
@@ -24,6 +24,7 @@ __export(web_socket_server_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(web_socket_server_exports);
|
|
26
26
|
var import_url = require("url");
|
|
27
|
+
var import_http = require("http");
|
|
27
28
|
var import_ws = require("ws");
|
|
28
29
|
|
|
29
30
|
// src/client-message.ts
|
|
@@ -383,8 +384,32 @@ var WeakList = class {
|
|
|
383
384
|
};
|
|
384
385
|
|
|
385
386
|
// src/websocket/web-socket-server.ts
|
|
387
|
+
function rejectUpgrade(socket, statusCode, reason) {
|
|
388
|
+
var _a;
|
|
389
|
+
const safeStatusCode = Number.isInteger(statusCode) ? statusCode : 403;
|
|
390
|
+
const statusText = (_a = import_http.STATUS_CODES[safeStatusCode]) != null ? _a : "Forbidden";
|
|
391
|
+
const body = reason || statusText;
|
|
392
|
+
const headers = [
|
|
393
|
+
`HTTP/1.1 ${safeStatusCode} ${statusText}`,
|
|
394
|
+
"Connection: close",
|
|
395
|
+
"Content-Type: text/plain; charset=utf-8",
|
|
396
|
+
`Content-Length: ${Buffer.byteLength(body)}`,
|
|
397
|
+
"",
|
|
398
|
+
body
|
|
399
|
+
];
|
|
400
|
+
try {
|
|
401
|
+
socket.write(headers.join("\r\n"));
|
|
402
|
+
} finally {
|
|
403
|
+
socket.destroy();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
386
406
|
function setupWebSocketServer(graph, server, streamEndpoints, mutationEndpoints, options) {
|
|
387
|
-
const {
|
|
407
|
+
const {
|
|
408
|
+
createContext,
|
|
409
|
+
presenceHandler,
|
|
410
|
+
path = "/api/ws",
|
|
411
|
+
verifyUpgradeRequest
|
|
412
|
+
} = options;
|
|
388
413
|
const clients = new WeakList();
|
|
389
414
|
graph.afterStep(() => {
|
|
390
415
|
for (const client of clients) {
|
|
@@ -422,14 +447,29 @@ function setupWebSocketServer(graph, server, streamEndpoints, mutationEndpoints,
|
|
|
422
447
|
}
|
|
423
448
|
});
|
|
424
449
|
server.on("upgrade", (req, socket, head) => {
|
|
450
|
+
var _a;
|
|
425
451
|
const { pathname } = (0, import_url.parse)(req.url || "/", true);
|
|
426
|
-
if (pathname
|
|
452
|
+
if (pathname !== path) {
|
|
453
|
+
socket.destroy();
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
Promise.resolve((_a = verifyUpgradeRequest == null ? void 0 : verifyUpgradeRequest(req)) != null ? _a : true).then((verificationResult) => {
|
|
457
|
+
var _a2, _b;
|
|
458
|
+
if (verificationResult !== true) {
|
|
459
|
+
rejectUpgrade(
|
|
460
|
+
socket,
|
|
461
|
+
(_a2 = verificationResult.statusCode) != null ? _a2 : 403,
|
|
462
|
+
(_b = verificationResult.reason) != null ? _b : "Forbidden"
|
|
463
|
+
);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
427
466
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
428
467
|
wss.emit("connection", ws, req);
|
|
429
468
|
});
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
469
|
+
}).catch((err) => {
|
|
470
|
+
console.error("Error verifying upgrade request:", err);
|
|
471
|
+
rejectUpgrade(socket, 500, "Internal Server Error");
|
|
472
|
+
});
|
|
433
473
|
});
|
|
434
474
|
}
|
|
435
475
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/websocket/web-socket-server.ts","../src/client-message.ts","../src/server-message.ts","../src/websocket/queue.ts","../src/websocket/rate-limiter.ts","../src/websocket/web-socket-client-handler.ts","../src/weak-list.ts"],"sourcesContent":["import { parse } from \"url\";\nimport { Server, IncomingMessage } from \"http\";\nimport { WebSocketServer, WebSocket, RawData } from \"ws\";\nimport { WebSocketClientHandler } from \"./web-socket-client-handler.js\";\nimport WeakList from \"../weak-list.js\";\nimport {\n StreamEndpoints,\n MutationEndpoints,\n RPCDefinition,\n} from \"../stream-types.js\";\nimport { Graph } from \"derivation\";\nimport { PresenceHandler } from \"../presence-manager.js\";\n\nexport type WebSocketServerOptions<Ctx> = {\n createContext: (ws: WebSocket, req: IncomingMessage) => Ctx | Promise<Ctx>;\n presenceHandler?: PresenceHandler;\n path?: string;\n};\n\nexport function setupWebSocketServer<Defs extends RPCDefinition, Ctx = void>(\n graph: Graph,\n server: Server,\n streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>,\n mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>,\n options: WebSocketServerOptions<Ctx>,\n) {\n const { createContext, presenceHandler, path = \"/api/ws\" } = options;\n const clients = new WeakList<WebSocketClientHandler<Defs, Ctx>>();\n\n graph.afterStep(() => {\n for (const client of clients) {\n client.handleStep();\n }\n });\n\n const wss = new WebSocketServer({ noServer: true, maxPayload: 100 * 1024 });\n\n // Handle WebSocket connections for reactive streams\n wss.on(\"connection\", (ws, req) => {\n const { pathname } = parse(req.url || \"/\", true);\n if (pathname === path) {\n const messageBuffer: RawData[] = [];\n let client: WebSocketClientHandler<Defs, Ctx> | null = null;\n\n // Set up temporary message handler to buffer messages\n const tempMessageHandler = (msg: RawData) => {\n messageBuffer.push(msg);\n };\n ws.on(\"message\", tempMessageHandler);\n\n // Create context (handle both sync and async)\n Promise.resolve(createContext(ws, req))\n .then((context) => {\n // Remove temporary handler\n ws.removeListener(\"message\", tempMessageHandler);\n\n // Create client handler\n client = new WebSocketClientHandler<Defs, Ctx>(\n ws,\n context,\n streamEndpoints,\n mutationEndpoints,\n presenceHandler,\n );\n clients.add(client);\n\n // Process buffered messages\n for (const msg of messageBuffer) {\n client.handleMessage(msg.toString());\n }\n messageBuffer.length = 0;\n })\n .catch((err) => {\n console.error(\"Error creating context:\", err);\n ws.close();\n });\n }\n });\n\n // Handle HTTP upgrade (for /api/ws)\n server.on(\"upgrade\", (req, socket, head) => {\n const { pathname } = parse(req.url || \"/\", true);\n\n if (pathname === \"/api/ws\") {\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n } else {\n socket.destroy();\n }\n });\n}\n","import { z } from \"zod\";\n\nexport const SubscribeMessageSchema = z.object({\n type: z.literal(\"subscribe\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type SubscribeMessage = z.infer<typeof SubscribeMessageSchema>;\n\nexport const UnsubscribeMessageSchema = z.object({\n type: z.literal(\"unsubscribe\"),\n id: z.number(),\n});\nexport type UnsubscribeMessage = z.infer<typeof UnsubscribeMessageSchema>;\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const CallMessageSchema = z.object({\n type: z.literal(\"call\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type CallMessage = z.infer<typeof CallMessageSchema>;\n\nexport const PresenceMessageSchema = z.object({\n type: z.literal(\"presence\"),\n data: z.looseObject({}),\n});\nexport type PresenceMessage = z.infer<typeof PresenceMessageSchema>;\n\nexport const ClientMessageSchema = z.discriminatedUnion(\"type\", [\n SubscribeMessageSchema,\n UnsubscribeMessageSchema,\n HeartbeatMessageSchema,\n CallMessageSchema,\n PresenceMessageSchema,\n]);\nexport type ClientMessage = z.infer<typeof ClientMessageSchema>;\n\nexport function parseClientMessage(data: unknown): ClientMessage {\n return ClientMessageSchema.parse(data);\n}\n\nexport const ClientMessage = {\n subscribe: (\n id: number,\n name: string,\n args: object,\n ): SubscribeMessage => ({\n type: \"subscribe\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n unsubscribe: (id: number): UnsubscribeMessage => ({\n type: \"unsubscribe\",\n id,\n }),\n call: (\n id: number,\n name: string,\n args: object,\n ): CallMessage => ({\n type: \"call\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n presence: (data: object): PresenceMessage => ({\n type: \"presence\",\n data: data as Record<string, unknown>,\n }),\n};\n","import { z } from \"zod\";\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const SubscribedSchema = z.object({\n type: z.literal(\"snapshot\"),\n id: z.number(),\n snapshot: z.unknown(),\n});\nexport type SubscribedMessage = z.infer<typeof SubscribedSchema>;\n\nexport const DeltaMessageSchema = z.object({\n type: z.literal(\"delta\"),\n changes: z.record(z.number(), z.unknown()),\n});\nexport type DeltaMessage = z.infer<typeof DeltaMessageSchema>;\n\nexport const ResultMessageSchema = z.object({\n type: z.literal(\"result\"),\n id: z.number(),\n success: z.boolean(),\n value: z.unknown().optional(),\n error: z.string().optional(),\n});\nexport type ResultMessage = z.infer<typeof ResultMessageSchema>;\n\nexport const ServerMessageSchema = z.discriminatedUnion(\"type\", [\n HeartbeatMessageSchema,\n SubscribedSchema,\n DeltaMessageSchema,\n ResultMessageSchema,\n]);\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>;\n\nexport const ServerMessage = {\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n subscribed: (id: number, snapshot: unknown): SubscribedMessage => ({\n type: \"snapshot\",\n id,\n snapshot,\n }),\n delta: (changes: Record<string, unknown>): DeltaMessage => ({\n type: \"delta\",\n changes: changes,\n }),\n resultSuccess: (id: number, value: unknown): ResultMessage => ({\n type: \"result\",\n id,\n success: true,\n value,\n }),\n resultError: (id: number, error: string): ResultMessage => ({\n type: \"result\",\n id,\n success: false,\n error,\n }),\n};\n","export class Queue<T> {\n private front: T[] = [];\n private back: T[] = [];\n\n get length(): number {\n return this.front.length + this.back.length;\n }\n\n isEmpty(): boolean {\n return this.front.length === 0 && this.back.length === 0;\n }\n\n push(item: T): void {\n this.front.push(item);\n }\n\n peek(): T | undefined {\n if (this.back.length === 0) {\n if (this.front.length === 0) {\n return undefined;\n }\n return this.front[0];\n }\n return this.back[this.back.length - 1];\n }\n\n pop(): T | undefined {\n if (this.back.length === 0) {\n // Reverse front and swap to back\n this.back = this.front.reverse();\n this.front = [];\n }\n return this.back.pop();\n }\n}\n","import { Queue } from \"./queue.js\";\n\nexport class RateLimiter {\n private readonly timestamps = new Queue<bigint>();\n private readonly maxOccurrences: number;\n private readonly windowNanos: bigint;\n\n constructor(maxOccurrences: number, windowSeconds: number) {\n this.maxOccurrences = maxOccurrences;\n this.windowNanos = BigInt(windowSeconds) * BigInt(1_000_000_000);\n }\n\n trigger(): boolean {\n const now = process.hrtime.bigint();\n\n const cutoff = now - this.windowNanos;\n while (!this.timestamps.isEmpty()) {\n const oldest = this.timestamps.peek();\n if (oldest === undefined || oldest >= cutoff) {\n break;\n }\n this.timestamps.pop();\n }\n\n if (this.timestamps.length >= this.maxOccurrences) {\n return true;\n }\n\n this.timestamps.push(now);\n\n return false;\n }\n}\n","import { parseClientMessage, ClientMessage } from \"../client-message.js\";\nimport { ServerMessage } from \"../server-message.js\";\nimport {\n Source,\n StreamEndpoints,\n MutationEndpoints,\n RPCDefinition,\n} from \"../stream-types.js\";\nimport { RateLimiter } from \"./rate-limiter.js\";\nimport { PresenceHandler } from \"../presence-manager.js\";\nimport { WebSocket, RawData } from \"ws\";\n\nexport class WebSocketClientHandler<Defs extends RPCDefinition, Ctx = void> {\n private readonly ws: WebSocket;\n private readonly context: Ctx;\n private readonly streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>;\n private readonly mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>;\n private readonly presenceHandler?: PresenceHandler;\n private currentPresence?: Record<string, unknown>;\n private closed = false;\n private readonly streams = new Map<number, Source<unknown>>();\n private heartbeatTimeout: NodeJS.Timeout | undefined;\n private inactivityTimeout: NodeJS.Timeout | undefined;\n private readonly rateLimiter: RateLimiter;\n\n constructor(\n ws: WebSocket,\n context: Ctx,\n streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>,\n mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>,\n presenceHandler?: PresenceHandler,\n ) {\n this.ws = ws;\n this.context = context;\n this.streamEndpoints = streamEndpoints;\n this.mutationEndpoints = mutationEndpoints;\n this.presenceHandler = presenceHandler;\n this.rateLimiter = new RateLimiter(100, 300); // 100 messages over 5 minutes\n\n console.log(\"new client connected\");\n\n // Set up transport handlers\n this.ws.on(\"message\", (data: RawData) =>\n this.handleMessage(data.toString()),\n );\n this.ws.on(\"close\", () => this.handleDisconnect());\n\n this.resetHeartbeat();\n this.resetInactivity();\n }\n\n private resetHeartbeat() {\n if (this.heartbeatTimeout) {\n clearTimeout(this.heartbeatTimeout);\n }\n\n this.heartbeatTimeout = setTimeout(() => {\n this.sendMessage(ServerMessage.heartbeat());\n }, 10_000);\n }\n\n private resetInactivity() {\n if (this.inactivityTimeout) {\n clearTimeout(this.inactivityTimeout);\n }\n\n this.inactivityTimeout = setTimeout(() => {\n this.close();\n }, 30_000);\n }\n\n handleMessage(message: string) {\n this.resetInactivity();\n\n // Check rate limit\n if (this.rateLimiter.trigger()) {\n console.log(\"Rate limit exceeded, closing connection\");\n this.close();\n return;\n }\n\n let data: object;\n try {\n data = JSON.parse(message);\n } catch {\n console.error(\"Invalid JSON received:\", message);\n return this.close();\n }\n\n let parsed: ClientMessage;\n try {\n parsed = parseClientMessage(data);\n } catch (err) {\n console.error(\"Invalid client message:\", err);\n return this.close();\n }\n\n this.handleClientMessage(parsed);\n }\n\n async handleClientMessage(message: ClientMessage) {\n switch (message.type) {\n case \"subscribe\": {\n const { id, name, args } = message;\n\n if (!(name in this.streamEndpoints)) {\n console.error(`Unknown stream: ${name}`);\n this.close();\n return;\n }\n\n const endpoint = this.streamEndpoints[name as keyof Defs[\"streams\"]];\n\n try {\n const source = await endpoint(\n args as Defs[\"streams\"][keyof Defs[\"streams\"]][\"args\"],\n this.context,\n );\n this.streams.set(id, source);\n this.sendMessage(ServerMessage.subscribed(id, source.Snapshot));\n console.log(`Client subscribed to \\\"${name}\\\" (${id})`);\n } catch (err) {\n console.error(`Error building stream ${name}:`, err);\n this.close();\n }\n break;\n }\n\n case \"unsubscribe\": {\n const { id } = message;\n this.streams.delete(id);\n console.log(`Client unsubscribed from ${id}`);\n break;\n }\n\n case \"call\": {\n const { id, name, args } = message;\n\n if (!(name in this.mutationEndpoints)) {\n console.error(`Unknown mutation: ${name}`);\n this.close();\n return;\n }\n\n const endpoint =\n this.mutationEndpoints[name as keyof Defs[\"mutations\"]];\n\n endpoint(\n args as Defs[\"mutations\"][keyof Defs[\"mutations\"]][\"args\"],\n this.context,\n )\n .then((result) => {\n if (result.success) {\n this.sendMessage(ServerMessage.resultSuccess(id, result.value));\n console.log(\n `Mutation \\\"${name}\\\" (${id}) completed successfully`,\n );\n } else {\n this.sendMessage(ServerMessage.resultError(id, result.error));\n console.log(\n `Mutation \\\"${name}\\\" (${id}) returned error: ${result.error}`,\n );\n }\n })\n .catch((err) => {\n console.error(\n `Unhandled exception in mutation \\\"${name}\\\" (${id}):`,\n err,\n );\n this.close();\n });\n break;\n }\n\n case \"heartbeat\":\n break;\n\n case \"presence\": {\n if (!this.presenceHandler) {\n console.error(\"Presence not configured\");\n this.close();\n return;\n }\n\n const { data } = message;\n\n if (this.currentPresence !== undefined) {\n this.presenceHandler.update(this.currentPresence, data);\n } else {\n this.presenceHandler.add(data);\n }\n\n this.currentPresence = data;\n break;\n }\n }\n }\n\n handleStep() {\n if (this.closed) return;\n const changes: Record<number, unknown> = {};\n\n for (const [id, source] of this.streams) {\n const change = source.LastChange;\n if (change === null) continue;\n changes[id] = change;\n }\n\n if (Object.keys(changes).length > 0) {\n this.sendMessage(ServerMessage.delta(changes));\n }\n }\n\n sendMessage(message: ServerMessage) {\n this.resetHeartbeat();\n\n if (!this.closed) {\n // Check buffer if available (WebSocket provides this, MessagePort doesn't)\n if (this.ws.bufferedAmount > 100 * 1024) {\n console.log(\"Send buffer exceeded 100KB, closing connection\");\n this.close();\n return;\n }\n\n try {\n this.ws.send(JSON.stringify(message));\n } catch (err) {\n console.error(\"Failed to send message:\", err);\n this.close();\n }\n }\n }\n\n private handleDisconnect() {\n console.log(\"client disconnected\");\n this.close();\n }\n\n close() {\n if (this.closed) return;\n this.closed = true;\n clearTimeout(this.heartbeatTimeout);\n clearTimeout(this.inactivityTimeout);\n\n if (this.presenceHandler && this.currentPresence !== undefined) {\n this.presenceHandler.remove(this.currentPresence);\n }\n\n try {\n this.ws.close();\n } catch {}\n }\n}\n","export default class WeakList<T extends object> implements Iterable<T> {\n private items: WeakRef<T>[] = [];\n\n add(value: T): void {\n this.items.push(new WeakRef(value));\n }\n\n *[Symbol.iterator](): Iterator<T> {\n const newItems: WeakRef<T>[] = [];\n\n for (const ref of this.items) {\n const value = ref.deref();\n if (value !== undefined) {\n yield value;\n newItems.push(ref);\n }\n }\n\n this.items = newItems;\n }\n}\n"],"mappings":"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","names":["import_zod","HeartbeatMessageSchema"]}
|
|
1
|
+
{"version":3,"sources":["../src/websocket/web-socket-server.ts","../src/client-message.ts","../src/server-message.ts","../src/websocket/queue.ts","../src/websocket/rate-limiter.ts","../src/websocket/web-socket-client-handler.ts","../src/weak-list.ts"],"sourcesContent":["import { parse } from \"url\";\nimport { Server, IncomingMessage, STATUS_CODES } from \"http\";\nimport { WebSocketServer, WebSocket, RawData } from \"ws\";\nimport { WebSocketClientHandler } from \"./web-socket-client-handler.js\";\nimport WeakList from \"../weak-list.js\";\nimport {\n StreamEndpoints,\n MutationEndpoints,\n RPCDefinition,\n} from \"../stream-types.js\";\nimport { Graph } from \"derivation\";\nimport { PresenceHandler } from \"../presence-manager.js\";\n\nexport type WebSocketServerOptions<Ctx> = {\n createContext: (ws: WebSocket, req: IncomingMessage) => Ctx | Promise<Ctx>;\n presenceHandler?: PresenceHandler;\n path?: string;\n verifyUpgradeRequest?: (\n req: IncomingMessage,\n ) =>\n | true\n | { statusCode?: number; reason?: string }\n | Promise<true | { statusCode?: number; reason?: string }>;\n};\n\nfunction rejectUpgrade(\n socket: NodeJS.WritableStream & { destroy(error?: Error): void },\n statusCode: number,\n reason: string,\n) {\n const safeStatusCode = Number.isInteger(statusCode) ? statusCode : 403;\n const statusText = STATUS_CODES[safeStatusCode] ?? \"Forbidden\";\n const body = reason || statusText;\n const headers = [\n `HTTP/1.1 ${safeStatusCode} ${statusText}`,\n \"Connection: close\",\n \"Content-Type: text/plain; charset=utf-8\",\n `Content-Length: ${Buffer.byteLength(body)}`,\n \"\",\n body,\n ];\n\n try {\n socket.write(headers.join(\"\\r\\n\"));\n } finally {\n socket.destroy();\n }\n}\n\nexport function setupWebSocketServer<Defs extends RPCDefinition, Ctx = void>(\n graph: Graph,\n server: Server,\n streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>,\n mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>,\n options: WebSocketServerOptions<Ctx>,\n) {\n const {\n createContext,\n presenceHandler,\n path = \"/api/ws\",\n verifyUpgradeRequest,\n } = options;\n const clients = new WeakList<WebSocketClientHandler<Defs, Ctx>>();\n\n graph.afterStep(() => {\n for (const client of clients) {\n client.handleStep();\n }\n });\n\n const wss = new WebSocketServer({ noServer: true, maxPayload: 100 * 1024 });\n\n // Handle WebSocket connections for reactive streams\n wss.on(\"connection\", (ws, req) => {\n const { pathname } = parse(req.url || \"/\", true);\n if (pathname === path) {\n const messageBuffer: RawData[] = [];\n let client: WebSocketClientHandler<Defs, Ctx> | null = null;\n\n // Set up temporary message handler to buffer messages\n const tempMessageHandler = (msg: RawData) => {\n messageBuffer.push(msg);\n };\n ws.on(\"message\", tempMessageHandler);\n\n // Create context (handle both sync and async)\n Promise.resolve(createContext(ws, req))\n .then((context) => {\n // Remove temporary handler\n ws.removeListener(\"message\", tempMessageHandler);\n\n // Create client handler\n client = new WebSocketClientHandler<Defs, Ctx>(\n ws,\n context,\n streamEndpoints,\n mutationEndpoints,\n presenceHandler,\n );\n clients.add(client);\n\n // Process buffered messages\n for (const msg of messageBuffer) {\n client.handleMessage(msg.toString());\n }\n messageBuffer.length = 0;\n })\n .catch((err) => {\n console.error(\"Error creating context:\", err);\n ws.close();\n });\n }\n });\n\n // Handle HTTP upgrade (for /api/ws)\n server.on(\"upgrade\", (req, socket, head) => {\n const { pathname } = parse(req.url || \"/\", true);\n\n if (pathname !== path) {\n socket.destroy();\n return;\n }\n\n Promise.resolve(verifyUpgradeRequest?.(req) ?? true)\n .then((verificationResult) => {\n if (verificationResult !== true) {\n rejectUpgrade(\n socket,\n verificationResult.statusCode ?? 403,\n verificationResult.reason ?? \"Forbidden\",\n );\n return;\n }\n\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n })\n .catch((err) => {\n console.error(\"Error verifying upgrade request:\", err);\n rejectUpgrade(socket, 500, \"Internal Server Error\");\n });\n });\n}\n","import { z } from \"zod\";\n\nexport const SubscribeMessageSchema = z.object({\n type: z.literal(\"subscribe\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type SubscribeMessage = z.infer<typeof SubscribeMessageSchema>;\n\nexport const UnsubscribeMessageSchema = z.object({\n type: z.literal(\"unsubscribe\"),\n id: z.number(),\n});\nexport type UnsubscribeMessage = z.infer<typeof UnsubscribeMessageSchema>;\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const CallMessageSchema = z.object({\n type: z.literal(\"call\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type CallMessage = z.infer<typeof CallMessageSchema>;\n\nexport const PresenceMessageSchema = z.object({\n type: z.literal(\"presence\"),\n data: z.looseObject({}),\n});\nexport type PresenceMessage = z.infer<typeof PresenceMessageSchema>;\n\nexport const ClientMessageSchema = z.discriminatedUnion(\"type\", [\n SubscribeMessageSchema,\n UnsubscribeMessageSchema,\n HeartbeatMessageSchema,\n CallMessageSchema,\n PresenceMessageSchema,\n]);\nexport type ClientMessage = z.infer<typeof ClientMessageSchema>;\n\nexport function parseClientMessage(data: unknown): ClientMessage {\n return ClientMessageSchema.parse(data);\n}\n\nexport const ClientMessage = {\n subscribe: (\n id: number,\n name: string,\n args: object,\n ): SubscribeMessage => ({\n type: \"subscribe\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n unsubscribe: (id: number): UnsubscribeMessage => ({\n type: \"unsubscribe\",\n id,\n }),\n call: (\n id: number,\n name: string,\n args: object,\n ): CallMessage => ({\n type: \"call\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n presence: (data: object): PresenceMessage => ({\n type: \"presence\",\n data: data as Record<string, unknown>,\n }),\n};\n","import { z } from \"zod\";\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const SubscribedSchema = z.object({\n type: z.literal(\"snapshot\"),\n id: z.number(),\n snapshot: z.unknown(),\n});\nexport type SubscribedMessage = z.infer<typeof SubscribedSchema>;\n\nexport const DeltaMessageSchema = z.object({\n type: z.literal(\"delta\"),\n changes: z.record(z.number(), z.unknown()),\n});\nexport type DeltaMessage = z.infer<typeof DeltaMessageSchema>;\n\nexport const ResultMessageSchema = z.object({\n type: z.literal(\"result\"),\n id: z.number(),\n success: z.boolean(),\n value: z.unknown().optional(),\n error: z.string().optional(),\n});\nexport type ResultMessage = z.infer<typeof ResultMessageSchema>;\n\nexport const ServerMessageSchema = z.discriminatedUnion(\"type\", [\n HeartbeatMessageSchema,\n SubscribedSchema,\n DeltaMessageSchema,\n ResultMessageSchema,\n]);\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>;\n\nexport const ServerMessage = {\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n subscribed: (id: number, snapshot: unknown): SubscribedMessage => ({\n type: \"snapshot\",\n id,\n snapshot,\n }),\n delta: (changes: Record<string, unknown>): DeltaMessage => ({\n type: \"delta\",\n changes: changes,\n }),\n resultSuccess: (id: number, value: unknown): ResultMessage => ({\n type: \"result\",\n id,\n success: true,\n value,\n }),\n resultError: (id: number, error: string): ResultMessage => ({\n type: \"result\",\n id,\n success: false,\n error,\n }),\n};\n","export class Queue<T> {\n private front: T[] = [];\n private back: T[] = [];\n\n get length(): number {\n return this.front.length + this.back.length;\n }\n\n isEmpty(): boolean {\n return this.front.length === 0 && this.back.length === 0;\n }\n\n push(item: T): void {\n this.front.push(item);\n }\n\n peek(): T | undefined {\n if (this.back.length === 0) {\n if (this.front.length === 0) {\n return undefined;\n }\n return this.front[0];\n }\n return this.back[this.back.length - 1];\n }\n\n pop(): T | undefined {\n if (this.back.length === 0) {\n // Reverse front and swap to back\n this.back = this.front.reverse();\n this.front = [];\n }\n return this.back.pop();\n }\n}\n","import { Queue } from \"./queue.js\";\n\nexport class RateLimiter {\n private readonly timestamps = new Queue<bigint>();\n private readonly maxOccurrences: number;\n private readonly windowNanos: bigint;\n\n constructor(maxOccurrences: number, windowSeconds: number) {\n this.maxOccurrences = maxOccurrences;\n this.windowNanos = BigInt(windowSeconds) * BigInt(1_000_000_000);\n }\n\n trigger(): boolean {\n const now = process.hrtime.bigint();\n\n const cutoff = now - this.windowNanos;\n while (!this.timestamps.isEmpty()) {\n const oldest = this.timestamps.peek();\n if (oldest === undefined || oldest >= cutoff) {\n break;\n }\n this.timestamps.pop();\n }\n\n if (this.timestamps.length >= this.maxOccurrences) {\n return true;\n }\n\n this.timestamps.push(now);\n\n return false;\n }\n}\n","import { parseClientMessage, ClientMessage } from \"../client-message.js\";\nimport { ServerMessage } from \"../server-message.js\";\nimport {\n Source,\n StreamEndpoints,\n MutationEndpoints,\n RPCDefinition,\n} from \"../stream-types.js\";\nimport { RateLimiter } from \"./rate-limiter.js\";\nimport { PresenceHandler } from \"../presence-manager.js\";\nimport { WebSocket, RawData } from \"ws\";\n\nexport class WebSocketClientHandler<Defs extends RPCDefinition, Ctx = void> {\n private readonly ws: WebSocket;\n private readonly context: Ctx;\n private readonly streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>;\n private readonly mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>;\n private readonly presenceHandler?: PresenceHandler;\n private currentPresence?: Record<string, unknown>;\n private closed = false;\n private readonly streams = new Map<number, Source<unknown>>();\n private heartbeatTimeout: NodeJS.Timeout | undefined;\n private inactivityTimeout: NodeJS.Timeout | undefined;\n private readonly rateLimiter: RateLimiter;\n\n constructor(\n ws: WebSocket,\n context: Ctx,\n streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>,\n mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>,\n presenceHandler?: PresenceHandler,\n ) {\n this.ws = ws;\n this.context = context;\n this.streamEndpoints = streamEndpoints;\n this.mutationEndpoints = mutationEndpoints;\n this.presenceHandler = presenceHandler;\n this.rateLimiter = new RateLimiter(100, 300); // 100 messages over 5 minutes\n\n console.log(\"new client connected\");\n\n // Set up transport handlers\n this.ws.on(\"message\", (data: RawData) =>\n this.handleMessage(data.toString()),\n );\n this.ws.on(\"close\", () => this.handleDisconnect());\n\n this.resetHeartbeat();\n this.resetInactivity();\n }\n\n private resetHeartbeat() {\n if (this.heartbeatTimeout) {\n clearTimeout(this.heartbeatTimeout);\n }\n\n this.heartbeatTimeout = setTimeout(() => {\n this.sendMessage(ServerMessage.heartbeat());\n }, 10_000);\n }\n\n private resetInactivity() {\n if (this.inactivityTimeout) {\n clearTimeout(this.inactivityTimeout);\n }\n\n this.inactivityTimeout = setTimeout(() => {\n this.close();\n }, 30_000);\n }\n\n handleMessage(message: string) {\n this.resetInactivity();\n\n // Check rate limit\n if (this.rateLimiter.trigger()) {\n console.log(\"Rate limit exceeded, closing connection\");\n this.close();\n return;\n }\n\n let data: object;\n try {\n data = JSON.parse(message);\n } catch {\n console.error(\"Invalid JSON received:\", message);\n return this.close();\n }\n\n let parsed: ClientMessage;\n try {\n parsed = parseClientMessage(data);\n } catch (err) {\n console.error(\"Invalid client message:\", err);\n return this.close();\n }\n\n this.handleClientMessage(parsed);\n }\n\n async handleClientMessage(message: ClientMessage) {\n switch (message.type) {\n case \"subscribe\": {\n const { id, name, args } = message;\n\n if (!(name in this.streamEndpoints)) {\n console.error(`Unknown stream: ${name}`);\n this.close();\n return;\n }\n\n const endpoint = this.streamEndpoints[name as keyof Defs[\"streams\"]];\n\n try {\n const source = await endpoint(\n args as Defs[\"streams\"][keyof Defs[\"streams\"]][\"args\"],\n this.context,\n );\n this.streams.set(id, source);\n this.sendMessage(ServerMessage.subscribed(id, source.Snapshot));\n console.log(`Client subscribed to \\\"${name}\\\" (${id})`);\n } catch (err) {\n console.error(`Error building stream ${name}:`, err);\n this.close();\n }\n break;\n }\n\n case \"unsubscribe\": {\n const { id } = message;\n this.streams.delete(id);\n console.log(`Client unsubscribed from ${id}`);\n break;\n }\n\n case \"call\": {\n const { id, name, args } = message;\n\n if (!(name in this.mutationEndpoints)) {\n console.error(`Unknown mutation: ${name}`);\n this.close();\n return;\n }\n\n const endpoint =\n this.mutationEndpoints[name as keyof Defs[\"mutations\"]];\n\n endpoint(\n args as Defs[\"mutations\"][keyof Defs[\"mutations\"]][\"args\"],\n this.context,\n )\n .then((result) => {\n if (result.success) {\n this.sendMessage(ServerMessage.resultSuccess(id, result.value));\n console.log(\n `Mutation \\\"${name}\\\" (${id}) completed successfully`,\n );\n } else {\n this.sendMessage(ServerMessage.resultError(id, result.error));\n console.log(\n `Mutation \\\"${name}\\\" (${id}) returned error: ${result.error}`,\n );\n }\n })\n .catch((err) => {\n console.error(\n `Unhandled exception in mutation \\\"${name}\\\" (${id}):`,\n err,\n );\n this.close();\n });\n break;\n }\n\n case \"heartbeat\":\n break;\n\n case \"presence\": {\n if (!this.presenceHandler) {\n console.error(\"Presence not configured\");\n this.close();\n return;\n }\n\n const { data } = message;\n\n if (this.currentPresence !== undefined) {\n this.presenceHandler.update(this.currentPresence, data);\n } else {\n this.presenceHandler.add(data);\n }\n\n this.currentPresence = data;\n break;\n }\n }\n }\n\n handleStep() {\n if (this.closed) return;\n const changes: Record<number, unknown> = {};\n\n for (const [id, source] of this.streams) {\n const change = source.LastChange;\n if (change === null) continue;\n changes[id] = change;\n }\n\n if (Object.keys(changes).length > 0) {\n this.sendMessage(ServerMessage.delta(changes));\n }\n }\n\n sendMessage(message: ServerMessage) {\n this.resetHeartbeat();\n\n if (!this.closed) {\n // Check buffer if available (WebSocket provides this, MessagePort doesn't)\n if (this.ws.bufferedAmount > 100 * 1024) {\n console.log(\"Send buffer exceeded 100KB, closing connection\");\n this.close();\n return;\n }\n\n try {\n this.ws.send(JSON.stringify(message));\n } catch (err) {\n console.error(\"Failed to send message:\", err);\n this.close();\n }\n }\n }\n\n private handleDisconnect() {\n console.log(\"client disconnected\");\n this.close();\n }\n\n close() {\n if (this.closed) return;\n this.closed = true;\n clearTimeout(this.heartbeatTimeout);\n clearTimeout(this.inactivityTimeout);\n\n if (this.presenceHandler && this.currentPresence !== undefined) {\n this.presenceHandler.remove(this.currentPresence);\n }\n\n try {\n this.ws.close();\n } catch {}\n }\n}\n","export default class WeakList<T extends object> implements Iterable<T> {\n private items: WeakRef<T>[] = [];\n\n add(value: T): void {\n this.items.push(new WeakRef(value));\n }\n\n *[Symbol.iterator](): Iterator<T> {\n const newItems: WeakRef<T>[] = [];\n\n for (const ref of this.items) {\n const value = ref.deref();\n if (value !== undefined) {\n yield value;\n newItems.push(ref);\n }\n }\n\n this.items = newItems;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAsB;AACtB,kBAAsD;AACtD,gBAAoD;;;ACFpD,iBAAkB;AAEX,IAAM,yBAAyB,aAAE,OAAO;AAAA,EAC7C,MAAM,aAAE,QAAQ,WAAW;AAAA,EAC3B,IAAI,aAAE,OAAO;AAAA,EACb,MAAM,aAAE,OAAO;AAAA,EACf,MAAM,aAAE,YAAY,CAAC,CAAC;AACxB,CAAC;AAGM,IAAM,2BAA2B,aAAE,OAAO;AAAA,EAC/C,MAAM,aAAE,QAAQ,aAAa;AAAA,EAC7B,IAAI,aAAE,OAAO;AACf,CAAC;AAGM,IAAM,yBAAyB,aAAE,OAAO;AAAA,EAC7C,MAAM,aAAE,QAAQ,WAAW;AAC7B,CAAC;AAGM,IAAM,oBAAoB,aAAE,OAAO;AAAA,EACxC,MAAM,aAAE,QAAQ,MAAM;AAAA,EACtB,IAAI,aAAE,OAAO;AAAA,EACb,MAAM,aAAE,OAAO;AAAA,EACf,MAAM,aAAE,YAAY,CAAC,CAAC;AACxB,CAAC;AAGM,IAAM,wBAAwB,aAAE,OAAO;AAAA,EAC5C,MAAM,aAAE,QAAQ,UAAU;AAAA,EAC1B,MAAM,aAAE,YAAY,CAAC,CAAC;AACxB,CAAC;AAGM,IAAM,sBAAsB,aAAE,mBAAmB,QAAQ;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,mBAAmB,MAA8B;AAC/D,SAAO,oBAAoB,MAAM,IAAI;AACvC;;;AC9CA,IAAAA,cAAkB;AAEX,IAAMC,0BAAyB,cAAE,OAAO;AAAA,EAC7C,MAAM,cAAE,QAAQ,WAAW;AAC7B,CAAC;AAGM,IAAM,mBAAmB,cAAE,OAAO;AAAA,EACvC,MAAM,cAAE,QAAQ,UAAU;AAAA,EAC1B,IAAI,cAAE,OAAO;AAAA,EACb,UAAU,cAAE,QAAQ;AACtB,CAAC;AAGM,IAAM,qBAAqB,cAAE,OAAO;AAAA,EACzC,MAAM,cAAE,QAAQ,OAAO;AAAA,EACvB,SAAS,cAAE,OAAO,cAAE,OAAO,GAAG,cAAE,QAAQ,CAAC;AAC3C,CAAC;AAGM,IAAM,sBAAsB,cAAE,OAAO;AAAA,EAC1C,MAAM,cAAE,QAAQ,QAAQ;AAAA,EACxB,IAAI,cAAE,OAAO;AAAA,EACb,SAAS,cAAE,QAAQ;AAAA,EACnB,OAAO,cAAE,QAAQ,EAAE,SAAS;AAAA,EAC5B,OAAO,cAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAGM,IAAM,sBAAsB,cAAE,mBAAmB,QAAQ;AAAA,EAC9DA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,gBAAgB;AAAA,EAC3B,WAAW,OAAyB;AAAA,IAClC,MAAM;AAAA,EACR;AAAA,EACA,YAAY,CAAC,IAAY,cAA0C;AAAA,IACjE,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AAAA,EACA,OAAO,CAAC,aAAoD;AAAA,IAC1D,MAAM;AAAA,IACN;AAAA,EACF;AAAA,EACA,eAAe,CAAC,IAAY,WAAmC;AAAA,IAC7D,MAAM;AAAA,IACN;AAAA,IACA,SAAS;AAAA,IACT;AAAA,EACF;AAAA,EACA,aAAa,CAAC,IAAY,WAAkC;AAAA,IAC1D,MAAM;AAAA,IACN;AAAA,IACA,SAAS;AAAA,IACT;AAAA,EACF;AACF;;;AC9DO,IAAM,QAAN,MAAe;AAAA,EAAf;AACL,SAAQ,QAAa,CAAC;AACtB,SAAQ,OAAY,CAAC;AAAA;AAAA,EAErB,IAAI,SAAiB;AACnB,WAAO,KAAK,MAAM,SAAS,KAAK,KAAK;AAAA,EACvC;AAAA,EAEA,UAAmB;AACjB,WAAO,KAAK,MAAM,WAAW,KAAK,KAAK,KAAK,WAAW;AAAA,EACzD;AAAA,EAEA,KAAK,MAAe;AAClB,SAAK,MAAM,KAAK,IAAI;AAAA,EACtB;AAAA,EAEA,OAAsB;AACpB,QAAI,KAAK,KAAK,WAAW,GAAG;AAC1B,UAAI,KAAK,MAAM,WAAW,GAAG;AAC3B,eAAO;AAAA,MACT;AACA,aAAO,KAAK,MAAM,CAAC;AAAA,IACrB;AACA,WAAO,KAAK,KAAK,KAAK,KAAK,SAAS,CAAC;AAAA,EACvC;AAAA,EAEA,MAAqB;AACnB,QAAI,KAAK,KAAK,WAAW,GAAG;AAE1B,WAAK,OAAO,KAAK,MAAM,QAAQ;AAC/B,WAAK,QAAQ,CAAC;AAAA,IAChB;AACA,WAAO,KAAK,KAAK,IAAI;AAAA,EACvB;AACF;;;AChCO,IAAM,cAAN,MAAkB;AAAA,EAKvB,YAAY,gBAAwB,eAAuB;AAJ3D,SAAiB,aAAa,IAAI,MAAc;AAK9C,SAAK,iBAAiB;AACtB,SAAK,cAAc,OAAO,aAAa,IAAI,OAAO,GAAa;AAAA,EACjE;AAAA,EAEA,UAAmB;AACjB,UAAM,MAAM,QAAQ,OAAO,OAAO;AAElC,UAAM,SAAS,MAAM,KAAK;AAC1B,WAAO,CAAC,KAAK,WAAW,QAAQ,GAAG;AACjC,YAAM,SAAS,KAAK,WAAW,KAAK;AACpC,UAAI,WAAW,UAAa,UAAU,QAAQ;AAC5C;AAAA,MACF;AACA,WAAK,WAAW,IAAI;AAAA,IACtB;AAEA,QAAI,KAAK,WAAW,UAAU,KAAK,gBAAgB;AACjD,aAAO;AAAA,IACT;AAEA,SAAK,WAAW,KAAK,GAAG;AAExB,WAAO;AAAA,EACT;AACF;;;ACpBO,IAAM,yBAAN,MAAqE;AAAA,EAa1E,YACE,IACA,SACA,iBACA,mBACA,iBACA;AAZF,SAAQ,SAAS;AACjB,SAAiB,UAAU,oBAAI,IAA6B;AAY1D,SAAK,KAAK;AACV,SAAK,UAAU;AACf,SAAK,kBAAkB;AACvB,SAAK,oBAAoB;AACzB,SAAK,kBAAkB;AACvB,SAAK,cAAc,IAAI,YAAY,KAAK,GAAG;AAE3C,YAAQ,IAAI,sBAAsB;AAGlC,SAAK,GAAG;AAAA,MAAG;AAAA,MAAW,CAAC,SACrB,KAAK,cAAc,KAAK,SAAS,CAAC;AAAA,IACpC;AACA,SAAK,GAAG,GAAG,SAAS,MAAM,KAAK,iBAAiB,CAAC;AAEjD,SAAK,eAAe;AACpB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEQ,iBAAiB;AACvB,QAAI,KAAK,kBAAkB;AACzB,mBAAa,KAAK,gBAAgB;AAAA,IACpC;AAEA,SAAK,mBAAmB,WAAW,MAAM;AACvC,WAAK,YAAY,cAAc,UAAU,CAAC;AAAA,IAC5C,GAAG,GAAM;AAAA,EACX;AAAA,EAEQ,kBAAkB;AACxB,QAAI,KAAK,mBAAmB;AAC1B,mBAAa,KAAK,iBAAiB;AAAA,IACrC;AAEA,SAAK,oBAAoB,WAAW,MAAM;AACxC,WAAK,MAAM;AAAA,IACb,GAAG,GAAM;AAAA,EACX;AAAA,EAEA,cAAc,SAAiB;AAC7B,SAAK,gBAAgB;AAGrB,QAAI,KAAK,YAAY,QAAQ,GAAG;AAC9B,cAAQ,IAAI,yCAAyC;AACrD,WAAK,MAAM;AACX;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAQ;AACN,cAAQ,MAAM,0BAA0B,OAAO;AAC/C,aAAO,KAAK,MAAM;AAAA,IACpB;AAEA,QAAI;AACJ,QAAI;AACF,eAAS,mBAAmB,IAAI;AAAA,IAClC,SAAS,KAAK;AACZ,cAAQ,MAAM,2BAA2B,GAAG;AAC5C,aAAO,KAAK,MAAM;AAAA,IACpB;AAEA,SAAK,oBAAoB,MAAM;AAAA,EACjC;AAAA,EAEA,MAAM,oBAAoB,SAAwB;AAChD,YAAQ,QAAQ,MAAM;AAAA,MACpB,KAAK,aAAa;AAChB,cAAM,EAAE,IAAI,MAAM,KAAK,IAAI;AAE3B,YAAI,EAAE,QAAQ,KAAK,kBAAkB;AACnC,kBAAQ,MAAM,mBAAmB,IAAI,EAAE;AACvC,eAAK,MAAM;AACX;AAAA,QACF;AAEA,cAAM,WAAW,KAAK,gBAAgB,IAA6B;AAEnE,YAAI;AACF,gBAAM,SAAS,MAAM;AAAA,YACnB;AAAA,YACA,KAAK;AAAA,UACP;AACA,eAAK,QAAQ,IAAI,IAAI,MAAM;AAC3B,eAAK,YAAY,cAAc,WAAW,IAAI,OAAO,QAAQ,CAAC;AAC9D,kBAAQ,IAAI,yBAA0B,IAAI,MAAO,EAAE,GAAG;AAAA,QACxD,SAAS,KAAK;AACZ,kBAAQ,MAAM,yBAAyB,IAAI,KAAK,GAAG;AACnD,eAAK,MAAM;AAAA,QACb;AACA;AAAA,MACF;AAAA,MAEA,KAAK,eAAe;AAClB,cAAM,EAAE,GAAG,IAAI;AACf,aAAK,QAAQ,OAAO,EAAE;AACtB,gBAAQ,IAAI,4BAA4B,EAAE,EAAE;AAC5C;AAAA,MACF;AAAA,MAEA,KAAK,QAAQ;AACX,cAAM,EAAE,IAAI,MAAM,KAAK,IAAI;AAE3B,YAAI,EAAE,QAAQ,KAAK,oBAAoB;AACrC,kBAAQ,MAAM,qBAAqB,IAAI,EAAE;AACzC,eAAK,MAAM;AACX;AAAA,QACF;AAEA,cAAM,WACJ,KAAK,kBAAkB,IAA+B;AAExD;AAAA,UACE;AAAA,UACA,KAAK;AAAA,QACP,EACG,KAAK,CAAC,WAAW;AAChB,cAAI,OAAO,SAAS;AAClB,iBAAK,YAAY,cAAc,cAAc,IAAI,OAAO,KAAK,CAAC;AAC9D,oBAAQ;AAAA,cACN,aAAc,IAAI,MAAO,EAAE;AAAA,YAC7B;AAAA,UACF,OAAO;AACL,iBAAK,YAAY,cAAc,YAAY,IAAI,OAAO,KAAK,CAAC;AAC5D,oBAAQ;AAAA,cACN,aAAc,IAAI,MAAO,EAAE,qBAAqB,OAAO,KAAK;AAAA,YAC9D;AAAA,UACF;AAAA,QACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,kBAAQ;AAAA,YACN,oCAAqC,IAAI,MAAO,EAAE;AAAA,YAClD;AAAA,UACF;AACA,eAAK,MAAM;AAAA,QACb,CAAC;AACH;AAAA,MACF;AAAA,MAEA,KAAK;AACH;AAAA,MAEF,KAAK,YAAY;AACf,YAAI,CAAC,KAAK,iBAAiB;AACzB,kBAAQ,MAAM,yBAAyB;AACvC,eAAK,MAAM;AACX;AAAA,QACF;AAEA,cAAM,EAAE,KAAK,IAAI;AAEjB,YAAI,KAAK,oBAAoB,QAAW;AACtC,eAAK,gBAAgB,OAAO,KAAK,iBAAiB,IAAI;AAAA,QACxD,OAAO;AACL,eAAK,gBAAgB,IAAI,IAAI;AAAA,QAC/B;AAEA,aAAK,kBAAkB;AACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,aAAa;AACX,QAAI,KAAK,OAAQ;AACjB,UAAM,UAAmC,CAAC;AAE1C,eAAW,CAAC,IAAI,MAAM,KAAK,KAAK,SAAS;AACvC,YAAM,SAAS,OAAO;AACtB,UAAI,WAAW,KAAM;AACrB,cAAQ,EAAE,IAAI;AAAA,IAChB;AAEA,QAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,WAAK,YAAY,cAAc,MAAM,OAAO,CAAC;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,YAAY,SAAwB;AAClC,SAAK,eAAe;AAEpB,QAAI,CAAC,KAAK,QAAQ;AAEhB,UAAI,KAAK,GAAG,iBAAiB,MAAM,MAAM;AACvC,gBAAQ,IAAI,gDAAgD;AAC5D,aAAK,MAAM;AACX;AAAA,MACF;AAEA,UAAI;AACF,aAAK,GAAG,KAAK,KAAK,UAAU,OAAO,CAAC;AAAA,MACtC,SAAS,KAAK;AACZ,gBAAQ,MAAM,2BAA2B,GAAG;AAC5C,aAAK,MAAM;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,mBAAmB;AACzB,YAAQ,IAAI,qBAAqB;AACjC,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,QAAQ;AACN,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,iBAAa,KAAK,gBAAgB;AAClC,iBAAa,KAAK,iBAAiB;AAEnC,QAAI,KAAK,mBAAmB,KAAK,oBAAoB,QAAW;AAC9D,WAAK,gBAAgB,OAAO,KAAK,eAAe;AAAA,IAClD;AAEA,QAAI;AACF,WAAK,GAAG,MAAM;AAAA,IAChB,SAAQ;AAAA,IAAC;AAAA,EACX;AACF;;;AC5PA,IAAqB,WAArB,MAAuE;AAAA,EAAvE;AACE,SAAQ,QAAsB,CAAC;AAAA;AAAA,EAE/B,IAAI,OAAgB;AAClB,SAAK,MAAM,KAAK,IAAI,QAAQ,KAAK,CAAC;AAAA,EACpC;AAAA,EAEA,EAAE,OAAO,QAAQ,IAAiB;AAChC,UAAM,WAAyB,CAAC;AAEhC,eAAW,OAAO,KAAK,OAAO;AAC5B,YAAM,QAAQ,IAAI,MAAM;AACxB,UAAI,UAAU,QAAW;AACvB,cAAM;AACN,iBAAS,KAAK,GAAG;AAAA,MACnB;AAAA,IACF;AAEA,SAAK,QAAQ;AAAA,EACf;AACF;;;ANKA,SAAS,cACP,QACA,YACA,QACA;AA7BF;AA8BE,QAAM,iBAAiB,OAAO,UAAU,UAAU,IAAI,aAAa;AACnE,QAAM,cAAa,8BAAa,cAAc,MAA3B,YAAgC;AACnD,QAAM,OAAO,UAAU;AACvB,QAAM,UAAU;AAAA,IACd,YAAY,cAAc,IAAI,UAAU;AAAA,IACxC;AAAA,IACA;AAAA,IACA,mBAAmB,OAAO,WAAW,IAAI,CAAC;AAAA,IAC1C;AAAA,IACA;AAAA,EACF;AAEA,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK,MAAM,CAAC;AAAA,EACnC,UAAE;AACA,WAAO,QAAQ;AAAA,EACjB;AACF;AAEO,SAAS,qBACd,OACA,QACA,iBACA,mBACA,SACA;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACF,IAAI;AACJ,QAAM,UAAU,IAAI,SAA4C;AAEhE,QAAM,UAAU,MAAM;AACpB,eAAW,UAAU,SAAS;AAC5B,aAAO,WAAW;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,MAAM,IAAI,0BAAgB,EAAE,UAAU,MAAM,YAAY,MAAM,KAAK,CAAC;AAG1E,MAAI,GAAG,cAAc,CAAC,IAAI,QAAQ;AAChC,UAAM,EAAE,SAAS,QAAI,kBAAM,IAAI,OAAO,KAAK,IAAI;AAC/C,QAAI,aAAa,MAAM;AACrB,YAAM,gBAA2B,CAAC;AAClC,UAAI,SAAmD;AAGvD,YAAM,qBAAqB,CAAC,QAAiB;AAC3C,sBAAc,KAAK,GAAG;AAAA,MACxB;AACA,SAAG,GAAG,WAAW,kBAAkB;AAGnC,cAAQ,QAAQ,cAAc,IAAI,GAAG,CAAC,EACnC,KAAK,CAAC,YAAY;AAEjB,WAAG,eAAe,WAAW,kBAAkB;AAG/C,iBAAS,IAAI;AAAA,UACX;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,gBAAQ,IAAI,MAAM;AAGlB,mBAAW,OAAO,eAAe;AAC/B,iBAAO,cAAc,IAAI,SAAS,CAAC;AAAA,QACrC;AACA,sBAAc,SAAS;AAAA,MACzB,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,gBAAQ,MAAM,2BAA2B,GAAG;AAC5C,WAAG,MAAM;AAAA,MACX,CAAC;AAAA,IACL;AAAA,EACF,CAAC;AAGD,SAAO,GAAG,WAAW,CAAC,KAAK,QAAQ,SAAS;AAnH9C;AAoHI,UAAM,EAAE,SAAS,QAAI,kBAAM,IAAI,OAAO,KAAK,IAAI;AAE/C,QAAI,aAAa,MAAM;AACrB,aAAO,QAAQ;AACf;AAAA,IACF;AAEA,YAAQ,SAAQ,kEAAuB,SAAvB,YAA+B,IAAI,EAChD,KAAK,CAAC,uBAAuB;AA5HpC,UAAAC,KAAA;AA6HQ,UAAI,uBAAuB,MAAM;AAC/B;AAAA,UACE;AAAA,WACAA,MAAA,mBAAmB,eAAnB,OAAAA,MAAiC;AAAA,WACjC,wBAAmB,WAAnB,YAA6B;AAAA,QAC/B;AACA;AAAA,MACF;AAEA,UAAI,cAAc,KAAK,QAAQ,MAAM,CAAC,OAAO;AAC3C,YAAI,KAAK,cAAc,IAAI,GAAG;AAAA,MAChC,CAAC;AAAA,IACH,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,cAAQ,MAAM,oCAAoC,GAAG;AACrD,oBAAc,QAAQ,KAAK,uBAAuB;AAAA,IACpD,CAAC;AAAA,EACL,CAAC;AACH;","names":["import_zod","HeartbeatMessageSchema","_a"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IncomingMessage, Server } from 'http';
|
|
2
2
|
import { WebSocket } from 'ws';
|
|
3
|
-
import { R as RPCDefinition, g as StreamEndpoints, c as MutationEndpoints } from './stream-types-
|
|
3
|
+
import { R as RPCDefinition, g as StreamEndpoints, c as MutationEndpoints } from './stream-types-Cn-LsIb9.cjs';
|
|
4
4
|
import { Graph } from 'derivation';
|
|
5
5
|
import { P as PresenceHandler } from './presence-manager-4LlEyuGp.cjs';
|
|
6
6
|
|
|
@@ -8,6 +8,13 @@ type WebSocketServerOptions<Ctx> = {
|
|
|
8
8
|
createContext: (ws: WebSocket, req: IncomingMessage) => Ctx | Promise<Ctx>;
|
|
9
9
|
presenceHandler?: PresenceHandler;
|
|
10
10
|
path?: string;
|
|
11
|
+
verifyUpgradeRequest?: (req: IncomingMessage) => true | {
|
|
12
|
+
statusCode?: number;
|
|
13
|
+
reason?: string;
|
|
14
|
+
} | Promise<true | {
|
|
15
|
+
statusCode?: number;
|
|
16
|
+
reason?: string;
|
|
17
|
+
}>;
|
|
11
18
|
};
|
|
12
19
|
declare function setupWebSocketServer<Defs extends RPCDefinition, Ctx = void>(graph: Graph, server: Server, streamEndpoints: StreamEndpoints<Defs["streams"], Ctx>, mutationEndpoints: MutationEndpoints<Defs["mutations"], Ctx>, options: WebSocketServerOptions<Ctx>): void;
|
|
13
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IncomingMessage, Server } from 'http';
|
|
2
2
|
import { WebSocket } from 'ws';
|
|
3
|
-
import { R as RPCDefinition, g as StreamEndpoints, c as MutationEndpoints } from './stream-types-
|
|
3
|
+
import { R as RPCDefinition, g as StreamEndpoints, c as MutationEndpoints } from './stream-types-Cn-LsIb9.js';
|
|
4
4
|
import { Graph } from 'derivation';
|
|
5
5
|
import { P as PresenceHandler } from './presence-manager-4LlEyuGp.js';
|
|
6
6
|
|
|
@@ -8,6 +8,13 @@ type WebSocketServerOptions<Ctx> = {
|
|
|
8
8
|
createContext: (ws: WebSocket, req: IncomingMessage) => Ctx | Promise<Ctx>;
|
|
9
9
|
presenceHandler?: PresenceHandler;
|
|
10
10
|
path?: string;
|
|
11
|
+
verifyUpgradeRequest?: (req: IncomingMessage) => true | {
|
|
12
|
+
statusCode?: number;
|
|
13
|
+
reason?: string;
|
|
14
|
+
} | Promise<true | {
|
|
15
|
+
statusCode?: number;
|
|
16
|
+
reason?: string;
|
|
17
|
+
}>;
|
|
11
18
|
};
|
|
12
19
|
declare function setupWebSocketServer<Defs extends RPCDefinition, Ctx = void>(graph: Graph, server: Server, streamEndpoints: StreamEndpoints<Defs["streams"], Ctx>, mutationEndpoints: MutationEndpoints<Defs["mutations"], Ctx>, options: WebSocketServerOptions<Ctx>): void;
|
|
13
20
|
|
package/dist/websocket-server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/websocket/web-socket-server.ts
|
|
2
2
|
import { parse } from "url";
|
|
3
|
+
import { STATUS_CODES } from "http";
|
|
3
4
|
import { WebSocketServer } from "ws";
|
|
4
5
|
|
|
5
6
|
// src/client-message.ts
|
|
@@ -359,8 +360,32 @@ var WeakList = class {
|
|
|
359
360
|
};
|
|
360
361
|
|
|
361
362
|
// src/websocket/web-socket-server.ts
|
|
363
|
+
function rejectUpgrade(socket, statusCode, reason) {
|
|
364
|
+
var _a;
|
|
365
|
+
const safeStatusCode = Number.isInteger(statusCode) ? statusCode : 403;
|
|
366
|
+
const statusText = (_a = STATUS_CODES[safeStatusCode]) != null ? _a : "Forbidden";
|
|
367
|
+
const body = reason || statusText;
|
|
368
|
+
const headers = [
|
|
369
|
+
`HTTP/1.1 ${safeStatusCode} ${statusText}`,
|
|
370
|
+
"Connection: close",
|
|
371
|
+
"Content-Type: text/plain; charset=utf-8",
|
|
372
|
+
`Content-Length: ${Buffer.byteLength(body)}`,
|
|
373
|
+
"",
|
|
374
|
+
body
|
|
375
|
+
];
|
|
376
|
+
try {
|
|
377
|
+
socket.write(headers.join("\r\n"));
|
|
378
|
+
} finally {
|
|
379
|
+
socket.destroy();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
362
382
|
function setupWebSocketServer(graph, server, streamEndpoints, mutationEndpoints, options) {
|
|
363
|
-
const {
|
|
383
|
+
const {
|
|
384
|
+
createContext,
|
|
385
|
+
presenceHandler,
|
|
386
|
+
path = "/api/ws",
|
|
387
|
+
verifyUpgradeRequest
|
|
388
|
+
} = options;
|
|
364
389
|
const clients = new WeakList();
|
|
365
390
|
graph.afterStep(() => {
|
|
366
391
|
for (const client of clients) {
|
|
@@ -398,14 +423,29 @@ function setupWebSocketServer(graph, server, streamEndpoints, mutationEndpoints,
|
|
|
398
423
|
}
|
|
399
424
|
});
|
|
400
425
|
server.on("upgrade", (req, socket, head) => {
|
|
426
|
+
var _a;
|
|
401
427
|
const { pathname } = parse(req.url || "/", true);
|
|
402
|
-
if (pathname
|
|
428
|
+
if (pathname !== path) {
|
|
429
|
+
socket.destroy();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
Promise.resolve((_a = verifyUpgradeRequest == null ? void 0 : verifyUpgradeRequest(req)) != null ? _a : true).then((verificationResult) => {
|
|
433
|
+
var _a2, _b;
|
|
434
|
+
if (verificationResult !== true) {
|
|
435
|
+
rejectUpgrade(
|
|
436
|
+
socket,
|
|
437
|
+
(_a2 = verificationResult.statusCode) != null ? _a2 : 403,
|
|
438
|
+
(_b = verificationResult.reason) != null ? _b : "Forbidden"
|
|
439
|
+
);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
403
442
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
404
443
|
wss.emit("connection", ws, req);
|
|
405
444
|
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
445
|
+
}).catch((err) => {
|
|
446
|
+
console.error("Error verifying upgrade request:", err);
|
|
447
|
+
rejectUpgrade(socket, 500, "Internal Server Error");
|
|
448
|
+
});
|
|
409
449
|
});
|
|
410
450
|
}
|
|
411
451
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/websocket/web-socket-server.ts","../src/client-message.ts","../src/server-message.ts","../src/websocket/queue.ts","../src/websocket/rate-limiter.ts","../src/websocket/web-socket-client-handler.ts","../src/weak-list.ts"],"sourcesContent":["import { parse } from \"url\";\nimport { Server, IncomingMessage } from \"http\";\nimport { WebSocketServer, WebSocket, RawData } from \"ws\";\nimport { WebSocketClientHandler } from \"./web-socket-client-handler.js\";\nimport WeakList from \"../weak-list.js\";\nimport {\n StreamEndpoints,\n MutationEndpoints,\n RPCDefinition,\n} from \"../stream-types.js\";\nimport { Graph } from \"derivation\";\nimport { PresenceHandler } from \"../presence-manager.js\";\n\nexport type WebSocketServerOptions<Ctx> = {\n createContext: (ws: WebSocket, req: IncomingMessage) => Ctx | Promise<Ctx>;\n presenceHandler?: PresenceHandler;\n path?: string;\n};\n\nexport function setupWebSocketServer<Defs extends RPCDefinition, Ctx = void>(\n graph: Graph,\n server: Server,\n streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>,\n mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>,\n options: WebSocketServerOptions<Ctx>,\n) {\n const { createContext, presenceHandler, path = \"/api/ws\" } = options;\n const clients = new WeakList<WebSocketClientHandler<Defs, Ctx>>();\n\n graph.afterStep(() => {\n for (const client of clients) {\n client.handleStep();\n }\n });\n\n const wss = new WebSocketServer({ noServer: true, maxPayload: 100 * 1024 });\n\n // Handle WebSocket connections for reactive streams\n wss.on(\"connection\", (ws, req) => {\n const { pathname } = parse(req.url || \"/\", true);\n if (pathname === path) {\n const messageBuffer: RawData[] = [];\n let client: WebSocketClientHandler<Defs, Ctx> | null = null;\n\n // Set up temporary message handler to buffer messages\n const tempMessageHandler = (msg: RawData) => {\n messageBuffer.push(msg);\n };\n ws.on(\"message\", tempMessageHandler);\n\n // Create context (handle both sync and async)\n Promise.resolve(createContext(ws, req))\n .then((context) => {\n // Remove temporary handler\n ws.removeListener(\"message\", tempMessageHandler);\n\n // Create client handler\n client = new WebSocketClientHandler<Defs, Ctx>(\n ws,\n context,\n streamEndpoints,\n mutationEndpoints,\n presenceHandler,\n );\n clients.add(client);\n\n // Process buffered messages\n for (const msg of messageBuffer) {\n client.handleMessage(msg.toString());\n }\n messageBuffer.length = 0;\n })\n .catch((err) => {\n console.error(\"Error creating context:\", err);\n ws.close();\n });\n }\n });\n\n // Handle HTTP upgrade (for /api/ws)\n server.on(\"upgrade\", (req, socket, head) => {\n const { pathname } = parse(req.url || \"/\", true);\n\n if (pathname === \"/api/ws\") {\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n } else {\n socket.destroy();\n }\n });\n}\n","import { z } from \"zod\";\n\nexport const SubscribeMessageSchema = z.object({\n type: z.literal(\"subscribe\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type SubscribeMessage = z.infer<typeof SubscribeMessageSchema>;\n\nexport const UnsubscribeMessageSchema = z.object({\n type: z.literal(\"unsubscribe\"),\n id: z.number(),\n});\nexport type UnsubscribeMessage = z.infer<typeof UnsubscribeMessageSchema>;\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const CallMessageSchema = z.object({\n type: z.literal(\"call\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type CallMessage = z.infer<typeof CallMessageSchema>;\n\nexport const PresenceMessageSchema = z.object({\n type: z.literal(\"presence\"),\n data: z.looseObject({}),\n});\nexport type PresenceMessage = z.infer<typeof PresenceMessageSchema>;\n\nexport const ClientMessageSchema = z.discriminatedUnion(\"type\", [\n SubscribeMessageSchema,\n UnsubscribeMessageSchema,\n HeartbeatMessageSchema,\n CallMessageSchema,\n PresenceMessageSchema,\n]);\nexport type ClientMessage = z.infer<typeof ClientMessageSchema>;\n\nexport function parseClientMessage(data: unknown): ClientMessage {\n return ClientMessageSchema.parse(data);\n}\n\nexport const ClientMessage = {\n subscribe: (\n id: number,\n name: string,\n args: object,\n ): SubscribeMessage => ({\n type: \"subscribe\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n unsubscribe: (id: number): UnsubscribeMessage => ({\n type: \"unsubscribe\",\n id,\n }),\n call: (\n id: number,\n name: string,\n args: object,\n ): CallMessage => ({\n type: \"call\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n presence: (data: object): PresenceMessage => ({\n type: \"presence\",\n data: data as Record<string, unknown>,\n }),\n};\n","import { z } from \"zod\";\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const SubscribedSchema = z.object({\n type: z.literal(\"snapshot\"),\n id: z.number(),\n snapshot: z.unknown(),\n});\nexport type SubscribedMessage = z.infer<typeof SubscribedSchema>;\n\nexport const DeltaMessageSchema = z.object({\n type: z.literal(\"delta\"),\n changes: z.record(z.number(), z.unknown()),\n});\nexport type DeltaMessage = z.infer<typeof DeltaMessageSchema>;\n\nexport const ResultMessageSchema = z.object({\n type: z.literal(\"result\"),\n id: z.number(),\n success: z.boolean(),\n value: z.unknown().optional(),\n error: z.string().optional(),\n});\nexport type ResultMessage = z.infer<typeof ResultMessageSchema>;\n\nexport const ServerMessageSchema = z.discriminatedUnion(\"type\", [\n HeartbeatMessageSchema,\n SubscribedSchema,\n DeltaMessageSchema,\n ResultMessageSchema,\n]);\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>;\n\nexport const ServerMessage = {\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n subscribed: (id: number, snapshot: unknown): SubscribedMessage => ({\n type: \"snapshot\",\n id,\n snapshot,\n }),\n delta: (changes: Record<string, unknown>): DeltaMessage => ({\n type: \"delta\",\n changes: changes,\n }),\n resultSuccess: (id: number, value: unknown): ResultMessage => ({\n type: \"result\",\n id,\n success: true,\n value,\n }),\n resultError: (id: number, error: string): ResultMessage => ({\n type: \"result\",\n id,\n success: false,\n error,\n }),\n};\n","export class Queue<T> {\n private front: T[] = [];\n private back: T[] = [];\n\n get length(): number {\n return this.front.length + this.back.length;\n }\n\n isEmpty(): boolean {\n return this.front.length === 0 && this.back.length === 0;\n }\n\n push(item: T): void {\n this.front.push(item);\n }\n\n peek(): T | undefined {\n if (this.back.length === 0) {\n if (this.front.length === 0) {\n return undefined;\n }\n return this.front[0];\n }\n return this.back[this.back.length - 1];\n }\n\n pop(): T | undefined {\n if (this.back.length === 0) {\n // Reverse front and swap to back\n this.back = this.front.reverse();\n this.front = [];\n }\n return this.back.pop();\n }\n}\n","import { Queue } from \"./queue.js\";\n\nexport class RateLimiter {\n private readonly timestamps = new Queue<bigint>();\n private readonly maxOccurrences: number;\n private readonly windowNanos: bigint;\n\n constructor(maxOccurrences: number, windowSeconds: number) {\n this.maxOccurrences = maxOccurrences;\n this.windowNanos = BigInt(windowSeconds) * BigInt(1_000_000_000);\n }\n\n trigger(): boolean {\n const now = process.hrtime.bigint();\n\n const cutoff = now - this.windowNanos;\n while (!this.timestamps.isEmpty()) {\n const oldest = this.timestamps.peek();\n if (oldest === undefined || oldest >= cutoff) {\n break;\n }\n this.timestamps.pop();\n }\n\n if (this.timestamps.length >= this.maxOccurrences) {\n return true;\n }\n\n this.timestamps.push(now);\n\n return false;\n }\n}\n","import { parseClientMessage, ClientMessage } from \"../client-message.js\";\nimport { ServerMessage } from \"../server-message.js\";\nimport {\n Source,\n StreamEndpoints,\n MutationEndpoints,\n RPCDefinition,\n} from \"../stream-types.js\";\nimport { RateLimiter } from \"./rate-limiter.js\";\nimport { PresenceHandler } from \"../presence-manager.js\";\nimport { WebSocket, RawData } from \"ws\";\n\nexport class WebSocketClientHandler<Defs extends RPCDefinition, Ctx = void> {\n private readonly ws: WebSocket;\n private readonly context: Ctx;\n private readonly streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>;\n private readonly mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>;\n private readonly presenceHandler?: PresenceHandler;\n private currentPresence?: Record<string, unknown>;\n private closed = false;\n private readonly streams = new Map<number, Source<unknown>>();\n private heartbeatTimeout: NodeJS.Timeout | undefined;\n private inactivityTimeout: NodeJS.Timeout | undefined;\n private readonly rateLimiter: RateLimiter;\n\n constructor(\n ws: WebSocket,\n context: Ctx,\n streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>,\n mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>,\n presenceHandler?: PresenceHandler,\n ) {\n this.ws = ws;\n this.context = context;\n this.streamEndpoints = streamEndpoints;\n this.mutationEndpoints = mutationEndpoints;\n this.presenceHandler = presenceHandler;\n this.rateLimiter = new RateLimiter(100, 300); // 100 messages over 5 minutes\n\n console.log(\"new client connected\");\n\n // Set up transport handlers\n this.ws.on(\"message\", (data: RawData) =>\n this.handleMessage(data.toString()),\n );\n this.ws.on(\"close\", () => this.handleDisconnect());\n\n this.resetHeartbeat();\n this.resetInactivity();\n }\n\n private resetHeartbeat() {\n if (this.heartbeatTimeout) {\n clearTimeout(this.heartbeatTimeout);\n }\n\n this.heartbeatTimeout = setTimeout(() => {\n this.sendMessage(ServerMessage.heartbeat());\n }, 10_000);\n }\n\n private resetInactivity() {\n if (this.inactivityTimeout) {\n clearTimeout(this.inactivityTimeout);\n }\n\n this.inactivityTimeout = setTimeout(() => {\n this.close();\n }, 30_000);\n }\n\n handleMessage(message: string) {\n this.resetInactivity();\n\n // Check rate limit\n if (this.rateLimiter.trigger()) {\n console.log(\"Rate limit exceeded, closing connection\");\n this.close();\n return;\n }\n\n let data: object;\n try {\n data = JSON.parse(message);\n } catch {\n console.error(\"Invalid JSON received:\", message);\n return this.close();\n }\n\n let parsed: ClientMessage;\n try {\n parsed = parseClientMessage(data);\n } catch (err) {\n console.error(\"Invalid client message:\", err);\n return this.close();\n }\n\n this.handleClientMessage(parsed);\n }\n\n async handleClientMessage(message: ClientMessage) {\n switch (message.type) {\n case \"subscribe\": {\n const { id, name, args } = message;\n\n if (!(name in this.streamEndpoints)) {\n console.error(`Unknown stream: ${name}`);\n this.close();\n return;\n }\n\n const endpoint = this.streamEndpoints[name as keyof Defs[\"streams\"]];\n\n try {\n const source = await endpoint(\n args as Defs[\"streams\"][keyof Defs[\"streams\"]][\"args\"],\n this.context,\n );\n this.streams.set(id, source);\n this.sendMessage(ServerMessage.subscribed(id, source.Snapshot));\n console.log(`Client subscribed to \\\"${name}\\\" (${id})`);\n } catch (err) {\n console.error(`Error building stream ${name}:`, err);\n this.close();\n }\n break;\n }\n\n case \"unsubscribe\": {\n const { id } = message;\n this.streams.delete(id);\n console.log(`Client unsubscribed from ${id}`);\n break;\n }\n\n case \"call\": {\n const { id, name, args } = message;\n\n if (!(name in this.mutationEndpoints)) {\n console.error(`Unknown mutation: ${name}`);\n this.close();\n return;\n }\n\n const endpoint =\n this.mutationEndpoints[name as keyof Defs[\"mutations\"]];\n\n endpoint(\n args as Defs[\"mutations\"][keyof Defs[\"mutations\"]][\"args\"],\n this.context,\n )\n .then((result) => {\n if (result.success) {\n this.sendMessage(ServerMessage.resultSuccess(id, result.value));\n console.log(\n `Mutation \\\"${name}\\\" (${id}) completed successfully`,\n );\n } else {\n this.sendMessage(ServerMessage.resultError(id, result.error));\n console.log(\n `Mutation \\\"${name}\\\" (${id}) returned error: ${result.error}`,\n );\n }\n })\n .catch((err) => {\n console.error(\n `Unhandled exception in mutation \\\"${name}\\\" (${id}):`,\n err,\n );\n this.close();\n });\n break;\n }\n\n case \"heartbeat\":\n break;\n\n case \"presence\": {\n if (!this.presenceHandler) {\n console.error(\"Presence not configured\");\n this.close();\n return;\n }\n\n const { data } = message;\n\n if (this.currentPresence !== undefined) {\n this.presenceHandler.update(this.currentPresence, data);\n } else {\n this.presenceHandler.add(data);\n }\n\n this.currentPresence = data;\n break;\n }\n }\n }\n\n handleStep() {\n if (this.closed) return;\n const changes: Record<number, unknown> = {};\n\n for (const [id, source] of this.streams) {\n const change = source.LastChange;\n if (change === null) continue;\n changes[id] = change;\n }\n\n if (Object.keys(changes).length > 0) {\n this.sendMessage(ServerMessage.delta(changes));\n }\n }\n\n sendMessage(message: ServerMessage) {\n this.resetHeartbeat();\n\n if (!this.closed) {\n // Check buffer if available (WebSocket provides this, MessagePort doesn't)\n if (this.ws.bufferedAmount > 100 * 1024) {\n console.log(\"Send buffer exceeded 100KB, closing connection\");\n this.close();\n return;\n }\n\n try {\n this.ws.send(JSON.stringify(message));\n } catch (err) {\n console.error(\"Failed to send message:\", err);\n this.close();\n }\n }\n }\n\n private handleDisconnect() {\n console.log(\"client disconnected\");\n this.close();\n }\n\n close() {\n if (this.closed) return;\n this.closed = true;\n clearTimeout(this.heartbeatTimeout);\n clearTimeout(this.inactivityTimeout);\n\n if (this.presenceHandler && this.currentPresence !== undefined) {\n this.presenceHandler.remove(this.currentPresence);\n }\n\n try {\n this.ws.close();\n } catch {}\n }\n}\n","export default class WeakList<T extends object> implements Iterable<T> {\n private items: WeakRef<T>[] = [];\n\n add(value: T): void {\n this.items.push(new WeakRef(value));\n }\n\n *[Symbol.iterator](): Iterator<T> {\n const newItems: WeakRef<T>[] = [];\n\n for (const ref of this.items) {\n const value = ref.deref();\n if (value !== undefined) {\n yield value;\n newItems.push(ref);\n }\n }\n\n this.items = newItems;\n }\n}\n"],"mappings":"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","names":["z","HeartbeatMessageSchema"]}
|
|
1
|
+
{"version":3,"sources":["../src/websocket/web-socket-server.ts","../src/client-message.ts","../src/server-message.ts","../src/websocket/queue.ts","../src/websocket/rate-limiter.ts","../src/websocket/web-socket-client-handler.ts","../src/weak-list.ts"],"sourcesContent":["import { parse } from \"url\";\nimport { Server, IncomingMessage, STATUS_CODES } from \"http\";\nimport { WebSocketServer, WebSocket, RawData } from \"ws\";\nimport { WebSocketClientHandler } from \"./web-socket-client-handler.js\";\nimport WeakList from \"../weak-list.js\";\nimport {\n StreamEndpoints,\n MutationEndpoints,\n RPCDefinition,\n} from \"../stream-types.js\";\nimport { Graph } from \"derivation\";\nimport { PresenceHandler } from \"../presence-manager.js\";\n\nexport type WebSocketServerOptions<Ctx> = {\n createContext: (ws: WebSocket, req: IncomingMessage) => Ctx | Promise<Ctx>;\n presenceHandler?: PresenceHandler;\n path?: string;\n verifyUpgradeRequest?: (\n req: IncomingMessage,\n ) =>\n | true\n | { statusCode?: number; reason?: string }\n | Promise<true | { statusCode?: number; reason?: string }>;\n};\n\nfunction rejectUpgrade(\n socket: NodeJS.WritableStream & { destroy(error?: Error): void },\n statusCode: number,\n reason: string,\n) {\n const safeStatusCode = Number.isInteger(statusCode) ? statusCode : 403;\n const statusText = STATUS_CODES[safeStatusCode] ?? \"Forbidden\";\n const body = reason || statusText;\n const headers = [\n `HTTP/1.1 ${safeStatusCode} ${statusText}`,\n \"Connection: close\",\n \"Content-Type: text/plain; charset=utf-8\",\n `Content-Length: ${Buffer.byteLength(body)}`,\n \"\",\n body,\n ];\n\n try {\n socket.write(headers.join(\"\\r\\n\"));\n } finally {\n socket.destroy();\n }\n}\n\nexport function setupWebSocketServer<Defs extends RPCDefinition, Ctx = void>(\n graph: Graph,\n server: Server,\n streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>,\n mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>,\n options: WebSocketServerOptions<Ctx>,\n) {\n const {\n createContext,\n presenceHandler,\n path = \"/api/ws\",\n verifyUpgradeRequest,\n } = options;\n const clients = new WeakList<WebSocketClientHandler<Defs, Ctx>>();\n\n graph.afterStep(() => {\n for (const client of clients) {\n client.handleStep();\n }\n });\n\n const wss = new WebSocketServer({ noServer: true, maxPayload: 100 * 1024 });\n\n // Handle WebSocket connections for reactive streams\n wss.on(\"connection\", (ws, req) => {\n const { pathname } = parse(req.url || \"/\", true);\n if (pathname === path) {\n const messageBuffer: RawData[] = [];\n let client: WebSocketClientHandler<Defs, Ctx> | null = null;\n\n // Set up temporary message handler to buffer messages\n const tempMessageHandler = (msg: RawData) => {\n messageBuffer.push(msg);\n };\n ws.on(\"message\", tempMessageHandler);\n\n // Create context (handle both sync and async)\n Promise.resolve(createContext(ws, req))\n .then((context) => {\n // Remove temporary handler\n ws.removeListener(\"message\", tempMessageHandler);\n\n // Create client handler\n client = new WebSocketClientHandler<Defs, Ctx>(\n ws,\n context,\n streamEndpoints,\n mutationEndpoints,\n presenceHandler,\n );\n clients.add(client);\n\n // Process buffered messages\n for (const msg of messageBuffer) {\n client.handleMessage(msg.toString());\n }\n messageBuffer.length = 0;\n })\n .catch((err) => {\n console.error(\"Error creating context:\", err);\n ws.close();\n });\n }\n });\n\n // Handle HTTP upgrade (for /api/ws)\n server.on(\"upgrade\", (req, socket, head) => {\n const { pathname } = parse(req.url || \"/\", true);\n\n if (pathname !== path) {\n socket.destroy();\n return;\n }\n\n Promise.resolve(verifyUpgradeRequest?.(req) ?? true)\n .then((verificationResult) => {\n if (verificationResult !== true) {\n rejectUpgrade(\n socket,\n verificationResult.statusCode ?? 403,\n verificationResult.reason ?? \"Forbidden\",\n );\n return;\n }\n\n wss.handleUpgrade(req, socket, head, (ws) => {\n wss.emit(\"connection\", ws, req);\n });\n })\n .catch((err) => {\n console.error(\"Error verifying upgrade request:\", err);\n rejectUpgrade(socket, 500, \"Internal Server Error\");\n });\n });\n}\n","import { z } from \"zod\";\n\nexport const SubscribeMessageSchema = z.object({\n type: z.literal(\"subscribe\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type SubscribeMessage = z.infer<typeof SubscribeMessageSchema>;\n\nexport const UnsubscribeMessageSchema = z.object({\n type: z.literal(\"unsubscribe\"),\n id: z.number(),\n});\nexport type UnsubscribeMessage = z.infer<typeof UnsubscribeMessageSchema>;\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const CallMessageSchema = z.object({\n type: z.literal(\"call\"),\n id: z.number(),\n name: z.string(),\n args: z.looseObject({}),\n});\nexport type CallMessage = z.infer<typeof CallMessageSchema>;\n\nexport const PresenceMessageSchema = z.object({\n type: z.literal(\"presence\"),\n data: z.looseObject({}),\n});\nexport type PresenceMessage = z.infer<typeof PresenceMessageSchema>;\n\nexport const ClientMessageSchema = z.discriminatedUnion(\"type\", [\n SubscribeMessageSchema,\n UnsubscribeMessageSchema,\n HeartbeatMessageSchema,\n CallMessageSchema,\n PresenceMessageSchema,\n]);\nexport type ClientMessage = z.infer<typeof ClientMessageSchema>;\n\nexport function parseClientMessage(data: unknown): ClientMessage {\n return ClientMessageSchema.parse(data);\n}\n\nexport const ClientMessage = {\n subscribe: (\n id: number,\n name: string,\n args: object,\n ): SubscribeMessage => ({\n type: \"subscribe\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n unsubscribe: (id: number): UnsubscribeMessage => ({\n type: \"unsubscribe\",\n id,\n }),\n call: (\n id: number,\n name: string,\n args: object,\n ): CallMessage => ({\n type: \"call\",\n id,\n name,\n args: args as Record<string, unknown>,\n }),\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n presence: (data: object): PresenceMessage => ({\n type: \"presence\",\n data: data as Record<string, unknown>,\n }),\n};\n","import { z } from \"zod\";\n\nexport const HeartbeatMessageSchema = z.object({\n type: z.literal(\"heartbeat\"),\n});\nexport type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;\n\nexport const SubscribedSchema = z.object({\n type: z.literal(\"snapshot\"),\n id: z.number(),\n snapshot: z.unknown(),\n});\nexport type SubscribedMessage = z.infer<typeof SubscribedSchema>;\n\nexport const DeltaMessageSchema = z.object({\n type: z.literal(\"delta\"),\n changes: z.record(z.number(), z.unknown()),\n});\nexport type DeltaMessage = z.infer<typeof DeltaMessageSchema>;\n\nexport const ResultMessageSchema = z.object({\n type: z.literal(\"result\"),\n id: z.number(),\n success: z.boolean(),\n value: z.unknown().optional(),\n error: z.string().optional(),\n});\nexport type ResultMessage = z.infer<typeof ResultMessageSchema>;\n\nexport const ServerMessageSchema = z.discriminatedUnion(\"type\", [\n HeartbeatMessageSchema,\n SubscribedSchema,\n DeltaMessageSchema,\n ResultMessageSchema,\n]);\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>;\n\nexport const ServerMessage = {\n heartbeat: (): HeartbeatMessage => ({\n type: \"heartbeat\",\n }),\n subscribed: (id: number, snapshot: unknown): SubscribedMessage => ({\n type: \"snapshot\",\n id,\n snapshot,\n }),\n delta: (changes: Record<string, unknown>): DeltaMessage => ({\n type: \"delta\",\n changes: changes,\n }),\n resultSuccess: (id: number, value: unknown): ResultMessage => ({\n type: \"result\",\n id,\n success: true,\n value,\n }),\n resultError: (id: number, error: string): ResultMessage => ({\n type: \"result\",\n id,\n success: false,\n error,\n }),\n};\n","export class Queue<T> {\n private front: T[] = [];\n private back: T[] = [];\n\n get length(): number {\n return this.front.length + this.back.length;\n }\n\n isEmpty(): boolean {\n return this.front.length === 0 && this.back.length === 0;\n }\n\n push(item: T): void {\n this.front.push(item);\n }\n\n peek(): T | undefined {\n if (this.back.length === 0) {\n if (this.front.length === 0) {\n return undefined;\n }\n return this.front[0];\n }\n return this.back[this.back.length - 1];\n }\n\n pop(): T | undefined {\n if (this.back.length === 0) {\n // Reverse front and swap to back\n this.back = this.front.reverse();\n this.front = [];\n }\n return this.back.pop();\n }\n}\n","import { Queue } from \"./queue.js\";\n\nexport class RateLimiter {\n private readonly timestamps = new Queue<bigint>();\n private readonly maxOccurrences: number;\n private readonly windowNanos: bigint;\n\n constructor(maxOccurrences: number, windowSeconds: number) {\n this.maxOccurrences = maxOccurrences;\n this.windowNanos = BigInt(windowSeconds) * BigInt(1_000_000_000);\n }\n\n trigger(): boolean {\n const now = process.hrtime.bigint();\n\n const cutoff = now - this.windowNanos;\n while (!this.timestamps.isEmpty()) {\n const oldest = this.timestamps.peek();\n if (oldest === undefined || oldest >= cutoff) {\n break;\n }\n this.timestamps.pop();\n }\n\n if (this.timestamps.length >= this.maxOccurrences) {\n return true;\n }\n\n this.timestamps.push(now);\n\n return false;\n }\n}\n","import { parseClientMessage, ClientMessage } from \"../client-message.js\";\nimport { ServerMessage } from \"../server-message.js\";\nimport {\n Source,\n StreamEndpoints,\n MutationEndpoints,\n RPCDefinition,\n} from \"../stream-types.js\";\nimport { RateLimiter } from \"./rate-limiter.js\";\nimport { PresenceHandler } from \"../presence-manager.js\";\nimport { WebSocket, RawData } from \"ws\";\n\nexport class WebSocketClientHandler<Defs extends RPCDefinition, Ctx = void> {\n private readonly ws: WebSocket;\n private readonly context: Ctx;\n private readonly streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>;\n private readonly mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>;\n private readonly presenceHandler?: PresenceHandler;\n private currentPresence?: Record<string, unknown>;\n private closed = false;\n private readonly streams = new Map<number, Source<unknown>>();\n private heartbeatTimeout: NodeJS.Timeout | undefined;\n private inactivityTimeout: NodeJS.Timeout | undefined;\n private readonly rateLimiter: RateLimiter;\n\n constructor(\n ws: WebSocket,\n context: Ctx,\n streamEndpoints: StreamEndpoints<Defs[\"streams\"], Ctx>,\n mutationEndpoints: MutationEndpoints<Defs[\"mutations\"], Ctx>,\n presenceHandler?: PresenceHandler,\n ) {\n this.ws = ws;\n this.context = context;\n this.streamEndpoints = streamEndpoints;\n this.mutationEndpoints = mutationEndpoints;\n this.presenceHandler = presenceHandler;\n this.rateLimiter = new RateLimiter(100, 300); // 100 messages over 5 minutes\n\n console.log(\"new client connected\");\n\n // Set up transport handlers\n this.ws.on(\"message\", (data: RawData) =>\n this.handleMessage(data.toString()),\n );\n this.ws.on(\"close\", () => this.handleDisconnect());\n\n this.resetHeartbeat();\n this.resetInactivity();\n }\n\n private resetHeartbeat() {\n if (this.heartbeatTimeout) {\n clearTimeout(this.heartbeatTimeout);\n }\n\n this.heartbeatTimeout = setTimeout(() => {\n this.sendMessage(ServerMessage.heartbeat());\n }, 10_000);\n }\n\n private resetInactivity() {\n if (this.inactivityTimeout) {\n clearTimeout(this.inactivityTimeout);\n }\n\n this.inactivityTimeout = setTimeout(() => {\n this.close();\n }, 30_000);\n }\n\n handleMessage(message: string) {\n this.resetInactivity();\n\n // Check rate limit\n if (this.rateLimiter.trigger()) {\n console.log(\"Rate limit exceeded, closing connection\");\n this.close();\n return;\n }\n\n let data: object;\n try {\n data = JSON.parse(message);\n } catch {\n console.error(\"Invalid JSON received:\", message);\n return this.close();\n }\n\n let parsed: ClientMessage;\n try {\n parsed = parseClientMessage(data);\n } catch (err) {\n console.error(\"Invalid client message:\", err);\n return this.close();\n }\n\n this.handleClientMessage(parsed);\n }\n\n async handleClientMessage(message: ClientMessage) {\n switch (message.type) {\n case \"subscribe\": {\n const { id, name, args } = message;\n\n if (!(name in this.streamEndpoints)) {\n console.error(`Unknown stream: ${name}`);\n this.close();\n return;\n }\n\n const endpoint = this.streamEndpoints[name as keyof Defs[\"streams\"]];\n\n try {\n const source = await endpoint(\n args as Defs[\"streams\"][keyof Defs[\"streams\"]][\"args\"],\n this.context,\n );\n this.streams.set(id, source);\n this.sendMessage(ServerMessage.subscribed(id, source.Snapshot));\n console.log(`Client subscribed to \\\"${name}\\\" (${id})`);\n } catch (err) {\n console.error(`Error building stream ${name}:`, err);\n this.close();\n }\n break;\n }\n\n case \"unsubscribe\": {\n const { id } = message;\n this.streams.delete(id);\n console.log(`Client unsubscribed from ${id}`);\n break;\n }\n\n case \"call\": {\n const { id, name, args } = message;\n\n if (!(name in this.mutationEndpoints)) {\n console.error(`Unknown mutation: ${name}`);\n this.close();\n return;\n }\n\n const endpoint =\n this.mutationEndpoints[name as keyof Defs[\"mutations\"]];\n\n endpoint(\n args as Defs[\"mutations\"][keyof Defs[\"mutations\"]][\"args\"],\n this.context,\n )\n .then((result) => {\n if (result.success) {\n this.sendMessage(ServerMessage.resultSuccess(id, result.value));\n console.log(\n `Mutation \\\"${name}\\\" (${id}) completed successfully`,\n );\n } else {\n this.sendMessage(ServerMessage.resultError(id, result.error));\n console.log(\n `Mutation \\\"${name}\\\" (${id}) returned error: ${result.error}`,\n );\n }\n })\n .catch((err) => {\n console.error(\n `Unhandled exception in mutation \\\"${name}\\\" (${id}):`,\n err,\n );\n this.close();\n });\n break;\n }\n\n case \"heartbeat\":\n break;\n\n case \"presence\": {\n if (!this.presenceHandler) {\n console.error(\"Presence not configured\");\n this.close();\n return;\n }\n\n const { data } = message;\n\n if (this.currentPresence !== undefined) {\n this.presenceHandler.update(this.currentPresence, data);\n } else {\n this.presenceHandler.add(data);\n }\n\n this.currentPresence = data;\n break;\n }\n }\n }\n\n handleStep() {\n if (this.closed) return;\n const changes: Record<number, unknown> = {};\n\n for (const [id, source] of this.streams) {\n const change = source.LastChange;\n if (change === null) continue;\n changes[id] = change;\n }\n\n if (Object.keys(changes).length > 0) {\n this.sendMessage(ServerMessage.delta(changes));\n }\n }\n\n sendMessage(message: ServerMessage) {\n this.resetHeartbeat();\n\n if (!this.closed) {\n // Check buffer if available (WebSocket provides this, MessagePort doesn't)\n if (this.ws.bufferedAmount > 100 * 1024) {\n console.log(\"Send buffer exceeded 100KB, closing connection\");\n this.close();\n return;\n }\n\n try {\n this.ws.send(JSON.stringify(message));\n } catch (err) {\n console.error(\"Failed to send message:\", err);\n this.close();\n }\n }\n }\n\n private handleDisconnect() {\n console.log(\"client disconnected\");\n this.close();\n }\n\n close() {\n if (this.closed) return;\n this.closed = true;\n clearTimeout(this.heartbeatTimeout);\n clearTimeout(this.inactivityTimeout);\n\n if (this.presenceHandler && this.currentPresence !== undefined) {\n this.presenceHandler.remove(this.currentPresence);\n }\n\n try {\n this.ws.close();\n } catch {}\n }\n}\n","export default class WeakList<T extends object> implements Iterable<T> {\n private items: WeakRef<T>[] = [];\n\n add(value: T): void {\n this.items.push(new WeakRef(value));\n }\n\n *[Symbol.iterator](): Iterator<T> {\n const newItems: WeakRef<T>[] = [];\n\n for (const ref of this.items) {\n const value = ref.deref();\n if (value !== undefined) {\n yield value;\n newItems.push(ref);\n }\n }\n\n this.items = newItems;\n }\n}\n"],"mappings":"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","names":["z","HeartbeatMessageSchema","_a"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@derivation/rpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"homepage": "https://github.com/derivationjs/rpc",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,24 +48,19 @@
|
|
|
48
48
|
"build": "tsup",
|
|
49
49
|
"test": "vitest run",
|
|
50
50
|
"test:watch": "vitest",
|
|
51
|
-
"lint": "eslint src",
|
|
52
51
|
"typecheck": "tsc --noEmit",
|
|
53
|
-
"prepack": "rm -rf dist && npm run
|
|
52
|
+
"prepack": "rm -rf dist && npm run typecheck && npm run test"
|
|
54
53
|
},
|
|
55
54
|
"dependencies": {
|
|
56
|
-
"@derivation/composable": "^0.
|
|
57
|
-
"@types/node": "^25.
|
|
55
|
+
"@derivation/composable": "^0.8.0",
|
|
56
|
+
"@types/node": "^25.4.0",
|
|
58
57
|
"@types/ws": "^8.18.1",
|
|
59
|
-
"derivation": "^0.
|
|
60
|
-
"immutable": "^5.1.
|
|
58
|
+
"derivation": "^0.8.0",
|
|
59
|
+
"immutable": "^5.1.5",
|
|
61
60
|
"ws": "^8.19.0",
|
|
62
61
|
"zod": "^4.3.6"
|
|
63
62
|
},
|
|
64
63
|
"devDependencies": {
|
|
65
|
-
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
66
|
-
"@typescript-eslint/parser": "^8.55.0",
|
|
67
|
-
"eslint": "^9.0.0",
|
|
68
|
-
"eslint-plugin-import": "^2.32.0",
|
|
69
64
|
"tsup": "^8.5.1",
|
|
70
65
|
"typescript": "^5.9.3",
|
|
71
66
|
"vitest": "^4.0.18"
|