first commit
This commit is contained in:
65
docs/api-stack/api/api-sse/src/error-codes/error-codes.md
Normal file
65
docs/api-stack/api/api-sse/src/error-codes/error-codes.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# `SSE_CODES` + `SseError`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`SSE_CODES` is the stable error-code registry every contract-level failure path in the SSE family raises. Concrete engines (`api-sse-bun`, `api-sse-cloudflare`, ...) re-throw using this same registry so consumers can switch on `err.code === SSE_CODES.LAST_EVENT_ID_UNKNOWN` regardless of which engine produced the error.
|
||||
|
||||
The registry is frozen at module-init time (via `defineCodes` from `@suaveplan/error`) and the six entries are pinned by the package's `error-codes.spec-conformance.test.ts` — every entry has a production throw site reachable from `production-throws.ts`. New codes go through an OpenSpec change; this file is the canonical list.
|
||||
|
||||
## Features
|
||||
|
||||
- Six stable codes: `HANDSHAKE_FAILED`, `PRINCIPAL_EXPIRED`, `BUFFER_OVERFLOW`, `LAST_EVENT_ID_UNKNOWN`, `CONNECTION_CAP_EXCEEDED`, `BROKER_UNAVAILABLE`.
|
||||
- Every code is namespaced under `SSE.*`.
|
||||
- `SseError extends SuaveplanError<SseCode>` — `instanceof SseError` narrows the union.
|
||||
- `defineCodes` returns a frozen record; mutation throws in strict mode.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { SSE_CODES, SseError } from "@suaveplan/api-sse/error-codes/error-codes";
|
||||
|
||||
throw new SseError(
|
||||
SSE_CODES.LAST_EVENT_ID_UNKNOWN,
|
||||
"error.sse.last_event_id_unknown",
|
||||
{ context: { lastEventId: "evicted-99" } }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { SseError } from "@suaveplan/api-sse/error-codes/error-codes";
|
||||
|
||||
function handle(err: unknown) {
|
||||
if (err instanceof SseError) {
|
||||
switch (err.code) {
|
||||
case "SSE.LAST_EVENT_ID_UNKNOWN":
|
||||
// fall back to live subscription
|
||||
return;
|
||||
case "SSE.PRINCIPAL_EXPIRED":
|
||||
// close + force client re-auth
|
||||
return;
|
||||
case "SSE.BUFFER_OVERFLOW":
|
||||
// emit OTel counter `sse.dropped_events_total`
|
||||
return;
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `SSE_CODES` — frozen record `{ HANDSHAKE_FAILED: "SSE.HANDSHAKE_FAILED", ... }`.
|
||||
- `SseCode` — union of every value in `SSE_CODES`.
|
||||
- `class SseError extends SuaveplanError<SseCode>` — ctor `(code, messageKey, options?)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The six codes were chosen to match the SSE contract surface exposed by this package — every other observable failure (transport down, rate limit, etc.) is the engine's concern and lives in the engine's own code registry. Plugin authors who need a new contract-level code raise an OpenSpec change rather than reaching for a generic one; the registry is intentionally small to keep switch-statements exhaustive.
|
||||
@@ -0,0 +1,60 @@
|
||||
# `production-throws`
|
||||
|
||||
## Purpose
|
||||
|
||||
`production-throws` exports the canonical factory functions that the host (and concrete SSE engines) invoke at every documented contract-level failure point. Each function constructs and throws a fully-formed `SseError` with the appropriate context payload — engines call these directly rather than re-implementing the throw shape, so the wire-visible error envelope stays identical across implementations.
|
||||
|
||||
The factories also let `api-sse` (a contract-only package) satisfy the "every `SSE_CODES` entry has a production throw site" invariant without inventing fake engines. Each factory's signature is the minimum information the host needs to surface a useful error to the client.
|
||||
|
||||
## Features
|
||||
|
||||
- One factory per `SSE_CODES` entry.
|
||||
- Every factory returns `never` so `try / catch` flow analysis tightens.
|
||||
- `context` payloads carry the structured diagnostic data (`detail`, `principalId`, `connectionId + queued`, `lastEventId`, `scope + limit`, `operation`).
|
||||
- `brokerUnavailable` accepts an optional `cause` so engines can chain transport-level errors.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { lastEventIdUnknown } from "@suaveplan/api-sse/error-codes/production-throws";
|
||||
|
||||
function replay(id: string) {
|
||||
if (!buffer.has(id)) {
|
||||
lastEventIdUnknown(id);
|
||||
}
|
||||
return buffer.since(id);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
brokerUnavailable,
|
||||
connectionCapExceeded,
|
||||
} from "@suaveplan/api-sse/error-codes/production-throws";
|
||||
|
||||
async function handshake() {
|
||||
if (activeConnections >= globalCap) {
|
||||
connectionCapExceeded("global", globalCap);
|
||||
}
|
||||
try {
|
||||
await transport.connect();
|
||||
} catch (cause) {
|
||||
brokerUnavailable("connect", cause as Error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `handshakeFailed(detail?: string): never`
|
||||
- `principalExpired(principalId?: string): never`
|
||||
- `bufferOverflow(connectionId: string, queued: number): never`
|
||||
- `lastEventIdUnknown(lastEventId: string): never`
|
||||
- `connectionCapExceeded(scope: "global" | "principal", limit: number): never`
|
||||
- `brokerUnavailable(operation: string, cause?: Error): never`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The factories live in a separate module from the `SSE_CODES` registry so the registry stays a pure data object and tree-shaking can drop the throw factories from a bundle that only needs to switch on codes. The conformance test imports the factory list and exercises every one, which is how the package guarantees "every code reachable" without coupling the registry definition to the throw sites.
|
||||
Reference in New Issue
Block a user