neue Homepage auf react basis

This commit is contained in:
2026-08-07 21:39:30 +02:00
parent 164bdd19ad
commit ee89442c04
52191 changed files with 1230242 additions and 58 deletions
@@ -0,0 +1,66 @@
import { ActionFunctionArgs, LoaderFunctionArgs, RouterContextProvider } from "../router/utils.js";
import { AssetsManifest, CriticalCss, EntryContext, FutureConfig } from "../dom/ssr/entry.js";
import { ServerRouteManifest } from "./routes.js";
import { ServerInstrumentation } from "../router/instrumentation.js";
//#region lib/server-runtime/build.d.ts
type OptionalCriticalCss = CriticalCss | undefined;
/**
* The output of the compiler for the server build.
*/
interface ServerBuild {
entry: {
module: ServerEntryModule;
};
routes: ServerRouteManifest;
assets: AssetsManifest;
basename?: string;
publicPath: string;
assetsBuildDirectory: string;
future: FutureConfig;
ssr: boolean;
unstable_getCriticalCss?: (args: {
pathname: string;
}) => OptionalCriticalCss | Promise<OptionalCriticalCss>;
/**
* @deprecated This is now done via a custom header during prerendering
*/
isSpaMode: boolean;
prerender: string[];
routeDiscovery: {
mode: "lazy" | "initial";
manifestPath: string;
};
allowedActionOrigins?: string[] | false;
}
interface HandleDocumentRequestFunction {
(request: Request, responseStatusCode: number, responseHeaders: Headers, context: EntryContext, loadContext: RouterContextProvider): Promise<Response> | Response;
}
interface HandleDataRequestFunction {
(response: Response, args: {
request: LoaderFunctionArgs["request"] | ActionFunctionArgs["request"];
context: LoaderFunctionArgs["context"] | ActionFunctionArgs["context"];
params: LoaderFunctionArgs["params"] | ActionFunctionArgs["params"];
}): Promise<Response> | Response;
}
interface HandleErrorFunction {
(error: unknown, args: {
request: LoaderFunctionArgs["request"] | ActionFunctionArgs["request"];
context: LoaderFunctionArgs["context"] | ActionFunctionArgs["context"];
params: LoaderFunctionArgs["params"] | ActionFunctionArgs["params"];
}): void;
}
/**
* A module that serves as the entry point for a Remix app during server
* rendering.
*/
interface ServerEntryModule {
default: HandleDocumentRequestFunction;
handleDataRequest?: HandleDataRequestFunction;
handleError?: HandleErrorFunction;
instrumentations?: ServerInstrumentation[];
streamTimeout?: number;
}
//#endregion
export { HandleDataRequestFunction, HandleDocumentRequestFunction, HandleErrorFunction, ServerBuild, ServerEntryModule };
@@ -0,0 +1,92 @@
import { CookieParseOptions as CookieParseOptions$1, CookieSerializeOptions as CookieSerializeOptions$1 } from "cookie-es";
//#region lib/server-runtime/cookies.d.ts
interface CookieSignatureOptions {
/**
* An array of secrets that may be used to sign/unsign the value of a cookie.
*
* The array makes it easy to rotate secrets. New secrets should be added to
* the beginning of the array. `cookie.serialize()` will always use the first
* value in the array, but `cookie.parse()` may use any of them so that
* cookies that were signed with older secrets still work.
*/
secrets?: string[];
}
type CookieOptions = CookieParseOptions$1 & CookieSerializeOptions$1 & CookieSignatureOptions;
/**
* A HTTP cookie.
*
* A Cookie is a logical container for metadata about a HTTP cookie; its name
* and options. But it doesn't contain a value. Instead, it has `parse()` and
* `serialize()` methods that allow a single instance to be reused for
* parsing/encoding multiple different values.
*
* @see https://remix.run/utils/cookies#cookie-api
*/
interface Cookie {
/**
* The name of the cookie, used in the `Cookie` and `Set-Cookie` headers.
*/
readonly name: string;
/**
* True if this cookie uses one or more secrets for verification.
*/
readonly isSigned: boolean;
/**
* The Date this cookie expires.
*
* Note: This is calculated at access time using `maxAge` when no `expires`
* option is provided to `createCookie()`.
*/
readonly expires?: Date;
/**
* Parses a raw `Cookie` header and returns the value of this cookie or
* `null` if it's not present.
*/
parse(cookieHeader: string | null, options?: CookieParseOptions$1): Promise<any>;
/**
* Serializes the given value to a string and returns the `Set-Cookie`
* header.
*/
serialize(value: any, options?: CookieSerializeOptions$1): Promise<string>;
}
/**
* Creates a logical container for managing a browser cookie from the server.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param name The name of the cookie.
* @param cookieOptions Options for parsing and serializing the cookie.
* @returns A {@link Cookie} object for parsing and serializing the cookie.
*/
declare const createCookie: (name: string, cookieOptions?: CookieOptions) => Cookie;
/**
* A function that determines whether a value is a React Router {@link Cookie}
* object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Cookie} object;
* otherwise, `false`.
*/
type IsCookieFunction = (object: any) => object is Cookie;
/**
* Returns `true` if a value is a React Router {@link Cookie} object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Cookie} object;
* otherwise, `false`.
*/
declare const isCookie: IsCookieFunction;
//#endregion
export { Cookie, CookieOptions, type CookieParseOptions$1 as CookieParseOptions, type CookieSerializeOptions$1 as CookieSerializeOptions, CookieSignatureOptions, IsCookieFunction, createCookie, isCookie };
@@ -0,0 +1,153 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { warnOnce } from "./warnings.js";
import { sign, unsign } from "./crypto.js";
import { parse, serialize } from "cookie-es";
//#region lib/server-runtime/cookies.ts
/**
* Creates a logical container for managing a browser cookie from the server.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param name The name of the cookie.
* @param cookieOptions Options for parsing and serializing the cookie.
* @returns A {@link Cookie} object for parsing and serializing the cookie.
*/
const createCookie = (name, cookieOptions = {}) => {
let { secrets = [], ...options } = {
path: "/",
sameSite: "lax",
...cookieOptions
};
warnOnceAboutExpiresCookie(name, options.expires);
return {
get name() {
return name;
},
get isSigned() {
return secrets.length > 0;
},
get expires() {
return typeof options.maxAge !== "undefined" ? new Date(Date.now() + options.maxAge * 1e3) : options.expires;
},
async parse(cookieHeader, parseOptions) {
if (!cookieHeader) return null;
let cookies = parse(cookieHeader, {
...options,
...parseOptions
});
if (name in cookies) {
let value = cookies[name];
if (typeof value === "string" && value !== "") return await decodeCookieValue(value, secrets);
else return "";
} else return null;
},
async serialize(value, serializeOptions) {
return serialize(name, value === "" ? "" : await encodeCookieValue(value, secrets), {
...options,
...serializeOptions
});
}
};
};
/**
* Returns `true` if a value is a React Router {@link Cookie} object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Cookie} object;
* otherwise, `false`.
*/
const isCookie = (object) => {
return object != null && typeof object.name === "string" && typeof object.isSigned === "boolean" && typeof object.parse === "function" && typeof object.serialize === "function";
};
async function encodeCookieValue(value, secrets) {
let encoded = encodeData(value);
if (secrets.length > 0) encoded = await sign(encoded, secrets[0]);
return encoded;
}
async function decodeCookieValue(value, secrets) {
if (secrets.length > 0) {
for (let secret of secrets) {
let unsignedValue = await unsign(value, secret);
if (unsignedValue !== false) return decodeData(unsignedValue);
}
return null;
}
return decodeData(value);
}
function encodeData(value) {
return btoa(myUnescape(encodeURIComponent(JSON.stringify(value))));
}
function decodeData(value) {
try {
return JSON.parse(decodeURIComponent(myEscape(atob(value))));
} catch (e) {
return {};
}
}
function myEscape(value) {
let str = value.toString();
let result = "";
let index = 0;
let chr, code;
while (index < str.length) {
chr = str.charAt(index++);
if (/[\w*+\-./@]/.exec(chr)) result += chr;
else {
code = chr.charCodeAt(0);
if (code < 256) result += "%" + hex(code, 2);
else result += "%u" + hex(code, 4).toUpperCase();
}
}
return result;
}
function hex(code, length) {
let result = code.toString(16);
while (result.length < length) result = "0" + result;
return result;
}
function myUnescape(value) {
let str = value.toString();
let result = "";
let index = 0;
let chr, part;
while (index < str.length) {
chr = str.charAt(index++);
if (chr === "%") if (str.charAt(index) === "u") {
part = str.slice(index + 1, index + 5);
if (/^[\da-f]{4}$/i.exec(part)) {
result += String.fromCharCode(parseInt(part, 16));
index += 5;
continue;
}
} else {
part = str.slice(index, index + 2);
if (/^[\da-f]{2}$/i.exec(part)) {
result += String.fromCharCode(parseInt(part, 16));
index += 2;
continue;
}
}
result += chr;
}
return result;
}
function warnOnceAboutExpiresCookie(name, expires) {
warnOnce(!expires, `The "${name}" cookie has an "expires" property set. This will cause the expires value to not be updated when the session is committed. Instead, you should set the expires value when serializing the cookie. You can use \`commitSession(session, { expires })\` if using a session storage object, or \`cookie.serialize("value", { expires })\` if you're using the cookie directly.`);
}
//#endregion
export { createCookie, isCookie };
@@ -0,0 +1,43 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/crypto.ts
const encoder = /* @__PURE__ */ new TextEncoder();
const sign = async (value, secret) => {
let data = encoder.encode(value);
let key = await createKey(secret, ["sign"]);
let signature = await crypto.subtle.sign("HMAC", key, data);
let hash = btoa(String.fromCharCode(...new Uint8Array(signature))).replace(/=+$/, "");
return value + "." + hash;
};
const unsign = async (cookie, secret) => {
let index = cookie.lastIndexOf(".");
let value = cookie.slice(0, index);
let hash = cookie.slice(index + 1);
let data = encoder.encode(value);
let key = await createKey(secret, ["verify"]);
try {
let signature = byteStringToUint8Array(atob(hash));
return await crypto.subtle.verify("HMAC", key, signature, data) ? value : false;
} catch (e) {
return false;
}
};
const createKey = async (secret, usages) => crypto.subtle.importKey("raw", encoder.encode(secret), {
name: "HMAC",
hash: "SHA-256"
}, false, usages);
function byteStringToUint8Array(byteString) {
let array = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) array[i] = byteString.charCodeAt(i);
return array;
}
//#endregion
export { sign, unsign };
@@ -0,0 +1,25 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { isDataWithResponseInit, isRedirectStatusCode } from "../router/router.js";
//#region lib/server-runtime/data.ts
async function callRouteHandler(handler, args) {
let result = await handler({
request: args.request,
url: args.url,
params: args.params,
context: args.context,
pattern: args.pattern
});
if (isDataWithResponseInit(result) && result.init && result.init.status && isRedirectStatusCode(result.init.status)) throw new Response(null, result.init);
return result;
}
//#endregion
export { callRouteHandler };
@@ -0,0 +1,9 @@
//#region lib/server-runtime/dev.d.ts
type DevServerHooks = {
getCriticalCss?: (pathname: string) => Promise<string | undefined>;
processRequestError?: (error: unknown) => void;
};
declare function setDevServerHooks(devServerHooks: DevServerHooks): void;
//#endregion
export { setDevServerHooks };
@@ -0,0 +1,26 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/dev.ts
const globalDevServerHooksKey = "__reactRouterDevServerHooks";
function setDevServerHooks(devServerHooks) {
globalThis[globalDevServerHooksKey] = devServerHooks;
}
function getDevServerHooks() {
return globalThis[globalDevServerHooksKey];
}
function getBuildTimeHeader(request, headerName) {
if (typeof process !== "undefined") try {
if (process.env.hasOwnProperty("IS_RR_BUILD_REQUEST") && process.env.IS_RR_BUILD_REQUEST === "yes") return request.headers.get(headerName);
} catch (e) {}
return null;
}
//#endregion
export { getBuildTimeHeader, getDevServerHooks, setDevServerHooks };
@@ -0,0 +1,20 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/entry.ts
function createEntryRouteModules(manifest) {
return Object.keys(manifest).reduce((memo, routeId) => {
let route = manifest[routeId];
if (route) memo[routeId] = route.module;
return memo;
}, {});
}
//#endregion
export { createEntryRouteModules };
@@ -0,0 +1,75 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import "./mode.js";
//#region lib/server-runtime/errors.ts
/**
* This thing probably warrants some explanation.
*
* The whole point here is to emulate componentDidCatch for server rendering and
* data loading. It can get tricky. React can do this on component boundaries
* but doesn't support it for server rendering or data loading. We know enough
* with nested routes to be able to emulate the behavior (because we know them
* statically before rendering.)
*
* Each route can export an `ErrorBoundary`.
*
* - When rendering throws an error, the nearest error boundary will render
* (normal react componentDidCatch). This will be the route's own boundary, but
* if none is provided, it will bubble up to the parents.
* - When data loading throws an error, the nearest error boundary will render
* - When performing an action, the nearest error boundary for the action's
* route tree will render (no redirect happens)
*
* During normal react rendering, we do nothing special, just normal
* componentDidCatch.
*
* For server rendering, we mutate `renderBoundaryRouteId` to know the last
* layout that has an error boundary that tried to render. This emulates which
* layout would catch a thrown error. If the rendering fails, we catch the error
* on the server, and go again a second time with the emulator holding on to the
* information it needs to render the same error boundary as a dynamically
* thrown render error.
*
* When data loading, server or client side, we use the emulator to likewise
* hang on to the error and re-render at the appropriate layout (where a thrown
* error would have been caught by cDC).
*
* When actions throw, it all works the same. There's an edge case to be aware
* of though. Actions normally are required to redirect, but in the case of
* errors, we render the action's route with the emulator holding on to the
* error. If during this render a parent route/loader throws we ignore that new
* error and render the action's original error as deeply as possible. In other
* words, we simply ignore the new error and use the action's error in place
* because it came first, and that just wouldn't be fair to let errors cut in
* line.
*/
function sanitizeError(error, serverMode) {
if (error instanceof Error && serverMode !== "development") {
let sanitized = /* @__PURE__ */ new Error("Unexpected Server Error");
sanitized.stack = void 0;
return sanitized;
}
return error;
}
function sanitizeErrors(errors, serverMode) {
return Object.entries(errors).reduce((acc, [routeId, error]) => {
return Object.assign(acc, { [routeId]: sanitizeError(error, serverMode) });
}, {});
}
function serializeError(error, serverMode) {
let sanitized = sanitizeError(error, serverMode);
return {
message: sanitized.message,
stack: sanitized.stack
};
}
//#endregion
export { sanitizeError, sanitizeErrors, serializeError };
@@ -0,0 +1,73 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import invariant from "./invariant.js";
import { splitSetCookieString } from "cookie-es";
//#region lib/server-runtime/headers.ts
function getDocumentHeaders(context, build) {
return getDocumentHeadersImpl(context, (m) => {
let route = build.routes[m.route.id];
invariant(route, `Route with id "${m.route.id}" not found in build`);
return route.module.headers;
});
}
function getDocumentHeadersImpl(context, getRouteHeadersFn, _defaultHeaders) {
let boundaryIdx = context.errors ? context.matches.findIndex((m) => context.errors[m.route.id]) : -1;
let matches = boundaryIdx >= 0 ? context.matches.slice(0, boundaryIdx + 1) : context.matches;
let errorHeaders;
if (boundaryIdx >= 0) {
let { actionHeaders, actionData, loaderHeaders, loaderData } = context;
context.matches.slice(boundaryIdx).some((match) => {
let id = match.route.id;
if (actionHeaders[id] && (!actionData || !actionData.hasOwnProperty(id))) errorHeaders = actionHeaders[id];
else if (loaderHeaders[id] && !loaderData.hasOwnProperty(id)) errorHeaders = loaderHeaders[id];
return errorHeaders != null;
});
}
const defaultHeaders = new Headers(_defaultHeaders);
return matches.reduce((parentHeaders, match, idx) => {
let { id } = match.route;
let loaderHeaders = context.loaderHeaders[id] || new Headers();
let actionHeaders = context.actionHeaders[id] || new Headers();
let includeErrorHeaders = errorHeaders != null && idx === matches.length - 1;
let includeErrorCookies = includeErrorHeaders && errorHeaders !== loaderHeaders && errorHeaders !== actionHeaders;
let headersFn = getRouteHeadersFn(match);
if (headersFn == null) {
let headers = new Headers(parentHeaders);
if (includeErrorCookies) prependCookies(errorHeaders, headers);
prependCookies(actionHeaders, headers);
prependCookies(loaderHeaders, headers);
return headers;
}
let headers = new Headers(typeof headersFn === "function" ? headersFn({
loaderHeaders,
parentHeaders,
actionHeaders,
errorHeaders: includeErrorHeaders ? errorHeaders : void 0
}) : headersFn);
if (includeErrorCookies) prependCookies(errorHeaders, headers);
prependCookies(actionHeaders, headers);
prependCookies(loaderHeaders, headers);
prependCookies(parentHeaders, headers);
return headers;
}, new Headers(defaultHeaders));
}
function prependCookies(parentHeaders, childHeaders) {
let parentSetCookieString = parentHeaders.get("Set-Cookie");
if (parentSetCookieString) {
let cookies = splitSetCookieString(parentSetCookieString);
let childCookies = new Set(childHeaders.getSetCookie());
cookies.forEach((cookie) => {
if (!childCookies.has(cookie)) childHeaders.append("Set-Cookie", cookie);
});
}
}
//#endregion
export { getDocumentHeaders };
@@ -0,0 +1,19 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/invariant.ts
function invariant(value, message) {
if (value === false || value === null || typeof value === "undefined") {
console.error("The following error is a bug in React Router; please open an issue! https://github.com/remix-run/react-router/issues/new/choose");
throw new Error(message);
}
}
//#endregion
export { invariant as default };
@@ -0,0 +1,12 @@
//#region lib/server-runtime/mode.d.ts
/**
* The mode to use when running the server.
*/
declare enum ServerMode {
Development = "development",
Production = "production",
Test = "test"
}
//#endregion
export { ServerMode };
@@ -0,0 +1,25 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/mode.ts
/**
* The mode to use when running the server.
*/
let ServerMode = /* @__PURE__ */ function(ServerMode) {
ServerMode["Development"] = "development";
ServerMode["Production"] = "production";
ServerMode["Test"] = "test";
return ServerMode;
}({});
function isServerMode(value) {
return value === "development" || value === "production" || value === "test";
}
//#endregion
export { ServerMode, isServerMode };
@@ -0,0 +1,28 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { matchRoutesImpl } from "../router/utils.js";
import invariant from "./invariant.js";
//#region lib/server-runtime/routeMatching.ts
function matchServerRoutes(manifest, dataRoutes, branches, pathname, basename) {
let matches = matchRoutesImpl(dataRoutes, pathname, basename ?? "/", false, branches);
if (!matches) return null;
return matches.map((match) => {
let route = manifest[match.route.id];
invariant(route, `Route with id "${match.route.id}" not found in manifest.`);
return {
params: match.params,
pathname: match.pathname,
route
};
});
}
//#endregion
export { matchServerRoutes };
@@ -0,0 +1,13 @@
import { RouteManifest } from "../router/utils.js";
import { ServerRouteModule } from "../dom/ssr/routeModules.js";
import { Route } from "../dom/ssr/routes.js";
//#region lib/server-runtime/routes.d.ts
type ServerRouteManifest = RouteManifest<Omit<ServerRoute, "children">>;
interface ServerRoute extends Route {
children: ServerRoute[];
module: ServerRouteModule;
}
//#endregion
export { ServerRouteManifest };
@@ -0,0 +1,74 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { redirect, redirectDocument, replace } from "../router/utils.js";
import { SingleFetchRedirectSymbol, decodeViaTurboStream } from "../dom/ssr/single-fetch.js";
import invariant from "./invariant.js";
import { callRouteHandler } from "./data.js";
import { getBuildTimeHeader } from "./dev.js";
//#region lib/server-runtime/routes.ts
function groupRoutesByParentId(manifest) {
let routes = {};
Object.values(manifest).forEach((route) => {
if (route) {
let parentId = route.parentId || "";
if (!routes[parentId]) routes[parentId] = [];
routes[parentId].push(route);
}
});
return routes;
}
function createStaticHandlerDataRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId(manifest)) {
return (routesByParentId[parentId] || []).map((route) => {
let commonRoute = {
id: route.id,
path: route.path,
middleware: route.module.middleware,
loader: route.module.loader ? async (args) => {
let preRenderedData = getBuildTimeHeader(args.request, "X-React-Router-Prerender-Data");
if (preRenderedData != null) {
let encoded = preRenderedData ? decodeURI(preRenderedData) : preRenderedData;
invariant(encoded, "Missing prerendered data for route");
let uint8array = new TextEncoder().encode(encoded);
let data = (await decodeViaTurboStream(new ReadableStream({ start(controller) {
controller.enqueue(uint8array);
controller.close();
} }), global)).value;
if (data && SingleFetchRedirectSymbol in data) {
let result = data[SingleFetchRedirectSymbol];
let init = { status: result.status };
if (result.reload) throw redirectDocument(result.redirect, init);
else if (result.replace) throw replace(result.redirect, init);
else throw redirect(result.redirect, init);
} else {
invariant(data && route.id in data, "Unable to decode prerendered data");
let result = data[route.id];
invariant("data" in result, "Unable to process prerendered data");
return result.data;
}
}
return await callRouteHandler(route.module.loader, args);
} : void 0,
action: route.module.action ? (args) => callRouteHandler(route.module.action, args) : void 0,
ErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null ? () => null : void 0,
handle: route.module.handle
};
return route.index ? {
index: true,
...commonRoute
} : {
caseSensitive: route.caseSensitive,
children: createStaticHandlerDataRoutes(manifest, route.id, routesByParentId),
...commonRoute
};
});
}
//#endregion
export { createStaticHandlerDataRoutes };
@@ -0,0 +1,22 @@
import { RouterContextProvider } from "../router/utils.js";
import { ServerBuild } from "./build.js";
//#region lib/server-runtime/server.d.ts
type RequestHandler = (request: Request, loadContext?: RouterContextProvider) => Promise<Response>;
type CreateRequestHandlerFunction = (build: ServerBuild | (() => ServerBuild | Promise<ServerBuild>), mode?: string) => RequestHandler;
/**
* Creates a request handler for a React Router server build.
*
* This is a low-level API used by server adapters to translate incoming
* requests into React Router responses.
*
* @category Utils
* @param build The server build, or a function that resolves to the server
* build, used to handle requests.
* @param mode The mode in which the server build is running.
* @returns A request handler that returns a response for each incoming request.
*/
declare const createRequestHandler: CreateRequestHandlerFunction;
//#endregion
export { CreateRequestHandlerFunction, RequestHandler, createRequestHandler };
@@ -0,0 +1,364 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { ErrorResponseImpl, RouterContextProvider, createDataFunctionUrl, defaultMapRouteProperties, getRoutePattern, isRouteErrorResponse, removeTrailingSlash, stripBasename } from "../router/utils.js";
import { instrumentHandler, instrumentationResultMetaContext } from "../router/instrumentation.js";
import { createStaticHandler, getStaticContextFromError, isMutationMethod, isRedirectResponse, isResponse } from "../router/router.js";
import { getManifestPath } from "../dom/ssr/fog-of-war.js";
import { createEntryRouteModules } from "./entry.js";
import { isServerMode } from "./mode.js";
import { sanitizeErrors, serializeError } from "./errors.js";
import { matchServerRoutes } from "./routeMatching.js";
import { getBuildTimeHeader, getDevServerHooks } from "./dev.js";
import { createStaticHandlerDataRoutes } from "./routes.js";
import { createServerHandoffString } from "./serverHandoff.js";
import { getDocumentHeaders } from "./headers.js";
import { throwIfPotentialCSRFAttack } from "../actions.js";
import { getNormalizedPath } from "./urls.js";
import { SERVER_NO_BODY_STATUS_CODES, encodeViaTurboStream, generateSingleFetchRedirectResponse, singleFetchAction, singleFetchLoaders } from "./single-fetch.js";
//#region lib/server-runtime/server.ts
function derive(build, mode) {
let dataRoutes = createStaticHandlerDataRoutes(build.routes);
let serverMode = isServerMode(mode) ? mode : "production";
let staticHandler = createStaticHandler(dataRoutes, {
basename: build.basename,
mapRouteProperties: defaultMapRouteProperties,
instrumentations: build.entry.module.instrumentations,
future: build.future
});
let errorHandler = build.entry.module.handleError || ((error, { request }) => {
if (serverMode !== "test" && !request.signal.aborted) console.error(isRouteErrorResponse(error) && error.error ? error.error : error);
});
let requestHandlerInstrumentations = build.entry.module.instrumentations?.map((i) => i.handler).filter(Boolean);
let requestHandler = async (request, initialContext) => {
let params = {};
let loadContext;
let handleError = (error) => {
if (mode === "development") getDevServerHooks()?.processRequestError?.(error);
errorHandler(error, {
context: loadContext,
params,
request
});
};
if (initialContext && !(initialContext instanceof RouterContextProvider)) {
let error = /* @__PURE__ */ new Error("Invalid `context` value provided to `handleRequest`. You must return an instance of `RouterContextProvider` from your `getLoadContext` function.");
handleError(error);
return returnLastResortErrorResponse(error, serverMode);
}
loadContext = initialContext || new RouterContextProvider();
let requestUrl = new URL(request.url);
let normalizedPath = getNormalizedPath(request);
let normalizedPathname = normalizedPath.pathname;
let isSpaMode = getBuildTimeHeader(request, "X-React-Router-SPA-Mode") === "yes";
if (!build.ssr) {
let decodedPath = decodeURI(normalizedPathname);
if (build.basename && build.basename !== "/") {
let strippedPath = stripBasename(decodedPath, build.basename);
if (strippedPath == null) {
errorHandler(new ErrorResponseImpl(404, "Not Found", `Refusing to prerender the \`${decodedPath}\` path because it does not start with the basename \`${build.basename}\``), {
context: loadContext,
params,
request
});
return new Response("Not Found", {
status: 404,
statusText: "Not Found"
});
}
decodedPath = strippedPath;
}
if (build.prerender.length === 0) isSpaMode = true;
else if (!build.prerender.some((p) => removeTrailingSlash(p) === removeTrailingSlash(decodedPath))) if (requestUrl.pathname.endsWith(".data")) {
errorHandler(new ErrorResponseImpl(404, "Not Found", `Refusing to SSR the path \`${decodedPath}\` because \`ssr:false\` is set and the path is not included in the \`prerender\` config, so in production the path will be a 404.`), {
context: loadContext,
params,
request
});
return new Response("Not Found", {
status: 404,
statusText: "Not Found"
});
} else isSpaMode = true;
}
let manifestUrl = getManifestPath(build.routeDiscovery.manifestPath, build.basename);
if (build.routeDiscovery.mode === "lazy" && requestUrl.pathname === manifestUrl) try {
return await handleManifestRequest(build, staticHandler.dataRoutes, staticHandler._internalRouteBranches, requestUrl);
} catch (e) {
handleError(e);
return new Response("Unknown Server Error", { status: 500 });
}
let matches = matchServerRoutes(build.routes, staticHandler.dataRoutes, staticHandler._internalRouteBranches, normalizedPathname, build.basename);
if (matches && matches.length > 0) Object.assign(params, matches[0].params);
if (requestHandlerInstrumentations?.length) loadContext.set(instrumentationResultMetaContext, {
url: createDataFunctionUrl(request, normalizedPath),
pattern: matches ? getRoutePattern(matches) : "",
params: matches?.[0]?.params ? { ...matches[0].params } : {}
});
let response;
if (requestUrl.pathname.endsWith(".data")) {
response = await handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError);
if (isRedirectResponse(response)) response = generateSingleFetchRedirectResponse(response, request, build, serverMode);
if (build.entry.module.handleDataRequest) {
response = await build.entry.module.handleDataRequest(response, {
context: loadContext,
params: matches ? matches[0].params : {},
request
});
if (isRedirectResponse(response)) response = generateSingleFetchRedirectResponse(response, request, build, serverMode);
}
} else if (!isSpaMode && matches && matches[matches.length - 1].route.module.default == null && matches[matches.length - 1].route.module.ErrorBoundary == null) response = await handleResourceRequest(serverMode, build, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError);
else {
let { pathname } = requestUrl;
let criticalCss = void 0;
if (build.unstable_getCriticalCss) criticalCss = await build.unstable_getCriticalCss({ pathname });
else if (mode === "development" && getDevServerHooks()?.getCriticalCss) criticalCss = await getDevServerHooks()?.getCriticalCss?.(pathname);
response = await handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss);
}
if (request.method === "HEAD") return new Response(null, {
headers: response.headers,
status: response.status,
statusText: response.statusText
});
return response;
};
if (requestHandlerInstrumentations?.length) requestHandler = instrumentHandler(requestHandler, requestHandlerInstrumentations);
return {
serverMode,
staticHandler,
errorHandler,
requestHandler
};
}
/**
* Creates a request handler for a React Router server build.
*
* This is a low-level API used by server adapters to translate incoming
* requests into React Router responses.
*
* @category Utils
* @param build The server build, or a function that resolves to the server
* build, used to handle requests.
* @param mode The mode in which the server build is running.
* @returns A request handler that returns a response for each incoming request.
*/
const createRequestHandler = (build, mode) => {
let _build;
let serverMode;
let staticHandler;
let errorHandler;
let _requestHandler;
return async function requestHandler(request, initialContext) {
_build = typeof build === "function" ? await build() : build;
if (typeof build === "function") {
let derived = derive(_build, mode);
serverMode = derived.serverMode;
staticHandler = derived.staticHandler;
errorHandler = derived.errorHandler;
_requestHandler = derived.requestHandler;
} else if (!serverMode || !staticHandler || !errorHandler || !_requestHandler) {
let derived = derive(_build, mode);
serverMode = derived.serverMode;
staticHandler = derived.staticHandler;
errorHandler = derived.errorHandler;
_requestHandler = derived.requestHandler;
}
return _requestHandler(request, initialContext);
};
};
async function handleManifestRequest(build, dataRoutes, branches, url) {
if (url.toString().length > 7680) return new Response(null, {
statusText: "Bad Request",
status: 400
});
if (build.assets.version !== url.searchParams.get("version")) return new Response(null, {
status: 204,
headers: { "X-Remix-Reload-Document": "true" }
});
let patches = {};
if (url.searchParams.has("paths")) {
let pathParam = url.searchParams.get("paths") || "";
let paths = new Set(pathParam.split(",").filter(Boolean));
for (let path of paths) {
if (!path.startsWith("/")) path = `/${path}`;
let matches = matchServerRoutes(build.routes, dataRoutes, branches, path, build.basename);
if (matches) for (let match of matches) {
let routeId = match.route.id;
let route = build.assets.routes[routeId];
if (route) patches[routeId] = route;
}
}
return Response.json(patches, { headers: { "Cache-Control": "public, max-age=31536000, immutable" } });
}
return new Response("Invalid Request", { status: 400 });
}
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, loadContext, handleError) {
return isMutationMethod(request.method) ? await singleFetchAction(build, serverMode, staticHandler, request, loadContext, handleError) : await singleFetchLoaders(build, serverMode, staticHandler, request, loadContext, handleError);
}
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss) {
try {
if (isMutationMethod(request.method)) try {
throwIfPotentialCSRFAttack(request, Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []);
} catch (e) {
handleError(e);
return new Response("Bad Request", { status: 400 });
}
let result = await staticHandler.query(request, {
requestContext: loadContext,
generateMiddlewareResponse: async (query) => {
try {
let innerResult = await query(request);
if (!isResponse(innerResult)) innerResult = await renderHtml(innerResult, isSpaMode);
return innerResult;
} catch (error) {
handleError(error);
return new Response(null, { status: 500 });
}
},
normalizePath: (r) => getNormalizedPath(r)
});
if (!isResponse(result)) result = await renderHtml(result, isSpaMode);
return result;
} catch (error) {
handleError(error);
return new Response(null, { status: 500 });
}
async function renderHtml(context, isSpaMode) {
let headers = getDocumentHeaders(context, build);
if (SERVER_NO_BODY_STATUS_CODES.has(context.statusCode)) return new Response(null, {
status: context.statusCode,
headers
});
if (context.errors) {
Object.values(context.errors).forEach((err) => {
if (!isRouteErrorResponse(err) || err.error) handleError(err);
});
context.errors = sanitizeErrors(context.errors, serverMode);
}
let state = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: context.errors
};
let baseServerHandoff = {
basename: build.basename,
future: build.future,
routeDiscovery: build.routeDiscovery,
ssr: build.ssr,
isSpaMode
};
let entryContext = {
manifest: build.assets,
branches: staticHandler._internalRouteBranches,
routeModules: createEntryRouteModules(build.routes),
staticHandlerContext: context,
criticalCss,
serverHandoffString: createServerHandoffString({
...baseServerHandoff,
criticalCss
}),
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
renderMeta: {},
future: build.future,
ssr: build.ssr,
routeDiscovery: build.routeDiscovery,
isSpaMode,
serializeError: (err) => serializeError(err, serverMode)
};
let handleDocumentRequestFunction = build.entry.module.default;
try {
return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
} catch (error) {
handleError(error);
let errorForSecondRender = error;
if (isResponse(error)) try {
let data = await unwrapResponse(error);
errorForSecondRender = new ErrorResponseImpl(error.status, error.statusText, data);
} catch (e) {}
context = getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender);
if (context.errors) context.errors = sanitizeErrors(context.errors, serverMode);
let state = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: context.errors
};
entryContext = {
...entryContext,
staticHandlerContext: context,
serverHandoffString: createServerHandoffString(baseServerHandoff),
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
renderMeta: {}
};
try {
return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
} catch (error) {
handleError(error);
return returnLastResortErrorResponse(error, serverMode);
}
}
}
}
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
try {
return handleQueryRouteResult(await staticHandler.queryRoute(request, {
routeId,
requestContext: loadContext,
generateMiddlewareResponse: async (queryRoute) => {
try {
return handleQueryRouteResult(await queryRoute(request));
} catch (error) {
return handleQueryRouteError(error);
}
},
normalizePath: (r) => getNormalizedPath(r)
}));
} catch (error) {
return handleQueryRouteError(error);
}
function handleQueryRouteResult(result) {
if (isResponse(result)) return result;
if (typeof result === "string") return new Response(result);
return Response.json(result);
}
function handleQueryRouteError(error) {
if (isResponse(error)) return error;
if (isRouteErrorResponse(error)) {
handleError(error);
return errorResponseToJson(error, serverMode);
}
if (error instanceof Error && error.message === "Expected a response from queryRoute") {
let newError = /* @__PURE__ */ new Error("Expected a Response to be returned from resource route handler");
handleError(newError);
return returnLastResortErrorResponse(newError, serverMode);
}
handleError(error);
return returnLastResortErrorResponse(error, serverMode);
}
}
function errorResponseToJson(errorResponse, serverMode) {
return Response.json(serializeError(errorResponse.error || /* @__PURE__ */ new Error("Unexpected Server Error"), serverMode), {
status: errorResponse.status,
statusText: errorResponse.statusText
});
}
function returnLastResortErrorResponse(error, serverMode) {
let message = "Unexpected Server Error";
if (serverMode !== "production") message += `\n\n${String(error)}`;
return new Response(message, {
status: 500,
headers: { "Content-Type": "text/plain" }
});
}
function unwrapResponse(response) {
let contentType = response.headers.get("Content-Type");
return contentType && /\bapplication\/json\b/.test(contentType) ? response.body == null ? null : response.json() : response.text();
}
//#endregion
export { createRequestHandler };
@@ -0,0 +1,17 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { escapeHtml } from "../dom/ssr/markup.js";
//#region lib/server-runtime/serverHandoff.ts
function createServerHandoffString(serverHandoff) {
return escapeHtml(JSON.stringify(serverHandoff));
}
//#endregion
export { createServerHandoffString };
@@ -0,0 +1,174 @@
import { Cookie, CookieOptions } from "./cookies.js";
import { CookieParseOptions, CookieSerializeOptions } from "cookie-es";
//#region lib/server-runtime/sessions.d.ts
/**
* An object of name/value pairs to be used in the session.
*/
interface SessionData {
[name: string]: any;
}
/**
* Session persists data across HTTP requests.
*
* @see https://reactrouter.com/explanation/sessions-and-cookies#sessions
*/
interface Session<Data = SessionData, FlashData = Data> {
/**
* A unique identifier for this session.
*
* Note: This will be the empty string for newly created sessions and
* sessions that are not backed by a database (i.e. cookie-based sessions).
*/
readonly id: string;
/**
* The raw data contained in this session.
*
* This is useful mostly for SessionStorage internally to access the raw
* session data to persist.
*/
readonly data: FlashSessionData<Data, FlashData>;
/**
* Returns `true` if the session has a value for the given `name`, `false`
* otherwise.
*/
has(name: (keyof Data | keyof FlashData) & string): boolean;
/**
* Returns the value for the given `name` in this session.
*/
get<Key extends (keyof Data | keyof FlashData) & string>(name: Key): (Key extends keyof Data ? Data[Key] : undefined) | (Key extends keyof FlashData ? FlashData[Key] : undefined) | undefined;
/**
* Sets a value in the session for the given `name`.
*/
set<Key extends keyof Data & string>(name: Key, value: Data[Key]): void;
/**
* Sets a value in the session that is only valid until the next `get()`.
* This can be useful for temporary values, like error messages.
*/
flash<Key extends keyof FlashData & string>(name: Key, value: FlashData[Key]): void;
/**
* Removes a value from the session.
*/
unset(name: keyof Data & string): void;
}
type FlashSessionData<Data, FlashData> = Partial<Data & { [Key in keyof FlashData as FlashDataKey<Key & string>]: FlashData[Key] }>;
type FlashDataKey<Key extends string> = `__flash_${Key}__`;
type CreateSessionFunction = <Data = SessionData, FlashData = Data>(initialData?: Data, id?: string) => Session<Data, FlashData>;
/**
* Creates a new Session object.
*
* Note: This function is typically not invoked directly by application code.
* Instead, use a `SessionStorage` object's `getSession` method.
*
* @category Utils
* @param initialData The initial data for the session.
* @param id The identifier for the session. Defaults to an empty string for a
* new session.
* @returns A new {@link Session} object.
*/
declare const createSession: CreateSessionFunction;
/**
* A function that determines whether a value is a React Router {@link Session}
* object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Session} object;
* otherwise, `false`.
*/
type IsSessionFunction = (object: any) => object is Session;
/**
* Returns `true` if a value is a React Router {@link Session} object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Session} object;
* otherwise, `false`.
*/
declare const isSession: IsSessionFunction;
/**
* SessionStorage stores session data between HTTP requests and knows how to
* parse and create cookies.
*
* A SessionStorage creates Session objects using a `Cookie` header as input.
* Then, later it generates the `Set-Cookie` header to be used in the response.
*/
interface SessionStorage<Data = SessionData, FlashData = Data> {
/**
* Parses a Cookie header from a HTTP request and returns the associated
* Session. If there is no session associated with the cookie, this will
* return a new Session with no data.
*/
getSession: (cookieHeader?: string | null, options?: CookieParseOptions) => Promise<Session<Data, FlashData>>;
/**
* Stores all data in the Session and returns the Set-Cookie header to be
* used in the HTTP response.
*/
commitSession: (session: Session<Data, FlashData>, options?: CookieSerializeOptions) => Promise<string>;
/**
* Deletes all data associated with the Session and returns the Set-Cookie
* header to be used in the HTTP response.
*/
destroySession: (session: Session<Data, FlashData>, options?: CookieSerializeOptions) => Promise<string>;
}
/**
* SessionIdStorageStrategy is designed to allow anyone to easily build their
* own SessionStorage using `createSessionStorage(strategy)`.
*
* This strategy describes a common scenario where the session id is stored in
* a cookie but the actual session data is stored elsewhere, usually in a
* database or on disk. A set of create, read, update, and delete operations
* are provided for managing the session data.
*/
interface SessionIdStorageStrategy<Data = SessionData, FlashData = Data> {
/**
* The Cookie used to store the session id, or options used to automatically
* create one.
*/
cookie?: Cookie | (CookieOptions & {
name?: string;
});
/**
* Creates a new record with the given data and returns the session id.
*/
createData: (data: FlashSessionData<Data, FlashData>, expires?: Date) => Promise<string>;
/**
* Returns data for a given session id, or `null` if there isn't any.
*/
readData: (id: string) => Promise<FlashSessionData<Data, FlashData> | null>;
/**
* Updates data for the given session id.
*/
updateData: (id: string, data: FlashSessionData<Data, FlashData>, expires?: Date) => Promise<void>;
/**
* Deletes data for a given session id from the data store.
*/
deleteData: (id: string) => Promise<void>;
}
/**
* Creates a SessionStorage object using a SessionIdStorageStrategy.
*
* Note: This is a low-level API that should only be used if none of the
* existing session storage options meet your requirements.
*
* @category Utils
* @param strategy The strategy used to store session identifiers and data.
* @returns A {@link SessionStorage} object that persists session data using the
* provided strategy.
*/
declare function createSessionStorage<Data = SessionData, FlashData = Data>({
cookie: cookieArg,
createData,
readData,
updateData,
deleteData
}: SessionIdStorageStrategy<Data, FlashData>): SessionStorage<Data, FlashData>;
//#endregion
export { FlashSessionData, IsSessionFunction, Session, SessionData, SessionIdStorageStrategy, SessionStorage, createSession, createSessionStorage, isSession };
@@ -0,0 +1,115 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { warnOnce } from "./warnings.js";
import { createCookie, isCookie } from "./cookies.js";
//#region lib/server-runtime/sessions.ts
function flash(name) {
return `__flash_${name}__`;
}
/**
* Creates a new Session object.
*
* Note: This function is typically not invoked directly by application code.
* Instead, use a `SessionStorage` object's `getSession` method.
*
* @category Utils
* @param initialData The initial data for the session.
* @param id The identifier for the session. Defaults to an empty string for a
* new session.
* @returns A new {@link Session} object.
*/
const createSession = (initialData = {}, id = "") => {
let map = new Map(Object.entries(initialData));
return {
get id() {
return id;
},
get data() {
return Object.fromEntries(map);
},
has(name) {
return map.has(name) || map.has(flash(name));
},
get(name) {
if (map.has(name)) return map.get(name);
let flashName = flash(name);
if (map.has(flashName)) {
let value = map.get(flashName);
map.delete(flashName);
return value;
}
},
set(name, value) {
map.set(name, value);
},
flash(name, value) {
map.set(flash(name), value);
},
unset(name) {
map.delete(name);
}
};
};
/**
* Returns `true` if a value is a React Router {@link Session} object.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param object The value to check.
* @returns `true` if the value is a React Router {@link Session} object;
* otherwise, `false`.
*/
const isSession = (object) => {
return object != null && typeof object.id === "string" && typeof object.data !== "undefined" && typeof object.has === "function" && typeof object.get === "function" && typeof object.set === "function" && typeof object.flash === "function" && typeof object.unset === "function";
};
/**
* Creates a SessionStorage object using a SessionIdStorageStrategy.
*
* Note: This is a low-level API that should only be used if none of the
* existing session storage options meet your requirements.
*
* @category Utils
* @param strategy The strategy used to store session identifiers and data.
* @returns A {@link SessionStorage} object that persists session data using the
* provided strategy.
*/
function createSessionStorage({ cookie: cookieArg, createData, readData, updateData, deleteData }) {
let cookie = isCookie(cookieArg) ? cookieArg : createCookie(cookieArg?.name || "__session", cookieArg);
warnOnceAboutSigningSessionCookie(cookie);
return {
async getSession(cookieHeader, options) {
let id = cookieHeader && await cookie.parse(cookieHeader, options);
return createSession(id && await readData(id) || {}, id || "");
},
async commitSession(session, options) {
let { id, data } = session;
let expires = options?.maxAge != null ? new Date(Date.now() + options.maxAge * 1e3) : options?.expires != null ? options.expires : cookie.expires;
if (id) await updateData(id, data, expires);
else id = await createData(data, expires);
return cookie.serialize(id, options);
},
async destroySession(session, options) {
await deleteData(session.id);
return cookie.serialize("", {
...options,
maxAge: void 0,
expires: /* @__PURE__ */ new Date(0)
});
}
};
}
function warnOnceAboutSigningSessionCookie(cookie) {
warnOnce(cookie.isSigned, `The "${cookie.name}" cookie is not signed, but session cookies should be signed to prevent tampering on the client before they are sent back to the server. See https://reactrouter.com/explanation/sessions-and-cookies#signing-cookies for more information.`);
}
//#endregion
export { createSession, createSessionStorage, isSession, warnOnceAboutSigningSessionCookie };
@@ -0,0 +1,33 @@
import { SessionData, SessionIdStorageStrategy, SessionStorage } from "../sessions.js";
//#region lib/server-runtime/sessions/cookieStorage.d.ts
interface CookieSessionStorageOptions {
/**
* The Cookie used to store the session data on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
}
/**
* Creates and returns a SessionStorage object that stores all session data
* directly in the session cookie itself.
*
* This has the advantage that no database or other backend services are
* needed, and can help to simplify some load-balanced scenarios. However, it
* also has the limitation that serialized session data may not exceed the
* browser's maximum cookie size. Trade-offs!
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param options Options for creating the cookie-backed session storage.
* @returns A {@link SessionStorage} object that stores all session data in its
* cookie.
*/
declare function createCookieSessionStorage<Data = SessionData, FlashData = Data>({
cookie: cookieArg
}?: CookieSessionStorageOptions): SessionStorage<Data, FlashData>;
//#endregion
export { createCookieSessionStorage };
@@ -0,0 +1,53 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createCookie, isCookie } from "../cookies.js";
import { createSession, warnOnceAboutSigningSessionCookie } from "../sessions.js";
//#region lib/server-runtime/sessions/cookieStorage.ts
/**
* Creates and returns a SessionStorage object that stores all session data
* directly in the session cookie itself.
*
* This has the advantage that no database or other backend services are
* needed, and can help to simplify some load-balanced scenarios. However, it
* also has the limitation that serialized session data may not exceed the
* browser's maximum cookie size. Trade-offs!
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param options Options for creating the cookie-backed session storage.
* @returns A {@link SessionStorage} object that stores all session data in its
* cookie.
*/
function createCookieSessionStorage({ cookie: cookieArg } = {}) {
let cookie = isCookie(cookieArg) ? cookieArg : createCookie(cookieArg?.name || "__session", cookieArg);
warnOnceAboutSigningSessionCookie(cookie);
return {
async getSession(cookieHeader, options) {
return createSession(cookieHeader && await cookie.parse(cookieHeader, options) || {});
},
async commitSession(session, options) {
let serializedCookie = await cookie.serialize(session.data, options);
if (serializedCookie.length > 4096) throw new Error("Cookie length will exceed browser maximum. Length: " + serializedCookie.length);
return serializedCookie;
},
async destroySession(_session, options) {
return cookie.serialize("", {
...options,
maxAge: void 0,
expires: /* @__PURE__ */ new Date(0)
});
}
};
}
//#endregion
export { createCookieSessionStorage };
@@ -0,0 +1,29 @@
import { SessionData, SessionIdStorageStrategy, SessionStorage } from "../sessions.js";
//#region lib/server-runtime/sessions/memoryStorage.d.ts
interface MemorySessionStorageOptions {
/**
* The Cookie used to store the session id on the client, or options used
* to automatically create one.
*/
cookie?: SessionIdStorageStrategy["cookie"];
}
/**
* Creates and returns a simple in-memory SessionStorage object.
*
* Intended for local development and testing. It does not scale beyond a single
* process, and all session data is lost when the server process stops/restarts.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param options Options for creating the in-memory session storage.
* @returns A {@link SessionStorage} object that stores session data in memory.
*/
declare function createMemorySessionStorage<Data = SessionData, FlashData = Data>({
cookie
}?: MemorySessionStorageOptions): SessionStorage<Data, FlashData>;
//#endregion
export { createMemorySessionStorage };
@@ -0,0 +1,58 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createSessionStorage } from "../sessions.js";
//#region lib/server-runtime/sessions/memoryStorage.ts
/**
* Creates and returns a simple in-memory SessionStorage object.
*
* Intended for local development and testing. It does not scale beyond a single
* process, and all session data is lost when the server process stops/restarts.
*
* @public
* @category Utils
* @mode framework
* @mode data
* @param options Options for creating the in-memory session storage.
* @returns A {@link SessionStorage} object that stores session data in memory.
*/
function createMemorySessionStorage({ cookie } = {}) {
let map = /* @__PURE__ */ new Map();
return createSessionStorage({
cookie,
async createData(data, expires) {
let id = crypto.randomUUID();
map.set(id, {
data,
expires
});
return id;
},
async readData(id) {
if (map.has(id)) {
let { data, expires } = map.get(id);
if (!expires || expires > /* @__PURE__ */ new Date()) return data;
if (expires) map.delete(id);
}
return null;
},
async updateData(id, data, expires) {
map.set(id, {
data,
expires
});
},
async deleteData(id) {
map.delete(id);
}
});
}
//#endregion
export { createMemorySessionStorage };
@@ -0,0 +1,7 @@
//#region lib/server-runtime/single-fetch.d.ts
type Serializable = undefined | null | boolean | string | symbol | number | Array<Serializable> | {
[key: PropertyKey]: Serializable;
} | bigint | Date | URL | RegExp | Error | Map<Serializable, Serializable> | Set<Serializable> | Promise<Serializable>;
//#endregion
export { Serializable };
@@ -0,0 +1,215 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { ErrorResponseImpl, isRouteErrorResponse, stripBasename } from "../router/utils.js";
import { isRedirectStatusCode, isResponse } from "../router/router.js";
import { encode } from "../../vendor/turbo-stream-v2/turbo-stream.js";
import { NO_BODY_STATUS_CODES, SingleFetchRedirectSymbol } from "../dom/ssr/single-fetch.js";
import "./mode.js";
import { sanitizeError, sanitizeErrors } from "./errors.js";
import { getDocumentHeaders } from "./headers.js";
import { throwIfPotentialCSRFAttack } from "../actions.js";
import { getNormalizedPath } from "./urls.js";
//#region lib/server-runtime/single-fetch.ts
const SERVER_NO_BODY_STATUS_CODES = new Set([...NO_BODY_STATUS_CODES, 304]);
async function singleFetchAction(build, serverMode, staticHandler, request, loadContext, handleError) {
try {
try {
throwIfPotentialCSRFAttack(request, Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []);
} catch (e) {
return handleQueryError(/* @__PURE__ */ new Error("Bad Request"), 400);
}
return handleQueryResult(await staticHandler.query(request, {
requestContext: loadContext,
skipLoaderErrorBubbling: true,
skipRevalidation: true,
generateMiddlewareResponse: async (query) => {
try {
return handleQueryResult(await query(request));
} catch (error) {
return handleQueryError(error);
}
},
normalizePath: (r) => getNormalizedPath(r)
}));
} catch (error) {
return handleQueryError(error);
}
function handleQueryResult(result) {
return isResponse(result) ? result : staticContextToResponse(result);
}
function handleQueryError(error, status = 500) {
handleError(error);
return generateSingleFetchResponse(request, build, serverMode, {
result: { error },
headers: new Headers(),
status
});
}
function staticContextToResponse(context) {
let headers = getDocumentHeaders(context, build);
if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) return new Response(null, {
status: context.statusCode,
headers
});
if (context.errors) {
Object.values(context.errors).forEach((err) => {
if (!isRouteErrorResponse(err) || err.error) handleError(err);
});
context.errors = sanitizeErrors(context.errors, serverMode);
}
let singleFetchResult;
if (context.errors) singleFetchResult = { error: Object.values(context.errors)[0] };
else singleFetchResult = { data: Object.values(context.actionData || {})[0] };
return generateSingleFetchResponse(request, build, serverMode, {
result: singleFetchResult,
headers,
status: context.statusCode
});
}
}
async function singleFetchLoaders(build, serverMode, staticHandler, request, loadContext, handleError) {
let routesParam = new URL(request.url).searchParams.get("_routes");
let loadRouteIds = routesParam ? new Set(routesParam.split(",")) : null;
try {
return handleQueryResult(await staticHandler.query(request, {
requestContext: loadContext,
filterMatchesToLoad: (m) => !loadRouteIds || loadRouteIds.has(m.route.id),
skipLoaderErrorBubbling: true,
generateMiddlewareResponse: async (query) => {
try {
return handleQueryResult(await query(request));
} catch (error) {
return handleQueryError(error);
}
},
normalizePath: (r) => getNormalizedPath(r)
}));
} catch (error) {
return handleQueryError(error);
}
function handleQueryResult(result) {
return isResponse(result) ? result : staticContextToResponse(result);
}
function handleQueryError(error) {
handleError(error);
return generateSingleFetchResponse(request, build, serverMode, {
result: { error },
headers: new Headers(),
status: 500
});
}
function staticContextToResponse(context) {
let headers = getDocumentHeaders(context, build);
if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) return new Response(null, {
status: context.statusCode,
headers
});
if (context.errors) {
Object.values(context.errors).forEach((err) => {
if (!isRouteErrorResponse(err) || err.error) handleError(err);
});
context.errors = sanitizeErrors(context.errors, serverMode);
}
let results = {};
let loadedMatches = new Set(context.matches.filter((m) => loadRouteIds ? loadRouteIds.has(m.route.id) : m.route.loader != null).map((m) => m.route.id));
if (context.errors) for (let [id, error] of Object.entries(context.errors)) results[id] = { error };
for (let [id, data] of Object.entries(context.loaderData)) if (!(id in results) && loadedMatches.has(id)) results[id] = { data };
return generateSingleFetchResponse(request, build, serverMode, {
result: results,
headers,
status: context.statusCode
});
}
}
function generateSingleFetchResponse(request, build, serverMode, { result, headers, status }) {
let resultHeaders = new Headers(headers);
resultHeaders.set("X-Remix-Response", "yes");
if (SERVER_NO_BODY_STATUS_CODES.has(status)) return new Response(null, {
status,
headers: resultHeaders
});
resultHeaders.set("Content-Type", "text/x-script");
resultHeaders.delete("Content-Length");
return new Response(encodeViaTurboStream(result, request.signal, build.entry.module.streamTimeout, serverMode), {
status: status || 200,
headers: resultHeaders
});
}
function generateSingleFetchRedirectResponse(redirectResponse, request, build, serverMode) {
let redirect = getSingleFetchRedirect(redirectResponse.status, redirectResponse.headers, build.basename);
let headers = new Headers(redirectResponse.headers);
headers.delete("Location");
headers.set("Content-Type", "text/x-script");
return generateSingleFetchResponse(request, build, serverMode, {
result: request.method === "GET" ? { [SingleFetchRedirectSymbol]: redirect } : redirect,
headers,
status: 202
});
}
function getSingleFetchRedirect(status, headers, basename) {
let redirect = headers.get("Location");
if (basename) redirect = stripBasename(redirect, basename) || redirect;
return {
redirect,
status,
revalidate: headers.has("X-Remix-Revalidate") || headers.has("Set-Cookie"),
reload: headers.has("X-Remix-Reload-Document"),
replace: headers.has("X-Remix-Replace")
};
}
function encodeViaTurboStream(data, requestSignal, streamTimeout, serverMode) {
let controller = new AbortController();
let timeoutId = setTimeout(() => {
controller.abort(/* @__PURE__ */ new Error("Server Timeout"));
cleanupCallbacks();
}, typeof streamTimeout === "number" ? streamTimeout : 4950);
let abortControllerOnRequestAbort = () => {
controller.abort(requestSignal.reason);
cleanupCallbacks();
};
requestSignal.addEventListener("abort", abortControllerOnRequestAbort);
let cleanupCallbacks = () => {
clearTimeout(timeoutId);
requestSignal.removeEventListener("abort", abortControllerOnRequestAbort);
};
return encode(data, {
signal: controller.signal,
onComplete: cleanupCallbacks,
plugins: [(value) => {
if (value instanceof Error) {
let { name, message, stack } = serverMode === "production" ? sanitizeError(value, serverMode) : value;
return [
"SanitizedError",
name,
message,
stack
];
}
if (value instanceof ErrorResponseImpl) {
let { data, status, statusText } = value;
return [
"ErrorResponse",
data,
status,
statusText
];
}
if (value && typeof value === "object" && SingleFetchRedirectSymbol in value) return ["SingleFetchRedirect", value[SingleFetchRedirectSymbol]];
}],
postPlugins: [(value) => {
if (!value) return;
if (typeof value !== "object") return;
return ["SingleFetchClassInstance", Object.fromEntries(Object.entries(value))];
}, () => ["SingleFetchFallback"]]
});
}
//#endregion
export { SERVER_NO_BODY_STATUS_CODES, encodeViaTurboStream, generateSingleFetchRedirectResponse, singleFetchAction, singleFetchLoaders };
@@ -0,0 +1,28 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/urls.ts
function getNormalizedPath(request) {
let url = new URL(request.url);
let pathname = url.pathname;
if (pathname.endsWith("/_.data")) pathname = pathname.replace(/_\.data$/, "");
else pathname = pathname.replace(/\.data$/, "");
let searchParams = new URLSearchParams(url.search);
searchParams.delete("_routes");
let search = searchParams.toString();
if (search) search = `?${search}`;
return {
pathname,
search,
hash: ""
};
}
//#endregion
export { getNormalizedPath };
@@ -0,0 +1,20 @@
/**
* react-router v8.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
//#region lib/server-runtime/warnings.ts
const alreadyWarned = {};
function warnOnce(condition, message) {
if (!condition && !alreadyWarned[message]) {
alreadyWarned[message] = true;
console.warn(message);
}
}
//#endregion
export { warnOnce };