first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
# error-codes
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The error-codes module owns the stable `AUTH.*` code registry and the
`AuthError` class used by every production failure path inside
`@suaveplan/api-plugin-auth`. Consumers branch on the namespaced string
literal (e.g. `error.code === "AUTH.UNAUTHENTICATED"`) — the registry
exists so the strings never drift across the plugin, its consumers, and
the OpenAPI error responses surfaced by `@suaveplan/api`.
## Features
- 10 namespaced `AUTH.*` codes built via `@suaveplan/error`'s
`defineCodes("AUTH", […])` — type-safe at compile time and stable at
runtime.
- `AuthError` extends `SuaveplanError<AuthErrorCode>` so
`instanceof AuthError` narrows `error.code` to the union.
- Codes carry HTTP status via `context.statusCode` — the
`@suaveplan/api` error-handler reads that field and translates to the
matching response code (401 / 403 / 500 / 503).
## Usage: Basic
```ts
import { AUTH_CODES, AuthError } from "@suaveplan/api-plugin-auth/error-codes/error-codes";
throw new AuthError(AUTH_CODES.UNAUTHENTICATED, "Login required", {
context: { statusCode: 401 },
});
```
## Usage: Advanced
Pass `context.required` on a role-check failure so callers and the
OpenAPI 403 response can show which roles satisfy the gate. Carry the
underlying ZodError as `cause` on input-validation failures so the
toJSON pipeline preserves it on the wire:
```ts
import { AUTH_CODES, AuthError } from "@suaveplan/api-plugin-auth/error-codes/error-codes";
import z from "zod";
const parsed = z.object({ email: z.string().email() }).safeParse(input);
if (!parsed.success) {
throw new AuthError(AUTH_CODES.INVALID_INPUT, "Invalid sign-up body", {
cause: parsed.error,
context: { statusCode: 400, issues: parsed.error.issues },
});
}
const principal = c.get("user");
if (!principal) {
throw new AuthError(AUTH_CODES.UNAUTHENTICATED, "Login required", {
context: { statusCode: 401 },
});
}
const required = ["admin", "owner"] as const;
if (!principal.roles.some((r) => required.includes(r as never))) {
throw new AuthError(AUTH_CODES.INSUFFICIENT_ROLE, "Forbidden", {
context: { statusCode: 403, required: [...required] },
});
}
```
## API Reference
### `AUTH_CODES`
`Record<AuthCodeKey, "AUTH.<KEY>">` — frozen mapping built with
`defineCodes`. Keys: `UNAUTHENTICATED`, `INSUFFICIENT_ROLE`,
`EMAIL_NOT_VERIFIED`, `SESSION_RESOLVE_FAILED`,
`PRINCIPAL_MAPPING_FAILED`, `BETTER_AUTH_INIT_FAILED`, `INVALID_INPUT`,
`ROUTE_PREFIX_COLLISION`, `SCHEMA_APPLY_FAILED`, `SERVICE_NOT_AVAILABLE`.
### `AuthErrorCode`
Discriminated string union, `"AUTH.UNAUTHENTICATED" | …` — the type of
`AUTH_CODES[key]`.
### `class AuthError extends SuaveplanError<AuthErrorCode>`
- `new AuthError(code, message, options?)` — `code` is an `AuthErrorCode`;
`options` is the `SuaveplanErrorOptions` shape (`{ context?, cause?,
traceId?, clock? }`). The constructor is `public` so any plugin module
may throw directly.
- `error.authCode` — convenience field that narrows to `AuthErrorCode`
even when the value is treated as a plain `SuaveplanError`.
## Implementation Notes
- The full code list lives in this file. Adding a new code requires
authoring a new OpenSpec change so the spec delta, registry, and call
sites move together.
- `context.statusCode` is non-normative on `SuaveplanError` (the field
is read by `@suaveplan/api`'s error-handler); the auth plugin always
attaches it for codes that map cleanly to an HTTP response so the
served error matches the route's OpenAPI schema.
- `SERVICE_NOT_AVAILABLE` lists missing service names in
`context.missing` so operators reading logs / span events can see
exactly which `ctx.getService(...)` call returned `undefined`.