# error-codes _(no submodules)_ ## 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` 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">` — 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` - `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`.