5.8 KiB
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
function createWebSocketClient(options: WebSocketClientOptions): WebSocketClient
interface WebSocketClient {
readonly id: string
readonly url: string
readonly state: ConnectionState
connect(signal?: AbortSignal): Promise<void>
disconnect(code?: number, reason?: string): void
send(message: WebSocketMessage, signal?: AbortSignal): Promise<void>
subscribe<T>(channel: string, handler: MessageHandler<T>): () => void
on(event: ConnectionEvent, handler: (data: unknown) => void): () => void
}
Types
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'closed'
type ConnectionEvent = 'connect' | 'disconnect' | 'reconnect' | 'error' | 'message'
type MessageHandler<T> = (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
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
type NetworkWebsocketsErrorCode =
| 'WS_CONNECTION_FAILED'
| 'WS_SEND_FAILED'
| 'WS_QUEUE_FULL'
| 'WS_PROTOCOL_ERROR'
| 'WS_TIMEOUT'
| 'WS_ABORT'
function registerNetworkWebsocketsErrorCodes(): void
Genesis additions
- Structured
NetworkWebsocketsErrorextendingSuaveplanErrorwith stableWS.*codes - Zod schemas for
WebSocketClientOptions,WebSocketMessage,ReconnectOptions,HeartbeatOptions - Injectable
WebSocketAdapter— decouple from native WebSocket for testing - Multiplexed channels —
subscribe(channel, handler)routes messages bymessage.channel - Backpressure queue — bounded send queue;
WS_QUEUE_FULLwhenmaxQueueSizeexceeded - Heartbeat/ping-pong — injectable interval via
Clock - Reconnect via
@suaveplan/retrywith configurable backoff - OTel instrumentation — span
ws.connect, counterws.messages_sent_total,ws.messages_received_total, histogramws.message_size_bytes
File layout
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.