yinzerflow 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/docs/core/websockets.md +343 -0
- package/index.d.ts +269 -0
- package/index.js +33 -21
- package/index.js.map +16 -8
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1456,6 +1456,156 @@ export interface Context<T extends HandlerCallbackGenerics = HandlerCallbackGene
|
|
|
1456
1456
|
* @see {@link HandlerCallbackGenerics} for custom typing options
|
|
1457
1457
|
*/
|
|
1458
1458
|
export type HandlerCallback<T extends HandlerCallbackGenerics = HandlerCallbackGenerics> = (ctx: Context<T>, error?: unknown) => Promise<T["response"] | void> | T["response"] | void;
|
|
1459
|
+
/**
|
|
1460
|
+
* User-facing WebSocket types for YinzerFlow.
|
|
1461
|
+
*
|
|
1462
|
+
* These types define the public API surface for WebSocket support:
|
|
1463
|
+
* route handlers, the per-connection `ws` object, upgrade requests,
|
|
1464
|
+
* route options, and backpressure configuration.
|
|
1465
|
+
*/
|
|
1466
|
+
// ============================================
|
|
1467
|
+
// WebSocket Connection Object
|
|
1468
|
+
// ============================================
|
|
1469
|
+
/**
|
|
1470
|
+
* A live WebSocket connection exposed to route handlers.
|
|
1471
|
+
*
|
|
1472
|
+
* @template T - Shape of per-socket data attached during upgrade
|
|
1473
|
+
*
|
|
1474
|
+
* @example
|
|
1475
|
+
* ```typescript
|
|
1476
|
+
* app.ws<{ userId: string }>('/feed', {
|
|
1477
|
+
* message(ws, data) {
|
|
1478
|
+
* ws.send(`Echo: ${data}`);
|
|
1479
|
+
* ws.publish('updates', data as string);
|
|
1480
|
+
* console.log(ws.data.userId, ws.readyState);
|
|
1481
|
+
* }
|
|
1482
|
+
* });
|
|
1483
|
+
* ```
|
|
1484
|
+
*/
|
|
1485
|
+
interface WebSocket$1<T = unknown> {
|
|
1486
|
+
/** Send a text or binary message to this client. */
|
|
1487
|
+
send: (data: Buffer | string) => void;
|
|
1488
|
+
/** Write a pre-encoded WebSocket frame directly (used internally by pub/sub broadcast). */
|
|
1489
|
+
sendRaw: (encodedFrame: Buffer) => void;
|
|
1490
|
+
/** Initiate a graceful close handshake. */
|
|
1491
|
+
close: (code?: number, reason?: string) => void;
|
|
1492
|
+
/** Send a ping frame (payload must be ≤125 bytes per RFC 6455 §5.5). */
|
|
1493
|
+
ping: (data?: Buffer) => void;
|
|
1494
|
+
/** Subscribe this connection to a named channel for pub/sub. */
|
|
1495
|
+
subscribe: (channel: string) => void;
|
|
1496
|
+
/** Unsubscribe this connection from a named channel. */
|
|
1497
|
+
unsubscribe: (channel: string) => void;
|
|
1498
|
+
/** Publish a message to all subscribers of a channel (excluding this connection). Returns recipient count. */
|
|
1499
|
+
publish: (channel: string, data: Buffer | string) => number;
|
|
1500
|
+
/** Check if this connection is subscribed to a channel. */
|
|
1501
|
+
isSubscribed: (channel: string) => boolean;
|
|
1502
|
+
/** Per-socket data attached during the upgrade handler. */
|
|
1503
|
+
readonly data: T;
|
|
1504
|
+
/** Current connection state (use wsReadyState constants for comparison). */
|
|
1505
|
+
readonly readyState: number;
|
|
1506
|
+
/** Remote IP address of the connected client. */
|
|
1507
|
+
readonly remoteAddress: string;
|
|
1508
|
+
/** Number of bytes queued in the socket's write buffer. */
|
|
1509
|
+
readonly bufferedAmount: number;
|
|
1510
|
+
}
|
|
1511
|
+
// ============================================
|
|
1512
|
+
// Upgrade Request
|
|
1513
|
+
// ============================================
|
|
1514
|
+
/**
|
|
1515
|
+
* Parsed HTTP upgrade request passed to the `upgrade` handler.
|
|
1516
|
+
* Contains everything needed to decide whether to accept the connection
|
|
1517
|
+
* and what per-socket data to attach.
|
|
1518
|
+
*/
|
|
1519
|
+
export interface WebSocketUpgradeRequest {
|
|
1520
|
+
/** HTTP headers from the upgrade request (lowercased keys). */
|
|
1521
|
+
headers: Record<string, string>;
|
|
1522
|
+
/** URL path of the upgrade request. */
|
|
1523
|
+
path: string;
|
|
1524
|
+
/** Parsed query string parameters. */
|
|
1525
|
+
query: Record<string, string>;
|
|
1526
|
+
/** Route parameters extracted from parameterized paths (e.g., `/ws/:room` → `{ room: 'lobby' }`). */
|
|
1527
|
+
params: Record<string, string>;
|
|
1528
|
+
/** Remote IP address of the client. */
|
|
1529
|
+
remoteAddress: string;
|
|
1530
|
+
}
|
|
1531
|
+
// ============================================
|
|
1532
|
+
// Route Handlers
|
|
1533
|
+
// ============================================
|
|
1534
|
+
/**
|
|
1535
|
+
* Handler functions for a WebSocket route.
|
|
1536
|
+
*
|
|
1537
|
+
* @template T - Shape of per-socket data returned from `upgrade`
|
|
1538
|
+
*
|
|
1539
|
+
* @example
|
|
1540
|
+
* ```typescript
|
|
1541
|
+
* app.ws<{ username: string }>('/chat', {
|
|
1542
|
+
* upgrade(req) {
|
|
1543
|
+
* const user = validateToken(req.headers.authorization);
|
|
1544
|
+
* return user ? { username: user.name } : false;
|
|
1545
|
+
* },
|
|
1546
|
+
* open(ws) {
|
|
1547
|
+
* ws.subscribe('chat');
|
|
1548
|
+
* },
|
|
1549
|
+
* message(ws, data, isBinary) {
|
|
1550
|
+
* ws.publish('chat', data as string);
|
|
1551
|
+
* },
|
|
1552
|
+
* close(ws, code, reason) {
|
|
1553
|
+
* // cleanup — unsubscribe is automatic
|
|
1554
|
+
* }
|
|
1555
|
+
* });
|
|
1556
|
+
* ```
|
|
1557
|
+
*/
|
|
1558
|
+
export interface WebSocketHandlers<T = unknown> {
|
|
1559
|
+
/**
|
|
1560
|
+
* Called before the handshake completes. Return per-socket data to accept,
|
|
1561
|
+
* or `false` to reject the connection with a 403 response.
|
|
1562
|
+
* If omitted, the connection is accepted with `undefined` as data.
|
|
1563
|
+
*/
|
|
1564
|
+
upgrade?: (request: WebSocketUpgradeRequest) => Promise<T | false> | T | false;
|
|
1565
|
+
/** Called when the connection is established and ready. */
|
|
1566
|
+
open?: (ws: WebSocket$1<T>) => void;
|
|
1567
|
+
/** Called when a complete message (text or binary) is received. */
|
|
1568
|
+
message?: (ws: WebSocket$1<T>, data: Buffer | string, isBinary: boolean) => void;
|
|
1569
|
+
/** Called when the connection is closed (after close handshake or on error). */
|
|
1570
|
+
close?: (ws: WebSocket$1<T>, code: number, reason: string) => void;
|
|
1571
|
+
/** Called when a socket error occurs. */
|
|
1572
|
+
error?: (ws: WebSocket$1<T>, error: Error) => void;
|
|
1573
|
+
/** Called when a backpressured socket's write buffer drains. */
|
|
1574
|
+
drain?: (ws: WebSocket$1<T>) => void;
|
|
1575
|
+
}
|
|
1576
|
+
// ============================================
|
|
1577
|
+
// Route Options
|
|
1578
|
+
// ============================================
|
|
1579
|
+
/**
|
|
1580
|
+
* Per-route WebSocket options that override global websocket configuration.
|
|
1581
|
+
*/
|
|
1582
|
+
export interface WebSocketRouteOptions {
|
|
1583
|
+
/** Maximum incoming message payload in bytes (default: global `websocket.maxPayloadLength`). */
|
|
1584
|
+
maxPayloadLength?: number;
|
|
1585
|
+
/** Seconds of inactivity before closing the connection (default: global `websocket.idleTimeout`). 0 = no timeout. */
|
|
1586
|
+
idleTimeout?: number;
|
|
1587
|
+
/** Backpressure handling for this route. */
|
|
1588
|
+
backpressure?: WebSocketBackpressureOptions;
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Backpressure configuration for WebSocket connections.
|
|
1592
|
+
*/
|
|
1593
|
+
export interface WebSocketBackpressureOptions {
|
|
1594
|
+
/**
|
|
1595
|
+
* Strategy when the client can't keep up with outgoing data:
|
|
1596
|
+
* - `'buffer'`: Queue messages up to `limit` bytes, then close the connection (safe default)
|
|
1597
|
+
* - `'drop'`: Silently discard messages (ideal for real-time data like trading quotes)
|
|
1598
|
+
* @default 'buffer'
|
|
1599
|
+
*/
|
|
1600
|
+
strategy: "buffer" | "drop";
|
|
1601
|
+
/** Maximum buffered bytes before the connection is closed (only applies to 'buffer' strategy). @default 1048576 (1MB) */
|
|
1602
|
+
limit?: number;
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* WebSocket message hook handler signature.
|
|
1606
|
+
* Used for `wsBeforeMessage` and `wsAfterMessage` global hooks.
|
|
1607
|
+
*/
|
|
1608
|
+
export type WebSocketMessageHook = (ws: WebSocket$1, data: Buffer | string, isBinary: boolean) => Promise<void> | void;
|
|
1459
1609
|
export type InternalGlobalHookOptions = {
|
|
1460
1610
|
routesToExclude: Array<string>;
|
|
1461
1611
|
} & {
|
|
@@ -1474,11 +1624,19 @@ export interface InternalHookRegistryImpl {
|
|
|
1474
1624
|
handler: HandlerCallback;
|
|
1475
1625
|
options?: InternalGlobalHookOptions;
|
|
1476
1626
|
}>;
|
|
1627
|
+
readonly _wsBeforeMessage: Set<{
|
|
1628
|
+
handler: WebSocketMessageHook;
|
|
1629
|
+
}>;
|
|
1630
|
+
readonly _wsAfterMessage: Set<{
|
|
1631
|
+
handler: WebSocketMessageHook;
|
|
1632
|
+
}>;
|
|
1477
1633
|
_onError: HandlerCallback;
|
|
1478
1634
|
_onNotFound: HandlerCallback;
|
|
1479
1635
|
_addBeforeRoutingHooks: (handlers: Array<HandlerCallback>, options?: InternalGlobalHookOptions) => void;
|
|
1480
1636
|
_addBeforeHooks: (handlers: Array<HandlerCallback>, options?: InternalGlobalHookOptions) => void;
|
|
1481
1637
|
_addAfterHooks: (handlers: Array<HandlerCallback>, options?: InternalGlobalHookOptions) => void;
|
|
1638
|
+
_addWsBeforeMessageHooks: (handlers: Array<WebSocketMessageHook>) => void;
|
|
1639
|
+
_addWsAfterMessageHooks: (handlers: Array<WebSocketMessageHook>) => void;
|
|
1482
1640
|
_addOnError: (handler: HandlerCallback) => void;
|
|
1483
1641
|
_addOnNotFound: (handler: HandlerCallback) => void;
|
|
1484
1642
|
setLogger: (logger: {
|
|
@@ -2449,6 +2607,31 @@ export interface InternalServerOptions {
|
|
|
2449
2607
|
* @default true
|
|
2450
2608
|
*/
|
|
2451
2609
|
gracefulShutdownTimeout: TimeString | number;
|
|
2610
|
+
/**
|
|
2611
|
+
* WebSocket configuration — controls max payload, idle timeout, connection limits, and backpressure.
|
|
2612
|
+
* Only takes effect when `app.ws()` routes are registered (zero overhead otherwise).
|
|
2613
|
+
*/
|
|
2614
|
+
websocket: InternalWebSocketOptions;
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Internal WebSocket Configuration
|
|
2618
|
+
*/
|
|
2619
|
+
export interface InternalWebSocketOptions {
|
|
2620
|
+
/** Maximum incoming message payload in bytes. @default 16777216 (16MB) */
|
|
2621
|
+
maxPayloadLength: number;
|
|
2622
|
+
/** Seconds of inactivity before closing. 0 = no timeout. @default 120 */
|
|
2623
|
+
idleTimeout: number;
|
|
2624
|
+
/** Maximum concurrent WebSocket connections per IP. @default 50 */
|
|
2625
|
+
maxConnectionsPerIp: number;
|
|
2626
|
+
/** Allowed origins for upgrade requests. Empty = allow all. @default [] */
|
|
2627
|
+
allowedOrigins: Array<string>;
|
|
2628
|
+
/** Backpressure handling when clients can't keep up. */
|
|
2629
|
+
backpressure: {
|
|
2630
|
+
/** 'buffer' (queue up to limit, safe default) or 'drop' (discard, for real-time data). @default 'buffer' */
|
|
2631
|
+
strategy: "buffer" | "drop";
|
|
2632
|
+
/** Max queued bytes before closing connection (buffer strategy only). @default 1048576 (1MB) */
|
|
2633
|
+
limit: number;
|
|
2634
|
+
};
|
|
2452
2635
|
}
|
|
2453
2636
|
/**
|
|
2454
2637
|
* Internal Logging Configuration
|
|
@@ -2866,6 +3049,19 @@ export interface Setup extends HttpMethodHandlers {
|
|
|
2866
3049
|
* @see {@link HandlerCallback} for not-found handler function signature
|
|
2867
3050
|
*/
|
|
2868
3051
|
onNotFound: (handler: HandlerCallback) => void;
|
|
3052
|
+
/**
|
|
3053
|
+
* Register a WebSocket route.
|
|
3054
|
+
*
|
|
3055
|
+
* @template T - Per-socket data shape returned from the upgrade handler
|
|
3056
|
+
* @param path - URL path for WebSocket connections (e.g., '/ws', '/chat/:room')
|
|
3057
|
+
* @param handlers - Lifecycle handlers (upgrade, open, message, close, error, drain)
|
|
3058
|
+
* @param options - Per-route options that override global websocket config
|
|
3059
|
+
*/
|
|
3060
|
+
ws: <T = unknown>(path: string, handlers: WebSocketHandlers<T>, options?: WebSocketRouteOptions) => void;
|
|
3061
|
+
/** Register global hooks that run before each WebSocket message handler. */
|
|
3062
|
+
wsBeforeMessage: (handlers: Array<WebSocketMessageHook>) => void;
|
|
3063
|
+
/** Register global hooks that run after each WebSocket message handler. */
|
|
3064
|
+
wsAfterMessage: (handlers: Array<WebSocketMessageHook>) => void;
|
|
2869
3065
|
}
|
|
2870
3066
|
export type InternalSetupMethod = (path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions) => void;
|
|
2871
3067
|
export interface InternalSetupImpl extends Setup {
|
|
@@ -2894,6 +3090,12 @@ declare class HookRegistryImpl implements InternalHookRegistryImpl {
|
|
|
2894
3090
|
handler: HandlerCallback;
|
|
2895
3091
|
options?: InternalGlobalHookOptions;
|
|
2896
3092
|
}>;
|
|
3093
|
+
readonly _wsBeforeMessage: Set<{
|
|
3094
|
+
handler: WebSocketMessageHook;
|
|
3095
|
+
}>;
|
|
3096
|
+
readonly _wsAfterMessage: Set<{
|
|
3097
|
+
handler: WebSocketMessageHook;
|
|
3098
|
+
}>;
|
|
2897
3099
|
_onError: HandlerCallback;
|
|
2898
3100
|
_onNotFound: HandlerCallback;
|
|
2899
3101
|
private _logger;
|
|
@@ -2906,6 +3108,8 @@ declare class HookRegistryImpl implements InternalHookRegistryImpl {
|
|
|
2906
3108
|
_addBeforeRoutingHooks(handlers: Array<HandlerCallback>, options?: InternalGlobalHookOptions): void;
|
|
2907
3109
|
_addBeforeHooks(handlers: Array<HandlerCallback>, options?: InternalGlobalHookOptions): void;
|
|
2908
3110
|
_addAfterHooks(handlers: Array<HandlerCallback>, options?: InternalGlobalHookOptions): void;
|
|
3111
|
+
_addWsBeforeMessageHooks(handlers: Array<WebSocketMessageHook>): void;
|
|
3112
|
+
_addWsAfterMessageHooks(handlers: Array<WebSocketMessageHook>): void;
|
|
2909
3113
|
private _validateHandlersArray;
|
|
2910
3114
|
_addOnError(handler: HandlerCallback): void;
|
|
2911
3115
|
_addOnNotFound(handler: HandlerCallback): void;
|
|
@@ -3095,10 +3299,23 @@ declare const log: {
|
|
|
3095
3299
|
personality: boolean;
|
|
3096
3300
|
};
|
|
3097
3301
|
};
|
|
3302
|
+
export interface WsRouteMatch {
|
|
3303
|
+
handlers: WebSocketHandlers;
|
|
3304
|
+
options: WebSocketRouteOptions | undefined;
|
|
3305
|
+
params: Record<string, string>;
|
|
3306
|
+
}
|
|
3307
|
+
declare class WebSocketRouter {
|
|
3308
|
+
private readonly _exactRoutes;
|
|
3309
|
+
private readonly _parameterizedRoutes;
|
|
3310
|
+
_register(path: string, handlers: WebSocketHandlers, options?: WebSocketRouteOptions): void;
|
|
3311
|
+
_match(path: string): WsRouteMatch | undefined;
|
|
3312
|
+
_hasRoutes(): boolean;
|
|
3313
|
+
}
|
|
3098
3314
|
declare class SetupImpl implements InternalSetupImpl {
|
|
3099
3315
|
readonly _configuration: InternalServerOptions;
|
|
3100
3316
|
readonly _routeRegistry: RouteRegistryImpl;
|
|
3101
3317
|
readonly _hooks: HookRegistryImpl;
|
|
3318
|
+
readonly _wsRouter: WebSocketRouter;
|
|
3102
3319
|
_log: typeof log;
|
|
3103
3320
|
constructor(customConfiguration?: ServerOptions);
|
|
3104
3321
|
get(path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions): void;
|
|
@@ -3114,6 +3331,9 @@ declare class SetupImpl implements InternalSetupImpl {
|
|
|
3114
3331
|
afterAll(handlers: Array<HandlerCallback<any>>, options?: InternalGlobalHookOptions): void;
|
|
3115
3332
|
onError(handler: HandlerCallback<any>): void;
|
|
3116
3333
|
onNotFound(handler: HandlerCallback<any>): void;
|
|
3334
|
+
ws<T = unknown>(path: string, handlers: WebSocketHandlers<T>, options?: WebSocketRouteOptions): void;
|
|
3335
|
+
wsBeforeMessage(handlers: Array<WebSocketMessageHook>): void;
|
|
3336
|
+
wsAfterMessage(handlers: Array<WebSocketMessageHook>): void;
|
|
3117
3337
|
}
|
|
3118
3338
|
export declare class YinzerFlow extends SetupImpl {
|
|
3119
3339
|
private _isListening;
|
|
@@ -3123,6 +3343,9 @@ export declare class YinzerFlow extends SetupImpl {
|
|
|
3123
3343
|
private readonly _maxBufferSize;
|
|
3124
3344
|
private _accessLog?;
|
|
3125
3345
|
private _accessLogEnabled;
|
|
3346
|
+
private _wsConnections?;
|
|
3347
|
+
private _wsChannelManager?;
|
|
3348
|
+
private _wsSecurity?;
|
|
3126
3349
|
constructor(configuration?: ServerOptions);
|
|
3127
3350
|
get log(): typeof YinzerFlow._log;
|
|
3128
3351
|
private _configureLogging;
|
|
@@ -3134,6 +3357,17 @@ export declare class YinzerFlow extends SetupImpl {
|
|
|
3134
3357
|
private _looksLikeHttp;
|
|
3135
3358
|
private _parseContentLength;
|
|
3136
3359
|
private _handleConnection;
|
|
3360
|
+
private _handleConnectionData;
|
|
3361
|
+
private _handleHeaderPhase;
|
|
3362
|
+
private _handleWebSocketUpgrade;
|
|
3363
|
+
private _handleWebSocketUpgradeAsync;
|
|
3364
|
+
private _setupWsConnection;
|
|
3365
|
+
private _mergeWsConnectionOptions;
|
|
3366
|
+
private _wrapWsHandlers;
|
|
3367
|
+
private _parseHeadersMap;
|
|
3368
|
+
private _sendHttpError;
|
|
3369
|
+
publish(channel: string, data: Buffer | string): number;
|
|
3370
|
+
subscriberCount(channel: string): number;
|
|
3137
3371
|
listen(): Promise<void>;
|
|
3138
3372
|
close(): Promise<void>;
|
|
3139
3373
|
status(): {
|
|
@@ -3154,5 +3388,40 @@ export declare const colors: {
|
|
|
3154
3388
|
readonly magenta: "\u001B[95m";
|
|
3155
3389
|
readonly gray: "\u001B[90m";
|
|
3156
3390
|
};
|
|
3391
|
+
export declare const wsOpcode: {
|
|
3392
|
+
readonly continuation: 0;
|
|
3393
|
+
readonly text: 1;
|
|
3394
|
+
readonly binary: 2;
|
|
3395
|
+
readonly close: 8;
|
|
3396
|
+
readonly ping: 9;
|
|
3397
|
+
readonly pong: 10;
|
|
3398
|
+
};
|
|
3399
|
+
export declare const wsCloseCode: {
|
|
3400
|
+
readonly normal: 1000;
|
|
3401
|
+
readonly goingAway: 1001;
|
|
3402
|
+
readonly protocolError: 1002;
|
|
3403
|
+
readonly unsupported: 1003;
|
|
3404
|
+
readonly noStatus: 1005;
|
|
3405
|
+
readonly abnormal: 1006;
|
|
3406
|
+
readonly invalidPayload: 1007;
|
|
3407
|
+
readonly policyViolation: 1008;
|
|
3408
|
+
readonly tooLarge: 1009;
|
|
3409
|
+
readonly missingExtension: 1010;
|
|
3410
|
+
readonly internalError: 1011;
|
|
3411
|
+
};
|
|
3412
|
+
export declare const wsReadyState: {
|
|
3413
|
+
readonly connecting: 0;
|
|
3414
|
+
readonly open: 1;
|
|
3415
|
+
readonly closing: 2;
|
|
3416
|
+
readonly closed: 3;
|
|
3417
|
+
};
|
|
3418
|
+
export declare const wsBackpressureStrategy: {
|
|
3419
|
+
readonly buffer: "buffer";
|
|
3420
|
+
readonly drop: "drop";
|
|
3421
|
+
};
|
|
3422
|
+
|
|
3423
|
+
export {
|
|
3424
|
+
WebSocket$1 as WebSocket,
|
|
3425
|
+
};
|
|
3157
3426
|
|
|
3158
3427
|
export {};
|