# OpenSpec Change: enterprise-network-websockets - **Change ID**: enterprise-network-websockets - **Package**: `@suaveplan/network-websockets` - **Category**: universal/network - **Target path**: `universal/network/network-websockets` - **Status**: SUPERSEDED-BY-GENESIS-WAVE - **Date**: 2026-04-24 --- ## Summary Deliver `@suaveplan/network-websockets` on the genesis branch from scratch at enterprise quality. The package provides WebSocket protocol utilities: typed message framing, connection lifecycle management (connect/disconnect/reconnect), heartbeat/ping-pong, backpressure-aware message queuing, multiplexing over a single WebSocket connection, and injectable transport adapters. Genesis adds structured errors via `@suaveplan/error`, Zod schemas for all message and option types, OTel instrumentation, `AbortSignal` on async operations, retry/reconnect via `@suaveplan/retry`, event-bus integration for connection state changes, and property-based tests for message framing invariants. --- ## Current-state audit (genesis) The package does not exist on genesis yet — this is a fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Base class for `NetworkWebsocketsError`; stable error codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `EventBusLike` | | `@suaveplan/events` (`workspace:^`) | Event bus for connection state changes | | `@suaveplan/logger` (`workspace:^`) | Structured logging | | `@suaveplan/retry` (`workspace:^`) | Reconnect with exponential backoff | | `@suaveplan/serialize` (`workspace:^`) | Message serialization (JSON/binary) | | `zod` (`catalog:forms`) | Zod schemas for all public types | | `@opentelemetry/api` (`catalog:observability`) | Spans + counters on connect/message/disconnect | | `tslib` (`catalog:build`) | Runtime helpers | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`). --- ## Public API surface ### Core ```ts function createWebSocketClient(options: WebSocketClientOptions): WebSocketClient interface WebSocketClient { readonly id: string readonly url: string readonly state: ConnectionState connect(signal?: AbortSignal): Promise disconnect(code?: number, reason?: string): void send(message: WebSocketMessage, signal?: AbortSignal): Promise subscribe(channel: string, handler: MessageHandler): () => void on(event: ConnectionEvent, handler: (data: unknown) => void): () => void } ``` ### Types ```ts type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'closed' type ConnectionEvent = 'connect' | 'disconnect' | 'reconnect' | 'error' | 'message' type MessageHandler = (message: T, meta: MessageMeta) => void interface WebSocketMessage { channel?: string type: string payload: unknown id?: string correlationId?: string } interface MessageMeta { receivedAt: number channel?: string type: string } interface WebSocketClientOptions { url: string protocols?: string[] reconnect?: ReconnectOptions heartbeat?: HeartbeatOptions maxQueueSize?: number serialization?: 'json' | 'msgpack' logger?: LoggerLike clock?: Clock eventBus?: EventBusLike adapter?: WebSocketAdapter } interface ReconnectOptions { enabled?: boolean maxAttempts?: number initialDelayMs?: number maxDelayMs?: number } interface HeartbeatOptions { enabled?: boolean intervalMs?: number timeoutMs?: number } ``` ### Transport adapter ```ts interface WebSocketAdapter { connect(url: string, protocols?: string[]): WebSocketHandle } interface WebSocketHandle { readonly readyState: number send(data: string | Uint8Array): void close(code?: number, reason?: string): void onopen: (() => void) | null onclose: ((code: number, reason: string) => void) | null onmessage: ((data: string | Uint8Array) => void) | null onerror: ((error: Error) => void) | null } ``` ### Error codes ```ts type NetworkWebsocketsErrorCode = | 'WS_CONNECTION_FAILED' | 'WS_SEND_FAILED' | 'WS_QUEUE_FULL' | 'WS_PROTOCOL_ERROR' | 'WS_TIMEOUT' | 'WS_ABORT' function registerNetworkWebsocketsErrorCodes(): void ``` --- ## Genesis additions 1. **Structured `NetworkWebsocketsError`** extending `SuaveplanError` with stable `WS.*` codes 2. **Zod schemas** for `WebSocketClientOptions`, `WebSocketMessage`, `ReconnectOptions`, `HeartbeatOptions` 3. **Injectable `WebSocketAdapter`** — decouple from native WebSocket for testing 4. **Multiplexed channels** — `subscribe(channel, handler)` routes messages by `message.channel` 5. **Backpressure queue** — bounded send queue; `WS_QUEUE_FULL` when `maxQueueSize` exceeded 6. **Heartbeat/ping-pong** — injectable interval via `Clock` 7. **Reconnect via `@suaveplan/retry`** with configurable backoff 8. **OTel instrumentation** — span `ws.connect`, counter `ws.messages_sent_total`, `ws.messages_received_total`, histogram `ws.message_size_bytes` --- ## File layout ```text universal/network/network-websockets/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts types/ common.ts common.md core/ client/ client.ts client.md client.types.ts client.test.ts index.ts queue/ queue.ts queue.md queue.test.ts index.ts heartbeat/ heartbeat.ts heartbeat.md heartbeat.test.ts index.ts adapters/ native-adapter.ts native-adapter.md native-adapter.test.ts index.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/logger`, `@suaveplan/retry`, `@suaveplan/serialize` must be published.