Socket
Socket
Sign inDemoInstall

@remix-run/server-runtime

Package Overview
Dependencies
Maintainers
2
Versions
1005
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@remix-run/server-runtime - npm Package Compare versions

Comparing version 0.0.0-experimental-a547b878b to 0.0.0-experimental-a7ab46039

dist/deprecations.d.ts

15

dist/build.d.ts

@@ -1,4 +0,5 @@

import type { DataFunctionArgs } from "./routeModules";
import type { ActionFunctionArgs, LoaderFunctionArgs } from "./routeModules";
import type { AssetsManifest, EntryContext, FutureConfig } from "./entry";
import type { ServerRouteManifest } from "./routes";
import type { AppLoadContext } from "./data";
/**

@@ -8,2 +9,3 @@ * The output of the compiler for the server build.

export interface ServerBuild {
mode: string;
entry: {

@@ -14,12 +16,17 @@ module: ServerEntryModule;

assets: AssetsManifest;
basename?: string;
publicPath: string;
assetsBuildDirectory: string;
future: FutureConfig;
isSpaMode: boolean;
}
export interface HandleDocumentRequestFunction {
(request: Request, responseStatusCode: number, responseHeaders: Headers, context: EntryContext): Promise<Response> | Response;
(request: Request, responseStatusCode: number, responseHeaders: Headers, context: EntryContext, loadContext: AppLoadContext): Promise<Response> | Response;
}
export interface HandleDataRequestFunction {
(response: Response, args: DataFunctionArgs): Promise<Response> | Response;
(response: Response, args: LoaderFunctionArgs | ActionFunctionArgs): Promise<Response> | Response;
}
export interface HandleErrorFunction {
(error: unknown, args: LoaderFunctionArgs | ActionFunctionArgs): void;
}
/**

@@ -32,2 +39,4 @@ * A module that serves as the entry point for a Remix app during server

handleDataRequest?: HandleDataRequestFunction;
handleError?: HandleErrorFunction;
streamTimeout?: number;
}

12

dist/cookies.d.ts

@@ -15,3 +15,3 @@ import type { CookieParseOptions, CookieSerializeOptions } from "cookie";

}
export declare type CookieOptions = CookieParseOptions & CookieSerializeOptions & CookieSignatureOptions;
export type CookieOptions = CookieParseOptions & CookieSerializeOptions & CookieSignatureOptions;
/**

@@ -25,3 +25,3 @@ * A HTTP cookie.

*
* @see https://remix.run/api/remix#cookie-api
* @see https://remix.run/utils/cookies#cookie-api
*/

@@ -55,7 +55,7 @@ export interface Cookie {

}
export declare type CreateCookieFunction = (name: string, cookieOptions?: CookieOptions) => Cookie;
export type CreateCookieFunction = (name: string, cookieOptions?: CookieOptions) => Cookie;
/**
* Creates a logical container for managing a browser cookie from the server.
*
* @see https://remix.run/api/remix#createcookie
* @see https://remix.run/utils/cookies#createcookie
*/

@@ -66,8 +66,8 @@ export declare const createCookieFactory: ({ sign, unsign, }: {

}) => CreateCookieFunction;
export declare type IsCookieFunction = (object: any) => object is Cookie;
export type IsCookieFunction = (object: any) => object is Cookie;
/**
* Returns true if an object is a Remix cookie container.
*
* @see https://remix.run/api/remix#iscookie
* @see https://remix.run/utils/cookies#iscookie
*/
export declare const isCookie: IsCookieFunction;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -19,5 +19,16 @@ * Copyright (c) Remix Software Inc.

/**
* 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
*/
/**
* Creates a logical container for managing a browser cookie from the server.
*
* @see https://remix.run/api/remix#createcookie
* @see https://remix.run/utils/cookies#createcookie
*/

