import { Container, type RequestContext } from '@basaltkit/core'; import { type ErrorDetails } from './error-details.js'; import { type ValidationIssue } from './errors.js'; import type { HttpReply, HttpRequest, BasaltRoute } from './route.js'; declare module '@basaltkit/core' { interface RequestContext { /** Per-request DI scope — `scoped` instances live here. */ container?: Container; } } /** * Runs inside the request context, before validation and the handler. Plugins * register enrichers in the 'http:enrichers' metadata bucket — tenancy uses * this to resolve and attach the current tenant. */ export type RequestEnricher = (info: { request: HttpRequest; context: RequestContext; container: Container; /** * The route being served, so an enricher can honour its `meta` — tenancy * uses `meta.tenant` to tell central routes from tenant ones. Optional * because enrichers written before this existed do not read it. */ route?: BasaltRoute; }) => void | Promise; /** * Runs after enrichers, with access to the route definition (and its `meta`). * Plugins register guards in the 'http:guards' metadata bucket — auth uses * `meta.auth`, permissions uses `meta.can`. A guard rejects by throwing. */ export type RouteGuard = (info: { route: BasaltRoute; request: HttpRequest; context: RequestContext; container: Container; /** * The reply, so a guard can set response headers (e.g. `Retry-After`) before * rejecting. Optional: a pipeline may run guards without one. */ reply?: HttpReply; }) => void | Promise; export interface RoutePipeline { container?: Container; enrichers?: RequestEnricher[]; guards?: RouteGuard[]; } /** * The framework-neutral request pipeline every adapter shares: establishes the * request context (id, correlation, scoped container), runs enrichers then * guards, validates body/query/params, and invokes the handler. Returns the * handler's value (the adapter sends it unless the handler already replied). */ export declare function runRoute(definition: BasaltRoute, request: HttpRequest, reply: HttpReply, pipeline?: RoutePipeline): Promise; export interface ErrorResponse { status: number; body: { error: { code: string; message: string; part?: string; issues?: ValidationIssue[]; /** * Structured payload from an error deliberately constructed with one * (`new HttpError(status, code, message, { details })`, or any * `BasaltError` with a numeric `status`). Absent otherwise — an * unexpected exception never grows one. */ details?: ErrorDetails; }; }; } /** * Maps a thrown error to a standardized HTTP response — shared by all adapters * so error shapes are identical regardless of framework. */ export declare function toErrorResponse(error: unknown): ErrorResponse; /** * A client error raised by the HTTP framework itself — Fastify's body parser * (`FST_*` codes: malformed JSON, body too large, unsupported content type), a * body parser's `SyntaxError` explicitly tagged with a 4xx `statusCode` (the * Fastify adapter's JSON parser), or an http-errors style error that marks * itself `expose: true` (body-parser). * Those used to become a 500 INTERNAL_ERROR: the client got the wrong status * and every malformed request was logged and alerted on as a server bug. * * Deliberately NOT honoured: any other error that merely carries a `status` or * `statusCode` — a failed upstream SDK call (`401 Invalid API key`) is a bug in * this server, not the caller's fault, and must stay a 500. The framework's own * message is never echoed (it can quote the offending input). */ export declare function clientErrorOf(error: unknown): ErrorResponse | null;