UNPKG

@tanstack/start-server-core

Version:

Modern and scalable routing for React applications

234 lines (233 loc) • 10.8 kB
import { RequestHeaderMap, RequestHeaderName, ResponseHeaderMap, ResponseHeaderName, TypedHeaders } from 'fetchdts'; import { CookieSerializeOptions } from 'cookie-es'; import { RequestHandler } from './request-handler.js'; export interface StartEvent { request: Request; /** The parsed request URL. Callers must not mutate it. */ requestUrl: URL; /** * Lazily created response state. Stays `undefined` until the first * response helper write so requests that never use helpers pay nothing. */ responseState?: ResponseState; /** The most recently reconciled response, used by response read helpers. */ currentResponse?: Response; /** Applied helper operations belong to this invocation, across runtime copies. */ responseHeaderAppends?: WeakMap<Response, Map<string, HeaderAppendOperation>>; } /** * Event-owned mutable response state, recorded by the response helpers. * * A plain `Headers` object cannot distinguish between "not set" and * "explicitly removed", so removals and Set-Cookie intent are tracked * alongside the headers. All mutations flow through the helper functions in * this module (`setResponseHeader`, `setCookie`, ...), which record that * intent directly. */ interface ResponseState { status?: number; statusText?: string; headers: Headers; removedHeaders?: Set<string>; clearHeaders: boolean; setCookieBehavior?: 'merge' | 'replace'; headerAppends?: Map<string, HeaderAppendOperation>; /** Outside production: a discarded bodyless status was already reported. */ bodylessStatusWarned?: boolean; /** Identity of each Set-Cookie string seen by this request's merges. */ setCookieKeys?: Map<string, string>; } interface HeaderAppendOperation { value: string; previous?: HeaderAppendOperation; } export type { ResponseHeaderName, RequestHeaderName }; /** A detached view of outgoing headers. Use the response helpers for writes. */ export type ReadonlyResponseHeaders = Omit<TypedHeaders<ResponseHeaderMap>, 'append' | 'delete' | 'set' | 'forEach'> & { forEach: (callback: (value: string, name: ResponseHeaderName, headers: ReadonlyResponseHeaders) => void, thisArg?: unknown) => void; }; type ProtectedHeaders = ReadonlyMap<string, string | null>; type MaybePromise<T> = T | Promise<T>; /** Transport requirements belong to the encoded body, not its wrapper. */ export declare function transferResponseProtocol(source: Response, target: Response): void; /** Preserve metadata when Start reconstructs an existing response. */ export declare function transferResponseMetadata(source: Response, target: Response): void; export declare function getErrorHeaders(error: unknown): Headers | undefined; /** Apply helper state to a response that leaves Start. */ export declare function finalizeResponse(response: Response, event: StartEvent): Response; /** * Internal: status and status text for a serialized server-function error. * Logs the error and discards a non-error helper status, like any error * response Start builds. */ export declare function resolveErrorResponseStatus(error: unknown): { status: number; statusText: string; }; export declare function handleStartError(error: unknown): Response; /** Apply helper state to a response inside the request middleware pipeline. */ export declare function reconcileResponse(response: Response, event: StartEvent, disposeBody?: (reason: string) => void): Response; /** * Restore protocol headers that middleware changed directly on the response. * Start does this once, when the response leaves the request middleware * pipeline; helper writes never override them. */ export declare function restoreResponseProtocol(response: Response, event: StartEvent): Response; /** * Build a Start-owned serialized reply with already normalized protocol * headers. A bodyless helper status never applies to it; only a HEAD request * drops its body. */ export declare function createFinalizedResponse(body: string | Uint8Array, headers: Headers, protectedHeaders: ProtectedHeaders, event: StartEvent): Response; export declare function protectResponseHeaders(response: Response, headers: ProtectedHeaders): void; /** * Set and protect transport headers on a new response. Reuse the body stream * without cloning or teeing it; callers transfer ownership to the result. */ export declare function setProtectedResponseHeaders(response: Response, headers: ProtectedHeaders): Response; /** A Start handler that runs in a request scope and receives its event. */ type ScopedRequestHandler = (request: Request, requestOpts: any, event: StartEvent) => MaybePromise<Response>; /** * Keep the complete server entry, including custom error handling, inside one * request scope. A custom returned response is used as-is; call handleStartError * in a custom catch to preserve Start's response helper state. */ export declare function createServerEntry<TRegister = unknown>(entry: { fetch: RequestHandler<TRegister>; }): { fetch: RequestHandler<TRegister>; }; /** * Establish request scope; the response pipeline owns reconciliation. A nested * handler delegating the same request participates in its scope. Independent * fetch invocations and different requests get a fresh event. */ export declare function withStartRequest(handler: ScopedRequestHandler): (request: Request, requestOpts: any) => MaybePromise<Response>; export declare function getStartEvent(): StartEvent; /** * Internal: returns the per-request parsed URL for `request`, memoized on the * Start event so the framework parses each request URL only once. Falls back * to parsing when called outside a Start event (e.g. in unit tests) or for a * foreign request. Callers MUST NOT mutate the returned URL. */ export declare function getParsedRequestUrl(request: Request): URL; export declare function getRequest(): Request; export declare function getRequestHeaders(): TypedHeaders<RequestHeaderMap>; export declare function getRequestHeader(name: RequestHeaderName): string | undefined; export declare function getRequestIP(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; /** * 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 uses the request URL's host. */ export declare function getRequestHost(opts?: { xForwardedHost?: boolean; }): string; /** * Get 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. */ export declare function getRequestUrl(opts?: { xForwardedHost?: boolean; xForwardedProto?: boolean; }): URL; /** * Get the request protocol. * * If `x-forwarded-proto` header is set to "https", it will return "https". You can disable this behavior by setting `xForwardedProto` to `false`. * * If protocol cannot be determined, it will default to "http". */ export declare function getRequestProtocol(opts?: { xForwardedProto?: boolean; }): 'http' | 'https' | (string & {}); export declare function setResponseHeaders(headers: TypedHeaders<ResponseHeaderMap> | ReadonlyResponseHeaders): void; /** * Read a detached snapshot of the effective response headers. * Use response header and cookie helpers to change the outgoing response. */ export declare function getResponseHeaders(): ReadonlyResponseHeaders; export declare function getResponseHeader(name: ResponseHeaderName): string | undefined; export declare function setResponseHeader(name: ResponseHeaderName, value: string | Array<string>): void; /** * Append a response header value without replacing existing values. * * Ordinary values are appended once per returned response. Start transfers * that application state when it reconstructs a response. A new response * created by application code is fresh input: copying already-reconciled * headers into it also copies their appended values, and helper appends apply * again. Mutate the current response or pass its middleware result through * when preserving an existing response's headers. * * For `set-cookie`, values must be fully serialized cookie strings; they are * merged into the outgoing cookies and deduped by cookie identity * (name + domain + path), exactly like `setCookie`. A value that cannot be * parsed is identified by its exact string. This is the primitive to * use when bridging external session/auth libraries that produce raw * `Set-Cookie` strings. * * ```ts * appendResponseHeader('set-cookie', await externalLib.commitSession(session)) * ``` */ export declare function appendResponseHeader(name: ResponseHeaderName, value: string | Array<string>): void; export declare function removeResponseHeader(name: ResponseHeaderName): void; export declare function clearResponseHeaders(headerNames?: Array<ResponseHeaderName>): void; export declare function getResponseStatus(): number; export declare function setResponseStatus(code?: number, text?: string): void; /** * Parse the request to get HTTP Cookie header string and return an object of all cookie name-value pairs. * @returns Object of cookie name-value pairs * ```ts * const cookies = getCookies() * ``` */ export declare function getCookies(): Record<string, string>; /** * Get a cookie value by name. * @param name Name of the cookie to get * @returns {*} Value of the cookie (String or undefined) * ```ts * const authorization = getCookie('Authorization') * ``` */ export declare function getCookie(name: string): string | undefined; /** * Set a cookie value by name. * @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('Authorization', '1234567') * ``` */ export declare function setCookie(name: string, value: string, options?: CookieSerializeOptions): void; /** * Remove a cookie by name. * @param name Name of the cookie to delete * @param serializeOptions {CookieSerializeOptions} Cookie options * ```ts * deleteCookie('SessionId') * ``` */ export declare function deleteCookie(name: string, options?: CookieSerializeOptions): void; /** * Internal: helper status state for a serialized server-function reply. * Bodyless statuses never apply to it and are omitted with their status text. */ export declare function getSerializedResponseState(): { status: number | undefined; statusText: string | undefined; };