Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
# OpenSpec Change: enterprise-network
- **Change ID**: enterprise-network
- **Package**: `@suaveplan/network`
- **Category**: universal
- **Target path**: `universal/network/network`
- **Status**: PROPOSED
- **Date**: 2026-04-24
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/network.md`
---
## Summary
Deliver `@suaveplan/network` on the genesis branch from scratch at enterprise quality. The package provides core networking utilities for universal TypeScript: a typed `HttpClient` with middleware pipeline, `WebSocketClient` with auto-reconnect and backpressure, URL construction/parsing helpers, fetch timeout and cancellation wrappers, request/response interceptors, and network-quality probing. Injectable `fetch` and `WebSocket` constructors for test-time substitution. No hard browser/Node platform deps.
---
## Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data. The main-branch package had 85.40% coverage.
---
## Available workspace deps on genesis
| Package | Role |
|---|---|
| `@suaveplan/error` (`workspace:^`) | Base class for `NetworkError`; `NETWORK.*` codes |
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` |
| `@suaveplan/retry` (`workspace:^`) | Exponential backoff + circuit breaker on HTTP/WS paths |
| `@suaveplan/serialize` (`workspace:^`) | JSON request/response body serialisation |
| `zod` (`catalog:forms`) | Zod schemas for all public option types |
| `@opentelemetry/api` (`catalog:observability`) | Spans + counters on every request |
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`).
---
## Public API
### HttpClient
```ts
class NetworkError extends SuaveplanError
type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'
interface HttpRequest {
url: string
method?: HttpMethod // default: 'GET'
headers?: Record<string, string>
body?: unknown // serialised via @suaveplan/serialize
timeoutMs?: number // default: 30000
signal?: AbortSignal
}
interface HttpResponse<T = unknown> {
status: number
statusText: string
headers: Record<string, string>
body: T
url: string
durationMs: number
}
type HttpMiddleware = (request: HttpRequest, next: (req: HttpRequest) => Promise<HttpResponse>) => Promise<HttpResponse>
interface HttpClientOptions {
baseUrl?: string
defaultHeaders?: Record<string, string>
defaultTimeoutMs?: number
middleware?: HttpMiddleware[]
fetch?: typeof globalThis.fetch
logger?: LoggerLike
clock?: Clock
}
class HttpClient {
constructor(options?: HttpClientOptions)
request<T = unknown>(req: HttpRequest): Promise<HttpResponse<T>>
get<T = unknown>(path: string, options?: Omit<HttpRequest, 'url' | 'method'>): Promise<HttpResponse<T>>
post<T = unknown>(path: string, body: unknown, options?: Omit<HttpRequest, 'url' | 'method' | 'body'>): Promise<HttpResponse<T>>
put<T = unknown>(path: string, body: unknown, options?: Omit<HttpRequest, 'url' | 'method' | 'body'>): Promise<HttpResponse<T>>
patch<T = unknown>(path: string, body: unknown, options?: Omit<HttpRequest, 'url' | 'method' | 'body'>): Promise<HttpResponse<T>>
delete<T = unknown>(path: string, options?: Omit<HttpRequest, 'url' | 'method'>): Promise<HttpResponse<T>>
use(middleware: HttpMiddleware): this
}
function createHttpClient(options?: HttpClientOptions): HttpClient
```
### WebSocketClient
```ts
type WsReadyState = 'connecting' | 'open' | 'closing' | 'closed'
type WsMessage = string | ArrayBuffer | Uint8Array
interface WebSocketClientOptions {
url: string
protocols?: string[]
reconnect?: boolean // default: true
reconnectDelayMs?: number // base delay; uses @suaveplan/retry exponential backoff
maxReconnectAttempts?: number // default: 10
pingIntervalMs?: number // default: 30000; 0 = disabled
timeoutMs?: number // connect timeout; default: 10000
WebSocket?: typeof WebSocket // injectable; defaults to globalThis.WebSocket
logger?: LoggerLike
clock?: Clock
}
class WebSocketClient {
constructor(options: WebSocketClientOptions)
connect(signal?: AbortSignal): Promise<void>
disconnect(): void
send(message: WsMessage): void
on(event: 'message', listener: (msg: WsMessage) => void): () => void
on(event: 'open' | 'close' | 'error', listener: () => void): () => void
get readyState(): WsReadyState
get reconnectCount(): number
}
function createWebSocketClient(options: WebSocketClientOptions): WebSocketClient
```
### URL utilities
```ts
function buildUrl(base: string, path?: string, params?: Record<string, string | number | boolean>): string
function parseQueryString(qs: string): Record<string, string>
function serializeQueryString(params: Record<string, string | number | boolean>): string
function isAbsoluteUrl(url: string): boolean
function joinPaths(...segments: string[]): string
```
### Error codes
```ts
type NetworkErrorCode =
| 'NETWORK_REQUEST_FAILED'
| 'NETWORK_TIMEOUT'
| 'NETWORK_ABORT'
| 'NETWORK_BAD_STATUS' // 4xx/5xx
| 'NETWORK_PARSE_FAILED'
| 'NETWORK_WS_CONNECT_FAILED'
| 'NETWORK_WS_SEND_FAILED'
| 'NETWORK_WS_MAX_RECONNECTS'
| 'NETWORK_INVALID_URL'
const NETWORK_ERROR_METADATA: Readonly<Record<NetworkErrorCode, CodeMetadata>>
function registerNetworkErrorCodes(): void
```
### Zod schemas
```ts
HttpRequestSchema
HttpClientOptionsSchema
WebSocketClientOptionsSchema
```
### Built-in middleware
```ts
const loggingMiddleware: (logger: LoggerLike) => HttpMiddleware
const retryMiddleware: (options?: RetryOptions) => HttpMiddleware // @suaveplan/retry
const authMiddleware: (token: string | (() => string)) => HttpMiddleware
const rateLimitMiddleware: (options: { maxRpm: number; clock?: Clock }) => HttpMiddleware
```
---
## New additions for genesis
1. **Middleware pipeline** — `use(middleware)` appends to ordered chain; each middleware gets `(request, next)` and can modify request/response, short-circuit, or retry.
2. **Injectable `fetch`** — `HttpClientOptions.fetch` defaults to `globalThis.fetch`; swap to a stub in tests without `vi.mock`.
3. **Injectable `WebSocket`** — same pattern; `WebSocketClientOptions.WebSocket` defaults to `globalThis.WebSocket`.
4. **Retry wiring** — `retryMiddleware` uses `@suaveplan/retry` with circuit breaker; configurable per `HttpClient` instance.
5. **WS auto-reconnect** — exponential backoff via `@suaveplan/retry`; configurable max attempts; ping/pong keepalive.
6. **OTel instrumentation** — span `http.request` (attrs: `http.method`, `http.url`, `http.status_code`), counter `http_request_total`, histogram `http_request_duration_ms`; counter `ws.reconnect_total`.
7. **Property-based tests** — `buildUrl` round-trip with query params, `parseQueryString(serializeQueryString(params)) == params` for any param map.
8. **Benchmark suite** — `buildUrl` with 10 params, middleware chain dispatch (5 middlewares, no-op fetch stub).
---
## File layout
```text
universal/network/network/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/
index.ts
types/
common.ts
common.md
core/
http-client/
http-client.ts
http-client.md
http-client.types.ts
http-client.test.ts
index.ts
websocket-client/
websocket-client.ts
websocket-client.md
websocket-client.types.ts
websocket-client.test.ts
index.ts
middleware/
logging/
logging.ts
logging.md
logging.test.ts
index.ts
retry/
retry.ts
retry.md
retry.test.ts
index.ts
auth/
auth.ts
auth.md
auth.test.ts
index.ts
rate-limit/
rate-limit.ts
rate-limit.md
rate-limit.test.ts
index.ts
utils/
url.ts
url.md
url.test.ts
__benchmarks__/
network.bench.ts
tests/
integration.test.ts
property.test.ts
```
---
## Impact
**Downstream**: `@suaveplan/service-discovery`, `@suaveplan/webhook-client` (may refactor to consume), any server/API packages.
**Breaking changes**: none — fresh build.
**Prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/retry`, `@suaveplan/serialize` published to Verdaccio.

