@orkestrel/websocket 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
- import { Duplex } from 'node:stream';
2
- import { EmitterErrorHandler } from '@orkestrel/emitter';
3
- import { EmitterHooks } from '@orkestrel/emitter';
4
- import { EmitterInterface } from '@orkestrel/emitter';
1
+ import type { Duplex } from 'node:stream';
2
+ import type { EmitterErrorHandler } from '@orkestrel/emitter';
3
+ import type { EmitterHooks } from '@orkestrel/emitter';
4
+ import type { EmitterInterface } from '@orkestrel/emitter';
5
5
 
6
6
  /**
7
- * Compute the `Sec-WebSocket-Accept` response value for an RFC 6455 upgrade.
7
+ * Computes the `Sec-WebSocket-Accept` response value for an RFC 6455 upgrade.
8
8
  *
9
9
  * @remarks
10
10
  * The base64-encoded SHA-1 of the client's `Sec-WebSocket-Key` concatenated with the
@@ -17,61 +17,69 @@ import { EmitterInterface } from '@orkestrel/emitter';
17
17
  export declare function computeWebSocketAccept(key: string): string;
18
18
 
19
19
  /**
20
- * Create a server-native WebSocket over a raw upgraded `node:stream` Duplex socket.
20
+ * Creates a server-native WebSocket over a raw upgraded `node:stream` Duplex socket
21
+ * server mode when a `key` is given, client mode otherwise.
21
22
  *
22
23
  * @remarks
23
- * The construction entry point for the {@link NodeWebSocketInterface} (AGENTS §8). Pass
24
- * the upgraded `socket` plus the client's `Sec-WebSocket-Key` as `key` to run in SERVER
25
- * mode the wrapper writes the `101 Switching Protocols` handshake and sends unmasked
26
- * frames; omit `key` for CLIENT mode (no handshake, masked frames). This is the
27
- * lean-native handle; it speaks only the WebSocket wire protocol — an MCP transport (the
28
- * later chunk) is built ON it. It is the WebSocket counterpart to
29
- * `createSQLiteDatabase` / `createIndexedDBDatabase`.
24
+ * The construction entry point for the {@link NodeWebSocketInterface}. In server mode the
25
+ * wrapper writes the `101 Switching Protocols` handshake and sends unmasked frames; in
26
+ * client mode it writes no handshake and masks every outgoing frame. This is the
27
+ * lean-native handle: it speaks the WebSocket wire protocol and nothing above it, so a
28
+ * message transport is built on it rather than into it.
30
29
  *
31
30
  * @param options - The {@link NodeWebSocketOptions} (`socket`, optional `key` / `head` /
32
31
  * `protocol` / `on`)
33
32
  * @returns A typed {@link NodeWebSocketInterface}
33
+ * @throws A `WebSocketError` coded `OPTION` when `payload`, `timeout`, `key`, or `protocol` is refused, thrown before the wrapper writes to or assumes ownership of the `socket`
34
34
  *
35
- * @example
35
+ * @example Accept an upgrade and echo messages (server mode)
36
36
  * ```ts
37
- * import { createNodeWebSocket } from '@src/server'
37
+ * import { createNodeWebSocket } from '@orkestrel/websocket'
38
38
  *
39
- * // In a node:http 'upgrade' handler — server mode, identified by the client key:
40
39
  * server.on('upgrade', (request, socket, head) => {
40
+ * const key = request.headers['sec-websocket-key']
41
+ * if (typeof key !== 'string') {
42
+ * socket.destroy()
43
+ * return
44
+ * }
41
45
  * const ws = createNodeWebSocket({
42
46
  * socket,
43
- * key: request.headers['sec-websocket-key'],
44
- * head,
45
- * on: { message: (text) => ws.send(`echo: ${text}`) },
47
+ * key,
48
+ * head, // any bytes already buffered after the upgrade headers
49
+ * on: { message: (text) => ws.send(`echo: ${text}`) }, // wired before the first frame arrives
46
50
  * })
51
+ * ws.emitter.on('message', (text) => log('echoed', text)) // a second observer of the same event
52
+ * ws.emitter.on('close', (code, reason) => log('closed', code, reason))
47
53
  * })
48
54
  * ```
49
55
  */
50
56
  export declare function createNodeWebSocket(options: NodeWebSocketOptions): NodeWebSocketInterface;
51
57
 
52
58
  /**
53
- * Encode a single RFC 6455 frame to its wire bytes — the inverse of
54
- * {@link parseWebSocketFrame}.
59
+ * Encodes a single RFC 6455 frame to its wire bytes — the inverse of
60
+ * `parseWebSocketFrame`.
55
61
  *
56
62
  * @remarks
57
63
  * Builds a final (FIN-set) frame: byte 0 is `0x80 | opcode`; the payload length uses
58
64
  * the 7-bit form below 126, the `126` + 16-bit form below 65 536, or the `127` +
59
65
  * 64-bit form beyond; when `masked` is set the mask bit is set, a 4-byte key (supplied
60
- * via `options.mask`, else random) is written, and the payload is XOR-masked. Server→
61
- * client frames are unmasked (the default); pass `masked: true` to encode a CLIENT
62
- * frame (e.g. to feed the parser in a test). A `string` payload is encoded as UTF-8.
63
- * Returns one contiguous `Buffer` (header + payload), so the wrapper writes it with a
64
- * single `socket.write`. Pure.
66
+ * through `options.mask`, else random) is written, and the payload is XOR-masked. Server→
67
+ * client frames are unmasked (the default); pass `masked: true` to encode a client
68
+ * frame (for example to feed the parser in a test). A `string` payload is encoded as
69
+ * UTF-8. Returns one contiguous `Buffer` (header + payload), so the wrapper writes it
70
+ * with a single `socket.write`. Pure.
65
71
  *
66
72
  * @param opcode - The frame opcode (a `WEBSOCKET_OPCODE_*` value)
67
73
  * @param payload - The payload, a `Buffer` or a UTF-8 `string`
68
74
  * @param options - Masking control ({@link WebSocketEncodeOptions}); defaults to unmasked
69
75
  * @returns The complete frame as wire bytes
76
+ * @throws A {@link WebSocketError} coded `FRAME` when `opcode` is outside the four-bit wire field, when `options.mask` is not 4 bytes, or when `options.mask` is supplied without `masked: true`
70
77
  */
