@remix-run/server-runtime
Advanced tools
Comparing version 0.0.0-nightly-eccc180-20230707 to 0.0.0-nightly-ed3a99a49-20241012
@@ -1,2 +0,2 @@ | ||
import type { DataFunctionArgs } from "./routeModules"; | ||
import type { ActionFunctionArgs, LoaderFunctionArgs } from "./routeModules"; | ||
import type { AssetsManifest, EntryContext, FutureConfig } from "./entry"; | ||
@@ -9,2 +9,3 @@ import type { ServerRouteManifest } from "./routes"; | ||
export interface ServerBuild { | ||
mode: string; | ||
entry: { | ||
@@ -15,5 +16,7 @@ module: ServerEntryModule; | ||
assets: AssetsManifest; | ||
basename?: string; | ||
publicPath: string; | ||
assetsBuildDirectory: string; | ||
future: FutureConfig; | ||
isSpaMode: boolean; | ||
} | ||
@@ -24,6 +27,6 @@ export interface HandleDocumentRequestFunction { | ||
export interface HandleDataRequestFunction { | ||
(response: Response, args: DataFunctionArgs): Promise<Response> | Response; | ||
(response: Response, args: LoaderFunctionArgs | ActionFunctionArgs): Promise<Response> | Response; | ||
} | ||
export interface HandleErrorFunction { | ||
(error: unknown, args: DataFunctionArgs): void; | ||
(error: unknown, args: LoaderFunctionArgs | ActionFunctionArgs): void; | ||
} | ||
@@ -38,2 +41,3 @@ /** | ||
handleError?: HandleErrorFunction; | ||
streamTimeout?: number; | ||
} |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -1,5 +0,7 @@ | ||
import type { ActionFunction, DataFunctionArgs, LoaderFunction } from "./routeModules"; | ||
import type { ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs } from "./routeModules"; | ||
/** | ||
* 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 | ||
*/ | ||
@@ -11,19 +13,19 @@ export interface AppLoadContext { | ||
* Data for a route that was returned from a `loader()`. | ||
* | ||
* Note: This moves to unknown in ReactRouter and eventually likely in Remix | ||
*/ | ||
export type AppData = any; | ||
export declare function callRouteActionRR({ loadContext, action, params, request, routeId, }: { | ||
export type AppData = unknown; | ||
export declare function callRouteAction({ loadContext, action, params, request, routeId, singleFetch, }: { | ||
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; | ||
}): Promise<{} | Response | null>; | ||
export declare function callRouteLoader({ loadContext, loader, params, request, routeId, singleFetch, }: { | ||
request: Request; | ||
loader: LoaderFunction; | ||
params: DataFunctionArgs["params"]; | ||
params: LoaderFunctionArgs["params"]; | ||
loadContext: AppLoadContext; | ||
routeId: string; | ||
}): Promise<import("@remix-run/router").UNSAFE_DeferredData | Response>; | ||
singleFetch: boolean; | ||
}): Promise<{} | Response | null>; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -19,3 +19,5 @@ * Copyright (c) Remix Software Inc. | ||
* 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 | ||
*/ | ||
@@ -25,7 +27,5 @@ | ||
* Data for a route that was returned from a `loader()`. | ||
* | ||
* Note: This moves to unknown in ReactRouter and eventually likely in Remix | ||
*/ | ||
async function callRouteActionRR({ | ||
async function callRouteAction({ | ||
loadContext, | ||
@@ -35,6 +35,7 @@ action, | ||
request, | ||
routeId | ||
routeId, | ||
singleFetch | ||
}) { | ||
let result = await action({ | ||
request: stripDataParam(stripIndexParam(request)), | ||
request: singleFetch ? stripRoutesParam(stripIndexParam(request)) : stripDataParam(stripIndexParam(request)), | ||
context: loadContext, | ||
@@ -46,5 +47,10 @@ params | ||
} | ||
// 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, | ||
@@ -54,6 +60,7 @@ loader, | ||
request, | ||
routeId | ||
routeId, | ||
singleFetch | ||
}) { | ||
let result = await loader({ | ||
request: stripDataParam(stripIndexParam(request)), | ||
request: singleFetch ? stripRoutesParam(stripIndexParam(request)) : stripDataParam(stripIndexParam(request)), | ||
context: loadContext, | ||
@@ -71,2 +78,7 @@ params | ||
} | ||
// Allow naked object returns when single fetch is enabled | ||
if (singleFetch) { | ||
return result; | ||
} | ||
return responses.isResponse(result) ? result : responses.json(result); | ||
@@ -93,3 +105,12 @@ } | ||
} | ||
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); | ||
} | ||
@@ -99,6 +120,29 @@ 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); | ||
} | ||
function stripRoutesParam(request) { | ||
let url = new URL(request.url); | ||
url.searchParams.delete("_routes"); | ||
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; |
import type { ServerBuild } from "./build"; | ||
export declare function broadcastDevReady(build: ServerBuild, origin?: string): Promise<void>; | ||
export declare function logDevReady(build: ServerBuild): void; | ||
type DevServerHooks = { | ||
getCriticalCss?: (build: ServerBuild, pathname: string) => Promise<string | undefined>; | ||
processRequestError?: (error: unknown) => void; | ||
}; | ||
export declare function setDevServerHooks(devServerHooks: DevServerHooks): void; | ||
export declare function getDevServerHooks(): DevServerHooks | undefined; | ||
export {}; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -16,3 +16,3 @@ * Copyright (c) Remix Software Inc. | ||
async function broadcastDevReady(build, origin) { | ||
origin ?? (origin = process.env.REMIX_DEV_HTTP_ORIGIN); | ||
origin ??= process.env.REMIX_DEV_ORIGIN; | ||
if (!origin) throw Error("Dev server origin not set"); | ||
@@ -41,4 +41,15 @@ let url = new URL(origin); | ||
} | ||
const globalDevServerHooksKey = "__remix_devServerHooks"; | ||
function setDevServerHooks(devServerHooks) { | ||
// @ts-expect-error | ||
globalThis[globalDevServerHooksKey] = devServerHooks; | ||
} | ||
function getDevServerHooks() { | ||
// @ts-expect-error | ||
return globalThis[globalDevServerHooksKey]; | ||
} | ||
exports.broadcastDevReady = broadcastDevReady; | ||
exports.getDevServerHooks = getDevServerHooks; | ||
exports.logDevReady = logDevReady; | ||
exports.setDevServerHooks = setDevServerHooks; |
import type { StaticHandlerContext } from "@remix-run/router"; | ||
import type { SerializedError } from "./errors"; | ||
import type { RouteManifest, ServerRouteManifest, EntryRoute } from "./routes"; | ||
@@ -7,23 +8,26 @@ import type { RouteModules, EntryRouteModule } from "./routeModules"; | ||
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; | ||
} | ||
type Dev = { | ||
port?: number; | ||
appServerPort?: number; | ||
remixRequestHandlerPath?: string; | ||
rebuildPollIntervalMs?: number; | ||
}; | ||
export interface FutureConfig { | ||
v2_dev: boolean | Dev; | ||
/** @deprecated Use the `postcss` config option instead */ | ||
unstable_postcss: boolean; | ||
/** @deprecated Use the `tailwind` config option instead */ | ||
unstable_tailwind: boolean; | ||
v2_errorBoundary: boolean; | ||
v2_headers: boolean; | ||
v2_meta: boolean; | ||
v2_normalizeFormMethod: boolean; | ||
v2_routeConvention: boolean; | ||
v3_fetcherPersist: boolean; | ||
v3_relativeSplatPath: boolean; | ||
v3_throwAbortReason: boolean; | ||
v3_lazyRouteDiscovery: boolean; | ||
v3_singleFetch: boolean; | ||
} | ||
@@ -41,2 +45,1 @@ export interface AssetsManifest { | ||
export declare function createEntryRouteModules(manifest: ServerRouteManifest): RouteModules<EntryRouteModule>; | ||
export {}; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -44,11 +44,2 @@ import type { StaticHandlerContext } from "@remix-run/router"; | ||
*/ | ||
/** | ||
* @deprecated in favor of the `ErrorResponse` class in React Router. Please | ||
* enable the `future.v2_errorBoundary` flag to ease your migration to Remix v2. | ||
*/ | ||
export interface ThrownResponse<T = any> { | ||
status: number; | ||
statusText: string; | ||
data: T; | ||
} | ||
export declare function sanitizeError<T = unknown>(error: T, serverMode: ServerMode): T | Error; | ||
@@ -55,0 +46,0 @@ export declare function sanitizeErrors(errors: NonNullable<StaticHandlerContext["errors"]>, serverMode: ServerMode): {}; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -60,6 +60,2 @@ * Copyright (c) Remix Software Inc. | ||
/** | ||
* @deprecated in favor of the `ErrorResponse` class in React Router. Please | ||
* enable the `future.v2_errorBoundary` flag to ease your migration to Remix v2. | ||
*/ | ||
function sanitizeError(error, serverMode) { | ||
@@ -83,2 +79,3 @@ if (error instanceof Error && serverMode !== mode.ServerMode.Development) { | ||
// https://github.com/microsoft/TypeScript/issues/15300 | ||
function serializeError(error, serverMode) { | ||
@@ -85,0 +82,0 @@ let sanitized = sanitizeError(error, serverMode); |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -15,3 +15,5 @@ * Copyright (c) Remix Software Inc. | ||
* 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 | ||
*/ | ||
@@ -21,7 +23,5 @@ | ||
* Data for a route that was returned from a `loader()`. | ||
* | ||
* Note: This moves to unknown in ReactRouter and eventually likely in Remix | ||
*/ | ||
async function callRouteActionRR({ | ||
async function callRouteAction({ | ||
loadContext, | ||
@@ -31,6 +31,7 @@ action, | ||
request, | ||
routeId | ||
routeId, | ||
singleFetch | ||
}) { | ||
let result = await action({ | ||
request: stripDataParam(stripIndexParam(request)), | ||
request: singleFetch ? stripRoutesParam(stripIndexParam(request)) : stripDataParam(stripIndexParam(request)), | ||
context: loadContext, | ||
@@ -42,5 +43,10 @@ params | ||
} | ||
// 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, | ||
@@ -50,6 +56,7 @@ loader, | ||
request, | ||
routeId | ||
routeId, | ||
singleFetch | ||
}) { | ||
let result = await loader({ | ||
request: stripDataParam(stripIndexParam(request)), | ||
request: singleFetch ? stripRoutesParam(stripIndexParam(request)) : stripDataParam(stripIndexParam(request)), | ||
context: loadContext, | ||
@@ -67,2 +74,7 @@ params | ||
} | ||
// Allow naked object returns when single fetch is enabled | ||
if (singleFetch) { | ||
return result; | ||
} | ||
return isResponse(result) ? result : json(result); | ||
@@ -89,3 +101,12 @@ } | ||
} | ||
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); | ||
} | ||
@@ -95,5 +116,28 @@ 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); | ||
} | ||
function stripRoutesParam(request) { | ||
let url = new URL(request.url); | ||
url.searchParams.delete("_routes"); | ||
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-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -12,3 +12,3 @@ * Copyright (c) Remix Software Inc. | ||
async function broadcastDevReady(build, origin) { | ||
origin ?? (origin = process.env.REMIX_DEV_HTTP_ORIGIN); | ||
origin ??= process.env.REMIX_DEV_ORIGIN; | ||
if (!origin) throw Error("Dev server origin not set"); | ||
@@ -37,3 +37,12 @@ let url = new URL(origin); | ||
} | ||
const globalDevServerHooksKey = "__remix_devServerHooks"; | ||
function setDevServerHooks(devServerHooks) { | ||
// @ts-expect-error | ||
globalThis[globalDevServerHooksKey] = devServerHooks; | ||
} | ||
function getDevServerHooks() { | ||
// @ts-expect-error | ||
return globalThis[globalDevServerHooksKey]; | ||
} | ||
export { broadcastDevReady, logDevReady }; | ||
export { broadcastDevReady, getDevServerHooks, logDevReady, setDevServerHooks }; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -56,6 +56,2 @@ * Copyright (c) Remix Software Inc. | ||
/** | ||
* @deprecated in favor of the `ErrorResponse` class in React Router. Please | ||
* enable the `future.v2_errorBoundary` flag to ease your migration to Remix v2. | ||
*/ | ||
function sanitizeError(error, serverMode) { | ||
@@ -79,2 +75,3 @@ if (error instanceof Error && serverMode !== ServerMode.Development) { | ||
// https://github.com/microsoft/TypeScript/issues/15300 | ||
function serializeError(error, serverMode) { | ||
@@ -81,0 +78,0 @@ let sanitized = sanitizeError(error, serverMode); |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -13,2 +13,3 @@ * Copyright (c) Remix Software Inc. | ||
// @ts-ignore | ||
function composeUploadHandlers(...handlers) { | ||
@@ -15,0 +16,0 @@ return async part => { |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -13,3 +13,3 @@ * Copyright (c) Remix Software Inc. | ||
function getDocumentHeadersRR(build, context) { | ||
function getDocumentHeaders(build, context) { | ||
let boundaryIdx = context.errors ? context.matches.findIndex(m => context.errors[m.route.id]) : -1; | ||
@@ -53,5 +53,4 @@ let matches = boundaryIdx >= 0 ? context.matches.slice(0, boundaryIdx + 1) : context.matches; | ||
// When the future flag is enabled, use the parent headers for any route | ||
// that doesn't have a `headers` export | ||
if (routeModule.headers == null && build.future.v2_headers) { | ||
// Use the parent headers for any route without a `headers` export | ||
if (routeModule.headers == null) { | ||
let headers = new Headers(parentHeaders); | ||
@@ -93,2 +92,2 @@ if (includeErrorCookies) { | ||
export { getDocumentHeadersRR }; | ||
export { getDocumentHeaders }; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -13,3 +13,4 @@ * Copyright (c) Remix Software Inc. | ||
export { composeUploadHandlers as unstable_composeUploadHandlers, parseMultipartFormData as unstable_parseMultipartFormData } from './formData.js'; | ||
export { defer, json, redirect } from './responses.js'; | ||
export { defer, json, redirect, redirectDocument, replace } from './responses.js'; | ||
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, data } from './single-fetch.js'; | ||
export { createRequestHandler } from './server.js'; | ||
@@ -21,2 +22,2 @@ export { createSession, createSessionStorageFactory, isSession } from './sessions.js'; | ||
export { MaxPartSizeExceededError } from './upload/errors.js'; | ||
export { broadcastDevReady, logDevReady } from './dev.js'; | ||
export { broadcastDevReady, logDevReady, setDevServerHooks as unstable_setDevServerHooks } from './dev.js'; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -11,3 +11,3 @@ * Copyright (c) Remix Software Inc. | ||
*/ | ||
import { json as json$1, defer as defer$1, redirect as redirect$1 } from '@remix-run/router'; | ||
import { json as json$1, defer as defer$1, redirect as redirect$1, replace as replace$1, redirectDocument as redirectDocument$1 } from '@remix-run/router'; | ||
import { serializeError } from './errors.js'; | ||
@@ -17,2 +17,3 @@ | ||
// interfaces must conform to the types they extend | ||
/** | ||
@@ -45,2 +46,23 @@ * This is a shortcut for creating `application/json` responses. Converts `data` | ||
}; | ||
/** | ||
* A redirect response. Sets the status code and the `Location` header. | ||
* Defaults to "302 Found". | ||
* | ||
* @see https://remix.run/utils/redirect | ||
*/ | ||
const replace = (url, init = 302) => { | ||
return replace$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) { | ||
@@ -114,2 +136,2 @@ let deferred = value; | ||
export { createDeferredReadableStream, defer, isDeferredData, isRedirectResponse, isRedirectStatusCode, isResponse, json, redirect }; | ||
export { createDeferredReadableStream, defer, isDeferredData, isRedirectResponse, isRedirectStatusCode, isResponse, json, redirect, redirectDocument, replace }; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -13,4 +13,4 @@ * Copyright (c) Remix Software Inc. | ||
function matchServerRoutes(routes, pathname) { | ||
let matches = matchRoutes(routes, pathname); | ||
function matchServerRoutes(routes, pathname, basename) { | ||
let matches = matchRoutes(routes, pathname, basename); | ||
if (!matches) return null; | ||
@@ -17,0 +17,0 @@ return matches.map(match => ({ |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -11,3 +11,3 @@ * Copyright (c) Remix Software Inc. | ||
*/ | ||
import { callRouteLoaderRR, callRouteActionRR } from './data.js'; | ||
import { callRouteLoader, callRouteAction } from './data.js'; | ||
@@ -43,9 +43,11 @@ // NOTE: make sure to change the Route in remix-react if you change this | ||
return (routesByParentId[parentId] || []).map(route => { | ||
let hasErrorBoundary = future.v2_errorBoundary === true ? route.id === "root" || route.module.ErrorBoundary != null : route.id === "root" || route.module.CatchBoundary != null || route.module.ErrorBoundary != null; | ||
let commonRoute = { | ||
// Always include root due to default boundaries | ||
hasErrorBoundary, | ||
hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null, | ||
id: route.id, | ||
path: route.path, | ||
loader: route.module.loader ? args => callRouteLoaderRR({ | ||
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, | ||
@@ -55,5 +57,6 @@ params: args.params, | ||
loader: route.module.loader, | ||
routeId: route.id | ||
routeId: route.id, | ||
singleFetch: future.v3_singleFetch === true | ||
}) : undefined, | ||
action: route.module.action ? args => callRouteActionRR({ | ||
action: route.module.action ? (args, dataStrategyCtx) => callRouteAction({ | ||
request: args.request, | ||
@@ -63,3 +66,4 @@ params: args.params, | ||
action: route.module.action, | ||
routeId: route.id | ||
routeId: route.id, | ||
singleFetch: future.v3_singleFetch === true | ||
}) : undefined, | ||
@@ -66,0 +70,0 @@ handle: route.module.handle |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -11,18 +11,28 @@ * Copyright (c) Remix Software Inc. | ||
*/ | ||
import { createStaticHandler, UNSAFE_DEFERRED_SYMBOL, isRouteErrorResponse, json, getStaticContextFromError } from '@remix-run/router'; | ||
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 { getDocumentHeadersRR } from './headers.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, createDeferredReadableStream, isResponse } from './responses.js'; | ||
import { isRedirectResponse, json, createDeferredReadableStream, isResponse } from './responses.js'; | ||
import { createServerHandoffString } from './serverHandoff.js'; | ||
import { getDevServerHooks } from './dev.js'; | ||
import { getSingleFetchRedirect, encodeViaTurboStream, SINGLE_FETCH_REDIRECT_STATUS, singleFetchAction, singleFetchLoaders, SingleFetchRedirectSymbol } from './single-fetch.js'; | ||
import { resourceRouteJsonWarning } from './deprecations.js'; | ||
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); | ||
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, { | ||
@@ -32,29 +42,118 @@ request | ||
if (serverMode !== ServerMode.Test && !request.signal.aborted) { | ||
console.error(error); | ||
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 | ||
}; | ||
} | ||
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 handleError = error => errorHandler(error, { | ||
context: loadContext, | ||
params: matches && matches.length > 0 ? matches[0].params : {}, | ||
request | ||
}); | ||
let 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 | ||
}); | ||
}; | ||
// Manifest request for fog of war | ||
let manifestUrl = `${_build.basename ?? "/"}/__manifest`.replace(/\/+/g, "/"); | ||
if (url.pathname === manifestUrl) { | ||
try { | ||
let res = await handleManifestRequest(_build, routes, url); | ||
return res; | ||
} catch (e) { | ||
handleError(e); | ||
return new Response("Unknown Server Error", { | ||
status: 500 | ||
}); | ||
} | ||
} | ||
let matches = matchServerRoutes(routes, url.pathname, _build.basename); | ||
if (matches && matches.length > 0) { | ||
Object.assign(params, matches[0].params); | ||
} | ||
let response; | ||
if (url.searchParams.has("_data")) { | ||
if (_build.future.v3_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, loadContext, handleError); | ||
if (build.entry.module.handleDataRequest) { | ||
let match = matches.find(match => match.route.id == routeId); | ||
response = await build.entry.module.handleDataRequest(response, { | ||
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: match ? match.params : {}, | ||
params, | ||
request | ||
}); | ||
if (isRedirectResponse(response)) { | ||
response = createRemixRedirectResponse(response, _build.basename); | ||
} | ||
} | ||
} else if (matches && matches[matches.length - 1].route.module.default == null) { | ||
response = await handleResourceRequestRR(serverMode, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError); | ||
} else if (_build.future.v3_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: singleFetchMatches ? singleFetchMatches[0].params : {}, | ||
request | ||
}); | ||
if (isRedirectResponse(response)) { | ||
let result = getSingleFetchRedirect(response.status, response.headers, _build.basename); | ||
if (request.method === "GET") { | ||
result = { | ||
[SingleFetchRedirectSymbol]: result | ||
}; | ||
} | ||
let headers = new Headers(response.headers); | ||
headers.set("Content-Type", "text/x-script"); | ||
return new Response(encodeViaTurboStream(result, request.signal, _build.entry.module.streamTimeout, serverMode), { | ||
status: SINGLE_FETCH_REDIRECT_STATUS, | ||
headers | ||
}); | ||
} | ||
} | ||
} 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, loadContext, handleError); | ||
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); | ||
} | ||
@@ -71,3 +170,25 @@ if (request.method === "HEAD") { | ||
}; | ||
async function handleDataRequestRR(serverMode, staticHandler, routeId, request, loadContext, handleError) { | ||
async function handleManifestRequest(build, routes, url) { | ||
let patches = {}; | ||
if (url.searchParams.has("p")) { | ||
for (let path of url.searchParams.getAll("p")) { | ||
let matches = matchServerRoutes(routes, path, build.basename); | ||
if (matches) { | ||
for (let match of matches) { | ||
let routeId = match.route.id; | ||
patches[routeId] = build.assets.routes[routeId]; | ||
} | ||
} | ||
} | ||
return json(patches, { | ||
headers: { | ||
"Cache-Control": "public, max-age=31536000, immutable" | ||
} | ||
}); // Override the TypedResponse stuff from json() | ||
} | ||
return new Response("Invalid Request", { | ||
status: 400 | ||
}); | ||
} | ||
async function handleDataRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
try { | ||
@@ -79,16 +200,3 @@ let response = await staticHandler.queryRoute(request, { | ||
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.set("X-Remix-Status", 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 | ||
}); | ||
return createRemixRedirectResponse(response, build.basename); | ||
} | ||
@@ -101,20 +209,25 @@ if (UNSAFE_DEFERRED_SYMBOL in response) { | ||
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 = safelySetHeader(response, "X-Remix-Response", "yes"); | ||
return response; | ||
} catch (error) { | ||
if (isResponse(error)) { | ||
error.headers.set("X-Remix-Catch", "yes"); | ||
return error; | ||
let response = safelySetHeader(error, "X-Remix-Catch", "yes"); | ||
return response; | ||
} | ||
if (isRouteErrorResponse(error)) { | ||
if (error.error) { | ||
handleError(error.error); | ||
} | ||
handleError(error); | ||
return errorResponseToJson(error, serverMode); | ||
} | ||
let errorInstance = error instanceof Error ? error : new Error("Unexpected Server Error"); | ||
let errorInstance = error instanceof Error || error instanceof DOMException ? error : new Error("Unexpected Server Error"); | ||
handleError(errorInstance); | ||
return json(serializeError(errorInstance, serverMode), { | ||
return json$1(serializeError(errorInstance, serverMode), { | ||
status: 500, | ||
@@ -127,37 +240,36 @@ headers: { | ||
} | ||
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; | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
let { | ||
result, | ||
headers, | ||
status | ||
} = request.method !== "GET" ? await singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetchLoaders(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
// 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"); | ||
// 304 responses should not have a body | ||
if (status === 304) { | ||
return new Response(null, { | ||
status: 304, | ||
headers: resultHeaders | ||
}); | ||
} | ||
return findParentBoundary(routes, route.parentId, error); | ||
} | ||
// 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; | ||
// We use a less-descriptive `text/x-script` here instead of something like | ||
// `text/x-turbo` to enable compression when deployed via Cloudflare. See: | ||
// - https://github.com/remix-run/remix/issues/9884 | ||
// - https://developers.cloudflare.com/speed/optimization/content/brotli/content-compression/ | ||
resultHeaders.set("Content-Type", "text/x-script"); | ||
// 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, loadContext, handleError) { | ||
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) { | ||
let context; | ||
@@ -177,6 +289,16 @@ try { | ||
} | ||
let headers = getDocumentHeaders(build, context); | ||
// 304 responses should not have a body or a content-type | ||
if (context.statusCode === 304) { | ||
return new Response(null, { | ||
status: 304, | ||
headers | ||
}); | ||
} | ||
// 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) { | ||
@@ -189,7 +311,10 @@ handleError(err); | ||
// Restructure context.errors to the right Catch/Error Boundary | ||
if (build.future.v2_errorBoundary !== true) { | ||
differentiateCatchVersusErrorBoundaries(build, context); | ||
} | ||
let headers = getDocumentHeadersRR(build, context); | ||
// 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 = { | ||
@@ -199,12 +324,19 @@ manifest: build.assets, | ||
staticHandlerContext: context, | ||
criticalCss, | ||
serverHandoffString: createServerHandoffString({ | ||
url: context.location.pathname, | ||
state: { | ||
loaderData: context.loaderData, | ||
actionData: context.actionData, | ||
errors: serializeErrors(context.errors, serverMode) | ||
}, | ||
future: build.future | ||
basename: build.basename, | ||
criticalCss, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.v3_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
future: build.future | ||
...(build.future.v3_singleFetch ? { | ||
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
} : null), | ||
future: build.future, | ||
isSpaMode: build.isSpaMode, | ||
serializeError: err => serializeError(err, serverMode) | ||
}; | ||
@@ -216,5 +348,17 @@ let handleDocumentRequestFunction = build.entry.module.default; | ||
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, error); | ||
context = getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender); | ||
@@ -226,8 +370,11 @@ // Sanitize errors outside of development environments | ||
// Restructure context.errors to the right Catch/Error Boundary | ||
if (build.future.v2_errorBoundary !== true) { | ||
differentiateCatchVersusErrorBoundaries(build, context); | ||
} | ||
// Update entryContext for the second render pass | ||
// 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 = { | ||
@@ -237,10 +384,13 @@ ...entryContext, | ||
serverHandoffString: createServerHandoffString({ | ||
url: context.location.pathname, | ||
state: { | ||
loaderData: context.loaderData, | ||
actionData: context.actionData, | ||
errors: serializeErrors(context.errors, serverMode) | ||
}, | ||
future: build.future | ||
}) | ||
basename: build.basename, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.v3_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.v3_singleFetch ? { | ||
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
} : null) | ||
}; | ||
@@ -255,3 +405,3 @@ try { | ||
} | ||
async function handleResourceRequestRR(serverMode, staticHandler, routeId, request, loadContext, handleError) { | ||
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
try { | ||
@@ -265,3 +415,12 @@ // Note we keep the routeId here to align with the Remix handling of | ||
}); | ||
// callRouteLoader/callRouteAction always return responses | ||
if (typeof response === "object" && response !== null) { | ||
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.v3_singleFetch && !isResponse(response)) { | ||
console.warn(resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId)); | ||
response = json(response); | ||
} | ||
// 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"); | ||
@@ -273,8 +432,8 @@ return response; | ||
// match identically to what Remix returns | ||
error.headers.set("X-Remix-Catch", "yes"); | ||
return error; | ||
let response = safelySetHeader(error, "X-Remix-Catch", "yes"); | ||
return response; | ||
} | ||
if (isRouteErrorResponse(error)) { | ||
if (error.error) { | ||
handleError(error.error); | ||
if (error) { | ||
handleError(error); | ||
} | ||
@@ -288,3 +447,5 @@ return errorResponseToJson(error, serverMode); | ||
function errorResponseToJson(errorResponse, serverMode) { | ||
return json(serializeError(errorResponse.error || new Error("Unexpected Server Error"), 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, | ||
@@ -311,3 +472,42 @@ statusText: errorResponse.statusText, | ||
} | ||
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 | ||
}); | ||
} | ||
export { createRequestHandler, differentiateCatchVersusErrorBoundaries }; | ||
// Anytime we are setting a header on a `Response` created in the loader/action, | ||
// we have to so it in this manner since in an `undici` world, if the `Response` | ||
// came directly from a `fetch` call, the headers are immutable will throw if | ||
// we try to set a new header. This is a sort of shallow clone of the `Response` | ||
// so we can safely set our own header. | ||
function safelySetHeader(response, name, value) { | ||
let headers = new Headers(response.headers); | ||
headers.set(name, value); | ||
return new Response(response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers, | ||
duplex: response.body ? "half" : undefined | ||
}); | ||
} | ||
export { createRequestHandler }; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -123,6 +123,7 @@ * Copyright (c) Remix Software Inc. | ||
} = 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); | ||
} | ||
@@ -135,2 +136,3 @@ return cookie.serialize(id, options); | ||
...options, | ||
maxAge: undefined, | ||
expires: new Date(0) | ||
@@ -137,0 +139,0 @@ }); |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -44,2 +44,3 @@ * Copyright (c) Remix Software Inc. | ||
...options, | ||
maxAge: undefined, | ||
expires: new Date(0) | ||
@@ -46,0 +47,0 @@ }); |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -23,3 +23,2 @@ * Copyright (c) Remix Software Inc. | ||
} = {}) => { | ||
let uniqueId = 0; | ||
let map = new Map(); | ||
@@ -29,3 +28,3 @@ return createSessionStorage({ | ||
async createData(data, expires) { | ||
let id = (++uniqueId).toString(); | ||
let id = Math.random().toString(36).substring(2, 10); | ||
map.set(id, { | ||
@@ -32,0 +31,0 @@ data, |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -17,2 +17,3 @@ * Copyright (c) Remix Software Inc. | ||
// @ts-ignore | ||
function composeUploadHandlers(...handlers) { | ||
@@ -19,0 +20,0 @@ return async part => { |
import type { StaticHandlerContext } from "@remix-run/router"; | ||
import type { ServerBuild } from "./build"; | ||
export declare function getDocumentHeadersRR(build: ServerBuild, context: StaticHandlerContext): Headers; | ||
export declare function getDocumentHeaders(build: ServerBuild, context: StaticHandlerContext): Headers; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -17,3 +17,3 @@ * Copyright (c) Remix Software Inc. | ||
function getDocumentHeadersRR(build, context) { | ||
function getDocumentHeaders(build, context) { | ||
let boundaryIdx = context.errors ? context.matches.findIndex(m => context.errors[m.route.id]) : -1; | ||
@@ -57,5 +57,4 @@ let matches = boundaryIdx >= 0 ? context.matches.slice(0, boundaryIdx + 1) : context.matches; | ||
// When the future flag is enabled, use the parent headers for any route | ||
// that doesn't have a `headers` export | ||
if (routeModule.headers == null && build.future.v2_headers) { | ||
// Use the parent headers for any route without a `headers` export | ||
if (routeModule.headers == null) { | ||
let headers = new Headers(parentHeaders); | ||
@@ -97,2 +96,2 @@ if (includeErrorCookies) { | ||
exports.getDocumentHeadersRR = getDocumentHeadersRR; | ||
exports.getDocumentHeaders = getDocumentHeaders; |
export { createCookieFactory, isCookie } from "./cookies"; | ||
export { composeUploadHandlers as unstable_composeUploadHandlers, parseMultipartFormData as unstable_parseMultipartFormData, } from "./formData"; | ||
export { defer, json, redirect } from "./responses"; | ||
export { defer, json, redirect, redirectDocument, replace } from "./responses"; | ||
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, data, } from "./single-fetch"; | ||
export type { SingleFetchResult as UNSAFE_SingleFetchResult, SingleFetchResults as UNSAFE_SingleFetchResults, } from "./single-fetch"; | ||
export { createRequestHandler } from "./server"; | ||
@@ -10,4 +12,5 @@ export { createSession, createSessionStorageFactory, isSession, } from "./sessions"; | ||
export { MaxPartSizeExceededError } from "./upload/errors"; | ||
export { broadcastDevReady, logDevReady } from "./dev"; | ||
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, FlashSessionData, HandleDataRequestFunction, HandleDocumentRequestFunction, HeadersArgs, HeadersFunction, HtmlLinkDescriptor, HtmlMetaDescriptor, LinkDescriptor, LinksFunction, LoaderArgs, LoaderFunction, MemoryUploadHandlerFilterArgs, MemoryUploadHandlerOptions, MetaDescriptor, MetaFunction, HandleErrorFunction, PageLinkDescriptor, RequestHandler, RouteComponent, RouteHandle, SerializeFrom, ServerBuild, ServerEntryModule, V2_ServerRuntimeMetaArgs, V2_ServerRuntimeMetaDescriptor, V2_ServerRuntimeMetaFunction, Session, SessionData, SessionIdStorageStrategy, SessionStorage, SignFunction, TypedDeferredData, TypedResponse, UnsignFunction, UploadHandler, UploadHandlerPart, } from "./reexport"; | ||
export type { Future } from "./future"; | ||
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-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -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'); | ||
@@ -36,2 +37,6 @@ var sessions = require('./sessions.js'); | ||
exports.redirect = responses.redirect; | ||
exports.redirectDocument = responses.redirectDocument; | ||
exports.replace = responses.replace; | ||
exports.UNSAFE_SingleFetchRedirectSymbol = singleFetch.SingleFetchRedirectSymbol; | ||
exports.data = singleFetch.data; | ||
exports.createRequestHandler = server.createRequestHandler; | ||
@@ -47,1 +52,2 @@ exports.createSession = sessions.createSession; | ||
exports.logDevReady = dev.logDevReady; | ||
exports.unstable_setDevServerHooks = dev.setDevServerHooks; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -94,16 +94,7 @@ type Primitive = null | undefined | string | number | boolean | symbol | bigint; | ||
*/ | ||
export 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. | ||
@@ -110,0 +101,0 @@ */ |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -0,2 +1,4 @@ | ||
export type { ErrorResponse } from "@remix-run/router"; | ||
export type { HandleDataRequestFunction, HandleDocumentRequestFunction, HandleErrorFunction, ServerBuild, ServerEntryModule, } from "./build"; | ||
export type { Future } from "./future"; | ||
export type { UploadHandlerPart, UploadHandler } from "./formData"; | ||
@@ -6,9 +8,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 { TypedDeferredData, TypedResponse } from "./responses"; | ||
export type { ActionArgs, ActionFunction, DataFunctionArgs, ErrorBoundaryComponent, HeadersArgs, HeadersFunction, HtmlMetaDescriptor, LinksFunction, LoaderArgs, LoaderFunction, MetaDescriptor, MetaFunction, RouteComponent, RouteHandle, V2_ServerRuntimeMetaArgs, V2_ServerRuntimeMetaDescriptor, V2_ServerRuntimeMetaFunction, } from "./routeModules"; | ||
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, FlashSessionData, } from "./sessions"; |
@@ -34,2 +34,17 @@ import { type UNSAFE_DeferredData as DeferredData } from "@remix-run/router"; | ||
export declare const redirect: RedirectFunction; | ||
/** | ||
* A redirect response. Sets the status code and the `Location` header. | ||
* Defaults to "302 Found". | ||
* | ||
* @see https://remix.run/utils/redirect | ||
*/ | ||
export declare const replace: 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; | ||
@@ -36,0 +51,0 @@ export declare function isResponse(value: any): value is Response; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -20,2 +20,3 @@ * Copyright (c) Remix Software Inc. | ||
// interfaces must conform to the types they extend | ||
/** | ||
@@ -48,2 +49,23 @@ * This is a shortcut for creating `application/json` responses. Converts `data` | ||
}; | ||
/** | ||
* A redirect response. Sets the status code and the `Location` header. | ||
* Defaults to "302 Found". | ||
* | ||
* @see https://remix.run/utils/redirect | ||
*/ | ||
const replace = (url, init = 302) => { | ||
return router.replace(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) { | ||
@@ -125,1 +147,3 @@ let deferred = value; | ||
exports.redirect = redirect; | ||
exports.redirectDocument = redirectDocument; | ||
exports.replace = replace; |
@@ -8,2 +8,2 @@ import type { Params } from "@remix-run/router"; | ||
} | ||
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-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -17,4 +17,4 @@ * Copyright (c) Remix Software Inc. | ||
function matchServerRoutes(routes, pathname) { | ||
let matches = router.matchRoutes(routes, pathname); | ||
function matchServerRoutes(routes, pathname, basename) { | ||
let matches = router.matchRoutes(routes, pathname, basename); | ||
if (!matches) return null; | ||
@@ -21,0 +21,0 @@ return matches.map(match => ({ |
@@ -1,56 +0,60 @@ | ||
import type { AgnosticRouteMatch, Location, Params, RouterState } from "@remix-run/router"; | ||
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 { SerializeFrom } from "./serialize"; | ||
type RouteData = RouterState["loaderData"]; | ||
export interface RouteModules<RouteModule> { | ||
[routeId: string]: RouteModule; | ||
[routeId: string]: RouteModule | undefined; | ||
} | ||
/** | ||
* The arguments passed to ActionFunction and LoaderFunction. | ||
* | ||
* Note this is almost identical to React Router's version but over there the | ||
* context is optional since it's only there during static handler invocations. | ||
* Keeping Remix's own definition for now so it can differentiate between | ||
* client/server | ||
* @deprecated Use `LoaderFunctionArgs`/`ActionFunctionArgs` instead | ||
*/ | ||
export interface DataFunctionArgs { | ||
request: Request; | ||
export type DataFunctionArgs = RRActionFunctionArgs<AppLoadContext> & RRLoaderFunctionArgs<AppLoadContext> & { | ||
context: AppLoadContext; | ||
params: Params; | ||
} | ||
export type LoaderArgs = DataFunctionArgs; | ||
export 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. | ||
* | ||
* @deprecated Please enable the v2_errorBoundary flag to eliminate the need | ||
* for this type. If you are still using this, please use `@remix-run/react`'s | ||
* `CatchBoundaryComponent` type | ||
* Arguments passed to a route `action` function | ||
*/ | ||
export type CatchBoundaryComponent = any; | ||
export type ActionFunctionArgs = RRActionFunctionArgs<AppLoadContext> & { | ||
context: AppLoadContext; | ||
}; | ||
/** | ||
* A React component that is rendered when there is an error on a route. | ||
* | ||
* @deprecated Please enable the v2_errorBoundary flag to eliminate the need | ||
* for this type. If you are still using this, please use `@remix-run/react`'s | ||
* `ErrorBoundaryComponent` type | ||
* A function that handles data mutations for a route on the client | ||
* @private Public API is exported from @remix-run/react | ||
*/ | ||
export type ErrorBoundaryComponent = any; | ||
type ClientActionFunction = (args: ClientActionFunctionArgs) => ReturnType<RRActionFunction>; | ||
/** | ||
* V2 version of the ErrorBoundary that eliminates the distinction between | ||
* Error and Catch Boundaries and behaves like RR 6.4 errorElement and captures | ||
* errors with useRouteError() | ||
* | ||
* @deprecated Please enable the v2_errorBoundary flag to eliminate the need | ||
* for this type. If you are still using this, please use `@remix-run/react`'s | ||
* `V2_ErrorBoundaryComponent` type | ||
* Arguments passed to a route `clientAction` function | ||
* @private Public API is exported from @remix-run/react | ||
*/ | ||
export type V2_ErrorBoundaryComponent = any; | ||
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; | ||
}; | ||
/** | ||
* 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 = { | ||
@@ -77,14 +81,10 @@ loaderHeaders: Headers; | ||
/** | ||
* 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 | ||
* | ||
@@ -127,10 +127,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" | ||
@@ -140,48 +140,25 @@ * } | ||
*/ | ||
export interface V1_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[]; | ||
} | ||
export type MetaFunction<Loader extends LoaderFunction | unknown = unknown, ParentsLoaders extends Record<string, LoaderFunction> = {}> = V1_MetaFunction<Loader, ParentsLoaders>; | ||
interface V2_ServerRuntimeMetaMatch<RouteId extends string = string, Loader extends LoaderFunction | unknown = unknown> { | ||
interface ServerRuntimeMetaMatch<RouteId extends string = string, Loader extends LoaderFunction | unknown = unknown> { | ||
id: RouteId; | ||
pathname: AgnosticRouteMatch["pathname"]; | ||
data: Loader extends LoaderFunction ? SerializeFrom<Loader> : unknown; | ||
handle?: unknown; | ||
handle?: RouteHandle; | ||
params: AgnosticRouteMatch["params"]; | ||
meta: V2_ServerRuntimeMetaDescriptor[]; | ||
meta: ServerRuntimeMetaDescriptor[]; | ||
error?: unknown; | ||
} | ||
type V2_ServerRuntimeMetaMatches<MatchLoaders extends Record<string, unknown> = Record<string, unknown>> = Array<{ | ||
[K in keyof MatchLoaders]: V2_ServerRuntimeMetaMatch<Exclude<K, number | symbol>, MatchLoaders[K]>; | ||
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 V2_ServerRuntimeMetaArgs<Loader extends LoaderFunction | unknown = unknown, MatchLoaders extends Record<string, unknown> = Record<string, unknown>> { | ||
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: V2_ServerRuntimeMetaMatches<MatchLoaders>; | ||
matches: ServerRuntimeMetaMatches<MatchLoaders>; | ||
error?: unknown; | ||
} | ||
export interface V2_ServerRuntimeMetaFunction<Loader extends LoaderFunction | unknown = unknown, ParentsLoaders extends Record<string, LoaderFunction> = {}> { | ||
(args: V2_ServerRuntimeMetaArgs<Loader, ParentsLoaders>): V2_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 V1_HtmlMetaDescriptor { | ||
charset?: "utf-8"; | ||
charSet?: "utf-8"; | ||
title?: string; | ||
[name: string]: null | string | undefined | Record<string, string> | Array<Record<string, string> | string>; | ||
} | ||
export type HtmlMetaDescriptor = V1_HtmlMetaDescriptor; | ||
export type MetaDescriptor = HtmlMetaDescriptor; | ||
export type V2_ServerRuntimeMetaDescriptor = { | ||
export type ServerRuntimeMetaDescriptor = { | ||
charSet: "utf-8"; | ||
@@ -216,18 +193,15 @@ } | { | ||
/** | ||
* A React component that is rendered for a route. | ||
* | ||
* @deprecated Please use `@remix-run/react`'s `RouteComponent` type instead | ||
*/ | ||
export type RouteComponent = any; | ||
/** | ||
* An arbitrary object that is associated with a route. | ||
*/ | ||
export type RouteHandle = any; | ||
export type RouteHandle = unknown; | ||
export interface EntryRouteModule { | ||
CatchBoundary?: CatchBoundaryComponent; | ||
ErrorBoundary?: ErrorBoundaryComponent | V2_ErrorBoundaryComponent; | ||
default: RouteComponent; | ||
clientAction?: ClientActionFunction; | ||
clientLoader?: ClientLoaderFunction; | ||
ErrorBoundary?: any; | ||
HydrateFallback?: any; | ||
Layout?: any; | ||
default: any; | ||
handle?: RouteHandle; | ||
links?: LinksFunction; | ||
meta?: MetaFunction | HtmlMetaDescriptor; | ||
meta?: ServerRuntimeMetaFunction; | ||
} | ||
@@ -234,0 +208,0 @@ export interface ServerRouteModule extends EntryRouteModule { |
@@ -18,5 +18,7 @@ import type { AgnosticDataRouteObject } from "@remix-run/router"; | ||
hasLoader: boolean; | ||
hasCatchBoundary: boolean; | ||
hasClientAction: boolean; | ||
hasClientLoader: boolean; | ||
hasErrorBoundary: boolean; | ||
imports?: string[]; | ||
css?: string[]; | ||
module: string; | ||
@@ -23,0 +25,0 @@ parentId?: string; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -46,9 +46,11 @@ * Copyright (c) Remix Software Inc. | ||
return (routesByParentId[parentId] || []).map(route => { | ||
let hasErrorBoundary = future.v2_errorBoundary === true ? route.id === "root" || route.module.ErrorBoundary != null : route.id === "root" || route.module.CatchBoundary != null || route.module.ErrorBoundary != null; | ||
let commonRoute = { | ||
// Always include root due to default boundaries | ||
hasErrorBoundary, | ||
hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null, | ||
id: route.id, | ||
path: route.path, | ||
loader: route.module.loader ? args => data.callRouteLoaderRR({ | ||
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, | ||
@@ -58,5 +60,6 @@ params: args.params, | ||
loader: route.module.loader, | ||
routeId: route.id | ||
routeId: route.id, | ||
singleFetch: future.v3_singleFetch === true | ||
}) : undefined, | ||
action: route.module.action ? args => data.callRouteActionRR({ | ||
action: route.module.action ? (args, dataStrategyCtx) => data.callRouteAction({ | ||
request: args.request, | ||
@@ -66,3 +69,4 @@ params: args.params, | ||
action: route.module.action, | ||
routeId: route.id | ||
routeId: route.id, | ||
singleFetch: future.v3_singleFetch === true | ||
}) : undefined, | ||
@@ -69,0 +73,0 @@ handle: route.module.handle |
@@ -1,26 +0,13 @@ | ||
import type { AppData } from "./data"; | ||
import type { Jsonify } from "./jsonify"; | ||
import type { TypedDeferredData, TypedResponse } from "./responses"; | ||
type JsonPrimitive = string | number | boolean | String | Number | Boolean | null; | ||
type NonJsonPrimitive = undefined | Function | symbol; | ||
type IsAny<T> = 0 extends 1 & T ? true : false; | ||
type Serialize<T> = IsAny<T> extends true ? any : T extends TypedDeferredData<infer U> ? SerializeDeferred<U> : 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) */ | ||
type SerializeTuple<T extends unknown[]> = T extends [infer F, ...infer R] ? [Serialize<F>, ...SerializeTuple<R>] : []; | ||
/** JSON serialize objects (not including arrays) and classes */ | ||
type SerializeObject<T extends object> = { | ||
[k in keyof T as T[k] extends NonJsonPrimitive ? never : k]: Serialize<T[k]>; | ||
}; | ||
type SerializeDeferred<T extends Record<string, unknown>> = { | ||
[k in keyof T as T[k] extends Promise<unknown> ? k : T[k] extends NonJsonPrimitive ? never : k]: T[k] extends Promise<infer U> ? Promise<Serialize<U>> extends never ? "wtf" : Promise<Serialize<U>> : Serialize<T[k]> extends never ? k : Serialize<T[k]>; | ||
}; | ||
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>; | ||
}; | ||
type ArbitraryFunction = (...args: any[]) => unknown; | ||
import type { ClientActionFunctionArgs, ClientLoaderFunctionArgs } from "./routeModules"; | ||
import { type SerializeFrom as SingleFetch_SerializeFrom } from "./single-fetch"; | ||
import type { Future } from "./future"; | ||
type SingleFetchEnabled = Future extends { | ||
v3_singleFetch: infer T extends boolean; | ||
} ? T : false; | ||
/** | ||
* 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 | ||
* | ||
@@ -30,3 +17,15 @@ * For example: | ||
*/ | ||
export 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> = SingleFetchEnabled extends true ? SingleFetch_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 "@remix-run/router"; | ||
import type { AppLoadContext } from "./data"; | ||
import type { ServerBuild } from "./build"; | ||
export type RequestHandler = (request: Request, loadContext?: AppLoadContext) => Promise<Response>; | ||
export type CreateRequestHandlerFunction = (build: ServerBuild, mode?: string) => RequestHandler; | ||
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-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -25,8 +25,18 @@ * Copyright (c) Remix Software Inc. | ||
var serverHandoff = require('./serverHandoff.js'); | ||
var dev = require('./dev.js'); | ||
var singleFetch = require('./single-fetch.js'); | ||
var deprecations = require('./deprecations.js'); | ||
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); | ||
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, { | ||
@@ -36,29 +46,118 @@ request | ||
if (serverMode !== mode.ServerMode.Test && !request.signal.aborted) { | ||
console.error(error); | ||
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 | ||
}; | ||
} | ||
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 handleError = error => errorHandler(error, { | ||
context: loadContext, | ||
params: matches && matches.length > 0 ? matches[0].params : {}, | ||
request | ||
}); | ||
let 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 | ||
}); | ||
}; | ||
// Manifest request for fog of war | ||
let manifestUrl = `${_build.basename ?? "/"}/__manifest`.replace(/\/+/g, "/"); | ||
if (url.pathname === manifestUrl) { | ||
try { | ||
let res = await handleManifestRequest(_build, routes, url); | ||
return res; | ||
} catch (e) { | ||
handleError(e); | ||
return new Response("Unknown Server Error", { | ||
status: 500 | ||
}); | ||
} | ||
} | ||
let matches = routeMatching.matchServerRoutes(routes, url.pathname, _build.basename); | ||
if (matches && matches.length > 0) { | ||
Object.assign(params, matches[0].params); | ||
} | ||
let response; | ||
if (url.searchParams.has("_data")) { | ||
if (_build.future.v3_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, loadContext, handleError); | ||
if (build.entry.module.handleDataRequest) { | ||
let match = matches.find(match => match.route.id == routeId); | ||
response = await build.entry.module.handleDataRequest(response, { | ||
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: match ? match.params : {}, | ||
params, | ||
request | ||
}); | ||
if (responses.isRedirectResponse(response)) { | ||
response = createRemixRedirectResponse(response, _build.basename); | ||
} | ||
} | ||
} else if (matches && matches[matches.length - 1].route.module.default == null) { | ||
response = await handleResourceRequestRR(serverMode, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError); | ||
} else if (_build.future.v3_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: singleFetchMatches ? singleFetchMatches[0].params : {}, | ||
request | ||
}); | ||
if (responses.isRedirectResponse(response)) { | ||
let result = singleFetch.getSingleFetchRedirect(response.status, response.headers, _build.basename); | ||
if (request.method === "GET") { | ||
result = { | ||
[singleFetch.SingleFetchRedirectSymbol]: result | ||
}; | ||
} | ||
let headers = new Headers(response.headers); | ||
headers.set("Content-Type", "text/x-script"); | ||
return new Response(singleFetch.encodeViaTurboStream(result, request.signal, _build.entry.module.streamTimeout, serverMode), { | ||
status: singleFetch.SINGLE_FETCH_REDIRECT_STATUS, | ||
headers | ||
}); | ||
} | ||
} | ||
} 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, loadContext, handleError); | ||
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); | ||
} | ||
@@ -75,3 +174,25 @@ if (request.method === "HEAD") { | ||
}; | ||
async function handleDataRequestRR(serverMode, staticHandler, routeId, request, loadContext, handleError) { | ||
async function handleManifestRequest(build, routes, url) { | ||
let patches = {}; | ||
if (url.searchParams.has("p")) { | ||
for (let path of url.searchParams.getAll("p")) { | ||
let matches = routeMatching.matchServerRoutes(routes, path, build.basename); | ||
if (matches) { | ||
for (let match of matches) { | ||
let routeId = match.route.id; | ||
patches[routeId] = build.assets.routes[routeId]; | ||
} | ||
} | ||
} | ||
return responses.json(patches, { | ||
headers: { | ||
"Cache-Control": "public, max-age=31536000, immutable" | ||
} | ||
}); // Override the TypedResponse stuff from json() | ||
} | ||
return new Response("Invalid Request", { | ||
status: 400 | ||
}); | ||
} | ||
async function handleDataRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
try { | ||
@@ -83,16 +204,3 @@ let response = await staticHandler.queryRoute(request, { | ||
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.set("X-Remix-Status", 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 | ||
}); | ||
return createRemixRedirectResponse(response, build.basename); | ||
} | ||
@@ -105,18 +213,23 @@ if (router.UNSAFE_DEFERRED_SYMBOL in response) { | ||
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 = safelySetHeader(response, "X-Remix-Response", "yes"); | ||
return response; | ||
} catch (error) { | ||
if (responses.isResponse(error)) { | ||
error.headers.set("X-Remix-Catch", "yes"); | ||
return error; | ||
let response = safelySetHeader(error, "X-Remix-Catch", "yes"); | ||
return response; | ||
} | ||
if (router.isRouteErrorResponse(error)) { | ||
if (error.error) { | ||
handleError(error.error); | ||
} | ||
handleError(error); | ||
return errorResponseToJson(error, serverMode); | ||
} | ||
let errorInstance = error instanceof Error ? error : new Error("Unexpected Server Error"); | ||
let errorInstance = error instanceof Error || error instanceof DOMException ? error : new Error("Unexpected Server Error"); | ||
handleError(errorInstance); | ||
@@ -131,37 +244,36 @@ return router.json(errors.serializeError(errorInstance, serverMode), { | ||
} | ||
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 = router.isRouteErrorResponse(error) && (!error.error || error.status === 404); | ||
if (isCatch && route.module.CatchBoundary || !isCatch && route.module.ErrorBoundary || !route.parentId) { | ||
return route.id; | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
let { | ||
result, | ||
headers, | ||
status | ||
} = request.method !== "GET" ? await singleFetch.singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetch.singleFetchLoaders(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
// 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"); | ||
// 304 responses should not have a body | ||
if (status === 304) { | ||
return new Response(null, { | ||
status: 304, | ||
headers: resultHeaders | ||
}); | ||
} | ||
return findParentBoundary(routes, route.parentId, error); | ||
} | ||
// 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; | ||
// We use a less-descriptive `text/x-script` here instead of something like | ||
// `text/x-turbo` to enable compression when deployed via Cloudflare. See: | ||
// - https://github.com/remix-run/remix/issues/9884 | ||
// - https://developers.cloudflare.com/speed/optimization/content/brotli/content-compression/ | ||
resultHeaders.set("Content-Type", "text/x-script"); | ||
// 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, loadContext, handleError) { | ||
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) { | ||
let context; | ||
@@ -181,6 +293,16 @@ try { | ||
} | ||
let headers$1 = headers.getDocumentHeaders(build, context); | ||
// 304 responses should not have a body or a content-type | ||
if (context.statusCode === 304) { | ||
return new Response(null, { | ||
status: 304, | ||
headers: headers$1 | ||
}); | ||
} | ||
// 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) { | ||
@@ -193,7 +315,10 @@ handleError(err); | ||
// Restructure context.errors to the right Catch/Error Boundary | ||
if (build.future.v2_errorBoundary !== true) { | ||
differentiateCatchVersusErrorBoundaries(build, context); | ||
} | ||
let headers$1 = headers.getDocumentHeadersRR(build, context); | ||
// 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 = { | ||
@@ -203,12 +328,19 @@ manifest: build.assets, | ||
staticHandlerContext: context, | ||
criticalCss, | ||
serverHandoffString: serverHandoff.createServerHandoffString({ | ||
url: context.location.pathname, | ||
state: { | ||
loaderData: context.loaderData, | ||
actionData: context.actionData, | ||
errors: errors.serializeErrors(context.errors, serverMode) | ||
}, | ||
future: build.future | ||
basename: build.basename, | ||
criticalCss, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.v3_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
future: build.future | ||
...(build.future.v3_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) | ||
}; | ||
@@ -220,5 +352,17 @@ let handleDocumentRequestFunction = build.entry.module.default; | ||
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, error); | ||
context = router.getStaticContextFromError(staticHandler.dataRoutes, context, errorForSecondRender); | ||
@@ -230,8 +374,11 @@ // Sanitize errors outside of development environments | ||
// Restructure context.errors to the right Catch/Error Boundary | ||
if (build.future.v2_errorBoundary !== true) { | ||
differentiateCatchVersusErrorBoundaries(build, context); | ||
} | ||
// Update entryContext for the second render pass | ||
// 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 = { | ||
@@ -241,10 +388,13 @@ ...entryContext, | ||
serverHandoffString: serverHandoff.createServerHandoffString({ | ||
url: context.location.pathname, | ||
state: { | ||
loaderData: context.loaderData, | ||
actionData: context.actionData, | ||
errors: errors.serializeErrors(context.errors, serverMode) | ||
}, | ||
future: build.future | ||
}) | ||
basename: build.basename, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.v3_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.v3_singleFetch ? { | ||
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
} : null) | ||
}; | ||
@@ -259,3 +409,3 @@ try { | ||
} | ||
async function handleResourceRequestRR(serverMode, staticHandler, routeId, request, loadContext, handleError) { | ||
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
try { | ||
@@ -269,3 +419,12 @@ // Note we keep the routeId here to align with the Remix handling of | ||
}); | ||
// callRouteLoader/callRouteAction always return responses | ||
if (typeof response === "object" && response !== null) { | ||
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.v3_singleFetch && !responses.isResponse(response)) { | ||
console.warn(deprecations.resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId)); | ||
response = responses.json(response); | ||
} | ||
// 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"); | ||
@@ -277,8 +436,8 @@ return response; | ||
// match identically to what Remix returns | ||
error.headers.set("X-Remix-Catch", "yes"); | ||
return error; | ||
let response = safelySetHeader(error, "X-Remix-Catch", "yes"); | ||
return response; | ||
} | ||
if (router.isRouteErrorResponse(error)) { | ||
if (error.error) { | ||
handleError(error.error); | ||
if (error) { | ||
handleError(error); | ||
} | ||
@@ -292,3 +451,5 @@ return errorResponseToJson(error, serverMode); | ||
function errorResponseToJson(errorResponse, serverMode) { | ||
return router.json(errors.serializeError(errorResponse.error || new Error("Unexpected Server Error"), 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, | ||
@@ -315,4 +476,42 @@ statusText: errorResponse.statusText, | ||
} | ||
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 | ||
}); | ||
} | ||
// Anytime we are setting a header on a `Response` created in the loader/action, | ||
// we have to so it in this manner since in an `undici` world, if the `Response` | ||
// came directly from a `fetch` call, the headers are immutable will throw if | ||
// we try to set a new header. This is a sort of shallow clone of the `Response` | ||
// so we can safely set our own header. | ||
function safelySetHeader(response, name, value) { | ||
let headers = new Headers(response.headers); | ||
headers.set(name, value); | ||
return new Response(response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers, | ||
duplex: response.body ? "half" : undefined | ||
}); | ||
} | ||
exports.createRequestHandler = createRequestHandler; | ||
exports.differentiateCatchVersusErrorBoundaries = differentiateCatchVersusErrorBoundaries; |
@@ -5,6 +5,8 @@ import type { HydrationState } from "@remix-run/router"; | ||
export declare function createServerHandoffString<T>(serverHandoff: { | ||
state: ValidateShape<T, HydrationState>; | ||
url: string; | ||
state?: ValidateShape<T, HydrationState>; | ||
criticalCss?: string; | ||
basename: string | undefined; | ||
future: FutureConfig; | ||
isSpaMode: boolean; | ||
}): string; | ||
export {}; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -127,6 +127,7 @@ * Copyright (c) Remix Software Inc. | ||
} = 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); | ||
} | ||
@@ -139,2 +140,3 @@ return cookie.serialize(id, options); | ||
...options, | ||
maxAge: undefined, | ||
expires: new Date(0) | ||
@@ -141,0 +143,0 @@ }); |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -48,2 +48,3 @@ * Copyright (c) Remix Software Inc. | ||
...options, | ||
maxAge: undefined, | ||
expires: new Date(0) | ||
@@ -50,0 +51,0 @@ }); |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -27,3 +27,2 @@ * Copyright (c) Remix Software Inc. | ||
} = {}) => { | ||
let uniqueId = 0; | ||
let map = new Map(); | ||
@@ -33,3 +32,3 @@ return createSessionStorage({ | ||
async createData(data, expires) { | ||
let id = (++uniqueId).toString(); | ||
let id = Math.random().toString(36).substring(2, 10); | ||
map.set(id, { | ||
@@ -36,0 +35,0 @@ data, |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -1,2 +0,2 @@ | ||
import type { UploadHandler } from "@remix-run/server-runtime"; | ||
import type { UploadHandler } from "../formData"; | ||
export type MemoryUploadHandlerFilterArgs = { | ||
@@ -3,0 +3,0 @@ filename?: string; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-eccc180-20230707 | ||
* @remix-run/server-runtime v0.0.0-nightly-ed3a99a49-20241012 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
MIT License | ||
Copyright (c) Remix Software Inc. 2020-2021 | ||
Copyright (c) Shopify Inc. 2022-2023 | ||
Copyright (c) Shopify Inc. 2022-2024 | ||
@@ -6,0 +6,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
{ | ||
"name": "@remix-run/server-runtime", | ||
"version": "0.0.0-nightly-eccc180-20230707", | ||
"version": "0.0.0-nightly-ed3a99a49-20241012", | ||
"description": "Server runtime for Remix", | ||
@@ -19,15 +19,24 @@ "bugs": { | ||
"dependencies": { | ||
"@remix-run/router": "1.7.1", | ||
"@types/cookie": "^0.4.1", | ||
"@remix-run/router": "1.20.0", | ||
"@types/cookie": "^0.6.0", | ||
"@web3-storage/multipart-parser": "^1.0.0", | ||
"cookie": "^0.4.1", | ||
"cookie": "^0.6.0", | ||
"set-cookie-parser": "^2.4.8", | ||
"source-map": "^0.7.3" | ||
"source-map": "^0.7.3", | ||
"turbo-stream": "2.4.0" | ||
}, | ||
"devDependencies": { | ||
"@remix-run/web-file": "^3.0.2", | ||
"@types/set-cookie-parser": "^2.4.1" | ||
"@types/set-cookie-parser": "^2.4.1", | ||
"typescript": "^5.1.6" | ||
}, | ||
"peerDependencies": { | ||
"typescript": "^5.1.0" | ||
}, | ||
"peerDependenciesMeta": { | ||
"typescript": { | ||
"optional": true | ||
} | ||
}, | ||
"engines": { | ||
"node": ">=14.0.0" | ||
"node": ">=18.0.0" | ||
}, | ||
@@ -39,3 +48,6 @@ "files": [ | ||
"README.md" | ||
] | ||
} | ||
], | ||
"scripts": { | ||
"tsc": "tsc" | ||
} | ||
} |
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
85
5213
203227
8
+ Addedturbo-stream@2.4.0
+ Added@remix-run/router@1.20.0(transitive)
+ Added@types/cookie@0.6.0(transitive)
+ Addedcookie@0.6.0(transitive)
+ Addedturbo-stream@2.4.0(transitive)
+ Addedtypescript@5.7.2(transitive)
- Removed@remix-run/router@1.7.1(transitive)
- Removed@types/cookie@0.4.1(transitive)
- Removedcookie@0.4.2(transitive)
Updated@remix-run/router@1.20.0
Updated@types/cookie@^0.6.0
Updatedcookie@^0.6.0