@nexusts/ws 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @nexusts/ws
2
+
3
+ > **NexusTS** — Bun-native fullstack framework
4
+
5
+ ## Description
6
+
7
+ WebSockets (Bun native, Node fallback via ws).
8
+
9
+ @WebSocketGateway(path) / @OnWebSocketMessage() decorators. Rooms, broadcast. Runtime auto-detected (Bun primary; ws as optional peer dep on Node).
10
+
11
+ ## Install
12
+
13
+ This module is part of the NexusTS monorepo. Each module is published as its own npm package under the `@nexusts/` scope.
14
+
15
+ Most apps start with just the core:
16
+
17
+ ```bash
18
+ bun add @nexusts/core
19
+ ```
20
+
21
+ Then add this module only if you need it:
22
+
23
+ ```bash
24
+ bun add @nexusts/ws
25
+ ```
26
+
27
+ ## Peer dependencies
28
+
29
+ ```bash
30
+ bun add ws
31
+ ```
32
+
33
+ - **`ws`** ^8.18.0 — Required on Node.js. On Bun the WebSocket runtime is built in, so you can skip this dependency.
34
+
35
+ Without them the module loads but its public methods throw a clear error pointing to this install command on first call.
36
+
37
+ ## Usage
38
+
39
+ ```typescript
40
+ import { /* public API */ } from "@nexusts/ws";
41
+ ```
42
+
43
+ See the [user guide](../../docs/user-guide/ws.md) and the [example app](../../examples/) for a working demo.
44
+
45
+ ## License
46
+
47
+ MIT — see the root [LICENSE](../../LICENSE).
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `WebSocketClient` — the framework's per-connection wrapper.
3
+ *
4
+ * Hono's `WSContext` is a thin wrapper over the underlying
5
+ * WebSocket. `WebSocketClient` adds:
6
+ * - A unique connection id (UUID-ish).
7
+ * - Room membership tracking.
8
+ * - User-attached data (auth userId, custom keys).
9
+ * - A stable `send()` API that JSON-encodes objects.
10
+ *
11
+ * Constructed by the runtime adapter (`runtime/bun.ts` or
12
+ * `runtime/node.ts`) and passed to the gateway's lifecycle hooks.
13
+ */
14
+ import type { WsMessage, WsReadyState, WsClientData } from "./types.js";
15
+ /** Minimal interface that the wrapper needs from the underlying WSContext. */
16
+ export interface UnderlyingWs {
17
+ send(data: string | ArrayBuffer | Uint8Array, options?: {
18
+ compress?: boolean;
19
+ }): void;
20
+ close(code?: number, reason?: string): void;
21
+ readonly readyState: number;
22
+ readonly url: string | null;
23
+ readonly protocol: string | null;
24
+ }
25
+ export declare class WebSocketClientImpl {
26
+ readonly id: string;
27
+ readonly rooms: Set<string>;
28
+ data: WsClientData;
29
+ private _underlying;
30
+ constructor(underlying: UnderlyingWs, id?: string);
31
+ get url(): string | null;
32
+ get protocol(): string | null;
33
+ get readyState(): WsReadyState;
34
+ send(data: WsMessage): void;
35
+ close(code?: number, reason?: string): void;
36
+ setData(data: Partial<WsClientData>): void;
37
+ joinRoom(room: string): void;
38
+ leaveRoom(room: string): void;
39
+ /** Internal: get the underlying WSContext (for the runtime layer). */
40
+ raw(): UnderlyingWs;
41
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * WebSocket decorators.
3
+ *
4
+ * Two layers:
5
+ * 1. `@WebSocketGateway(path)` — class-level. Marks the class as a
6
+ * WebSocket gateway. The framework scans for these and registers
7
+ * a Hono `upgradeWebSocket` handler at `<path>`.
8
+ * 2. `@OnWebSocketOpen()`, `@OnWebSocketMessage()`, etc. —
9
+ * method-level. Wire lifecycle events to specific methods on
10
+ * the gateway class.
11
+ *
12
+ * Implementation note: lifecycle decorators store metadata on the
13
+ * method function itself (via a `Symbol.for` key). The class-level
14
+ * `@WebSocketGateway` walks the prototype to collect them. This
15
+ * pattern is robust regardless of the target passed by the TS
16
+ * decorator transform.
17
+ */
18
+ /**
19
+ * Mark a class as a WebSocket gateway. `path` is the URL path the
20
+ * gateway listens on (e.g. `/ws`, `/chat`).
21
+ *
22
+ * @Injectable()
23
+ * @WebSocketGateway('/ws')
24
+ * class ChatGateway { ... }
25
+ */
26
+ export declare function WebSocketGateway(path: string): ClassDecorator;
27
+ /** Read the gateway path. Internal — used by the framework. */
28
+ export declare function getGatewayPath(target: object): string | undefined;
29
+ /** Method decorator factory: bind to `onOpen` lifecycle. */
30
+ export declare function OnWebSocketOpen(): MethodDecorator;
31
+ /** Method decorator factory: bind to `onMessage` lifecycle. */
32
+ export declare function OnWebSocketMessage(): MethodDecorator;
33
+ /** Method decorator factory: bind to `onClose` lifecycle. */
34
+ export declare function OnWebSocketClose(): MethodDecorator;
35
+ /** Method decorator factory: bind to `onError` lifecycle. */
36
+ export declare function OnWebSocketError(): MethodDecorator;
37
+ /** Read the bound lifecycle methods. Internal — used by the framework. */
38
+ export declare function getLifecycleHandlers(target: object): {
39
+ open?: string;
40
+ message?: string;
41
+ close?: string;
42
+ error?: string;
43
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * `nexusjs/ws` — Hono WebSocket integration.
3
+ *
4
+ * Provides a single API for WebSocket gateways that works on both
5
+ * Bun (primary, via `hono/adapter/bun/websocket`) and Node (via
6
+ * the `ws` package).
7
+ *
8
+ * Quick start:
9
+ *
10
+ * @Injectable()
11
+ * @WebSocketGateway('/ws')
12
+ * class ChatGateway {
13
+ * constructor(@Inject(WEBSOCKET_SERVICE_TOKEN) private ws: WebSocketService) {}
14
+ *
15
+ * @OnWebSocketOpen()
16
+ * onOpen(client: WebSocketClient) {
17
+ * this.ws.joinRoom(client, 'lobby');
18
+ * }
19
+ *
20
+ * @OnWebSocketMessage()
21
+ * onMessage(client: WebSocketClient, data: any) {
22
+ * this.ws.broadcastToRoom('lobby', { user: client.id, text: data.text });
23
+ * }
24
+ *
25
+ * @OnWebSocketClose()
26
+ * onClose(client: WebSocketClient) {
27
+ * this.ws.leaveAllRooms(client);
28
+ * }
29
+ * }
30
+ *
31
+ * @Module({
32
+ * imports: [WebSocketModule.forRoot({ gateways: [ChatGateway] })],
33
+ * })
34
+ * class AppModule {}
35
+ *
36
+ * Runtime wiring:
37
+ *
38
+ * // Bun
39
+ * const { websocket } = await bunWsAdapter.install(app, [ChatGateway]);
40
+ * Bun.serve({ port: 3000, fetch: app.fetch, websocket });
41
+ *
42
+ * // Node
43
+ * const { handleUpgrade } = await nodeWsAdapter.bind([ChatGateway]);
44
+ * const wss = new WebSocketServer({ noServer: true });
45
+ * server.on('upgrade', (req, socket, head) => handleUpgrade(req, socket, head));
46
+ */
47
+ export { WebSocketService, WEBSOCKET_SERVICE_TOKEN } from "./service.js";
48
+ export { WebSocketClientImpl } from "./client.js";
49
+ export { WebSocketModule } from "./module.js";
50
+ export { BunWsAdapter } from "./runtime/bun.js";
51
+ export { NodeWsAdapter } from "./runtime/node.js";
52
+ export type { NodeWsServer } from "./runtime/node.js";
53
+ export { detectRuntime, type WsRuntime } from "./runtime/index.js";
54
+ export { WebSocketGateway, OnWebSocketOpen, OnWebSocketMessage, OnWebSocketClose, OnWebSocketError, getGatewayPath, getLifecycleHandlers, } from "./decorators.js";
55
+ export type { WebSocketClient, WebSocketConfig, WsClientData, WsLifecycle, WsLifecycleHandlers, WsMessage, WsReadyState, WsDecodeOptions, } from "./types.js";
56
+ export type { WebSocketGatewayOptions, GatewayClass } from "./runtime/types.js";
package/dist/index.js ADDED
@@ -0,0 +1,560 @@
1
+ // @bun
2
+ var __legacyDecorateClassTS = function(decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
5
+ r = Reflect.decorate(decorators, target, key, desc);
6
+ else
7
+ for (var i = decorators.length - 1;i >= 0; i--)
8
+ if (d = decorators[i])
9
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
10
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
11
+ };
12
+ var __legacyDecorateParamTS = (index, decorator) => (target, key) => decorator(target, key, index);
13
+ var __legacyMetadataTS = (k, v) => {
14
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
15
+ return Reflect.metadata(k, v);
16
+ };
17
+ var __require = import.meta.require;
18
+
19
+ // packages/ws/src/service.ts
20
+ var WEBSOCKET_SERVICE_TOKEN = Symbol.for("nexus:WebSocketService");
21
+
22
+ class WebSocketService {
23
+ clients = new Map;
24
+ rooms = new Map;
25
+ onConnectListeners = [];
26
+ onDisconnectListeners = [];
27
+ register(client) {
28
+ this.clients.set(client.id, client);
29
+ for (const cb of this.onConnectListeners) {
30
+ try {
31
+ cb(client);
32
+ } catch {}
33
+ }
34
+ return client.id;
35
+ }
36
+ unregister(client) {
37
+ this.clients.delete(client.id);
38
+ for (const room of client.rooms) {
39
+ const set = this.rooms.get(room);
40
+ if (set) {
41
+ set.delete(client.id);
42
+ if (set.size === 0)
43
+ this.rooms.delete(room);
44
+ }
45
+ }
46
+ for (const cb of this.onDisconnectListeners) {
47
+ try {
48
+ cb(client);
49
+ } catch {}
50
+ }
51
+ }
52
+ get size() {
53
+ return this.clients.size;
54
+ }
55
+ getConnections() {
56
+ return [...this.clients.values()];
57
+ }
58
+ getConnection(id) {
59
+ return this.clients.get(id);
60
+ }
61
+ joinRoom(client, room) {
62
+ const impl = client;
63
+ impl.joinRoom(room);
64
+ let set = this.rooms.get(room);
65
+ if (!set) {
66
+ set = new Set;
67
+ this.rooms.set(room, set);
68
+ }
69
+ set.add(client.id);
70
+ }
71
+ leaveRoom(client, room) {
72
+ const impl = client;
73
+ impl.leaveRoom(room);
74
+ const set = this.rooms.get(room);
75
+ if (set) {
76
+ set.delete(client.id);
77
+ if (set.size === 0)
78
+ this.rooms.delete(room);
79
+ }
80
+ }
81
+ leaveAllRooms(client) {
82
+ const impl = client;
83
+ for (const room of impl.rooms) {
84
+ const set = this.rooms.get(room);
85
+ if (set) {
86
+ set.delete(client.id);
87
+ if (set.size === 0)
88
+ this.rooms.delete(room);
89
+ }
90
+ }
91
+ impl.rooms.clear();
92
+ }
93
+ getRoomMembers(room) {
94
+ const ids = this.rooms.get(room);
95
+ if (!ids)
96
+ return [];
97
+ const out = [];
98
+ for (const id of ids) {
99
+ const c = this.clients.get(id);
100
+ if (c)
101
+ out.push(c);
102
+ }
103
+ return out;
104
+ }
105
+ getRooms() {
106
+ return [...this.rooms.keys()];
107
+ }
108
+ hasRoom(room) {
109
+ const set = this.rooms.get(room);
110
+ return !!set && set.size > 0;
111
+ }
112
+ broadcast(data, filter) {
113
+ for (const client of this.clients.values()) {
114
+ if (filter && !filter(client))
115
+ continue;
116
+ try {
117
+ client.send(data);
118
+ } catch {}
119
+ }
120
+ }
121
+ broadcastToRoom(room, data) {
122
+ const ids = this.rooms.get(room);
123
+ if (!ids)
124
+ return;
125
+ for (const id of ids) {
126
+ const client = this.clients.get(id);
127
+ if (!client)
128
+ continue;
129
+ try {
130
+ client.send(data);
131
+ } catch {}
132
+ }
133
+ }
134
+ sendTo(id, data) {
135
+ const client = this.clients.get(id);
136
+ if (!client)
137
+ return false;
138
+ try {
139
+ client.send(data);
140
+ return true;
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+ closeAll(code = 1001, reason = "Server shutting down") {
146
+ for (const client of this.clients.values()) {
147
+ try {
148
+ client.close(code, reason);
149
+ } catch {}
150
+ }
151
+ }
152
+ onConnect(cb) {
153
+ this.onConnectListeners.push(cb);
154
+ }
155
+ onDisconnect(cb) {
156
+ this.onDisconnectListeners.push(cb);
157
+ }
158
+ }
159
+ // packages/ws/src/client.ts
160
+ import { randomUUID } from "crypto";
161
+
162
+ class WebSocketClientImpl {
163
+ id;
164
+ rooms = new Set;
165
+ data = {};
166
+ _underlying;
167
+ constructor(underlying, id) {
168
+ this.id = id ?? randomUUID();
169
+ this._underlying = underlying;
170
+ }
171
+ get url() {
172
+ return this._underlying.url;
173
+ }
174
+ get protocol() {
175
+ return this._underlying.protocol;
176
+ }
177
+ get readyState() {
178
+ return this._underlying.readyState;
179
+ }
180
+ send(data) {
181
+ if (typeof data === "object" && !(data instanceof ArrayBuffer) && !(data instanceof Uint8Array)) {
182
+ this._underlying.send(JSON.stringify(data));
183
+ } else {
184
+ this._underlying.send(data);
185
+ }
186
+ }
187
+ close(code, reason) {
188
+ this._underlying.close(code, reason);
189
+ }
190
+ setData(data) {
191
+ Object.assign(this.data, data);
192
+ }
193
+ joinRoom(room) {
194
+ this.rooms.add(room);
195
+ }
196
+ leaveRoom(room) {
197
+ this.rooms.delete(room);
198
+ }
199
+ raw() {
200
+ return this._underlying;
201
+ }
202
+ }
203
+ // packages/ws/src/module.ts
204
+ import { Inject, Module } from "@nexusts/core";
205
+
206
+ // packages/ws/src/runtime/index.ts
207
+ function detectRuntime() {
208
+ if (typeof globalThis.Bun !== "undefined")
209
+ return "bun";
210
+ if (typeof process !== "undefined" && process.versions?.node)
211
+ return "node";
212
+ if (typeof globalThis.caches !== "undefined" && typeof globalThis.WebSocketPair !== "undefined") {
213
+ return "cloudflare";
214
+ }
215
+ return "unknown";
216
+ }
217
+
218
+ // packages/ws/src/module.ts
219
+ class WebSocketModule {
220
+ static forRoot(config = {}) {
221
+ const fullConfig = {
222
+ path: config.path ?? "/ws",
223
+ json: config.json ?? true,
224
+ heartbeatSeconds: config.heartbeatSeconds ?? 30,
225
+ onUnknownRuntime: config.onUnknownRuntime ?? "throw",
226
+ gateways: config.gateways ?? []
227
+ };
228
+
229
+ class ConfiguredWebSocketModule {
230
+ service;
231
+ constructor(service) {
232
+ this.service = service;
233
+ }
234
+ detectRuntime() {
235
+ return detectRuntime();
236
+ }
237
+ }
238
+ ConfiguredWebSocketModule = __legacyDecorateClassTS([
239
+ Module({
240
+ providers: [
241
+ WebSocketService,
242
+ { provide: WEBSOCKET_SERVICE_TOKEN, useExisting: WebSocketService },
243
+ { provide: "WS_CONFIG", useValue: fullConfig }
244
+ ],
245
+ exports: [WebSocketService, WEBSOCKET_SERVICE_TOKEN, "WS_CONFIG"]
246
+ }),
247
+ __legacyDecorateParamTS(0, Inject(WEBSOCKET_SERVICE_TOKEN)),
248
+ __legacyMetadataTS("design:paramtypes", [
249
+ typeof WebSocketService === "undefined" ? Object : WebSocketService
250
+ ])
251
+ ], ConfiguredWebSocketModule);
252
+ Object.defineProperty(ConfiguredWebSocketModule, "name", {
253
+ value: "ConfiguredWebSocketModule"
254
+ });
255
+ return ConfiguredWebSocketModule;
256
+ }
257
+ }
258
+ WebSocketModule = __legacyDecorateClassTS([
259
+ Module({
260
+ providers: [
261
+ WebSocketService,
262
+ { provide: WEBSOCKET_SERVICE_TOKEN, useExisting: WebSocketService }
263
+ ],
264
+ exports: [WebSocketService, WEBSOCKET_SERVICE_TOKEN]
265
+ })
266
+ ], WebSocketModule);
267
+ // packages/ws/src/decorators.ts
268
+ var GATEWAY_KEY = Symbol.for("nexus:ws:gateway");
269
+ var LIFECYCLE_KEY = Symbol.for("nexus:ws:lifecycle");
270
+ function WebSocketGateway(path) {
271
+ return function(target) {
272
+ const ctor = target;
273
+ const proto = ctor.prototype ?? target;
274
+ const handlers = {};
275
+ for (const name of Object.getOwnPropertyNames(proto)) {
276
+ if (name === "constructor")
277
+ continue;
278
+ const m = proto[name];
279
+ if (typeof m === "function" && m[LIFECYCLE_KEY]) {
280
+ Object.assign(handlers, m[LIFECYCLE_KEY]);
281
+ }
282
+ }
283
+ proto[GATEWAY_KEY] = { path, handlers };
284
+ };
285
+ }
286
+ function getGatewayPath(target) {
287
+ const t = target;
288
+ const meta = t[GATEWAY_KEY];
289
+ return meta?.path;
290
+ }
291
+ function makeLifecycleDecorator(lifecycle) {
292
+ return function(_target, propertyKey, descriptor) {
293
+ const fn = descriptor.value;
294
+ if (typeof fn !== "function")
295
+ return descriptor;
296
+ fn[LIFECYCLE_KEY] = fn[LIFECYCLE_KEY] ?? {};
297
+ fn[LIFECYCLE_KEY][lifecycle] = String(propertyKey);
298
+ return descriptor;
299
+ };
300
+ }
301
+ function OnWebSocketOpen() {
302
+ return makeLifecycleDecorator("open");
303
+ }
304
+ function OnWebSocketMessage() {
305
+ return makeLifecycleDecorator("message");
306
+ }
307
+ function OnWebSocketClose() {
308
+ return makeLifecycleDecorator("close");
309
+ }
310
+ function OnWebSocketError() {
311
+ return makeLifecycleDecorator("error");
312
+ }
313
+ function getLifecycleHandlers(target) {
314
+ const t = target;
315
+ if (t[GATEWAY_KEY]?.handlers)
316
+ return t[GATEWAY_KEY].handlers;
317
+ const out = {};
318
+ const proto = t.prototype ?? t;
319
+ for (const name of Object.getOwnPropertyNames(proto)) {
320
+ if (name === "constructor")
321
+ continue;
322
+ const m = proto[name];
323
+ if (typeof m === "function" && m[LIFECYCLE_KEY]) {
324
+ Object.assign(out, m[LIFECYCLE_KEY]);
325
+ }
326
+ }
327
+ return out;
328
+ }
329
+
330
+ // packages/ws/src/runtime/bun.ts
331
+ class BunWsAdapter {
332
+ service;
333
+ options;
334
+ constructor(service, options = {}) {
335
+ this.service = service;
336
+ this.options = options;
337
+ }
338
+ async install(app, gateways) {
339
+ const { createBunWebSocket } = await import("hono/bun");
340
+ const { upgradeWebSocket, websocket } = createBunWebSocket();
341
+ for (const Gateway of gateways) {
342
+ const path = getGatewayPath(Gateway.prototype);
343
+ if (!path) {
344
+ throw new Error(`WebSocket gateway ${Gateway.name} is missing @WebSocketGateway(path)`);
345
+ }
346
+ const handlers = getLifecycleHandlers(Gateway.prototype);
347
+ const opts = this.options;
348
+ app.get(path, upgradeWebSocket((_c) => {
349
+ const gateway = new Gateway;
350
+ if (gateway && "setService" in gateway && typeof gateway.setService === "function") {
351
+ gateway.setService(this.service);
352
+ }
353
+ return {
354
+ onOpen: (_evt, ws) => {
355
+ const client = new WebSocketClientImpl(ws);
356
+ this.service.register(client);
357
+ (opts.onOpen ?? this.defaultOnOpen)(client);
358
+ if (handlers.open) {
359
+ Promise.resolve(gateway[handlers.open](client)).catch((err) => opts.onError?.(client, err));
360
+ }
361
+ },
362
+ onMessage: (evt, ws) => {
363
+ const id = ws.__clientId;
364
+ const client = id ? this.service.getConnection(id) : undefined;
365
+ if (!client)
366
+ return;
367
+ const raw = evt.data;
368
+ const data = this.parseMessage(raw);
369
+ if (handlers.message) {
370
+ Promise.resolve(gateway[handlers.message](client, data)).catch((err) => opts.onError?.(client, err));
371
+ }
372
+ },
373
+ onClose: (evt, ws) => {
374
+ const id = ws.__clientId;
375
+ const client = id ? this.service.getConnection(id) : undefined;
376
+ if (!client)
377
+ return;
378
+ this.service.unregister(client);
379
+ (opts.onClose ?? this.defaultOnClose)(client, evt.code, evt.reason);
380
+ if (handlers.close) {
381
+ Promise.resolve(gateway[handlers.close](client, evt.code, evt.reason)).catch((err) => opts.onError?.(client, err));
382
+ }
383
+ },
384
+ onError: (evt, ws) => {
385
+ const id = ws.__clientId;
386
+ const client = id ? this.service.getConnection(id) : undefined;
387
+ if (!client)
388
+ return;
389
+ const err = evt.error ?? new Error("WebSocket error");
390
+ if (handlers.error) {
391
+ Promise.resolve(gateway[handlers.error](client, err)).catch(() => {});
392
+ } else {
393
+ opts.onError?.(client, err);
394
+ }
395
+ }
396
+ };
397
+ }));
398
+ }
399
+ return { websocket };
400
+ }
401
+ parseMessage(raw) {
402
+ if (this.options.json === false)
403
+ return raw;
404
+ if (typeof raw === "string") {
405
+ try {
406
+ return JSON.parse(raw);
407
+ } catch {
408
+ return raw;
409
+ }
410
+ }
411
+ return raw;
412
+ }
413
+ defaultOnOpen(_client) {}
414
+ defaultOnClose(_client, _code, _reason) {}
415
+ }
416
+ // packages/ws/src/runtime/node.ts
417
+ class NodeWsAdapter {
418
+ service;
419
+ options;
420
+ constructor(service, options = {}) {
421
+ this.service = service;
422
+ this.options = options;
423
+ }
424
+ async bind(gateways) {
425
+ let WS;
426
+ try {
427
+ WS = (await import("ws")).default ? await import("ws") : await import("ws");
428
+ const WebSocketServer = WS.WebSocketServer ?? WS.Server ?? WS.default?.WebSocketServer;
429
+ if (!WebSocketServer)
430
+ throw new Error("no WebSocketServer export");
431
+ } catch (err) {
432
+ throw new Error("WebSocketModule.forRoot() on Node requires the `ws` package. Install with: bun add ws");
433
+ }
434
+ const handlers = new Map;
435
+ const gatewayInstances = new Map;
436
+ for (const Gateway of gateways) {
437
+ const path = getGatewayPath(Gateway.prototype);
438
+ if (!path)
439
+ continue;
440
+ gatewayInstances.set(path, Gateway);
441
+ handlers.set(path, (req) => {
442
+ const url = new URL(req.url ?? "/", "http://localhost");
443
+ return url.pathname === path;
444
+ });
445
+ }
446
+ const wss = new WS.WebSocketServer({ noServer: true });
447
+ const opts = this.options;
448
+ const service = this.service;
449
+ wss.on("connection", (ws, req) => {
450
+ const url = new URL(req.url ?? "/", "http://localhost");
451
+ const path = url.pathname;
452
+ const Gateway = gatewayInstances.get(path);
453
+ if (!Gateway) {
454
+ ws.close(1008, "Unknown path");
455
+ return;
456
+ }
457
+ const gateway = new Gateway;
458
+ if (gateway && "setService" in gateway && typeof gateway.setService === "function") {
459
+ gateway.setService(service);
460
+ }
461
+ const client = new WebSocketClientImpl(adaptUnderlying(ws, req));
462
+ service.register(client);
463
+ (opts.onOpen ?? (() => {}))(client);
464
+ const handlers2 = getLifecycleHandlers(Gateway.prototype);
465
+ ws.on("message", (data) => {
466
+ const parsed = this.parseMessage(data);
467
+ if (handlers2.message) {
468
+ Promise.resolve(gateway[handlers2.message](client, parsed)).catch((err) => opts.onError?.(client, err));
469
+ }
470
+ });
471
+ ws.on("close", (code, reasonBuf) => {
472
+ const reason = reasonBuf?.toString?.() ?? "";
473
+ service.unregister(client);
474
+ (opts.onClose ?? (() => {}))(client, code, reason);
475
+ if (handlers2.close) {
476
+ Promise.resolve(gateway[handlers2.close](client, code, reason)).catch((err) => opts.onError?.(client, err));
477
+ }
478
+ });
479
+ ws.on("error", (err) => {
480
+ if (handlers2.error) {
481
+ Promise.resolve(gateway[handlers2.error](client, err)).catch(() => {});
482
+ } else {
483
+ opts.onError?.(client, err);
484
+ }
485
+ });
486
+ });
487
+ return {
488
+ handleUpgrade(req, socket, head, callback) {
489
+ const url = new URL(req.url ?? "/", "http://localhost");
490
+ const handler = handlers.get(url.pathname);
491
+ if (!handler || !handler(req)) {
492
+ socket.write(`HTTP/1.1 404 Not Found\r
493
+ \r
494
+ `);
495
+ socket.destroy();
496
+ return;
497
+ }
498
+ wss.handleUpgrade(req, socket, head, callback ?? (() => {}));
499
+ }
500
+ };
501
+ }
502
+ parseMessage(raw) {
503
+ if (this.options.json === false)
504
+ return raw;
505
+ if (typeof raw === "string") {
506
+ try {
507
+ return JSON.parse(raw);
508
+ } catch {
509
+ return raw;
510
+ }
511
+ }
512
+ if (raw && typeof raw === "object" && "toString" in raw) {
513
+ const s = raw.toString();
514
+ try {
515
+ return JSON.parse(s);
516
+ } catch {
517
+ return s;
518
+ }
519
+ }
520
+ return raw;
521
+ }
522
+ }
523
+ function adaptUnderlying(ws, req) {
524
+ return {
525
+ send(data, _options) {
526
+ ws.send(data);
527
+ },
528
+ close(code, reason) {
529
+ ws.close(code, reason);
530
+ },
531
+ get readyState() {
532
+ return ws.readyState;
533
+ },
534
+ get url() {
535
+ return req.url ?? null;
536
+ },
537
+ get protocol() {
538
+ return ws.protocol ?? null;
539
+ }
540
+ };
541
+ }
542
+ export {
543
+ getLifecycleHandlers,
544
+ getGatewayPath,
545
+ detectRuntime,
546
+ WebSocketService,
547
+ WebSocketModule,
548
+ WebSocketGateway,
549
+ WebSocketClientImpl,
550
+ WEBSOCKET_SERVICE_TOKEN,
551
+ OnWebSocketOpen,
552
+ OnWebSocketMessage,
553
+ OnWebSocketError,
554
+ OnWebSocketClose,
555
+ NodeWsAdapter,
556
+ BunWsAdapter
557
+ };
558
+
559
+ //# debugId=A4A5EDFDAED3B04B64756E2164756E21
560
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/service.ts", "../src/client.ts", "../src/module.ts", "../src/runtime/index.ts", "../src/decorators.ts", "../src/runtime/bun.ts", "../src/runtime/node.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * `WebSocketService` — connection registry, rooms, broadcasting.\n *\n * The service is the central state for all open WebSocket\n * connections. It is registered in the DI container as a singleton\n * and consumed by `@WebSocketGateway` classes.\n *\n * Features:\n * - **Connection registry** — `getConnections()` returns all\n * currently-open clients.\n * - **Per-connection lookup** — `getConnection(id)`.\n * - **Rooms** — `joinRoom`, `leaveRoom`, `broadcastToRoom`. Rooms\n * are auto-cleaned when empty.\n * - **Broadcast** — `broadcast(data)` sends to all open clients.\n * - **Targeted send** — `sendTo(id, data)`.\n * - **Lifecycle tracking** — `onConnect` / `onDisconnect` callbacks\n * for metrics, audit logging, etc.\n */\n\nimport type { WebSocketClient, WsMessage, WsClientData } from \"./types.js\";\nimport { WebSocketClientImpl } from \"./client.js\";\n\nexport const WEBSOCKET_SERVICE_TOKEN = Symbol.for(\"nexus:WebSocketService\");\n\nexport class WebSocketService {\n\t/** id -> client */\n\tprivate clients = new Map<string, WebSocketClientImpl>();\n\t/** room -> Set<id> */\n\tprivate rooms = new Map<string, Set<string>>();\n\t/** Lifecycle callbacks. */\n\tprivate onConnectListeners: Array<(c: WebSocketClient) => void> = [];\n\tprivate onDisconnectListeners: Array<(c: WebSocketClient) => void> = [];\n\n\t/* ---------------- registry ---------------- */\n\n\t/** Register a new client. Returns the framework id. */\n\tregister(client: WebSocketClientImpl): string {\n\t\tthis.clients.set(client.id, client);\n\t\tfor (const cb of this.onConnectListeners) {\n\t\t\ttry {\n\t\t\t\tcb(client);\n\t\t\t} catch {\n\t\t\t\t/* ignore listener errors */\n\t\t\t}\n\t\t}\n\t\treturn client.id;\n\t}\n\n\t/** Remove a client from the registry. Auto-cleans empty rooms. */\n\tunregister(client: WebSocketClientImpl): void {\n\t\tthis.clients.delete(client.id);\n\t\tfor (const room of client.rooms) {\n\t\t\tconst set = this.rooms.get(room);\n\t\t\tif (set) {\n\t\t\t\tset.delete(client.id);\n\t\t\t\tif (set.size === 0) this.rooms.delete(room);\n\t\t\t}\n\t\t}\n\t\tfor (const cb of this.onDisconnectListeners) {\n\t\t\ttry {\n\t\t\t\tcb(client);\n\t\t\t} catch {\n\t\t\t\t/* ignore listener errors */\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Number of currently-open connections. */\n\tget size(): number {\n\t\treturn this.clients.size;\n\t}\n\n\t/** Get all open connections. */\n\tgetConnections(): WebSocketClient[] {\n\t\treturn [...this.clients.values()];\n\t}\n\n\t/** Get a connection by id. */\n\tgetConnection(id: string): WebSocketClient | undefined {\n\t\treturn this.clients.get(id);\n\t}\n\n\t/* ---------------- rooms ---------------- */\n\n\t/** Join a room. */\n\tjoinRoom(client: WebSocketClient, room: string): void {\n\t\tconst impl = client as WebSocketClientImpl;\n\t\timpl.joinRoom(room);\n\t\tlet set = this.rooms.get(room);\n\t\tif (!set) {\n\t\t\tset = new Set();\n\t\t\tthis.rooms.set(room, set);\n\t\t}\n\t\tset.add(client.id);\n\t}\n\n\t/** Leave a room. */\n\tleaveRoom(client: WebSocketClient, room: string): void {\n\t\tconst impl = client as WebSocketClientImpl;\n\t\timpl.leaveRoom(room);\n\t\tconst set = this.rooms.get(room);\n\t\tif (set) {\n\t\t\tset.delete(client.id);\n\t\t\tif (set.size === 0) this.rooms.delete(room);\n\t\t}\n\t}\n\n\t/** Leave all rooms. */\n\tleaveAllRooms(client: WebSocketClient): void {\n\t\tconst impl = client as WebSocketClientImpl;\n\t\tfor (const room of impl.rooms) {\n\t\t\tconst set = this.rooms.get(room);\n\t\t\tif (set) {\n\t\t\t\tset.delete(client.id);\n\t\t\t\tif (set.size === 0) this.rooms.delete(room);\n\t\t\t}\n\t\t}\n\t\timpl.rooms.clear();\n\t}\n\n\t/** All client ids in a room. */\n\tgetRoomMembers(room: string): WebSocketClient[] {\n\t\tconst ids = this.rooms.get(room);\n\t\tif (!ids) return [];\n\t\tconst out: WebSocketClient[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst c = this.clients.get(id);\n\t\t\tif (c) out.push(c);\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** All room names currently in use. */\n\tgetRooms(): string[] {\n\t\treturn [...this.rooms.keys()];\n\t}\n\n\t/** Whether a room has any members. */\n\thasRoom(room: string): boolean {\n\t\tconst set = this.rooms.get(room);\n\t\treturn !!set && set.size > 0;\n\t}\n\n\t/* ---------------- broadcast ---------------- */\n\n\t/** Send to all open clients. */\n\tbroadcast(data: WsMessage, filter?: (client: WebSocketClient) => boolean): void {\n\t\tfor (const client of this.clients.values()) {\n\t\t\tif (filter && !filter(client)) continue;\n\t\t\ttry {\n\t\t\t\tclient.send(data);\n\t\t\t} catch {\n\t\t\t\t/* ignore send errors */\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Send to all clients in a room. */\n\tbroadcastToRoom(room: string, data: WsMessage): void {\n\t\tconst ids = this.rooms.get(room);\n\t\tif (!ids) return;\n\t\tfor (const id of ids) {\n\t\t\tconst client = this.clients.get(id);\n\t\t\tif (!client) continue;\n\t\t\ttry {\n\t\t\t\tclient.send(data);\n\t\t\t} catch {\n\t\t\t\t/* ignore send errors */\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Send to a single client by id. */\n\tsendTo(id: string, data: WsMessage): boolean {\n\t\tconst client = this.clients.get(id);\n\t\tif (!client) return false;\n\t\ttry {\n\t\t\tclient.send(data);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/** Close all connections (graceful shutdown). */\n\tcloseAll(code = 1001, reason = \"Server shutting down\"): void {\n\t\tfor (const client of this.clients.values()) {\n\t\t\ttry {\n\t\t\t\tclient.close(code, reason);\n\t\t\t} catch {\n\t\t\t\t/* ignore */\n\t\t\t}\n\t\t}\n\t}\n\n\t/* ---------------- lifecycle listeners ---------------- */\n\n\t/** Register a callback for new connections. */\n\tonConnect(cb: (client: WebSocketClient) => void): void {\n\t\tthis.onConnectListeners.push(cb);\n\t}\n\n\t/** Register a callback for closed connections. */\n\tonDisconnect(cb: (client: WebSocketClient) => void): void {\n\t\tthis.onDisconnectListeners.push(cb);\n\t}\n}\n",
6
+ "/**\n * `WebSocketClient` — the framework's per-connection wrapper.\n *\n * Hono's `WSContext` is a thin wrapper over the underlying\n * WebSocket. `WebSocketClient` adds:\n * - A unique connection id (UUID-ish).\n * - Room membership tracking.\n * - User-attached data (auth userId, custom keys).\n * - A stable `send()` API that JSON-encodes objects.\n *\n * Constructed by the runtime adapter (`runtime/bun.ts` or\n * `runtime/node.ts`) and passed to the gateway's lifecycle hooks.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { WsMessage, WsReadyState, WsClientData } from \"./types.js\";\n\n/** Minimal interface that the wrapper needs from the underlying WSContext. */\nexport interface UnderlyingWs {\n\tsend(data: string | ArrayBuffer | Uint8Array, options?: { compress?: boolean }): void;\n\tclose(code?: number, reason?: string): void;\n\treadonly readyState: number;\n\treadonly url: string | null;\n\treadonly protocol: string | null;\n}\n\nexport class WebSocketClientImpl {\n\treadonly id: string;\n\treadonly rooms: Set<string> = new Set();\n\tdata: WsClientData = {};\n\tprivate _underlying: UnderlyingWs;\n\n\tconstructor(underlying: UnderlyingWs, id?: string) {\n\t\tthis.id = id ?? randomUUID();\n\t\tthis._underlying = underlying;\n\t}\n\n\tget url(): string | null {\n\t\treturn this._underlying.url;\n\t}\n\n\tget protocol(): string | null {\n\t\treturn this._underlying.protocol;\n\t}\n\n\tget readyState(): WsReadyState {\n\t\treturn this._underlying.readyState as WsReadyState;\n\t}\n\n\tsend(data: WsMessage): void {\n\t\tif (typeof data === \"object\" && !(data instanceof ArrayBuffer) && !(data instanceof Uint8Array)) {\n\t\t\tthis._underlying.send(JSON.stringify(data));\n\t\t} else {\n\t\t\tthis._underlying.send(data as string | ArrayBuffer | Uint8Array);\n\t\t}\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tthis._underlying.close(code, reason);\n\t}\n\n\tsetData(data: Partial<WsClientData>): void {\n\t\tObject.assign(this.data, data);\n\t}\n\n\tjoinRoom(room: string): void {\n\t\tthis.rooms.add(room);\n\t}\n\n\tleaveRoom(room: string): void {\n\t\tthis.rooms.delete(room);\n\t}\n\n\t/** Internal: get the underlying WSContext (for the runtime layer). */\n\traw(): UnderlyingWs {\n\t\treturn this._underlying;\n\t}\n}\n",
7
+ "/**\n * `WebSocketModule` — wires `WebSocketService` into the DI container\n * and provides runtime-specific helpers for the application.\n *\n * Usage (Bun):\n *\n * @Module({\n * imports: [\n * WebSocketModule.forRoot({\n * gateways: [ChatGateway],\n * path: '/ws',\n * }),\n * ],\n * })\n * class AppModule {}\n *\n * const app = new Application(AppModule);\n * const { websocket } = await app.wireWebSocket();\n * Bun.serve({\n * port: 3000,\n * fetch: app.fetch,\n * websocket,\n * });\n *\n * Usage (Node):\n *\n * const app = new Application(AppModule);\n * const { server, wss } = await app.serveNodeWebSocket(3000);\n */\n\nimport { Inject, Module } from \"@nexusts/core\";\nimport { WebSocketService, WEBSOCKET_SERVICE_TOKEN } from \"./service.js\";\nimport type { WebSocketConfig } from \"./types.js\";\nimport { detectRuntime } from \"./runtime/index.js\";\nimport type { GatewayClass } from \"./runtime/types.js\";\n\n@Module({\n\tproviders: [\n\t\tWebSocketService,\n\t\t{ provide: WEBSOCKET_SERVICE_TOKEN, useExisting: WebSocketService },\n\t],\n\texports: [WebSocketService, WEBSOCKET_SERVICE_TOKEN],\n})\nexport class WebSocketModule {\n\tstatic forRoot(config: WebSocketConfig & { gateways?: GatewayClass[] } = {}) {\n\t\tconst fullConfig: Required<WebSocketConfig> & { gateways: GatewayClass[] } = {\n\t\t\tpath: config.path ?? \"/ws\",\n\t\t\tjson: config.json ?? true,\n\t\t\theartbeatSeconds: config.heartbeatSeconds ?? 30,\n\t\t\tonUnknownRuntime: config.onUnknownRuntime ?? \"throw\",\n\t\t\tgateways: config.gateways ?? [],\n\t\t};\n\n\t\t@Module({\n\t\t\tproviders: [\n\t\t\t\tWebSocketService,\n\t\t\t\t{ provide: WEBSOCKET_SERVICE_TOKEN, useExisting: WebSocketService },\n\t\t\t\t{ provide: \"WS_CONFIG\", useValue: fullConfig },\n\t\t\t],\n\t\t\texports: [WebSocketService, WEBSOCKET_SERVICE_TOKEN, \"WS_CONFIG\"],\n\t\t})\n\t\tclass ConfiguredWebSocketModule {\n\t\t\tconstructor(@Inject(WEBSOCKET_SERVICE_TOKEN) readonly service: WebSocketService) {}\n\n\t\t\t/** Detect the runtime at boot time. */\n\t\t\tdetectRuntime() {\n\t\t\t\treturn detectRuntime();\n\t\t\t}\n\t\t}\n\t\tObject.defineProperty(ConfiguredWebSocketModule, \"name\", {\n\t\t\tvalue: \"ConfiguredWebSocketModule\",\n\t\t});\n\n\t\treturn ConfiguredWebSocketModule as unknown as typeof ConfiguredWebSocketModule & {\n\t\t\treadonly config: typeof fullConfig;\n\t\t};\n\t}\n}\n",
8
+ "/**\n * Runtime detection & helper for `nexusjs/ws`.\n */\n\nexport type WsRuntime = \"bun\" | \"node\" | \"cloudflare\" | \"unknown\";\n\n/**\n * Detect the current JavaScript runtime.\n *\n * - `bun` — Bun (native `Bun.serve` WebSocket)\n * - `node` — Node.js (uses the `ws` package)\n * - `cloudflare` — Cloudflare Workers / Pages (Durable Objects only)\n * - `unknown` — fall-through; no adapter auto-installed\n */\nexport function detectRuntime(): WsRuntime {\n\tif (typeof (globalThis as { Bun?: unknown }).Bun !== \"undefined\") return \"bun\";\n\tif (typeof process !== \"undefined\" && process.versions?.node) return \"node\";\n\t// Cloudflare: `caches` global + absence of `process.versions.node` is a strong signal.\n\tif (\n\t\ttypeof (globalThis as { caches?: unknown }).caches !== \"undefined\" &&\n\t\ttypeof (globalThis as { WebSocketPair?: unknown }).WebSocketPair !== \"undefined\"\n\t) {\n\t\treturn \"cloudflare\";\n\t}\n\treturn \"unknown\";\n}\n",
9
+ "/**\n * WebSocket decorators.\n *\n * Two layers:\n * 1. `@WebSocketGateway(path)` — class-level. Marks the class as a\n * WebSocket gateway. The framework scans for these and registers\n * a Hono `upgradeWebSocket` handler at `<path>`.\n * 2. `@OnWebSocketOpen()`, `@OnWebSocketMessage()`, etc. —\n * method-level. Wire lifecycle events to specific methods on\n * the gateway class.\n *\n * Implementation note: lifecycle decorators store metadata on the\n * method function itself (via a `Symbol.for` key). The class-level\n * `@WebSocketGateway` walks the prototype to collect them. This\n * pattern is robust regardless of the target passed by the TS\n * decorator transform.\n */\n\nimport type { WsLifecycle } from \"./types.js\";\n\nconst GATEWAY_KEY = Symbol.for(\"nexus:ws:gateway\");\nconst LIFECYCLE_KEY = Symbol.for(\"nexus:ws:lifecycle\");\n\ninterface GatewayMetadata {\n\tpath: string;\n\thandlers: Record<string, string>;\n}\n\n/**\n * Mark a class as a WebSocket gateway. `path` is the URL path the\n * gateway listens on (e.g. `/ws`, `/chat`).\n *\n * @Injectable()\n * @WebSocketGateway('/ws')\n * class ChatGateway { ... }\n */\nexport function WebSocketGateway(path: string): ClassDecorator {\n\treturn function (target: Function) {\n\t\tconst ctor = target as unknown as { prototype: object };\n\t\tconst proto = ctor.prototype ?? (target as unknown as object);\n\t\t// Collect lifecycle handlers from method-level decorators.\n\t\tconst handlers: Record<string, string> = {};\n\t\tfor (const name of Object.getOwnPropertyNames(proto)) {\n\t\t\tif (name === \"constructor\") continue;\n\t\t\tconst m = (proto as any)[name];\n\t\t\tif (typeof m === \"function\" && (m as any)[LIFECYCLE_KEY]) {\n\t\t\t\tObject.assign(handlers, (m as any)[LIFECYCLE_KEY]);\n\t\t\t}\n\t\t}\n\t\t(proto as any)[GATEWAY_KEY] = { path, handlers } satisfies GatewayMetadata;\n\t};\n}\n\n/** Read the gateway path. Internal — used by the framework. */\nexport function getGatewayPath(target: object): string | undefined {\n\tconst t = target as any;\n\tconst meta: GatewayMetadata | undefined = t[GATEWAY_KEY];\n\treturn meta?.path;\n}\n\n/* ------------------------------------------------------------------ *\n * Lifecycle method decorators\n * ------------------------------------------------------------------ */\n\nfunction makeLifecycleDecorator(lifecycle: WsLifecycle) {\n\treturn function (\n\t\t_target: object,\n\t\tpropertyKey: string | symbol,\n\t\tdescriptor: PropertyDescriptor,\n\t) {\n\t\tconst fn = descriptor.value as Function & { [LIFECYCLE_KEY]?: Record<string, string> };\n\t\tif (typeof fn !== \"function\") return descriptor;\n\t\tfn[LIFECYCLE_KEY] = fn[LIFECYCLE_KEY] ?? {};\n\t\tfn[LIFECYCLE_KEY][lifecycle] = String(propertyKey);\n\t\treturn descriptor;\n\t};\n}\n\n/** Method decorator factory: bind to `onOpen` lifecycle. */\nexport function OnWebSocketOpen(): MethodDecorator {\n\treturn makeLifecycleDecorator(\"open\");\n}\n/** Method decorator factory: bind to `onMessage` lifecycle. */\nexport function OnWebSocketMessage(): MethodDecorator {\n\treturn makeLifecycleDecorator(\"message\");\n}\n/** Method decorator factory: bind to `onClose` lifecycle. */\nexport function OnWebSocketClose(): MethodDecorator {\n\treturn makeLifecycleDecorator(\"close\");\n}\n/** Method decorator factory: bind to `onError` lifecycle. */\nexport function OnWebSocketError(): MethodDecorator {\n\treturn makeLifecycleDecorator(\"error\");\n}\n\n/** Read the bound lifecycle methods. Internal — used by the framework. */\nexport function getLifecycleHandlers(target: object): {\n\topen?: string;\n\tmessage?: string;\n\tclose?: string;\n\terror?: string;\n} {\n\tconst t = target as any;\n\t// 1. Prefer the metadata stored by @WebSocketGateway.\n\tif (t[GATEWAY_KEY]?.handlers) return t[GATEWAY_KEY].handlers;\n\t// 2. Walk the prototype and collect from method functions.\n\tconst out: Record<string, string> = {};\n\tconst proto = t.prototype ?? t;\n\tfor (const name of Object.getOwnPropertyNames(proto)) {\n\t\tif (name === \"constructor\") continue;\n\t\tconst m = (proto as any)[name];\n\t\tif (typeof m === \"function\" && (m as any)[LIFECYCLE_KEY]) {\n\t\t\tObject.assign(out, (m as any)[LIFECYCLE_KEY]);\n\t\t}\n\t}\n\treturn out;\n}\n",
10
+ "/**\n * Bun runtime adapter for `nexusjs/ws`.\n *\n * Uses Hono's `createBunWebSocket` to bridge Hono's `WSEvents`\n * to Bun's native WebSocket API.\n *\n * The user wires the returned `websocket` config into\n * `Bun.serve({ websocket, fetch: app.fetch })`. The framework\n * also calls `Bun.serve` automatically if `serve` is provided\n * to `WebSocketModule.forRoot({ serve: { port: 3000 } })`.\n */\n\nimport type { Hono } from \"hono\";\nimport { getGatewayPath, getLifecycleHandlers } from \"../decorators.js\";\nimport type { WebSocketService } from \"../service.js\";\nimport { WebSocketClientImpl } from \"../client.js\";\nimport type { WebSocketGatewayOptions, GatewayClass } from \"./types.js\";\n\n/** Adapter that wires `@WebSocketGateway` classes to Bun's WebSocket. */\nexport class BunWsAdapter {\n\treadonly service: WebSocketService;\n\tprivate readonly options: WebSocketGatewayOptions;\n\n\tconstructor(service: WebSocketService, options: WebSocketGatewayOptions = {}) {\n\t\tthis.service = service;\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Returns the `websocket` config object that the user passes to\n\t * `Bun.serve({ websocket, fetch: app.fetch })`.\n\t *\n\t * The `upgradeWebSocket` middleware is also installed on the\n\t * Hono app at every registered gateway's path.\n\t */\n\tasync install(app: Hono, gateways: GatewayClass[]): Promise<{ websocket: BunWebSocketConfig }> {\n\t\t// Lazy-import Hono's Bun adapter to keep Node builds from breaking.\n\t\t// @ts-ignore - bun adapter is Bun-only\n\t\tconst { createBunWebSocket } = await import(\"hono/bun\");\n\t\tconst { upgradeWebSocket, websocket } = createBunWebSocket();\n\n\t\tfor (const Gateway of gateways) {\n\t\t\tconst path = getGatewayPath(Gateway.prototype);\n\t\t\tif (!path) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`WebSocket gateway ${Gateway.name} is missing @WebSocketGateway(path)`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst handlers = getLifecycleHandlers(Gateway.prototype);\n\t\t\tconst opts = this.options;\n\n\t\t\tapp.get(\n\t\t\t\tpath,\n\t\t\t\tupgradeWebSocket((_c: unknown) => {\n\t\t\t\t\t// Build the gateway instance per-connection. (DI happens\n\t\t\t\t\t// via the service — the gateway pulls dependencies from\n\t\t\t\t\t// the container through the service.)\n\t\t\t\t\tconst gateway = new Gateway();\n\t\t\t\t\t// Inject the service if the gateway has a setter / field.\n\t\t\t\t\tif (gateway && \"setService\" in (gateway as any) && typeof (gateway as any).setService === \"function\") {\n\t\t\t\t\t\t(gateway as any).setService(this.service);\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tonOpen: (_evt: Event, ws: any) => {\n\t\t\t\t\t\t\tconst client = new WebSocketClientImpl(ws);\n\t\t\t\t\t\t\tthis.service.register(client);\n\t\t\t\t\t\t\t(opts.onOpen ?? this.defaultOnOpen)(client);\n\t\t\t\t\t\t\tif (handlers.open) {\n\t\t\t\t\t\t\t\tPromise.resolve((gateway as any)[handlers.open](client)).catch(\n\t\t\t\t\t\t\t\t\t(err) => opts.onError?.(client, err as Error),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonMessage: (evt: MessageEvent, ws: any) => {\n\t\t\t\t\t\t\tconst id = (ws as any).__clientId as string | undefined;\n\t\t\t\t\t\t\tconst client = id ? this.service.getConnection(id) as WebSocketClientImpl | undefined : undefined;\n\t\t\t\t\t\t\tif (!client) return;\n\t\t\t\t\t\t\tconst raw = evt.data;\n\t\t\t\t\t\t\tconst data = this.parseMessage(raw);\n\t\t\t\t\t\t\tif (handlers.message) {\n\t\t\t\t\t\t\t\tPromise.resolve((gateway as any)[handlers.message](client, data)).catch(\n\t\t\t\t\t\t\t\t\t(err) => opts.onError?.(client, err as Error),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonClose: (evt: CloseEvent, ws: any) => {\n\t\t\t\t\t\t\tconst id = (ws as any).__clientId as string | undefined;\n\t\t\t\t\t\t\tconst client = id ? this.service.getConnection(id) as WebSocketClientImpl | undefined : undefined;\n\t\t\t\t\t\t\tif (!client) return;\n\t\t\t\t\t\t\tthis.service.unregister(client);\n\t\t\t\t\t\t\t(opts.onClose ?? this.defaultOnClose)(client, evt.code, evt.reason);\n\t\t\t\t\t\t\tif (handlers.close) {\n\t\t\t\t\t\t\t\tPromise.resolve((gateway as any)[handlers.close](client, evt.code, evt.reason)).catch(\n\t\t\t\t\t\t\t\t\t(err) => opts.onError?.(client, err as Error),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonError: (evt: Event, ws: any) => {\n\t\t\t\t\t\t\tconst id = (ws as any).__clientId as string | undefined;\n\t\t\t\t\t\t\tconst client = id ? this.service.getConnection(id) as WebSocketClientImpl | undefined : undefined;\n\t\t\t\t\t\t\tif (!client) return;\n\t\t\t\t\t\t\tconst err = (evt as any).error ?? new Error(\"WebSocket error\");\n\t\t\t\t\t\t\tif (handlers.error) {\n\t\t\t\t\t\t\t\tPromise.resolve((gateway as any)[handlers.error](client, err)).catch(() => {});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\topts.onError?.(client, err);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\treturn { websocket };\n\t}\n\n\tprivate parseMessage(raw: unknown): unknown {\n\t\tif (this.options.json === false) return raw;\n\t\tif (typeof raw === \"string\") {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(raw);\n\t\t\t} catch {\n\t\t\t\treturn raw;\n\t\t\t}\n\t\t}\n\t\treturn raw;\n\t}\n\n\tprivate defaultOnOpen(_client: any): void {}\n\tprivate defaultOnClose(_client: any, _code: number, _reason: string): void {}\n}\n\n\n/** Bun's `websocket` config shape (subset we use). */\nexport interface BunWebSocketConfig {\n\topen?: (ws: any) => void;\n\tmessage?: (ws: any, message: any) => void;\n\tclose?: (ws: any, code: number, reason: string) => void;\n\terror?: (ws: any, error: Error) => void;\n\t// ... other Bun WS options\n}\n",
11
+ "/**\n * Node runtime adapter for `nexusjs/ws`.\n *\n * Uses the `ws` package directly. The user provides a\n * `WebSocketServer` (or one is created automatically) and the\n * framework wires connection events to the gateway's lifecycle\n * handlers.\n *\n * The `ws` package is an optional peer dependency. Apps targeting\n * Bun don't need to install it.\n *\n * Usage (with auto-server):\n *\n * const mod = WebSocketModule.forRoot({ path: '/ws' });\n * const { app, wsServer } = await mod.mountNode(gateways);\n * const server = createServer(app.fetch);\n * server.listen(3000);\n * wsServer.emit('connection', ws, req); // via the framework\n *\n * Usage (with existing `http.Server`):\n *\n * const server = http.createServer(app.fetch);\n * const wss = new WebSocketServer({ server });\n * mod.bindNode(wss, gateways);\n * server.listen(3000);\n */\n\nimport type { Hono } from \"hono\";\nimport { getGatewayPath, getLifecycleHandlers } from \"../decorators.js\";\nimport type { WebSocketService } from \"../service.js\";\nimport { WebSocketClientImpl } from \"../client.js\";\nimport type { WebSocketGatewayOptions, GatewayClass } from \"./types.js\";\n\n/** Adapter that wires `@WebSocketGateway` classes to a `ws.WebSocketServer`. */\nexport class NodeWsAdapter {\n\treadonly service: WebSocketService;\n\tprivate readonly options: WebSocketGatewayOptions;\n\n\tconstructor(service: WebSocketService, options: WebSocketGatewayOptions = {}) {\n\t\tthis.service = service;\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Create a `WebSocketServer` that upgrades at the given gateway\n\t * paths. The user is responsible for binding it to an HTTP\n\t * server.\n\t */\n\tasync bind(gateways: GatewayClass[]): Promise<NodeWsServer> {\n\t\t// Lazy-import `ws` to keep Bun builds clean.\n\t\tlet WS: typeof import(\"ws\");\n\t\ttry {\n\t\t\tWS = (await import(\"ws\")).default ? await import(\"ws\") : (await import(\"ws\"));\n\t\t\t// @ts-ignore - ws exports shape varies\n\t\t\tconst WebSocketServer = (WS as any).WebSocketServer ?? (WS as any).Server ?? (WS as any).default?.WebSocketServer;\n\t\t\tif (!WebSocketServer) throw new Error(\"no WebSocketServer export\");\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t\"WebSocketModule.forRoot() on Node requires the `ws` package. \" +\n\t\t\t\t\t\"Install with: bun add ws\",\n\t\t\t);\n\t\t}\n\n\t\t// Build the per-path upgrade handlers map.\n\t\tconst handlers = new Map<string, (req: any) => boolean>();\n\t\tconst gatewayInstances = new Map<string, GatewayClass>();\n\t\tfor (const Gateway of gateways) {\n\t\t\tconst path = getGatewayPath(Gateway.prototype);\n\t\t\tif (!path) continue;\n\t\t\tgatewayInstances.set(path, Gateway);\n\t\t\thandlers.set(path, (req: any) => {\n\t\t\t\tconst url = new URL(req.url ?? \"/\", \"http://localhost\");\n\t\t\t\treturn url.pathname === path;\n\t\t\t});\n\t\t}\n\n\t\t// @ts-ignore - WS is dynamically imported\n\t\tconst wss = new (WS as any).WebSocketServer({ noServer: true });\n\t\tconst opts = this.options;\n\t\tconst service = this.service;\n\n\t\twss.on(\"connection\", (ws: any, req: any) => {\n\t\t\tconst url = new URL(req.url ?? \"/\", \"http://localhost\");\n\t\t\tconst path = url.pathname;\n\t\t\tconst Gateway = gatewayInstances.get(path);\n\t\t\tif (!Gateway) {\n\t\t\t\tws.close(1008, \"Unknown path\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst gateway = new Gateway();\n\t\t\tif (gateway && \"setService\" in (gateway as any) && typeof (gateway as any).setService === \"function\") {\n\t\t\t\t(gateway as any).setService(service);\n\t\t\t}\n\t\t\tconst client = new WebSocketClientImpl(adaptUnderlying(ws, req));\n\t\t\tservice.register(client);\n\t\t\t(opts.onOpen ?? (() => {}))(client);\n\n\t\t\tconst handlers = getLifecycleHandlers(Gateway.prototype);\n\n\t\t\tws.on(\"message\", (data: any) => {\n\t\t\t\tconst parsed = this.parseMessage(data);\n\t\t\t\tif (handlers.message) {\n\t\t\t\t\tPromise.resolve((gateway as any)[handlers.message](client, parsed)).catch(\n\t\t\t\t\t\t(err) => opts.onError?.(client, err as Error),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tws.on(\"close\", (code: number, reasonBuf: any) => {\n\t\t\t\tconst reason = reasonBuf?.toString?.() ?? \"\";\n\t\t\t\tservice.unregister(client);\n\t\t\t\t(opts.onClose ?? (() => {}))(client, code, reason);\n\t\t\t\tif (handlers.close) {\n\t\t\t\t\tPromise.resolve((gateway as any)[handlers.close](client, code, reason)).catch(\n\t\t\t\t\t\t(err) => opts.onError?.(client, err as Error),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tws.on(\"error\", (err: Error) => {\n\t\t\t\tif (handlers.error) {\n\t\t\t\t\tPromise.resolve((gateway as any)[handlers.error](client, err)).catch(() => {});\n\t\t\t\t} else {\n\t\t\t\t\topts.onError?.(client, err);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\treturn {\n\t\t\thandleUpgrade(req: any, socket: any, head: any, callback?: (ws: any) => void) {\n\t\t\t\tconst url = new URL(req.url ?? \"/\", \"http://localhost\");\n\t\t\t\tconst handler = handlers.get(url.pathname);\n\t\t\t\tif (!handler || !handler(req)) {\n\t\t\t\t\tsocket.write(\"HTTP/1.1 404 Not Found\\r\\n\\r\\n\");\n\t\t\t\t\tsocket.destroy();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\twss.handleUpgrade(req, socket, head, callback ?? (() => {}));\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate parseMessage(raw: unknown): unknown {\n\t\tif (this.options.json === false) return raw;\n\t\tif (typeof raw === \"string\") {\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(raw);\n\t\t\t} catch {\n\t\t\t\treturn raw;\n\t\t\t}\n\t\t}\n\t\tif (raw && typeof raw === \"object\" && \"toString\" in raw) {\n\t\t\t// ws sometimes gives Buffer\n\t\t\tconst s = (raw as any).toString();\n\t\t\ttry {\n\t\t\t\treturn JSON.parse(s);\n\t\t\t} catch {\n\t\t\t\treturn s;\n\t\t\t}\n\t\t}\n\t\treturn raw;\n\t}\n}\n\n/** Adapts a `ws.WebSocket` to the `UnderlyingWs` interface. */\nfunction adaptUnderlying(ws: any, req: any) {\n\treturn {\n\t\tsend(data: string | ArrayBuffer | Uint8Array, _options?: { compress?: boolean }) {\n\t\t\t// ws accepts string | Buffer | ArrayBuffer | Buffer[]\n\t\t\tws.send(data as any);\n\t\t},\n\t\tclose(code?: number, reason?: string) {\n\t\t\tws.close(code, reason);\n\t\t},\n\t\tget readyState() {\n\t\t\treturn ws.readyState;\n\t\t},\n\t\tget url() {\n\t\t\treturn req.url ?? null;\n\t\t},\n\t\tget protocol() {\n\t\t\treturn ws.protocol ?? null;\n\t\t},\n\t};\n}\n\n/** A handle to attach the WS upgrade to a Node HTTP server. */\nexport interface NodeWsServer {\n\thandleUpgrade(req: any, socket: any, head: any, callback?: (ws: any) => void): void;\n}\n"
12
+ ],
13
+ "mappings": "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",
14
+ "debugId": "A4A5EDFDAED3B04B64756E2164756E21",
15
+ "names": []
16
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `WebSocketModule` — wires `WebSocketService` into the DI container
3
+ * and provides runtime-specific helpers for the application.
4
+ *
5
+ * Usage (Bun):
6
+ *
7
+ * @Module({
8
+ * imports: [
9
+ * WebSocketModule.forRoot({
10
+ * gateways: [ChatGateway],
11
+ * path: '/ws',
12
+ * }),
13
+ * ],
14
+ * })
15
+ * class AppModule {}
16
+ *
17
+ * const app = new Application(AppModule);
18
+ * const { websocket } = await app.wireWebSocket();
19
+ * Bun.serve({
20
+ * port: 3000,
21
+ * fetch: app.fetch,
22
+ * websocket,
23
+ * });
24
+ *
25
+ * Usage (Node):
26
+ *
27
+ * const app = new Application(AppModule);
28
+ * const { server, wss } = await app.serveNodeWebSocket(3000);
29
+ */
30
+ import { WebSocketService } from "./service.js";
31
+ import type { WebSocketConfig } from "./types.js";
32
+ import type { GatewayClass } from "./runtime/types.js";
33
+ export declare class WebSocketModule {
34
+ static forRoot(config?: WebSocketConfig & {
35
+ gateways?: GatewayClass[];
36
+ }): {
37
+ new (service: WebSocketService): {
38
+ readonly service: WebSocketService;
39
+ /** Detect the runtime at boot time. */
40
+ detectRuntime(): import("./index.js").WsRuntime;
41
+ };
42
+ } & {
43
+ readonly config: Required<WebSocketConfig> & {
44
+ gateways: GatewayClass[];
45
+ };
46
+ };
47
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Bun runtime adapter for `nexusjs/ws`.
3
+ *
4
+ * Uses Hono's `createBunWebSocket` to bridge Hono's `WSEvents`
5
+ * to Bun's native WebSocket API.
6
+ *
7
+ * The user wires the returned `websocket` config into
8
+ * `Bun.serve({ websocket, fetch: app.fetch })`. The framework
9
+ * also calls `Bun.serve` automatically if `serve` is provided
10
+ * to `WebSocketModule.forRoot({ serve: { port: 3000 } })`.
11
+ */
12
+ import type { Hono } from "hono";
13
+ import type { WebSocketService } from "../service.js";
14
+ import type { WebSocketGatewayOptions, GatewayClass } from "./types.js";
15
+ /** Adapter that wires `@WebSocketGateway` classes to Bun's WebSocket. */
16
+ export declare class BunWsAdapter {
17
+ readonly service: WebSocketService;
18
+ private readonly options;
19
+ constructor(service: WebSocketService, options?: WebSocketGatewayOptions);
20
+ /**
21
+ * Returns the `websocket` config object that the user passes to
22
+ * `Bun.serve({ websocket, fetch: app.fetch })`.
23
+ *
24
+ * The `upgradeWebSocket` middleware is also installed on the
25
+ * Hono app at every registered gateway's path.
26
+ */
27
+ install(app: Hono, gateways: GatewayClass[]): Promise<{
28
+ websocket: BunWebSocketConfig;
29
+ }>;
30
+ private parseMessage;
31
+ private defaultOnOpen;
32
+ private defaultOnClose;
33
+ }
34
+ /** Bun's `websocket` config shape (subset we use). */
35
+ export interface BunWebSocketConfig {
36
+ open?: (ws: any) => void;
37
+ message?: (ws: any, message: any) => void;
38
+ close?: (ws: any, code: number, reason: string) => void;
39
+ error?: (ws: any, error: Error) => void;
40
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Runtime detection & helper for `nexusjs/ws`.
3
+ */
4
+ export type WsRuntime = "bun" | "node" | "cloudflare" | "unknown";
5
+ /**
6
+ * Detect the current JavaScript runtime.
7
+ *
8
+ * - `bun` — Bun (native `Bun.serve` WebSocket)
9
+ * - `node` — Node.js (uses the `ws` package)
10
+ * - `cloudflare` — Cloudflare Workers / Pages (Durable Objects only)
11
+ * - `unknown` — fall-through; no adapter auto-installed
12
+ */
13
+ export declare function detectRuntime(): WsRuntime;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Node runtime adapter for `nexusjs/ws`.
3
+ *
4
+ * Uses the `ws` package directly. The user provides a
5
+ * `WebSocketServer` (or one is created automatically) and the
6
+ * framework wires connection events to the gateway's lifecycle
7
+ * handlers.
8
+ *
9
+ * The `ws` package is an optional peer dependency. Apps targeting
10
+ * Bun don't need to install it.
11
+ *
12
+ * Usage (with auto-server):
13
+ *
14
+ * const mod = WebSocketModule.forRoot({ path: '/ws' });
15
+ * const { app, wsServer } = await mod.mountNode(gateways);
16
+ * const server = createServer(app.fetch);
17
+ * server.listen(3000);
18
+ * wsServer.emit('connection', ws, req); // via the framework
19
+ *
20
+ * Usage (with existing `http.Server`):
21
+ *
22
+ * const server = http.createServer(app.fetch);
23
+ * const wss = new WebSocketServer({ server });
24
+ * mod.bindNode(wss, gateways);
25
+ * server.listen(3000);
26
+ */
27
+ import type { WebSocketService } from "../service.js";
28
+ import type { WebSocketGatewayOptions, GatewayClass } from "./types.js";
29
+ /** Adapter that wires `@WebSocketGateway` classes to a `ws.WebSocketServer`. */
30
+ export declare class NodeWsAdapter {
31
+ readonly service: WebSocketService;
32
+ private readonly options;
33
+ constructor(service: WebSocketService, options?: WebSocketGatewayOptions);
34
+ /**
35
+ * Create a `WebSocketServer` that upgrades at the given gateway
36
+ * paths. The user is responsible for binding it to an HTTP
37
+ * server.
38
+ */
39
+ bind(gateways: GatewayClass[]): Promise<NodeWsServer>;
40
+ private parseMessage;
41
+ }
42
+ /** A handle to attach the WS upgrade to a Node HTTP server. */
43
+ export interface NodeWsServer {
44
+ handleUpgrade(req: any, socket: any, head: any, callback?: (ws: any) => void): void;
45
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Runtime adapter types for `nexusjs/ws`.
3
+ */
4
+ import type { WebSocketClient } from "../types.js";
5
+ /** Options shared by all runtime adapters. */
6
+ export interface WebSocketGatewayOptions {
7
+ /** Auto-parse JSON messages. Default: true. */
8
+ json?: boolean;
9
+ /** Called after a client connects. */
10
+ onOpen?: (client: WebSocketClient) => void;
11
+ /** Called after a client disconnects. */
12
+ onClose?: (client: WebSocketClient, code: number, reason: string) => void;
13
+ /** Called on a connection error. */
14
+ onError?: (client: WebSocketClient, err: Error) => void;
15
+ }
16
+ /** A WebSocket gateway class — a class that has `@WebSocketGateway(path)`. */
17
+ export type GatewayClass = new (...args: any[]) => any;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `WebSocketService` — connection registry, rooms, broadcasting.
3
+ *
4
+ * The service is the central state for all open WebSocket
5
+ * connections. It is registered in the DI container as a singleton
6
+ * and consumed by `@WebSocketGateway` classes.
7
+ *
8
+ * Features:
9
+ * - **Connection registry** — `getConnections()` returns all
10
+ * currently-open clients.
11
+ * - **Per-connection lookup** — `getConnection(id)`.
12
+ * - **Rooms** — `joinRoom`, `leaveRoom`, `broadcastToRoom`. Rooms
13
+ * are auto-cleaned when empty.
14
+ * - **Broadcast** — `broadcast(data)` sends to all open clients.
15
+ * - **Targeted send** — `sendTo(id, data)`.
16
+ * - **Lifecycle tracking** — `onConnect` / `onDisconnect` callbacks
17
+ * for metrics, audit logging, etc.
18
+ */
19
+ import type { WebSocketClient, WsMessage } from "./types.js";
20
+ import { WebSocketClientImpl } from "./client.js";
21
+ export declare const WEBSOCKET_SERVICE_TOKEN: unique symbol;
22
+ export declare class WebSocketService {
23
+ /** id -> client */
24
+ private clients;
25
+ /** room -> Set<id> */
26
+ private rooms;
27
+ /** Lifecycle callbacks. */
28
+ private onConnectListeners;
29
+ private onDisconnectListeners;
30
+ /** Register a new client. Returns the framework id. */
31
+ register(client: WebSocketClientImpl): string;
32
+ /** Remove a client from the registry. Auto-cleans empty rooms. */
33
+ unregister(client: WebSocketClientImpl): void;
34
+ /** Number of currently-open connections. */
35
+ get size(): number;
36
+ /** Get all open connections. */
37
+ getConnections(): WebSocketClient[];
38
+ /** Get a connection by id. */
39
+ getConnection(id: string): WebSocketClient | undefined;
40
+ /** Join a room. */
41
+ joinRoom(client: WebSocketClient, room: string): void;
42
+ /** Leave a room. */
43
+ leaveRoom(client: WebSocketClient, room: string): void;
44
+ /** Leave all rooms. */
45
+ leaveAllRooms(client: WebSocketClient): void;
46
+ /** All client ids in a room. */
47
+ getRoomMembers(room: string): WebSocketClient[];
48
+ /** All room names currently in use. */
49
+ getRooms(): string[];
50
+ /** Whether a room has any members. */
51
+ hasRoom(room: string): boolean;
52
+ /** Send to all open clients. */
53
+ broadcast(data: WsMessage, filter?: (client: WebSocketClient) => boolean): void;
54
+ /** Send to all clients in a room. */
55
+ broadcastToRoom(room: string, data: WsMessage): void;
56
+ /** Send to a single client by id. */
57
+ sendTo(id: string, data: WsMessage): boolean;
58
+ /** Close all connections (graceful shutdown). */
59
+ closeAll(code?: number, reason?: string): void;
60
+ /** Register a callback for new connections. */
61
+ onConnect(cb: (client: WebSocketClient) => void): void;
62
+ /** Register a callback for closed connections. */
63
+ onDisconnect(cb: (client: WebSocketClient) => void): void;
64
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Public types for `nexusjs/ws`.
3
+ *
4
+ * `nexusjs/ws` is a thin abstraction over Hono's runtime-specific
5
+ * WebSocket support. The same `@WebSocketGateway` / `@OnWebSocketMessage`
6
+ * pattern works on Bun (via `hono/adapter/bun/websocket`) and Node
7
+ * (via the `ws` package).
8
+ *
9
+ * Cloudflare Workers WebSockets require a Durable Objects context
10
+ * and are out of scope for v0.5 — see `docs/user-guide/ws.md` for
11
+ * the integration recipe.
12
+ */
13
+ /** Mirrors the standard `WebSocket.readyState` enum. */
14
+ export declare const enum WsReadyState {
15
+ CONNECTING = 0,
16
+ OPEN = 1,
17
+ CLOSING = 2,
18
+ CLOSED = 3
19
+ }
20
+ /** A message that can be sent or received. */
21
+ export type WsMessage = string | ArrayBuffer | Uint8Array | object;
22
+ /** Decode options. */
23
+ export interface WsDecodeOptions {
24
+ /** Parse JSON strings automatically. Default: true. */
25
+ json?: boolean;
26
+ }
27
+ /** User-attached arbitrary data on a connection. */
28
+ export interface WsClientData {
29
+ /** Optional authenticated user id. */
30
+ userId?: string | number;
31
+ /** Free-form key-value bag for app use. */
32
+ [key: string]: unknown;
33
+ }
34
+ /** A wrapper around the underlying WebSocket that adds identity, rooms, and data. */
35
+ export interface WebSocketClient {
36
+ /** Unique connection id (assigned by the framework). */
37
+ readonly id: string;
38
+ /** Rooms this client has joined. */
39
+ readonly rooms: ReadonlySet<string>;
40
+ /** User-attached data. */
41
+ readonly data: WsClientData;
42
+ /** The Hono URL the client connected to. */
43
+ readonly url: string | null;
44
+ /** Optional sub-protocol negotiated. */
45
+ readonly protocol: string | null;
46
+ /** Current readyState. */
47
+ readonly readyState: WsReadyState;
48
+ /** Send a message. Plain objects are JSON-encoded. */
49
+ send(data: WsMessage): void;
50
+ /** Close the connection. */
51
+ close(code?: number, reason?: string): void;
52
+ /** Attach arbitrary data to the client (mutation is reflected on `client.data`). */
53
+ setData(data: Partial<WsClientData>): void;
54
+ /** Join a room. */
55
+ joinRoom(room: string): void;
56
+ /** Leave a room. */
57
+ leaveRoom(room: string): void;
58
+ }
59
+ export interface WebSocketConfig {
60
+ /** Default mount path prefix. Default: "/ws". */
61
+ path?: string;
62
+ /** Whether to auto-parse JSON messages. Default: true. */
63
+ json?: boolean;
64
+ /** Heartbeat interval in seconds. 0 = disabled. Default: 30. */
65
+ heartbeatSeconds?: number;
66
+ /** How to handle unknown runtimes. Default: "throw". */
67
+ onUnknownRuntime?: "throw" | "noop";
68
+ }
69
+ export type WsLifecycle = "open" | "message" | "close" | "error";
70
+ /** The method handlers on a gateway. */
71
+ export interface WsLifecycleHandlers {
72
+ open?: (client: WebSocketClient) => void | Promise<void>;
73
+ message?: (client: WebSocketClient, data: unknown) => void | Promise<void>;
74
+ close?: (client: WebSocketClient, code: number, reason: string) => void | Promise<void>;
75
+ error?: (client: WebSocketClient, error: Error) => void | Promise<void>;
76
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@nexusts/ws",
3
+ "version": "0.7.2",
4
+ "description": "WebSockets (Bun native, Node fallback via ws)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": ["dist", "README.md"],
16
+ "scripts": {
17
+ "build": "bun run ../../build.ts"
18
+ },
19
+ "keywords": ["nexusts", "framework", "bun"],
20
+ "license": "MIT",
21
+ "peerDependencies": { "ws": "^8.18.0" },
22
+ "peerDependenciesMeta": { "ws": { "optional": true } },
23
+ "dependencies": {
24
+ "@nexusts/core": "file:../core"
25
+ }
26
+ }