Skip to content

[Feature] Typed-protocol WebSocket server-side wrapper #187

Description

@pathosDev

Size / Priority

Rationale

WebSocketActor (src/io/broker/) is client-side — connects out to a WS server. Server-side WebSocket support exists but is backend-specific (Fastify's @fastify/websocket, Hono's WS, Express's express-ws) — no uniform actor-ts surface.

A typed WebSocket route + handler abstraction:

  • Lets users define a typed bidirectional protocol (In from client, Out to client).
  • Works across all three HTTP backends.
  • Integrates with route DSL (path matching, auth middleware).

Design sketch

// src/http/WebSocket.ts (new)

export interface WebSocketSession<In, Out> {
  readonly clientHost: string;
  send(msg: Out): void;
  close(code?: number, reason?: string): void;
  readonly closed: Promise<void>;
}

export interface WebSocketHandler<In, Out> {
  onOpen?(session: WebSocketSession<In, Out>): void | Promise<void>;
  onMessage(session: WebSocketSession<In, Out>, msg: In): void | Promise<void>;
  onClose?(session: WebSocketSession<In, Out>, code: number, reason: string): void | Promise<void>;
  onError?(session: WebSocketSession<In, Out>, err: Error): void;
}

/** Route DSL integration. */
export function webSocketRoute<In = unknown, Out = unknown>(
  pattern: string,
  handler: WebSocketHandler<In, Out>,
  options?: {
    readonly serializer?: 'json' | 'cbor';
    readonly maxMessageBytes?: number;
  },
): Route;

Backend integration via the existing HttpServerBackend interface — extend with an optional registerWebSocketRoute(pattern, handler, options) method.

Out of scope / non-goals

  • Subprotocol negotiation — phase 1: pass-through; user-defined.
  • Compression — defer.
  • Streaming binary — phase 1: discrete messages; binary supported as Uint8Array.

Open design questions

  1. Serializer: shared with HTTP marshalling? Recommend: yes (same JSON/CBOR codecs).
  2. Maximum-message size enforcement: backend-level (Fastify's maxPayload) vs framework-level. Recommend: framework-level (consistent across backends).
  3. Session lifecycle as actor: should each WS session spawn its own actor? Powerful but heavy. Recommend: handler-based (sketch) for simplicity; user can spawn per-session actors if needed.

Test plan

  1. WS server with echo handler; client connects, sends "hi", receives "hi".
  2. Typed protocol — In/Out typed; type errors caught.
  3. Close from server side.
  4. Close from client side (onClose fires).
  5. Backend parity — same handler works on Fastify, Hono, Express.
  6. Max-message-size enforced.
  7. JSON / CBOR serialization.

Acceptance criteria

  • WebSocketHandler<In, Out> + WebSocketSession<In, Out> types.
  • webSocketRoute(pattern, handler, options?).
  • All three HTTP backends support it.
  • Documentation: "Typed server-side WebSocket".
  • Test suite (7 cases).
  • CHANGELOG entry.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: lowNice-to-have / niche / demand-driven

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions