@daloyjs/core 1.0.0-beta.6 → 1.0.0-beta.7
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 +2 -2
- package/dist/adapters/bun.d.ts +13 -1
- package/dist/adapters/bun.js +8 -1
- package/dist/adapters/cloudflare.d.ts +7 -1
- package/dist/adapters/cloudflare.js +6 -1
- package/dist/adapters/deno.d.ts +11 -1
- package/dist/adapters/deno.js +8 -1
- package/dist/adapters/fastly.d.ts +12 -2
- package/dist/adapters/fastly.js +12 -2
- package/dist/adapters/lambda.d.ts +37 -1
- package/dist/adapters/lambda.js +6 -1
- package/dist/adapters/node.d.ts +12 -1
- package/dist/adapters/node.js +7 -1
- package/dist/adapters/vercel.d.ts +13 -1
- package/dist/adapters/vercel.js +12 -1
- package/dist/app.d.ts +67 -17
- package/dist/app.js +97 -31
- package/dist/banner.d.ts +6 -0
- package/dist/banner.js +6 -0
- package/dist/cli.d.ts +35 -0
- package/dist/cli.js +23 -1
- package/dist/combine.d.ts +8 -0
- package/dist/combine.js +8 -0
- package/dist/compression.d.ts +3 -0
- package/dist/compression.js +3 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/conn-info.d.ts +35 -2
- package/dist/conn-info.js +35 -2
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +2 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.js +12 -0
- package/dist/dependency.d.ts +4 -0
- package/dist/dependency.js +3 -0
- package/dist/discriminator.d.ts +13 -0
- package/dist/discriminator.js +23 -1
- package/dist/docs.d.ts +78 -0
- package/dist/docs.js +21 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +14 -0
- package/dist/etag.d.ts +2 -0
- package/dist/etag.js +2 -0
- package/dist/fetch-guard.d.ts +7 -0
- package/dist/fetch-guard.js +7 -0
- package/dist/fetch-resilience.d.ts +4 -0
- package/dist/fetch-resilience.js +4 -0
- package/dist/http-signatures.d.ts +32 -0
- package/dist/http-signatures.js +30 -0
- package/dist/index.d.ts +1 -1
- package/dist/ip-restriction.d.ts +40 -3
- package/dist/ip-restriction.js +35 -3
- package/dist/jwk.d.ts +12 -1
- package/dist/jwk.js +6 -0
- package/dist/jwt.d.ts +14 -0
- package/dist/jwt.js +10 -0
- package/dist/load-shedding.d.ts +3 -0
- package/dist/load-shedding.js +3 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.js +3 -0
- package/dist/mcp.d.ts +152 -10
- package/dist/mcp.js +223 -19
- package/dist/middleware.d.ts +68 -0
- package/dist/middleware.js +17 -0
- package/dist/mtls.d.ts +19 -2
- package/dist/mtls.js +12 -2
- package/dist/multipart.d.ts +42 -5
- package/dist/multipart.js +41 -5
- package/dist/openapi.d.ts +15 -9
- package/dist/openapi.js +6 -9
- package/dist/rate-limit-redis.d.ts +21 -2
- package/dist/rate-limit-redis.js +17 -2
- package/dist/safe-redirect.d.ts +6 -0
- package/dist/safe-redirect.js +6 -0
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/dist/scheduler.d.ts +4 -0
- package/dist/schema.d.ts +25 -0
- package/dist/security-schemes.d.ts +50 -0
- package/dist/security-schemes.js +6 -0
- package/dist/security.d.ts +33 -0
- package/dist/security.js +28 -10
- package/dist/session.d.ts +34 -5
- package/dist/session.js +31 -5
- package/dist/streaming.d.ts +19 -0
- package/dist/streaming.js +16 -0
- package/dist/subdomains.d.ts +4 -0
- package/dist/subdomains.js +4 -0
- package/dist/time-claims.d.ts +22 -2
- package/dist/time-claims.js +6 -0
- package/dist/tracing.d.ts +12 -0
- package/dist/tracing.js +6 -0
- package/dist/types.d.ts +70 -1
- package/dist/waf.js +21 -1
- package/dist/webhook-delivery.d.ts +6 -0
- package/dist/webhook-delivery.js +5 -0
- package/dist/websocket.d.ts +137 -4
- package/dist/websocket.js +105 -4
- package/package.json +1 -1
package/dist/websocket.d.ts
CHANGED
|
@@ -76,10 +76,15 @@ export interface WebSocketConnection<TData = unknown> {
|
|
|
76
76
|
* been read.
|
|
77
77
|
*/
|
|
78
78
|
export interface WebSocketContext<P extends string = string, S = AppState> {
|
|
79
|
+
/** The original RFC 6455 upgrade `Request`. */
|
|
79
80
|
request: Request;
|
|
81
|
+
/** Path parameters extracted from the matched route pattern. */
|
|
80
82
|
params: PathParams<P>;
|
|
83
|
+
/** Query-string parameters from the upgrade URL. */
|
|
81
84
|
query: Record<string, string>;
|
|
85
|
+
/** Upgrade request headers, lower-cased keys. */
|
|
82
86
|
headers: Record<string, string>;
|
|
87
|
+
/** Per-app state produced by the route's state factory. */
|
|
83
88
|
state: S;
|
|
84
89
|
/** Subprotocols offered by the client (parsed from `Sec-WebSocket-Protocol`). */
|
|
85
90
|
protocols: string[];
|
|
@@ -208,25 +213,45 @@ export interface WebSocketHandler<P extends string = string, S = AppState, TData
|
|
|
208
213
|
* @since 0.33.0
|
|
209
214
|
*/
|
|
210
215
|
acknowledgeCrossOriginUpgrade?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Auth/decision hook run before the 101 response is sent (after the
|
|
218
|
+
* {@link WebSocketHandler.allowedOrigins} check). Return a `Response` to
|
|
219
|
+
* reject, a string to select that subprotocol, or `undefined` to accept.
|
|
220
|
+
*/
|
|
211
221
|
beforeUpgrade?(request: Request, ctx: WebSocketContext<P, S>): Response | string | undefined | Promise<Response | string | undefined>;
|
|
222
|
+
/** Called once after the upgrade completes and the connection is open. */
|
|
212
223
|
open?(conn: WebSocketConnection<TData>, ctx: WebSocketContext<P, S>): void | Promise<void>;
|
|
224
|
+
/** Called for each complete inbound message; `data` is a string for text frames, bytes for binary (`isBinary: true`). */
|
|
213
225
|
message?(conn: WebSocketConnection<TData>, data: string | Uint8Array | ArrayBuffer, isBinary: boolean): void | Promise<void>;
|
|
226
|
+
/** Called when the connection closes, with the RFC 6455 close code and reason. */
|
|
214
227
|
close?(conn: WebSocketConnection<TData>, code: number, reason: string): void | Promise<void>;
|
|
228
|
+
/** Called when a socket or handler error occurs on the connection. */
|
|
215
229
|
error?(conn: WebSocketConnection<TData>, err: unknown): void | Promise<void>;
|
|
230
|
+
/** Called when the socket's send buffer drains after backpressure. */
|
|
216
231
|
drain?(conn: WebSocketConnection<TData>): void | Promise<void>;
|
|
217
232
|
}
|
|
218
233
|
/** Result of {@link normalizeWebSocketOptions}: fully resolved limits applied by the adapter. */
|
|
219
234
|
export interface NormalizedWebSocketOptions {
|
|
235
|
+
/** Close the connection when queued outbound bytes exceed `backpressureLimit`. Defaults to `true`. */
|
|
220
236
|
closeOnBackpressureLimit: boolean;
|
|
237
|
+
/** Maximum queued outbound bytes before backpressure handling triggers. Defaults to 1 MiB. */
|
|
221
238
|
backpressureLimit: number;
|
|
239
|
+
/** Per-message compression flag. Defaults to `false`; refused in production under secureDefaults. */
|
|
222
240
|
perMessageDeflate: boolean;
|
|
241
|
+
/** Idle timeout in seconds. Defaults to `120`. */
|
|
223
242
|
idleTimeout: number;
|
|
243
|
+
/** Maximum inbound message payload length in bytes. Defaults to 1 MiB. */
|
|
224
244
|
maxPayloadLength: number;
|
|
225
245
|
}
|
|
226
246
|
/**
|
|
227
247
|
* Resolve a user-supplied {@link WebSocketHandler} into the strict
|
|
228
248
|
* {@link NormalizedWebSocketOptions} the adapter consumes. Applies defaults,
|
|
229
249
|
* runs production safety checks, and throws on invalid values.
|
|
250
|
+
*
|
|
251
|
+
* @param handler The user-supplied handler whose option fields are validated.
|
|
252
|
+
* @param context Environment flags: `production` and `secureDefaults` gate the perMessageDeflate refusal.
|
|
253
|
+
* @returns The fully resolved options applied by the runtime adapter.
|
|
254
|
+
* @throws Error when a limit is not a positive integer, `allowedOrigins` is malformed, `perMessageDeflate` is enabled in production under secureDefaults, or `maxPayloadLength` exceeds the body schema maximum.
|
|
230
255
|
*/
|
|
231
256
|
export declare function normalizeWebSocketOptions(handler: WebSocketHandler<any, any, any>, context: {
|
|
232
257
|
production: boolean;
|
|
@@ -239,16 +264,27 @@ export type WebSocketBeforeUpgrade<P extends string = string, S = AppState> = No
|
|
|
239
264
|
* a `beforeUpgrade` handler to spend from the same shared buckets as HTTP
|
|
240
265
|
* routes (for example, login and WebSocket session-establishment endpoints).
|
|
241
266
|
*
|
|
267
|
+
* @param options The same {@link RateLimitOptions} accepted by the HTTP `rateLimit` middleware.
|
|
268
|
+
* @returns A `beforeUpgrade` hook that returns the 429 (or error) `Response` when the limit is exceeded, or `undefined` to allow the upgrade. Rate-limit headers are copied onto rejection responses.
|
|
242
269
|
* @since 0.23.0
|
|
243
270
|
*/
|
|
244
271
|
export declare function wsRateLimit<P extends string = string, S = AppState>(options: RateLimitOptions): WebSocketBeforeUpgrade<P, S>;
|
|
245
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* Helper for declaring a handler with full type-inference.
|
|
274
|
+
*
|
|
275
|
+
* @param handler The {@link WebSocketHandler} to type-check; returned as-is.
|
|
276
|
+
* @returns The same handler object, with `P`/`S`/`TData` inferred.
|
|
277
|
+
*/
|
|
246
278
|
export declare function defineWebSocket<P extends string, S = AppState, TData = unknown>(handler: WebSocketHandler<P, S, TData>): WebSocketHandler<P, S, TData>;
|
|
247
279
|
/** Entry stored inside {@link WebSocketRegistry} for a single WS route. */
|
|
248
280
|
export interface WebSocketRouteEntry {
|
|
281
|
+
/** Route path pattern the entry was registered under (may contain `:param` segments). */
|
|
249
282
|
path: PathString;
|
|
283
|
+
/** The user-supplied lifecycle handler for this route. */
|
|
250
284
|
handler: WebSocketHandler<any, any, any>;
|
|
285
|
+
/** Factory producing the per-upgrade `state` object handed to the context. */
|
|
251
286
|
createState: WebSocketStateFactory;
|
|
287
|
+
/** Resolved limits from {@link normalizeWebSocketOptions} applied by the adapter. */
|
|
252
288
|
options: NormalizedWebSocketOptions;
|
|
253
289
|
}
|
|
254
290
|
type WebSocketStateFactory = () => Record<string, unknown>;
|
|
@@ -263,7 +299,21 @@ export declare class WebSocketRegistry {
|
|
|
263
299
|
private router;
|
|
264
300
|
private _size;
|
|
265
301
|
private entries;
|
|
302
|
+
/**
|
|
303
|
+
* Register a WebSocket route.
|
|
304
|
+
*
|
|
305
|
+
* @param path Route path pattern (supports `:param` segments).
|
|
306
|
+
* @param handler Lifecycle handler for connections on this route.
|
|
307
|
+
* @param createState Per-upgrade state factory. Defaults to `() => ({})`.
|
|
308
|
+
* @param options Resolved limits; defaults to normalizing `handler` with dev-mode secure defaults.
|
|
309
|
+
*/
|
|
266
310
|
add(path: PathString, handler: WebSocketHandler<any, any, any>, createState?: WebSocketStateFactory, options?: NormalizedWebSocketOptions): void;
|
|
311
|
+
/**
|
|
312
|
+
* Match a request pathname against the registered WS routes.
|
|
313
|
+
*
|
|
314
|
+
* @param pathname URL pathname of the upgrade request.
|
|
315
|
+
* @returns The matched entry plus extracted params, or `undefined` when no route matches.
|
|
316
|
+
*/
|
|
267
317
|
find(pathname: string): RouteMatch<WebSocketRouteEntry> | undefined;
|
|
268
318
|
/**
|
|
269
319
|
* List every registered WebSocket route entry in registration order.
|
|
@@ -276,7 +326,17 @@ export declare class WebSocketRegistry {
|
|
|
276
326
|
* @since 0.37.0
|
|
277
327
|
*/
|
|
278
328
|
list(): WebSocketRouteEntry[];
|
|
329
|
+
/** Number of registered WebSocket routes. */
|
|
279
330
|
get size(): number;
|
|
331
|
+
/**
|
|
332
|
+
* Aggregate the per-route options into a single set for runtimes (Bun) that
|
|
333
|
+
* take one server-wide WebSocket config: `closeOnBackpressureLimit` only when
|
|
334
|
+
* every route enables it, largest `backpressureLimit`/`maxPayloadLength`,
|
|
335
|
+
* smallest `idleTimeout`, and `perMessageDeflate` if any route enables it.
|
|
336
|
+
* Returns the secure defaults when no routes are registered.
|
|
337
|
+
*
|
|
338
|
+
* @returns The merged {@link NormalizedWebSocketOptions} for the whole app.
|
|
339
|
+
*/
|
|
280
340
|
runtimeOptions(): NormalizedWebSocketOptions;
|
|
281
341
|
}
|
|
282
342
|
/**
|
|
@@ -284,17 +344,30 @@ export declare class WebSocketRegistry {
|
|
|
284
344
|
* `Sec-WebSocket-Key` per RFC 6455 §4.2.2:
|
|
285
345
|
*
|
|
286
346
|
* `base64(SHA1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))`
|
|
347
|
+
*
|
|
348
|
+
* @param key - The client's `Sec-WebSocket-Key` header value.
|
|
349
|
+
* @returns The base64-encoded accept token for the 101 response.
|
|
350
|
+
* @throws Error when Web Crypto (`crypto.subtle`) is unavailable.
|
|
287
351
|
*/
|
|
288
352
|
export declare function computeAcceptKey(key: string): Promise<string>;
|
|
289
353
|
/**
|
|
290
354
|
* Parse the offered subprotocols from a `Sec-WebSocket-Protocol` header.
|
|
291
355
|
* Returns trimmed, non-empty tokens in client preference order.
|
|
356
|
+
*
|
|
357
|
+
* @param header - Raw header value; `null`/`undefined` yields an empty list.
|
|
358
|
+
* @returns The offered subprotocol tokens, most-preferred first.
|
|
292
359
|
*/
|
|
293
360
|
export declare function parseSubprotocols(header: string | null | undefined): string[];
|
|
294
361
|
/**
|
|
295
362
|
* Validate a server-selected subprotocol before it is written to the 101
|
|
296
363
|
* response. RFC 6455 requires the value to be one of the client-offered
|
|
297
364
|
* tokens; enforcing it also prevents accidental response-header injection.
|
|
365
|
+
*
|
|
366
|
+
* @param protocol - The subprotocol the server wants to select.
|
|
367
|
+
* @param offered - Tokens the client offered (from {@link parseSubprotocols}).
|
|
368
|
+
* @returns The validated `protocol`, unchanged.
|
|
369
|
+
* @throws WebSocketProtocolError when `protocol` is not a valid HTTP token
|
|
370
|
+
* or was not offered by the client.
|
|
298
371
|
*/
|
|
299
372
|
export declare function validateSelectedSubprotocol(protocol: string, offered: readonly string[]): string;
|
|
300
373
|
/**
|
|
@@ -315,6 +388,12 @@ export type HandshakeResult = {
|
|
|
315
388
|
* Validate the upgrade request headers and compute the accept key when
|
|
316
389
|
* valid. This helper does **not** touch the wire — it only decides whether
|
|
317
390
|
* the handshake should succeed and what the response key should be.
|
|
391
|
+
*
|
|
392
|
+
* @param headers - Header accessor for the upgrade request (any object with
|
|
393
|
+
* a `Headers`-style `get`).
|
|
394
|
+
* @returns `{ ok: true, acceptKey, protocols }` when the RFC 6455 handshake
|
|
395
|
+
* headers are valid, or `{ ok: false, status, reason }` describing the
|
|
396
|
+
* error response the adapter should send.
|
|
318
397
|
*/
|
|
319
398
|
export declare function validateUpgrade(headers: {
|
|
320
399
|
get(name: string): string | null;
|
|
@@ -329,6 +408,10 @@ export declare function validateUpgrade(headers: {
|
|
|
329
408
|
* `{ ok: false, reason }` with a short human-readable reason suitable for
|
|
330
409
|
* the upgrade-error body.
|
|
331
410
|
*
|
|
411
|
+
* @param request - The upgrade request whose `Origin` header is checked.
|
|
412
|
+
* @param policy - The route's `allowedOrigins` value: `undefined` (allow
|
|
413
|
+
* all), `"same-origin"`, a string allowlist, or a predicate function.
|
|
414
|
+
* @returns `{ ok: true }` when permitted, otherwise `{ ok: false, reason }`.
|
|
332
415
|
* @since 0.33.0
|
|
333
416
|
*/
|
|
334
417
|
export declare function checkWebSocketOrigin(request: Request, policy: WebSocketHandler<any, any, any>["allowedOrigins"]): {
|
|
@@ -339,8 +422,11 @@ export declare function checkWebSocketOrigin(request: Request, policy: WebSocket
|
|
|
339
422
|
};
|
|
340
423
|
/** A single decoded RFC 6455 frame, as returned by {@link parseFrame}. */
|
|
341
424
|
export interface ParsedFrame {
|
|
425
|
+
/** FIN bit — `true` when this frame ends a message. */
|
|
342
426
|
fin: boolean;
|
|
427
|
+
/** Frame opcode (see `WS_OPCODE`): continuation, text, binary, close, ping, or pong. */
|
|
343
428
|
opcode: number;
|
|
429
|
+
/** Unmasked payload bytes — a subarray view over the input buffer, not a copy. */
|
|
344
430
|
payload: Uint8Array;
|
|
345
431
|
/** Number of bytes consumed from the input buffer (header + payload). */
|
|
346
432
|
consumed: number;
|
|
@@ -355,6 +441,15 @@ export declare const FRAME_INCOMPLETE: unique symbol;
|
|
|
355
441
|
* The parser unmasks payloads in-place when needed. The returned `payload`
|
|
356
442
|
* is a subarray view over `buf`; copy it if you intend to retain it past
|
|
357
443
|
* the next call.
|
|
444
|
+
*
|
|
445
|
+
* @param buf - Buffered socket bytes beginning at a frame boundary.
|
|
446
|
+
* @param opts - `requireMask: true` enforces the RFC 6455 rule that
|
|
447
|
+
* client-to-server frames are masked. Defaults to `{}` (not enforced).
|
|
448
|
+
* @returns The decoded {@link ParsedFrame}, or {@link FRAME_INCOMPLETE}
|
|
449
|
+
* when more bytes are needed.
|
|
450
|
+
* @throws WebSocketProtocolError on RSV bits, unknown opcodes, fragmented
|
|
451
|
+
* or oversized control frames, unmasked client frames, or payload lengths
|
|
452
|
+
* above `Number.MAX_SAFE_INTEGER`.
|
|
358
453
|
*/
|
|
359
454
|
export declare function parseFrame(buf: Uint8Array, opts?: {
|
|
360
455
|
requireMask?: boolean;
|
|
@@ -363,6 +458,12 @@ export declare function parseFrame(buf: Uint8Array, opts?: {
|
|
|
363
458
|
* Encode a single frame. By default the frame is emitted unmasked (server
|
|
364
459
|
* → client). Pass `mask: true` to generate a client-style masked frame (used
|
|
365
460
|
* mainly for testing).
|
|
461
|
+
*
|
|
462
|
+
* @param opts - Frame parts: `opcode` (required), `payload` (default empty),
|
|
463
|
+
* `fin` (default `true`), and `mask` (default `false`).
|
|
464
|
+
* @returns The wire-ready frame bytes (header + payload).
|
|
465
|
+
* @throws WebSocketProtocolError when a control-frame payload exceeds 125
|
|
466
|
+
* bytes; Error when masking is requested without Web Crypto.
|
|
366
467
|
*/
|
|
367
468
|
export declare function encodeFrame(opts: {
|
|
368
469
|
fin?: boolean;
|
|
@@ -370,9 +471,23 @@ export declare function encodeFrame(opts: {
|
|
|
370
471
|
payload?: Uint8Array;
|
|
371
472
|
mask?: boolean;
|
|
372
473
|
}): Uint8Array;
|
|
373
|
-
/**
|
|
474
|
+
/**
|
|
475
|
+
* Encode a CLOSE frame payload (`uint16 code` + optional UTF-8 reason).
|
|
476
|
+
*
|
|
477
|
+
* @param code - RFC 6455 close status code, written big-endian.
|
|
478
|
+
* @param reason - Optional human-readable reason. Defaults to `""`.
|
|
479
|
+
* @returns The 2+N byte close payload.
|
|
480
|
+
* @throws WebSocketProtocolError when the encoded reason exceeds 123 bytes.
|
|
481
|
+
*/
|
|
374
482
|
export declare function encodeClosePayload(code: number, reason?: string): Uint8Array;
|
|
375
|
-
/**
|
|
483
|
+
/**
|
|
484
|
+
* Decode a CLOSE frame payload. Returns `{ code: 1005, reason: "" }` when empty.
|
|
485
|
+
*
|
|
486
|
+
* @param payload - Unmasked close-frame payload bytes.
|
|
487
|
+
* @returns The close `code` and decoded UTF-8 `reason`.
|
|
488
|
+
* @throws WebSocketProtocolError when the payload is exactly 1 byte or the
|
|
489
|
+
* reason is not valid UTF-8.
|
|
490
|
+
*/
|
|
376
491
|
export declare function decodeClosePayload(payload: Uint8Array): {
|
|
377
492
|
code: number;
|
|
378
493
|
reason: string;
|
|
@@ -400,10 +515,15 @@ export interface MessageEvent {
|
|
|
400
515
|
}
|
|
401
516
|
/** Callbacks supplied to a {@link FrameSink} by an adapter to receive decoded frames and protocol events. */
|
|
402
517
|
export interface FrameSinkEvents {
|
|
518
|
+
/** A complete (possibly reassembled) text or binary message arrived. */
|
|
403
519
|
onMessage(ev: MessageEvent): void;
|
|
520
|
+
/** A PING control frame arrived; `payload` is a copied buffer. */
|
|
404
521
|
onPing(payload: Uint8Array): void;
|
|
522
|
+
/** A PONG control frame arrived; `payload` is a copied buffer. */
|
|
405
523
|
onPong(payload: Uint8Array): void;
|
|
524
|
+
/** A CLOSE frame arrived; the sink stops processing further input. */
|
|
406
525
|
onClose(code: number, reason: string): void;
|
|
526
|
+
/** An RFC 6455 violation was detected; the adapter should CLOSE(1002). */
|
|
407
527
|
onProtocolError(err: WebSocketProtocolError): void;
|
|
408
528
|
}
|
|
409
529
|
/**
|
|
@@ -422,11 +542,24 @@ export declare class FrameSink {
|
|
|
422
542
|
requireMask?: boolean;
|
|
423
543
|
maxPayloadLength?: number;
|
|
424
544
|
});
|
|
545
|
+
/**
|
|
546
|
+
* Feed raw socket bytes into the assembler. Complete frames trigger the
|
|
547
|
+
* configured callbacks; partial frames are buffered until more bytes
|
|
548
|
+
* arrive. No-op after a CLOSE frame or protocol error.
|
|
549
|
+
*
|
|
550
|
+
* @param chunk - The next bytes read from the socket.
|
|
551
|
+
*/
|
|
425
552
|
push(chunk: Uint8Array): void;
|
|
426
553
|
private handle;
|
|
427
554
|
private assertMessageSize;
|
|
428
555
|
}
|
|
429
|
-
/**
|
|
556
|
+
/**
|
|
557
|
+
* Coerce arbitrary `send()` payloads to a `Uint8Array` + opcode pair.
|
|
558
|
+
*
|
|
559
|
+
* @param data - A string (sent as TEXT) or any `ArrayBuffer`/view (sent as
|
|
560
|
+
* BINARY without copying when possible).
|
|
561
|
+
* @returns The `WS_OPCODE` to use and the payload bytes to frame.
|
|
562
|
+
*/
|
|
430
563
|
export declare function encodeSendPayload(data: string | ArrayBufferLike | ArrayBufferView): {
|
|
431
564
|
opcode: number;
|
|
432
565
|
payload: Uint8Array;
|
package/dist/websocket.js
CHANGED
|
@@ -124,6 +124,11 @@ function declaredSchemaMaxBytes(schema) {
|
|
|
124
124
|
* Resolve a user-supplied {@link WebSocketHandler} into the strict
|
|
125
125
|
* {@link NormalizedWebSocketOptions} the adapter consumes. Applies defaults,
|
|
126
126
|
* runs production safety checks, and throws on invalid values.
|
|
127
|
+
*
|
|
128
|
+
* @param handler The user-supplied handler whose option fields are validated.
|
|
129
|
+
* @param context Environment flags: `production` and `secureDefaults` gate the perMessageDeflate refusal.
|
|
130
|
+
* @returns The fully resolved options applied by the runtime adapter.
|
|
131
|
+
* @throws Error when a limit is not a positive integer, `allowedOrigins` is malformed, `perMessageDeflate` is enabled in production under secureDefaults, or `maxPayloadLength` exceeds the body schema maximum.
|
|
127
132
|
*/
|
|
128
133
|
export function normalizeWebSocketOptions(handler, context) {
|
|
129
134
|
const closeOnBackpressureLimit = handler.closeOnBackpressureLimit ?? true;
|
|
@@ -162,6 +167,8 @@ export function normalizeWebSocketOptions(handler, context) {
|
|
|
162
167
|
* a `beforeUpgrade` handler to spend from the same shared buckets as HTTP
|
|
163
168
|
* routes (for example, login and WebSocket session-establishment endpoints).
|
|
164
169
|
*
|
|
170
|
+
* @param options The same {@link RateLimitOptions} accepted by the HTTP `rateLimit` middleware.
|
|
171
|
+
* @returns A `beforeUpgrade` hook that returns the 429 (or error) `Response` when the limit is exceeded, or `undefined` to allow the upgrade. Rate-limit headers are copied onto rejection responses.
|
|
165
172
|
* @since 0.23.0
|
|
166
173
|
*/
|
|
167
174
|
export function wsRateLimit(options) {
|
|
@@ -200,7 +207,12 @@ function copyWsRateLimitHeaders(headers, response) {
|
|
|
200
207
|
response.headers.set(key, value);
|
|
201
208
|
});
|
|
202
209
|
}
|
|
203
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* Helper for declaring a handler with full type-inference.
|
|
212
|
+
*
|
|
213
|
+
* @param handler The {@link WebSocketHandler} to type-check; returned as-is.
|
|
214
|
+
* @returns The same handler object, with `P`/`S`/`TData` inferred.
|
|
215
|
+
*/
|
|
204
216
|
export function defineWebSocket(handler) {
|
|
205
217
|
return handler;
|
|
206
218
|
}
|
|
@@ -215,6 +227,14 @@ export class WebSocketRegistry {
|
|
|
215
227
|
router = new Router();
|
|
216
228
|
_size = 0;
|
|
217
229
|
entries = [];
|
|
230
|
+
/**
|
|
231
|
+
* Register a WebSocket route.
|
|
232
|
+
*
|
|
233
|
+
* @param path Route path pattern (supports `:param` segments).
|
|
234
|
+
* @param handler Lifecycle handler for connections on this route.
|
|
235
|
+
* @param createState Per-upgrade state factory. Defaults to `() => ({})`.
|
|
236
|
+
* @param options Resolved limits; defaults to normalizing `handler` with dev-mode secure defaults.
|
|
237
|
+
*/
|
|
218
238
|
add(path, handler, createState = () => ({}), options = normalizeWebSocketOptions(handler, {
|
|
219
239
|
production: false,
|
|
220
240
|
secureDefaults: true,
|
|
@@ -224,6 +244,12 @@ export class WebSocketRegistry {
|
|
|
224
244
|
this.entries.push(entry);
|
|
225
245
|
this._size += 1;
|
|
226
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Match a request pathname against the registered WS routes.
|
|
249
|
+
*
|
|
250
|
+
* @param pathname URL pathname of the upgrade request.
|
|
251
|
+
* @returns The matched entry plus extracted params, or `undefined` when no route matches.
|
|
252
|
+
*/
|
|
227
253
|
find(pathname) {
|
|
228
254
|
return this.router.find("GET", pathname);
|
|
229
255
|
}
|
|
@@ -240,9 +266,19 @@ export class WebSocketRegistry {
|
|
|
240
266
|
list() {
|
|
241
267
|
return [...this.entries];
|
|
242
268
|
}
|
|
269
|
+
/** Number of registered WebSocket routes. */
|
|
243
270
|
get size() {
|
|
244
271
|
return this._size;
|
|
245
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Aggregate the per-route options into a single set for runtimes (Bun) that
|
|
275
|
+
* take one server-wide WebSocket config: `closeOnBackpressureLimit` only when
|
|
276
|
+
* every route enables it, largest `backpressureLimit`/`maxPayloadLength`,
|
|
277
|
+
* smallest `idleTimeout`, and `perMessageDeflate` if any route enables it.
|
|
278
|
+
* Returns the secure defaults when no routes are registered.
|
|
279
|
+
*
|
|
280
|
+
* @returns The merged {@link NormalizedWebSocketOptions} for the whole app.
|
|
281
|
+
*/
|
|
246
282
|
runtimeOptions() {
|
|
247
283
|
if (this.entries.length === 0) {
|
|
248
284
|
return {
|
|
@@ -282,6 +318,10 @@ function getSubtle() {
|
|
|
282
318
|
* `Sec-WebSocket-Key` per RFC 6455 §4.2.2:
|
|
283
319
|
*
|
|
284
320
|
* `base64(SHA1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))`
|
|
321
|
+
*
|
|
322
|
+
* @param key - The client's `Sec-WebSocket-Key` header value.
|
|
323
|
+
* @returns The base64-encoded accept token for the 101 response.
|
|
324
|
+
* @throws Error when Web Crypto (`crypto.subtle`) is unavailable.
|
|
285
325
|
*/
|
|
286
326
|
export async function computeAcceptKey(key) {
|
|
287
327
|
const digest = await getSubtle().digest("SHA-1", enc.encode(key + WS_GUID));
|
|
@@ -290,6 +330,9 @@ export async function computeAcceptKey(key) {
|
|
|
290
330
|
/**
|
|
291
331
|
* Parse the offered subprotocols from a `Sec-WebSocket-Protocol` header.
|
|
292
332
|
* Returns trimmed, non-empty tokens in client preference order.
|
|
333
|
+
*
|
|
334
|
+
* @param header - Raw header value; `null`/`undefined` yields an empty list.
|
|
335
|
+
* @returns The offered subprotocol tokens, most-preferred first.
|
|
293
336
|
*/
|
|
294
337
|
export function parseSubprotocols(header) {
|
|
295
338
|
if (!header)
|
|
@@ -304,6 +347,12 @@ const WS_TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
|
304
347
|
* Validate a server-selected subprotocol before it is written to the 101
|
|
305
348
|
* response. RFC 6455 requires the value to be one of the client-offered
|
|
306
349
|
* tokens; enforcing it also prevents accidental response-header injection.
|
|
350
|
+
*
|
|
351
|
+
* @param protocol - The subprotocol the server wants to select.
|
|
352
|
+
* @param offered - Tokens the client offered (from {@link parseSubprotocols}).
|
|
353
|
+
* @returns The validated `protocol`, unchanged.
|
|
354
|
+
* @throws WebSocketProtocolError when `protocol` is not a valid HTTP token
|
|
355
|
+
* or was not offered by the client.
|
|
307
356
|
*/
|
|
308
357
|
export function validateSelectedSubprotocol(protocol, offered) {
|
|
309
358
|
if (!WS_TOKEN_RE.test(protocol)) {
|
|
@@ -318,6 +367,12 @@ export function validateSelectedSubprotocol(protocol, offered) {
|
|
|
318
367
|
* Validate the upgrade request headers and compute the accept key when
|
|
319
368
|
* valid. This helper does **not** touch the wire — it only decides whether
|
|
320
369
|
* the handshake should succeed and what the response key should be.
|
|
370
|
+
*
|
|
371
|
+
* @param headers - Header accessor for the upgrade request (any object with
|
|
372
|
+
* a `Headers`-style `get`).
|
|
373
|
+
* @returns `{ ok: true, acceptKey, protocols }` when the RFC 6455 handshake
|
|
374
|
+
* headers are valid, or `{ ok: false, status, reason }` describing the
|
|
375
|
+
* error response the adapter should send.
|
|
321
376
|
*/
|
|
322
377
|
export async function validateUpgrade(headers) {
|
|
323
378
|
const upgrade = headers.get("upgrade");
|
|
@@ -378,6 +433,10 @@ function isValidWebSocketKey(key) {
|
|
|
378
433
|
* `{ ok: false, reason }` with a short human-readable reason suitable for
|
|
379
434
|
* the upgrade-error body.
|
|
380
435
|
*
|
|
436
|
+
* @param request - The upgrade request whose `Origin` header is checked.
|
|
437
|
+
* @param policy - The route's `allowedOrigins` value: `undefined` (allow
|
|
438
|
+
* all), `"same-origin"`, a string allowlist, or a predicate function.
|
|
439
|
+
* @returns `{ ok: true }` when permitted, otherwise `{ ok: false, reason }`.
|
|
381
440
|
* @since 0.33.0
|
|
382
441
|
*/
|
|
383
442
|
export function checkWebSocketOrigin(request, policy) {
|
|
@@ -438,6 +497,15 @@ export const FRAME_INCOMPLETE = Symbol("daloy.ws.frameIncomplete");
|
|
|
438
497
|
* The parser unmasks payloads in-place when needed. The returned `payload`
|
|
439
498
|
* is a subarray view over `buf`; copy it if you intend to retain it past
|
|
440
499
|
* the next call.
|
|
500
|
+
*
|
|
501
|
+
* @param buf - Buffered socket bytes beginning at a frame boundary.
|
|
502
|
+
* @param opts - `requireMask: true` enforces the RFC 6455 rule that
|
|
503
|
+
* client-to-server frames are masked. Defaults to `{}` (not enforced).
|
|
504
|
+
* @returns The decoded {@link ParsedFrame}, or {@link FRAME_INCOMPLETE}
|
|
505
|
+
* when more bytes are needed.
|
|
506
|
+
* @throws WebSocketProtocolError on RSV bits, unknown opcodes, fragmented
|
|
507
|
+
* or oversized control frames, unmasked client frames, or payload lengths
|
|
508
|
+
* above `Number.MAX_SAFE_INTEGER`.
|
|
441
509
|
*/
|
|
442
510
|
export function parseFrame(buf, opts = {}) {
|
|
443
511
|
if (buf.length < 2)
|
|
@@ -514,6 +582,12 @@ export function parseFrame(buf, opts = {}) {
|
|
|
514
582
|
* Encode a single frame. By default the frame is emitted unmasked (server
|
|
515
583
|
* → client). Pass `mask: true` to generate a client-style masked frame (used
|
|
516
584
|
* mainly for testing).
|
|
585
|
+
*
|
|
586
|
+
* @param opts - Frame parts: `opcode` (required), `payload` (default empty),
|
|
587
|
+
* `fin` (default `true`), and `mask` (default `false`).
|
|
588
|
+
* @returns The wire-ready frame bytes (header + payload).
|
|
589
|
+
* @throws WebSocketProtocolError when a control-frame payload exceeds 125
|
|
590
|
+
* bytes; Error when masking is requested without Web Crypto.
|
|
517
591
|
*/
|
|
518
592
|
export function encodeFrame(opts) {
|
|
519
593
|
const fin = opts.fin !== false;
|
|
@@ -579,7 +653,14 @@ export function encodeFrame(opts) {
|
|
|
579
653
|
}
|
|
580
654
|
return out;
|
|
581
655
|
}
|
|
582
|
-
/**
|
|
656
|
+
/**
|
|
657
|
+
* Encode a CLOSE frame payload (`uint16 code` + optional UTF-8 reason).
|
|
658
|
+
*
|
|
659
|
+
* @param code - RFC 6455 close status code, written big-endian.
|
|
660
|
+
* @param reason - Optional human-readable reason. Defaults to `""`.
|
|
661
|
+
* @returns The 2+N byte close payload.
|
|
662
|
+
* @throws WebSocketProtocolError when the encoded reason exceeds 123 bytes.
|
|
663
|
+
*/
|
|
583
664
|
export function encodeClosePayload(code, reason = "") {
|
|
584
665
|
const reasonBytes = enc.encode(reason);
|
|
585
666
|
if (reasonBytes.length > WS_MAX_CONTROL_PAYLOAD - 2) {
|
|
@@ -591,7 +672,14 @@ export function encodeClosePayload(code, reason = "") {
|
|
|
591
672
|
out.set(reasonBytes, 2);
|
|
592
673
|
return out;
|
|
593
674
|
}
|
|
594
|
-
/**
|
|
675
|
+
/**
|
|
676
|
+
* Decode a CLOSE frame payload. Returns `{ code: 1005, reason: "" }` when empty.
|
|
677
|
+
*
|
|
678
|
+
* @param payload - Unmasked close-frame payload bytes.
|
|
679
|
+
* @returns The close `code` and decoded UTF-8 `reason`.
|
|
680
|
+
* @throws WebSocketProtocolError when the payload is exactly 1 byte or the
|
|
681
|
+
* reason is not valid UTF-8.
|
|
682
|
+
*/
|
|
595
683
|
export function decodeClosePayload(payload) {
|
|
596
684
|
if (payload.length === 0)
|
|
597
685
|
return { code: WS_CLOSE_CODE.NO_STATUS_RECEIVED, reason: "" };
|
|
@@ -634,6 +722,13 @@ export class FrameSink {
|
|
|
634
722
|
constructor(opts) {
|
|
635
723
|
this.opts = opts;
|
|
636
724
|
}
|
|
725
|
+
/**
|
|
726
|
+
* Feed raw socket bytes into the assembler. Complete frames trigger the
|
|
727
|
+
* configured callbacks; partial frames are buffered until more bytes
|
|
728
|
+
* arrive. No-op after a CLOSE frame or protocol error.
|
|
729
|
+
*
|
|
730
|
+
* @param chunk - The next bytes read from the socket.
|
|
731
|
+
*/
|
|
637
732
|
push(chunk) {
|
|
638
733
|
if (this.closed)
|
|
639
734
|
return;
|
|
@@ -738,7 +833,13 @@ export class FrameSink {
|
|
|
738
833
|
}
|
|
739
834
|
}
|
|
740
835
|
// ---------- Helpers shared by adapters ----------
|
|
741
|
-
/**
|
|
836
|
+
/**
|
|
837
|
+
* Coerce arbitrary `send()` payloads to a `Uint8Array` + opcode pair.
|
|
838
|
+
*
|
|
839
|
+
* @param data - A string (sent as TEXT) or any `ArrayBuffer`/view (sent as
|
|
840
|
+
* BINARY without copying when possible).
|
|
841
|
+
* @returns The `WS_OPCODE` to use and the payload bytes to frame.
|
|
842
|
+
*/
|
|
742
843
|
export function encodeSendPayload(data) {
|
|
743
844
|
if (typeof data === "string") {
|
|
744
845
|
return { opcode: WS_OPCODE.TEXT, payload: enc.encode(data) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daloyjs/core",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.7",
|
|
4
4
|
"description": "DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify-grade ops — distributed via pnpm.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|