71
78
  export declare function encodeWebSocketFrame(opcode: number, payload: Buffer | string, options?: WebSocketEncodeOptions): Buffer;
72
79
 
73
80
  /**
74
- * Whether a numeric value is a valid RFC 6455 close status code to RECEIVE (§7.4.1).
81
+ * Checks whether a numeric value is a close status code an RFC 6455 endpoint may
82
+ * receive (§7.4.1).
75
83
  *
76
84
  * @remarks
77
85
  * True for `1000`–`1003`, `1007`–`1014`, and the application range `3000`–`4999`; false
@@ -84,7 +92,7 @@ export declare function encodeWebSocketFrame(opcode: number, payload: Buffer | s
84
92
  * never throws.
85
93
  *
86
94
  * @param code - The close status code to validate
87
- * @returns `true` when `code` is a valid RFC 6455 close code
95
+ * @returns True if `code` is a valid RFC 6455 close code; false otherwise
88
96
  *
89
97
  * @example
90
98
  * ```ts
@@ -94,25 +102,27 @@ export declare function encodeWebSocketFrame(opcode: number, payload: Buffer | s
94
102
  export declare function isCloseCode(code: number): boolean;
95
103
 
96
104
  /**
97
- * Whether the next frame uses the shortest valid RFC 6455 payload-length encoding.
105
+ * Checks whether a caught value is a {@link WebSocketError}, narrowing it so a `catch` can
106
+ * branch on `error.code`.
98
107
  *
99
- * @remarks
100
- * Returns `undefined` until the complete length prefix is buffered. The 16-bit form
101
- * is canonical only for lengths at least 126; the 64-bit form only for lengths at
102
- * least 65,536 and with its most-significant bit clear (RFC 6455 §5.2).
103
- *
104
- * @param buffer - The accumulation buffer containing the next frame header
105
- * @returns Its canonicality, or `undefined` while the length prefix is incomplete
108
+ * @param value - The value to test (typically a `catch` binding)
109
+ * @returns True if `value` is a `WebSocketError`; false otherwise
106
110
  *
107
111
  * @example
108
112
  * ```ts
109
- * if (isWebSocketFrameCanonical(buffer) === false) fail(WEBSOCKET_CLOSE_PROTOCOL)
113
+ * import { isWebSocketError } from '@src/server'
114
+ *
115
+ * try {
116
+ * ws.close(1000.5)
117
+ * } catch (error) {
118
+ * if (isWebSocketError(error) && error.code === 'CLOSE') ws.close()
119
+ * }
110
120
  * ```
111
121
  */
112
- export declare function isWebSocketFrameCanonical(buffer: Buffer): boolean | undefined;
122
+ export declare function isWebSocketError(value: unknown): value is WebSocketError;
113
123
 
114
124
  /**
115
- * Whether a value is a canonical RFC 6455 `Sec-WebSocket-Key`.
125
+ * Checks whether a value is a canonical RFC 6455 `Sec-WebSocket-Key`.
116
126
  *
117
127
  * @remarks
118
128
  * A valid key is exactly 16 random bytes encoded as 24 characters of base64, ending
@@ -120,7 +130,7 @@ export declare function isWebSocketFrameCanonical(buffer: Buffer): boolean | und
120
130
  * malformed or non-canonical encodings return `false`; nothing is thrown.
121
131
  *
122
132
  * @param key - The proposed `Sec-WebSocket-Key` header value
123
- * @returns `true` when `key` is the canonical base64 encoding of 16 bytes
133
+ * @returns True if `key` is the canonical base64 encoding of 16 bytes; false otherwise
124
134
  *
125
135
  * @example
126
136
  * ```ts
@@ -131,7 +141,7 @@ export declare function isWebSocketFrameCanonical(buffer: Buffer): boolean | und
131
141
  export declare function isWebSocketKey(key: string): boolean;
132
142
 
133
143
  /**
134
- * Whether a value is one valid WebSocket subprotocol token.
144
+ * Checks whether a value is one valid WebSocket subprotocol token.
135
145
  *
136
146
  * @remarks
137
147
  * Subprotocols use the HTTP `token` grammar. Whitespace, separators, commas, and
@@ -139,25 +149,45 @@ export declare function isWebSocketKey(key: string): boolean;
139
149
  * second handshake header.
140
150
  *
141
151
  * @param protocol - The negotiated subprotocol to validate
142
- * @returns `true` when `protocol` is one non-empty HTTP token
152
+ * @returns True if `protocol` is one non-empty HTTP token; false otherwise
143
153
  *
144
154
  * @example
145
155
  * ```ts
146
- * if (!isWebSocketProtocol(protocol)) throw new RangeError('invalid protocol')
156
+ * if (!isWebSocketProtocol(protocol)) socket.destroy()
147
157
  * ```
148
158
  */
149
159
  export declare function isWebSocketProtocol(protocol: string): boolean;
150
160
 
