Initial Commit
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
# OpenSpec Change: enterprise-websocket-gateway
|
||||
|
||||
- **Change ID**: enterprise-websocket-gateway
|
||||
- **Package**: `@suaveplan/websocket-gateway`
|
||||
- **Category**: api/framework
|
||||
- **Target path**: `api/framework/websocket-gateway`
|
||||
- **Status**: SUPERSEDED-BY-GENESIS-WAVE
|
||||
- **Date**: 2026-04-24
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/websocket-gateway` on the genesis branch from scratch at enterprise quality. The package provides a WebSocket gateway layer for the Suaveplan API tier: connection lifecycle management (`connect` / `disconnect` / `reconnect`), message routing (topic-based publish/subscribe + RPC-style request/reply), authentication via JWT (injected `@suaveplan/jwt-tenant` verifier), per-connection rate limiting, backpressure via bounded send queues, heartbeat/ping-pong with configurable intervals, and a Hono WebSocket upgrade adapter. Backed by `@suaveplan/events` for internal message fan-out. Genesis adds structured errors, Zod message schemas, OTel instrumentation, and a benchmark suite.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
Package does not exist on genesis — fresh build. No reference enterprise spec file found; design based on `@suaveplan/api-gateway` pattern and the Hono WebSocket API.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/error` (`workspace:^`) | `WebSocketGatewayError` hierarchy |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `EventBusLike` |
|
||||
| `@suaveplan/logger` (`workspace:^`) | Per-connection structured logging |
|
||||
| `@suaveplan/events` (`workspace:^`) | Internal topic fan-out |
|
||||
| `@suaveplan/rate-limiter` (`workspace:^`) | Per-connection message rate limiting |
|
||||
| `@suaveplan/api` (`workspace:^`) | Hono app type |
|
||||
| `hono` (`catalog:server`) | `upgradeWebSocket` helper |
|
||||
| `zod` (`catalog:forms`) | Message schema validation |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Spans + metrics |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
---
|
||||
|
||||
## Public API surface
|
||||
|
||||
### Core
|
||||
|
||||
```ts
|
||||
function createWebSocketGateway(options: WebSocketGatewayOptions): WebSocketGateway
|
||||
|
||||
interface WebSocketGateway {
|
||||
handleUpgrade(app: HonoApp, path: string): void
|
||||
publish(topic: string, message: unknown): void
|
||||
broadcast(message: unknown): void
|
||||
getConnection(id: string): WsConnection | undefined
|
||||
connectionCount(): number
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
interface WsConnection {
|
||||
id: string
|
||||
userId?: string
|
||||
tenantId?: string
|
||||
subscribe(topic: string): void
|
||||
unsubscribe(topic: string): void
|
||||
send(message: unknown): void
|
||||
close(): void
|
||||
}
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
```ts
|
||||
interface WebSocketGatewayOptions {
|
||||
maxConnections?: number
|
||||
sendQueueSize?: number
|
||||
heartbeatIntervalMs?: number
|
||||
pingTimeoutMs?: number
|
||||
rateLimiter?: RateLimiterLike
|
||||
authenticate?: (token: string) => Promise<WsAuthResult>
|
||||
logger?: LoggerLike
|
||||
eventBus?: EventBusLike
|
||||
}
|
||||
|
||||
interface WsAuthResult { userId: string; tenantId: string; claims: Record<string, unknown> }
|
||||
type WsMessageHandler = (conn: WsConnection, message: unknown) => void | Promise<void>
|
||||
```
|
||||
|
||||
### Error codes
|
||||
|
||||
```ts
|
||||
type WsGatewayErrorCode =
|
||||
| "WS_MAX_CONNECTIONS"
|
||||
| "WS_AUTH_FAILED"
|
||||
| "WS_RATE_LIMITED"
|
||||
| "WS_QUEUE_FULL"
|
||||
| "WS_INVALID_MESSAGE"
|
||||
| "WS_CONNECTION_NOT_FOUND"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Genesis additions
|
||||
|
||||
1. **Zod message envelope schema** — `{ type: string, topic?: string, payload: unknown, id?: string }` validated on every inbound message.
|
||||
2. **OTel** — `ws.connections_active` gauge, `ws.messages_received_total` / `ws.messages_sent_total` counters, `ws.message_processing_duration_ms` histogram.
|
||||
3. **Heartbeat** — ping frame sent every `heartbeatIntervalMs` (default 30 s); connection closed if pong not received within `pingTimeoutMs` (default 10 s).
|
||||
4. **Backpressure** — outbound send queue per connection; drops oldest messages when full (with counter `ws.queue_drops_total`).
|
||||
5. **Topic-based pub/sub** — `connection.subscribe(topic)` / `gateway.publish(topic, msg)` fan-out.
|
||||
6. **Benchmark suite** — measure message throughput and concurrent connection overhead.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
api/framework/websocket-gateway/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
constants/index.ts
|
||||
types/
|
||||
common.ts
|
||||
common.md
|
||||
errors/
|
||||
ws-errors.ts
|
||||
ws-errors.md
|
||||
ws-errors.test.ts
|
||||
index.ts
|
||||
core/
|
||||
gateway/
|
||||
gateway.ts
|
||||
gateway.md
|
||||
gateway.types.ts
|
||||
gateway.test.ts
|
||||
index.ts
|
||||
connection/
|
||||
connection.ts
|
||||
connection.md
|
||||
connection.test.ts
|
||||
index.ts
|
||||
router/
|
||||
ws-router.ts
|
||||
ws-router.md
|
||||
ws-router.test.ts
|
||||
index.ts
|
||||
heartbeat/
|
||||
heartbeat.ts
|
||||
heartbeat.md
|
||||
heartbeat.test.ts
|
||||
index.ts
|
||||
adapters/
|
||||
hono-ws-adapter/
|
||||
hono-ws-adapter.ts
|
||||
hono-ws-adapter.md
|
||||
hono-ws-adapter.test.ts
|
||||
index.ts
|
||||
__benchmarks__/
|
||||
ws-gateway.bench.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
Downstream: any real-time feature (notifications, live collaboration, chat). Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/logger`, `@suaveplan/events`, `@suaveplan/rate-limiter`, `@suaveplan/api`.
|
||||
@@ -0,0 +1,103 @@
|
||||
# Tasks: enterprise-websocket-gateway
|
||||
|
||||
**Change ID**: enterprise-websocket-gateway
|
||||
**Package**: `@suaveplan/websocket-gateway`
|
||||
**Target path**: `api/framework/websocket-gateway`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/logger`, `@suaveplan/events`, `@suaveplan/rate-limiter`, `@suaveplan/api`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] Create `api/framework/websocket-gateway/` with all subdirs per layout.
|
||||
- [x] `package.json` v0.0.0, `"sideEffects": false`, subpath exports.
|
||||
- [x] `tsconfig.json` extending `library-node.json`.
|
||||
- [x] `vitest.config.ts` via `createTestConfig()`.
|
||||
- [x] Scripts: `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`, `bench`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Types and errors
|
||||
|
||||
- [x] `src/types/common.ts` — all interfaces and types listed in proposal.
|
||||
- [x] `src/types/common.md` (≥200 words, both examples).
|
||||
- [x] `src/errors/ws-errors.ts` — 6-code union, metadata, `registerWsGatewayErrorCodes()`.
|
||||
- [x] `ws-errors.test.ts` and `ws-errors.md`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Connection
|
||||
|
||||
- [x] `WsConnection` class: `id` (UUID), `userId`, `tenantId`, `topics: Set<string>`, outbound `sendQueue` (bounded ring buffer of size `sendQueueSize`).
|
||||
- [x] `subscribe(topic)` / `unsubscribe(topic)`.
|
||||
- [x] `send(message)` — enqueue; drop oldest + increment `ws.queue_drops_total` if full.
|
||||
- [x] `close()` — flush queue, call `ws.close()`.
|
||||
- [x] `connection.test.ts`: subscribe/unsubscribe, send queued, queue-full drops oldest, close flushes.
|
||||
- [x] `connection.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Heartbeat
|
||||
|
||||
- [x] `HeartbeatManager`: sends ping every `heartbeatIntervalMs`; schedules `pingTimeoutMs` deadline per connection; on pong received, cancel deadline; on deadline exceeded, close connection.
|
||||
- [x] Injectable `Clock` for testability.
|
||||
- [x] `heartbeat.test.ts`: pong received cancels deadline, deadline exceeded closes connection.
|
||||
- [x] `heartbeat.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. WS Router
|
||||
|
||||
- [x] `WsRouter`: registers `on(type: string, handler: WsMessageHandler)` handlers; routes inbound messages by `message.type`.
|
||||
- [x] `WsMessageEnvelopeSchema` (Zod) validates every inbound message; invalid → `WsGatewayError(WS_INVALID_MESSAGE)`.
|
||||
- [x] `ws-router.test.ts`: known type routed, unknown type → error, invalid envelope → error.
|
||||
- [x] `ws-router.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Gateway
|
||||
|
||||
- [x] `createWebSocketGateway(options)`:
|
||||
- Maintains `connections: Map<string, WsConnection>`.
|
||||
- `handleUpgrade(app, path)` — registers Hono WebSocket route.
|
||||
- On connect: auth via `options.authenticate`; max-connections check; create `WsConnection`; start heartbeat; emit `ws:connected` event.
|
||||
- On message: rate-limit check; validate + route via `WsRouter`.
|
||||
- On disconnect: remove from map; stop heartbeat; emit `ws:disconnected` event.
|
||||
- `publish(topic, message)` — fan out to all connections subscribed to topic.
|
||||
- `broadcast(message)` — fan out to all connections.
|
||||
- `close()` — close all connections gracefully.
|
||||
- [x] OTel: `ws.connections_active` gauge updated on connect/disconnect; counters + histograms on message receive/send.
|
||||
- [x] `gateway.test.ts` (≤500 lines): connect + auth, auth failure closes ws, max-connections rejects, publish to topic, broadcast, rate-limited message dropped.
|
||||
- [x] `gateway.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Hono adapter
|
||||
|
||||
- [x] `createHonoWsAdapter(gateway: WebSocketGateway): (app: HonoApp, path: string) => void` — wraps Hono `upgradeWebSocket`; bridges native ws events to gateway lifecycle.
|
||||
- [x] `hono-ws-adapter.test.ts`: upgrade handled, message forwarded.
|
||||
- [x] `hono-ws-adapter.md`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Benchmarks (`src/__benchmarks__/ws-gateway.bench.ts`)
|
||||
|
||||
- [x] `ws_message_routing`: route 10 000 messages through `WsRouter` with 5 registered handlers.
|
||||
- [x] `ws_publish_100_connections`: `publish()` to a topic with 100 subscribers.
|
||||
|
||||
---
|
||||
|
||||
## 9. Integration tests (`tests/integration.test.ts`)
|
||||
|
||||
- [x] Start a real Hono server + WebSocket gateway; connect N clients; send messages; assert routing + heartbeat + disconnect.
|
||||
|
||||
---
|
||||
|
||||
## 10. Docs and publish
|
||||
|
||||
- [x] All co-located `.md` files (≥200 words, both examples, API reference).
|
||||
- [x] Root `README.md`.
|
||||
- [x] `bunx turbo lint typecheck test --filter=@suaveplan/websocket-gateway` — green.
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/websocket-gateway` — 100%.
|
||||
- [x] `bun scripts/publish.ts --package @suaveplan/websocket-gateway`.
|
||||
- [x] Archive → `openspec/archive/2026-04-24-enterprise-websocket-gateway/`.
|
||||
Reference in New Issue
Block a user