@@ -29,6 +40,5 @@ const createCookieFactory = ({

let {
secrets,
secrets = [],
...options
} = {
secrets: [],
path: "/",

@@ -69,3 +79,3 @@ sameSite: "lax",

*
* @see https://remix.run/api/remix#iscookie
* @see https://remix.run/utils/cookies#iscookie
*/

@@ -72,0 +82,0 @@ const isCookie = object => {

@@ -1,2 +0,2 @@

export declare type SignFunction = (value: string, secret: string) => Promise<string>;
export declare type UnsignFunction = (cookie: string, secret: string) => Promise<string | false>;
export type SignFunction = (value: string, secret: string) => Promise<string>;
export type UnsignFunction = (cookie: string, secret: string) => Promise<string | false>;

@@ -1,5 +0,8 @@

import type { ActionFunction, DataFunctionArgs, LoaderFunction } from "./routeModules";
import type { ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs } from "./routeModules";
import type { ResponseStub } from "./single-fetch";
/**
* An object of unknown type for route loaders and actions provided by the
* server's `getLoadContext()` function.
* server's `getLoadContext()` function. This is defined as an empty interface
* specifically so apps can leverage declaration merging to augment this type
* globally: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
*/

@@ -12,31 +15,20 @@ export interface AppLoadContext {

*/
export declare type AppData = any;
export declare function callRouteAction({ loadContext, routeId, action, params, request, }: {
loadContext: AppLoadContext;
routeId: string;
action?: ActionFunction;
params: DataFunctionArgs["params"];
export type AppData = unknown;
export declare function callRouteAction({ loadContext, action, params, request, routeId, singleFetch, response, }: {
request: Request;
}): Promise<Response>;
export declare function callRouteLoader({ loadContext, routeId, loader, params, request, }: {
request: Request;
routeId: string;
loader?: LoaderFunction;
params: DataFunctionArgs["params"];
loadContext: AppLoadContext;
}): Promise<Response>;
export declare function callRouteActionRR({ loadContext, action, params, request, routeId, }: {
request: Request;
action: ActionFunction;
params: DataFunctionArgs["params"];
params: ActionFunctionArgs["params"];
loadContext: AppLoadContext;
routeId: string;
}): Promise<Response>;
export declare function callRouteLoaderRR({ loadContext, loader, params, request, routeId, }: {
singleFetch: boolean;
response?: ResponseStub;
}): Promise<{} | Response | null>;
export declare function callRouteLoader({ loadContext, loader, params, request, routeId, singleFetch, response, }: {
request: Request;
loader: LoaderFunction;
params: DataFunctionArgs["params"];
params: LoaderFunctionArgs["params"];
loadContext: AppLoadContext;
routeId: string;
}): Promise<Response>;
export declare function extractData(response: Response): Promise<unknown>;
singleFetch: boolean;
response?: ResponseStub;
}): Promise<{} | Response | null>;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -17,3 +17,14 @@ * Copyright (c) Remix Software Inc.

async function callRouteActionRR({
/**
* An object of unknown type for route loaders and actions provided by the
* server's `getLoadContext()` function. This is defined as an empty interface
* specifically so apps can leverage declaration merging to augment this type
* globally: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
*/
/**
* Data for a route that was returned from a `loader()`.
*/
async function callRouteAction({
loadContext,

@@ -23,3 +34,5 @@ action,

request,
routeId
routeId,
singleFetch,
response
}) {

@@ -29,3 +42,8 @@ let result = await action({

context: loadContext,
params
params,
// Only provided when single fetch is enabled, and made available via
// `defineAction` types, not `ActionFunctionArgs`
...(singleFetch ? {
response
} : null)
});

@@ -35,5 +53,10 @@ if (result === undefined) {

}
// Allow naked object returns when single fetch is enabled
if (singleFetch) {
return result;
}
return responses.isResponse(result) ? result : responses.json(result);
}
async function callRouteLoaderRR({
async function callRouteLoader({
loadContext,

@@ -43,3 +66,5 @@ loader,

request,
routeId
routeId,
singleFetch,
response
}) {

@@ -49,3 +74,8 @@ let result = await loader({

context: loadContext,
params
params,
// Only provided when single fetch is enabled, and made available via
// `defineLoader` types, not `LoaderFunctionArgs`
...(singleFetch ? {
response
} : null)
});

@@ -55,4 +85,21 @@ if (result === undefined) {

}
if (responses.isDeferredData(result)) {
if (result.init && responses.isRedirectStatusCode(result.init.status || 200)) {
return responses.redirect(new Headers(result.init.headers).get("Location"), result.init);
}
return result;
}
// Allow naked object returns when single fetch is enabled
if (singleFetch) {
return result;
}
return responses.isResponse(result) ? result : responses.json(result);
}
// TODO: Document these search params better
// and stop stripping these in V2. These break
// support for running in a SW and also expose
// valuable info to data funcs that is being asked
// for such as "is this a data request?".
function stripIndexParam(request) {

@@ -71,3 +118,12 @@ let url = new URL(request.url);

}
return new Request(url.href, request);
let init = {
method: request.method,
body: request.body,
headers: request.headers,
signal: request.signal
};
if (init.body) {
init.duplex = "half";
}
return new Request(url.href, init);
}

@@ -77,6 +133,15 @@ function stripDataParam(request) {

url.searchParams.delete("_data");
return new Request(url.href, request);
let init = {
method: request.method,
body: request.body,
headers: request.headers,
signal: request.signal
};
if (init.body) {
init.duplex = "half";
}
return new Request(url.href, init);
}
exports.callRouteActionRR = callRouteActionRR;
exports.callRouteLoaderRR = callRouteLoaderRR;
exports.callRouteAction = callRouteAction;
exports.callRouteLoader = callRouteLoader;

@@ -1,18 +0,31 @@

import type { AppState } from "./errors";
import type { RouteManifest, ServerRouteManifest, EntryRoute, ServerRoute } from "./routes";
import type { RouteData } from "./routeData";
import type { RouteMatch } from "./routeMatching";
import type { StaticHandlerContext } from "@remix-run/router";
import type { SerializedError } from "./errors";
import type { RouteManifest, ServerRouteManifest, EntryRoute } from "./routes";
import type { RouteModules, EntryRouteModule } from "./routeModules";
export interface EntryContext {
appState: AppState;
manifest: AssetsManifest;
matches: RouteMatch<EntryRoute>[];
routeData: RouteData;
actionData?: RouteData;
routeModules: RouteModules<EntryRouteModule>;
criticalCss?: string;
serverHandoffString?: string;
serverHandoffStream?: ReadableStream<Uint8Array>;
renderMeta?: {
didRenderScripts?: boolean;
streamCache?: Record<number, Promise<void> & {
result?: {
done: boolean;
value: string;
};
error?: unknown;
}>;
};
staticHandlerContext: StaticHandlerContext;
future: FutureConfig;
isSpaMode: boolean;
serializeError(error: Error): SerializedError;
}
export interface FutureConfig {
v2_meta: boolean;
v3_fetcherPersist: boolean;
v3_relativeSplatPath: boolean;
v3_throwAbortReason: boolean;
unstable_singleFetch: boolean;
}

@@ -27,4 +40,4 @@ export interface AssetsManifest {

version: string;
hmrRuntime?: string;
}
export declare function createEntryMatches(matches: RouteMatch<ServerRoute>[], routes: RouteManifest<EntryRoute>): RouteMatch<EntryRoute>[];
export declare function createEntryRouteModules(manifest: ServerRouteManifest): RouteModules<EntryRouteModule>;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -15,9 +15,2 @@ * Copyright (c) Remix Software Inc.

function createEntryMatches(matches, routes) {
return matches.map(match => ({
params: match.params,
pathname: match.pathname,
route: routes[match.route.id]
}));
}
function createEntryRouteModules(manifest) {

@@ -30,3 +23,2 @@ return Object.keys(manifest).reduce((memo, routeId) => {

exports.createEntryMatches = createEntryMatches;
exports.createEntryRouteModules = createEntryRouteModules;

@@ -0,1 +1,3 @@

import type { StaticHandlerContext } from "@remix-run/router";
import { ServerMode } from "./mode";
/**

@@ -42,20 +44,9 @@ * This thing probably warrants some explanation.

*/
export interface AppState {
error?: SerializedError;
catch?: ThrownResponse;
catchBoundaryRouteId: string | null;
loaderBoundaryRouteId: string | null;
renderBoundaryRouteId: string | null;
trackBoundaries: boolean;
trackCatchBoundaries: boolean;
}
export interface ThrownResponse<T = any> {
status: number;
statusText: string;
data: T;
}
export declare type SerializedError = {
export declare function sanitizeError<T = unknown>(error: T, serverMode: ServerMode): T | Error;
export declare function sanitizeErrors(errors: NonNullable<StaticHandlerContext["errors"]>, serverMode: ServerMode): {};
export type SerializedError = {
message: string;
stack?: string;
};
export declare function serializeError(error: Error): Promise<SerializedError>;
export declare function serializeError(error: Error, serverMode: ServerMode): SerializedError;
export declare function serializeErrors(errors: StaticHandlerContext["errors"], serverMode: ServerMode): StaticHandlerContext["errors"];
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -15,2 +15,5 @@ * Copyright (c) Remix Software Inc.

var router = require('@remix-run/router');
var mode = require('./mode.js');
/**

@@ -58,12 +61,64 @@ * This thing probably warrants some explanation.

function sanitizeError(error, serverMode) {
if (error instanceof Error && serverMode !== mode.ServerMode.Development) {
let sanitized = new Error("Unexpected Server Error");
sanitized.stack = undefined;
return sanitized;
}
return error;
}
function sanitizeErrors(errors, serverMode) {
return Object.entries(errors).reduce((acc, [routeId, error]) => {
return Object.assign(acc, {
[routeId]: sanitizeError(error, serverMode)
});
}, {});
}
// must be type alias due to inference issues on interfaces
// https://github.com/microsoft/TypeScript/issues/15300
async function serializeError(error) {
function serializeError(error, serverMode) {
let sanitized = sanitizeError(error, serverMode);
return {
message: error.message,
stack: error.stack
message: sanitized.message,
stack: sanitized.stack
};
}
function serializeErrors(errors, serverMode) {
if (!errors) return null;
let entries = Object.entries(errors);
let serialized = {};
for (let [key, val] of entries) {
// Hey you! If you change this, please change the corresponding logic in
// deserializeErrors in remix-react/errors.ts :)
if (router.isRouteErrorResponse(val)) {
serialized[key] = {
...val,
__type: "RouteErrorResponse"
};
} else if (val instanceof Error) {
let sanitized = sanitizeError(val, serverMode);
serialized[key] = {
message: sanitized.message,
stack: sanitized.stack,
__type: "Error",
// If this is a subclass (i.e., ReferenceError), send up the type so we
// can re-create the same type during hydration. This will only apply
// in dev mode since all production errors are sanitized to normal
// Error instances
...(sanitized.name !== "Error" ? {
__subType: sanitized.name
} : {})
};
} else {
serialized[key] = val;
}
}
return serialized;
}
exports.sanitizeError = sanitizeError;
exports.sanitizeErrors = sanitizeErrors;
exports.serializeError = serializeError;
exports.serializeErrors = serializeErrors;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -15,5 +15,16 @@ * Copyright (c) Remix Software Inc.

/**
* 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
*/
/**
* Creates a logical container for managing a browser cookie from the server.
*
* @see https://remix.run/api/remix#createcookie
* @see https://remix.run/utils/cookies#createcookie
*/

@@ -25,6 +36,5 @@ const createCookieFactory = ({

let {
secrets,
secrets = [],
...options
} = {
secrets: [],
path: "/",

@@ -65,3 +75,3 @@ sameSite: "lax",

*
* @see https://remix.run/api/remix#iscookie
* @see https://remix.run/utils/cookies#iscookie
*/

@@ -68,0 +78,0 @@ const isCookie = object => {

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -11,5 +11,16 @@ * Copyright (c) Remix Software Inc.

*/
import { isResponse, json } from './responses.js';
import { isResponse, json, isDeferredData, isRedirectStatusCode, redirect } from './responses.js';
async function callRouteActionRR({
/**
* An object of unknown type for route loaders and actions provided by the
* server's `getLoadContext()` function. This is defined as an empty interface
* specifically so apps can leverage declaration merging to augment this type
* globally: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
*/
/**
* Data for a route that was returned from a `loader()`.
*/
async function callRouteAction({
loadContext,

@@ -19,3 +30,5 @@ action,

request,
routeId
routeId,
singleFetch,
response
}) {

@@ -25,3 +38,8 @@ let result = await action({

context: loadContext,
params
params,
// Only provided when single fetch is enabled, and made available via
// `defineAction` types, not `ActionFunctionArgs`
...(singleFetch ? {
response
} : null)
});

@@ -31,5 +49,10 @@ if (result === undefined) {

}
// Allow naked object returns when single fetch is enabled
if (singleFetch) {
return result;
}
return isResponse(result) ? result : json(result);
}
async function callRouteLoaderRR({
async function callRouteLoader({
loadContext,

@@ -39,3 +62,5 @@ loader,

request,
routeId
routeId,
singleFetch,
response
}) {

@@ -45,3 +70,8 @@ let result = await loader({

context: loadContext,
params
params,
// Only provided when single fetch is enabled, and made available via
// `defineLoader` types, not `LoaderFunctionArgs`
...(singleFetch ? {
response
} : null)
});

@@ -51,4 +81,21 @@ if (result === undefined) {

}
if (isDeferredData(result)) {
if (result.init && isRedirectStatusCode(result.init.status || 200)) {
return redirect(new Headers(result.init.headers).get("Location"), result.init);
}
return result;
}
// Allow naked object returns when single fetch is enabled
if (singleFetch) {
return result;
}
return isResponse(result) ? result : json(result);
}
// TODO: Document these search params better
// and stop stripping these in V2. These break
// support for running in a SW and also expose
// valuable info to data funcs that is being asked
// for such as "is this a data request?".
function stripIndexParam(request) {

@@ -67,3 +114,12 @@ let url = new URL(request.url);

}
return new Request(url.href, request);
let init = {
method: request.method,
body: request.body,
headers: request.headers,
signal: request.signal
};
if (init.body) {
init.duplex = "half";
}
return new Request(url.href, init);
}

@@ -73,5 +129,14 @@ function stripDataParam(request) {

url.searchParams.delete("_data");
return new Request(url.href, request);
let init = {
method: request.method,
body: request.body,
headers: request.headers,
signal: request.signal
};
if (init.body) {
init.duplex = "half";
}
return new Request(url.href, init);
}
export { callRouteActionRR, callRouteLoaderRR };
export { callRouteAction, callRouteLoader };
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -11,9 +11,2 @@ * Copyright (c) Remix Software Inc.

*/
function createEntryMatches(matches, routes) {
return matches.map(match => ({
params: match.params,
pathname: match.pathname,
route: routes[match.route.id]
}));
}
function createEntryRouteModules(manifest) {

@@ -26,2 +19,2 @@ return Object.keys(manifest).reduce((memo, routeId) => {

export { createEntryMatches, createEntryRouteModules };
export { createEntryRouteModules };
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -11,2 +11,5 @@ * Copyright (c) Remix Software Inc.

*/
import { isRouteErrorResponse } from '@remix-run/router';
import { ServerMode } from './mode.js';
/**

@@ -54,12 +57,61 @@ * This thing probably warrants some explanation.

function sanitizeError(error, serverMode) {
if (error instanceof Error && serverMode !== ServerMode.Development) {
let sanitized = new Error("Unexpected Server Error");
sanitized.stack = undefined;
return sanitized;
}
return error;
}
function sanitizeErrors(errors, serverMode) {
return Object.entries(errors).reduce((acc, [routeId, error]) => {
return Object.assign(acc, {
[routeId]: sanitizeError(error, serverMode)
});
}, {});
}
// must be type alias due to inference issues on interfaces
// https://github.com/microsoft/TypeScript/issues/15300
async function serializeError(error) {
function serializeError(error, serverMode) {
let sanitized = sanitizeError(error, serverMode);
return {
message: error.message,
stack: error.stack
message: sanitized.message,
stack: sanitized.stack
};
}
function serializeErrors(errors, serverMode) {
if (!errors) return null;
let entries = Object.entries(errors);
let serialized = {};
for (let [key, val] of entries) {
// Hey you! If you change this, please change the corresponding logic in
// deserializeErrors in remix-react/errors.ts :)
if (isRouteErrorResponse(val)) {
serialized[key] = {
...val,
__type: "RouteErrorResponse"
};
} else if (val instanceof Error) {
let sanitized = sanitizeError(val, serverMode);
serialized[key] = {
message: sanitized.message,
stack: sanitized.stack,
__type: "Error",
// If this is a subclass (i.e., ReferenceError), send up the type so we
// can re-create the same type during hydration. This will only apply
// in dev mode since all production errors are sanitized to normal
// Error instances
...(sanitized.name !== "Error" ? {
__subType: sanitized.name
} : {})
};
} else {
serialized[key] = val;
}
}
return serialized;
}
export { serializeError };
export { sanitizeError, sanitizeErrors, serializeError, serializeErrors };
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -13,2 +13,3 @@ * Copyright (c) Remix Software Inc.

// @ts-ignore
function composeUploadHandlers(...handlers) {

@@ -30,3 +31,3 @@ return async part => {

* TODO: Update this comment
* @see https://remix.run/api/remix#parsemultipartformdata-node
* @see https://remix.run/utils/parse-multipart-form-data
*/

@@ -33,0 +34,0 @@ async function parseMultipartFormData(request, uploadHandler) {

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -13,19 +13,63 @@ * Copyright (c) Remix Software Inc.

function getDocumentHeadersRR(build, context, matches) {
return matches.reduce((parentHeaders, match, index) => {
var _context$loaderHeader, _context$actionHeader;
function getDocumentHeaders(build, context) {
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) {
// Look for any errorHeaders from the boundary route down, which can be
// identified by the presence of headers but no data
let {
actionHeaders,
actionData,
loaderHeaders,
loaderData
} = context;
context.matches.slice(boundaryIdx).some(match => {
let id = match.route.id;
if (actionHeaders[id] && (!actionData || actionData[id] === undefined)) {
errorHeaders = actionHeaders[id];
} else if (loaderHeaders[id] && loaderData[id] === undefined) {
errorHeaders = loaderHeaders[id];
}
return errorHeaders != null;
});
}
return matches.reduce((parentHeaders, match, idx) => {
let {
id
} = match.route;
let routeModule = build.routes[id].module;
let loaderHeaders = ((_context$loaderHeader = context.loaderHeaders) === null || _context$loaderHeader === void 0 ? void 0 : _context$loaderHeader[id]) || new Headers();
let actionHeaders = ((_context$actionHeader = context.actionHeaders) === null || _context$actionHeader === void 0 ? void 0 : _context$actionHeader[id]) || new Headers();
let loaderHeaders = context.loaderHeaders[id] || new Headers();
let actionHeaders = context.actionHeaders[id] || new Headers();
// Only expose errorHeaders to the leaf headers() function to
// avoid duplication via parentHeaders
let includeErrorHeaders = errorHeaders != undefined && idx === matches.length - 1;
// Only prepend cookies from errorHeaders at the leaf renderable route
// when it's not the same as loaderHeaders/actionHeaders to avoid
// duplicate cookies
let includeErrorCookies = includeErrorHeaders && errorHeaders !== loaderHeaders && errorHeaders !== actionHeaders;
// Use the parent headers for any route without a `headers` export
if (routeModule.headers == null) {
let headers = new Headers(parentHeaders);
if (includeErrorCookies) {
prependCookies(errorHeaders, headers);
}
prependCookies(actionHeaders, headers);
prependCookies(loaderHeaders, headers);
return headers;
}
let headers = new Headers(routeModule.headers ? typeof routeModule.headers === "function" ? routeModule.headers({
loaderHeaders,
parentHeaders,
actionHeaders
actionHeaders,
errorHeaders: includeErrorHeaders ? errorHeaders : undefined
}) : routeModule.headers : undefined);
// Automatically preserve Set-Cookie headers that were set either by the
// loader or by a parent route.
// Automatically preserve Set-Cookie headers from bubbled responses,
// loaders, errors, and parent routes
if (includeErrorCookies) {
prependCookies(errorHeaders, headers);
}
prependCookies(actionHeaders, headers);

@@ -47,2 +91,2 @@ prependCookies(loaderHeaders, headers);

export { getDocumentHeadersRR };
export { getDocumentHeaders };
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -13,3 +13,4 @@ * Copyright (c) Remix Software Inc.

export { composeUploadHandlers as unstable_composeUploadHandlers, parseMultipartFormData as unstable_parseMultipartFormData } from './formData.js';
export { json, redirect } from './responses.js';
export { defer, json, redirect, redirectDocument } from './responses.js';
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, defineAction as unstable_defineAction, defineLoader as unstable_defineLoader } from './single-fetch.js';
export { createRequestHandler } from './server.js';

@@ -21,1 +22,2 @@ export { createSession, createSessionStorageFactory, isSession } from './sessions.js';

export { MaxPartSizeExceededError } from './upload/errors.js';
export { broadcastDevReady, logDevReady, setDevServerHooks as unstable_setDevServerHooks } from './dev.js';
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -14,8 +14,8 @@ * Copyright (c) Remix Software Inc.

*/
let ServerMode;
(function (ServerMode) {
let ServerMode = /*#__PURE__*/function (ServerMode) {
ServerMode["Development"] = "development";
ServerMode["Production"] = "production";
ServerMode["Test"] = "test";
})(ServerMode || (ServerMode = {}));
return ServerMode;
}({});
function isServerMode(value) {

@@ -22,0 +22,0 @@ return value === ServerMode.Development || value === ServerMode.Production || value === ServerMode.Test;

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -11,2 +11,5 @@ * Copyright (c) Remix Software Inc.

*/
import { json as json$1, defer as defer$1, redirect as redirect$1, redirectDocument as redirectDocument$1 } from '@remix-run/router';
import { serializeError } from './errors.js';
// must be a type since this is a subtype of response

@@ -19,39 +22,40 @@ // interfaces must conform to the types they extend

*
* @see https://remix.run/api/remix#json
* @see https://remix.run/utils/json
*/
const json = (data, init = {}) => {
let responseInit = typeof init === "number" ? {
status: init
} : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/json; charset=utf-8");
}
return new Response(JSON.stringify(data), {
...responseInit,
headers
});
return json$1(data, init);
};
/**
* This is a shortcut for creating Remix deferred responses
*
* @see https://remix.run/utils/defer
*/
const defer = (data, init = {}) => {
return defer$1(data, init);
};
/**
* A redirect response. Sets the status code and the `Location` header.
* Defaults to "302 Found".
*
* @see https://remix.run/api/remix#redirect
* @see https://remix.run/utils/redirect
*/
const redirect = (url, init = 302) => {
let responseInit = init;
if (typeof responseInit === "number") {
responseInit = {
status: responseInit
};
} else if (typeof responseInit.status === "undefined") {
responseInit.status = 302;
}
let headers = new Headers(responseInit.headers);
headers.set("Location", url);
return new Response(null, {
...responseInit,
headers
});
return redirect$1(url, init);
};
/**
* A redirect response that will force a document reload to the new location.
* Sets the status code and the `Location` header.
* Defaults to "302 Found".
*
* @see https://remix.run/utils/redirect
*/
const redirectDocument = (url, init = 302) => {
return redirectDocument$1(url, init);
};
function isDeferredData(value) {
let deferred = value;
return deferred && typeof deferred === "object" && typeof deferred.data === "object" && typeof deferred.subscribe === "function" && typeof deferred.cancel === "function" && typeof deferred.resolveData === "function";
}
function isResponse(value) {

@@ -61,6 +65,62 @@ return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";

const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
function isRedirectStatusCode(statusCode) {
return redirectStatusCodes.has(statusCode);
}
function isRedirectResponse(response) {
return redirectStatusCodes.has(response.status);
return isRedirectStatusCode(response.status);
}
function isTrackedPromise(value) {
return value != null && typeof value.then === "function" && value._tracked === true;
}
export { isRedirectResponse, isResponse, json, redirect };
// TODO: Figure out why ReadableStream types are borked sooooooo badly
// in this file. Probably related to our TS configurations and configs
// bleeding into each other.
const DEFERRED_VALUE_PLACEHOLDER_PREFIX = "__deferred_promise:";
function createDeferredReadableStream(deferredData, signal, serverMode) {
let encoder = new TextEncoder();
let stream = new ReadableStream({
async start(controller) {
let criticalData = {};
let preresolvedKeys = [];
for (let [key, value] of Object.entries(deferredData.data)) {
if (isTrackedPromise(value)) {
criticalData[key] = `${DEFERRED_VALUE_PLACEHOLDER_PREFIX}${key}`;
if (typeof value._data !== "undefined" || typeof value._error !== "undefined") {
preresolvedKeys.push(key);
}
} else {
criticalData[key] = value;
}
}
// Send the critical data
controller.enqueue(encoder.encode(JSON.stringify(criticalData) + "\n\n"));
for (let preresolvedKey of preresolvedKeys) {
enqueueTrackedPromise(controller, encoder, preresolvedKey, deferredData.data[preresolvedKey], serverMode);
}
let unsubscribe = deferredData.subscribe((aborted, settledKey) => {
if (settledKey) {
enqueueTrackedPromise(controller, encoder, settledKey, deferredData.data[settledKey], serverMode);
}
});
await deferredData.resolveData(signal);
unsubscribe();
controller.close();
}
});
return stream;
}
function enqueueTrackedPromise(controller, encoder, settledKey, promise, serverMode) {
if ("_error" in promise) {
controller.enqueue(encoder.encode("error:" + JSON.stringify({
[settledKey]: promise._error instanceof Error ? serializeError(promise._error, serverMode) : promise._error
}) + "\n\n"));
} else {
controller.enqueue(encoder.encode("data:" + JSON.stringify({
[settledKey]: promise._data ?? null
}) + "\n\n"));
}
}
export { createDeferredReadableStream, defer, isDeferredData, isRedirectResponse, isRedirectStatusCode, isResponse, json, redirect, redirectDocument };
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -11,7 +11,6 @@ * Copyright (c) Remix Software Inc.

*/
import { matchRoutes } from 'react-router-dom';
import { matchRoutes } from '@remix-run/router';
// TODO: export/import from react-router-dom
function matchServerRoutes(routes, pathname) {
let matches = matchRoutes(routes, pathname);
function matchServerRoutes(routes, pathname, basename) {
let matches = matchRoutes(routes, pathname, basename);
if (!matches) return null;

@@ -18,0 +17,0 @@ return matches.map(match => ({

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -11,9 +11,26 @@ * Copyright (c) Remix Software Inc.

*/
import { callRouteLoaderRR, callRouteActionRR } from './data.js';
import { callRouteLoader, callRouteAction } from './data.js';
// TODO: RRR - Change import to @remix-run/router
function createRoutes(manifest, parentId) {
return Object.entries(manifest).filter(([, route]) => route.parentId === parentId).map(([id, route]) => ({
// NOTE: make sure to change the Route in remix-react if you change this
// NOTE: make sure to change the EntryRoute in remix-react if you change this
function groupRoutesByParentId(manifest) {
let routes = {};
Object.values(manifest).forEach(route => {
let parentId = route.parentId || "";
if (!routes[parentId]) {
routes[parentId] = [];
}
routes[parentId].push(route);
});
return routes;
}
// Create a map of routes by parentId to use recursively instead of
// repeatedly filtering the manifest.
function createRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId(manifest)) {
return (routesByParentId[parentId] || []).map(route => ({
...route,
children: createRoutes(manifest, id)
children: createRoutes(manifest, route.id, routesByParentId)
}));

@@ -24,24 +41,31 @@ }

// createStaticHandler
function createStaticHandlerDataRoutes(manifest, loadContext, parentId) {
return Object.values(manifest).filter(route => route.parentId === parentId).map(route => {
function createStaticHandlerDataRoutes(manifest, future, parentId = "", routesByParentId = groupRoutesByParentId(manifest)) {
return (routesByParentId[parentId] || []).map(route => {
let commonRoute = {
// Always include root due to default boundaries
hasErrorBoundary: route.id === "root" || route.module.CatchBoundary != null || route.module.ErrorBoundary != null,
hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null,
id: route.id,
path: route.path,
loader: route.module.loader ? args => callRouteLoaderRR({
...args,
loadContext,
loader: route.module.loader ?
// Need to use RR's version here to permit the optional context even
// though we know it'll always be provided in remix
(args, dataStrategyCtx) => callRouteLoader({
request: args.request,
params: args.params,
loadContext: args.context,
loader: route.module.loader,
routeId: route.id
routeId: route.id,
singleFetch: future.unstable_singleFetch === true,
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response
}) : undefined,
action: route.module.action ? args => callRouteActionRR({
...args,
loadContext,
action: route.module.action ? (args, dataStrategyCtx) => callRouteAction({
request: args.request,
params: args.params,
loadContext: args.context,
action: route.module.action,
routeId: route.id
routeId: route.id,
singleFetch: future.unstable_singleFetch === true,
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response
}) : undefined,
handle: route.module.handle,
// TODO: RRR - Implement!
shouldRevalidate: () => true
handle: route.module.handle
};

@@ -53,3 +77,3 @@ return route.index ? {

caseSensitive: route.caseSensitive,
children: createStaticHandlerDataRoutes(manifest, loadContext, route.id),
children: createStaticHandlerDataRoutes(manifest, future, route.id, routesByParentId),
...commonRoute

@@ -56,0 +80,0 @@ };

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -11,47 +11,159 @@ * Copyright (c) Remix Software Inc.

*/
import { createEntryMatches, createEntryRouteModules } from './entry.js';
import { serializeError } from './errors.js';
import { getDocumentHeadersRR } from './headers.js';
import { UNSAFE_DEFERRED_SYMBOL, isRouteErrorResponse, json as json$1, UNSAFE_ErrorResponseImpl, getStaticContextFromError, stripBasename, createStaticHandler } from '@remix-run/router';
import { createEntryRouteModules } from './entry.js';
import { serializeError, sanitizeErrors, serializeErrors } from './errors.js';
import { getDocumentHeaders } from './headers.js';
import invariant from './invariant.js';
import { isServerMode, ServerMode } from './mode.js';
import { ServerMode, isServerMode } from './mode.js';
import { matchServerRoutes } from './routeMatching.js';
import { createRoutes, createStaticHandlerDataRoutes } from './routes.js';
import { isRedirectResponse, json } from './responses.js';
import { json, isRedirectResponse, createDeferredReadableStream, isResponse, isRedirectStatusCode } from './responses.js';
import { createServerHandoffString } from './serverHandoff.js';
import { unstable_createStaticHandler } from './router/router.js';
import { isRouteErrorResponse } from './router/utils.js';
import { getDevServerHooks } from './dev.js';
import { getSingleFetchRedirect, encodeViaTurboStream, singleFetchAction, singleFetchLoaders, getResponseStubs, getSingleFetchDataStrategy, mergeResponseStubs, isResponseStub, getSingleFetchResourceRouteDataStrategy, ResponseStubOperationsSymbol, SingleFetchRedirectSymbol } from './single-fetch.js';
import { resourceRouteJsonWarning } from './deprecations.js';
// TODO: RRR - Change import to @remix-run/router
const createRequestHandler = (build, mode) => {
function derive(build, mode) {
var _build$future, _build$future2;
let routes = createRoutes(build.routes);
let dataRoutes = createStaticHandlerDataRoutes(build.routes, build.future);
let serverMode = isServerMode(mode) ? mode : ServerMode.Production;
let staticHandler = createStaticHandler(dataRoutes, {
basename: build.basename,
future: {
v7_relativeSplatPath: ((_build$future = build.future) === null || _build$future === void 0 ? void 0 : _build$future.v3_relativeSplatPath) === true,
v7_throwAbortReason: ((_build$future2 = build.future) === null || _build$future2 === void 0 ? void 0 : _build$future2.v3_throwAbortReason) === true
}
});
let errorHandler = build.entry.module.handleError || ((error, {
request
}) => {
if (serverMode !== ServerMode.Test && !request.signal.aborted) {
console.error(
// @ts-expect-error This is "private" from users but intended for internal use
isRouteErrorResponse(error) && error.error ? error.error : error);
}
});
return {
routes,
dataRoutes,
serverMode,
staticHandler,
errorHandler
};
}
// @ts-ignore
const createRequestHandler = (build, mode) => {
let _build;
let routes;
let serverMode;
let staticHandler;
let errorHandler;
return async function requestHandler(request, loadContext = {}) {
_build = typeof build === "function" ? await build() : build;
mode ??= _build.mode;
if (typeof build === "function") {
let derived = derive(_build, mode);
routes = derived.routes;
serverMode = derived.serverMode;
staticHandler = derived.staticHandler;
errorHandler = derived.errorHandler;
} else if (!routes || !serverMode || !staticHandler || !errorHandler) {
let derived = derive(_build, mode);
routes = derived.routes;
serverMode = derived.serverMode;
staticHandler = derived.staticHandler;
errorHandler = derived.errorHandler;
}
let url = new URL(request.url);
let matches = matchServerRoutes(routes, url.pathname);
let staticHandler;
{
staticHandler = unstable_createStaticHandler(createStaticHandlerDataRoutes(build.routes, loadContext));
// Manifest request for fog of war
if (url.pathname === "/__manifest") {
await new Promise(resolve => setTimeout(resolve, 1000));
let data = {
patches: {},
notFoundPaths: []
};
let paths = url.searchParams.getAll("paths");
if (paths.length > 0) {
for (let path of paths) {
let matches = matchServerRoutes(routes, path, _build.basename);
if (matches) {
for (let match of matches) {
let routeId = match.route.id;
data.patches[routeId] = _build.assets.routes[routeId];
}
} else {
data.notFoundPaths.push(path);
}
}
return json(data);
}
return new Response("Invalid Request", {
status: 400
});
}
let matches = matchServerRoutes(routes, url.pathname, _build.basename);
let params = matches && matches.length > 0 ? matches[0].params : {};
let handleError = error => {
if (mode === ServerMode.Development) {
var _getDevServerHooks, _getDevServerHooks$pr;
(_getDevServerHooks = getDevServerHooks()) === null || _getDevServerHooks === void 0 ? void 0 : (_getDevServerHooks$pr = _getDevServerHooks.processRequestError) === null || _getDevServerHooks$pr === void 0 ? void 0 : _getDevServerHooks$pr.call(_getDevServerHooks, error);
}
errorHandler(error, {
context: loadContext,
params,
request
});
};
let response;
if (url.searchParams.has("_data")) {
if (_build.future.unstable_singleFetch) {
handleError(new Error("Warning: Single fetch-enabled apps should not be making ?_data requests, " + "this is likely to break in the future"));
}
let routeId = url.searchParams.get("_data");
{
response = await handleDataRequestRR(serverMode, staticHandler, routeId, request);
response = await handleDataRequest(serverMode, _build, staticHandler, routeId, request, loadContext, handleError);
if (_build.entry.module.handleDataRequest) {
response = await _build.entry.module.handleDataRequest(response, {
context: loadContext,
params,
request
});
if (isRedirectResponse(response)) {
response = createRemixRedirectResponse(response, _build.basename);
}
}
if (build.entry.module.handleDataRequest) {
let match = matches.find(match => match.route.id == routeId);
response = await build.entry.module.handleDataRequest(response, {
} else if (_build.future.unstable_singleFetch && url.pathname.endsWith(".data")) {
let handlerUrl = new URL(request.url);
handlerUrl.pathname = handlerUrl.pathname.replace(/\.data$/, "").replace(/^\/_root$/, "/");
let singleFetchMatches = matchServerRoutes(routes, handlerUrl.pathname, _build.basename);
response = await handleSingleFetchRequest(serverMode, _build, staticHandler, request, handlerUrl, loadContext, handleError);
if (_build.entry.module.handleDataRequest) {
response = await _build.entry.module.handleDataRequest(response, {
context: loadContext,
params: match.params,
params: singleFetchMatches ? singleFetchMatches[0].params : {},
request
});
if (isRedirectResponse(response)) {
let result = getSingleFetchRedirect(response.status, response.headers);
if (request.method === "GET") {
result = {
[SingleFetchRedirectSymbol]: result
};
}
let headers = new Headers(response.headers);
headers.set("Content-Type", "text/x-turbo");
return new Response(encodeViaTurboStream(result, request.signal, _build.entry.module.streamTimeout, serverMode), {
status: 200,
headers
});
}
}
} else if (matches && matches[matches.length - 1].route.module.default == null) {
{
response = await handleResourceRequestRR(serverMode, staticHandler, matches.slice(-1)[0].route.id, request);
}
} else if (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 {
{
response = await handleDocumentRequestRR(serverMode, build, staticHandler, request);
}
var _getDevServerHooks2, _getDevServerHooks2$g;
let criticalCss = mode === ServerMode.Development ? await ((_getDevServerHooks2 = getDevServerHooks()) === null || _getDevServerHooks2 === void 0 ? void 0 : (_getDevServerHooks2$g = _getDevServerHooks2.getCriticalCss) === null || _getDevServerHooks2$g === void 0 ? void 0 : _getDevServerHooks2$g.call(_getDevServerHooks2, _build, url.pathname)) : undefined;
response = await handleDocumentRequest(serverMode, _build, staticHandler, request, loadContext, handleError, criticalCss);
}

@@ -68,83 +180,77 @@ if (request.method === "HEAD") {

};
async function handleDataRequestRR(serverMode, staticHandler, routeId, request) {
async function handleDataRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
try {
let response = await staticHandler.queryRoute(request, routeId);
let response = await staticHandler.queryRoute(request, {
routeId,
requestContext: loadContext
});
if (isRedirectResponse(response)) {
// We don't have any way to prevent a fetch request from following
// redirects. So we use the `X-Remix-Redirect` header to indicate the
// next URL, and then "follow" the redirect manually on the client.
let headers = new Headers(response.headers);
headers.set("X-Remix-Redirect", headers.get("Location"));
headers.delete("Location");
if (response.headers.get("Set-Cookie") !== null) {
headers.set("X-Remix-Revalidate", "yes");
}
return new Response(null, {
status: 204,
headers
});
return createRemixRedirectResponse(response, build.basename);
}
if (UNSAFE_DEFERRED_SYMBOL in response) {
let deferredData = response[UNSAFE_DEFERRED_SYMBOL];
let body = createDeferredReadableStream(deferredData, request.signal, serverMode);
let init = deferredData.init || {};
let headers = new Headers(init.headers);
headers.set("Content-Type", "text/remix-deferred");
// Mark successful responses with a header so we can identify in-flight
// network errors that are missing this header
headers.set("X-Remix-Response", "yes");
init.headers = headers;
return new Response(body, init);
}
// Mark all successful responses with a header so we can identify in-flight
// network errors that are missing this header
response.headers.set("X-Remix-Response", "yes");
return response;
} catch (error) {
if (error instanceof Response) {
if (isResponse(error)) {
error.headers.set("X-Remix-Catch", "yes");
return error;
}
let status = 500;
let errorInstance = error;
if (isRouteErrorResponse(error)) {
status = error.status;
errorInstance = error.error || errorInstance;
handleError(error);
return errorResponseToJson(error, serverMode);
}
if (serverMode !== ServerMode.Test && !request.signal.aborted) {
console.error(errorInstance);
}
if (serverMode === ServerMode.Development && errorInstance instanceof Error) {
return errorBoundaryError(errorInstance, status);
}
return errorBoundaryError(new Error("Unexpected Server Error"), status);
let errorInstance = error instanceof Error || error instanceof DOMException ? error : new Error("Unexpected Server Error");
handleError(errorInstance);
return json$1(serializeError(errorInstance, serverMode), {
status: 500,
headers: {
"X-Remix-Error": "yes"
}
});
}
}
function findParentBoundary(routes, routeId, error) {
// Fall back to the root route if we don't match any routes, since Remix
// has default error/catch boundary handling. This handles the case where
// react-router doesn't have a matching "root" route to assign the error to
// so it returns context.errors = { __shim-error-route__: ErrorResponse }
let route = routes[routeId] || routes["root"];
// Router-thrown ErrorResponses will have the error instance. User-thrown
// Responses will not have an error. The one exception here is internal 404s
// which we handle the same as user-thrown 404s
let isCatch = isRouteErrorResponse(error) && (!error.error || error.status === 404);
if (isCatch && route.module.CatchBoundary || !isCatch && route.module.ErrorBoundary || !route.parentId) {
return route.id;
}
return findParentBoundary(routes, route.parentId, error);
}
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, handlerUrl, loadContext, handleError) {
let {
result,
headers,
status
} = request.method !== "GET" ? await singleFetchAction(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetchLoaders(serverMode, staticHandler, request, handlerUrl, loadContext, handleError);
// Re-generate a remix-friendly context.errors structure. The Router only
// handles generic errors and does not distinguish error versus catch. We
// may have a thrown response tagged to a route that only exports an
// ErrorBoundary or vice versa. So we adjust here and ensure that
// data-loading errors are properly associated with routes that have the right
// type of boundaries.
function differentiateCatchVersusErrorBoundaries(build, context) {
if (!context.errors) {
return;
}
let errors = {};
for (let routeId of Object.keys(context.errors)) {
let error = context.errors[routeId];
let handlingRouteId = findParentBoundary(build.routes, routeId, error);
errors[handlingRouteId] = error;
}
context.errors = errors;
// Mark all successful responses with a header so we can identify in-flight
// network errors that are missing this header
let resultHeaders = new Headers(headers);
resultHeaders.set("X-Remix-Response", "yes");
resultHeaders.set("Content-Type", "text/x-turbo");
// Note: Deferred data is already just Promises, so we don't have to mess
// `activeDeferreds` or anything :)
return new Response(encodeViaTurboStream(result, request.signal, build.entry.module.streamTimeout, serverMode), {
status: status || 200,
headers: resultHeaders
});
}
async function handleDocumentRequestRR(serverMode, build, staticHandler, request) {
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) {
let context;
let responseStubs = getResponseStubs();
try {
context = await staticHandler.query(request);
context = await staticHandler.query(request, {
requestContext: loadContext,
unstable_dataStrategy: build.future.unstable_singleFetch ? getSingleFetchDataStrategy(responseStubs) : undefined
});
} catch (error) {
if (!request.signal.aborted && serverMode !== ServerMode.Test) {
console.error(error);
}
handleError(error);
return new Response(null, {

@@ -154,113 +260,120 @@ status: 500

}
if (context instanceof Response) {
if (isResponse(context)) {
return context;
}
let statusCode;
let headers;
if (build.future.unstable_singleFetch) {
let merged = mergeResponseStubs(context, responseStubs);
statusCode = merged.statusCode;
headers = merged.headers;
if (isRedirectStatusCode(statusCode) && headers.has("Location")) {
return new Response(null, {
status: statusCode,
headers
});
}
} else {
statusCode = context.statusCode;
headers = getDocumentHeaders(build, context);
}
// Restructure context.errors to the right Catch/Error Boundary
differentiateCatchVersusErrorBoundaries(build, context);
let appState = {
trackBoundaries: true,
trackCatchBoundaries: true,
catchBoundaryRouteId: null,
renderBoundaryRouteId: null,
loaderBoundaryRouteId: null
};
// Copy staticContext.errors to catch/error boundaries in appState
// Note: Only assign the boundary id if this module has a boundary. This
// should be true in almost all cases, except for cases in which no
// boundaries exist and the router "assigns" the catch/error to the root
// route for lack of a better place to put it. If the root doesn't have a
// catch/error boundary, then we leave the boundaryId null to bubble to the
// default boundary at render time
for (let match of context.matches) {
var _context$errors, _build$routes$id, _build$routes$id2;
let route = match.route;
let id = route.id;
let error = (_context$errors = context.errors) === null || _context$errors === void 0 ? void 0 : _context$errors[id];
let hasCatchBoundary = ((_build$routes$id = build.routes[id]) === null || _build$routes$id === void 0 ? void 0 : _build$routes$id.module.CatchBoundary) != null;
let hasErrorBoundary = ((_build$routes$id2 = build.routes[id]) === null || _build$routes$id2 === void 0 ? void 0 : _build$routes$id2.module.ErrorBoundary) != null;
if (!error) {
continue;
} else if (isRouteErrorResponse(error)) {
// Internal Router errors will throw an ErrorResponse with the actual
// error instance, while user-thrown ErrorResponses will not have the
// instance. We also exclude 404s so we can handle them as CatchBoundary
// errors so the user has a singular location for 404 UI
if (error.internal && error.error && error.status !== 404) {
if (hasErrorBoundary) {
appState.loaderBoundaryRouteId = id;
}
appState.trackBoundaries = false;
appState.error = await serializeError(error.error);
if (error.status === 405 && error.error.message.includes("Invalid request method")) {
// Note: Emptying this out ensures our response matches the Remix-flow
// exactly, but functionally both end up using the root error boundary
// if it exists. Might be able to clean this up eventually.
context.matches = [];
}
break;
// Sanitize errors outside of development environments
if (context.errors) {
Object.values(context.errors).forEach(err => {
// @ts-expect-error `err.error` is "private" from users but intended for internal use
if ((!isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) {
handleError(err);
}
});
context.errors = sanitizeErrors(context.errors, serverMode);
}
// ErrorResponse's without an error were thrown by the user action/loader
if (hasCatchBoundary) {
appState.catchBoundaryRouteId = id;
}
appState.trackCatchBoundaries = false;
appState.catch = {
// Order is important for response matching assertions!
data: error.error && error.status === 404 ? error.error.message : error.data,
status: error.status,
statusText: error.statusText
};
break;
} else {
if (hasErrorBoundary) {
appState.loaderBoundaryRouteId = id;
}
appState.trackBoundaries = false;
appState.error = await serializeError(error);
break;
}
}
let renderableMatches = getRenderableMatches(context.matches, appState);
if (!renderableMatches) {
var _root$module;
renderableMatches = [];
let root = staticHandler.dataRoutes[0];
if (root !== null && root !== void 0 && (_root$module = root.module) !== null && _root$module !== void 0 && _root$module.CatchBoundary) {
appState.catchBoundaryRouteId = "root";
renderableMatches.push({
params: {},
pathname: "",
route: staticHandler.dataRoutes[0]
});
}
}
let headers = getDocumentHeadersRR(build, context, renderableMatches);
let serverHandoff = {
actionData: context.actionData || undefined,
appState,
matches: createEntryMatches(renderableMatches, build.assets.routes),
routeData: context.loaderData || {},
future: build.future
// Server UI state to send to the client.
// - When single fetch is enabled, this is streamed down via `serverHandoffStream`
// - Otherwise it's stringified into `serverHandoffString`
let state = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: serializeErrors(context.errors, serverMode)
};
let entryContext = {
...serverHandoff,
manifest: build.assets,
routeModules: createEntryRouteModules(build.routes),
serverHandoffString: createServerHandoffString(serverHandoff)
staticHandlerContext: context,
criticalCss,
serverHandoffString: createServerHandoffString({
url: context.location.pathname,
basename: build.basename,
criticalCss,
future: build.future,
isSpaMode: build.isSpaMode,
...(!build.future.unstable_singleFetch ? {
state
} : null)
}),
...(build.future.unstable_singleFetch ? {
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
renderMeta: {}
} : null),
future: build.future,
isSpaMode: build.isSpaMode,
serializeError: err => serializeError(err, serverMode)
};
let handleDocumentRequestParameters = [request, context.statusCode, headers, entryContext];
let handleDocumentRequestFunction = build.entry.module.default;
try {
return await handleDocumentRequestFunction(...handleDocumentRequestParameters);
return await handleDocumentRequestFunction(request, statusCode, headers, entryContext, loadContext);
} catch (error) {
handleDocumentRequestParameters[1] = 500;
appState.trackBoundaries = false;
appState.error = await serializeError(error);
entryContext.serverHandoffString = createServerHandoffString(serverHandoff);
handleError(error);
let errorForSecondRender = error;
// If they threw a response, unwrap it into an ErrorResponse like we would
// have for a loader/action
if (isResponse(error)) {
try {
let data = await unwrapResponse(error);
errorForSecondRender = new UNSAFE_ErrorResponseImpl(error.status, error.statusText, data);
} catch (e) {
// If we can't unwrap the response - just leave it as-is
}
}
// Get a new StaticHandlerContext that contains the error at the right boundary
context = getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender);
// Sanitize errors outside of development environments
if (context.errors) {
context.errors = sanitizeErrors(context.errors, serverMode);
}
// Get a new entryContext for the second render pass
// Server UI state to send to the client.
// - When single fetch is enabled, this is streamed down via `serverHandoffStream`
// - Otherwise it's stringified into `serverHandoffString`
let state = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: serializeErrors(context.errors, serverMode)
};
entryContext = {
...entryContext,
staticHandlerContext: context,
serverHandoffString: createServerHandoffString({
url: context.location.pathname,
basename: build.basename,
future: build.future,
isSpaMode: build.isSpaMode,
...(!build.future.unstable_singleFetch ? {
state
} : null)
}),
...(build.future.unstable_singleFetch ? {
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
renderMeta: {}
} : null)
};
try {
return await handleDocumentRequestFunction(...handleDocumentRequestParameters);
return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext);
} catch (error) {
handleError(error);
return returnLastResortErrorResponse(error, serverMode);

@@ -270,13 +383,46 @@ }

}
async function handleResourceRequestRR(serverMode, staticHandler, routeId, request) {
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
try {
let responseStubs = build.future.unstable_singleFetch ? getResponseStubs() : {};
// Note we keep the routeId here to align with the Remix handling of
// resource routes which doesn't take ?index into account and just takes
// the leaf match
let response = await staticHandler.queryRoute(request, routeId);
// callRouteLoader/callRouteAction always return responses
invariant(response instanceof Response, "Expected a Response to be returned from queryRoute");
let response = await staticHandler.queryRoute(request, {
routeId,
requestContext: loadContext,
...(build.future.unstable_singleFetch ? {
unstable_dataStrategy: getSingleFetchResourceRouteDataStrategy({
responseStubs
})
} : null)
});
if (typeof response === "object") {
invariant(!(UNSAFE_DEFERRED_SYMBOL in response), `You cannot return a \`defer()\` response from a Resource Route. Did you ` + `forget to export a default UI component from the "${routeId}" route?`);
}
if (build.future.unstable_singleFetch) {
let stub = responseStubs[routeId];
if (isResponse(response)) {
// If a response was returned, we use it's status and we merge our
// response stub headers onto it
let ops = stub[ResponseStubOperationsSymbol];
for (let [op, ...args] of ops) {
// @ts-expect-error
response.headers[op](...args);
}
} else {
console.warn(resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId));
// Otherwise we create a json Response using the stub
response = json(response, {
status: stub.status,
headers: stub.headers
});
}
}
// callRouteLoader/callRouteAction always return responses (w/o single fetch).
// With single fetch, users should always be Responses from resource routes
invariant(isResponse(response), "Expected a Response to be returned from queryRoute");
return response;
} catch (error) {
if (error instanceof Response) {
if (isResponse(error)) {
// Note: Not functionally required but ensures that our response headers

@@ -287,10 +433,18 @@ // match identically to what Remix returns

}
if (isRouteErrorResponse(error)) {
if (error) {
handleError(error);
}
return errorResponseToJson(error, serverMode);
}
handleError(error);
return returnLastResortErrorResponse(error, serverMode);
}
}
const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
new Set(["GET", "HEAD", ...validActionMethods]);
async function errorBoundaryError(error, status) {
return json(await serializeError(error), {
status,
function errorResponseToJson(errorResponse, serverMode) {
return json$1(serializeError(
// @ts-expect-error This is "private" from users but intended for internal use
errorResponse.error || new Error("Unexpected Server Error"), serverMode), {
status: errorResponse.status,
statusText: errorResponse.statusText,
headers: {

@@ -301,27 +455,3 @@ "X-Remix-Error": "yes"

}
// This prevents `<Outlet/>` from rendering anything below where the error threw
// TODO: maybe do this in <RemixErrorBoundary + context>
function getRenderableMatches(matches, appState) {
if (!matches) {
return null;
}
// no error, no worries
if (!appState.catch && !appState.error) {
return matches;
}
let lastRenderableIndex = -1;
matches.forEach((match, index) => {
let id = match.route.id;
if (appState.renderBoundaryRouteId === id || appState.loaderBoundaryRouteId === id || appState.catchBoundaryRouteId === id) {
lastRenderableIndex = index;
}
});
return matches.slice(0, lastRenderableIndex + 1);
}
function returnLastResortErrorResponse(error, serverMode) {
if (serverMode !== ServerMode.Test) {
console.error(error);
}
let message = "Unexpected Server Error";

@@ -340,120 +470,26 @@ if (serverMode !== ServerMode.Production) {

}
function unwrapResponse(response) {
let contentType = response.headers.get("Content-Type");
// Check between word boundaries instead of startsWith() due to the last
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
return contentType && /\bapplication\/json\b/.test(contentType) ? response.body == null ? null : response.json() : response.text();
}
function createRemixRedirectResponse(response, basename) {
// We don't have any way to prevent a fetch request from following
// redirects. So we use the `X-Remix-Redirect` header to indicate the
// next URL, and then "follow" the redirect manually on the client.
let headers = new Headers(response.headers);
let redirectUrl = headers.get("Location");
headers.set("X-Remix-Redirect", basename ? stripBasename(redirectUrl, basename) || redirectUrl : redirectUrl);
headers.set("X-Remix-Status", String(response.status));
headers.delete("Location");
if (response.headers.get("Set-Cookie") !== null) {
headers.set("X-Remix-Revalidate", "yes");
}
return new Response(null, {
status: 204,
headers
});
}
// TODO: Remove before we "finalize" router migration
// async function assert(
// a: Response,
// b: Response,
// accessor: (r: Response) => object | Promise<object>,
// message: string
// ) {
// let aStr = JSON.stringify(await accessor(a));
// let bStr = JSON.stringify(await accessor(b));
// if (aStr !== bStr) {
// console.error(message);
// message += "\nResponse 1:\n" + aStr + "\nResponse 2:\n" + bStr;
// throw new Error(message);
// }
// }
// async function assertResponsesMatch(_a: Response, _b: Response) {
// let a = _a.clone();
// let b = _b.clone();
// assert(
// a,
// b,
// (r) => Object.fromEntries(r.headers.entries()),
// "Headers did not match!"
// );
// if (a.headers.get("Content-Type")?.startsWith("application/json")) {
// if (a.headers.get("X-Remix-Error")) {
// assert(
// a,
// b,
// async (r) => {
// let { stack, ...json } = await r.json();
// return {
// ...json,
// stack: stack ? "yes" : "no",
// };
// },
// "JSON error response body did not match!\n Response 1:\n" +
// (await a.clone().text()) +
// "\nResponse 2:\n" +
// (await b.clone().text())
// );
// } else {
// assert(
// a,
// b,
// (r) => r.json(),
// "JSON response body did not match!\nResponse 1:\n" +
// (await a.clone().text()) +
// "\nResponse 2:\n" +
// (await b.clone().text())
// );
// }
// } else {
// assert(
// a,
// b,
// async (r) => {
// let text = await r.text();
// // Strip stack trace from default error boundary HTML
// if (
// text.includes(">Application Error</h1>") &&
// text.includes("💿 Hey developer👋")
// ) {
// return stripStackTraceFromDefaultErrorBoundary(text);
// }
// if (text.includes("<script>window.__remixContext")) {
// return stripStackTraceFromRemixContext(text);
// }
// return text;
// },
// "Non-JSON response body did not match!\nResponse 1:\n" +
// (await a.clone().text()) +
// "\nResponse 2:\n" +
// (await b.clone().text())
// );
// }
// }
// function stripStackTraceFromDefaultErrorBoundary(text: string) {
// let openPreStart = text.indexOf("<pre ");
// let openPreEnd = text.indexOf('">', openPreStart + 1);
// let closePre = text.indexOf("</pre>", openPreEnd + 1);
// let error = text.substring(openPreEnd + 2, closePre);
// let stackStart = error.indexOf("\n");
// let errorWithoutStack = error.substring(0, stackStart);
// return (
// text.replace(error, "ERROR REMOVED") + "\n\nError:\n" + errorWithoutStack
// );
// }
// function stripStackTraceFromRemixContext(text: string) {
// let scriptStart = text.indexOf("<script>window.__remixContext");
// let scriptEnd = text.indexOf("</script>", scriptStart + 1);
// let scriptContents = text.substring(scriptStart, scriptEnd + 9);
// let remixContext = JSON.parse(
// scriptContents
// .replace("<script>window.__remixContext = ", "")
// .replace(";</script>", "")
// );
// // If we didn't have an error - assert the full document
// if (!remixContext?.appState?.error?.stack) {
// return text;
// }
// // Otherwise remove the stack trace and assert the text/JSON as a
// // concatenated string
// remixContext.appState.error.stack = "yes";
// text = text.replace(scriptContents, "CONTEXT REMOVED");
// return text + "\n\nContext:\n" + JSON.stringify(remixContext);
// }
export { createRequestHandler, differentiateCatchVersusErrorBoundaries };
export { createRequestHandler };
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -13,2 +13,3 @@ * Copyright (c) Remix Software Inc.

// TODO: Remove Promises from serialization
function createServerHandoffString(serverHandoff) {

@@ -15,0 +16,0 @@ // Uses faster alternative of jsesc to escape data returned from the loaders.

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -18,2 +18,8 @@ * Copyright (c) Remix Software Inc.

/**
* Session persists data across HTTP requests.
*
* @see https://remix.run/utils/sessions#session-api
*/
function flash(name) {

@@ -28,3 +34,3 @@ return `__flash_${name}__`;

*
* @see https://remix.run/api/remix#createsession
* @see https://remix.run/utils/sessions#createsession
*/

@@ -67,3 +73,3 @@ const createSession = (initialData = {}, id = "") => {

*
* @see https://remix.run/api/remix#issession
* @see https://remix.run/utils/sessions#issession
*/

@@ -83,2 +89,12 @@ const isSession = object => {

/**
* 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.
*/
/**
* Creates a SessionStorage object using a SessionIdStorageStrategy.

@@ -89,3 +105,3 @@ *

*
* @see https://remix.run/api/remix#createsessionstorage
* @see https://remix.run/utils/sessions#createsessionstorage
*/

@@ -112,6 +128,7 @@ const createSessionStorageFactory = createCookie => ({

} = session;
let expires = (options === null || options === void 0 ? void 0 : options.maxAge) != null ? new Date(Date.now() + options.maxAge * 1000) : (options === null || options === void 0 ? void 0 : options.expires) != null ? options.expires : cookie.expires;
if (id) {
await updateData(id, data, cookie.expires);
await updateData(id, data, expires);
} else {
id = await createData(data, cookie.expires);
id = await createData(data, expires);
}

@@ -124,2 +141,3 @@ return cookie.serialize(id, options);

...options,
maxAge: undefined,
expires: new Date(0)

@@ -131,5 +149,5 @@ });

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://remix.run/api/remix#signing-cookies ` + `for more information.`);
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://remix.run/utils/cookies#signing-cookies ` + `for more information.`);
}
export { createSession, createSessionStorageFactory, isSession, warnOnceAboutSigningSessionCookie };
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -23,3 +23,3 @@ * Copyright (c) Remix Software Inc.

*
* @see https://remix.run/api/remix#createcookiesessionstorage
* @see https://remix.run/utils/sessions#createcookiesessionstorage
*/

@@ -45,2 +45,3 @@ const createCookieSessionStorageFactory = createCookie => ({

...options,
maxAge: undefined,
expires: new Date(0)

@@ -47,0 +48,0 @@ });

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -18,3 +18,3 @@ * Copyright (c) Remix Software Inc.

*
* @see https://remix.run/api/remix#creatememorysessionstorage
* @see https://remix.run/utils/sessions#creatememorysessionstorage
*/

@@ -24,3 +24,2 @@ const createMemorySessionStorageFactory = createSessionStorage => ({

} = {}) => {
let uniqueId = 0;
let map = new Map();

@@ -30,3 +29,3 @@ return createSessionStorage({

async createData(data, expires) {
let id = (++uniqueId).toString();
let id = Math.random().toString(36).substring(2, 10);
map.set(id, {

@@ -33,0 +32,0 @@ data,

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

@@ -1,2 +0,2 @@

export declare type UploadHandlerPart = {
export type UploadHandlerPart = {
name: string;

@@ -7,3 +7,3 @@ filename?: string;

};
export declare type UploadHandler = (part: UploadHandlerPart) => Promise<File | string | null | undefined>;
export type UploadHandler = (part: UploadHandlerPart) => Promise<File | string | null | undefined>;
export declare function composeUploadHandlers(...handlers: UploadHandler[]): UploadHandler;

@@ -14,4 +14,4 @@ /**

* TODO: Update this comment
* @see https://remix.run/api/remix#parsemultipartformdata-node
* @see https://remix.run/utils/parse-multipart-form-data
*/
export declare function parseMultipartFormData(request: Request, uploadHandler: UploadHandler): Promise<FormData>;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -17,2 +17,3 @@ * Copyright (c) Remix Software Inc.

// @ts-ignore
function composeUploadHandlers(...handlers) {

@@ -34,3 +35,3 @@ return async part => {

* TODO: Update this comment
* @see https://remix.run/api/remix#parsemultipartformdata-node
* @see https://remix.run/utils/parse-multipart-form-data
*/

@@ -37,0 +38,0 @@ async function parseMultipartFormData(request, uploadHandler) {

@@ -0,6 +1,3 @@

import type { StaticHandlerContext } from "@remix-run/router";
import type { ServerBuild } from "./build";
import type { ServerRoute } from "./routes";
import type { RouteMatch } from "./routeMatching";
import type { StaticHandlerContext } from "./router";
export declare function getDocumentHeaders(build: ServerBuild, matches: RouteMatch<ServerRoute>[], routeLoaderResponses: Record<string, Response>, actionResponse?: Response): Headers;
export declare function getDocumentHeadersRR(build: ServerBuild, context: StaticHandlerContext, matches: RouteMatch<ServerRoute>[]): Headers;
export declare function getDocumentHeaders(build: ServerBuild, context: StaticHandlerContext): Headers;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -17,19 +17,63 @@ * Copyright (c) Remix Software Inc.

function getDocumentHeadersRR(build, context, matches) {
return matches.reduce((parentHeaders, match, index) => {
var _context$loaderHeader, _context$actionHeader;
function getDocumentHeaders(build, context) {
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) {
// Look for any errorHeaders from the boundary route down, which can be
// identified by the presence of headers but no data
let {
actionHeaders,
actionData,
loaderHeaders,
loaderData
} = context;
context.matches.slice(boundaryIdx).some(match => {
let id = match.route.id;
if (actionHeaders[id] && (!actionData || actionData[id] === undefined)) {
errorHeaders = actionHeaders[id];
} else if (loaderHeaders[id] && loaderData[id] === undefined) {
errorHeaders = loaderHeaders[id];
}
return errorHeaders != null;
});
}
return matches.reduce((parentHeaders, match, idx) => {
let {
id
} = match.route;
let routeModule = build.routes[id].module;
let loaderHeaders = ((_context$loaderHeader = context.loaderHeaders) === null || _context$loaderHeader === void 0 ? void 0 : _context$loaderHeader[id]) || new Headers();
let actionHeaders = ((_context$actionHeader = context.actionHeaders) === null || _context$actionHeader === void 0 ? void 0 : _context$actionHeader[id]) || new Headers();
let loaderHeaders = context.loaderHeaders[id] || new Headers();
let actionHeaders = context.actionHeaders[id] || new Headers();
// Only expose errorHeaders to the leaf headers() function to
// avoid duplication via parentHeaders
let includeErrorHeaders = errorHeaders != undefined && idx === matches.length - 1;
// Only prepend cookies from errorHeaders at the leaf renderable route
// when it's not the same as loaderHeaders/actionHeaders to avoid
// duplicate cookies
let includeErrorCookies = includeErrorHeaders && errorHeaders !== loaderHeaders && errorHeaders !== actionHeaders;
// Use the parent headers for any route without a `headers` export
if (routeModule.headers == null) {
let headers = new Headers(parentHeaders);
if (includeErrorCookies) {
prependCookies(errorHeaders, headers);
}
prependCookies(actionHeaders, headers);
prependCookies(loaderHeaders, headers);
return headers;
}
let headers = new Headers(routeModule.headers ? typeof routeModule.headers === "function" ? routeModule.headers({
loaderHeaders,
parentHeaders,
actionHeaders
actionHeaders,
errorHeaders: includeErrorHeaders ? errorHeaders : undefined
}) : routeModule.headers : undefined);
// Automatically preserve Set-Cookie headers that were set either by the
// loader or by a parent route.
// Automatically preserve Set-Cookie headers from bubbled responses,
// loaders, errors, and parent routes
if (includeErrorCookies) {
prependCookies(errorHeaders, headers);
}
prependCookies(actionHeaders, headers);

@@ -51,2 +95,2 @@ prependCookies(loaderHeaders, headers);

exports.getDocumentHeadersRR = getDocumentHeadersRR;
exports.getDocumentHeaders = getDocumentHeaders;
export { createCookieFactory, isCookie } from "./cookies";
export { composeUploadHandlers as unstable_composeUploadHandlers, parseMultipartFormData as unstable_parseMultipartFormData, } from "./formData";
export { json, redirect } from "./responses";
export { defer, json, redirect, redirectDocument } from "./responses";
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, defineLoader as unstable_defineLoader, defineAction as unstable_defineAction, } from "./single-fetch";
export type { Loader as unstable_Loader, Action as unstable_Action, Serialize as unstable_Serialize, SingleFetchResult as UNSAFE_SingleFetchResult, SingleFetchResults as UNSAFE_SingleFetchResults, } from "./single-fetch";
export { createRequestHandler } from "./server";
export { createSession, isSession, createSessionStorageFactory, } from "./sessions";
export { createSession, createSessionStorageFactory, isSession, } from "./sessions";
export { createCookieSessionStorageFactory } from "./sessions/cookieStorage";

@@ -10,3 +12,4 @@ export { createMemorySessionStorageFactory } from "./sessions/memoryStorage";

export { MaxPartSizeExceededError } from "./upload/errors";
export { broadcastDevReady, logDevReady, setDevServerHooks as unstable_setDevServerHooks, } from "./dev";
export type { CreateCookieFunction, CreateCookieSessionStorageFunction, CreateMemorySessionStorageFunction, CreateRequestHandlerFunction, CreateSessionFunction, CreateSessionStorageFunction, IsCookieFunction, IsSessionFunction, JsonFunction, RedirectFunction, } from "./interface";
export type { ActionArgs, ActionFunction, AppData, AppLoadContext, Cookie, CookieOptions, CookieParseOptions, CookieSerializeOptions, CookieSignatureOptions, DataFunctionArgs, EntryContext, ErrorBoundaryComponent, HandleDataRequestFunction, HandleDocumentRequestFunction, HeadersFunction, HtmlLinkDescriptor, HtmlMetaDescriptor, LinkDescriptor, LinksFunction, LoaderArgs, LoaderFunction, MemoryUploadHandlerFilterArgs, MemoryUploadHandlerOptions, MetaDescriptor, MetaFunction, PageLinkDescriptor, RequestHandler, RouteComponent, RouteHandle, SerializeFrom, ServerBuild, ServerEntryModule, Session, SessionData, SessionIdStorageStrategy, SessionStorage, SignFunction, TypedResponse, UnsignFunction, UploadHandlerPart, UploadHandler, } from "./reexport";
export type { ActionFunction, ActionFunctionArgs, AppLoadContext, Cookie, CookieOptions, CookieParseOptions, CookieSerializeOptions, CookieSignatureOptions, DataFunctionArgs, EntryContext, ErrorResponse, FlashSessionData, HandleDataRequestFunction, HandleDocumentRequestFunction, HeadersArgs, HeadersFunction, HtmlLinkDescriptor, LinkDescriptor, LinksFunction, LoaderFunction, LoaderFunctionArgs, MemoryUploadHandlerFilterArgs, MemoryUploadHandlerOptions, HandleErrorFunction, PageLinkDescriptor, RequestHandler, SerializeFrom, ServerBuild, ServerEntryModule, ServerRuntimeMetaArgs, ServerRuntimeMetaDescriptor, ServerRuntimeMetaFunction, Session, SessionData, SessionIdStorageStrategy, SessionStorage, SignFunction, TypedDeferredData, TypedResponse, UnsignFunction, UploadHandler, UploadHandlerPart, } from "./reexport";
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -18,2 +18,3 @@ * Copyright (c) Remix Software Inc.

var responses = require('./responses.js');
var singleFetch = require('./single-fetch.js');
var server = require('./server.js');

@@ -25,2 +26,3 @@ var sessions = require('./sessions.js');

var errors = require('./upload/errors.js');
var dev = require('./dev.js');

@@ -33,4 +35,9 @@

exports.unstable_parseMultipartFormData = formData.parseMultipartFormData;
exports.defer = responses.defer;
exports.json = responses.json;
exports.redirect = responses.redirect;
exports.redirectDocument = responses.redirectDocument;
exports.UNSAFE_SingleFetchRedirectSymbol = singleFetch.SingleFetchRedirectSymbol;
exports.unstable_defineAction = singleFetch.defineAction;
exports.unstable_defineLoader = singleFetch.defineLoader;
exports.createRequestHandler = server.createRequestHandler;

@@ -44,1 +51,4 @@ exports.createSession = sessions.createSession;

exports.MaxPartSizeExceededError = errors.MaxPartSizeExceededError;
exports.broadcastDevReady = dev.broadcastDevReady;
exports.logDevReady = dev.logDevReady;
exports.unstable_setDevServerHooks = dev.setDevServerHooks;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

@@ -1,3 +0,3 @@

declare type Primitive = null | undefined | string | number | boolean | symbol | bigint;
declare type LiteralUnion<LiteralType, BaseType extends Primitive> = LiteralType | (BaseType & Record<never, never>);
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
type LiteralUnion<LiteralType, BaseType extends Primitive> = LiteralType | (BaseType & Record<never, never>);
interface HtmlLinkProps {

@@ -94,16 +94,7 @@ /**

*/
export declare type HtmlLinkDescriptor = ((HtmlLinkProps & Pick<Required<HtmlLinkProps>, "href">) | (HtmlLinkPreloadImage & Pick<Required<HtmlLinkPreloadImage>, "imageSizes">) | (HtmlLinkPreloadImage & Pick<Required<HtmlLinkPreloadImage>, "href"> & {
export type HtmlLinkDescriptor = (HtmlLinkProps & Pick<Required<HtmlLinkProps>, "href">) | (HtmlLinkPreloadImage & Pick<Required<HtmlLinkPreloadImage>, "imageSizes">) | (HtmlLinkPreloadImage & Pick<Required<HtmlLinkPreloadImage>, "href"> & {
imageSizes?: never;
})) & {
});
export interface PageLinkDescriptor extends Omit<HtmlLinkDescriptor, "href" | "rel" | "type" | "sizes" | "imageSrcSet" | "imageSizes" | "as" | "color" | "title"> {
/**
* @deprecated Use `imageSrcSet` instead.
*/
imagesrcset?: string;
/**
* @deprecated Use `imageSizes` instead.
*/
imagesizes?: string;
};
export interface PageLinkDescriptor extends Omit<HtmlLinkDescriptor, "href" | "rel" | "type" | "sizes" | "imageSrcSet" | "imageSizes" | "imagesrcset" | "imagesizes" | "as" | "color" | "title"> {
/**
* The absolute path of the page to prefetch.

@@ -113,3 +104,3 @@ */

}
export declare type LinkDescriptor = HtmlLinkDescriptor | PageLinkDescriptor;
export type LinkDescriptor = HtmlLinkDescriptor | PageLinkDescriptor;
export {};
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -18,12 +18,13 @@ * Copyright (c) Remix Software Inc.

*/
exports.ServerMode = void 0;
(function (ServerMode) {
let ServerMode = /*#__PURE__*/function (ServerMode) {
ServerMode["Development"] = "development";
ServerMode["Production"] = "production";
ServerMode["Test"] = "test";
})(exports.ServerMode || (exports.ServerMode = {}));
return ServerMode;
}({});
function isServerMode(value) {
return value === exports.ServerMode.Development || value === exports.ServerMode.Production || value === exports.ServerMode.Test;
return value === ServerMode.Development || value === ServerMode.Production || value === ServerMode.Test;
}
exports.ServerMode = ServerMode;
exports.isServerMode = isServerMode;

@@ -1,2 +0,3 @@

export type { HandleDataRequestFunction, HandleDocumentRequestFunction, ServerBuild, ServerEntryModule, } from "./build";
export type { ErrorResponse } from "@remix-run/router";
export type { HandleDataRequestFunction, HandleDocumentRequestFunction, HandleErrorFunction, ServerBuild, ServerEntryModule, } from "./build";
export type { UploadHandlerPart, UploadHandler } from "./formData";

@@ -6,9 +7,9 @@ export type { MemoryUploadHandlerOptions, MemoryUploadHandlerFilterArgs, } from "./upload/memoryUploadHandler";

export type { SignFunction, UnsignFunction } from "./crypto";
export type { AppLoadContext, AppData } from "./data";
export type { AppLoadContext } from "./data";
export type { EntryContext } from "./entry";
export type { HtmlLinkDescriptor, LinkDescriptor, PageLinkDescriptor, } from "./links";
export type { TypedResponse } from "./responses";
export type { ActionArgs, ActionFunction, DataFunctionArgs, ErrorBoundaryComponent, HeadersFunction, HtmlMetaDescriptor, LinksFunction, LoaderArgs, LoaderFunction, MetaDescriptor, MetaFunction, RouteComponent, RouteHandle, } from "./routeModules";
export type { TypedDeferredData, TypedResponse } from "./responses";
export type { ActionFunction, ActionFunctionArgs, DataFunctionArgs, HeadersArgs, HeadersFunction, LinksFunction, LoaderFunction, LoaderFunctionArgs, ServerRuntimeMetaArgs, ServerRuntimeMetaDescriptor, ServerRuntimeMetaFunction, } from "./routeModules";
export type { SerializeFrom } from "./serialize";
export type { RequestHandler } from "./server";
export type { Session, SessionData, SessionIdStorageStrategy, SessionStorage, } from "./sessions";
export type { Session, SessionData, SessionIdStorageStrategy, SessionStorage, FlashSessionData, } from "./sessions";

@@ -1,3 +0,11 @@

export declare type JsonFunction = <Data extends unknown>(data: Data, init?: number | ResponseInit) => TypedResponse<Data>;
export declare type TypedResponse<T extends unknown = unknown> = Response & {
import { type UNSAFE_DeferredData as DeferredData } from "@remix-run/router";
import type { ServerMode } from "./mode";
declare const typedDeferredDataBrand: unique symbol;
export type TypedDeferredData<Data extends Record<string, unknown>> = Pick<DeferredData, "init"> & {
data: Data;
readonly [typedDeferredDataBrand]: "TypedDeferredData";
};
export type DeferFunction = <Data extends Record<string, unknown>>(data: Data, init?: number | ResponseInit) => TypedDeferredData<Data>;
export type JsonFunction = <Data>(data: Data, init?: number | ResponseInit) => TypedResponse<Data>;
export type TypedResponse<T = unknown> = Omit<Response, "json"> & {
json(): Promise<T>;

@@ -9,15 +17,32 @@ };

*
* @see https://remix.run/api/remix#json
* @see https://remix.run/utils/json
*/
export declare const json: JsonFunction;
export declare type RedirectFunction = (url: string, init?: number | ResponseInit) => TypedResponse<never>;
/**
* This is a shortcut for creating Remix deferred responses
*
* @see https://remix.run/utils/defer
*/
export declare const defer: DeferFunction;
export type RedirectFunction = (url: string, init?: number | ResponseInit) => TypedResponse<never>;
/**
* A redirect response. Sets the status code and the `Location` header.
* Defaults to "302 Found".
*
* @see https://remix.run/api/remix#redirect
* @see https://remix.run/utils/redirect
*/
export declare const redirect: RedirectFunction;
/**
* A redirect response that will force a document reload to the new location.
* Sets the status code and the `Location` header.
* Defaults to "302 Found".
*
* @see https://remix.run/utils/redirect
*/
export declare const redirectDocument: RedirectFunction;
export declare function isDeferredData(value: any): value is DeferredData;
export declare function isResponse(value: any): value is Response;
export declare function isRedirectStatusCode(statusCode: number): boolean;
export declare function isRedirectResponse(response: Response): boolean;
export declare function isCatchResponse(response: Response): boolean;
export declare function createDeferredReadableStream(deferredData: DeferredData, signal: AbortSignal, serverMode: ServerMode): any;
export {};
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -15,2 +15,5 @@ * Copyright (c) Remix Software Inc.

var router = require('@remix-run/router');
var errors = require('./errors.js');
// must be a type since this is a subtype of response

@@ -23,39 +26,40 @@ // interfaces must conform to the types they extend

*
* @see https://remix.run/api/remix#json
* @see https://remix.run/utils/json
*/
const json = (data, init = {}) => {
let responseInit = typeof init === "number" ? {
status: init
} : init;
let headers = new Headers(responseInit.headers);
if (!headers.has("Content-Type")) {
headers.set("Content-Type", "application/json; charset=utf-8");
}
return new Response(JSON.stringify(data), {
...responseInit,
headers
});
return router.json(data, init);
};
/**
* This is a shortcut for creating Remix deferred responses
*
* @see https://remix.run/utils/defer
*/
const defer = (data, init = {}) => {
return router.defer(data, init);
};
/**
* A redirect response. Sets the status code and the `Location` header.
* Defaults to "302 Found".
*
* @see https://remix.run/api/remix#redirect
* @see https://remix.run/utils/redirect
*/
const redirect = (url, init = 302) => {
let responseInit = init;
if (typeof responseInit === "number") {
responseInit = {
status: responseInit
};
} else if (typeof responseInit.status === "undefined") {
responseInit.status = 302;
}
let headers = new Headers(responseInit.headers);
headers.set("Location", url);
return new Response(null, {
...responseInit,
headers
});
return router.redirect(url, init);
};
/**
* A redirect response that will force a document reload to the new location.
* Sets the status code and the `Location` header.
* Defaults to "302 Found".
*
* @see https://remix.run/utils/redirect
*/
const redirectDocument = (url, init = 302) => {
return router.redirectDocument(url, init);
};
function isDeferredData(value) {
let deferred = value;
return deferred && typeof deferred === "object" && typeof deferred.data === "object" && typeof deferred.subscribe === "function" && typeof deferred.cancel === "function" && typeof deferred.resolveData === "function";
}
function isResponse(value) {

@@ -65,9 +69,70 @@ return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";

const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
function isRedirectStatusCode(statusCode) {
return redirectStatusCodes.has(statusCode);
}
function isRedirectResponse(response) {
return redirectStatusCodes.has(response.status);
return isRedirectStatusCode(response.status);
}
function isTrackedPromise(value) {
return value != null && typeof value.then === "function" && value._tracked === true;
}
// TODO: Figure out why ReadableStream types are borked sooooooo badly
// in this file. Probably related to our TS configurations and configs
// bleeding into each other.
const DEFERRED_VALUE_PLACEHOLDER_PREFIX = "__deferred_promise:";
function createDeferredReadableStream(deferredData, signal, serverMode) {
let encoder = new TextEncoder();
let stream = new ReadableStream({
async start(controller) {
let criticalData = {};
let preresolvedKeys = [];
for (let [key, value] of Object.entries(deferredData.data)) {
if (isTrackedPromise(value)) {
criticalData[key] = `${DEFERRED_VALUE_PLACEHOLDER_PREFIX}${key}`;
if (typeof value._data !== "undefined" || typeof value._error !== "undefined") {
preresolvedKeys.push(key);
}
} else {
criticalData[key] = value;
}
}
// Send the critical data
controller.enqueue(encoder.encode(JSON.stringify(criticalData) + "\n\n"));
for (let preresolvedKey of preresolvedKeys) {
enqueueTrackedPromise(controller, encoder, preresolvedKey, deferredData.data[preresolvedKey], serverMode);
}
let unsubscribe = deferredData.subscribe((aborted, settledKey) => {
if (settledKey) {
enqueueTrackedPromise(controller, encoder, settledKey, deferredData.data[settledKey], serverMode);
}
});
await deferredData.resolveData(signal);
unsubscribe();
controller.close();
}
});
return stream;
}
function enqueueTrackedPromise(controller, encoder, settledKey, promise, serverMode) {
if ("_error" in promise) {
controller.enqueue(encoder.encode("error:" + JSON.stringify({
[settledKey]: promise._error instanceof Error ? errors.serializeError(promise._error, serverMode) : promise._error
}) + "\n\n"));
} else {
controller.enqueue(encoder.encode("data:" + JSON.stringify({
[settledKey]: promise._data ?? null
}) + "\n\n"));
}
}
exports.createDeferredReadableStream = createDeferredReadableStream;
exports.defer = defer;
exports.isDeferredData = isDeferredData;
exports.isRedirectResponse = isRedirectResponse;
exports.isRedirectStatusCode = isRedirectStatusCode;
exports.isResponse = isResponse;
exports.json = json;
exports.redirect = redirect;
exports.redirectDocument = redirectDocument;

@@ -1,2 +0,2 @@

import type { Params } from "react-router";
import type { Params } from "@remix-run/router";
import type { ServerRoute } from "./routes";

@@ -8,2 +8,2 @@ export interface RouteMatch<Route> {

}
export declare function matchServerRoutes(routes: ServerRoute[], pathname: string): RouteMatch<ServerRoute>[] | null;
export declare function matchServerRoutes(routes: ServerRoute[], pathname: string, basename?: string): RouteMatch<ServerRoute>[] | null;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -15,7 +15,6 @@ * Copyright (c) Remix Software Inc.

var reactRouterDom = require('react-router-dom');
var router = require('@remix-run/router');
// TODO: export/import from react-router-dom
function matchServerRoutes(routes, pathname) {
let matches = reactRouterDom.matchRoutes(routes, pathname);
function matchServerRoutes(routes, pathname, basename) {
let matches = router.matchRoutes(routes, pathname, basename);
if (!matches) return null;

@@ -22,0 +21,0 @@ return matches.map(match => ({

@@ -1,38 +0,70 @@

import type { Location } from "history";
import type { ComponentType } from "react";
import type { Params } from "react-router-dom";
import type { AppLoadContext, AppData } from "./data";
import type { ActionFunction as RRActionFunction, ActionFunctionArgs as RRActionFunctionArgs, AgnosticRouteMatch, LoaderFunction as RRLoaderFunction, LoaderFunctionArgs as RRLoaderFunctionArgs, Location, Params } from "@remix-run/router";
import type { AppData, AppLoadContext } from "./data";
import type { LinkDescriptor } from "./links";
import type { RouteData } from "./routeData";
import type { SerializeFrom } from "./serialize";
import type { ResponseStub } from "./single-fetch";
export interface RouteModules<RouteModule> {
[routeId: string]: RouteModule;
[routeId: string]: RouteModule | undefined;
}
/**
* The arguments passed to ActionFunction and LoaderFunction.
* @deprecated Use `LoaderFunctionArgs`/`ActionFunctionArgs` instead
*/
export interface DataFunctionArgs {
request: Request;
export type DataFunctionArgs = RRActionFunctionArgs<AppLoadContext> & RRLoaderFunctionArgs<AppLoadContext> & {
context: AppLoadContext;
params: Params;
}
export declare type LoaderArgs = DataFunctionArgs;
export declare type ActionArgs = DataFunctionArgs;
};
/**
* A function that handles data mutations for a route.
* A function that handles data mutations for a route on the server
*/
export interface ActionFunction {
(args: DataFunctionArgs): Promise<Response> | Response | Promise<AppData> | AppData;
}
export type ActionFunction = (args: ActionFunctionArgs) => ReturnType<RRActionFunction>;
/**
* A React component that is rendered when the server throws a Response.
* Arguments passed to a route `action` function
*/
export declare type CatchBoundaryComponent = ComponentType<{}>;
export type ActionFunctionArgs = RRActionFunctionArgs<AppLoadContext> & {
context: AppLoadContext;
response?: ResponseStub;
};
/**
* A React component that is rendered when there is an error on a route.
* A function that handles data mutations for a route on the client
* @private Public API is exported from @remix-run/react
*/
export declare type ErrorBoundaryComponent = ComponentType<{
error: Error;
}>;
type ClientActionFunction = (args: ClientActionFunctionArgs) => ReturnType<RRActionFunction>;
/**
* Arguments passed to a route `clientAction` function
* @private Public API is exported from @remix-run/react
*/
export type ClientActionFunctionArgs = RRActionFunctionArgs<undefined> & {
serverAction: <T = AppData>() => Promise<SerializeFrom<T>>;
};
/**
* A function that loads data for a route on the server
*/
export type LoaderFunction = (args: LoaderFunctionArgs) => ReturnType<RRLoaderFunction>;
/**
* Arguments passed to a route `loader` function
*/
export type LoaderFunctionArgs = RRLoaderFunctionArgs<AppLoadContext> & {
context: AppLoadContext;
response?: ResponseStub;
};
/**
* A function that loads data for a route on the client
* @private Public API is exported from @remix-run/react
*/
type ClientLoaderFunction = ((args: ClientLoaderFunctionArgs) => ReturnType<RRLoaderFunction>) & {
hydrate?: boolean;
};
/**
* Arguments passed to a route `clientLoader` function
* @private Public API is exported from @remix-run/react
*/
export type ClientLoaderFunctionArgs = RRLoaderFunctionArgs<undefined> & {
serverLoader: <T = AppData>() => Promise<SerializeFrom<T>>;
};
export type HeadersArgs = {
loaderHeaders: Headers;
parentHeaders: Headers;
actionHeaders: Headers;
errorHeaders: Headers | undefined;
};
/**
* A function that returns HTTP headers to be used for a route. These headers

@@ -42,7 +74,3 @@ * will be merged with (and take precedence over) headers from parent routes.

export interface HeadersFunction {
(args: {
loaderHeaders: Headers;
parentHeaders: Headers;
actionHeaders: Headers;
}): Headers | HeadersInit;
(args: HeadersArgs): Headers | HeadersInit;
}

@@ -57,14 +85,10 @@ /**

/**
* A function that loads data for a route.
*/
export interface LoaderFunction {
(args: DataFunctionArgs): Promise<Response> | Response | Promise<AppData> | AppData;
}
/**
* A function that returns an object of name + content pairs to use for
* `<meta>` tags for a route. These tags will be merged with (and take
* precedence over) tags from parent routes.
* A function that returns an array of data objects to use for rendering
* metadata HTML tags in a route. These tags are not rendered on descendant
* routes in the route hierarchy. In other words, they will only be rendered on
* the route in which they are exported.
*
* @param Loader - Loader for this meta function's route
* @param ParentsLoaders - Mapping from a parent's route filepath to that route's loader
* @param Loader - The type of the current route's loader function
* @param MatchLoaders - Mapping from a parent route's filepath to its loader
* function type
*

@@ -107,10 +131,10 @@ * Note that parent route filepaths are relative to the `app/` directory.

* "routes/sales/customers": CustomersLoader,
* }> = ({ data, parentsData }) => {
* }> = ({ data, matches }) => {
* const { name } = data
* // ^? string
* const { customerCount } = parentsData["routes/sales/customers"]
* const { customerCount } = matches.find((match) => match.id === "routes/sales/customers").data
* // ^? number
* const { salesCount } = parentsData["routes/sales"]
* const { salesCount } = matches.find((match) => match.id === "routes/sales").data
* // ^? number
* const { hello } = parentsData["root"]
* const { hello } = matches.find((match) => match.id === "root").data
* // ^? "world"

@@ -120,40 +144,67 @@ * }

*/
export interface MetaFunction<Loader extends LoaderFunction | unknown = unknown, ParentsLoaders extends Record<string, LoaderFunction> = {}> {
(args: {
data: Loader extends LoaderFunction ? SerializeFrom<Loader> : AppData;
parentsData: {
[k in keyof ParentsLoaders]: SerializeFrom<ParentsLoaders[k]>;
} & RouteData;
params: Params;
location: Location;
}): HtmlMetaDescriptor;
export interface ServerRuntimeMetaFunction<Loader extends LoaderFunction | unknown = unknown, ParentsLoaders extends Record<string, LoaderFunction | unknown> = Record<string, unknown>> {
(args: ServerRuntimeMetaArgs<Loader, ParentsLoaders>): ServerRuntimeMetaDescriptor[];
}
/**
* A name/content pair used to render `<meta>` tags in a meta function for a
* route. The value can be either a string, which will render a single `<meta>`
* tag, or an array of strings that will render multiple tags with the same
* `name` attribute.
*/
export interface HtmlMetaDescriptor {
charset?: "utf-8";
charSet?: "utf-8";
title?: string;
[name: string]: null | string | undefined | Record<string, string> | Array<Record<string, string> | string>;
interface ServerRuntimeMetaMatch<RouteId extends string = string, Loader extends LoaderFunction | unknown = unknown> {
id: RouteId;
pathname: AgnosticRouteMatch["pathname"];
data: Loader extends LoaderFunction ? SerializeFrom<Loader> : unknown;
handle?: RouteHandle;
params: AgnosticRouteMatch["params"];
meta: ServerRuntimeMetaDescriptor[];
error?: unknown;
}
export declare type MetaDescriptor = HtmlMetaDescriptor;
type ServerRuntimeMetaMatches<MatchLoaders extends Record<string, LoaderFunction | unknown> = Record<string, unknown>> = Array<{
[K in keyof MatchLoaders]: ServerRuntimeMetaMatch<Exclude<K, number | symbol>, MatchLoaders[K]>;
}[keyof MatchLoaders]>;
export interface ServerRuntimeMetaArgs<Loader extends LoaderFunction | unknown = unknown, MatchLoaders extends Record<string, LoaderFunction | unknown> = Record<string, unknown>> {
data: (Loader extends LoaderFunction ? SerializeFrom<Loader> : AppData) | undefined;
params: Params;
location: Location;
matches: ServerRuntimeMetaMatches<MatchLoaders>;
error?: unknown;
}
export type ServerRuntimeMetaDescriptor = {
charSet: "utf-8";
} | {
title: string;
} | {
name: string;
content: string;
} | {
property: string;
content: string;
} | {
httpEquiv: string;
content: string;
} | {
"script:ld+json": LdJsonObject;
} | {
tagName: "meta" | "link";
[name: string]: string;
} | {
[name: string]: unknown;
};
type LdJsonObject = {
[Key in string]: LdJsonValue;
} & {
[Key in string]?: LdJsonValue | undefined;
};
type LdJsonArray = LdJsonValue[] | readonly LdJsonValue[];
type LdJsonPrimitive = string | number | boolean | null;
type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray;
/**
* A React component that is rendered for a route.
*/
export declare type RouteComponent = ComponentType<{}>;
/**
* An arbitrary object that is associated with a route.
*/
export declare type RouteHandle = any;
export type RouteHandle = unknown;
export interface EntryRouteModule {
CatchBoundary?: CatchBoundaryComponent;
ErrorBoundary?: ErrorBoundaryComponent;
default: RouteComponent;
clientAction?: ClientActionFunction;
clientLoader?: ClientLoaderFunction;
ErrorBoundary?: any;
HydrateFallback?: any;
Layout?: any;
default: any;
handle?: RouteHandle;
links?: LinksFunction;
meta?: MetaFunction | HtmlMetaDescriptor;
meta?: ServerRuntimeMetaFunction;
}

@@ -167,1 +218,2 @@ export interface ServerRouteModule extends EntryRouteModule {

}
export {};

@@ -1,3 +0,3 @@

import type { AgnosticDataRouteObject } from "./router";
import { type AppLoadContext } from "./data";
import type { AgnosticDataRouteObject } from "@remix-run/router";
import type { FutureConfig } from "./entry";
import type { ServerRouteModule } from "./routeModules";

@@ -7,4 +7,4 @@ export interface RouteManifest<Route> {

}
export declare type ServerRouteManifest = RouteManifest<Omit<ServerRoute, "children">>;
interface Route {
export type ServerRouteManifest = RouteManifest<Omit<ServerRoute, "children">>;
export interface Route {
index?: boolean;

@@ -17,8 +17,12 @@ caseSensitive?: boolean;

export interface EntryRoute extends Route {
hasChildren: boolean;
hasAction: boolean;
hasLoader: boolean;
hasCatchBoundary: boolean;
hasClientAction: boolean;
hasClientLoader: boolean;
hasErrorBoundary: boolean;
imports?: string[];
css?: string[];
module: string;
parentId?: string;
}

@@ -29,4 +33,3 @@ export interface ServerRoute extends Route {

}
export declare function createRoutes(manifest: ServerRouteManifest, parentId?: string): ServerRoute[];
export declare function createStaticHandlerDataRoutes(manifest: ServerRouteManifest, loadContext: AppLoadContext, parentId?: string): AgnosticDataRouteObject[];
export {};
export declare function createRoutes(manifest: ServerRouteManifest, parentId?: string, routesByParentId?: Record<string, Omit<ServerRoute, "children">[]>): ServerRoute[];
export declare function createStaticHandlerDataRoutes(manifest: ServerRouteManifest, future: FutureConfig, parentId?: string, routesByParentId?: Record<string, Omit<ServerRoute, "children">[]>): AgnosticDataRouteObject[];
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -17,7 +17,24 @@ * Copyright (c) Remix Software Inc.

// TODO: RRR - Change import to @remix-run/router
function createRoutes(manifest, parentId) {
return Object.entries(manifest).filter(([, route]) => route.parentId === parentId).map(([id, route]) => ({
// NOTE: make sure to change the Route in remix-react if you change this
// NOTE: make sure to change the EntryRoute in remix-react if you change this
function groupRoutesByParentId(manifest) {
let routes = {};
Object.values(manifest).forEach(route => {
let parentId = route.parentId || "";
if (!routes[parentId]) {
routes[parentId] = [];
}
routes[parentId].push(route);
});
return routes;
}
// Create a map of routes by parentId to use recursively instead of
// repeatedly filtering the manifest.
function createRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId(manifest)) {
return (routesByParentId[parentId] || []).map(route => ({
...route,
children: createRoutes(manifest, id)
children: createRoutes(manifest, route.id, routesByParentId)
}));

@@ -28,24 +45,31 @@ }

// createStaticHandler
function createStaticHandlerDataRoutes(manifest, loadContext, parentId) {
return Object.values(manifest).filter(route => route.parentId === parentId).map(route => {
function createStaticHandlerDataRoutes(manifest, future, parentId = "", routesByParentId = groupRoutesByParentId(manifest)) {
return (routesByParentId[parentId] || []).map(route => {
let commonRoute = {
// Always include root due to default boundaries
hasErrorBoundary: route.id === "root" || route.module.CatchBoundary != null || route.module.ErrorBoundary != null,
hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null,
id: route.id,
path: route.path,
loader: route.module.loader ? args => data.callRouteLoaderRR({
...args,
loadContext,
loader: route.module.loader ?
// Need to use RR's version here to permit the optional context even
// though we know it'll always be provided in remix
(args, dataStrategyCtx) => data.callRouteLoader({
request: args.request,
params: args.params,
loadContext: args.context,
loader: route.module.loader,
routeId: route.id
routeId: route.id,
singleFetch: future.unstable_singleFetch === true,
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response
}) : undefined,
action: route.module.action ? args => data.callRouteActionRR({
...args,
loadContext,
action: route.module.action ? (args, dataStrategyCtx) => data.callRouteAction({
request: args.request,
params: args.params,
loadContext: args.context,
action: route.module.action,
routeId: route.id
routeId: route.id,
singleFetch: future.unstable_singleFetch === true,
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response
}) : undefined,
handle: route.module.handle,
// TODO: RRR - Implement!
shouldRevalidate: () => true
handle: route.module.handle
};

@@ -57,3 +81,3 @@ return route.index ? {

caseSensitive: route.caseSensitive,
children: createStaticHandlerDataRoutes(manifest, loadContext, route.id),
children: createStaticHandlerDataRoutes(manifest, future, route.id, routesByParentId),
...commonRoute

@@ -60,0 +84,0 @@ };

@@ -1,25 +0,8 @@

import type { AppData } from "./data";
import type { TypedResponse } from "./responses";
declare type JsonPrimitive = string | number | boolean | String | Number | Boolean | null;
declare type NonJsonPrimitive = undefined | Function | symbol;
declare type IsAny<T> = 0 extends 1 & T ? true : false;
declare type Serialize<T> = IsAny<T> extends true ? any : T extends JsonPrimitive ? T : T extends NonJsonPrimitive ? never : T extends {
toJSON(): infer U;
} ? U : T extends [] ? [] : T extends [unknown, ...unknown[]] ? SerializeTuple<T> : T extends ReadonlyArray<infer U> ? (U extends NonJsonPrimitive ? null : Serialize<U>)[] : T extends object ? SerializeObject<UndefinedToOptional<T>> : never;
/** JSON serialize [tuples](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) */
declare type SerializeTuple<T extends [unknown, ...unknown[]]> = {
[k in keyof T]: T[k] extends NonJsonPrimitive ? null : Serialize<T[k]>;
};
/** JSON serialize objects (not including arrays) and classes */
declare type SerializeObject<T extends object> = {
[k in keyof T as T[k] extends NonJsonPrimitive ? never : k]: Serialize<T[k]>;
};
declare type UndefinedToOptional<T extends object> = {
[k in keyof T as undefined extends T[k] ? never : k]: T[k];
} & {
[k in keyof T as undefined extends T[k] ? k : never]?: Exclude<T[k], undefined>;
};
declare type ArbitraryFunction = (...args: any[]) => unknown;
import type { Jsonify } from "./jsonify";
import type { TypedDeferredData, TypedResponse } from "./responses";
import type { ClientActionFunctionArgs, ClientLoaderFunctionArgs } from "./routeModules";
/**
* Infer JSON serialized data type returned by a loader or action.
* Infer JSON serialized data type returned by a loader or action, while
* avoiding deserialization if the input type if it's a clientLoader or
* clientAction that returns a non-Response
*

@@ -29,3 +12,15 @@ * For example:

*/
export declare type SerializeFrom<T extends AppData | ArbitraryFunction> = Serialize<T extends (...args: any[]) => infer Output ? Awaited<Output> extends TypedResponse<infer U> ? U : Awaited<Output> : Awaited<T>>;
export type SerializeFrom<T> = T extends (...args: any[]) => infer Output ? Parameters<T> extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs] ? SerializeClient<Awaited<Output>> : Serialize<Awaited<Output>> : Jsonify<Awaited<T>>;
type SerializeClient<Output> = Output extends TypedDeferredData<infer U> ? {
[K in keyof U as K extends symbol ? never : Promise<any> extends U[K] ? K : never]: DeferValueClient<U[K]>;
} & {
[K in keyof U as Promise<any> extends U[K] ? never : K]: U[K];
} : Output extends TypedResponse<infer U> ? Jsonify<U> : Awaited<Output>;
type DeferValueClient<T> = T extends undefined ? undefined : T extends Promise<unknown> ? Promise<Awaited<T>> : T;
type Serialize<Output> = Output extends TypedDeferredData<infer U> ? {
[K in keyof U as K extends symbol ? never : Promise<any> extends U[K] ? K : never]: DeferValue<U[K]>;
} & Jsonify<{
[K in keyof U as Promise<any> extends U[K] ? never : K]: U[K];
}> : Output extends TypedResponse<infer U> ? Jsonify<U> : Jsonify<Output>;
type DeferValue<T> = T extends undefined ? undefined : T extends Promise<unknown> ? Promise<Jsonify<Awaited<T>>> : Jsonify<T>;
export {};

@@ -1,7 +0,5 @@

import type { StaticHandlerContext } from "./router";
import type { AppLoadContext } from "./data";
import type { ServerBuild } from "./build";
export declare type RequestHandler = (request: Request, loadContext?: AppLoadContext) => Promise<Response>;
export declare type CreateRequestHandlerFunction = (build: ServerBuild, mode?: string) => RequestHandler;
export type RequestHandler = (request: Request, loadContext?: AppLoadContext) => Promise<Response>;
export type CreateRequestHandlerFunction = (build: ServerBuild | (() => ServerBuild | Promise<ServerBuild>), mode?: string) => RequestHandler;
export declare const createRequestHandler: CreateRequestHandlerFunction;
export declare function differentiateCatchVersusErrorBoundaries(build: ServerBuild, context: StaticHandlerContext): void;
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -15,2 +15,3 @@ * Copyright (c) Remix Software Inc.

var router = require('@remix-run/router');
var entry = require('./entry.js');

@@ -25,38 +26,149 @@ var errors = require('./errors.js');

var serverHandoff = require('./serverHandoff.js');
var router = require('./router/router.js');
var utils = require('./router/utils.js');
var dev = require('./dev.js');
var singleFetch = require('./single-fetch.js');
var deprecations = require('./deprecations.js');
// TODO: RRR - Change import to @remix-run/router
const createRequestHandler = (build, mode$1) => {
function derive(build, mode$1) {
var _build$future, _build$future2;
let routes$1 = routes.createRoutes(build.routes);
let dataRoutes = routes.createStaticHandlerDataRoutes(build.routes, build.future);
let serverMode = mode.isServerMode(mode$1) ? mode$1 : mode.ServerMode.Production;
let staticHandler = router.createStaticHandler(dataRoutes, {
basename: build.basename,
future: {
v7_relativeSplatPath: ((_build$future = build.future) === null || _build$future === void 0 ? void 0 : _build$future.v3_relativeSplatPath) === true,
v7_throwAbortReason: ((_build$future2 = build.future) === null || _build$future2 === void 0 ? void 0 : _build$future2.v3_throwAbortReason) === true
}
});
let errorHandler = build.entry.module.handleError || ((error, {
request
}) => {
if (serverMode !== mode.ServerMode.Test && !request.signal.aborted) {
console.error(
// @ts-expect-error This is "private" from users but intended for internal use
router.isRouteErrorResponse(error) && error.error ? error.error : error);
}
});
return {
routes: routes$1,
dataRoutes,
serverMode,
staticHandler,
errorHandler
};
}
// @ts-ignore
const createRequestHandler = (build, mode$1) => {
let _build;
let routes;
let serverMode;
let staticHandler;
let errorHandler;
return async function requestHandler(request, loadContext = {}) {
_build = typeof build === "function" ? await build() : build;
mode$1 ??= _build.mode;
if (typeof build === "function") {
let derived = derive(_build, mode$1);
routes = derived.routes;
serverMode = derived.serverMode;
staticHandler = derived.staticHandler;
errorHandler = derived.errorHandler;
} else if (!routes || !serverMode || !staticHandler || !errorHandler) {
let derived = derive(_build, mode$1);
routes = derived.routes;
serverMode = derived.serverMode;
staticHandler = derived.staticHandler;
errorHandler = derived.errorHandler;
}
let url = new URL(request.url);
let matches = routeMatching.matchServerRoutes(routes$1, url.pathname);
let staticHandler;
{
staticHandler = router.unstable_createStaticHandler(routes.createStaticHandlerDataRoutes(build.routes, loadContext));
// Manifest request for fog of war
if (url.pathname === "/__manifest") {
await new Promise(resolve => setTimeout(resolve, 1000));
let data = {
patches: {},
notFoundPaths: []
};
let paths = url.searchParams.getAll("paths");
if (paths.length > 0) {
for (let path of paths) {
let matches = routeMatching.matchServerRoutes(routes, path, _build.basename);
if (matches) {
for (let match of matches) {
let routeId = match.route.id;
data.patches[routeId] = _build.assets.routes[routeId];
}
} else {
data.notFoundPaths.push(path);
}
}
return responses.json(data);
}
return new Response("Invalid Request", {
status: 400
});
}
let matches = routeMatching.matchServerRoutes(routes, url.pathname, _build.basename);
let params = matches && matches.length > 0 ? matches[0].params : {};
let handleError = error => {
if (mode$1 === mode.ServerMode.Development) {
var _getDevServerHooks, _getDevServerHooks$pr;
(_getDevServerHooks = dev.getDevServerHooks()) === null || _getDevServerHooks === void 0 ? void 0 : (_getDevServerHooks$pr = _getDevServerHooks.processRequestError) === null || _getDevServerHooks$pr === void 0 ? void 0 : _getDevServerHooks$pr.call(_getDevServerHooks, error);
}
errorHandler(error, {
context: loadContext,
params,
request
});
};
let response;
if (url.searchParams.has("_data")) {
if (_build.future.unstable_singleFetch) {
handleError(new Error("Warning: Single fetch-enabled apps should not be making ?_data requests, " + "this is likely to break in the future"));
}
let routeId = url.searchParams.get("_data");
{
response = await handleDataRequestRR(serverMode, staticHandler, routeId, request);
response = await handleDataRequest(serverMode, _build, staticHandler, routeId, request, loadContext, handleError);
if (_build.entry.module.handleDataRequest) {
response = await _build.entry.module.handleDataRequest(response, {
context: loadContext,
params,
request
});
if (responses.isRedirectResponse(response)) {
response = createRemixRedirectResponse(response, _build.basename);
}
}
if (build.entry.module.handleDataRequest) {
let match = matches.find(match => match.route.id == routeId);
response = await build.entry.module.handleDataRequest(response, {
} else if (_build.future.unstable_singleFetch && url.pathname.endsWith(".data")) {
let handlerUrl = new URL(request.url);
handlerUrl.pathname = handlerUrl.pathname.replace(/\.data$/, "").replace(/^\/_root$/, "/");
let singleFetchMatches = routeMatching.matchServerRoutes(routes, handlerUrl.pathname, _build.basename);
response = await handleSingleFetchRequest(serverMode, _build, staticHandler, request, handlerUrl, loadContext, handleError);
if (_build.entry.module.handleDataRequest) {
response = await _build.entry.module.handleDataRequest(response, {
context: loadContext,
params: match.params,
params: singleFetchMatches ? singleFetchMatches[0].params : {},
request
});
if (responses.isRedirectResponse(response)) {
let result = singleFetch.getSingleFetchRedirect(response.status, response.headers);
if (request.method === "GET") {
result = {
[singleFetch.SingleFetchRedirectSymbol]: result
};
}
let headers = new Headers(response.headers);
headers.set("Content-Type", "text/x-turbo");
return new Response(singleFetch.encodeViaTurboStream(result, request.signal, _build.entry.module.streamTimeout, serverMode), {
status: 200,
headers
});
}
}
} else if (matches && matches[matches.length - 1].route.module.default == null) {
{
response = await handleResourceRequestRR(serverMode, staticHandler, matches.slice(-1)[0].route.id, request);
}
} else if (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 {
{
response = await handleDocumentRequestRR(serverMode, build, staticHandler, request);
}
var _getDevServerHooks2, _getDevServerHooks2$g;
let criticalCss = mode$1 === mode.ServerMode.Development ? await ((_getDevServerHooks2 = dev.getDevServerHooks()) === null || _getDevServerHooks2 === void 0 ? void 0 : (_getDevServerHooks2$g = _getDevServerHooks2.getCriticalCss) === null || _getDevServerHooks2$g === void 0 ? void 0 : _getDevServerHooks2$g.call(_getDevServerHooks2, _build, url.pathname)) : undefined;
response = await handleDocumentRequest(serverMode, _build, staticHandler, request, loadContext, handleError, criticalCss);
}

@@ -73,83 +185,77 @@ if (request.method === "HEAD") {

};
async function handleDataRequestRR(serverMode, staticHandler, routeId, request) {
async function handleDataRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
try {
let response = await staticHandler.queryRoute(request, routeId);
let response = await staticHandler.queryRoute(request, {
routeId,
requestContext: loadContext
});
if (responses.isRedirectResponse(response)) {
// We don't have any way to prevent a fetch request from following
// redirects. So we use the `X-Remix-Redirect` header to indicate the
// next URL, and then "follow" the redirect manually on the client.
let headers = new Headers(response.headers);
headers.set("X-Remix-Redirect", headers.get("Location"));
headers.delete("Location");
if (response.headers.get("Set-Cookie") !== null) {
headers.set("X-Remix-Revalidate", "yes");
}
return new Response(null, {
status: 204,
headers
});
return createRemixRedirectResponse(response, build.basename);
}
if (router.UNSAFE_DEFERRED_SYMBOL in response) {
let deferredData = response[router.UNSAFE_DEFERRED_SYMBOL];
let body = responses.createDeferredReadableStream(deferredData, request.signal, serverMode);
let init = deferredData.init || {};
let headers = new Headers(init.headers);
headers.set("Content-Type", "text/remix-deferred");
// Mark successful responses with a header so we can identify in-flight
// network errors that are missing this header
headers.set("X-Remix-Response", "yes");
init.headers = headers;
return new Response(body, init);
}
// Mark all successful responses with a header so we can identify in-flight
// network errors that are missing this header
response.headers.set("X-Remix-Response", "yes");
return response;
} catch (error) {
if (error instanceof Response) {
if (responses.isResponse(error)) {
error.headers.set("X-Remix-Catch", "yes");
return error;
}
let status = 500;
let errorInstance = error;
if (utils.isRouteErrorResponse(error)) {
status = error.status;
errorInstance = error.error || errorInstance;
if (router.isRouteErrorResponse(error)) {
handleError(error);
return errorResponseToJson(error, serverMode);
}
if (serverMode !== mode.ServerMode.Test && !request.signal.aborted) {
console.error(errorInstance);
}
if (serverMode === mode.ServerMode.Development && errorInstance instanceof Error) {
return errorBoundaryError(errorInstance, status);
}
return errorBoundaryError(new Error("Unexpected Server Error"), status);
let errorInstance = error instanceof Error || error instanceof DOMException ? error : new Error("Unexpected Server Error");
handleError(errorInstance);
return router.json(errors.serializeError(errorInstance, serverMode), {
status: 500,
headers: {
"X-Remix-Error": "yes"
}
});
}
}
function findParentBoundary(routes, routeId, error) {
// Fall back to the root route if we don't match any routes, since Remix
// has default error/catch boundary handling. This handles the case where
// react-router doesn't have a matching "root" route to assign the error to
// so it returns context.errors = { __shim-error-route__: ErrorResponse }
let route = routes[routeId] || routes["root"];
// Router-thrown ErrorResponses will have the error instance. User-thrown
// Responses will not have an error. The one exception here is internal 404s
// which we handle the same as user-thrown 404s
let isCatch = utils.isRouteErrorResponse(error) && (!error.error || error.status === 404);
if (isCatch && route.module.CatchBoundary || !isCatch && route.module.ErrorBoundary || !route.parentId) {
return route.id;
}
return findParentBoundary(routes, route.parentId, error);
}
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, handlerUrl, loadContext, handleError) {
let {
result,
headers,
status
} = request.method !== "GET" ? await singleFetch.singleFetchAction(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetch.singleFetchLoaders(serverMode, staticHandler, request, handlerUrl, loadContext, handleError);
// Re-generate a remix-friendly context.errors structure. The Router only
// handles generic errors and does not distinguish error versus catch. We
// may have a thrown response tagged to a route that only exports an
// ErrorBoundary or vice versa. So we adjust here and ensure that
// data-loading errors are properly associated with routes that have the right
// type of boundaries.
function differentiateCatchVersusErrorBoundaries(build, context) {
if (!context.errors) {
return;
}
let errors = {};
for (let routeId of Object.keys(context.errors)) {
let error = context.errors[routeId];
let handlingRouteId = findParentBoundary(build.routes, routeId, error);
errors[handlingRouteId] = error;
}
context.errors = errors;
// Mark all successful responses with a header so we can identify in-flight
// network errors that are missing this header
let resultHeaders = new Headers(headers);
resultHeaders.set("X-Remix-Response", "yes");
resultHeaders.set("Content-Type", "text/x-turbo");
// Note: Deferred data is already just Promises, so we don't have to mess
// `activeDeferreds` or anything :)
return new Response(singleFetch.encodeViaTurboStream(result, request.signal, build.entry.module.streamTimeout, serverMode), {
status: status || 200,
headers: resultHeaders
});
}
async function handleDocumentRequestRR(serverMode, build, staticHandler, request) {
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) {
let context;
let responseStubs = singleFetch.getResponseStubs();
try {
context = await staticHandler.query(request);
context = await staticHandler.query(request, {
requestContext: loadContext,
unstable_dataStrategy: build.future.unstable_singleFetch ? singleFetch.getSingleFetchDataStrategy(responseStubs) : undefined
});
} catch (error) {
if (!request.signal.aborted && serverMode !== mode.ServerMode.Test) {
console.error(error);
}
handleError(error);
return new Response(null, {

@@ -159,113 +265,120 @@ status: 500

}
if (context instanceof Response) {
if (responses.isResponse(context)) {
return context;
}
let statusCode;
let headers$1;
if (build.future.unstable_singleFetch) {
let merged = singleFetch.mergeResponseStubs(context, responseStubs);
statusCode = merged.statusCode;
headers$1 = merged.headers;
if (responses.isRedirectStatusCode(statusCode) && headers$1.has("Location")) {
return new Response(null, {
status: statusCode,
headers: headers$1
});
}
} else {
statusCode = context.statusCode;
headers$1 = headers.getDocumentHeaders(build, context);
}
// Restructure context.errors to the right Catch/Error Boundary
differentiateCatchVersusErrorBoundaries(build, context);
let appState = {
trackBoundaries: true,
trackCatchBoundaries: true,
catchBoundaryRouteId: null,
renderBoundaryRouteId: null,
loaderBoundaryRouteId: null
};
// Copy staticContext.errors to catch/error boundaries in appState
// Note: Only assign the boundary id if this module has a boundary. This
// should be true in almost all cases, except for cases in which no
// boundaries exist and the router "assigns" the catch/error to the root
// route for lack of a better place to put it. If the root doesn't have a
// catch/error boundary, then we leave the boundaryId null to bubble to the
// default boundary at render time
for (let match of context.matches) {
var _context$errors, _build$routes$id, _build$routes$id2;
let route = match.route;
let id = route.id;
let error = (_context$errors = context.errors) === null || _context$errors === void 0 ? void 0 : _context$errors[id];
let hasCatchBoundary = ((_build$routes$id = build.routes[id]) === null || _build$routes$id === void 0 ? void 0 : _build$routes$id.module.CatchBoundary) != null;
let hasErrorBoundary = ((_build$routes$id2 = build.routes[id]) === null || _build$routes$id2 === void 0 ? void 0 : _build$routes$id2.module.ErrorBoundary) != null;
if (!error) {
continue;
} else if (utils.isRouteErrorResponse(error)) {
// Internal Router errors will throw an ErrorResponse with the actual
// error instance, while user-thrown ErrorResponses will not have the
// instance. We also exclude 404s so we can handle them as CatchBoundary
// errors so the user has a singular location for 404 UI
if (error.internal && error.error && error.status !== 404) {
if (hasErrorBoundary) {
appState.loaderBoundaryRouteId = id;
}
appState.trackBoundaries = false;
appState.error = await errors.serializeError(error.error);
if (error.status === 405 && error.error.message.includes("Invalid request method")) {
// Note: Emptying this out ensures our response matches the Remix-flow
// exactly, but functionally both end up using the root error boundary
// if it exists. Might be able to clean this up eventually.
context.matches = [];
}
break;
// Sanitize errors outside of development environments
if (context.errors) {
Object.values(context.errors).forEach(err => {
// @ts-expect-error `err.error` is "private" from users but intended for internal use
if ((!router.isRouteErrorResponse(err) || err.error) && !singleFetch.isResponseStub(err)) {
handleError(err);
}
});
context.errors = errors.sanitizeErrors(context.errors, serverMode);
}
// ErrorResponse's without an error were thrown by the user action/loader
if (hasCatchBoundary) {
appState.catchBoundaryRouteId = id;
}
appState.trackCatchBoundaries = false;
appState.catch = {
// Order is important for response matching assertions!
data: error.error && error.status === 404 ? error.error.message : error.data,
status: error.status,
statusText: error.statusText
};
break;
} else {
if (hasErrorBoundary) {
appState.loaderBoundaryRouteId = id;
}
appState.trackBoundaries = false;
appState.error = await errors.serializeError(error);
break;
}
}
let renderableMatches = getRenderableMatches(context.matches, appState);
if (!renderableMatches) {
var _root$module;
renderableMatches = [];
let root = staticHandler.dataRoutes[0];
if (root !== null && root !== void 0 && (_root$module = root.module) !== null && _root$module !== void 0 && _root$module.CatchBoundary) {
appState.catchBoundaryRouteId = "root";
renderableMatches.push({
params: {},
pathname: "",
route: staticHandler.dataRoutes[0]
});
}
}
let headers$1 = headers.getDocumentHeadersRR(build, context, renderableMatches);
let serverHandoff$1 = {
actionData: context.actionData || undefined,
appState,
matches: entry.createEntryMatches(renderableMatches, build.assets.routes),
routeData: context.loaderData || {},
future: build.future
// Server UI state to send to the client.
// - When single fetch is enabled, this is streamed down via `serverHandoffStream`
// - Otherwise it's stringified into `serverHandoffString`
let state = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: errors.serializeErrors(context.errors, serverMode)
};
let entryContext = {
...serverHandoff$1,
manifest: build.assets,
routeModules: entry.createEntryRouteModules(build.routes),
serverHandoffString: serverHandoff.createServerHandoffString(serverHandoff$1)
staticHandlerContext: context,
criticalCss,
serverHandoffString: serverHandoff.createServerHandoffString({
url: context.location.pathname,
basename: build.basename,
criticalCss,
future: build.future,
isSpaMode: build.isSpaMode,
...(!build.future.unstable_singleFetch ? {
state
} : null)
}),
...(build.future.unstable_singleFetch ? {
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
renderMeta: {}
} : null),
future: build.future,
isSpaMode: build.isSpaMode,
serializeError: err => errors.serializeError(err, serverMode)
};
let handleDocumentRequestParameters = [request, context.statusCode, headers$1, entryContext];
let handleDocumentRequestFunction = build.entry.module.default;
try {
return await handleDocumentRequestFunction(...handleDocumentRequestParameters);
return await handleDocumentRequestFunction(request, statusCode, headers$1, entryContext, loadContext);
} catch (error) {
handleDocumentRequestParameters[1] = 500;
appState.trackBoundaries = false;
appState.error = await errors.serializeError(error);
entryContext.serverHandoffString = serverHandoff.createServerHandoffString(serverHandoff$1);
handleError(error);
let errorForSecondRender = error;
// If they threw a response, unwrap it into an ErrorResponse like we would
// have for a loader/action
if (responses.isResponse(error)) {
try {
let data = await unwrapResponse(error);
errorForSecondRender = new router.UNSAFE_ErrorResponseImpl(error.status, error.statusText, data);
} catch (e) {
// If we can't unwrap the response - just leave it as-is
}
}
// Get a new StaticHandlerContext that contains the error at the right boundary
context = router.getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender);
// Sanitize errors outside of development environments
if (context.errors) {
context.errors = errors.sanitizeErrors(context.errors, serverMode);
}
// Get a new entryContext for the second render pass
// Server UI state to send to the client.
// - When single fetch is enabled, this is streamed down via `serverHandoffStream`
// - Otherwise it's stringified into `serverHandoffString`
let state = {
loaderData: context.loaderData,
actionData: context.actionData,
errors: errors.serializeErrors(context.errors, serverMode)
};
entryContext = {
...entryContext,
staticHandlerContext: context,
serverHandoffString: serverHandoff.createServerHandoffString({
url: context.location.pathname,
basename: build.basename,
future: build.future,
isSpaMode: build.isSpaMode,
...(!build.future.unstable_singleFetch ? {
state
} : null)
}),
...(build.future.unstable_singleFetch ? {
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode),
renderMeta: {}
} : null)
};
try {
return await handleDocumentRequestFunction(...handleDocumentRequestParameters);
return await handleDocumentRequestFunction(request, context.statusCode, headers$1, entryContext, loadContext);
} catch (error) {
handleError(error);
return returnLastResortErrorResponse(error, serverMode);

@@ -275,13 +388,46 @@ }

}
async function handleResourceRequestRR(serverMode, staticHandler, routeId, request) {
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) {
try {
let responseStubs = build.future.unstable_singleFetch ? singleFetch.getResponseStubs() : {};
// Note we keep the routeId here to align with the Remix handling of
// resource routes which doesn't take ?index into account and just takes
// the leaf match
let response = await staticHandler.queryRoute(request, routeId);
// callRouteLoader/callRouteAction always return responses
invariant["default"](response instanceof Response, "Expected a Response to be returned from queryRoute");
let response = await staticHandler.queryRoute(request, {
routeId,
requestContext: loadContext,
...(build.future.unstable_singleFetch ? {
unstable_dataStrategy: singleFetch.getSingleFetchResourceRouteDataStrategy({
responseStubs
})
} : null)
});
if (typeof response === "object") {
invariant["default"](!(router.UNSAFE_DEFERRED_SYMBOL in response), `You cannot return a \`defer()\` response from a Resource Route. Did you ` + `forget to export a default UI component from the "${routeId}" route?`);
}
if (build.future.unstable_singleFetch) {
let stub = responseStubs[routeId];
if (responses.isResponse(response)) {
// If a response was returned, we use it's status and we merge our
// response stub headers onto it
let ops = stub[singleFetch.ResponseStubOperationsSymbol];
for (let [op, ...args] of ops) {
// @ts-expect-error
response.headers[op](...args);
}
} else {
console.warn(deprecations.resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId));
// Otherwise we create a json Response using the stub
response = responses.json(response, {
status: stub.status,
headers: stub.headers
});
}
}
// callRouteLoader/callRouteAction always return responses (w/o single fetch).
// With single fetch, users should always be Responses from resource routes
invariant["default"](responses.isResponse(response), "Expected a Response to be returned from queryRoute");
return response;
} catch (error) {
if (error instanceof Response) {
if (responses.isResponse(error)) {
// Note: Not functionally required but ensures that our response headers

@@ -292,10 +438,18 @@ // match identically to what Remix returns

}
if (router.isRouteErrorResponse(error)) {
if (error) {
handleError(error);
}
return errorResponseToJson(error, serverMode);
}
handleError(error);
return returnLastResortErrorResponse(error, serverMode);
}
}
const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
new Set(["GET", "HEAD", ...validActionMethods]);
async function errorBoundaryError(error, status) {
return responses.json(await errors.serializeError(error), {
status,
function errorResponseToJson(errorResponse, serverMode) {
return router.json(errors.serializeError(
// @ts-expect-error This is "private" from users but intended for internal use
errorResponse.error || new Error("Unexpected Server Error"), serverMode), {
status: errorResponse.status,
statusText: errorResponse.statusText,
headers: {

@@ -306,27 +460,3 @@ "X-Remix-Error": "yes"

}
// This prevents `<Outlet/>` from rendering anything below where the error threw
// TODO: maybe do this in <RemixErrorBoundary + context>
function getRenderableMatches(matches, appState) {
if (!matches) {
return null;
}
// no error, no worries
if (!appState.catch && !appState.error) {
return matches;
}
let lastRenderableIndex = -1;
matches.forEach((match, index) => {
let id = match.route.id;
if (appState.renderBoundaryRouteId === id || appState.loaderBoundaryRouteId === id || appState.catchBoundaryRouteId === id) {
lastRenderableIndex = index;
}
});
return matches.slice(0, lastRenderableIndex + 1);
}
function returnLastResortErrorResponse(error, serverMode) {
if (serverMode !== mode.ServerMode.Test) {
console.error(error);
}
let message = "Unexpected Server Error";

@@ -345,121 +475,26 @@ if (serverMode !== mode.ServerMode.Production) {

}
function unwrapResponse(response) {
let contentType = response.headers.get("Content-Type");
// Check between word boundaries instead of startsWith() due to the last
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
return contentType && /\bapplication\/json\b/.test(contentType) ? response.body == null ? null : response.json() : response.text();
}
function createRemixRedirectResponse(response, basename) {
// We don't have any way to prevent a fetch request from following
// redirects. So we use the `X-Remix-Redirect` header to indicate the
// next URL, and then "follow" the redirect manually on the client.
let headers = new Headers(response.headers);
let redirectUrl = headers.get("Location");
headers.set("X-Remix-Redirect", basename ? router.stripBasename(redirectUrl, basename) || redirectUrl : redirectUrl);
headers.set("X-Remix-Status", String(response.status));
headers.delete("Location");
if (response.headers.get("Set-Cookie") !== null) {
headers.set("X-Remix-Revalidate", "yes");
}
return new Response(null, {
status: 204,
headers
});
}
// TODO: Remove before we "finalize" router migration
// async function assert(
// a: Response,
// b: Response,
// accessor: (r: Response) => object | Promise<object>,
// message: string
// ) {
// let aStr = JSON.stringify(await accessor(a));
// let bStr = JSON.stringify(await accessor(b));
// if (aStr !== bStr) {
// console.error(message);
// message += "\nResponse 1:\n" + aStr + "\nResponse 2:\n" + bStr;
// throw new Error(message);
// }
// }
// async function assertResponsesMatch(_a: Response, _b: Response) {
// let a = _a.clone();
// let b = _b.clone();
// assert(
// a,
// b,
// (r) => Object.fromEntries(r.headers.entries()),
// "Headers did not match!"
// );
// if (a.headers.get("Content-Type")?.startsWith("application/json")) {
// if (a.headers.get("X-Remix-Error")) {
// assert(
// a,
// b,
// async (r) => {
// let { stack, ...json } = await r.json();
// return {
// ...json,
// stack: stack ? "yes" : "no",
// };
// },
// "JSON error response body did not match!\n Response 1:\n" +
// (await a.clone().text()) +
// "\nResponse 2:\n" +
// (await b.clone().text())
// );
// } else {
// assert(
// a,
// b,
// (r) => r.json(),
// "JSON response body did not match!\nResponse 1:\n" +
// (await a.clone().text()) +
// "\nResponse 2:\n" +
// (await b.clone().text())
// );
// }
// } else {
// assert(
// a,
// b,
// async (r) => {
// let text = await r.text();
// // Strip stack trace from default error boundary HTML
// if (
// text.includes(">Application Error</h1>") &&
// text.includes("💿 Hey developer👋")
// ) {
// return stripStackTraceFromDefaultErrorBoundary(text);
// }
// if (text.includes("<script>window.__remixContext")) {
// return stripStackTraceFromRemixContext(text);
// }
// return text;
// },
// "Non-JSON response body did not match!\nResponse 1:\n" +
// (await a.clone().text()) +
// "\nResponse 2:\n" +
// (await b.clone().text())
// );
// }
// }
// function stripStackTraceFromDefaultErrorBoundary(text: string) {
// let openPreStart = text.indexOf("<pre ");
// let openPreEnd = text.indexOf('">', openPreStart + 1);
// let closePre = text.indexOf("</pre>", openPreEnd + 1);
// let error = text.substring(openPreEnd + 2, closePre);
// let stackStart = error.indexOf("\n");
// let errorWithoutStack = error.substring(0, stackStart);
// return (
// text.replace(error, "ERROR REMOVED") + "\n\nError:\n" + errorWithoutStack
// );
// }
// function stripStackTraceFromRemixContext(text: string) {
// let scriptStart = text.indexOf("<script>window.__remixContext");
// let scriptEnd = text.indexOf("</script>", scriptStart + 1);
// let scriptContents = text.substring(scriptStart, scriptEnd + 9);
// let remixContext = JSON.parse(
// scriptContents
// .replace("<script>window.__remixContext = ", "")
// .replace(";</script>", "")
// );
// // If we didn't have an error - assert the full document
// if (!remixContext?.appState?.error?.stack) {
// return text;
// }
// // Otherwise remove the stack trace and assert the text/JSON as a
// // concatenated string
// remixContext.appState.error.stack = "yes";
// text = text.replace(scriptContents, "CONTEXT REMOVED");
// return text + "\n\nContext:\n" + JSON.stringify(remixContext);
// }
exports.createRequestHandler = createRequestHandler;
exports.differentiateCatchVersusErrorBoundaries = differentiateCatchVersusErrorBoundaries;

@@ -1,1 +0,12 @@

export declare function createServerHandoffString(serverHandoff: any): string;
import type { HydrationState } from "@remix-run/router";
import type { FutureConfig } from "./entry";
type ValidateShape<T, Shape> = T extends Shape ? Exclude<keyof T, keyof Shape> extends never ? T : never : never;
export declare function createServerHandoffString<T>(serverHandoff: {
state?: ValidateShape<T, HydrationState>;
criticalCss?: string;
url: string;
basename: string | undefined;
future: FutureConfig;
isSpaMode: boolean;
}): string;
export {};
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -17,2 +17,3 @@ * Copyright (c) Remix Software Inc.

// TODO: Remove Promises from serialization
function createServerHandoffString(serverHandoff) {

@@ -19,0 +20,0 @@ // Uses faster alternative of jsesc to escape data returned from the loaders.

@@ -12,5 +12,5 @@ import type { CookieParseOptions, CookieSerializeOptions } from "cookie";

*
* @see https://remix.run/api/remix#session-api
* @see https://remix.run/utils/sessions#session-api
*/
export interface Session {
export interface Session<Data = SessionData, FlashData = Data> {
/**

@@ -29,3 +29,3 @@ * A unique identifier for this session.

*/
readonly data: SessionData;
readonly data: FlashSessionData<Data, FlashData>;
/**

@@ -35,11 +35,11 @@ * Returns `true` if the session has a value for the given `name`, `false`

*/
has(name: string): boolean;
has(name: (keyof Data | keyof FlashData) & string): boolean;
/**
* Returns the value for the given `name` in this session.
*/
get(name: string): any;
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(name: string, value: any): void;
set<Key extends keyof Data & string>(name: Key, value: Data[Key]): void;
/**

@@ -49,9 +49,13 @@ * Sets a value in the session that is only valid until the next `get()`.

*/
flash(name: string, value: any): void;
flash<Key extends keyof FlashData & string>(name: Key, value: FlashData[Key]): void;
/**
* Removes a value from the session.
*/
unset(name: string): void;
unset(name: keyof Data & string): void;
}
export declare type CreateSessionFunction = (initialData?: SessionData, id?: string) => Session;
export type FlashSessionData<Data, FlashData> = Partial<Data & {
[Key in keyof FlashData as FlashDataKey<Key & string>]: FlashData[Key];
}>;
type FlashDataKey<Key extends string> = `__flash_${Key}__`;
export type CreateSessionFunction = <Data = SessionData, FlashData = Data>(initialData?: Data, id?: string) => Session<Data, FlashData>;
/**

@@ -63,10 +67,10 @@ * Creates a new Session object.

*
* @see https://remix.run/api/remix#createsession
* @see https://remix.run/utils/sessions#createsession
*/
export declare const createSession: CreateSessionFunction;
export declare type IsSessionFunction = (object: any) => object is Session;
export type IsSessionFunction = (object: any) => object is Session;
/**
* Returns true if an object is a Remix session.
*
* @see https://remix.run/api/remix#issession
* @see https://remix.run/utils/sessions#issession
*/

@@ -81,3 +85,3 @@ export declare const isSession: IsSessionFunction;

*/
export interface SessionStorage {
export interface SessionStorage<Data = SessionData, FlashData = Data> {
/**

@@ -88,3 +92,3 @@ * Parses a Cookie header from a HTTP request and returns the associated

*/
getSession(cookieHeader?: string | null, options?: CookieParseOptions): Promise<Session>;
getSession: (cookieHeader?: string | null, options?: CookieParseOptions) => Promise<Session<Data, FlashData>>;
/**

@@ -94,3 +98,3 @@ * Stores all data in the Session and returns the Set-Cookie header to be

*/
commitSession(session: Session, options?: CookieSerializeOptions): Promise<string>;
commitSession: (session: Session<Data, FlashData>, options?: CookieSerializeOptions) => Promise<string>;
/**

@@ -100,3 +104,3 @@ * Deletes all data associated with the Session and returns the Set-Cookie

*/
destroySession(session: Session, options?: CookieSerializeOptions): Promise<string>;
destroySession: (session: Session<Data, FlashData>, options?: CookieSerializeOptions) => Promise<string>;
}

@@ -112,3 +116,3 @@ /**

*/
export interface SessionIdStorageStrategy {
export interface SessionIdStorageStrategy<Data = SessionData, FlashData = Data> {
/**

@@ -124,11 +128,11 @@ * The Cookie used to store the session id, or options used to automatically

*/
createData: (data: SessionData, expires?: Date) => Promise<string>;
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<SessionData | null>;
readData: (id: string) => Promise<FlashSessionData<Data, FlashData> | null>;
/**
* Updates data for the given session id.
*/
updateData: (id: string, data: SessionData, expires?: Date) => Promise<void>;
updateData: (id: string, data: FlashSessionData<Data, FlashData>, expires?: Date) => Promise<void>;
/**

@@ -139,3 +143,3 @@ * Deletes data for a given session id from the data store.

}
export declare type CreateSessionStorageFunction = (strategy: SessionIdStorageStrategy) => SessionStorage;
export type CreateSessionStorageFunction = <Data = SessionData, FlashData = Data>(strategy: SessionIdStorageStrategy<Data, FlashData>) => SessionStorage<Data, FlashData>;
/**

@@ -147,5 +151,6 @@ * Creates a SessionStorage object using a SessionIdStorageStrategy.

*
* @see https://remix.run/api/remix#createsessionstorage
* @see https://remix.run/utils/sessions#createsessionstorage
*/
export declare const createSessionStorageFactory: (createCookie: CreateCookieFunction) => CreateSessionStorageFunction;
export declare function warnOnceAboutSigningSessionCookie(cookie: Cookie): void;
export {};
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -22,2 +22,8 @@ * Copyright (c) Remix Software Inc.

/**
* Session persists data across HTTP requests.
*
* @see https://remix.run/utils/sessions#session-api
*/
function flash(name) {

@@ -32,3 +38,3 @@ return `__flash_${name}__`;

*
* @see https://remix.run/api/remix#createsession
* @see https://remix.run/utils/sessions#createsession
*/

@@ -71,3 +77,3 @@ const createSession = (initialData = {}, id = "") => {

*
* @see https://remix.run/api/remix#issession
* @see https://remix.run/utils/sessions#issession
*/

@@ -87,2 +93,12 @@ const isSession = object => {

/**
* 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.
*/
/**
* Creates a SessionStorage object using a SessionIdStorageStrategy.

@@ -93,3 +109,3 @@ *

*
* @see https://remix.run/api/remix#createsessionstorage
* @see https://remix.run/utils/sessions#createsessionstorage
*/

@@ -116,6 +132,7 @@ const createSessionStorageFactory = createCookie => ({

} = session;
let expires = (options === null || options === void 0 ? void 0 : options.maxAge) != null ? new Date(Date.now() + options.maxAge * 1000) : (options === null || options === void 0 ? void 0 : options.expires) != null ? options.expires : cookie.expires;
if (id) {
await updateData(id, data, cookie.expires);
await updateData(id, data, expires);
} else {
id = await createData(data, cookie.expires);
id = await createData(data, expires);
}

@@ -128,2 +145,3 @@ return cookie.serialize(id, options);

...options,
maxAge: undefined,
expires: new Date(0)

@@ -135,3 +153,3 @@ });

function warnOnceAboutSigningSessionCookie(cookie) {
warnings.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://remix.run/api/remix#signing-cookies ` + `for more information.`);
warnings.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://remix.run/utils/cookies#signing-cookies ` + `for more information.`);
}

@@ -138,0 +156,0 @@

import type { CreateCookieFunction } from "../cookies";
import type { SessionStorage, SessionIdStorageStrategy } from "../sessions";
import type { SessionStorage, SessionIdStorageStrategy, SessionData } from "../sessions";
interface CookieSessionStorageOptions {

@@ -10,3 +10,3 @@ /**

}
export declare type CreateCookieSessionStorageFunction = (options?: CookieSessionStorageOptions) => SessionStorage;
export type CreateCookieSessionStorageFunction = <Data = SessionData, FlashData = Data>(options?: CookieSessionStorageOptions) => SessionStorage<Data, FlashData>;
/**

@@ -21,5 +21,5 @@ * Creates and returns a SessionStorage object that stores all session data

*
* @see https://remix.run/api/remix#createcookiesessionstorage
* @see https://remix.run/utils/sessions#createcookiesessionstorage
*/
export declare const createCookieSessionStorageFactory: (createCookie: CreateCookieFunction) => CreateCookieSessionStorageFunction;
export {};
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -27,3 +27,3 @@ * Copyright (c) Remix Software Inc.

*
* @see https://remix.run/api/remix#createcookiesessionstorage
* @see https://remix.run/utils/sessions#createcookiesessionstorage
*/

@@ -49,2 +49,3 @@ const createCookieSessionStorageFactory = createCookie => ({

...options,
maxAge: undefined,
expires: new Date(0)

@@ -51,0 +52,0 @@ });

@@ -1,2 +0,2 @@

import type { SessionStorage, SessionIdStorageStrategy, CreateSessionStorageFunction } from "../sessions";
import type { SessionData, SessionStorage, SessionIdStorageStrategy, CreateSessionStorageFunction } from "../sessions";
interface MemorySessionStorageOptions {

@@ -9,3 +9,3 @@ /**

}
export declare type CreateMemorySessionStorageFunction = (options?: MemorySessionStorageOptions) => SessionStorage;
export type CreateMemorySessionStorageFunction = <Data = SessionData, FlashData = Data>(options?: MemorySessionStorageOptions) => SessionStorage<Data, FlashData>;
/**

@@ -18,5 +18,5 @@ * Creates and returns a simple in-memory SessionStorage object, mostly useful

*
* @see https://remix.run/api/remix#creatememorysessionstorage
* @see https://remix.run/utils/sessions#creatememorysessionstorage
*/
export declare const createMemorySessionStorageFactory: (createSessionStorage: CreateSessionStorageFunction) => CreateMemorySessionStorageFunction;
export {};
/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -22,3 +22,3 @@ * Copyright (c) Remix Software Inc.

*
* @see https://remix.run/api/remix#creatememorysessionstorage
* @see https://remix.run/utils/sessions#creatememorysessionstorage
*/

@@ -28,3 +28,2 @@ const createMemorySessionStorageFactory = createSessionStorage => ({

} = {}) => {
let uniqueId = 0;
let map = new Map();

@@ -34,3 +33,3 @@ return createSessionStorage({

async createData(data, expires) {
let id = (++uniqueId).toString();
let id = Math.random().toString(36).substring(2, 10);
map.set(id, {

@@ -37,0 +36,0 @@ data,

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

@@ -1,3 +0,3 @@

import type { UploadHandler } from "@remix-run/server-runtime";
export declare type MemoryUploadHandlerFilterArgs = {
import type { UploadHandler } from "../formData";
export type MemoryUploadHandlerFilterArgs = {
filename?: string;

@@ -7,3 +7,3 @@ contentType: string;

};
export declare type MemoryUploadHandlerOptions = {
export type MemoryUploadHandlerOptions = {
/**

@@ -10,0 +10,0 @@ * The maximum upload size allowed. If the size is exceeded an error will be thrown.

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

/**
* @remix-run/server-runtime v0.0.0-experimental-a547b878b
* @remix-run/server-runtime v0.0.0-experimental-a7ab46039
*

@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc.

@@ -1,7 +0,22 @@

Copyright 2021 Remix Software Inc.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Copyright (c) Remix Software Inc. 2020-2021
Copyright (c) Shopify Inc. 2022-2024
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{
"name": "@remix-run/server-runtime",
"version": "0.0.0-experimental-a547b878b",
"version": "0.0.0-experimental-a7ab46039",
"description": "Server runtime for Remix",

@@ -19,21 +19,24 @@ "bugs": {

"dependencies": {
"@types/cookie": "^0.4.0",
"@remix-run/router": "0.0.0-experimental-abead7ae1",
"@types/cookie": "^0.6.0",
"@web3-storage/multipart-parser": "^1.0.0",
"cookie": "^0.4.1",
"react-router-dom": "6.3.0",
"cookie": "^0.6.0",
"set-cookie-parser": "^2.4.8",
"source-map": "^0.7.3"
"source-map": "^0.7.3",
"turbo-stream": "^2.0.0"
},
"devDependencies": {
"@remix-run/web-file": "^3.0.2",
"@types/set-cookie-parser": "^2.4.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"typescript": "^5.1.6"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
"typescript": "^5.1.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"engines": {
"node": ">=14"
"node": ">=18.0.0"
},

@@ -45,3 +48,6 @@ "files": [

"README.md"
]
}
],
"scripts": {
"tsc": "tsc"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc