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/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 {};