/** * Copyright (c) 2026 AzerothJS. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Cross-cutting concerns that wrap the whole app. * * The app's own middleware (app.use) runs BEFORE the handler: it accumulates typed context or * short-circuits, and it never sees the response - which is exactly right for auth, parsed * bodies, and guards. But some production concerns are about the RESPONSE: a request id echoed * back, security headers on every reply, CORS headers on the real answer, rate-limit counters. * Those live one layer out, as decorators over the whole app. * * An edge middleware is a plain `(next) => next` function over a `WebHandler`: it receives the * request, may answer or delegate, and may transform whatever comes back. It is honest about * the architecture's rule that a response is a VALUE - it returns a new Response, it does not * mutate a channel. `pipeline(app, a, b, c)` composes them with `a` outermost, yielding a * `WebHandler` you pass straight to `serve()`. In-process tests call `.handle(new Request(...))` * on the result, so the whole stack is exercised without a socket. */ /** What composition and adapters need from the app - exactly the kernel dispatcher's shape. */ export interface WebHandler { handle(request: Request): Promise; } /** @internal Publishes a handler's own error policy for {@link pipeline} to reuse. */ export declare function attachErrorPolicy(handler: WebHandler, policy: (error: unknown, request: Request) => Response): void; /** * A decorator over a request handler. Wrap `next`, returning a handler that may inspect the * request, answer directly, delegate, and transform the response. The one composition unit * for edge concerns (request id, security headers, CORS, rate limiting). */ export type HandlerWrapper = (next: WebHandler) => WebHandler; /** * @internal The brand that lets {@link App.use} tell an EDGE middleware from an app middleware. * * Both are single-argument functions, so nothing observable at runtime distinguishes them and an * overload resolved by types alone would silently pick the wrong one for a JavaScript caller. * Marking the four framework wrappers is what allows ONE verb to accept both kinds instead of the * framework growing a second one. */ export declare const EDGE: unique symbol; /** An edge middleware: a {@link HandlerWrapper} marked so `use` can recognise it. */ export type EdgeMiddleware = HandlerWrapper & { readonly [EDGE]: true; }; /** * Marks a wrapper as edge middleware. * * Applied by `cors`, `requestId`, `rateLimit` and `securityHeaders` to their return value. The * value stays an ordinary callable, so {@link pipeline} and any existing caller are unaffected - * the brand is additive. * * @param wrapper - The wrapper to mark. * @returns The same function, branded. * @example * export function myEdgeConcern(): EdgeMiddleware * { * return edge((next) => ({ handle: (request) => next.handle(request) })); * } */ export declare function edge(wrapper: HandlerWrapper): EdgeMiddleware; /** * Whether `value` is an edge middleware rather than an app middleware. * * @param value - Any middleware-shaped function. * @returns True when the value carries the {@link EDGE} brand. */ export declare function isEdge(value: unknown): value is EdgeMiddleware; /** * @internal Guards one composed edge layer: a throw from it (or from anything inside it) * becomes an error Response at that boundary, so the layers OUTSIDE it still receive a * Response to decorate - a CORS or request-id stamp must land on error replies too. * `toResponse` supplies the error policy of whoever owns the chain. */ export declare function guardLayer(layer: WebHandler, toResponse: (error: unknown, request: Request) => Response): WebHandler; /** * Composes edge middleware around an app, FIRST argument outermost: `pipeline(app, cors, rl)` * runs cors, then rate limiting, then the app, and unwinds responses back out through each. * The result is a `WebHandler` - hand it to `serve()`, or call `.handle()` in a test. */ export declare function pipeline(app: WebHandler, ...middleware: HandlerWrapper[]): WebHandler; /** * Bridges a kernel handler to a standard Fetch host - `Bun.serve`, `Deno.serve`, Cloudflare * Workers, Vercel Edge, or anything else whose contract is `(Request) => Response`: * * ```ts * export default { fetch: toFetchHandler(app) }; // Workers / Vercel Edge * Bun.serve({ fetch: toFetchHandler(app) }); * Deno.serve(toFetchHandler(app)); * ``` * * It exists because those hosts do not accept the kernel's {@link PayloadResponse}. That class * is a deliberate optimisation - constructing a real `Response` costs ~35x more than it on Node, * measured - and it satisfies `instanceof Response`, so nothing in user code can tell. But a * host checks the INTERNAL slot, not the prototype: Bun answers `Expected a Response object` and * Deno `must be a Response constructed via the Response constructor in this realm`. This adapter * is the one place that difference is paid for, and only for kernel-built responses - a handler * that already returned a native `Response` (a stream, an `sse()` body) passes straight through * untouched. The Node adapter never calls this, so its fast path is unaffected. * * @param target - An `App`, a composed `pipeline(...)`, or any `{ handle }` - a bare function is * accepted too, so `toFetchHandler(app.handle.bind(app))` works. * @returns A fetch function returning a native `Response` on every path. */ export declare function toFetchHandler(target: WebHandler | ((request: Request) => Response | Promise)): (request: Request) => Promise; /** * Materialises a kernel {@link PayloadResponse} into a `Response` built by this realm's own * constructor. Reads through `raw()`, which is also what the Node adapter writes, so a header a * middleware set through the `headers` view reaches the wire here exactly as it does there - * including repeated `Set-Cookie`, which a plain header record cannot carry and which a naive * `new Headers(record)` would collapse to one (session OR csrf, silently lost). * * A null-body status (204/304) must be constructed with a null body or the `Response` * constructor throws. * * @param response - Any response; a native one is returned unchanged. * @returns A native `Response` with the same status, headers, and bytes. */ export declare function toNativeResponse(response: Response): Response; /** * Returns a response with `extra` headers merged in (names lowercased, overwriting). Uses the * PayloadResponse fast path when possible - mutating the `headers` view alone would not reach * the record the Node adapter writes, so a kernel-built response is rebuilt over the same * bytes; any other response is re-wrapped once over its existing body. */ export declare function withResponseHeaders(response: Response, extra: Record): Response; /** The correlation id assigned to this request by {@link requestId}, if that middleware ran. */ export declare function requestIdOf(request: Request): string | undefined; /** * @internal The one place the correlation id is written, so the middleware and the in-process * forwarder cannot end up stamping two different slots. */ export declare function stampRequestId(request: Request, id: string): void; /** * Options for {@link requestId}: the header name and whether an inbound value is trusted or * always replaced. The generated id is what {@link requestIdOf} reads and `logRequests` * attaches to every line. */ export interface RequestIdOptions { /** Header carrying the id, both inbound and outbound (default `x-request-id`). */ header?: string; /** Mints a fresh id when none is trusted inbound (default `crypto.randomUUID`). */ generate?: () => string; /** * Honor a well-formed inbound id instead of always minting (default false: inbound * correlation data is client-forgeable, and it is trusted only where the deployment says * a proxy or upstream service already assigned it - the same default the framework * applies to every other proxy-supplied header). When on, a well-formed inbound id is * kept so one id spans the whole hop; malformed ids (control chars, over-long) are never * trusted - they are a header-injection vector - and a fresh one is minted instead. */ trustInbound?: boolean; } /** * Assigns every request a correlation id: mint a UUID (or honor a well-formed inbound one * when `trustInbound` says the deployment has a proxy assigning them), expose it on the * request (see {@link requestIdOf}) for handlers and the logger, and echo it on the response * so a client and its logs share one id across the whole call. */ export declare function requestId(options?: RequestIdOptions): EdgeMiddleware;