151
161
  /**
152
- * Read the declared payload length off the front of a buffer, without buffering or
153
- * reading the payload itself.
162
+ * Checks whether the next frame uses the shortest valid RFC 6455 payload-length
163
+ * encoding, answering `undefined` until its length prefix is complete.
164
+ *
165
+ * @remarks
166
+ * The 16-bit form is canonical only for lengths at least 126; the 64-bit form only for
167
+ * lengths at least 65,536 and with its most-significant bit clear (RFC 6455 §5.2). Reads
168
+ * the same length prefix as {@link measureWebSocketFrame}, under the same
169
+ * incomplete-buffer contract. Pure; never throws.
170
+ *
171
+ * @param buffer - The accumulation buffer containing the next frame header
172
+ * @returns Its canonicality, or `undefined` while the length prefix is incomplete
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * if (matchesWebSocketCanonical(buffer) === false) fail(WEBSOCKET_CLOSE_PROTOCOL)
177
+ * ```
178
+ */
179
+ export declare function matchesWebSocketCanonical(buffer: Buffer): boolean | undefined;
180
+
181
+ /**
182
+ * Reads the declared payload length off the front of a buffer without buffering or
183
+ * reading the payload itself, answering `undefined` until the length field is complete.
154
184
  *
155
185
  * @remarks
156
186
  * Decodes only byte 1's 7-bit length field, extended by the 16-bit (`126`) or 64-bit
157
- * (`127`) form exactly like {@link parseWebSocketFrame} — but stops there, so a caller
187
+ * (`127`) form exactly like `parseWebSocketFrame` — but stops there, so a caller
158
188
  * can reject an over-cap frame the moment its length is known, before the payload
159
- * bytes have even arrived. Returns `undefined` until the length field itself is fully
160
- * buffered (mirrors the parser's incomplete-buffer contract). Pure; never throws.
189
+ * bytes have even arrived. The incomplete-buffer contract mirrors the parser's. Pure;
190
+ * never throws.
161
191
  *
162
192
  * @param buffer - The accumulation buffer to read the next frame's length from
163
193
  * @returns The declared payload length, or `undefined` when the buffer is too short to know it yet
@@ -165,53 +195,79 @@ export declare function isWebSocketProtocol(protocol: string): boolean;
165
195
  * @example
166
196
  * ```ts
167
197
  * const declared = measureWebSocketFrame(buffer)
168
- * if (declared !== undefined && declared > limit) fail(WEBSOCKET_CLOSE_TOOBIG)
198
+ * if (declared !== undefined && declared > limit) fail(WEBSOCKET_CLOSE_TOO_BIG)
169
199
  * ```
170
200
  */
171
201
  export declare function measureWebSocketFrame(buffer: Buffer): number | undefined;
172
202
 
173
203
  /**
174
- * A server-native WebSocket over a raw upgraded `node:stream` Duplex — the lean
175
- * wrapper around the RFC 6455 wire protocol.
204
+ * Implements the wrapper contract over a raw upgraded `node:stream` Duplex socket,
205
+ * driving the RFC 6455 handshake, the frame codec, auto-pong, and the close handshake,
206
+ * and surfacing every event on an owned `emitter`.
176
207
  *
177
208
  * @remarks
178
- * Created by `createNodeWebSocket`. When given a client `key` it runs in SERVER mode —
209
+ * Created by `createNodeWebSocket`. When given a client `key` it runs in server mode —
179
210
  * it writes the `101 Switching Protocols` handshake (`computeWebSocketAccept(key)`) and
180
- * emits `open`; given no key it runs in CLIENT mode (no handshake, frames masked). It
211
+ * emits `open`; given no key it runs in client mode (no handshake, frames masked). It
181
212
  * then listens on the socket's `data`, accumulating bytes in `#buffer` and decoding
182
213
  * every complete frame with {@link parseWebSocketFrame} (slicing `consumed` and
183
- * re-parsing the remainder): a TEXT frame — reassembling continuation fragments across
184
- * `fin: false` frames — decodes to UTF-8 and emits `message`; a PING is auto-answered
185
- * with a PONG and emits `ping`; a PONG emits `pong`; a CLOSE is echoed and ends the
214
+ * re-parsing the remainder): a text frame — reassembling continuation fragments across
215
+ * `fin: false` frames — decodes to UTF-8 and emits `message`; a ping is auto-answered
216
+ * with a pong and emits `ping`; a pong emits `pong`; a close frame is echoed and ends the
186
217
  * socket, emitting `close`. `send` writes a text frame, `ping` a ping, `close` a close
187
- * frame; `destroy` tears down immediately. It owns a typed `#emitter` (AGENTS §13) that
188
- * isolates a throwing listener and routes the error to its own `error` handler (the `error`
189
- * option) — the socket never crashes. An underlying socket error emits the domain
190
- * `error` event and terminates the wrapper. The untyped socket `data` is narrowed to a
191
- * `Buffer` with a guard, never an assertion (AGENTS §14).
218
+ * frame; `destroy` tears down immediately. It owns a typed `#emitter` by composition, and
219
+ * the emitter isolates a throwing listener and routes the error to its own `error` handler
220
+ * (the `error` option) — the socket never crashes. An underlying socket error emits the
221
+ * domain `error` event and terminates the wrapper. The untyped socket `data` is narrowed
222
+ * to a `Buffer` with a guard, never an assertion.
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * import { NodeWebSocket } from '@src/server'
227
+ *
228
+ * // In a node:http 'upgrade' handler, over the socket the server already handed over:
229
+ * const key = request.headers['sec-websocket-key']
230
+ * if (typeof key !== 'string') {
231
+ * socket.destroy()
232
+ * return
233
+ * }
234
+ * const ws = new NodeWebSocket({ socket, key, head })
235
+ * ws.emitter.on('message', (text) => ws.send(`echo: ${text}`))
236
+ * ```
192
237
  */
193
238
  export declare class NodeWebSocket implements NodeWebSocketInterface {
194
239
  #private;
240
+ /**
241
+ * Creates a WebSocket wrapper over an already-upgraded Duplex socket.
242
+ *
243
+ * @remarks
244
+ * `key` selects the mode: present runs server mode and writes the `101 Switching
245
+ * Protocols` handshake, omitted runs client mode and masks every outgoing frame.
246
+ * {@link NodeWebSocketOptions} describes every member.
247
+ *
248
+ * @param options - The {@link NodeWebSocketOptions} the wrapper is built from
249
+ * @throws A {@link WebSocketError} coded `OPTION` when `payload`, `timeout`, `key`, or `protocol` is refused, or when `protocol` is supplied without a server `key`, thrown before the wrapper writes to or assumes ownership of the `socket`
250
+ */
195
251
  constructor(options: NodeWebSocketOptions);
196
252
  get emitter(): EmitterInterface<NodeWebSocketEventMap>;
197
253
  get readyState(): WebSocketReadyState;
198
- send(data: string): void;
199
- ping(data?: string): void;
254
+ send(message: string): void;
255
+ ping(payload?: string): void;
200
256
  close(code?: number, reason?: string): void;
201
257
  destroy(): void;
202
258
  }
