first commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# error-codes
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Stable error-code registry and `SseError` class for `@suaveplan/api-plugin-sse`. Every failure path in the plugin raises an `SseError` carrying one of eight `SSE_*` codes so consumers can branch deterministically on the string value rather than parsing error messages. Codes are declared via `defineCodes("SSE", [...] as const)` so the type system narrows `error.code` to a literal-string union — handlers cannot accidentally branch on an unregistered code.
|
||||
|
||||
## Features
|
||||
|
||||
- 8 stable codes covering authentication, capacity, lifecycle, validation, and backpressure failures.
|
||||
- `SseError extends SuaveplanError<SseErrorCode>` so `instanceof SseError` narrows the `code` field.
|
||||
- Public constructor for direct instantiation from callers.
|
||||
- `cause` chain preserved for upstream `ZodError` and runtime failures.
|
||||
- `context` carries diagnostic payload (`channel`, `principalId`, `max`, `statusCode`).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { SSE_CODES, SseError } from "@suaveplan/api-plugin-sse/error-codes/error-codes";
|
||||
|
||||
try {
|
||||
service.subscribe({ channel: "" });
|
||||
} catch (err) {
|
||||
if (err instanceof SseError && err.code === SSE_CODES.CHANNEL_REQUIRED) {
|
||||
return Response.json({ error: "channel required" }, { status: 400 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { SSE_CODES, SseError } from "@suaveplan/api-plugin-sse/error-codes/error-codes";
|
||||
|
||||
// Map every SSE_* code to an HTTP status for a unified handler.
|
||||
const STATUS_BY_CODE: Record<string, number> = {
|
||||
[SSE_CODES.UNAUTHENTICATED]: 401,
|
||||
[SSE_CODES.PER_PRINCIPAL_LIMIT_EXCEEDED]: 429,
|
||||
[SSE_CODES.TOO_MANY_CONNECTIONS]: 429,
|
||||
[SSE_CODES.CHANNEL_REQUIRED]: 400,
|
||||
[SSE_CODES.INVALID_INPUT]: 400,
|
||||
[SSE_CODES.SERVICE_NOT_AVAILABLE]: 500,
|
||||
[SSE_CODES.CLOSED]: 503,
|
||||
[SSE_CODES.QUEUE_OVERFLOW]: 503,
|
||||
};
|
||||
|
||||
app.onError((err, c) => {
|
||||
if (err instanceof SseError) {
|
||||
return c.json(
|
||||
{ code: err.code, message: err.message, context: err.context },
|
||||
STATUS_BY_CODE[err.code] ?? 500
|
||||
);
|
||||
}
|
||||
return c.text("internal", 500);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `SSE_CODES` — frozen record produced by `defineCodes("SSE", [...] as const)`. Keys: `UNAUTHENTICATED`, `TOO_MANY_CONNECTIONS`, `PER_PRINCIPAL_LIMIT_EXCEEDED`, `CHANNEL_REQUIRED`, `SERVICE_NOT_AVAILABLE`, `INVALID_INPUT`, `QUEUE_OVERFLOW`, `CLOSED`. Values are `SSE.<KEY>` strings.
|
||||
- `type SseErrorCode` — union of every value in `SSE_CODES`.
|
||||
- `class SseError extends SuaveplanError<SseErrorCode>` — public constructor `(code, message, options?)`. `options` accepts `{ cause?, context? }` per `SuaveplanErrorOptions`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `QUEUE_OVERFLOW` is not thrown — it is attached to the `sse.queue_overflow_total` OTel counter as the `code` attribute so dashboards can correlate drops with the registry entry without inventing a parallel taxonomy.
|
||||
- `UNAUTHENTICATED` is thrown only when the handler is configured with `requirePrincipal: true` AND `resolvePrincipalId` returned `undefined`. Anonymous routes never raise it.
|
||||
- `SERVICE_NOT_AVAILABLE` carries `context.missing: string[]` so the orchestrator can list every missing slot in one failure message.
|
||||
- All codes are stable across versions — adding new codes is additive; removing a code is a breaking change.
|
||||
Reference in New Issue
Block a user