@hedystia/ws 2.3.1

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.
@@ -0,0 +1,119 @@
1
+ import { ServerWebSocket, UpgradeOptions, UpgradeRequest, WSData, WSMessage, WebSocketHandlers, WebSocketServerOptions } from "./types.mjs";
2
+
3
+ //#region src/server.d.ts
4
+ /**
5
+ * Runtime-agnostic WebSocket server.
6
+ *
7
+ * @remarks
8
+ * Internally backed by the [`ws`](https://github.com/websockets/ws) package
9
+ * which runs on Bun, Node.js and Deno (via `npm:` specifiers). The class
10
+ * does **not** create or own an HTTP server — callers feed it raw upgrade
11
+ * tuples coming from any HTTP runtime they prefer.
12
+ *
13
+ * It implements topic-based pub/sub on top of the per-connection
14
+ * `subscribe` / `unsubscribe` / `publish` API expected by Hedystia,
15
+ * matching the shape of `Bun.ServerWebSocket`.
16
+ *
17
+ * @typeParam Data - Shape of the user-attached `data` field
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { createServer } from "node:http";
22
+ * import { WebSocketServer } from "@hedystia/ws/server";
23
+ *
24
+ * const wss = new WebSocketServer({
25
+ * open: (ws) => ws.send("welcome"),
26
+ * message: (ws, msg) => ws.publish("room", msg),
27
+ * });
28
+ *
29
+ * const http = createServer((_req, res) => res.end("ok"));
30
+ * http.on("upgrade", (req, socket, head) => {
31
+ * wss.upgrade({ rawRequest: req, socket, head }, { data: { user: "anon" } });
32
+ * });
33
+ * http.listen(3000);
34
+ * ```
35
+ */
36
+ declare class WebSocketServer<Data extends WSData = WSData> {
37
+ private readonly handlers;
38
+ private readonly wss;
39
+ private readonly topics;
40
+ private readonly socketTopics;
41
+ private readonly allSockets;
42
+ /**
43
+ * Build a new WebSocket server.
44
+ *
45
+ * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})
46
+ * @param options - Optional behavioural overrides ({@link WebSocketServerOptions})
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const wss = new WebSocketServer(
51
+ * { message: (ws, msg) => ws.send(msg) },
52
+ * { maxPayload: 1024 * 1024 },
53
+ * );
54
+ * ```
55
+ */
56
+ constructor(handlers: WebSocketHandlers<Data>, options?: WebSocketServerOptions);
57
+ /**
58
+ * Upgrade a raw HTTP upgrade tuple to a WebSocket connection.
59
+ *
60
+ * @remarks
61
+ * The returned promise resolves to the connected {@link ServerWebSocket}
62
+ * once the handshake completes; rejection means the handshake failed.
63
+ *
64
+ * @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event
65
+ * @param options - Optional initial `data` for the new connection
66
+ * @returns Promise that resolves with the established socket wrapper
67
+ *
68
+ * @throws {Error} When the underlying handshake throws synchronously
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * http.on("upgrade", async (req, socket, head) => {
73
+ * try {
74
+ * await wss.upgrade({ rawRequest: req, socket, head });
75
+ * } catch (err) {
76
+ * console.error("Upgrade failed", err);
77
+ * socket.destroy();
78
+ * }
79
+ * });
80
+ * ```
81
+ */
82
+ upgrade(req: UpgradeRequest, options?: UpgradeOptions<Data>): Promise<ServerWebSocket<Data>>;
83
+ /**
84
+ * Publish a message to all sockets currently subscribed to `topic`.
85
+ *
86
+ * @param topic - Topic name
87
+ * @param message - Payload to broadcast
88
+ * @param _compress - Reserved for future use; ignored under the `ws` adapter
89
+ * @returns Number of sockets that received the message.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * wss.publish("room", JSON.stringify({ kind: "ping" }));
94
+ * ```
95
+ */
96
+ publish(topic: string, message: WSMessage, _compress?: boolean): number;
97
+ /**
98
+ * Close the server and optionally terminate all live sockets.
99
+ *
100
+ * @param closeActiveConnections - When `true`, calls `socket.terminate()`
101
+ * on every live connection before shutting down.
102
+ */
103
+ close(closeActiveConnections?: boolean): void;
104
+ /**
105
+ * Attach the per-socket lifecycle listeners (`message`, `close`, `error`).
106
+ *
107
+ * @internal
108
+ */
109
+ private bind;
110
+ /**
111
+ * Build the {@link ServerWebSocket} wrapper exposed to user handlers.
112
+ *
113
+ * @internal
114
+ */
115
+ private wrap;
116
+ }
117
+ //#endregion
118
+ export { type ServerWebSocket, type UpgradeOptions, type UpgradeRequest, type WSData, type WSMessage, type WebSocketHandlers, WebSocketServer, type WebSocketServerOptions };
119
+ //# sourceMappingURL=server.d.mts.map
@@ -0,0 +1,236 @@
1
+ import { WebSocketServer as WebSocketServer$1 } from "ws";
2
+ //#region src/server.ts
3
+ /**
4
+ * Runtime-agnostic WebSocket server.
5
+ *
6
+ * @remarks
7
+ * Internally backed by the [`ws`](https://github.com/websockets/ws) package
8
+ * which runs on Bun, Node.js and Deno (via `npm:` specifiers). The class
9
+ * does **not** create or own an HTTP server — callers feed it raw upgrade
10
+ * tuples coming from any HTTP runtime they prefer.
11
+ *
12
+ * It implements topic-based pub/sub on top of the per-connection
13
+ * `subscribe` / `unsubscribe` / `publish` API expected by Hedystia,
14
+ * matching the shape of `Bun.ServerWebSocket`.
15
+ *
16
+ * @typeParam Data - Shape of the user-attached `data` field
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { createServer } from "node:http";
21
+ * import { WebSocketServer } from "@hedystia/ws/server";
22
+ *
23
+ * const wss = new WebSocketServer({
24
+ * open: (ws) => ws.send("welcome"),
25
+ * message: (ws, msg) => ws.publish("room", msg),
26
+ * });
27
+ *
28
+ * const http = createServer((_req, res) => res.end("ok"));
29
+ * http.on("upgrade", (req, socket, head) => {
30
+ * wss.upgrade({ rawRequest: req, socket, head }, { data: { user: "anon" } });
31
+ * });
32
+ * http.listen(3000);
33
+ * ```
34
+ */
35
+ var WebSocketServer = class {
36
+ handlers;
37
+ wss;
38
+ topics = /* @__PURE__ */ new Map();
39
+ socketTopics = /* @__PURE__ */ new WeakMap();
40
+ allSockets = /* @__PURE__ */ new Set();
41
+ /**
42
+ * Build a new WebSocket server.
43
+ *
44
+ * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})
45
+ * @param options - Optional behavioural overrides ({@link WebSocketServerOptions})
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const wss = new WebSocketServer(
50
+ * { message: (ws, msg) => ws.send(msg) },
51
+ * { maxPayload: 1024 * 1024 },
52
+ * );
53
+ * ```
54
+ */
55
+ constructor(handlers, options = {}) {
56
+ this.handlers = handlers;
57
+ this.wss = new WebSocketServer$1({
58
+ noServer: true,
59
+ maxPayload: options.maxPayload,
60
+ perMessageDeflate: options.perMessageDeflate ?? false
61
+ });
62
+ }
63
+ /**
64
+ * Upgrade a raw HTTP upgrade tuple to a WebSocket connection.
65
+ *
66
+ * @remarks
67
+ * The returned promise resolves to the connected {@link ServerWebSocket}
68
+ * once the handshake completes; rejection means the handshake failed.
69
+ *
70
+ * @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event
71
+ * @param options - Optional initial `data` for the new connection
72
+ * @returns Promise that resolves with the established socket wrapper
73
+ *
74
+ * @throws {Error} When the underlying handshake throws synchronously
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * http.on("upgrade", async (req, socket, head) => {
79
+ * try {
80
+ * await wss.upgrade({ rawRequest: req, socket, head });
81
+ * } catch (err) {
82
+ * console.error("Upgrade failed", err);
83
+ * socket.destroy();
84
+ * }
85
+ * });
86
+ * ```
87
+ */
88
+ upgrade(req, options) {
89
+ return new Promise((resolve, reject) => {
90
+ try {
91
+ this.wss.handleUpgrade(req.rawRequest, req.socket, req.head, (socket) => {
92
+ const data = options?.data ?? {};
93
+ const wrapped = this.wrap(socket, data, req.rawRequest);
94
+ this.bind(socket, wrapped);
95
+ resolve(wrapped);
96
+ });
97
+ } catch (err) {
98
+ reject(err);
99
+ }
100
+ });
101
+ }
102
+ /**
103
+ * Publish a message to all sockets currently subscribed to `topic`.
104
+ *
105
+ * @param topic - Topic name
106
+ * @param message - Payload to broadcast
107
+ * @param _compress - Reserved for future use; ignored under the `ws` adapter
108
+ * @returns Number of sockets that received the message.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * wss.publish("room", JSON.stringify({ kind: "ping" }));
113
+ * ```
114
+ */
115
+ publish(topic, message, _compress) {
116
+ const set = this.topics.get(topic);
117
+ if (!set || set.size === 0) return 0;
118
+ const payload = toSendable(message);
119
+ let count = 0;
120
+ for (const socket of set) if (socket.readyState === 1) {
121
+ socket.send(payload);
122
+ count++;
123
+ }
124
+ return count;
125
+ }
126
+ /**
127
+ * Close the server and optionally terminate all live sockets.
128
+ *
129
+ * @param closeActiveConnections - When `true`, calls `socket.terminate()`
130
+ * on every live connection before shutting down.
131
+ */
132
+ close(closeActiveConnections = false) {
133
+ if (closeActiveConnections) {
134
+ for (const socket of this.allSockets) try {
135
+ socket.terminate();
136
+ } catch {}
137
+ this.allSockets.clear();
138
+ }
139
+ this.wss.close();
140
+ }
141
+ /**
142
+ * Attach the per-socket lifecycle listeners (`message`, `close`, `error`).
143
+ *
144
+ * @internal
145
+ */
146
+ bind(socket, wrapped) {
147
+ this.allSockets.add(socket);
148
+ if (this.handlers.open) Promise.resolve(this.handlers.open(wrapped)).catch((err) => console.error("[ws] open handler error:", err));
149
+ socket.on("message", (raw, isBinary) => {
150
+ const message = isBinary ? raw instanceof ArrayBuffer ? new Uint8Array(raw) : Array.isArray(raw) ? Buffer.concat(raw) : raw : raw.toString();
151
+ Promise.resolve(this.handlers.message(wrapped, message)).catch((err) => console.error("[ws] message handler error:", err));
152
+ });
153
+ socket.on("close", (code, reason) => {
154
+ const owned = this.socketTopics.get(socket);
155
+ if (owned) {
156
+ for (const topic of owned) this.topics.get(topic)?.delete(socket);
157
+ this.socketTopics.delete(socket);
158
+ }
159
+ this.allSockets.delete(socket);
160
+ if (this.handlers.close) Promise.resolve(this.handlers.close(wrapped, code, reason?.toString() ?? "")).catch((err) => console.error("[ws] close handler error:", err));
161
+ });
162
+ socket.on("error", (err) => {
163
+ if (this.handlers.error) Promise.resolve(this.handlers.error(wrapped, err)).catch((e) => console.error("[ws] error handler error:", e));
164
+ });
165
+ }
166
+ /**
167
+ * Build the {@link ServerWebSocket} wrapper exposed to user handlers.
168
+ *
169
+ * @internal
170
+ */
171
+ wrap(socket, data, rawReq) {
172
+ const remoteAddress = rawReq?.socket?.remoteAddress || rawReq?.headers?.["x-forwarded-for"] || "";
173
+ const subscribe = (topic) => {
174
+ let set = this.topics.get(topic);
175
+ if (!set) {
176
+ set = /* @__PURE__ */ new Set();
177
+ this.topics.set(topic, set);
178
+ }
179
+ set.add(socket);
180
+ let owned = this.socketTopics.get(socket);
181
+ if (!owned) {
182
+ owned = /* @__PURE__ */ new Set();
183
+ this.socketTopics.set(socket, owned);
184
+ }
185
+ owned.add(topic);
186
+ };
187
+ const unsubscribe = (topic) => {
188
+ this.topics.get(topic)?.delete(socket);
189
+ this.socketTopics.get(socket)?.delete(topic);
190
+ };
191
+ const publishToPeers = (topic, message) => {
192
+ const set = this.topics.get(topic);
193
+ if (!set) return;
194
+ const payload = toSendable(message);
195
+ for (const peer of set) if (peer !== socket && peer.readyState === 1) peer.send(payload);
196
+ };
197
+ const wrapper = {
198
+ data,
199
+ get readyState() {
200
+ return socket.readyState;
201
+ },
202
+ remoteAddress,
203
+ send: (message, _compress) => {
204
+ const payload = toSendable(message);
205
+ socket.send(payload);
206
+ return typeof payload === "string" ? Buffer.byteLength(payload) : payload.byteLength;
207
+ },
208
+ close: (code, reason) => {
209
+ socket.close(code, reason);
210
+ },
211
+ subscribe,
212
+ unsubscribe,
213
+ publish: publishToPeers,
214
+ isSubscribed: (topic) => !!this.socketTopics.get(socket)?.has(topic),
215
+ cork: (cb) => cb(wrapper)
216
+ };
217
+ return wrapper;
218
+ }
219
+ };
220
+ /**
221
+ * Coerce a {@link WSMessage} into something the `ws` package can transmit.
222
+ *
223
+ * @param message - User-supplied payload
224
+ * @returns A `string`, `Buffer` or `Uint8Array` ready to be sent
225
+ *
226
+ * @internal
227
+ */
228
+ function toSendable(message) {
229
+ if (typeof message === "string") return message;
230
+ if (message instanceof ArrayBuffer) return Buffer.from(message);
231
+ return message;
232
+ }
233
+ //#endregion
234
+ export { WebSocketServer };
235
+
236
+ //# sourceMappingURL=server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.mjs","names":["WSServer"],"sources":["../src/server.ts"],"sourcesContent":["import { type WebSocket as NodeWebSocket, WebSocketServer as WSServer } from \"ws\";\n\nimport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\nexport type {\n ServerWebSocket,\n UpgradeOptions,\n UpgradeRequest,\n WebSocketHandlers,\n WebSocketServerOptions,\n WSData,\n WSMessage,\n} from \"./types\";\n\n/**\n * Runtime-agnostic WebSocket server.\n *\n * @remarks\n * Internally backed by the [`ws`](https://github.com/websockets/ws) package\n * which runs on Bun, Node.js and Deno (via `npm:` specifiers). The class\n * does **not** create or own an HTTP server — callers feed it raw upgrade\n * tuples coming from any HTTP runtime they prefer.\n *\n * It implements topic-based pub/sub on top of the per-connection\n * `subscribe` / `unsubscribe` / `publish` API expected by Hedystia,\n * matching the shape of `Bun.ServerWebSocket`.\n *\n * @typeParam Data - Shape of the user-attached `data` field\n *\n * @example\n * ```ts\n * import { createServer } from \"node:http\";\n * import { WebSocketServer } from \"@hedystia/ws/server\";\n *\n * const wss = new WebSocketServer({\n * open: (ws) => ws.send(\"welcome\"),\n * message: (ws, msg) => ws.publish(\"room\", msg),\n * });\n *\n * const http = createServer((_req, res) => res.end(\"ok\"));\n * http.on(\"upgrade\", (req, socket, head) => {\n * wss.upgrade({ rawRequest: req, socket, head }, { data: { user: \"anon\" } });\n * });\n * http.listen(3000);\n * ```\n */\nexport class WebSocketServer<Data extends WSData = WSData> {\n private readonly handlers: WebSocketHandlers<Data>;\n private readonly wss: WSServer;\n private readonly topics = new Map<string, Set<NodeWebSocket>>();\n private readonly socketTopics = new WeakMap<NodeWebSocket, Set<string>>();\n private readonly allSockets = new Set<NodeWebSocket>();\n\n /**\n * Build a new WebSocket server.\n *\n * @param handlers - Lifecycle handlers ({@link WebSocketHandlers})\n * @param options - Optional behavioural overrides ({@link WebSocketServerOptions})\n *\n * @example\n * ```ts\n * const wss = new WebSocketServer(\n * { message: (ws, msg) => ws.send(msg) },\n * { maxPayload: 1024 * 1024 },\n * );\n * ```\n */\n constructor(handlers: WebSocketHandlers<Data>, options: WebSocketServerOptions = {}) {\n this.handlers = handlers;\n this.wss = new WSServer({\n noServer: true,\n maxPayload: options.maxPayload,\n perMessageDeflate: (options.perMessageDeflate ?? false) as any,\n });\n }\n\n /**\n * Upgrade a raw HTTP upgrade tuple to a WebSocket connection.\n *\n * @remarks\n * The returned promise resolves to the connected {@link ServerWebSocket}\n * once the handshake completes; rejection means the handshake failed.\n *\n * @param req - Upgrade tuple emitted by `node:http`'s `'upgrade'` event\n * @param options - Optional initial `data` for the new connection\n * @returns Promise that resolves with the established socket wrapper\n *\n * @throws {Error} When the underlying handshake throws synchronously\n *\n * @example\n * ```ts\n * http.on(\"upgrade\", async (req, socket, head) => {\n * try {\n * await wss.upgrade({ rawRequest: req, socket, head });\n * } catch (err) {\n * console.error(\"Upgrade failed\", err);\n * socket.destroy();\n * }\n * });\n * ```\n */\n upgrade(req: UpgradeRequest, options?: UpgradeOptions<Data>): Promise<ServerWebSocket<Data>> {\n return new Promise((resolve, reject) => {\n try {\n this.wss.handleUpgrade(req.rawRequest, req.socket, req.head as Buffer, (socket) => {\n const data = (options?.data ?? ({} as Data)) as Data;\n const wrapped = this.wrap(socket, data, req.rawRequest);\n this.bind(socket, wrapped);\n resolve(wrapped);\n });\n } catch (err) {\n reject(err);\n }\n });\n }\n\n /**\n * Publish a message to all sockets currently subscribed to `topic`.\n *\n * @param topic - Topic name\n * @param message - Payload to broadcast\n * @param _compress - Reserved for future use; ignored under the `ws` adapter\n * @returns Number of sockets that received the message.\n *\n * @example\n * ```ts\n * wss.publish(\"room\", JSON.stringify({ kind: \"ping\" }));\n * ```\n */\n publish(topic: string, message: WSMessage, _compress?: boolean): number {\n const set = this.topics.get(topic);\n if (!set || set.size === 0) {\n return 0;\n }\n const payload = toSendable(message);\n let count = 0;\n for (const socket of set) {\n if (socket.readyState === 1) {\n socket.send(payload);\n count++;\n }\n }\n return count;\n }\n\n /**\n * Close the server and optionally terminate all live sockets.\n *\n * @param closeActiveConnections - When `true`, calls `socket.terminate()`\n * on every live connection before shutting down.\n */\n close(closeActiveConnections = false): void {\n if (closeActiveConnections) {\n for (const socket of this.allSockets) {\n try {\n socket.terminate();\n } catch {\n /* ignore */\n }\n }\n this.allSockets.clear();\n }\n this.wss.close();\n }\n\n /**\n * Attach the per-socket lifecycle listeners (`message`, `close`, `error`).\n *\n * @internal\n */\n private bind(socket: NodeWebSocket, wrapped: ServerWebSocket<Data>): void {\n this.allSockets.add(socket);\n\n if (this.handlers.open) {\n Promise.resolve(this.handlers.open(wrapped)).catch((err) =>\n console.error(\"[ws] open handler error:\", err),\n );\n }\n\n socket.on(\"message\", (raw, isBinary) => {\n const message: WSMessage = isBinary\n ? raw instanceof ArrayBuffer\n ? new Uint8Array(raw)\n : Array.isArray(raw)\n ? Buffer.concat(raw)\n : (raw as Buffer)\n : raw.toString();\n Promise.resolve(this.handlers.message(wrapped, message)).catch((err) =>\n console.error(\"[ws] message handler error:\", err),\n );\n });\n\n socket.on(\"close\", (code, reason) => {\n const owned = this.socketTopics.get(socket);\n if (owned) {\n for (const topic of owned) {\n this.topics.get(topic)?.delete(socket);\n }\n this.socketTopics.delete(socket);\n }\n this.allSockets.delete(socket);\n if (this.handlers.close) {\n Promise.resolve(this.handlers.close(wrapped, code, reason?.toString() ?? \"\")).catch((err) =>\n console.error(\"[ws] close handler error:\", err),\n );\n }\n });\n\n socket.on(\"error\", (err) => {\n if (this.handlers.error) {\n Promise.resolve(this.handlers.error(wrapped, err)).catch((e) =>\n console.error(\"[ws] error handler error:\", e),\n );\n }\n });\n }\n\n /**\n * Build the {@link ServerWebSocket} wrapper exposed to user handlers.\n *\n * @internal\n */\n private wrap(socket: NodeWebSocket, data: Data, rawReq: any): ServerWebSocket<Data> {\n const remoteAddress: string =\n (rawReq?.socket?.remoteAddress as string) ||\n (rawReq?.headers?.[\"x-forwarded-for\"] as string) ||\n \"\";\n\n const subscribe = (topic: string) => {\n let set = this.topics.get(topic);\n if (!set) {\n set = new Set();\n this.topics.set(topic, set);\n }\n set.add(socket);\n let owned = this.socketTopics.get(socket);\n if (!owned) {\n owned = new Set();\n this.socketTopics.set(socket, owned);\n }\n owned.add(topic);\n };\n\n const unsubscribe = (topic: string) => {\n this.topics.get(topic)?.delete(socket);\n this.socketTopics.get(socket)?.delete(topic);\n };\n\n const publishToPeers = (topic: string, message: WSMessage) => {\n const set = this.topics.get(topic);\n if (!set) {\n return;\n }\n const payload = toSendable(message);\n for (const peer of set) {\n if (peer !== socket && peer.readyState === 1) {\n peer.send(payload);\n }\n }\n };\n\n const wrapper: ServerWebSocket<Data> = {\n data,\n get readyState() {\n return socket.readyState;\n },\n remoteAddress,\n send: (message, _compress) => {\n const payload = toSendable(message);\n socket.send(payload);\n return typeof payload === \"string\"\n ? Buffer.byteLength(payload)\n : (payload as Buffer | Uint8Array).byteLength;\n },\n close: (code, reason) => {\n socket.close(code, reason);\n },\n subscribe,\n unsubscribe,\n publish: publishToPeers,\n isSubscribed: (topic) => !!this.socketTopics.get(socket)?.has(topic),\n cork: (cb) => cb(wrapper),\n };\n\n return wrapper;\n }\n}\n\n/**\n * Coerce a {@link WSMessage} into something the `ws` package can transmit.\n *\n * @param message - User-supplied payload\n * @returns A `string`, `Buffer` or `Uint8Array` ready to be sent\n *\n * @internal\n */\nfunction toSendable(message: WSMessage): string | Buffer | Uint8Array {\n if (typeof message === \"string\") {\n return message;\n }\n if (message instanceof ArrayBuffer) {\n return Buffer.from(message);\n }\n return message;\n}\n"],"mappings":"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"}
@@ -0,0 +1,208 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Payload accepted by every `send`/`publish` method.
4
+ *
5
+ * @remarks
6
+ * Matches the WHATWG `WebSocket.send` signature plus the `Uint8Array`
7
+ * convenience accepted by Bun and the `ws` package.
8
+ */
9
+ type WSMessage = string | ArrayBuffer | Uint8Array;
10
+ /**
11
+ * Bag of arbitrary, user-supplied state attached to a connection on
12
+ * upgrade and exposed to handlers as `ws.data`.
13
+ *
14
+ * @typeParam K - String key
15
+ * @typeParam V - Stored value
16
+ */
17
+ type WSData = Record<string, any>;
18
+ /**
19
+ * The per-connection wrapper passed to every handler.
20
+ *
21
+ * @remarks
22
+ * The interface intentionally mirrors `Bun.ServerWebSocket` so that the
23
+ * same handler code works on Bun (native) and on Node.js (via
24
+ * {@link WebSocketServer}). Topic-based pub/sub is implemented in
25
+ * user-space when running outside Bun.
26
+ *
27
+ * @typeParam Data - Shape of the user-attached `data` field
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const handlers: WebSocketHandlers<{ user: string }> = {
32
+ * open: (ws) => ws.subscribe(`user:${ws.data.user}`),
33
+ * message: (ws, msg) => ws.publish(`user:${ws.data.user}`, msg),
34
+ * };
35
+ * ```
36
+ */
37
+ interface ServerWebSocket<Data extends WSData = WSData> {
38
+ /** User-supplied state attached to the socket on upgrade. */
39
+ readonly data: Data;
40
+ /** Standard WHATWG ready-state (`0` connecting, `1` open, `2` closing, `3` closed). */
41
+ readonly readyState: number;
42
+ /** Remote IP, taken from `X-Forwarded-For` when present. */
43
+ readonly remoteAddress: string;
44
+ /**
45
+ * Send a message to this socket only.
46
+ *
47
+ * @param message - Payload to send
48
+ * @param compress - Whether to compress (honoured on Bun, ignored on Node)
49
+ * @returns Number of bytes written (best-effort on Node)
50
+ */
51
+ send(message: WSMessage, compress?: boolean): number;
52
+ /**
53
+ * Close the connection.
54
+ *
55
+ * @param code - Close code (defaults to 1000)
56
+ * @param reason - Optional human-readable reason
57
+ */
58
+ close(code?: number, reason?: string): void;
59
+ /**
60
+ * Subscribe this socket to a topic so it receives subsequent
61
+ * {@link ServerWebSocket.publish | publish} or
62
+ * {@link WebSocketServer.publish | server.publish} broadcasts.
63
+ *
64
+ * @param topic - Topic name
65
+ */
66
+ subscribe(topic: string): void;
67
+ /**
68
+ * Unsubscribe this socket from a previously joined topic.
69
+ *
70
+ * @param topic - Topic name
71
+ */
72
+ unsubscribe(topic: string): void;
73
+ /**
74
+ * Broadcast a message to every other socket subscribed to `topic`.
75
+ *
76
+ * @remarks
77
+ * The sender is excluded by default — matching Bun's default behaviour.
78
+ *
79
+ * @param topic - Topic name
80
+ * @param message - Payload to broadcast
81
+ * @param compress - Whether to compress (honoured on Bun, ignored on Node)
82
+ */
83
+ publish(topic: string, message: WSMessage, compress?: boolean): void;
84
+ /**
85
+ * Check whether this socket is currently subscribed to `topic`.
86
+ *
87
+ * @param topic - Topic name
88
+ * @returns `true` when subscribed
89
+ */
90
+ isSubscribed(topic: string): boolean;
91
+ /**
92
+ * Batch multiple writes inside `cb`.
93
+ *
94
+ * @remarks
95
+ * On Bun this corresponds to `corked()`; on Node it is a no-op alias
96
+ * that simply invokes `cb(this)` synchronously.
97
+ *
98
+ * @param cb - Function invoked with the same socket
99
+ */
100
+ cork(cb: (ws: ServerWebSocket<Data>) => void): void;
101
+ }
102
+ /**
103
+ * Bun-style compression dictionary identifier.
104
+ *
105
+ * @remarks
106
+ * Used by Bun's `perMessageDeflate` configuration. The `@hedystia/ws`
107
+ * server forwards the value to the underlying implementation, which only
108
+ * Bun interprets natively; Node falls back to defaults when the value is
109
+ * not a recognised `ws` shape.
110
+ */
111
+ type Compressor = "disable" | "shared" | "dedicated" | "3KB" | "4KB" | "8KB" | "16KB" | "32KB" | "64KB" | "128KB" | "256KB";
112
+ /**
113
+ * Per-message deflate configuration.
114
+ *
115
+ * @remarks
116
+ * Accepts either a boolean (`true` enables defaults, `false` disables it)
117
+ * or a free-form object whose shape is forwarded verbatim to the underlying
118
+ * implementation. Bun's {@link Compressor} strings (`"3KB"`, `"shared"`, …)
119
+ * and the [`ws`](https://github.com/websockets/ws) package's
120
+ * `PerMessageDeflateOptions` (`zlibDeflateOptions`, `threshold`, …) are
121
+ * both supported by simply matching whatever the runtime expects.
122
+ */
123
+ type PerMessageDeflate = boolean | (Record<string, any> & {
124
+ compress?: boolean | Compressor;
125
+ decompress?: boolean | Compressor;
126
+ });
127
+ /**
128
+ * Construction options for {@link WebSocketServer}.
129
+ */
130
+ interface WebSocketServerOptions {
131
+ /** Maximum allowed payload in bytes. Defaults to the underlying library's default. */
132
+ maxPayload?: number;
133
+ /** Per-message deflate configuration. */
134
+ perMessageDeflate?: PerMessageDeflate;
135
+ }
136
+ /**
137
+ * Lifecycle handlers passed to {@link WebSocketServer}.
138
+ *
139
+ * @typeParam Data - Shape of the user-attached `data` field
140
+ */
141
+ interface WebSocketHandlers<Data extends WSData = WSData> {
142
+ /** Called for every inbound message. */
143
+ message: (ws: ServerWebSocket<Data>, message: WSMessage) => void | Promise<void>;
144
+ /** Called once the handshake completes successfully. */
145
+ open?: (ws: ServerWebSocket<Data>) => void | Promise<void>;
146
+ /** Called after the connection closes (clean or otherwise). */
147
+ close?: (ws: ServerWebSocket<Data>, code: number, reason: string) => void | Promise<void>;
148
+ /** Called when the underlying transport raises an error. */
149
+ error?: (ws: ServerWebSocket<Data>, error: Error) => void | Promise<void>;
150
+ /**
151
+ * Called when back-pressure is relieved.
152
+ *
153
+ * @remarks
154
+ * Only fired by Bun; Node-backed servers never invoke it.
155
+ */
156
+ drain?: (ws: ServerWebSocket<Data>) => void | Promise<void>;
157
+ }
158
+ /**
159
+ * Options accepted by {@link createWebSocket}.
160
+ */
161
+ interface ClientWebSocketOptions {
162
+ /** Sub-protocols negotiated during the handshake. */
163
+ protocols?: string | string[];
164
+ /**
165
+ * Custom request headers.
166
+ *
167
+ * @remarks
168
+ * Honoured on Node via the `ws` package; runtimes that ship a WHATWG
169
+ * `WebSocket` (Bun, Deno, browsers, Node ≥ 22) ignore them — matching
170
+ * standard WebSocket semantics.
171
+ */
172
+ headers?: Record<string, string>;
173
+ }
174
+ /**
175
+ * Raw upgrade tuple consumed by {@link WebSocketServer.upgrade}.
176
+ *
177
+ * @remarks
178
+ * Mirrors what `node:http`'s `'upgrade'` event emits and what the `ws`
179
+ * package's `WebSocketServer.handleUpgrade` consumes.
180
+ */
181
+ interface UpgradeRequest {
182
+ /** Raw `IncomingMessage`-like object exposing `headers`, `method`, `url`. */
183
+ rawRequest: any;
184
+ /** Raw duplex socket (e.g. `node:net.Socket`). */
185
+ socket: any;
186
+ /** Initial buffer captured by the HTTP parser. */
187
+ head: Buffer | Uint8Array;
188
+ }
189
+ /**
190
+ * Options forwarded to {@link WebSocketServer.upgrade}.
191
+ *
192
+ * @typeParam Data - Shape of the user-attached `data` field
193
+ */
194
+ interface UpgradeOptions<Data extends WSData = WSData> {
195
+ /** Initial value of `ws.data` for the new connection. */
196
+ data?: Data;
197
+ /**
198
+ * Extra response headers.
199
+ *
200
+ * @remarks
201
+ * Forwarded to the underlying handshake when supported (Bun); ignored on
202
+ * Node-backed upgrades, which control headers via the `ws` package.
203
+ */
204
+ headers?: Record<string, string> | Headers;
205
+ }
206
+ //#endregion
207
+ export { ClientWebSocketOptions, ServerWebSocket, UpgradeOptions, UpgradeRequest, WSData, WSMessage, WebSocketHandlers, WebSocketServerOptions };
208
+ //# sourceMappingURL=types.d.cts.map