203
259
 
204
260
  /**
205
- * The event map of a {@link NodeWebSocketInterface} (AGENTS §13).
261
+ * Represents the event map a {@link NodeWebSocketInterface} emitter carries.
206
262
  *
207
263
  * @remarks
208
264
  * `open` — the handshake completed and the socket is ready. `message` — a text frame
209
- * arrived (its decoded UTF-8 string). `close` — the connection ended (its
210
- * {@link WebSocketClose} metadata). `error` the underlying socket faulted (a DOMAIN
211
- * event and then terminates the wrapper). `ping` / `pong` — a control frame arrived
212
- * (a ping is auto-answered with a pong).
213
- * Listener isolation is the emitter's (AGENTS §13): a listener throw is routed to the
214
- * emitter's `error` handler (the `error` option), never onto this map, so a buggy observer
265
+ * arrived (its decoded UTF-8 string). `close` — the connection ended, carrying the
266
+ * labeled `[code, reason]` tuple (each `undefined` when the peer sent none). `error`
267
+ * the underlying socket faulted (a domain event, and then terminates the wrapper).
268
+ * `ping` / `pong` — a control frame arrived (a ping is auto-answered with a pong).
269
+ * Listener isolation is the emitter's: a listener throw is routed to the emitter's
270
+ * `error` handler (the `error` option), never onto this map, so a buggy observer
215
271
  * never breaks the socket.
216
272
  */
