@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.
@@ -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-Q_EqNLtO.cjs';
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 ws;
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-Q_EqNLtO.js';
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 ws;
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 };
@@ -57,23 +57,24 @@ var ClientMessage = {
57
57
  };
58
58
 
59
59
  // src/websocket/web-socket-client.ts
60
- function changer(sink, input) {
61
- return (change) => {
62
- const i = input.deref();
63
- if (i) {
64
- sink.apply(change, i);
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(ws, sinks, graph) {
70
- this.ws = ws;
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
- this.ws.onmessage = (event) => {
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
- this.close();
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 resolve = this.pendingStreams.get(message.id);
113
- if (resolve) {
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(`[WebSocketClient] Received delta with ${changeCount} changes for streams: ${Object.keys(message.changes).join(", ")}`);
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 sink = this.activeStreams.get(id);
125
- if (sink && change && typeof change === "object") {
126
- sink(change);
127
- } else if (!sink) {
128
- console.log(`\u{1F9F9} Sink ${id} GC'd \u2014 auto-unsubscribing`);
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(`[WebSocketClient] Received mutation result for call ${message.id}:`, message.success ? "success" : "error");
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
- if (message.success) {
142
- resolve({ success: true, value: message.value });
143
- } else {
144
- resolve({
145
- success: false,
146
- error: message.error || "Unknown error"
147
- });
148
- }
149
- this.pendingMutations.delete(message.id);
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
- this.ws.send(JSON.stringify(message));
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
- this.sendMessage(ClientMessage.subscribe(id, String(key), args));
168
- const snapshot = await new Promise((resolve) => {
169
- this.pendingStreams.set(id, resolve);
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, changer(sinkAdapter, inputRef));
176
- this.registry.register(input, [id, String(key)]);
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
- this.sendMessage(
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
- return result;
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
- clearTimeout(this.heartbeatTimeout);
197
- clearTimeout(this.inactivityTimeout);
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
  };