UNPKG

@tanstack/start-server-core

Version:

Modern and scalable routing for React applications

801 lines (800 loc) • 32.1 kB
import { getSetCookieValues } from "./headers.js"; import { isRedirect } from "@tanstack/router-core"; import { AsyncLocalStorage } from "node:async_hooks"; import { parseCookie, parseSetCookie, serializeCookie } from "cookie-es"; //#region src/internal-request-response.ts var GLOBAL_EVENT_STORAGE_KEY = Symbol.for("tanstack-start:event-storage"); var globalObj = globalThis; if (!globalObj[GLOBAL_EVENT_STORAGE_KEY]) globalObj[GLOBAL_EVENT_STORAGE_KEY] = new AsyncLocalStorage(); var eventStorage = globalObj[GLOBAL_EVENT_STORAGE_KEY]; var PROTECTED_RESPONSE_HEADERS = Symbol.for("tanstack-start:protected-response-headers"); function getProtectedResponseHeaders(response) { return response[PROTECTED_RESPONSE_HEADERS]; } function installProtectedResponseHeaders(response, headers) { Object.defineProperty(response, PROTECTED_RESPONSE_HEADERS, { value: headers, configurable: true }); } /** Transport requirements belong to the encoded body, not its wrapper. */ function transferResponseProtocol(source, target) { const protectedHeaders = getProtectedResponseHeaders(source); if (protectedHeaders && getProtectedResponseHeaders(target) !== protectedHeaders) installProtectedResponseHeaders(target, protectedHeaders); } /** Preserve metadata when Start reconstructs an existing response. */ function transferResponseMetadata(source, target) { transferResponseProtocol(source, target); const event = eventStorage.getStore(); const appliedHeaderAppends = event?.responseHeaderAppends?.get(source); if (appliedHeaderAppends) event.responseHeaderAppends.set(target, appliedHeaderAppends); } function normalizeHeaderName(name) { return name.toLowerCase(); } function sanitizeStatusMessage(statusMessage) { return statusMessage.replace(/[^\t\x20-\x7e]/g, ""); } function sanitizeStatusCode(statusCode) { const code = typeof statusCode === "string" ? Number(statusCode) : statusCode; if (code === void 0 || !Number.isInteger(code) || code < 200 || code > 599) return; return code; } function getObjectProperty(value, key) { if (typeof value === "object" && value || typeof value === "function") return value[key]; } function getStatusCodeProperty(value, key) { const property = getObjectProperty(value, key); if (typeof property === "string" || typeof property === "number") return property; } function getErrorStatus(error) { const cause = getObjectProperty(error, "cause"); return sanitizeStatusCode(getStatusCodeProperty(error, "status") ?? getStatusCodeProperty(error, "statusCode") ?? getStatusCodeProperty(cause, "status") ?? getStatusCodeProperty(cause, "statusCode")); } function getErrorStatusText(error) { const cause = getObjectProperty(error, "cause"); const statusText = getObjectProperty(error, "statusText") ?? getObjectProperty(error, "statusMessage") ?? getObjectProperty(cause, "statusText") ?? getObjectProperty(cause, "statusMessage"); if (typeof statusText === "string") return sanitizeStatusMessage(statusText); } var GENERATED_BODY_EXCLUDED_HEADERS = /* @__PURE__ */ new Set([ "connection", "content-encoding", "content-length", "keep-alive", "proxy-connection", "te", "trailer", "transfer-encoding" ]); function isResponse(value) { return value instanceof Response || Object.prototype.toString.call(value) === "[object Response]"; } function getErrorHeaders(error) { let init = getObjectProperty(error, "headers"); if (!init) { const cause = getObjectProperty(error, "cause"); if (isResponse(cause)) return; init = getObjectProperty(cause, "headers"); } if (!init) return; let headers; try { headers = new Headers(init); } catch { return; } for (const name of GENERATED_BODY_EXCLUDED_HEADERS) headers.delete(name); return headers; } function getResponseState(event) { return event.responseState ||= { headers: new Headers(), clearHeaders: false }; } function setHeaderState(event, name, value) { const state = getResponseState(event); const normalizedName = normalizeHeaderName(name); state.headers.set(name, value); state.headerAppends?.delete(normalizedName); state.removedHeaders?.delete(normalizedName); if (normalizedName === "set-cookie") state.setCookieBehavior = "replace"; } function appendHeaderState(event, name, value) { const state = getResponseState(event); const normalizedName = normalizeHeaderName(name); const appendToReturnedHeader = normalizedName !== "set-cookie" && !state.clearHeaders && !state.removedHeaders?.has(normalizedName) && (!state.headers.has(normalizedName) || state.headerAppends?.has(normalizedName)); state.headers.append(name, value); if (appendToReturnedHeader) { const appends = state.headerAppends ||= /* @__PURE__ */ new Map(); appends.set(normalizedName, { value, previous: appends.get(normalizedName) }); } state.removedHeaders?.delete(normalizedName); if (normalizedName === "set-cookie" && state.setCookieBehavior !== "replace") state.setCookieBehavior = "merge"; } function deleteHeaderState(event, name) { const state = getResponseState(event); const normalizedName = normalizeHeaderName(name); state.headers.delete(name); state.headerAppends?.delete(normalizedName); (state.removedHeaders ||= /* @__PURE__ */ new Set()).add(normalizedName); if (normalizedName === "set-cookie") state.setCookieBehavior = "replace"; } function getDistinctCookieKey(name, options, defaultPath = "") { return [ name, options.domain || "", options.path ?? defaultPath ].join(";"); } function getDistinctCookieKeyFromHeader(cookie) { const parsed = parseSetCookie(cookie); return parsed ? getDistinctCookieKey(parsed.name, parsed) : `\0${cookie}`; } function replaceSetCookieValues(headers, cookies) { headers.delete("set-cookie"); for (const cookie of cookies) headers.append("set-cookie", cookie); } function getCookieKey(state, cookie) { const keys = state.setCookieKeys ||= /* @__PURE__ */ new Map(); let key = keys.get(cookie); if (key === void 0) { key = getDistinctCookieKeyFromHeader(cookie); keys.set(cookie, key); } return key; } function getMergedSetCookieValues(state, currentCookies, cookiesToMerge) { if (currentCookies.length === 0) return cookiesToMerge; const cookieKeysToMerge = /* @__PURE__ */ new Set(); for (const cookie of cookiesToMerge) cookieKeysToMerge.add(getCookieKey(state, cookie)); return currentCookies.filter((cookie) => !cookieKeysToMerge.has(getCookieKey(state, cookie))).concat(cookiesToMerge); } function mergeStartSetCookieValues(event, cookies) { if (cookies.length === 0) return; const state = getResponseState(event); state.removedHeaders?.delete("set-cookie"); if (state.setCookieBehavior !== "replace") state.setCookieBehavior = "merge"; replaceSetCookieValues(state.headers, getMergedSetCookieValues(state, getSetCookieValues(state.headers), cookies)); } function hasProtectedHeaderChanges(response, protectedHeaders) { const headers = response.headers; for (const [name, value] of protectedHeaders) if (headers.get(name) !== value) return true; return false; } function applyProtectedHeaders(protectedHeaders, headers) { for (const [name, value] of protectedHeaders) if (value === null) headers.delete(name); else headers.set(name, value); } function applyHeaderState(target, state, generatedBody, protectedHeaders, appliedHeaderAppends) { let headers = target; const mutableHeaders = () => { if (headers === target) headers = new Headers(target); return headers; }; if (state.clearHeaders) { for (const name of Array.from(target.keys())) if (!protectedHeaders?.has(name)) mutableHeaders().delete(name); } if (!state.clearHeaders && state.removedHeaders) { for (const name of state.removedHeaders) if (!protectedHeaders?.has(name) && headers.has(name)) mutableHeaders().delete(name); } for (const [name, value] of state.headers) if (name !== "set-cookie" && !protectedHeaders?.has(name) && !(generatedBody && GENERATED_BODY_EXCLUDED_HEADERS.has(name))) { let append = state.headerAppends?.get(name); if (!append) { if (headers.get(name) !== value) mutableHeaders().set(name, value); continue; } const applied = appliedHeaderAppends?.get(name); const pending = []; while (append && append !== applied) { pending.push(append.value); append = append.previous; } for (let index = pending.length - 1; index >= 0; index--) mutableHeaders().append(name, pending[index]); } if (state.setCookieBehavior && !protectedHeaders?.has("set-cookie")) { const eventSetCookies = getSetCookieValues(state.headers); const currentCookies = getSetCookieValues(headers); const cookies = state.setCookieBehavior === "replace" ? eventSetCookies : getMergedSetCookieValues(state, currentCookies, eventSetCookies); if (cookies.length !== currentCookies.length || cookies.some((cookie, index) => cookie !== currentCookies[index])) replaceSetCookieValues(mutableHeaders(), cookies); } return headers; } function canHaveBody(method, status) { return method !== "HEAD" && status !== 204 && status !== 205 && status !== 304; } /** A protected content type marks a Start-serialized body the client decodes. */ function isSerializedResponse(protectedHeaders) { return typeof protectedHeaders?.get("content-type") === "string"; } /** * A helper status that forbids a body never applies to a serialized * server-function reply, because the client must decode it. Only a HEAD request * drops its body. */ function getHelperStatus(state, serialized) { const status = state?.status; if (!serialized || status === void 0 || canHaveBody("GET", status)) return status; if (process.env.NODE_ENV !== "production" && !state.bodylessStatusWarned) { state.bodylessStatusWarned = true; console.warn(`setResponseStatus(${status}) does not apply to serialized server function responses, because the client must decode their body. Return a Response from the server function to send a response without a body.`); } } /** Helper status text describes the helper status and is ignored with it. */ function getHelperStatusText(state, helperStatus) { return helperStatus === state?.status ? state?.statusText : void 0; } function cancelDroppedBody(response, reason) { try { response.body?.cancel(reason).catch(() => {}); } catch {} } function createReconciledResponse(body, status, statusText, headers) { try { return new Response(body, { status, statusText, headers }); } catch (cause) { throw new Error("Unable to reconcile response because its body has already been consumed or locked.", { cause }); } } /** Apply helper state to a response that leaves Start. */ function finalizeResponse(response, event) { return applyResponseState(response, event, event.request.method); } /** * Apply helper state with the body rules of `method`. Request middleware still * reads a HEAD response's body, so the pipeline applies GET rules and strips a * HEAD body only when the response leaves it. */ function applyResponseState(response, event, method, disposeBody) { const protectedHeaders = getProtectedResponseHeaders(response); const serialized = isSerializedResponse(protectedHeaders); let helperStatus = getHelperStatus(event.responseState, serialized); const mustDropBody = !canHaveBody(method, helperStatus ?? response.status) && response.body !== null; if (mustDropBody) { const reason = method === "HEAD" ? "HEAD body stripped" : "Response body dropped by Start reconciliation"; if (disposeBody) disposeBody(reason); else cancelDroppedBody(response, reason); helperStatus = getHelperStatus(event.responseState, serialized); } const state = event.responseState; if (!state && !mustDropBody) { event.currentResponse = response; return response; } const status = helperStatus ?? response.status; const statusText = getHelperStatusText(state, helperStatus) ?? response.statusText; const statusChanged = status !== response.status; const statusTextChanged = statusText !== response.statusText; const headers = state ? applyHeaderState(response.headers, state, serialized, protectedHeaders, event.responseHeaderAppends?.get(response)) : response.headers; const reconciled = statusChanged || statusTextChanged || mustDropBody || headers !== response.headers ? createReconciledResponse(mustDropBody ? null : response.body, status, statusText, headers) : response; if (protectedHeaders && reconciled !== response) installProtectedResponseHeaders(reconciled, protectedHeaders); return publishResponse(reconciled, event); } function publishResponse(response, event) { const headerAppends = event.responseState?.headerAppends; if (headerAppends?.size) (event.responseHeaderAppends ||= /* @__PURE__ */ new WeakMap()).set(response, new Map(headerAppends)); event.currentResponse = response; return response; } function getHttpErrorStatus(status) { return status !== void 0 && status >= 400 ? status : void 0; } /** * Status for a response built from an uncaught error. Only an error status * (400-599) from a helper or from error metadata describes the failure, so a * success or redirect status set before the throw cannot hide it. The error is * logged unless its own metadata marks it as an HTTP error. Reporting runs * before helper state is read, so hooks that write helpers are included. */ function resolveErrorStatus(error, event) { const metadataStatus = getErrorStatus(error); const errorStatus = getHttpErrorStatus(metadataStatus); if (errorStatus === void 0) console.error(error); const state = event?.responseState; const helperStatus = getHttpErrorStatus(state?.status); const status = helperStatus ?? errorStatus ?? 500; const statusText = getHelperStatusText(state, helperStatus) ?? (status === errorStatus || metadataStatus === void 0 ? getErrorStatusText(error) : void 0) ?? ""; if (state && state.status !== helperStatus) { state.status = void 0; state.statusText = void 0; } return { status, statusText }; } /** * 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. */ function resolveErrorResponseStatus(error) { return resolveErrorStatus(error, eventStorage.getStore()); } function createErrorResponse(error, event) { let headers = getErrorHeaders(error) ?? new Headers(); const { status, statusText } = resolveErrorStatus(error, event); const state = event.responseState; const body = canHaveBody(event.request.method, status) ? JSON.stringify({ status, statusText, unhandled: true, message: "HTTPError" }) : null; if (!headers.has("content-type")) headers.set("content-type", "application/json"); if (state) headers = applyHeaderState(headers, state, true); return publishResponse(new Response(body !== null && !headers.has("content-type") ? new TextEncoder().encode(body) : body, { status, statusText, headers }), event); } function finalizeError(error, event) { if (error instanceof Response) return finalizeResponse(error, event); return createErrorResponse(error, event); } function handleStartError(error) { const event = eventStorage.getStore(); if (event) return finalizeError(error, event); if (error instanceof Response) return error; const request = new Request("http://localhost"); return createErrorResponse(error, { request, requestUrl: new URL(request.url) }); } /** Apply helper state to a response inside the request middleware pipeline. */ function reconcileResponse(response, event, disposeBody) { if (!event.responseState && (response === event.currentResponse || canHaveBody("GET", response.status)) || isRedirect(response)) { event.currentResponse = response; return response; } return applyResponseState(response, event, "GET", disposeBody); } /** * 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. */ function restoreResponseProtocol(response, event) { const protectedHeaders = getProtectedResponseHeaders(response); if (!protectedHeaders || !hasProtectedHeaderChanges(response, protectedHeaders)) return response; const headers = new Headers(response.headers); applyProtectedHeaders(protectedHeaders, headers); const restored = createReconciledResponse(response.body, response.status, response.statusText, headers); transferResponseMetadata(response, restored); event.currentResponse = restored; return restored; } /** * 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. */ function createFinalizedResponse(body, headers, protectedHeaders, event) { const state = event.responseState; let status = 200; let statusText = ""; if (state) { const helperStatus = getHelperStatus(state, true); status = helperStatus ?? 200; statusText = getHelperStatusText(state, helperStatus) ?? ""; headers = applyHeaderState(headers, state, true, protectedHeaders); } const response = new Response(canHaveBody(event.request.method, status) ? body : null, { status, statusText, headers }); installProtectedResponseHeaders(response, protectedHeaders); return publishResponse(response, event); } function protectResponseHeaders(response, headers) { const previous = getProtectedResponseHeaders(response); if (previous === headers) return; if (previous) { const merged = new Map(previous); for (const [name, value] of headers) merged.set(name, value); headers = merged; } installProtectedResponseHeaders(response, headers); } /** * Set and protect transport headers on a new response. Reuse the body stream * without cloning or teeing it; callers transfer ownership to the result. */ function setProtectedResponseHeaders(response, headers) { let next; try { next = new Response(response.body, response); applyProtectedHeaders(headers, next.headers); } catch (cause) { throw new Error("Unable to set response header because its body has already been consumed or locked.", { cause }); } transferResponseMetadata(response, next); protectResponseHeaders(next, headers); return next; } function decodePathname(pathname) { try { return decodeURI(pathname); } catch (error) { if (error instanceof URIError) return; throw error; } } function runInStartRequest(request, requestOpts, handler) { let requestUrl; try { requestUrl = new URL(request.url); } catch (error) { if (error instanceof TypeError) return new Response(null, { status: 400, statusText: "Bad Request" }); throw error; } const pathname = requestUrl.pathname; if (pathname.includes("%") && decodePathname(pathname) === void 0) return new Response(null, { status: 400, statusText: "Bad Request" }); const event = { request, requestUrl }; return eventStorage.run(event, handler, request, requestOpts, event); } /** * 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. */ function createServerEntry(entry) { return { fetch: withStartRequest(async (request, requestOpts) => { try { return await entry.fetch(request, requestOpts); } catch (error) { return handleStartError(error); } }) }; } /** * 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. */ function withStartRequest(handler) { return (request, requestOpts) => { const event = eventStorage.getStore(); if (event?.request === request) return handler(request, requestOpts, event); return runInStartRequest(request, requestOpts, handler); }; } function getStartEvent() { const event = eventStorage.getStore(); if (!event) throw new Error(`No StartEvent found in AsyncLocalStorage. Make sure you are using the function within the server runtime.`); return event; } /** * 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. */ function getParsedRequestUrl(request) { const event = eventStorage.getStore(); if (event && event.request === request) return event.requestUrl; return new URL(request.url); } function getRequest() { return getStartEvent().request; } function getRequestHeaders() { return getStartEvent().request.headers; } function getRequestHeader(name) { return getRequestHeaders().get(name) ?? void 0; } function getRequestIP(opts) { const request = getRequest(); if (opts?.xForwardedFor) { const forwardedIp = request.headers.get("x-forwarded-for")?.split(",", 1)[0]?.trim(); if (forwardedIp) return forwardedIp; } return request.context?.clientAddress || request.ip || void 0; } /** * 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. */ function getRequestHost(opts) { const headers = getRequestHeaders(); if (opts?.xForwardedHost) { const host = headers.get("x-forwarded-host")?.split(",", 1)[0]?.trim(); if (host) return host; } return headers.get("host") || getStartEvent().requestUrl.host || "localhost"; } /** * 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. */ function getRequestUrl(opts) { const event = getStartEvent(); const url = new URL(event.requestUrl); url.protocol = getRequestProtocol(opts); if (opts?.xForwardedHost) { const host = getRequestHost(opts); if (host) { url.host = host; if (!/:\d+$/.test(host)) url.port = ""; } } return 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". */ function getRequestProtocol(opts) { const request = getRequest(); if (opts?.xForwardedProto !== false) { const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",", 1)[0]?.trim().toLowerCase(); if (forwardedProto === "https") return "https"; if (forwardedProto === "http") return "http"; } return getStartEvent().requestUrl.protocol.slice(0, -1); } function setResponseHeaders(headers) { if (headers instanceof Headers) { for (const [name, value] of headers) if (name !== "set-cookie") setResponseHeader(name, value); const cookies = getSetCookieValues(headers); if (cookies.length > 0) setResponseHeader("set-cookie", cookies.length === 1 ? cookies[0] : cookies); return; } if (!Array.isArray(headers)) { for (const [name, value] of Object.entries(headers)) setResponseHeader(name, value); return; } const groupedHeaders = /* @__PURE__ */ new Map(); const addHeader = (name, value) => { const normalizedName = normalizeHeaderName(name); let header = groupedHeaders.get(normalizedName); if (!header) { header = { name, values: [] }; groupedHeaders.set(normalizedName, header); } header.values.push(value); }; for (const [name, value] of headers) addHeader(name, value); for (const { name, values } of groupedHeaders.values()) setResponseHeader(name, values.length === 1 ? values[0] : values); } var SnapshotHeaders; /** * Outside production, return the snapshot as a Headers subclass that warns when * code tries to change the outgoing response through it. Its contents and * behavior are identical to the production snapshot. */ function toResponseHeadersSnapshot(headers) { if (process.env.NODE_ENV === "production") return headers; SnapshotHeaders ??= class ResponseHeadersSnapshot extends Headers { append(name, value) { warnSnapshotMutation("append"); super.append(name, value); } delete(name) { warnSnapshotMutation("delete"); super.delete(name); } set(name, value) { warnSnapshotMutation("set"); super.set(name, value); } }; const entries = []; for (const [name, value] of headers) if (name !== "set-cookie") entries.push([name, value]); for (const cookie of getSetCookieValues(headers)) entries.push(["set-cookie", cookie]); return new SnapshotHeaders(entries); } function warnSnapshotMutation(method) { console.warn(`getResponseHeaders().${method}() does not change the response: getResponseHeaders() returns a read-only snapshot. Use setResponseHeader, appendResponseHeader, removeResponseHeader, clearResponseHeaders, or setCookie instead.`); } /** * Read a detached snapshot of the effective response headers. * Use response header and cookie helpers to change the outgoing response. */ function getResponseHeaders() { const event = getStartEvent(); const state = event.responseState; const currentResponse = event.currentResponse; if (!currentResponse) return toResponseHeadersSnapshot(state ? new Headers(state.headers) : new Headers()); const protectedHeaders = getProtectedResponseHeaders(currentResponse); let headers = currentResponse.headers; if (state) headers = applyHeaderState(headers, state, isSerializedResponse(protectedHeaders), protectedHeaders, event.responseHeaderAppends?.get(currentResponse)); if (headers === currentResponse.headers) headers = new Headers(headers); if (protectedHeaders) applyProtectedHeaders(protectedHeaders, headers); return toResponseHeadersSnapshot(headers); } function getResponseHeader(name) { const event = getStartEvent(); const normalizedName = normalizeHeaderName(name); if (normalizedName === "set-cookie" || event.responseState?.headerAppends?.has(normalizedName)) return getResponseHeaders().get(name) ?? void 0; const currentResponse = event.currentResponse; const protectedHeaders = currentResponse ? getProtectedResponseHeaders(currentResponse) : void 0; if (protectedHeaders?.has(normalizedName)) return protectedHeaders.get(normalizedName) ?? void 0; if (GENERATED_BODY_EXCLUDED_HEADERS.has(normalizedName) && isSerializedResponse(protectedHeaders)) return currentResponse.headers.get(name) ?? void 0; const state = event.responseState; if (state) { const stateValue = state.headers.get(name); if (stateValue !== null) return stateValue; if (state.clearHeaders) return; if (state.removedHeaders?.has(normalizedName)) return; } return currentResponse?.headers.get(name) ?? void 0; } function setResponseHeader(name, value) { const event = getStartEvent(); if (Array.isArray(value)) { deleteHeaderState(event, name); for (const valueItem of value) appendHeaderState(event, name, valueItem); } else setHeaderState(event, name, value); } /** * 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)) * ``` */ function appendResponseHeader(name, value) { const event = getStartEvent(); const values = Array.isArray(value) ? value : [value]; if (normalizeHeaderName(name) === "set-cookie") { for (const valueItem of values) { const headers = new Headers({ "set-cookie": valueItem }); mergeStartSetCookieValues(event, getSetCookieValues(headers)); } return; } for (const valueItem of values) appendHeaderState(event, name, valueItem); } function removeResponseHeader(name) { deleteHeaderState(getStartEvent(), name); } function clearResponseHeaders(headerNames) { const event = getStartEvent(); if (headerNames && headerNames.length > 0) { for (const name of headerNames) deleteHeaderState(event, name); return; } const state = getResponseState(event); state.clearHeaders = true; state.headers = new Headers(); state.headerAppends = void 0; } function getResponseStatus() { const event = getStartEvent(); const currentResponse = event.currentResponse; const serialized = !!currentResponse && isSerializedResponse(getProtectedResponseHeaders(currentResponse)); return getHelperStatus(event.responseState, serialized) ?? currentResponse?.status ?? 200; } function setResponseStatus(code, text) { const event = getStartEvent(); if (code !== void 0) { const status = sanitizeStatusCode(code); if (status === void 0) { if (process.env.NODE_ENV !== "production") console.warn(`setResponseStatus(${code}) was ignored: Fetch Response status codes must be integers from 200 to 599.`); return; } getResponseState(event).status = status; } if (text) getResponseState(event).statusText = sanitizeStatusMessage(text); } /** * 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() * ``` */ function getCookies() { const cookies = parseCookie(getRequestHeaders().get("cookie") || ""); const definedCookies = Object.create(null); for (const [name, value] of Object.entries(cookies)) if (value !== void 0) definedCookies[name] = value; return definedCookies; } /** * 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') * ``` */ function getCookie(name) { return getCookies()[name]; } /** * 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') * ``` */ function setCookie(name, value, options) { const { encode, stringify, ...attrs } = options ?? {}; mergeStartSetCookieValues(getStartEvent(), [serializeCookie({ name, value, path: "/", ...attrs }, { encode, stringify })]); } /** * Remove a cookie by name. * @param name Name of the cookie to delete * @param serializeOptions {CookieSerializeOptions} Cookie options * ```ts * deleteCookie('SessionId') * ``` */ function deleteCookie(name, options) { setCookie(name, "", { ...options, maxAge: 0 }); } /** * Internal: helper status state for a serialized server-function reply. * Bodyless statuses never apply to it and are omitted with their status text. */ function getSerializedResponseState() { const state = getStartEvent().responseState; const status = getHelperStatus(state, true); return { status, statusText: getHelperStatusText(state, status) }; } //#endregion export { appendResponseHeader, clearResponseHeaders, createFinalizedResponse, createServerEntry, deleteCookie, finalizeResponse, getCookie, getCookies, getErrorHeaders, getParsedRequestUrl, getRequest, getRequestHeader, getRequestHeaders, getRequestHost, getRequestIP, getRequestProtocol, getRequestUrl, getResponseHeader, getResponseHeaders, getResponseStatus, getSerializedResponseState, getStartEvent, handleStartError, protectResponseHeaders, reconcileResponse, removeResponseHeader, resolveErrorResponseStatus, restoreResponseProtocol, setCookie, setProtectedResponseHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, transferResponseMetadata, transferResponseProtocol, withStartRequest }; //# sourceMappingURL=internal-request-response.js.map