View File

@@ -0,0 +1,99 @@
# Tasks: enterprise-network
**Change ID**: enterprise-network
**Package**: `@suaveplan/network`
**Target path**: `universal/network/network`
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/retry`, `@suaveplan/serialize`
---
## 1. Scaffold
- [ ] Create directory `universal/network/network/` with `src/`, `tests/`, `src/__benchmarks__/`, and all feature subdirectories per the layout in `proposal.md`.
- [ ] Write `package.json` at version `0.1.0` with `"type": "module"`, `"sideEffects": false`, full `exports` map, deps: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/retry workspace:^`, `@suaveplan/serialize workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`.
- [ ] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`.
- [ ] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config`.
- [ ] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check` scripts.
---
## 2. Types + errors
- [ ] Write `src/types/common.ts` — `HttpMethod`, `HttpRequest`, `HttpResponse`, `HttpMiddleware`, `HttpClientOptions`, `WsReadyState`, `WsMessage`, `WebSocketClientOptions`.
- [ ] Write `src/types/common.md`.
- [ ] Write `src/error-codes.ts` — `NetworkErrorCode`, `NETWORK_ERROR_METADATA`, `registerNetworkErrorCodes()`.
- [ ] Write Zod schemas: `HttpRequestSchema`, `HttpClientOptionsSchema`, `WebSocketClientOptionsSchema`.
---
## 3. URL utilities
- [ ] Implement `buildUrl`, `parseQueryString`, `serializeQueryString`, `isAbsoluteUrl`, `joinPaths` in `src/utils/url.ts`.
- [ ] Write `url.test.ts` (≤500 lines): each function, edge cases (empty params, absolute URLs, path joining).
- [ ] Write `url.md`.
---
## 4. Middleware
### Logging middleware
- [ ] Implement `loggingMiddleware(logger): HttpMiddleware` — logs method, URL, status, duration.
- [ ] Write `logging.test.ts` and `logging.md`.
### Retry middleware
- [ ] Implement `retryMiddleware(options?): HttpMiddleware` — wraps `@suaveplan/retry`; retries on 429/5xx and network errors; uses exponential backoff.
- [ ] Write `retry.test.ts` and `retry.md`.
### Auth middleware
- [ ] Implement `authMiddleware(token): HttpMiddleware` — adds `Authorization: Bearer <token>` header; `token` may be a function for dynamic tokens.
- [ ] Write `auth.test.ts` and `auth.md`.
### Rate-limit middleware
- [ ] Implement `rateLimitMiddleware(options): HttpMiddleware` — sliding-window request rate limiting using injectable `Clock`; queues excess requests.
- [ ] Write `rate-limit.test.ts` and `rate-limit.md`.
---
## 5. HttpClient
- [ ] Implement `HttpClient` — middleware chain dispatch, injectable `fetch`, body serialisation via `@suaveplan/serialize`, timeout via `AbortSignal.timeout`-compatible wrapper, response body deserialisation, `use()`.
- [ ] Wire OTel: span `http.request`, counter `http_request_total`, histogram `http_request_duration_ms`.
- [ ] Write `http-client.test.ts` (≤500 lines): GET/POST/PUT/DELETE with stub fetch, timeout, abort, 4xx throws `NETWORK_BAD_STATUS`, middleware chain order.
- [ ] Write `http-client.md`.
---
## 6. WebSocketClient
- [ ] Implement `WebSocketClient` — injectable `WebSocket`, auto-reconnect with `@suaveplan/retry` backoff, ping/pong keepalive using injectable `Clock`, `on()` event listener registry, `send()`, `connect()`, `disconnect()`.
- [ ] Wire OTel: counter `ws.reconnect_total`.
- [ ] Write `websocket-client.test.ts` (≤500 lines): connect, message, reconnect, max reconnects exceeded throws, abort on disconnect.
- [ ] Write `websocket-client.md`.
---
## 7. Benchmarks
- [ ] Write `src/__benchmarks__/network.bench.ts` — `buildUrl` with 10 params, middleware chain dispatch 5 middlewares no-op stub fetch.
---
## 8. Tests
- [ ] Write `tests/integration.test.ts` — `HttpClient` + middleware stack end-to-end with stub fetch.
- [ ] Write `tests/property.test.ts` (fast-check) — `buildUrl` round-trip, `parseQueryString(serializeQueryString(p)) deep equals p`.
---
## 9. Docs + verify + publish
- [ ] All co-located `.md` files (≥200 words each).
- [ ] Write `README.md` at package root.
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/network` — green.
- [ ] `bunx turbo test:coverage --filter=@suaveplan/network` — 100%.
- [ ] `bun scripts/publish.ts --package @suaveplan/network`.
- [ ] Archive to `openspec/archive/2026-04-24-enterprise-network/`.