217
273
  export declare type NodeWebSocketEventMap = {
@@ -224,7 +280,8 @@ export declare type NodeWebSocketEventMap = {
224
280
  };
225
281
 
226
282
  /**
227
- * A server-native WebSocket over a raw upgraded socket — the behavioral contract.
283
+ * Represents the behavioral contract a server-native WebSocket exposes over a raw
284
+ * upgraded socket.
228
285
  *
229
286
  * @remarks
230
287
  * Created by `createNodeWebSocket`. In server mode it writes the RFC 6455 handshake
@@ -234,38 +291,86 @@ export declare type NodeWebSocketEventMap = {
234
291
  * frame is echoed and ends the socket, emitting `close`. `send` writes a text frame;
235
292
  * `ping` writes a ping; `close` writes a close frame (the 2-byte code + optional
236
293
  * reason); `destroy` tears the socket down immediately. `readyState` tracks the
237
- * lifecycle. It owns a typed `emitter` (AGENTS §13) and never throws on a faulty
294
+ * lifecycle. It owns a typed `emitter` by composition and never throws on a faulty
238
295
  * listener — the emitter routes it to its `error` handler (the `error` option).
296
+ * `ping` throws a `LIMIT`-coded `WebSocketError` when its UTF-8 payload exceeds
297
+ * `WEBSOCKET_CONTROL_MAX_LENGTH`; `close` throws a `CLOSE`-coded one for a status code
298
+ * `isCloseCode` refuses and a `LIMIT`-coded one for a reason past
299
+ * `WEBSOCKET_CLOSE_REASON_MAX_LENGTH`, in each case without changing `readyState`.
239
300
  */
240
301
  export declare interface NodeWebSocketInterface {
241
302
  readonly emitter: EmitterInterface<NodeWebSocketEventMap>;
242
303
  readonly readyState: WebSocketReadyState;
243
- send(data: string): void;
244
- ping(data?: string): void;
304
+ /**
305
+ * Writes a message as a UTF-8 text frame, masked in client mode and unmasked in server
306
+ * mode, and does nothing unless `readyState` is open.
307
+ *
308
+ * @remarks
309
+ * The peer's reply arrives back as a `message` event.
310
+ *
311
+ * @param message - The text to carry as the frame's payload
312
+ */
313
+ send(message: string): void;
314
+ /**
315
+ * Writes a ping frame with an optional payload, which the peer answers with a pong, and
316
+ * does nothing unless `readyState` is open.
317
+ *
318
+ * @remarks
319
+ * The answering pong arrives as the `pong` event.
320
+ *
321
+ * @param payload - The optional UTF-8 payload to carry
322
+ * @throws A `WebSocketError` coded `LIMIT` when the UTF-8 payload exceeds
323
+ * `WEBSOCKET_CONTROL_MAX_LENGTH`
324
+ */
325
+ ping(payload?: string): void;
326
+ /**
327
+ * Starts the closing handshake: moves to the closing ready state, writes a close frame
328
+ * carrying the two-byte big-endian `code` and an optional `reason`, and ends the
329
+ * writable side.
330
+ *
331
+ * @remarks
332
+ * The final `close` event fires after the peer echoes or the socket ends, and a second
333
+ * call is a no-op. Each refusal leaves `readyState` unchanged.
334
+ *
335
+ * @param code - The close status code, defaulting to `WEBSOCKET_CLOSE_NORMAL`
336
+ * @param reason - The optional UTF-8 reason to carry after the code
337
+ * @throws A `WebSocketError` coded `CLOSE` for an invalid or fractional `code`, and one
338
+ * coded `LIMIT` for a `reason` over `WEBSOCKET_CLOSE_REASON_MAX_LENGTH`
339
+ */
245
340
  close(code?: number, reason?: string): void;
341
+ /**
342
+ * Tears the socket down immediately: detaches the wrapper's domain socket listeners,
343
+ * destroys the socket, emits a final `close`, and tears the emitter down.
344
+ *
345
+ * @remarks
346
+ * Idempotent, and a hard stop rather than a handshake.
347
+ */
246
348
  destroy(): void;
247
349
  }
248
350
 
249
351
  /**
250
- * Options for `createNodeWebSocket`.
352
+ * Represents the options for `createNodeWebSocket` — the upgraded `socket`, the `key`
353
+ * that selects server or client mode, and the listeners, caps, and cancellation signal
354
+ * the wrapper runs under.
251
355
  *
252
356
  * @remarks
253
357
  * `socket` is the upgraded `node:stream` Duplex (the raw TCP stream after the HTTP
254
- * upgrade). `key` is the client's `Sec-WebSocket-Key`: present it to run in SERVER
255
- * mode — the wrapper writes the `101 Switching Protocols` handshake and sends UNMASKED
256
- * frames; omit it for CLIENT mode — no handshake is written and frames are MASKED (RFC
358
+ * upgrade). `key` is the client's `Sec-WebSocket-Key`: present it to run in server
359
+ * mode — the wrapper writes the `101 Switching Protocols` handshake and sends unmasked
360
+ * frames; omit it for client mode — no handshake is written and frames are masked (RFC
257
361
  * 6455 §5.3). `head` is any bytes buffered after the upgrade headers (replayed through
258
362
  * the parser). `protocol` is a negotiated subprotocol to echo in the handshake. `on`
259
- * wires initial listeners at construction (AGENTS §8 reserved option); `error` is the
260
- * emitter's listener-error handler (§13 a listener throw routes here). `payload` caps
261
- * both a single inbound frame's declared length AND the total bytes of a reassembled
363
+ * wires initial listeners at construction the reserved `on` option; `error` is the
364
+ * emitter's listener-error handler, where a listener throw routes. `payload` caps
365
+ * both a single inbound frame's declared length and the total bytes of a reassembled
262
366
  * fragmented message (default `WEBSOCKET_MAX_PAYLOAD`) — a breach closes 1009. `timeout`
263
367
  * is how long the wrapper waits, after sending a close frame, for the peer's echo before
264
368
  * it gives up and tears the socket down (default `WEBSOCKET_CLOSE_TIMEOUT_MS`). `signal`
265
369
  * is the external cancellation seam — on abort the socket destroys; composes with the
266
370
  * line's `@orkestrel/abort` and `@orkestrel/timeout` primitives, which expose native
267
371
  * `AbortSignal`s. An already-aborted signal tears the socket down immediately after
268
- * construction.
372
+ * construction. A refused member throws an `OPTION`-coded `WebSocketError` before the
373
+ * wrapper writes to or assumes ownership of the `socket`.
269
374
  */
270
375
  export declare interface NodeWebSocketOptions {
271
376
  readonly socket: Duplex;
@@ -273,7 +378,7 @@ export declare interface NodeWebSocketOptions {
273
378
  readonly head?: Buffer;
274
379
  readonly protocol?: string;
275
380
  readonly on?: EmitterHooks<NodeWebSocketEventMap>;
276
- /** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
381
+ /** Holds the emitter's listener-error handler — a listener throw routes here, not to a domain event. */
277
382
  readonly error?: EmitterErrorHandler;
278
383
  readonly payload?: number;
279
384
  readonly timeout?: number;
@@ -281,12 +386,13 @@ export declare interface NodeWebSocketOptions {
281
386
  }
282
387
 
283
388
  /**
284
- * Decode a byte sequence as strict UTF-8, or signal it is malformed.
389
+ * Decodes a byte sequence as strict UTF-8, answering `undefined` when the sequence is
390
+ * malformed.
285
391
  *
286
392
  * @remarks
287
- * Wraps `TextDecoder('utf-8', { fatal: true })` in a try/catch so a malformed sequence
288
- * returns `undefined` instead of throwing (AGENTS §14 — a guard-adjacent coercer never
289
- * throws on bad input). Pure.
393
+ * Wraps `TextDecoder('utf-8', { fatal: true })` in a try/catch, so a malformed sequence
394
+ * returns rather than throwing — a guard-adjacent coercer never throws on bad input.
395
+ * Pure.
290
396
  *
291
397
  * @param bytes - The raw bytes to decode
292
398
  * @returns The decoded string, or `undefined` when `bytes` is not valid UTF-8
@@ -300,119 +406,225 @@ export declare interface NodeWebSocketOptions {
300
406
  export declare function parseUTF8(bytes: Buffer): string | undefined;
301
407
 
302
408
  /**
303
- * Decode a single RFC 6455 frame from the front of a buffer.
409
+ * Decodes a single RFC 6455 frame from the front of a buffer, answering `undefined`
410
+ * while the buffer is incomplete so the caller accumulates and retries.
304
411
  *
305
412
  * @remarks
306
413
  * Reads the FIN bit and opcode (byte 0), the mask bit and 7-bit payload length (byte
307
414
  * 1) — extended to a 16-bit length when the 7-bit field is `126`, or a 64-bit length
308
415
  * when it is `127` — the optional 4-byte mask key, then the payload, XOR-unmasking it
309
- * against the key when the mask bit is set (client→server frames MUST be masked, RFC
416
+ * against the key when the mask bit is set (client→server frames must be masked, RFC
310
417
  * 6455 §5.3; an unmasked frame still decodes, leaving the payload as-is, so the caller
311
- * can enforce policy). Returns `undefined` the moment the buffer is too short for the
312
- * part it is up to (the length prefix, the mask, or the full payload) — the signal to
313
- * the caller to read more bytes and retry, exactly like {@link SSEParser} on a partial
314
- * line. `consumed` is the total bytes the frame occupied, so the caller slices the
315
- * remainder. Pure; never throws on a short buffer.
418
+ * can enforce policy). The incomplete answer comes the moment the buffer is too short
419
+ * for the part it is up to: the length prefix, the mask, or the full payload.
420
+ * `consumed` is the total bytes the frame occupied, so the caller slices the remainder.
421
+ * Pure; never throws on a short buffer.
316
422
  *
317
423
  * @param buffer - The accumulation buffer to decode the next frame from
318
424
  * @returns The parsed {@link WebSocketFrame}, or `undefined` when the buffer is incomplete
425
+ *
426
+ * @example
427
+ * ```ts
428
+ * const frame = parseWebSocketFrame(buffer)
429
+ * if (frame === undefined) return // incomplete — wait for more bytes
430
+ * ```
319
431
  */
320
432
  export declare function parseWebSocketFrame(buffer: Buffer): WebSocketFrame | undefined;
321
433
 
322
- /** Invalid-frame-payload-data status code (RFC 6455 §7.4.1) — e.g. non-UTF-8 text or an unparseable close reason. */
434
+ /**
435
+ * Names the invalid-frame-payload-data status code, 1007.
436
+ *
437
+ * @remarks
438
+ * Sent for non-UTF-8 text or an unparseable close reason (RFC 6455 §7.4.1).
439
+ */
323
440
  export declare const WEBSOCKET_CLOSE_INVALID = 1007;
324
441
 
325
- /** Normal-closure status code (RFC 6455 §7.4.1) — the default `close` code. */
442
+ /**
443
+ * Names the normal-closure status code, 1000.
444
+ *
445
+ * @remarks
446
+ * The default `close` code (RFC 6455 §7.4.1).
447
+ */
326
448
  export declare const WEBSOCKET_CLOSE_NORMAL = 1000;
327
449
 
328
- /** Protocol-error status code (RFC 6455 §7.4.1) — a framing/state rule was violated. */
450
+ /**
451
+ * Names the protocol-error status code, 1002.
452
+ *
453
+ * @remarks
454
+ * Sent when a framing or state rule was violated (RFC 6455 §7.4.1).
455
+ */
329
456
  export declare const WEBSOCKET_CLOSE_PROTOCOL = 1002;
330
457
 
331
- /** The maximum UTF-8 close-reason length after the two-byte status code. */
332
- export declare const WEBSOCKET_CLOSE_REASON_MAXLEN: number;
458
+ /**
459
+ * Names the maximum UTF-8 close-reason length after the two-byte status code, 123.
460
+ *
461
+ * @remarks
462
+ * What is left of {@link WEBSOCKET_CONTROL_MAX_LENGTH} after the close frame's status
463
+ * code.
464
+ */
465
+ export declare const WEBSOCKET_CLOSE_REASON_MAX_LENGTH: number;
333
466
 
334
- /** The default close-handshake timeout in milliseconds — how long `close()` waits for the peer's echo before tearing the socket down. */
467
+ /**
468
+ * Names the default close-handshake timeout, 30,000 milliseconds — how long `close` waits
469
+ * for the peer's echo.
470
+ *
471
+ * @remarks
472
+ * After it expires the wrapper tears the socket down, so a silent peer cannot leak the
473
+ * handle open.
474
+ */
335
475
  export declare const WEBSOCKET_CLOSE_TIMEOUT_MS = 30000;
336
476
 
337
- /** Message-too-big status code (RFC 6455 §7.4.1) — a reassembled message exceeded the payload cap. */
338
- export declare const WEBSOCKET_CLOSE_TOOBIG = 1009;
477
+ /**
478
+ * Names the message-too-big status code, 1009.
479
+ *
480
+ * @remarks
481
+ * Sent when a reassembled message exceeded the payload cap (RFC 6455 §7.4.1).
482
+ */
483
+ export declare const WEBSOCKET_CLOSE_TOO_BIG = 1009;
339
484
 
340
- /** Unsupported-data status code (RFC 6455 §7.4.1) — the endpoint received a data type it cannot accept (e.g. binary on a text-only endpoint). */
485
+ /**
486
+ * Names the unsupported-data status code, 1003.
487
+ *
488
+ * @remarks
489
+ * Sent when the endpoint received a data type it cannot accept (RFC 6455 §7.4.1), for
490
+ * example binary on a text-only endpoint.
491
+ */
341
492
  export declare const WEBSOCKET_CLOSE_UNSUPPORTED = 1003;
342
493
 
343
- /** The maximum control-frame payload length in bytes (RFC 6455 §5.5). */
344
- export declare const WEBSOCKET_CONTROL_MAXLEN = 125;
494
+ /**
495
+ * Names the maximum control-frame payload length, 125 bytes.
496
+ *
497
+ * @remarks
498
+ * The cap RFC 6455 §5.5 sets on every control frame's payload.
499
+ */
500
+ export declare const WEBSOCKET_CONTROL_MAX_LENGTH = 125;
345
501
 
346
- /** The post-`#fail` flush grace in milliseconds — how long a validation-breach close frame is given to flush through the socket's write buffer before the hard `destroy()` fallback fires (the normal path destroys sooner, on the `end()` flush callback). */
502
+ /**
503
+ * Names the flush grace, 1,000 milliseconds, a validation-breach close frame is given
504
+ * before the hard teardown fallback destroys the socket.
505
+ *
506
+ * @remarks
507
+ * Armed after `#fail` writes the close frame, so the frame drains through the socket's
508
+ * write buffer rather than being discarded. The normal path destroys sooner, on the
509
+ * `end()` flush callback.
510
+ */
347
511
  export declare const WEBSOCKET_FAIL_TIMEOUT_MS = 1000;
348
512
 
349
513
  /**
350
- * The RFC 6455 GUID concatenated to a client's `Sec-WebSocket-Key` before the SHA-1
351
- * hash that yields the `Sec-WebSocket-Accept` response value.
514
+ * Names the accept GUID concatenated to a client's `Sec-WebSocket-Key` before the accept
515
+ * hash, '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'.
352
516
  *
353
517
  * @remarks
354
- * A fixed, spec-mandated constant (RFC 6455 §4.2.2) read only by
518
+ * The base64-encoded SHA-1 of that concatenation is the `Sec-WebSocket-Accept` response
519
+ * value. A fixed, spec-mandated constant (RFC 6455 §4.2.2) — read only by
355
520
  * {@link computeWebSocketAccept}.
356
521
  */
357
522
  export declare const WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
358
523
 
359
- /** The default maximum inbound single-frame length AND reassembled-message total byte count (100 MiB — the `ws` package default). */
524
+ /**
525
+ * Names the default cap on both an inbound frame's declared length and a reassembled
526
+ * message's total byte count, 104,857,600 bytes (100 MiB).
527
+ *
528
+ * @remarks
529
+ * The same value the `ws` package defaults to. Either breach closes
530
+ * {@link WEBSOCKET_CLOSE_TOO_BIG}.
531
+ */
360
532
  export declare const WEBSOCKET_MAX_PAYLOAD = 104857600;
361
533
 
362
- /** Binary frame opcode — a raw byte payload (RFC 6455 §5.6). */
534
+ /**
535
+ * Names the binary frame opcode, 0x02.
536
+ *
537
+ * @remarks
538
+ * A raw byte payload (RFC 6455 §5.6).
539
+ */
363
540
  export declare const WEBSOCKET_OPCODE_BINARY = 2;
364
541
 
365
- /** Close frame opcode — a control frame ending the connection (RFC 6455 §5.5.1). */
542
+ /**
543
+ * Names the close frame opcode, 0x08.
544
+ *
545
+ * @remarks
546
+ * A control frame ending the connection (RFC 6455 §5.5.1).
547
+ */
366
548
  export declare const WEBSOCKET_OPCODE_CLOSE = 8;
367
549
 
368
- /** Continuation frame opcode — the next fragment of an open data message (RFC 6455 §5.4). */
550
+ /**
551
+ * Names the continuation frame opcode, 0x00.
552
+ *
553
+ * @remarks
554
+ * The next fragment of an open data message (RFC 6455 §5.4).
555
+ */
369
556
  export declare const WEBSOCKET_OPCODE_CONTINUATION = 0;
370
557
 
371
- /** Ping frame opcode — a control frame the peer must answer with a pong (RFC 6455 §5.5.2). */
558
+ /**
559
+ * Names the ping frame opcode, 0x09.
560
+ *
561
+ * @remarks
562
+ * A control frame the peer must answer with a pong (RFC 6455 §5.5.2).
563
+ */
372
564
  export declare const WEBSOCKET_OPCODE_PING = 9;
373
565
 
374
- /** Pong frame opcode — a control frame answering a ping (RFC 6455 §5.5.3). */
566
+ /**
567
+ * Names the pong frame opcode, 0x0a.
568
+ *
569
+ * @remarks
570
+ * A control frame answering a ping (RFC 6455 §5.5.3).
571
+ */
375
572
  export declare const WEBSOCKET_OPCODE_PONG = 10;
376
573
 
377
- /** Text frame opcode — a UTF-8 payload (RFC 6455 §5.6). */
574
+ /**
575
+ * Names the text frame opcode, 0x01.
576
+ *
577
+ * @remarks
578
+ * A UTF-8 payload (RFC 6455 §5.6).
579
+ */
378
580
  export declare const WEBSOCKET_OPCODE_TEXT = 1;
379
581
 
380
- /** Ready state for a closed WebSocket (the socket ended). */
582
+ /**
583
+ * Names the closed ready state, 3.
584
+ *
585
+ * @remarks
586
+ * The state a WebSocket holds after the socket ends.
587
+ */
381
588
  export declare const WEBSOCKET_READY_CLOSED: WebSocketReadyState;
382
589
 
383
- /** Ready state for a closing WebSocket (a close frame was sent or received). */
590
+ /**
591
+ * Names the closing ready state, 2.
592
+ *
593
+ * @remarks
594
+ * The state a WebSocket holds after a close frame is sent or received.
595
+ */
384
596
  export declare const WEBSOCKET_READY_CLOSING: WebSocketReadyState;
385
597
 
386
- /** Ready state for a connecting WebSocket (before the handshake completes). */
598
+ /**
599
+ * Names the connecting ready state, 0.
600
+ *
601
+ * @remarks
602
+ * The state a WebSocket holds before its handshake completes.
603
+ */
387
604
  export declare const WEBSOCKET_READY_CONNECTING: WebSocketReadyState;
388
605
 
389
- /** Ready state for an open WebSocket (the handshake completed; frames flow). */
606
+ /**
607
+ * Names the open ready state, 1.
608
+ *
609
+ * @remarks
610
+ * The state a WebSocket holds after the handshake completes and while frames flow.
611
+ */
390
612
  export declare const WEBSOCKET_READY_OPEN: WebSocketReadyState;
391
613
 
392
- /** The WebSocket protocol version this wrapper speaks (`Sec-WebSocket-Version: 13`). */
393
- export declare const WEBSOCKET_VERSION = "13";
394
-
395
614
  /**
396
- * The metadata of a closed WebSocket — why the connection ended.
615
+ * Names the supported protocol version, '13'.
397
616
  *
398
617
  * @remarks
399
- * `code` is the RFC 6455 close status code (undefined when the peer closed with no
400
- * payload); `reason` is the optional UTF-8 reason text (undefined when empty).
618
+ * The value this wrapper speaks, carried by the `Sec-WebSocket-Version` handshake header.
401
619
  */
402
- export declare interface WebSocketClose {
403
- readonly code: number | undefined;
404
- readonly reason: string | undefined;
405
- }
406
-
407
- /** A WebSocket close status code (RFC 6455 §7.4) — e.g. `WEBSOCKET_CLOSE_NORMAL` (1000). */
408
- export declare type WebSocketCloseCode = number;
620
+ export declare const WEBSOCKET_VERSION = "13";
409
621
 
410
622
  /**
411
- * Options for {@link encodeWebSocketFrame} — how a frame is masked on the wire.
623
+ * Represents the options for {@link encodeWebSocketFrame} — how a frame is masked on the wire.
412
624
  *
413
625
  * @remarks
414
- * `masked` toggles the mask bit (server→client frames are NOT masked, the default;
415
- * client→server frames MUST be, RFC 6455 §5.3). `mask` supplies an explicit 4-byte
626
+ * `masked` toggles the mask bit (server→client frames are not masked, the default;
627
+ * client→server frames must be, RFC 6455 §5.3). `mask` supplies an explicit 4-byte
416
628
  * mask key (deterministic, for tests); when `masked` is true and `mask` is omitted a
417
629
  * random key is generated.
418
630
  */
@@ -422,7 +634,57 @@ export declare interface WebSocketEncodeOptions {
422
634
  }
423
635
 
424
636
  /**
425
- * A parsed RFC 6455 frame — the structured result of decoding one frame off the wire.
637
+ * Represents an error the WebSocket wrapper throws for a refused caller-supplied value,
638
+ * carrying a machine-readable `code` and an optional `context`.
639
+ *
640
+ * @remarks
641
+ * The `code` is a {@link WebSocketErrorCode}; the `context` record holds the refused
642
+ * value under a key naming it: an `'OPTION'` carries the offending option (`payload`,
643
+ * `timeout`, `key`, or `protocol`), a `'LIMIT'` carries `size` and the `limit` it
644
+ * exceeded, a `'CLOSE'` carries the refused close `code`, and a `'FRAME'` carries
645
+ * `opcode` or the mask's `size`. Narrow a caught value with {@link isWebSocketError}.
646
+ *
647
+ * @example
648
+ * ```ts
649
+ * import { createNodeWebSocket, isWebSocketError } from '@src/server'
650
+ *
651
+ * try {
652
+ * createNodeWebSocket({ socket, key: 'not-base64' })
653
+ * } catch (error) {
654
+ * if (isWebSocketError(error) && error.code === 'OPTION') socket.destroy()
655
+ * }
656
+ * ```
657
+ */
658
+ export declare class WebSocketError extends Error {
659
+ readonly code: WebSocketErrorCode;
660
+ readonly context?: Readonly<Record<string, unknown>>;
661
+ /**
662
+ * Creates a WebSocket error carrying a machine-readable code.
663
+ *
664
+ * @param code - The machine-readable {@link WebSocketErrorCode} a `catch` branches on
665
+ * @param message - The human-readable description, carried as the `Error` message
666
+ * @param context - The refused value keyed by name; omitted leaves `context` `undefined`
667
+ */
668
+ constructor(code: WebSocketErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
669
+ }
670
+
671
+ /**
672
+ * Represents the subject a `WebSocketError` names as refused.
673
+ *
674
+ * @remarks
675
+ * `OPTION` — a {@link NodeWebSocketOptions} member was refused at construction
676
+ * (`payload`, `timeout`, `key`, `protocol`, or a `protocol` given without a server
677
+ * `key`). `LIMIT` — an outbound control-frame payload exceeded its RFC 6455 §5.5 cap
678
+ * (a `ping` payload past `WEBSOCKET_CONTROL_MAX_LENGTH`, a `close` reason past
679
+ * `WEBSOCKET_CLOSE_REASON_MAX_LENGTH`). `CLOSE` — a close status code `isCloseCode` refuses
680
+ * was passed to `close`. `FRAME` — an `encodeWebSocketFrame` frame-header argument was
681
+ * refused (an opcode outside the four-bit wire field, a mask that is not 4 bytes, or a
682
+ * mask supplied without `masked: true`).
683
+ */
684
+ export declare type WebSocketErrorCode = 'OPTION' | 'LIMIT' | 'CLOSE' | 'FRAME';
685
+
686
+ /**
687
+ * Represents a parsed RFC 6455 frame — the structured result of decoding one frame off the wire.
426
688
  *
427
689
  * @remarks
428
690
  * `fin` is the final-fragment bit (false for a continued fragment); `opcode`
@@ -430,7 +692,7 @@ export declare interface WebSocketEncodeOptions {
430
692
  * the already-unmasked application data; `consumed` is the total byte count the frame
431
693
  * occupied (header + mask + payload), so the caller slices it off the front of its
432
694
  * accumulation buffer and re-parses the remainder. `masked` is the mask bit off byte 1
433
- * (client→server frames MUST be masked, RFC 6455 §5.1); `rsv` is the three reserved
695
+ * (client→server frames must be masked, RFC 6455 §5.1); `rsv` is the three reserved
434
696
  * bits off byte 0 packed into a single 0–7 value (RFC 6455 §5.2) — non-zero means an
435
697
  * extension the wrapper does not negotiate, so the caller rejects it. Produced by
436
698
  * {@link parseWebSocketFrame}.
@@ -444,18 +706,13 @@ export declare interface WebSocketFrame {
444
706
  readonly rsv: number;
445
707
  }
446
708
 
447
- /** A decoded text message received from, or to send to, a WebSocket peer. */
448
- export declare interface WebSocketMessage {
449
- readonly data: string;
450
- }
451
-
452
709
  /**
453
- * A WebSocket ready state — the four browser-compatible lifecycle values.
710
+ * Represents a WebSocket ready state — the stage a connection has reached between the
711
+ * handshake and the socket's end.
454
712
  *
455
713
  * @remarks
456
- * `0` connecting, `1` open, `2` closing, `3` closed the same numbering the DOM
457
- * `WebSocket.readyState` uses, so the wrapper reads like the platform API. The named
458
- * `WEBSOCKET_READY_*` constants spell each value.
714
+ * The same numbering the DOM `WebSocket.readyState` uses, so the wrapper reads like the
715
+ * platform API. The named `WEBSOCKET_READY_*` constants spell each value.
459
716
  */
460
717
  export declare type WebSocketReadyState = 0 | 1 | 2 | 3;
461
718