@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/websocket/web-socket-client.ts","../src/client-message.ts"],"sourcesContent":["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","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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/websocket/web-socket-client.ts","../src/client-message.ts"],"sourcesContent":["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","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"],"mappings":"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","names":[]}
|
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
import { Graph } from 'derivation';
|
|
2
|
-
import { R as RPCDefinition, h as StreamSinks, d as MutationResult } from './stream-types-
|
|
2
|
+
import { R as RPCDefinition, h as StreamSinks, d as MutationResult } from './stream-types-Cn-LsIb9.cjs';
|
|
3
3
|
|
|
4
|
+
type WebSocketReconnectOptions = {
|
|
5
|
+
baseDelayMs?: number;
|
|
6
|
+
maxDelayMs?: number;
|
|
7
|
+
jitterRatio?: number;
|
|
8
|
+
maxReconnectAttempts?: number;
|
|
9
|
+
};
|
|
4
10
|
declare class WebSocketClient<Defs extends RPCDefinition> {
|
|
5
|
-
private
|
|
11
|
+
private url;
|
|
6
12
|
private sinks;
|
|
7
13
|
private graph;
|
|
14
|
+
private ws;
|
|
8
15
|
private nextId;
|
|
9
16
|
private pendingStreams;
|
|
10
17
|
private pendingMutations;
|
|
11
18
|
private activeStreams;
|
|
12
19
|
private heartbeatTimeout;
|
|
13
20
|
private inactivityTimeout;
|
|
21
|
+
private reconnectTimeout;
|
|
22
|
+
private reconnectAttempts;
|
|
23
|
+
private connected;
|
|
24
|
+
private closed;
|
|
25
|
+
private currentPresence;
|
|
26
|
+
private readonly reconnectOptions;
|
|
14
27
|
private registry;
|
|
28
|
+
constructor(url: string, sinks: StreamSinks<Defs["streams"]>, graph: Graph, reconnectOptions?: WebSocketReconnectOptions);
|
|
29
|
+
private createSocket;
|
|
30
|
+
private bindSocket;
|
|
31
|
+
private handleSocketOpen;
|
|
32
|
+
private handleTransportClose;
|
|
33
|
+
private scheduleReconnect;
|
|
34
|
+
private resubscribeAll;
|
|
35
|
+
private failPendingStreams;
|
|
36
|
+
private failPendingMutations;
|
|
15
37
|
private resetHeartbeat;
|
|
16
38
|
private resetInactivity;
|
|
17
|
-
constructor(ws: globalThis.WebSocket, sinks: StreamSinks<Defs["streams"]>, graph: Graph);
|
|
18
39
|
private handleMessage;
|
|
40
|
+
private isSocketOpen;
|
|
19
41
|
private sendMessage;
|
|
20
42
|
run<Key extends keyof Defs["streams"]>(key: Key, args: Defs["streams"][Key]["args"]): Promise<Defs["streams"][Key]["returnType"]>;
|
|
21
43
|
call<Key extends keyof Defs["mutations"]>(key: Key, args: Defs["mutations"][Key]["args"]): Promise<MutationResult<Defs["mutations"][Key]["result"]>>;
|
|
@@ -23,4 +45,4 @@ declare class WebSocketClient<Defs extends RPCDefinition> {
|
|
|
23
45
|
setPresence(value: Record<string, unknown>): void;
|
|
24
46
|
}
|
|
25
47
|
|
|
26
|
-
export { WebSocketClient };
|
|
48
|
+
export { WebSocketClient, type WebSocketReconnectOptions };
|
|
@@ -1,21 +1,43 @@
|
|
|
1
1
|
import { Graph } from 'derivation';
|
|
2
|
-
import { R as RPCDefinition, h as StreamSinks, d as MutationResult } from './stream-types-
|
|
2
|
+
import { R as RPCDefinition, h as StreamSinks, d as MutationResult } from './stream-types-Cn-LsIb9.js';
|
|
3
3
|
|
|
4
|
+
type WebSocketReconnectOptions = {
|
|
5
|
+
baseDelayMs?: number;
|
|
6
|
+
maxDelayMs?: number;
|
|
7
|
+
jitterRatio?: number;
|
|
8
|
+
maxReconnectAttempts?: number;
|
|
9
|
+
};
|
|
4
10
|
declare class WebSocketClient<Defs extends RPCDefinition> {
|
|
5
|
-
private
|
|
11
|
+
private url;
|
|
6
12
|
private sinks;
|
|
7
13
|
private graph;
|
|
14
|
+
private ws;
|
|
8
15
|
private nextId;
|
|
9
16
|
private pendingStreams;
|
|
10
17
|
private pendingMutations;
|
|
11
18
|
private activeStreams;
|
|
12
19
|
private heartbeatTimeout;
|
|
13
20
|
private inactivityTimeout;
|
|
21
|
+
private reconnectTimeout;
|
|
22
|
+
private reconnectAttempts;
|
|
23
|
+
private connected;
|
|
24
|
+
private closed;
|
|
25
|
+
private currentPresence;
|
|
26
|
+
private readonly reconnectOptions;
|
|
14
27
|
private registry;
|
|
28
|
+
constructor(url: string, sinks: StreamSinks<Defs["streams"]>, graph: Graph, reconnectOptions?: WebSocketReconnectOptions);
|
|
29
|
+
private createSocket;
|
|
30
|
+
private bindSocket;
|
|
31
|
+
private handleSocketOpen;
|
|
32
|
+
private handleTransportClose;
|
|
33
|
+
private scheduleReconnect;
|
|
34
|
+
private resubscribeAll;
|
|
35
|
+
private failPendingStreams;
|
|
36
|
+
private failPendingMutations;
|
|
15
37
|
private resetHeartbeat;
|
|
16
38
|
private resetInactivity;
|
|
17
|
-
constructor(ws: globalThis.WebSocket, sinks: StreamSinks<Defs["streams"]>, graph: Graph);
|
|
18
39
|
private handleMessage;
|
|
40
|
+
private isSocketOpen;
|
|
19
41
|
private sendMessage;
|
|
20
42
|
run<Key extends keyof Defs["streams"]>(key: Key, args: Defs["streams"][Key]["args"]): Promise<Defs["streams"][Key]["returnType"]>;
|
|
21
43
|
call<Key extends keyof Defs["mutations"]>(key: Key, args: Defs["mutations"][Key]["args"]): Promise<MutationResult<Defs["mutations"][Key]["result"]>>;
|
|
@@ -23,4 +45,4 @@ declare class WebSocketClient<Defs extends RPCDefinition> {
|
|
|
23
45
|
setPresence(value: Record<string, unknown>): void;
|
|
24
46
|
}
|
|
25
47
|
|
|
26
|
-
export { WebSocketClient };
|
|
48
|
+
export { WebSocketClient, type WebSocketReconnectOptions };
|
package/dist/websocket-client.js
CHANGED
|
@@ -57,23 +57,24 @@ var ClientMessage = {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
// src/websocket/web-socket-client.ts
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
};
|
|
60
|
+
var DISCONNECTED_ERROR = "WebSocket is disconnected";
|
|
61
|
+
var CONNECTION_CLOSED_ERROR = "WebSocket connection closed";
|
|
62
|
+
function randomJitter(range) {
|
|
63
|
+
if (range <= 0) return 0;
|
|
64
|
+
return (Math.random() * 2 - 1) * range;
|
|
67
65
|
}
|
|
68
66
|
var WebSocketClient = class {
|
|
69
|
-
constructor(
|
|
70
|
-
this.
|
|
67
|
+
constructor(url, sinks, graph, reconnectOptions = {}) {
|
|
68
|
+
this.url = url;
|
|
71
69
|
this.sinks = sinks;
|
|
72
70
|
this.graph = graph;
|
|
73
71
|
this.nextId = 1;
|
|
74
72
|
this.pendingStreams = /* @__PURE__ */ new Map();
|
|
75
73
|
this.pendingMutations = /* @__PURE__ */ new Map();
|
|
76
74
|
this.activeStreams = /* @__PURE__ */ new Map();
|
|
75
|
+
this.reconnectAttempts = 0;
|
|
76
|
+
this.connected = false;
|
|
77
|
+
this.closed = false;
|
|
77
78
|
this.registry = new FinalizationRegistry(
|
|
78
79
|
([id, name]) => {
|
|
79
80
|
console.log(`\u{1F9F9} Stream ${id} (${name}) collected \u2014 unsubscribing`);
|
|
@@ -81,14 +82,142 @@ var WebSocketClient = class {
|
|
|
81
82
|
this.activeStreams.delete(id);
|
|
82
83
|
}
|
|
83
84
|
);
|
|
84
|
-
|
|
85
|
+
var _a, _b, _c, _d;
|
|
86
|
+
this.reconnectOptions = {
|
|
87
|
+
baseDelayMs: (_a = reconnectOptions.baseDelayMs) != null ? _a : 500,
|
|
88
|
+
maxDelayMs: (_b = reconnectOptions.maxDelayMs) != null ? _b : 15e3,
|
|
89
|
+
jitterRatio: (_c = reconnectOptions.jitterRatio) != null ? _c : 0.2,
|
|
90
|
+
maxReconnectAttempts: (_d = reconnectOptions.maxReconnectAttempts) != null ? _d : Number.POSITIVE_INFINITY
|
|
91
|
+
};
|
|
92
|
+
this.ws = this.createSocket();
|
|
93
|
+
this.bindSocket(this.ws);
|
|
94
|
+
this.connected = this.isSocketOpen();
|
|
95
|
+
if (this.connected) {
|
|
96
|
+
this.resetHeartbeat();
|
|
97
|
+
this.resetInactivity();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
createSocket() {
|
|
101
|
+
return new globalThis.WebSocket(this.url);
|
|
102
|
+
}
|
|
103
|
+
bindSocket(socket) {
|
|
104
|
+
this.ws = socket;
|
|
105
|
+
socket.onopen = () => {
|
|
106
|
+
if (this.closed || this.ws !== socket) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.handleSocketOpen();
|
|
110
|
+
};
|
|
111
|
+
socket.onmessage = (event) => {
|
|
112
|
+
if (this.closed || this.ws !== socket) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
85
115
|
const message = JSON.parse(event.data);
|
|
86
116
|
this.handleMessage(message);
|
|
87
117
|
};
|
|
118
|
+
socket.onerror = () => {
|
|
119
|
+
if (this.closed || this.ws !== socket) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
this.handleTransportClose();
|
|
123
|
+
};
|
|
124
|
+
socket.onclose = () => {
|
|
125
|
+
if (this.closed || this.ws !== socket) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.handleTransportClose();
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
handleSocketOpen() {
|
|
132
|
+
this.connected = true;
|
|
133
|
+
this.reconnectAttempts = 0;
|
|
134
|
+
if (this.reconnectTimeout) {
|
|
135
|
+
clearTimeout(this.reconnectTimeout);
|
|
136
|
+
this.reconnectTimeout = void 0;
|
|
137
|
+
}
|
|
88
138
|
this.resetHeartbeat();
|
|
89
139
|
this.resetInactivity();
|
|
140
|
+
this.resubscribeAll();
|
|
141
|
+
if (this.currentPresence) {
|
|
142
|
+
this.sendMessage(ClientMessage.presence(this.currentPresence));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
handleTransportClose() {
|
|
146
|
+
if (!this.connected && this.reconnectTimeout) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.connected = false;
|
|
150
|
+
if (this.heartbeatTimeout) {
|
|
151
|
+
clearTimeout(this.heartbeatTimeout);
|
|
152
|
+
this.heartbeatTimeout = void 0;
|
|
153
|
+
}
|
|
154
|
+
if (this.inactivityTimeout) {
|
|
155
|
+
clearTimeout(this.inactivityTimeout);
|
|
156
|
+
this.inactivityTimeout = void 0;
|
|
157
|
+
}
|
|
158
|
+
this.failPendingMutations(DISCONNECTED_ERROR);
|
|
159
|
+
this.scheduleReconnect();
|
|
160
|
+
}
|
|
161
|
+
scheduleReconnect() {
|
|
162
|
+
if (this.closed || this.reconnectTimeout) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (this.reconnectAttempts >= this.reconnectOptions.maxReconnectAttempts) {
|
|
166
|
+
this.failPendingStreams(CONNECTION_CLOSED_ERROR);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const exponent = Math.min(this.reconnectAttempts, 30);
|
|
170
|
+
const baseDelay = this.reconnectOptions.baseDelayMs * 2 ** exponent;
|
|
171
|
+
const cappedDelay = Math.min(baseDelay, this.reconnectOptions.maxDelayMs);
|
|
172
|
+
const jitter = cappedDelay * this.reconnectOptions.jitterRatio;
|
|
173
|
+
const delay = Math.max(0, Math.round(cappedDelay + randomJitter(jitter)));
|
|
174
|
+
this.reconnectAttempts += 1;
|
|
175
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
176
|
+
this.reconnectTimeout = void 0;
|
|
177
|
+
if (this.closed) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const socket = this.createSocket();
|
|
182
|
+
this.bindSocket(socket);
|
|
183
|
+
this.connected = this.isSocketOpen();
|
|
184
|
+
if (this.connected) {
|
|
185
|
+
this.handleSocketOpen();
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error("Failed to create reconnect socket:", err);
|
|
189
|
+
this.scheduleReconnect();
|
|
190
|
+
}
|
|
191
|
+
}, delay);
|
|
192
|
+
}
|
|
193
|
+
resubscribeAll() {
|
|
194
|
+
for (const [id, pending] of this.pendingStreams) {
|
|
195
|
+
this.sendMessage(ClientMessage.subscribe(id, pending.name, pending.args));
|
|
196
|
+
}
|
|
197
|
+
for (const [id, active] of this.activeStreams) {
|
|
198
|
+
if (!active.input.deref()) {
|
|
199
|
+
this.activeStreams.delete(id);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
this.sendMessage(ClientMessage.subscribe(id, active.name, active.args));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
failPendingStreams(message) {
|
|
206
|
+
for (const [id, pending] of this.pendingStreams) {
|
|
207
|
+
pending.reject(new Error(message));
|
|
208
|
+
this.pendingStreams.delete(id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
failPendingMutations(message) {
|
|
212
|
+
for (const [id, resolve] of this.pendingMutations) {
|
|
213
|
+
resolve({ success: false, error: message });
|
|
214
|
+
this.pendingMutations.delete(id);
|
|
215
|
+
}
|
|
90
216
|
}
|
|
91
217
|
resetHeartbeat() {
|
|
218
|
+
if (!this.connected || this.closed) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
92
221
|
if (this.heartbeatTimeout) {
|
|
93
222
|
clearTimeout(this.heartbeatTimeout);
|
|
94
223
|
}
|
|
@@ -97,11 +226,18 @@ var WebSocketClient = class {
|
|
|
97
226
|
}, 1e4);
|
|
98
227
|
}
|
|
99
228
|
resetInactivity() {
|
|
229
|
+
if (!this.connected || this.closed) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
100
232
|
if (this.inactivityTimeout) {
|
|
101
233
|
clearTimeout(this.inactivityTimeout);
|
|
102
234
|
}
|
|
103
235
|
this.inactivityTimeout = setTimeout(() => {
|
|
104
|
-
|
|
236
|
+
try {
|
|
237
|
+
this.ws.close();
|
|
238
|
+
} catch (e) {
|
|
239
|
+
this.handleTransportClose();
|
|
240
|
+
}
|
|
105
241
|
}, 3e4);
|
|
106
242
|
}
|
|
107
243
|
handleMessage(message) {
|
|
@@ -109,25 +245,46 @@ var WebSocketClient = class {
|
|
|
109
245
|
switch (message.type) {
|
|
110
246
|
case "snapshot": {
|
|
111
247
|
console.log(`[WebSocketClient] Received snapshot for stream ${message.id}`);
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
resolve(message.snapshot);
|
|
248
|
+
const pending = this.pendingStreams.get(message.id);
|
|
249
|
+
if (pending) {
|
|
250
|
+
pending.resolve(message.snapshot);
|
|
115
251
|
this.pendingStreams.delete(message.id);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
const active = this.activeStreams.get(message.id);
|
|
255
|
+
if (!active) {
|
|
256
|
+
break;
|
|
116
257
|
}
|
|
258
|
+
const input = active.input.deref();
|
|
259
|
+
if (!input) {
|
|
260
|
+
this.sendMessage(ClientMessage.unsubscribe(message.id));
|
|
261
|
+
this.activeStreams.delete(message.id);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
active.sink.reset(message.snapshot, input);
|
|
265
|
+
this.graph.step();
|
|
117
266
|
break;
|
|
118
267
|
}
|
|
119
268
|
case "delta": {
|
|
120
269
|
const changeCount = Object.keys(message.changes).length;
|
|
121
|
-
console.log(
|
|
270
|
+
console.log(
|
|
271
|
+
`[WebSocketClient] Received delta with ${changeCount} changes for streams: ${Object.keys(message.changes).join(", ")}`
|
|
272
|
+
);
|
|
122
273
|
for (const [idStr, change] of Object.entries(message.changes)) {
|
|
123
274
|
const id = Number(idStr);
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
275
|
+
const active = this.activeStreams.get(id);
|
|
276
|
+
if (!active) {
|
|
277
|
+
this.sendMessage(ClientMessage.unsubscribe(id));
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const input = active.input.deref();
|
|
281
|
+
if (!input) {
|
|
129
282
|
this.sendMessage(ClientMessage.unsubscribe(id));
|
|
130
283
|
this.activeStreams.delete(id);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (change && typeof change === "object") {
|
|
287
|
+
active.sink.apply(change, input);
|
|
131
288
|
}
|
|
132
289
|
}
|
|
133
290
|
console.log("[WebSocketClient] Stepping graph");
|
|
@@ -135,19 +292,23 @@ var WebSocketClient = class {
|
|
|
135
292
|
break;
|
|
136
293
|
}
|
|
137
294
|
case "result": {
|
|
138
|
-
console.log(
|
|
295
|
+
console.log(
|
|
296
|
+
`[WebSocketClient] Received mutation result for call ${message.id}:`,
|
|
297
|
+
message.success ? "success" : "error"
|
|
298
|
+
);
|
|
139
299
|
const resolve = this.pendingMutations.get(message.id);
|
|
140
|
-
if (resolve) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
300
|
+
if (!resolve) {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
if (message.success) {
|
|
304
|
+
resolve({ success: true, value: message.value });
|
|
305
|
+
} else {
|
|
306
|
+
resolve({
|
|
307
|
+
success: false,
|
|
308
|
+
error: message.error || "Unknown error"
|
|
309
|
+
});
|
|
150
310
|
}
|
|
311
|
+
this.pendingMutations.delete(message.id);
|
|
151
312
|
break;
|
|
152
313
|
}
|
|
153
314
|
case "heartbeat":
|
|
@@ -155,52 +316,105 @@ var WebSocketClient = class {
|
|
|
155
316
|
break;
|
|
156
317
|
}
|
|
157
318
|
}
|
|
319
|
+
isSocketOpen() {
|
|
320
|
+
return this.ws.readyState === this.ws.OPEN;
|
|
321
|
+
}
|
|
158
322
|
sendMessage(message) {
|
|
323
|
+
if (this.closed) {
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
if (!this.isSocketOpen()) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
159
329
|
this.resetHeartbeat();
|
|
160
|
-
|
|
330
|
+
try {
|
|
331
|
+
this.ws.send(JSON.stringify(message));
|
|
332
|
+
return true;
|
|
333
|
+
} catch (e) {
|
|
334
|
+
this.handleTransportClose();
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
161
337
|
}
|
|
162
338
|
async run(key, args) {
|
|
163
339
|
console.log(
|
|
164
340
|
`Running stream ${String(key)} with args ${JSON.stringify(args)}`
|
|
165
341
|
);
|
|
166
342
|
const id = this.nextId++;
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
343
|
+
const name = String(key);
|
|
344
|
+
const typedArgs = args;
|
|
345
|
+
const snapshot = await new Promise((resolve, reject) => {
|
|
346
|
+
this.pendingStreams.set(id, { name, args: typedArgs, resolve, reject });
|
|
347
|
+
this.sendMessage(ClientMessage.subscribe(id, name, typedArgs));
|
|
170
348
|
});
|
|
171
349
|
const endpoint = this.sinks[key];
|
|
172
350
|
const sinkAdapter = endpoint(snapshot);
|
|
173
351
|
const { stream, input } = sinkAdapter.build();
|
|
174
352
|
const inputRef = new WeakRef(input);
|
|
175
|
-
this.activeStreams.set(id,
|
|
176
|
-
|
|
353
|
+
this.activeStreams.set(id, {
|
|
354
|
+
name,
|
|
355
|
+
args: typedArgs,
|
|
356
|
+
sink: sinkAdapter,
|
|
357
|
+
input: inputRef
|
|
358
|
+
});
|
|
359
|
+
this.registry.register(input, [id, name]);
|
|
177
360
|
return stream;
|
|
178
361
|
}
|
|
179
362
|
async call(key, args) {
|
|
180
363
|
console.log(
|
|
181
364
|
`Calling mutation ${String(key)} with args ${JSON.stringify(args)}`
|
|
182
365
|
);
|
|
366
|
+
if (!this.connected || !this.isSocketOpen()) {
|
|
367
|
+
return {
|
|
368
|
+
success: false,
|
|
369
|
+
error: DISCONNECTED_ERROR
|
|
370
|
+
};
|
|
371
|
+
}
|
|
183
372
|
const id = this.nextId++;
|
|
184
|
-
|
|
185
|
-
ClientMessage.call(id, String(key), args)
|
|
186
|
-
);
|
|
187
|
-
const result = await new Promise((resolve) => {
|
|
373
|
+
const resultPromise = new Promise((resolve) => {
|
|
188
374
|
this.pendingMutations.set(
|
|
189
375
|
id,
|
|
190
376
|
resolve
|
|
191
377
|
);
|
|
192
378
|
});
|
|
193
|
-
|
|
379
|
+
const sent = this.sendMessage(
|
|
380
|
+
ClientMessage.call(id, String(key), args)
|
|
381
|
+
);
|
|
382
|
+
if (!sent) {
|
|
383
|
+
this.pendingMutations.delete(id);
|
|
384
|
+
return {
|
|
385
|
+
success: false,
|
|
386
|
+
error: DISCONNECTED_ERROR
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return resultPromise;
|
|
194
390
|
}
|
|
195
391
|
close() {
|
|
196
|
-
|
|
197
|
-
|
|
392
|
+
if (this.closed) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
this.closed = true;
|
|
396
|
+
this.connected = false;
|
|
397
|
+
if (this.heartbeatTimeout) {
|
|
398
|
+
clearTimeout(this.heartbeatTimeout);
|
|
399
|
+
this.heartbeatTimeout = void 0;
|
|
400
|
+
}
|
|
401
|
+
if (this.inactivityTimeout) {
|
|
402
|
+
clearTimeout(this.inactivityTimeout);
|
|
403
|
+
this.inactivityTimeout = void 0;
|
|
404
|
+
}
|
|
405
|
+
if (this.reconnectTimeout) {
|
|
406
|
+
clearTimeout(this.reconnectTimeout);
|
|
407
|
+
this.reconnectTimeout = void 0;
|
|
408
|
+
}
|
|
409
|
+
this.failPendingStreams(CONNECTION_CLOSED_ERROR);
|
|
410
|
+
this.failPendingMutations(CONNECTION_CLOSED_ERROR);
|
|
198
411
|
try {
|
|
199
412
|
this.ws.close();
|
|
200
413
|
} catch (e) {
|
|
201
414
|
}
|
|
202
415
|
}
|
|
203
416
|
setPresence(value) {
|
|
417
|
+
this.currentPresence = value;
|
|
204
418
|
this.sendMessage(ClientMessage.presence(value));
|
|
205
419
|
}
|
|
206
420
|
};
|