@remix-run/server-runtime
Advanced tools
Comparing version 1.0.6 to 1.1.0
/** | ||
* @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>; |
92
data.js
/** | ||
* @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. |
658
server.js
/** | ||
* @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. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
111250
61
3125
1
23
274
Updatedreact-router-dom@^6.2.1