203 lines
5.8 KiB
Markdown
203 lines
5.8 KiB
Markdown
# 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<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
|
|
|
|
```ts
|
|
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
|
|
|
|
```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.
|