Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@remix-run/server-runtime

Package Overview
Dependencies
Maintainers
2
Versions
1026
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 1.0.6 to 1.1.0

esm/cookies.js

2

cookies.js
/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

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

import type { Params } from "react-router";
import type { ServerBuild } from "./build";
import type { RouteMatch } from "./routeMatching";
import type { ServerRoute } from "./routes";
/**

@@ -12,6 +12,12 @@ * An object of arbitrary for route loaders and actions provided by the

export declare type AppData = any;
export declare function loadRouteData(build: ServerBuild, routeId: string, request: Request, context: AppLoadContext, params: Params): Promise<Response>;
export declare function callRouteAction(build: ServerBuild, routeId: string, request: Request, context: AppLoadContext, params: Params): Promise<Response>;
export declare function isCatchResponse(value: any): boolean;
export declare function isRedirectResponse(response: Response): boolean;
export declare function extractData(response: Response): Promise<AppData>;
export declare function callRouteAction({ loadContext, match, request }: {
loadContext: unknown;
match: RouteMatch<ServerRoute>;
request: Request;
}): Promise<Response>;
export declare function callRouteLoader({ loadContext, match, request }: {
request: Request;
match: RouteMatch<ServerRoute>;
loadContext: unknown;
}): Promise<Response>;
export declare function extractData(response: Response): Promise<unknown>;
/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

async function loadRouteData(build, routeId, request, context, params) {
let routeModule = build.routes[routeId].module;
async function callRouteAction({
loadContext,
match,
request
}) {
let action = match.route.module.action;
if (!routeModule.loader) {
return Promise.resolve(responses.json(null));
if (!action) {
throw new Error(`You made a ${request.method} request to ${request.url} but did not provide ` + `an \`action\` for route "${match.route.id}", so there is no way to handle the ` + `request.`);
}

@@ -33,13 +37,13 @@

try {
result = await routeModule.loader({
request,
context,
params
result = await action({
request: stripDataParam(stripIndexParam(request.clone())),
context: loadContext,
params: match.params
});
} catch (error) {
if (!isResponse(error)) {
if (!responses.isResponse(error)) {
throw error;
}
if (!isRedirectResponse(error)) {
if (!responses.isRedirectResponse(error)) {
error.headers.set("X-Remix-Catch", "yes");

@@ -52,12 +56,16 @@ }

if (result === undefined) {
throw new Error(`You defined a loader for route "${routeId}" but didn't return ` + `anything from your \`loader\` function. Please return a value or \`null\`.`);
throw new Error(`You defined an action for route "${match.route.id}" but didn't return ` + `anything from your \`action\` function. Please return a value or \`null\`.`);
}
return isResponse(result) ? result : responses.json(result);
return responses.isResponse(result) ? result : responses.json(result);
}
async function callRouteAction(build, routeId, request, context, params) {
let routeModule = build.routes[routeId].module;
async function callRouteLoader({
loadContext,
match,
request
}) {
let loader = match.route.module.loader;
if (!routeModule.action) {
throw new Error(`You made a ${request.method} request to ${request.url} but did not provide ` + `an \`action\` for route "${routeId}", so there is no way to handle the ` + `request.`);
if (!loader) {
throw new Error(`You made a ${request.method} request to ${request.url} but did not provide ` + `a \`loader\` for route "${match.route.id}", so there is no way to handle the ` + `request.`);
}

@@ -68,13 +76,13 @@

try {
result = await routeModule.action({
request,
context,
params
result = await loader({
request: stripDataParam(stripIndexParam(request.clone())),
context: loadContext,
params: match.params
});
} catch (error) {
if (!isResponse(error)) {
if (!responses.isResponse(error)) {
throw error;
}
if (!isRedirectResponse(error)) {
if (!responses.isRedirectResponse(error)) {
error.headers.set("X-Remix-Catch", "yes");

@@ -87,19 +95,33 @@ }

if (result === undefined) {
throw new Error(`You defined an action for route "${routeId}" but didn't return ` + `anything from your \`action\` function. Please return a value or \`null\`.`);
throw new Error(`You defined an action for route "${match.route.id}" but didn't return ` + `anything from your \`action\` function. Please return a value or \`null\`.`);
}
return isResponse(result) ? result : responses.json(result);
return responses.isResponse(result) ? result : responses.json(result);
}
function isCatchResponse(value) {
return isResponse(value) && value.headers.get("X-Remix-Catch") != null;
function stripIndexParam(request) {
let url = new URL(request.url);
let indexValues = url.searchParams.getAll("index");
url.searchParams.delete("index");
let indexValuesToKeep = [];
for (let indexValue of indexValues) {
if (indexValue) {
indexValuesToKeep.push(indexValue);
}
}
for (let toKeep of indexValuesToKeep) {
url.searchParams.append("index", toKeep);
}
return new Request(url.href, request);
}
function isResponse(value) {
return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
function stripDataParam(request) {
let url = new URL(request.url);
url.searchParams.delete("_data");
return new Request(url.href, request);
}
const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
function isRedirectResponse(response) {
return redirectStatusCodes.has(response.status);
}
function extractData(response) {

@@ -121,5 +143,3 @@ let contentType = response.headers.get("Content-Type");

exports.callRouteAction = callRouteAction;
exports.callRouteLoader = callRouteLoader;
exports.extractData = extractData;
exports.isCatchResponse = isCatchResponse;
exports.isRedirectResponse = isRedirectResponse;
exports.loadRouteData = loadRouteData;

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

import type { ComponentDidCatchEmulator } from "./errors";
import type { AppState } from "./errors";
import type { RouteManifest, ServerRouteManifest, EntryRoute, ServerRoute } from "./routes";

@@ -7,3 +7,3 @@ import type { RouteData } from "./routeData";

export interface EntryContext {
componentDidCatchEmulator: ComponentDidCatchEmulator;
appState: AppState;
manifest: AssetsManifest;

@@ -10,0 +10,0 @@ matches: RouteMatch<EntryRoute>[];

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

@@ -42,3 +42,3 @@ /**

*/
export interface ComponentDidCatchEmulator {
export interface AppState {
error?: SerializedError;

@@ -45,0 +45,0 @@ catch?: ThrownResponse;

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

{
"name": "@remix-run/server-runtime",
"description": "Server runtime for Remix",
"version": "1.0.6",
"version": "1.1.0",
"license": "MIT",
"main": "./index.js",
"module": "./esm/index.js",
"repository": {

@@ -17,3 +20,3 @@ "type": "git",

"jsesc": "^3.0.1",
"react-router-dom": "^6.0.2",
"react-router-dom": "^6.2.1",
"set-cookie-parser": "^2.4.8",

@@ -20,0 +23,0 @@ "source-map": "^0.7.3"

@@ -5,3 +5,3 @@ /**

* The whole point here is to abstract out the server functionality that is required
* by the server runtime but is dependant on the platform runtime.
* by the server runtime but is dependent on the platform runtime.
*

@@ -8,0 +8,0 @@ * An example of this is error beautification as it depends on loading sourcemaps from

/**
* A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
*/
export declare function json(data: any, init?: number | ResponseInit): Response;
export declare function json<Data>(data: Data, init?: number | ResponseInit): Response;
/**

@@ -10,1 +10,5 @@ * A redirect response. Sets the status code and the `Location` header.

export declare function redirect(url: string, init?: number | ResponseInit): Response;
export declare function isResponse(value: any): value is Response;
export declare function isRedirectResponse(response: Response): boolean;
export declare function isCatchResponse(response: Response): boolean;
export declare function extractData(response: Response): Promise<unknown>;
/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

if (typeof init === "number") {
if (typeof responseInit === "number") {
responseInit = {
status: init
status: responseInit
};

@@ -56,8 +56,21 @@ } else if (typeof responseInit.status === "undefined") {

headers.set("Location", url);
return new Response("", { ...responseInit,
return new Response(null, { ...responseInit,
headers
});
}
function isResponse(value) {
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 isRedirectResponse(response) {
return redirectStatusCodes.has(response.status);
}
function isCatchResponse(response) {
return response.headers.get("X-Remix-Catch") != null;
}
exports.isCatchResponse = isCatchResponse;
exports.isRedirectResponse = isRedirectResponse;
exports.isResponse = isResponse;
exports.json = json;
exports.redirect = redirect;
import type { AppData } from "./data";
import type { ServerRoute } from "./routes";
import type { RouteMatch } from "./routeMatching";
export interface RouteData {
[routeId: string]: AppData;
}
export declare function createRouteData(matches: RouteMatch<ServerRoute>[], responses: Response[]): Promise<RouteData>;
export declare function createActionData(response: Response): Promise<RouteData>;
/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

var routes = require('./routes.js');
var routeData = require('./routeData.js');
var responses = require('./responses.js');

@@ -33,51 +32,47 @@ var serverHandoff = require('./serverHandoff.js');

function getRequestType(request, matches) {
if (isDataRequest(request)) {
return "data";
}
if (!matches) {
return "document";
}
let match = matches.slice(-1)[0];
if (!match.route.module.default) {
return "resource";
}
return "document";
}
/**
* Creates a function that serves HTTP requests.
*/
function createRequestHandler(build, platform, mode$1) {
let routes$1 = routes.createRoutes(build.routes);
let serverMode = mode.isServerMode(mode$1) ? mode$1 : mode.ServerMode.Production;
return async (request, loadContext = {}) => {
return async function requestHandler(request, loadContext) {
let url = new URL(request.url);
let matches = routeMatching.matchServerRoutes(routes$1, url.pathname);
let requestType = getRequestType(request, matches);
let requestType = getRequestType(url, matches);
let response;
switch (requestType) {
// has _data
case "data":
response = await handleDataRequest(request, loadContext, build, platform, matches);
response = await handleDataRequest({
request,
loadContext,
matches: matches,
handleDataRequest: build.entry.module.handleDataRequest,
serverMode
});
break;
// no _data & default export
case "document":
response = await handleDocumentRequest(request, loadContext, build, platform, routes$1, serverMode);
response = await renderDocumentRequest({
build,
loadContext,
matches,
request,
routes: routes$1,
serverMode
});
break;
// no _data or default export
case "resource":
response = await handleResourceRequest(request, loadContext, build, platform, matches);
response = await handleResourceRequest({
request,
loadContext,
matches: matches,
serverMode
});
break;
}
if (isHeadRequest(request)) {
if (request.method.toLowerCase() === "head") {
return new Response(null, {

@@ -94,119 +89,96 @@ headers: response.headers,

async function handleResourceRequest(request, loadContext, build, platform, matches) {
async function handleDataRequest({
handleDataRequest,
loadContext,
matches,
request,
serverMode
}) {
if (!isValidRequestMethod(request)) {
return errorBoundaryError(new Error(`Invalid request method "${request.method}"`), 405);
}
let url = new URL(request.url);
if (!matches) {
return jsonError(`No route matches URL "${url.pathname}"`, 404);
return errorBoundaryError(new Error(`No route matches URL "${url.pathname}"`), 404);
}
let routeMatch = matches.slice(-1)[0];
let response;
let match;
try {
return isActionRequest(request) ? await data.callRouteAction(build, routeMatch.route.id, request, loadContext, routeMatch.params) : await data.loadRouteData(build, routeMatch.route.id, request, loadContext, routeMatch.params);
} catch (error) {
var _platform$formatServe;
if (isActionRequest(request)) {
match = getActionRequestMatch(url, matches);
response = await data.callRouteAction({
loadContext,
match,
request: request
});
} else {
let routeId = url.searchParams.get("_data");
let formattedError = (await ((_platform$formatServe = platform.formatServerError) === null || _platform$formatServe === void 0 ? void 0 : _platform$formatServe.call(platform, error))) || error;
throw formattedError;
}
}
if (!routeId) {
return errorBoundaryError(new Error(`Missing route id in ?_data`), 403);
}
async function handleDataRequest(request, loadContext, build, platform, matches) {
if (!isValidRequestMethod(request)) {
return jsonError(`Invalid request method "${request.method}"`, 405);
}
let tempMatch = matches.find(match => match.route.id === routeId);
let url = new URL(request.url);
if (!tempMatch) {
return errorBoundaryError(new Error(`Route "${routeId}" does not match URL "${url.pathname}"`), 403);
}
if (!matches) {
return jsonError(`No route matches URL "${url.pathname}"`, 404);
}
let routeMatch;
if (isActionRequest(request)) {
routeMatch = matches[matches.length - 1];
if (!isIndexRequestUrl(url) && matches[matches.length - 1].route.id.endsWith("/index")) {
routeMatch = matches[matches.length - 2];
match = tempMatch;
response = await data.callRouteLoader({
loadContext,
match,
request
});
}
} else {
let routeId = url.searchParams.get("_data");
if (!routeId) {
return jsonError(`Missing route id in ?_data`, 403);
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");
return new Response(null, {
status: 204,
headers
});
}
let match = matches.find(match => match.route.id === routeId);
if (!match) {
return jsonError(`Route "${routeId}" does not match URL "${url.pathname}"`, 403);
if (handleDataRequest) {
response = await handleDataRequest(response.clone(), {
context: loadContext,
params: match.params,
request: request.clone()
});
}
routeMatch = match;
}
let clonedRequest = stripIndexParam(stripDataParam(request));
let response;
try {
response = isActionRequest(request) ? await data.callRouteAction(build, routeMatch.route.id, clonedRequest, loadContext, routeMatch.params) : await data.loadRouteData(build, routeMatch.route.id, clonedRequest, loadContext, routeMatch.params);
return response;
} catch (error) {
var _platform$formatServe2;
if (serverMode !== mode.ServerMode.Test) {
console.error(error);
}
let formattedError = (await ((_platform$formatServe2 = platform.formatServerError) === null || _platform$formatServe2 === void 0 ? void 0 : _platform$formatServe2.call(platform, error))) || error;
response = responses.json(await errors.serializeError(formattedError), {
status: 500,
headers: {
"X-Remix-Error": "unfortunately, yes"
}
});
}
if (serverMode === mode.ServerMode.Development) {
return errorBoundaryError(error, 500);
}
if (data.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");
return new Response("", {
status: 204,
headers
});
return errorBoundaryError(new Error("Unexpected Server Error"), 500);
}
if (build.entry.module.handleDataRequest) {
clonedRequest = stripIndexParam(stripDataParam(request));
return build.entry.module.handleDataRequest(response, {
request: clonedRequest,
context: loadContext,
params: routeMatch.params
});
}
return response;
}
async function handleDocumentRequest(request, loadContext, build, platform, routes, serverMode) {
async function renderDocumentRequest({
build,
loadContext,
matches,
request,
routes,
serverMode
}) {
let url = new URL(request.url);
let requestState = isValidRequestMethod(request) ? "ok" : "invalid-request";
let matches = requestState === "ok" ? routeMatching.matchServerRoutes(routes, url.pathname) : null;
if (!matches) {
// If we do not match a user-provided-route, fall back to the root
// to allow the CatchBoundary to take over while maintining invalid
// request state if already set
if (requestState === "ok") {
requestState = "no-match";
}
matches = [{
params: {},
pathname: "",
route: routes[0]
}];
}
let componentDidCatchEmulator = {
let appState = {
trackBoundaries: true,

@@ -220,161 +192,208 @@ trackCatchBoundaries: true,

};
let responseState = "ok";
let actionResponse;
let actionRouteId;
if (requestState !== "ok") {
responseState = "caught";
componentDidCatchEmulator.trackCatchBoundaries = false;
let withBoundaries = getMatchesUpToDeepestBoundary(matches, "CatchBoundary");
componentDidCatchEmulator.catchBoundaryRouteId = withBoundaries.length > 0 ? withBoundaries[withBoundaries.length - 1].route.id : null;
componentDidCatchEmulator.catch = {
status: requestState === "no-match" ? 404 : 405,
statusText: requestState === "no-match" ? "Not Found" : "Method Not Allowed",
data: null
if (!isValidRequestMethod(request)) {
matches = null;
appState.trackCatchBoundaries = false;
appState.catch = {
data: null,
status: 405,
statusText: "Method Not Allowed"
};
} else if (isActionRequest(request)) {
let actionMatch = matches[matches.length - 1];
} else if (!matches) {
appState.trackCatchBoundaries = false;
appState.catch = {
data: null,
status: 404,
statusText: "Not Found"
};
}
if (!isIndexRequestUrl(url) && actionMatch.route.id.endsWith("/index")) {
actionMatch = matches[matches.length - 2];
}
let actionStatus;
let actionData;
let actionMatch;
let actionResponse;
actionRouteId = actionMatch.route.id;
if (matches && isActionRequest(request)) {
actionMatch = getActionRequestMatch(url, matches);
try {
let clonedRequest = stripIndexParam(stripDataParam(request));
actionResponse = await data.callRouteAction(build, actionMatch.route.id, clonedRequest, loadContext, actionMatch.params);
actionResponse = await data.callRouteAction({
loadContext,
match: actionMatch,
request: request
});
if (data.isRedirectResponse(actionResponse)) {
if (responses.isRedirectResponse(actionResponse)) {
return actionResponse;
}
actionStatus = {
status: actionResponse.status,
statusText: actionResponse.statusText
};
if (responses.isCatchResponse(actionResponse)) {
appState.catchBoundaryRouteId = getDeepestRouteIdWithBoundary(matches, "CatchBoundary");
appState.trackCatchBoundaries = false;
appState.catch = { ...actionStatus,
data: await data.extractData(actionResponse)
};
} else {
actionData = {
[actionMatch.route.id]: await data.extractData(actionResponse)
};
}
} catch (error) {
var _platform$formatServe3;
appState.loaderBoundaryRouteId = getDeepestRouteIdWithBoundary(matches, "ErrorBoundary");
appState.trackBoundaries = false;
appState.error = await errors.serializeError(error);
let formattedError = (await ((_platform$formatServe3 = platform.formatServerError) === null || _platform$formatServe3 === void 0 ? void 0 : _platform$formatServe3.call(platform, error))) || error;
responseState = "error";
let withBoundaries = getMatchesUpToDeepestBoundary(matches, "ErrorBoundary");
componentDidCatchEmulator.loaderBoundaryRouteId = withBoundaries[withBoundaries.length - 1].route.id;
componentDidCatchEmulator.error = await errors.serializeError(formattedError);
if (serverMode !== mode.ServerMode.Test) {
console.error(`There was an error running the action for route ${actionMatch.route.id}`);
}
}
}
if (actionResponse && data.isCatchResponse(actionResponse)) {
responseState = "caught";
let withBoundaries = getMatchesUpToDeepestBoundary(matches, "CatchBoundary");
componentDidCatchEmulator.trackCatchBoundaries = false;
componentDidCatchEmulator.catchBoundaryRouteId = withBoundaries[withBoundaries.length - 1].route.id;
componentDidCatchEmulator.catch = {
status: actionResponse.status,
statusText: actionResponse.statusText,
data: await data.extractData(actionResponse.clone())
};
} // If we did not match a route, there is no need to call any loaders
let routeModules = entry.createEntryRouteModules(build.routes);
let matchesToLoad = matches || [];
if (appState.catch) {
matchesToLoad = getMatchesUpToDeepestBoundary( // get rid of the action, we don't want to call it's loader either
// because we'll be rendering the catch boundary, if you can get access
// to the loader data in the catch boundary then how the heck is it
// supposed to deal with thrown responses?
matchesToLoad.slice(0, -1), "CatchBoundary");
} else if (appState.error) {
matchesToLoad = getMatchesUpToDeepestBoundary( // get rid of the action, we don't want to call it's loader either
// because we'll be rendering the error boundary, if you can get access
// to the loader data in the error boundary then how the heck is it
// supposed to deal with errors in the loader, too?
matchesToLoad.slice(0, -1), "ErrorBoundary");
}
let matchesToLoad = requestState !== "ok" ? [] : matches;
let routeLoaderResults = await Promise.allSettled(matchesToLoad.map(match => match.route.module.loader ? data.callRouteLoader({
loadContext,
match,
request
}) : Promise.resolve(undefined))); // Store the state of the action. We will use this to determine later
// what catch or error boundary should be rendered under cases where
// actions don't throw but loaders do, actions throw and parent loaders
// also throw, etc.
switch (responseState) {
case "caught":
matchesToLoad = getMatchesUpToDeepestBoundary( // get rid of the action, we don't want to call it's loader either
// because we'll be rendering the catch boundary, if you can get access
// to the loader data in the catch boundary then how the heck is it
// supposed to deal with thrown responses?
matches.slice(0, -1), "CatchBoundary");
break;
let actionCatch = appState.catch;
let actionError = appState.error;
let actionCatchBoundaryRouteId = appState.catchBoundaryRouteId;
let actionLoaderBoundaryRouteId = appState.loaderBoundaryRouteId; // Reset the app error and catch state to propogate the loader states
// from the results into the app state.
case "error":
matchesToLoad = getMatchesUpToDeepestBoundary( // get rid of the action, we don't want to call it's loader either
// because we'll be rendering the error boundary, if you can get access
// to the loader data in the error boundary then how the heck is it
// supposed to deal with errors in the loader, too?
matches.slice(0, -1), "ErrorBoundary");
break;
} // Run all data loaders in parallel. Await them in series below. Note: This
// code is a little weird due to the way unhandled promise rejections are
// handled in node. We use a .catch() handler on each promise to avoid the
// warning, then handle errors manually afterwards.
appState.catch = undefined;
appState.error = undefined;
let routeLoaderResponses = [];
let loaderStatusCodes = [];
let routeData = {};
for (let index = 0; index < matchesToLoad.length; index++) {
let match = matchesToLoad[index];
let result = routeLoaderResults[index];
let error = result.status === "rejected" ? result.reason : undefined;
let response = result.status === "fulfilled" ? result.value : undefined;
let isRedirect = response ? responses.isRedirectResponse(response) : false;
let isCatch = response ? responses.isCatchResponse(response) : false; // If a parent loader has already caught or error'd, bail because
// we don't need any more child data.
let routeLoaderPromises = matchesToLoad.map(match => data.loadRouteData(build, match.route.id, stripIndexParam(stripDataParam(request.clone())), loadContext, match.params).catch(error => error));
let routeLoaderResults = await Promise.all(routeLoaderPromises);
if (appState.catch || appState.error) {
break;
} // If there is a response and it's a redirect, do it unless there
// is an action error or catch state, those action boundary states
// take precedence over loader sates, this means if a loader redirects
// after an action catches or errors we won't follow it, and instead
// render the boundary caused by the action.
for (let [index, response] of routeLoaderResults.entries()) {
let route = matches[index].route;
let routeModule = build.routes[route.id].module; // Rare case where an action throws an error, and then when we try to render
// the action's page to tell the user about the the error, a loader above
// the action route *also* threw an error or tried to redirect!
//
// Instead of rendering the loader error or redirecting like usual, we
// ignore the loader error or redirect because the action error was first
// and is higher priority to surface. Perhaps the action error is the
// reason the loader blows up now! It happened first and is more important
// to address.
//
// We just give up and move on with rendering the error as deeply as we can,
// which is the previous iteration of this loop
if (responseState === "error" && (response instanceof Error || data.isRedirectResponse(response)) || responseState === "caught" && data.isCatchResponse(response)) {
break;
}
if (!actionCatch && !actionError && response && isRedirect) {
return response;
} // Track the boundary ID's for the loaders
if (componentDidCatchEmulator.catch || componentDidCatchEmulator.error) {
continue;
}
if (routeModule.CatchBoundary) {
componentDidCatchEmulator.catchBoundaryRouteId = route.id;
if (match.route.module.CatchBoundary) {
appState.catchBoundaryRouteId = match.route.id;
}
if (routeModule.ErrorBoundary) {
componentDidCatchEmulator.loaderBoundaryRouteId = route.id;
if (match.route.module.ErrorBoundary) {
appState.loaderBoundaryRouteId = match.route.id;
}
if (response instanceof Error) {
var _platform$formatServe4;
if (error) {
loaderStatusCodes.push(500);
appState.trackBoundaries = false;
appState.error = await errors.serializeError(error);
if (serverMode !== mode.ServerMode.Test) {
console.error(`There was an error running the data loader for route ${route.id}`);
console.error(`There was an error running the data loader for route ${match.route.id}`);
}
let formattedError = (await ((_platform$formatServe4 = platform.formatServerError) === null || _platform$formatServe4 === void 0 ? void 0 : _platform$formatServe4.call(platform, response))) || response;
componentDidCatchEmulator.error = await errors.serializeError(formattedError);
routeLoaderResults[index] = responses.json(null, {
status: 500
});
} else if (data.isRedirectResponse(response)) {
return response;
} else if (data.isCatchResponse(response)) {
componentDidCatchEmulator.trackCatchBoundaries = false;
componentDidCatchEmulator.catch = {
status: response.status,
statusText: response.statusText,
data: await data.extractData(response.clone())
};
routeLoaderResults[index] = responses.json(null, {
status: response.status
});
break;
} else if (response) {
routeLoaderResponses.push(response);
loaderStatusCodes.push(response.status);
if (isCatch) {
// If it's a catch response, store it in app state, and bail
appState.trackCatchBoundaries = false;
appState.catch = {
data: await data.extractData(response),
status: response.status,
statusText: response.statusText
};
break;
} else {
// Extract and store the loader data
routeData[match.route.id] = await data.extractData(response);
}
}
} // We already filtered out all Errors, so these are all Responses.
} // If there was not a loader catch or error state triggered reset the
// boundaries as they are probably deeper in the tree if the action
// initially triggered a boundary as that match would not exist in the
// matches to load.
let routeLoaderResponses = routeLoaderResults; // Handle responses with a non-200 status code. The first loader with a
if (!appState.catch) {
appState.catchBoundaryRouteId = actionCatchBoundaryRouteId;
}
if (!appState.error) {
appState.loaderBoundaryRouteId = actionLoaderBoundaryRouteId;
} // If there was an action error or catch, we will reset the state to the
// initial values, otherwise we will use whatever came out of the loaders.
appState.catch = actionCatch || appState.catch;
appState.error = actionError || appState.error;
let renderableMatches = getRenderableMatches(matches, appState);
if (!renderableMatches) {
renderableMatches = [];
let root = routes[0];
if (root !== null && root !== void 0 && root.module.CatchBoundary) {
appState.catchBoundaryRouteId = "root";
renderableMatches.push({
params: {},
pathname: "",
route: routes[0]
});
}
} // Handle responses with a non-200 status code. The first loader with a
// non-200 status code determines the status code for the whole response.
let notOkResponse = [actionResponse, ...routeLoaderResponses].find(response => response && response.status !== 200);
let statusCode = requestState === "no-match" ? 404 : requestState === "invalid-request" ? 405 : responseState === "error" ? 500 : notOkResponse ? notOkResponse.status : 200;
let renderableMatches = getRenderableMatches(matches, componentDidCatchEmulator);
let serverEntryModule = build.entry.module;
let headers$1 = headers.getDocumentHeaders(build, renderableMatches, routeLoaderResponses, actionResponse);
let notOkResponse = actionStatus && actionStatus.status !== 200 ? actionStatus.status : loaderStatusCodes.find(status => status !== 200);
let responseStatusCode = appState.error ? 500 : typeof notOkResponse === "number" ? notOkResponse : appState.catch ? appState.catch.status : 200;
let responseHeaders = headers.getDocumentHeaders(build, renderableMatches, routeLoaderResponses, actionResponse);
let entryMatches = entry.createEntryMatches(renderableMatches, build.assets.routes);
let routeData$1 = await routeData.createRouteData(renderableMatches, routeLoaderResponses);
let actionData = actionResponse && actionRouteId ? {
[actionRouteId]: await routeData.createActionData(actionResponse)
} : undefined;
let routeModules = entry.createEntryRouteModules(build.routes);
let serverHandoff$1 = {
actionData,
appState: appState,
matches: entryMatches,
componentDidCatchEmulator,
routeData: routeData$1,
actionData
routeData
};

@@ -386,16 +405,8 @@ let entryContext = { ...serverHandoff$1,

};
let response;
let handleDocumentRequest = build.entry.module.default;
try {
response = await serverEntryModule.default(request, statusCode, headers$1, entryContext);
return await handleDocumentRequest(request.clone(), responseStatusCode, responseHeaders, entryContext);
} catch (error) {
var _platform$formatServe5;
let formattedError = (await ((_platform$formatServe5 = platform.formatServerError) === null || _platform$formatServe5 === void 0 ? void 0 : _platform$formatServe5.call(platform, error))) || error;
if (serverMode !== mode.ServerMode.Test) {
console.error(formattedError);
}
statusCode = 500; // Go again, this time with the componentDidCatch emulation. As it rendered
responseStatusCode = 500; // Go again, this time with the componentDidCatch emulation. As it rendered
// last time we mutated `componentDidCatch.routeId` for the last rendered

@@ -407,19 +418,21 @@ // route, now we know where to render the error boundary (feels a little

componentDidCatchEmulator.trackBoundaries = false;
componentDidCatchEmulator.error = await errors.serializeError(formattedError);
appState.trackBoundaries = false;
appState.error = await errors.serializeError(error);
entryContext.serverHandoffString = serverHandoff.createServerHandoffString(serverHandoff$1);
try {
response = await serverEntryModule.default(request, statusCode, headers$1, entryContext);
return await handleDocumentRequest(request.clone(), responseStatusCode, responseHeaders, entryContext);
} catch (error) {
var _platform$formatServe6;
if (serverMode !== mode.ServerMode.Test) {
console.error(error);
}
let formattedError = (await ((_platform$formatServe6 = platform.formatServerError) === null || _platform$formatServe6 === void 0 ? void 0 : _platform$formatServe6.call(platform, error))) || error;
let message = "Unexpected Server Error";
if (serverMode !== mode.ServerMode.Test) {
console.error(formattedError);
if (serverMode === mode.ServerMode.Development) {
message += `\n\n${String(error)}`;
} // Good grief folks, get your act together 😂!
response = new Response(`Unexpected Server Error\n\n${formattedError.message}`, {
return new Response(message, {
status: 500,

@@ -432,12 +445,63 @@ headers: {

}
}
return response;
async function handleResourceRequest({
loadContext,
matches,
request,
serverMode
}) {
let match = matches.slice(-1)[0];
try {
if (isActionRequest(request)) {
return await data.callRouteAction({
match,
loadContext,
request
});
} else {
return await data.callRouteLoader({
match,
loadContext,
request
});
}
} catch (error) {
if (serverMode !== mode.ServerMode.Test) {
console.error(error);
}
let message = "Unexpected Server Error";
if (serverMode === mode.ServerMode.Development) {
message += `\n\n${String(error)}`;
} // Good grief folks, get your act together 😂!
return new Response(message, {
status: 500,
headers: {
"Content-Type": "text/plain"
}
});
}
}
function jsonError(error, status = 403) {
return responses.json({
error
}, {
status
});
function getRequestType(url, matches) {
if (url.searchParams.has("_data")) {
return "data";
}
if (!matches) {
return "document";
}
let match = matches.slice(-1)[0];
if (!match.route.module.default) {
return "resource";
}
return "document";
}

@@ -450,2 +514,6 @@

function isHeadRequest(request) {
return request.method.toLowerCase() === "head";
}
function isValidRequestMethod(request) {

@@ -455,10 +523,11 @@ return request.method.toLowerCase() === "get" || isHeadRequest(request) || isActionRequest(request);

function isHeadRequest(request) {
return request.method.toLowerCase() === "head";
async function errorBoundaryError(error, status) {
return responses.json(await errors.serializeError(error), {
status,
headers: {
"X-Remix-Error": "yes"
}
});
}
function isDataRequest(request) {
return new URL(request.url).searchParams.has("_data");
}
function isIndexRequestUrl(url) {

@@ -476,28 +545,17 @@ let indexRequest = false;

function stripIndexParam(request) {
let url = new URL(request.url);
let indexValues = url.searchParams.getAll("index");
url.searchParams.delete("index");
let indexValuesToKeep = [];
function getActionRequestMatch(url, matches) {
let match = matches.slice(-1)[0];
for (let indexValue of indexValues) {
if (indexValue) {
indexValuesToKeep.push(indexValue);
}
if (!isIndexRequestUrl(url) && match.route.id.endsWith("/index")) {
return matches.slice(-2)[0];
}
for (let toKeep of indexValuesToKeep) {
url.searchParams.append("index", toKeep);
}
return match;
}
return new Request(url.toString(), request);
function getDeepestRouteIdWithBoundary(matches, key) {
let matched = getMatchesUpToDeepestBoundary(matches, key).slice(-1)[0];
return matched ? matched.route.id : null;
}
function stripDataParam(request) {
let url = new URL(request.url);
url.searchParams.delete("_data");
return new Request(url.toString(), request);
} // TODO: update to use key for lookup
function getMatchesUpToDeepestBoundary(matches, key) {

@@ -521,5 +579,9 @@ let deepestBoundaryIndex = -1;

function getRenderableMatches(matches, componentDidCatchEmulator) {
// no error, no worries
if (!componentDidCatchEmulator.catch && !componentDidCatchEmulator.error) {
function getRenderableMatches(matches, appState) {
if (!matches) {
return null;
} // no error, no worries
if (!appState.catch && !appState.error) {
return matches;

@@ -532,3 +594,3 @@ }

if (componentDidCatchEmulator.renderBoundaryRouteId === id || componentDidCatchEmulator.loaderBoundaryRouteId === id || componentDidCatchEmulator.catchBoundaryRouteId === id) {
if (appState.renderBoundaryRouteId === id || appState.loaderBoundaryRouteId === id || appState.catchBoundaryRouteId === id) {
lastRenderableIndex = index;

@@ -535,0 +597,0 @@ }

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

}) {
let cookie = cookies.isCookie(cookieArg) ? cookieArg : cookies.createCookie(cookieArg && cookieArg.name || "__session", cookieArg);
let cookie = cookies.isCookie(cookieArg) ? cookieArg : cookies.createCookie((cookieArg === null || cookieArg === void 0 ? void 0 : cookieArg.name) || "__session", cookieArg);
warnOnceAboutSigningSessionCookie(cookie);

@@ -134,6 +134,3 @@ return {

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.`
/* TODO: Update link with new docs. See https://remix.run/cookies#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/docs/en/v1/api/remix#signing-cookies ` + `for more information.`);
}

@@ -140,0 +137,0 @@

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

} = {}) {
let cookie = cookies.isCookie(cookieArg) ? cookieArg : cookies.createCookie(cookieArg && cookieArg.name || "__session", cookieArg);
let cookie = cookies.isCookie(cookieArg) ? cookieArg : cookies.createCookie((cookieArg === null || cookieArg === void 0 ? void 0 : cookieArg.name) || "__session", cookieArg);
sessions.warnOnceAboutSigningSessionCookie(cookie);

@@ -33,0 +33,0 @@ return {

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

/**
* @remix-run/server-runtime v1.0.6
* @remix-run/server-runtime v1.1.0
*

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

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