UNPKG

h3

Version:

Minimal H(TTP) framework built for high performance and portability.

1,201 lines (1,200 loc) 67.9 kB
import { CookieSerializeOptions, DynamicEventHandler, ErrorDetails, EventHandler, EventHandlerObject, EventHandlerRequest, EventHandlerResponse, EventHandlerWithFetch, FetchableObject, H3, H3$1, H3Config, H3Event, H3EventContext, H3Plugin, H3RouteMeta, HTTPError, HTTPEvent, HTTPHandler, HTTPMethod, InferEventInput, MaybePromise as MaybePromise$1, Middleware, TypedRequest } from "./h3.mjs"; import { NodeServerRequest, NodeServerResponse, ServerRequest, ServerRequestContext } from "srvx"; import { Hooks, Hooks as WebSocketHooks, Message as WebSocketMessage, Peer, Peer as WebSocketPeer } from "crossws"; declare function isEvent(input: any): input is H3Event; /** * Checks if the input is an object with `{ req: Request }` signature. * @param input - The input to check. * @returns True if the input is `{ req: Request }` */ declare function isHTTPEvent(input: any): input is HTTPEvent; /** * Gets the context of the event, if it does not exists, initializes a new context on `req.context`. */ declare function getEventContext<T extends ServerRequestContext | H3EventContext>(event: HTTPEvent | H3Event): T; declare function mockEvent(_request: string | URL | Request, options?: RequestInit & { h3?: H3EventContext; }): H3Event; /** The Standard Schema interface. */ interface StandardSchemaV1<Input = unknown, Output = Input> { /** The Standard Schema properties. */ readonly "~standard": Props<Input, Output>; } /** The Standard Schema properties interface. */ interface Props<Input = unknown, Output = Input> { /** The version number of the standard. */ readonly version: 1; /** The vendor name of the schema library. */ readonly vendor: string; /** Validates unknown input values. */ readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>; /** Inferred types associated with the schema. */ readonly types?: Types<Input, Output> | undefined; } /** The result interface of the validate function. */ type Result<Output> = SuccessResult<Output> | FailureResult; /** The result interface if validation succeeds. */ interface SuccessResult<Output> { /** The typed output value. */ readonly value: Output; /** The non-existent issues. */ readonly issues?: undefined; } /** The result interface if validation fails. */ interface FailureResult { /** The issues of failed validation. */ readonly issues: ReadonlyArray<Issue>; } /** The issue interface of the failure output. */ interface Issue { /** The error message of the issue. */ readonly message: string; /** The path of the issue, if any. */ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined; } /** The path segment interface of the issue. */ interface PathSegment { /** The key representing a path segment. */ readonly key: PropertyKey; } /** The Standard Schema types interface. */ interface Types<Input = unknown, Output = Input> { /** The input type of the schema. */ readonly input: Input; /** The output type of the schema. */ readonly output: Output; } /** Infers the output type of a Standard Schema. */ type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"]; type ValidateResult<T> = T | true | false | void; type OnValidateError<Source extends string = string> = (result: FailureResult & { _source?: Source; }) => ErrorDetails; declare function defineHandler<Req extends EventHandlerRequest = EventHandlerRequest, Res = EventHandlerResponse>(handler: EventHandler<Req, Res>): EventHandlerWithFetch<Req, Res>; declare function defineHandler<Req extends EventHandlerRequest = EventHandlerRequest, Res = EventHandlerResponse>(def: EventHandlerObject<Req, Res>): EventHandlerWithFetch<Req, Res>; type StringHeaders<T> = { [K in keyof T]: Extract<T[K], string>; }; /** * @experimental defineValidatedHandler is an experimental feature and API may change. */ declare function defineValidatedHandler<RequestBody extends StandardSchemaV1, RequestHeaders extends StandardSchemaV1, RequestQuery extends StandardSchemaV1, Res extends EventHandlerResponse = EventHandlerResponse>(def: Omit<EventHandlerObject, "handler"> & { validate?: { body?: RequestBody; headers?: RequestHeaders; query?: RequestQuery; onError?: OnValidateError; }; handler: EventHandler<{ body: InferOutput<RequestBody>; query: StringHeaders<InferOutput<RequestQuery>>; }, Res>; }): EventHandlerWithFetch<TypedRequest<InferOutput<RequestBody>, InferOutput<RequestHeaders>>, Res>; declare function dynamicEventHandler(initial?: EventHandler | FetchableObject): DynamicEventHandler; type MaybePromise<T> = T | Promise<T>; declare function defineLazyEventHandler(loader: () => MaybePromise<HTTPHandler>): EventHandlerWithFetch; declare function toEventHandler(handler: HTTPHandler | undefined): EventHandler | undefined; declare function defineMiddleware(input: Middleware): Middleware; declare function callMiddleware(event: H3Event, middleware: Middleware[], handler: EventHandler, index?: number): unknown | Promise<unknown>; /** * Converts any HTTPHandler or Middleware into Middleware. * * If FetchableObject or Handler returns a Response with 404 status, the next middleware will be called. */ declare function toMiddleware(input: HTTPHandler | Middleware | undefined): Middleware; declare function toResponse(val: unknown, event: H3Event, config?: H3Config): Response | Promise<Response>; declare class HTTPResponse { #private; body?: BodyInit | null; constructor(body: BodyInit | null, init?: Pick<ResponseInit, "status" | "statusText" | "headers">); get status(): number; get statusText(): string; get headers(): Headers; } type NodeHandler = (req: NodeServerRequest, res: NodeServerResponse) => unknown | Promise<unknown>; type NodeMiddleware = (req: NodeServerRequest, res: NodeServerResponse, next: (error?: Error) => void) => unknown | Promise<unknown>; /** * @deprecated Since h3 v2 you can directly use `app.fetch(request, init?, context?)` */ declare function toWebHandler(app: H3): (request: ServerRequest, context?: H3EventContext) => Promise<Response>; declare function fromWebHandler(handler: (request: ServerRequest, context?: H3EventContext) => Promise<Response>): EventHandler; /** * Convert a Node.js handler function (req, res, next?) to an EventHandler. * * **Note:** The returned event handler requires to be executed with h3 Node.js handler. */ declare function fromNodeHandler(handler: NodeMiddleware): EventHandler; declare function fromNodeHandler(handler: NodeHandler): EventHandler; declare function defineNodeHandler(handler: NodeHandler): NodeHandler; declare function defineNodeMiddleware(handler: NodeMiddleware): NodeMiddleware; /** * Route definition options */ interface RouteDefinition { /** * HTTP method for the route, e.g. 'GET', 'POST', etc. */ method: HTTPMethod; /** * Route pattern, e.g. '/api/users/:id' */ route: string; /** * Handler function for the route. */ handler: EventHandler; /** * Optional middleware to run before the handler. */ middleware?: Middleware[]; /** * Additional route metadata. */ meta?: H3RouteMeta; validate?: { body?: StandardSchemaV1; headers?: StandardSchemaV1; query?: StandardSchemaV1; }; } /** * Define a route as a plugin that can be registered with app.register() * * @example * ```js * import { z } from "zod"; * * const userRoute = defineRoute({ * method: 'POST', * validate: { * query: z.object({ id: z.string().uuid() }), * body: z.object({ name: z.string() }), * }, * handler: (event) => { * return { success: true }; * } * }); * * app.register(userRoute); * ``` */ declare function defineRoute(def: RouteDefinition): H3Plugin; /** * Remove a route handler from the app. * * @example * ```ts * import { H3, removeRoute } from "h3"; * * const app = new H3(); * app.get("/temp", () => "hello"); * * removeRoute(app, "GET", "/temp"); // route removed * ``` */ declare function removeRoute(app: H3$1, method: HTTPMethod | Lowercase<HTTPMethod> | "", route: string): void; /** * Create a lightweight request proxy that overrides only the URL. * * Avoids cloning the original request (no `new Request()` allocation). */ declare function requestWithURL(req: ServerRequest, url: string): ServerRequest; /** * Create a lightweight request proxy with the base path stripped from the URL pathname. */ declare function requestWithBaseURL(req: ServerRequest, base: string): ServerRequest; /** * Convert input into a web [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). * * If input is a relative URL, it will be normalized into a full path based on headers. * * If input is already a Request and no options are provided, it will be returned as-is. */ declare function toRequest(input: ServerRequest | URL | string, options?: RequestInit): ServerRequest; /** * Get parsed query string object from the request URL. * * @example * app.get("/", (event) => { * const query = getQuery(event); // { key: "value", key2: ["value1", "value2"] } * }); */ declare function getQuery<T, Event extends H3Event | HTTPEvent = HTTPEvent, _T = Exclude<InferEventInput<"query", Event, T>, undefined>>(event: Event): _T; declare function getValidatedQuery<Event extends HTTPEvent, S extends StandardSchemaV1<any, any>>(event: Event, validate: S, options?: { onError?: (result: FailureResult) => ErrorDetails; }): Promise<InferOutput<S>>; declare function getValidatedQuery<Event extends HTTPEvent, OutputT, InputT = InferEventInput<"query", Event, OutputT>>(event: Event, validate: (data: InputT) => ValidateResult<OutputT> | Promise<ValidateResult<OutputT>>, options?: { onError?: () => ErrorDetails; }): Promise<OutputT>; /** * Get matched route params. * * If `decode` option is `true`, it will decode the matched route params (like * `decodeURIComponent`), except encoded path separators (`%2f`, `%5c`) are kept * encoded so decoding can never reintroduce a `/` or `\` the router never matched. * * @example * app.get("/", (event) => { * const params = getRouterParams(event); // { key: "value" } * }); */ declare function getRouterParams(event: HTTPEvent, opts?: { decode?: boolean; }): NonNullable<H3Event["context"]["params"]>; declare function getValidatedRouterParams<Event extends HTTPEvent, S extends StandardSchemaV1>(event: Event, validate: S, options?: { decode?: boolean; onError?: (result: FailureResult) => ErrorDetails; }): Promise<InferOutput<S>>; declare function getValidatedRouterParams<Event extends HTTPEvent, OutputT, InputT = InferEventInput<"routerParams", Event, OutputT>>(event: Event, validate: (data: InputT) => ValidateResult<OutputT> | Promise<ValidateResult<OutputT>>, options?: { decode?: boolean; onError?: () => ErrorDetails; }): Promise<OutputT>; /** * Get a matched route param by name. * * If `decode` option is `true`, it will decode the matched route param (like * `decodeURIComponent`), except encoded path separators (`%2f`, `%5c`) are kept * encoded so decoding can never reintroduce a `/` or `\` the router never matched. * * @example * app.get("/", (event) => { * const param = getRouterParam(event, "key"); * }); */ declare function getRouterParam(event: HTTPEvent, name: string, opts?: { decode?: boolean; }): string | undefined; /** * * Checks if the incoming request method is of the expected type. * * If `allowHead` is `true`, it will allow `HEAD` requests to pass if the expected method is `GET`. * * @example * app.get("/", (event) => { * if (isMethod(event, "GET")) { * // Handle GET request * } else if (isMethod(event, ["POST", "PUT"])) { * // Handle POST or PUT request * } * }); */ declare function isMethod(event: HTTPEvent, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): boolean; /** * Asserts that the incoming request method is of the expected type using `isMethod`. * * If the method is not allowed, it will throw a 405 error and include an `Allow` * response header listing the permitted methods, as required by RFC 9110. * * If `allowHead` is `true`, it will allow `HEAD` requests to pass if the expected method is `GET`. * * @example * app.get("/", (event) => { * assertMethod(event, "GET"); * // Handle GET request, otherwise throw 405 error * }); */ declare function assertMethod(event: HTTPEvent, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): void; /** * Get the request hostname. * * If `xForwardedHost` is `true`, it will use the `x-forwarded-host` header if it exists. * * If no host header is found, it will return an empty string. * * @example * app.get("/", (event) => { * const host = getRequestHost(event); // "example.com" * }); */ declare function getRequestHost(event: HTTPEvent, opts?: { xForwardedHost?: boolean; }): string; /** * Get the request protocol. * * If `x-forwarded-proto` header is set to "https", it will return "https". If the header contains a comma-separated list of protocols, the first entry is used. You can disable this behavior by setting `xForwardedProto` to `false`. * * If protocol cannot be determined, it will default to "http". * * @example * app.get("/", (event) => { * const protocol = getRequestProtocol(event); // "https" * }); */ declare function getRequestProtocol(event: HTTPEvent | H3Event, opts?: { xForwardedProto?: boolean; }): "http" | "https" | (string & {}); /** * Generated the full incoming request URL. * * If `xForwardedHost` is `true`, it will use the `x-forwarded-host` header if it exists. * * If `xForwardedProto` is `false`, it will not use the `x-forwarded-proto` header. * * @example * app.get("/", (event) => { * const url = getRequestURL(event); // "https://example.com/path" * }); */ declare function getRequestURL(event: HTTPEvent | H3Event, opts?: { xForwardedHost?: boolean; xForwardedProto?: boolean; }): URL; /** * Try to get the client IP address from the incoming request. * * If `xForwardedFor` is `true`, it will use the `x-forwarded-for` header if it exists. * * If IP cannot be determined, it will default to `undefined`. * * @example * app.get("/", (event) => { * const ip = getRequestIP(event); // "192.0.2.0" * }); */ declare function getRequestIP(event: HTTPEvent, opts?: { /** * Use the X-Forwarded-For HTTP header set by proxies. * * Note: Make sure that this header can be trusted (your application running behind a CDN or reverse proxy) before enabling. */ xForwardedFor?: boolean; }): string | undefined; type IterationSource<Val, Ret = Val> = Iterable<Val> | AsyncIterable<Val> | Iterator<Val, Ret | undefined> | AsyncIterator<Val, Ret | undefined> | (() => Iterator<Val, Ret | undefined> | AsyncIterator<Val, Ret | undefined>); type IteratorSerializer<Value> = (value: Value) => Uint8Array | undefined; /** * Respond with an empty payload.<br> * * @example * app.get("/", () => noContent()); * * @param status status code to be send. By default, it is `204 No Content`. */ declare function noContent(status?: number): HTTPResponse; /** * Send a redirect response to the client. * * It adds the `location` header to the response and sets the status code to 302 by default. * * In the body, it sends a simple HTML page with a meta refresh tag to redirect the client in case the headers are ignored. * * @example * app.get("/", () => { * return redirect("https://example.com"); * }); * * @example * app.get("/", () => { * return redirect("https://example.com", 301); // Permanent redirect * }); */ declare function redirect(location: string, status?: number, statusText?: string): HTTPResponse; /** * Redirect the client back to the previous page using the `referer` header. * * If the `referer` header is missing or is a different origin, it falls back to the provided URL (default `"/"`). * * By default, only the **pathname** of the referer is used (query string and hash are stripped) * to prevent spoofed referers from carrying unintended parameters. Set `allowQuery: true` to preserve the query string. * * **Security:** The `fallback` value MUST be a trusted, hardcoded path — never use user input. * Passing user-controlled values (e.g., query params) as `fallback` creates an open redirect vulnerability. * * @example * app.post("/submit", (event) => { * // process form... * return redirectBack(event, { fallback: "/form" }); * }); */ declare function redirectBack(event: H3Event, opts?: { /** Fallback URL when referer is missing or cross-origin (default: `"/"`). **Must be a trusted, hardcoded path — never user input.** */ fallback?: string; /** HTTP status code for the redirect (default: `302`). */ status?: number; /** Preserve the query string from the referer URL (default: `false`). */ allowQuery?: boolean; }): HTTPResponse; /** * Write `HTTP/1.1 103 Early Hints` to the client. * * In runtimes that don't support early hints natively, this function * falls back to setting response headers which can be used by CDN. */ declare function writeEarlyHints(event: H3Event, hints: Record<string, string | string[]>): void | Promise<void>; /** * Iterate a source of chunks and send back each chunk in order. * Supports mixing async work together with emitting chunks. * * Each chunk must be a string or a buffer. * * For generator (yielding) functions, the returned value is treated the same as yielded values. * * @param iterable - Iterator that produces chunks of the response. * @param serializer - Function that converts values from the iterable into stream-compatible values. * @template Value - Test * * @example * return iterable(async function* work() { * // Open document body * yield "<!DOCTYPE html>\n<html><body><h1>Executing...</h1><ol>\n"; * // Do work ... * for (let i = 0; i < 1000; i++) { * await delay(1000); * // Report progress * yield `<li>Completed job #`; * yield i; * yield `</li>\n`; * } * // Close out the report * return `</ol></body></html>`; * }); * async function delay(ms) { * return new Promise((resolve) => setTimeout(resolve, ms)); * } */ declare function iterable<Value = unknown, Return = unknown>(iterable: IterationSource<Value, Return>, options?: { serializer: IteratorSerializer<Value | Return>; }): HTTPResponse; /** * Respond with HTML content. * * @example * app.get("/", () => html("<h1>Hello, World!</h1>")); * app.get("/", () => html`<h1>Hello, ${name}!</h1>`); */ declare function html(strings: TemplateStringsArray, ...values: unknown[]): HTTPResponse; declare function html(markup: string): HTTPResponse; /** * Advertise the query formats a resource accepts by setting the `Accept-Query` * response header (RFC 10008, HTTP `QUERY` method). * * The media types are serialized as a * [Structured Fields](https://www.rfc-editor.org/rfc/rfc8941) List: the base * media type becomes a token and any `;name=value` parameters are emitted with * their values as quoted strings. * * @example * app.query("/search", (event) => { * appendAcceptQuery(event, ["application/sql;charset=UTF-8", "application/jsonpath"]); * // Accept-Query: application/sql;charset="UTF-8", application/jsonpath * return handleSearch(event); * }); * * @param event The H3Event passed by the handler. * @param mediaTypes A media type (with optional parameters) or an array of them. */ declare function appendAcceptQuery(event: H3Event, mediaTypes: string | string[]): void; /** * Assert that the request `Content-Type` is present and one of the accepted * media types, following the requirements of RFC 10008 for the HTTP `QUERY` * method. * * Throws: * * - `400 Bad Request` if the `Content-Type` header is missing. * * - `422 Unprocessable Content` if the `Content-Type` header is malformed. * * - `415 Unsupported Media Type` if the media type is not accepted. * * Accepted types may use wildcards: `*` / `*&#47;*` match anything and * `type/*` matches any subtype of `type`. * * @example * app.query("/search", async (event) => { * requireContentType(event, ["application/sql", "application/jsonpath"]); * const body = await readBody(event, { type: "text" }); * // ... * }); * * @param event The HTTPEvent passed by the handler. * @param acceptedTypes An accepted media type or an array of them. * @returns The matched request media type (lower-cased, without parameters). */ declare function requireContentType(event: HTTPEvent, acceptedTypes: string | string[]): string; /** * Define a middleware that runs on each request. */ declare function onRequest(hook: (event: H3Event) => MaybePromise$1<void>): Middleware; /** * Define a middleware that runs after Response is generated. * * You can return a new Response from the handler to replace the original response. */ declare function onResponse(hook: (response: Response, event: H3Event) => unknown): Middleware; /** * Define a middleware that runs when an error occurs. * * You can return a new Response from the handler to gracefully handle the error. */ declare function onError(hook: (error: HTTPError, event: H3Event) => unknown): Middleware; /** * Define a middleware that checks whether request body size is within specified limit. * * If body size exceeds the limit, throws a `413` Request Entity Too Large response error. * If you need custom handling for this case, use `assertBodySize` instead. * * @param limit Body size limit in bytes * @see {assertBodySize} */ declare function bodyLimit(limit: number): Middleware; interface ProxyOptions { headers?: HeadersInit; /** * Header names allowed to bypass the built-in hop-by-hop denylist. Matched * case-insensitively. * * This is **not** an exclusive allowlist: all ordinary request headers are * still forwarded regardless. It only lists exceptions that force-forward a * header the proxy would otherwise drop — e.g. `forwardHeaders: ["host"]` * forwards the client's `host` verbatim. `filterHeaders` still wins over it. */ forwardHeaders?: string[]; /** * Denylist of incoming request header names to drop before proxying. * Header names are matched case-insensitively. */ filterHeaders?: string[]; /** * Options forwarded to the underlying `fetch()` call. * * Upstream 3xx responses are passed through to the client by default * (`redirect: "manual"`) rather than followed. Set * `fetchOptions: { redirect: "follow" }` to restore following redirects — but * note that following a redirect for a request with a streamed body can fail, * since the body cannot be replayed once it has been consumed. */ fetchOptions?: RequestInit & { duplex?: "half" | "full"; }; cookieDomainRewrite?: string | Record<string, string>; cookiePathRewrite?: string | Record<string, string>; /** * Rewrite `location` and `refresh` response headers, like nginx * `proxy_redirect`: * * - `true` (default): a URL whose origin matches the proxy `target` is * rewritten to the proxy's own origin (path and query preserved), so * client-side redirects keep flowing through the proxy instead of * exposing the upstream host. Relative and third-party URLs are left * untouched, as are internal (`/`-prefixed) targets, which already share * the proxy origin. * - A record maps URL prefixes to replacements (nginx * `proxy_redirect <from> <to>`); the first matching prefix is replaced, * e.g. `{ "https://upstream.example/two/": "/one/" }`. Only the explicit * mappings apply in this mode (including for internal targets). * - `false`: forward these headers verbatim. * * @default true */ locationRewrite?: boolean | Record<string, string>; onResponse?: (event: H3Event, response: Response) => void | Promise<void>; /** * Control how a client disconnect is handled. * * The incoming request's abort signal (`event.req.signal`) is always forwarded * to the proxied request, so a client disconnect aborts the upstream request * and releases its connection. By default the resulting abort is handled * quietly with a `499 Client Closed Request` response (never delivered, since * the client is already gone) rather than logged as a `502` gateway error. * * Set this to `true` to instead let the `AbortError` propagate to your handler * (e.g. to run cleanup). This also applies to a custom `fetchOptions.signal`, * except when it aborts with a `TimeoutError` — timeouts always map to `504` * (see `timeout`). */ propagateAbortError?: boolean; /** * Milliseconds to wait for the upstream response (headers) before giving up. * On timeout the proxy responds with `504 Gateway Timeout`. The deadline is * cleared once the upstream responds — it never cuts off a long-running * response body stream. * * Because a fired timeout aborts with a `TimeoutError`, a caller-supplied * `fetchOptions.signal` that is itself an `AbortSignal.timeout` is also * mapped to `504` (rather than the `499` used for client disconnects) — note * that such a signal stays armed during body streaming and can truncate it; * prefer this option. */ timeout?: number; /** * When `true`, add `x-forwarded-*` request headers derived from the incoming * request so the upstream learns the client and original request info: * * - `x-forwarded-for`: the client IP (`event.req.ip`, when available). * - `x-forwarded-proto`: the incoming request protocol. * - `x-forwarded-host`: the original host (incl. port). * - `x-forwarded-port`: the original port (or the protocol default — `443` for * https, `80` for http). * * Each header is only set when absent — a value already present on the * incoming request (or set via header options) is left untouched. * * **Security:** because present values win, a client-supplied * `x-forwarded-for` is forwarded verbatim and the real client IP is never * added. On an internet-facing server (no trusted proxy in front), strip * incoming values first with `filterHeaders: ["x-forwarded-for"]` if the * upstream trusts this header for allowlisting, rate limiting, or logging. * * Note that `x-forwarded-proto`/`-host` reflect `event.url` (the server's * resolved protocol and host), not the raw client headers — so `filterHeaders` * does not affect them. By default the server derives these from the real * transport and the on-the-wire `Host`, so a client cannot spoof them; they * only follow an inbound `x-forwarded-*` header when the server is explicitly * configured to trust an upstream proxy (e.g. srvx's `trustProxy`), which is * the correct setup when a proxy you control sits in front. * * Only applied by `proxyRequest` (which forwards the incoming request); * the lower-level `proxy` ignores this option. * * @default false */ xfwd?: boolean; } /** * Proxy the incoming request to a target URL. * * If the `target` starts with `/`, the request is handled internally by the app router * via `event.app.fetch()` instead of making an external HTTP request. * * The request body is streamed to the target without buffering. Per the Fetch * standard, a request body can only be consumed once, so reading it beforehand * (e.g. via `readBody()`, `readFormData()`, or body-reading middleware) locks * the stream and proxying fails. If you need to inspect the body and still * proxy it, read from a clone and leave the original event untouched. * * Upstream 3xx responses are passed through to the client by default rather than * followed. Set `fetchOptions: { redirect: "follow" }` to follow them instead — * but following a redirect with a streamed request body can fail, since the body * cannot be replayed once consumed. * * **Security:** Never pass unsanitized user input as the `target`. Callers are * responsible for validating and restricting the target URL (e.g. allowlisting * hosts, blocking internal paths, enforcing protocol). Consider using * `bodyLimit()` middleware to prevent large request bodies from consuming * excessive resources when proxying untrusted input. * * @example * app.all("/proxy", async (event) => { * const body = await event.req.clone().json(); // read from the clone * // ...inspect body... * return proxyRequest(event, "/target"); // original stream still intact * }); */ declare function proxyRequest(event: H3Event, target: string, opts?: ProxyOptions): Promise<HTTPResponse>; /** * Make a proxy request to a target URL and send the response back to the client. * * If the `target` starts with `/`, the request is dispatched internally via * `event.app.fetch()` (sub-request) and never leaves the process. This bypasses * any external security layer (reverse proxy auth, IP allowlisting, mTLS). * * Upstream 3xx responses are passed through to the client by default rather than * followed. Set `fetchOptions: { redirect: "follow" }` to follow them instead — * but following a redirect with a streamed request body can fail, since the body * cannot be replayed once consumed. (Internal sub-requests via `event.app.fetch()` * never follow redirects.) * * **Limitations** (inherited from `fetch`): upstream response bodies are always * decompressed (compression is not preserved end-to-end), the `host` header is * rewritten to the target (preserving it via `forwardHeaders: ["host"]` works on * Node.js but may be ignored on other runtimes), and unix sockets, TLS options, * or connection agents require a runtime-specific escape hatch (e.g. undici's * `dispatcher` in `fetchOptions` on Node.js). On browser and service-worker * runtimes, `redirect: "manual"` produces an unrelayable opaque-redirect for * external targets (a `502` is returned) — set * `fetchOptions: { redirect: "follow" }` there. * * **Security:** Never pass unsanitized user input as the `target`. Callers are * responsible for validating and restricting the target URL (e.g. allowlisting * hosts, blocking internal paths, enforcing protocol). */ declare function proxy(event: H3Event, target: string, opts?: ProxyOptions): Promise<HTTPResponse>; /** * Get the request headers object without headers known to cause issues when proxying. */ declare function getProxyRequestHeaders(event: H3Event, opts?: { host?: boolean; forwardHeaders?: string[]; filterHeaders?: string[]; }): Record<string, string>; /** * Make a fetch request carrying the event's context. * * Behavior depends on the target: * * An **internal** `url` (starting with `/`) is dispatched via * `event.app.fetch()` (sub-request) and never leaves the process. It inherits * the incoming request's filtered headers (via `getProxyRequestHeaders`) and * runtime metadata (`ip`, `waitUntil`, ...). * * An **external** `url` is sent with native `fetch(url, init)` **unchanged** — * the event's headers and context are *not* inherited (forwarding cookies or * authorization to arbitrary hosts would be unsafe). A streamed `init.body` * is given `duplex: "half"` when unset, which Node's `fetch` requires. * * **Security:** Never pass unsanitized user input as the `url`. Callers are * responsible for validating and restricting the URL. */ declare function fetchWithEvent(event: H3Event, url: string, init?: RequestInit & { duplex?: "half" | "full"; }): Promise<Response>; interface ReadBodyOptions { /** * Force a parser instead of inferring it from the request `Content-Type`. * * - `"json"` (default): parse as JSON. * - `"text"`: return the raw string body. * - `"urlencoded"`: parse as `application/x-www-form-urlencoded`. * - `"formData"`: parse as `multipart/form-data` (or url-encoded) form data. */ type?: "json" | "text" | "urlencoded" | "formData"; } /** * Reads request body and tries to parse using JSON.parse or URLSearchParams. * * By default the body is parsed as JSON (falling back to URL-encoded parsing * when the `Content-Type` is `application/x-www-form-urlencoded`). Other body * types, such as `multipart/form-data`, must be opted into explicitly via * `options.type` and are never auto-detected from the request headers. * * @example * app.post("/", async (event) => { * const body = await readBody(event); * }); * @example * app.post("/upload", async (event) => { * const body = await readBody(event, { type: "formData" }); * }); * * @param event H3 event passed by h3 handler * @param options Parsing options. Set `type` to force a parser instead of * inferring it from the request `Content-Type`. * * @return {*} The `Object`, `Array`, `String`, `Number`, `Boolean`, or `null` value corresponding to the request body */ declare function readBody<T, _Event extends HTTPEvent = HTTPEvent, _T = InferEventInput<"body", _Event, T>>(event: _Event, options?: ReadBodyOptions): Promise<undefined | _T>; declare function readValidatedBody<Event extends HTTPEvent, S extends StandardSchemaV1>(event: Event, validate: S, options?: { onError?: (result: FailureResult) => ErrorDetails; }): Promise<InferOutput<S>>; declare function readValidatedBody<Event extends HTTPEvent, OutputT, InputT = InferEventInput<"body", Event, OutputT>>(event: Event, validate: (data: InputT) => ValidateResult<OutputT> | Promise<ValidateResult<OutputT>>, options?: { onError?: () => ErrorDetails; }): Promise<OutputT>; /** * Asserts that request body size is within the specified limit. * * If body size exceeds the limit, throws a `413` Request Entity Too Large response error. * * @example * app.get("/", async (event) => { * await assertBodySize(event, 10 * 1024 * 1024); // 10MB * const data = await event.req.formData(); * }); * * @param event HTTP event * @param limit Body size limit in bytes */ declare function assertBodySize(event: HTTPEvent, limit: number): Promise<void>; /** * Parse the request to get HTTP Cookie header string and returning an object of all cookie name-value pairs. * @param event {HTTPEvent} H3 event or req passed by h3 handler * @returns Object of cookie name-value pairs * ```ts * const cookies = parseCookies(event) * ``` */ declare function parseCookies(event: HTTPEvent): Record<string, string | undefined>; /** * Get and validate all cookies using a Standard Schema or custom validator. * * @example * app.get("/", async (event) => { * const cookies = await getValidatedCookies(event, z.object({ * session: z.string(), * theme: z.enum(["light", "dark"]).optional(), * })); * }); */ declare function getValidatedCookies<Event extends HTTPEvent, S extends StandardSchemaV1<any, any>>(event: Event, validate: S, options?: { onError?: (result: FailureResult) => ErrorDetails; }): Promise<InferOutput<S>>; declare function getValidatedCookies<Event extends HTTPEvent, OutputT>(event: Event, validate: (data: Record<string, string | undefined>) => ValidateResult<OutputT> | Promise<ValidateResult<OutputT>>, options?: { onError?: () => ErrorDetails; }): Promise<OutputT>; /** * Get a cookie value by name. * @param event {HTTPEvent} H3 event or req passed by h3 handler * @param name Name of the cookie to get * @returns {*} Value of the cookie (String or undefined) * ```ts * const authorization = getCookie(request, 'Authorization') * ``` */ declare function getCookie(event: HTTPEvent, name: string): string | undefined; /** * Set a cookie value by name. * @param event {H3Event} H3 event or res passed by h3 handler * @param name Name of the cookie to set * @param value Value of the cookie to set * @param options {CookieSerializeOptions} Options for serializing the cookie * ```ts * setCookie(res, 'Authorization', '1234567') * ``` */ declare function setCookie(event: H3Event, name: string, value: string, options?: CookieSerializeOptions): void; /** * Remove a cookie by name. * @param event {H3Event} H3 event or res passed by h3 handler * @param name Name of the cookie to delete * @param serializeOptions {CookieSerializeOptions} Cookie options * ```ts * deleteCookie(res, 'SessionId') * ``` */ declare function deleteCookie(event: H3Event, name: string, serializeOptions?: CookieSerializeOptions): void; /** * Get a chunked cookie value by name. Will join chunks together. * @param event {HTTPEvent} { req: Request } * @param name Name of the cookie to get * @returns {*} Value of the cookie (String or undefined) * ```ts * const session = getChunkedCookie(event, 'Session') * ``` */ declare function getChunkedCookie(event: HTTPEvent, name: string): string | undefined; /** * Set a cookie value by name. Chunked cookies will be created as needed. * @param event {H3Event} H3 event or res passed by h3 handler * @param name Name of the cookie to set * @param value Value of the cookie to set * @param options {CookieSerializeOptions} Options for serializing the cookie * ```ts * setCookie(res, 'Session', '<session data>') * ``` */ declare function setChunkedCookie(event: H3Event, name: string, value: string, options?: CookieSerializeOptions & { chunkMaxLength?: number; }): void; /** * Remove a set of chunked cookies by name. * @param event {H3Event} H3 event or res passed by h3 handler * @param name Name of the cookie to delete * @param serializeOptions {CookieSerializeOptions} Cookie options * ```ts * deleteCookie(res, 'Session') * ``` */ declare function deleteChunkedCookie(event: H3Event, name: string, serializeOptions?: CookieSerializeOptions): void; /** * A helper class for [server sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format) */ declare class EventStream { private readonly _event; private readonly _transformStream; private readonly _writer; private readonly _encoder; private _writerIsClosed; private _paused; private _unsentData; private _disposed; private _handled; private get _isClosed(); constructor(event: H3Event, opts?: EventStreamOptions); /** * Publish new event(s) for the client */ push(message: string): Promise<void>; push(message: string[]): Promise<void>; push(message: EventStreamMessage): Promise<void>; push(message: EventStreamMessage[]): Promise<void>; pushComment(comment: string): Promise<void>; private _sendEvent; private _sendEvents; pause(): void; get isPaused(): boolean; resume(): Promise<void>; flush(): Promise<void>; /** * Close the stream and the connection if the stream is being sent to the client */ close(): Promise<void>; /** * Triggers callback when the writable stream is closed. * It is also triggered after calling the `close()` method. */ onClosed(cb: () => any): void; send(): Promise<BodyInit>; } interface EventStreamOptions { /** * Automatically close the writable stream when the request is closed * * Default is `true` */ autoclose?: boolean; } /** * See https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#fields */ interface EventStreamMessage { id?: string; event?: string; retry?: number; data: string; } /** * Initialize an EventStream instance for creating [server sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) * * @experimental This function is experimental and might be unstable in some environments. * * @example * * ```ts * import { createEventStream } from "h3"; * * app.get("/sse", (event) => { * const eventStream = createEventStream(event); * * // Send a message every second * const interval = setInterval(async () => { * await eventStream.push("Hello world"); * }, 1000); * * // cleanup the interval and close the stream when the connection is terminated * eventStream.onClosed(async () => { * console.log("closing SSE..."); * clearInterval(interval); * await eventStream.close(); * }); * * return eventStream.send(); * }); * ``` */ declare function createEventStream(event: H3Event, opts?: EventStreamOptions): EventStream; /** * Append a `Server-Timing` entry to the response. * * Multiple calls append to the same header (comma-separated per spec). * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing * * @example * app.get("/", (event) => { * setServerTiming(event, "db", { dur: 53, desc: "Database query" }); * return { data: "..." }; * }); * // Response header: Server-Timing: db;desc="Database query";dur=53 */ declare function setServerTiming(event: H3Event, name: string, opts?: { dur?: number; desc?: string; }): void; /** * Measure an async operation and append the timing to the `Server-Timing` header. * * Uses `performance.now()` for high-resolution timing. * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing * * @example * app.get("/", async (event) => { * const users = await withServerTiming(event, "db", () => fetchUsers()); * return users; * }); * // Response header: Server-Timing: db;dur=42.5 */ declare function withServerTiming<T>(event: H3Event, name: string, fn: () => T | Promise<T>): Promise<T>; /** * Make sure the status message is safe to use in a response. * * Allowed characters: horizontal tabs, spaces or visible ascii characters: https://www.rfc-editor.org/rfc/rfc7230#section-3.1.2 */ declare function sanitizeStatusMessage(statusMessage?: string): string; /** * Make sure the status code is a valid HTTP status code. */ declare function sanitizeStatusCode(statusCode?: string | number, defaultStatusCode?: number): number; interface CacheConditions { modifiedTime?: string | Date; maxAge?: number; etag?: string; cacheControls?: string[]; } /** * Check request caching headers (`If-None-Match`, `If-Modified-Since`) and add caching headers (Last-Modified, ETag, Cache-Control). * * Note: `public` is added by default, but never alongside a caller-supplied `private`/`no-store` directive, so passing `cacheControls: ["private"]` no longer produces a contradictory `public, private`. * @returns `true` when cache headers are matching. When `true` is returned, no response should be sent anymore */ declare function handleCacheHeaders(event: H3Event, opts: CacheConditions): boolean; interface ResolveDotSegmentsOptions { /** * Also decode percent-encoded path separators (`%2f`, `%5c`) into real `/` * segment boundaries before resolving `.`/`..`. * * `event.url.pathname` never decodes `%2f` on its own, because doing so * would change how many segments a path has and therefore which route * matches — a correctness concern for dispatch, not just a security one * (e.g. `/files/:id` may rely on `%2F` to keep an id with a literal slash * as one opaque segment). So never use the result for routing/dispatch. * * Enable this for any out-of-band scope/security check whose result is * later handed to something that collapses `%2f` back to `/` on its own — * which is the common case, not an exotic one: an ordinary reverse proxy * (e.g. nginx with a trailing-slash `proxy_pass`) decodes `%2f`→`/` on every * request, so an encoded separator that dodges a narrower rule at match time * then escapes it downstream. If a scope check feeds a proxy or redirect * target, you almost certainly want this on. * * Decoding is pessimistic but bounded: it collapses a separator nested as * repeated whole `%25` prefixes (`%252f`, `%25252f`, ...) at any depth, so a * downstream that keeps `%25`-re-encoding and decoding cannot smuggle one * past. It does NOT catch a separator whose own hex digits are themselves * percent-encoded (`%25%32%66` → `%2f` → `/` after two decodes) — and that * form can appear even in an already-once-decoded pathname (from wire * `%2525%2532%2566`), so once-decoded input is not by itself sufficient. * Treat this as covering the common `%25`-nesting case, not as an absolute * guarantee against every multi-decode chain. Other escapes (e.g. `%20`) are * never decoded. * * @default false */ decodeSlashes?: boolean; } /** * Resolve `.` and `..` segments in a path, without ever escaping above the * root `/`. The result is always an absolute path with a single leading `/`, * so it can never be protocol-relative (`//host`). * * Also decodes percent-encoded dot segments at any `%25`-nesting depth * (`%2e`, `%252e`, ...) and normalizes `\` to `/`, so encoded or * backslash-based traversal (e.g. `%2e%2e/`, `..\..\`) is caught the same * way as a literal `../`. * * `%2f`/`%5c` (encoded path separators) are left untouched by default — see * {@link ResolveDotSegmentsOptions.decodeSlashes}. * * Only `.`/`..` resolution and the decodes above alter the string; every other * percent-encoding (`%20`, non-ASCII, `%3A`, and any `%2e` not forming a whole * segment) is left intact, so the result stays in the same representation as an * un-decoded `event.url.pathname` and matches routes/rules consistently. * Interior empty segments are preserved (`/a//b` stays `/a//b`, per WHATWG URL * normalization) — only the leading slash is guaranteed single, so a consumer * doing exact prefix matching should normalize its allowlist the same way. */ declare function resolveDotSegments(path: string, opts?: ResolveDotSegmentsOptions): string; interface StaticAssetMeta { type?: string; etag?: string; mtime?: number | string | Date; path?: string; size?: number; encoding?: string; } interface ServeStaticOptions { /** * This function should resolve asset meta */ getMeta: (id: string) => StaticAssetMeta | undefined | Promise<StaticAssetMeta | undefined>; /** * This function should resolve asset content */ getContents: (id: string) => BodyInit | null | undefined | Promise<BodyInit | null | undefined>; /** * Headers to set on the response */ headers?: HeadersInit; /** * Map of supported encodings (compressions) and their file extensions. * * Each extension will be appended to the asset path to find the compressed version of the asset. * * @example { gzip: ".gz", br: ".br" } */ encodings?: Record<string, string>; /** * Default index file to serve when the path is a directory * * @default ["/index.html"] */ indexNames?: string[]; /** * When set to true, the function will not throw 404 error when the asset meta is not found or meta validation failed */ fallthrough?: boolean; /** * Custom MIME type resolver function * @param ext - File extension including dot (e.g., ".css", ".js") */ getType?: (ext: string) => string | undefined; } /** * Dynamically serve static assets based on the request path. */ declare function serveStatic(event: H3Event, options: ServeStaticOptions): Promise<HTTPResponse | undefined>; /** * Returns a new event handler that removes the base url of the event before calling the original handler. * * @example * const api = new H3() * .get("/", () => "Hello API!"); * const app = new H3(); * .use("/api/**", withBase("/api", api.handler)); * * @param base The base path to prefix. * @param handler The event handler to use with the adapted path. */ declare function withBase(base: string, input: HTTPHandler): EventHandler; /** * Check if the origin is allowed. */ declare function isCorsOriginAllowed(origin: string | null | undefined, options: CorsOptions): boolean; interface CorsOptions { /** * This determines the value of the "access-control-allow-origin" response header. * If "*", it can be used to allow all origins. * If an array of strings or regular expressions, it can be used with origin matching. * If a custom function, it's used to validate the origin. It takes the origin as an argument and returns `true` if allowed. * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin * @default "*" */ origin?: "*" | "null" | (string | RegExp)[] | ((origin: string) => boolean); /** * This determines the value of the "access-control-allow-methods" response header of a preflight request. * * The default `"*"` permits any method (including non-safelisted ones like `QUERY`). * When using an explicit allowlist, remember that `QUERY` is **not** a CORS-safelisted * method, so browsers preflight it — include `"QUERY"` in the array to allow it. * * When `credentials` is enabled, browsers treat `"*"` as a literal method name — in that * case the requested method is reflected back instead of sending a literal `*`. * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods * @default "*" * @example ["GET", "HEAD", "PUT", "POST", "QUERY"] */ methods?: "*" | string[]; /** * This determines the value of the "access-control-allow-headers" response header of a preflight request. * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers * @default "*" */ allowHeaders?: "*" | string[]; /** * This determines the value of the "access-control-expose-headers" response header. * * When `credentials` is enabled, browsers treat `"*"` as a literal header name — in that * case the header is omitted; list the headers explicitly to expose them. * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers * @default "*" */ exposeHeaders?: "*" | string[]; /** * This determines the value of the "access-control-allow-credentials" response header. * When request with credentials, the options that `origin`, `methods`, `exposeHeaders` and `allowHeaders` should not be set "*". * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials * @see https://fetch.spec.wh