@remix-run/server-runtime
Advanced tools
Comparing version 0.0.0-experimental-419fc7d09 to 0.0.0-experimental-432fae462
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -15,3 +15,3 @@ import type { ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs } from "./routeModules"; | ||
export type AppData = unknown; | ||
export declare function callRouteActionRR({ loadContext, action, params, request, routeId, singleFetch, }: { | ||
export declare function callRouteAction({ loadContext, action, params, request, routeId, singleFetch, }: { | ||
request: Request; | ||
@@ -24,3 +24,3 @@ action: ActionFunction; | ||
}): Promise<{} | Response | null>; | ||
export declare function callRouteLoaderRR({ loadContext, loader, params, request, routeId, singleFetch, }: { | ||
export declare function callRouteLoader({ loadContext, loader, params, request, routeId, singleFetch, }: { | ||
request: Request; | ||
@@ -27,0 +27,0 @@ loader: LoaderFunction; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -28,3 +28,3 @@ * Copyright (c) Remix Software Inc. | ||
async function callRouteActionRR({ | ||
async function callRouteAction({ | ||
loadContext, | ||
@@ -52,3 +52,3 @@ action, | ||
} | ||
async function callRouteLoaderRR({ | ||
async function callRouteLoader({ | ||
loadContext, | ||
@@ -127,3 +127,3 @@ loader, | ||
exports.callRouteActionRR = callRouteActionRR; | ||
exports.callRouteLoaderRR = callRouteLoaderRR; | ||
exports.callRouteAction = callRouteAction; | ||
exports.callRouteLoader = callRouteLoader; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -30,2 +30,3 @@ import type { StaticHandlerContext } from "@remix-run/router"; | ||
v3_throwAbortReason: boolean; | ||
unstable_lazyRouteDiscovery: boolean; | ||
unstable_singleFetch: boolean; | ||
@@ -32,0 +33,0 @@ } |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -24,3 +24,3 @@ * Copyright (c) Remix Software Inc. | ||
async function callRouteActionRR({ | ||
async function callRouteAction({ | ||
loadContext, | ||
@@ -48,3 +48,3 @@ action, | ||
} | ||
async function callRouteLoaderRR({ | ||
async function callRouteLoader({ | ||
loadContext, | ||
@@ -123,2 +123,2 @@ loader, | ||
export { callRouteActionRR, callRouteLoaderRR }; | ||
export { callRouteAction, callRouteLoader }; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -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; | ||
@@ -91,2 +91,2 @@ let matches = boundaryIdx >= 0 ? context.matches.slice(0, boundaryIdx + 1) : context.matches; | ||
export { getDocumentHeadersRR }; | ||
export { getDocumentHeaders }; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -13,4 +13,5 @@ * Copyright (c) Remix Software Inc. | ||
export { composeUploadHandlers as unstable_composeUploadHandlers, parseMultipartFormData as unstable_parseMultipartFormData } from './formData.js'; | ||
export { defer, json, redirect, redirectDocument } from './responses.js'; | ||
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, createRequestHandler } from './server.js'; | ||
export { defer, json, redirect, redirectDocument, replace } from './responses.js'; | ||
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, data as unstable_data, defineAction as unstable_defineAction, defineLoader as unstable_defineLoader } from './single-fetch.js'; | ||
export { createRequestHandler } from './server.js'; | ||
export { createSession, createSessionStorageFactory, isSession } from './sessions.js'; | ||
@@ -17,0 +18,0 @@ export { createCookieSessionStorageFactory } from './sessions/cookieStorage.js'; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -11,3 +11,3 @@ * Copyright (c) Remix Software Inc. | ||
*/ | ||
import { json as json$1, defer as defer$1, redirect as redirect$1, redirectDocument as redirectDocument$1 } from '@remix-run/router'; | ||
import { 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'; | ||
@@ -47,2 +47,12 @@ | ||
/** | ||
* 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. | ||
@@ -125,2 +135,2 @@ * Sets the status code and the `Location` header. | ||
export { createDeferredReadableStream, defer, isDeferredData, isRedirectResponse, isRedirectStatusCode, isResponse, json, redirect, redirectDocument }; | ||
export { createDeferredReadableStream, defer, isDeferredData, isRedirectResponse, isRedirectStatusCode, isResponse, json, redirect, redirectDocument, replace }; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -11,3 +11,3 @@ * Copyright (c) Remix Software Inc. | ||
*/ | ||
import { callRouteLoaderRR, callRouteActionRR } from './data.js'; | ||
import { callRouteLoader, callRouteAction } from './data.js'; | ||
@@ -51,3 +51,3 @@ // NOTE: make sure to change the Route in remix-react if you change this | ||
// though we know it'll always be provided in remix | ||
args => callRouteLoaderRR({ | ||
(args, dataStrategyCtx) => callRouteLoader({ | ||
request: args.request, | ||
@@ -60,3 +60,3 @@ params: args.params, | ||
}) : undefined, | ||
action: route.module.action ? args => callRouteActionRR({ | ||
action: route.module.action ? (args, dataStrategyCtx) => callRouteAction({ | ||
request: args.request, | ||
@@ -63,0 +63,0 @@ params: args.params, |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -11,7 +11,6 @@ * Copyright (c) Remix Software Inc. | ||
*/ | ||
import { stripBasename, UNSAFE_DEFERRED_SYMBOL, isRouteErrorResponse, json, UNSAFE_ErrorResponseImpl, getStaticContextFromError, createStaticHandler } from '@remix-run/router'; | ||
import { encode } from 'turbo-stream'; | ||
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, sanitizeError, sanitizeErrors, serializeErrors } from './errors.js'; | ||
import { getDocumentHeadersRR } from './headers.js'; | ||
import { serializeError, sanitizeErrors, serializeErrors } from './errors.js'; | ||
import { getDocumentHeaders } from './headers.js'; | ||
import invariant from './invariant.js'; | ||
@@ -21,5 +20,7 @@ import { ServerMode, isServerMode } from './mode.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'; | ||
@@ -78,4 +79,3 @@ function derive(build, mode) { | ||
let url = new URL(request.url); | ||
let matches = matchServerRoutes(routes, url.pathname, _build.basename); | ||
let params = matches && matches.length > 0 ? matches[0].params : {}; | ||
let params = {}; | ||
let handleError = error => { | ||
@@ -92,2 +92,21 @@ if (mode === ServerMode.Development) { | ||
}; | ||
// 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; | ||
@@ -106,2 +125,5 @@ if (url.searchParams.has("_data")) { | ||
}); | ||
if (isRedirectResponse(response)) { | ||
response = createRemixRedirectResponse(response, _build.basename); | ||
} | ||
} | ||
@@ -111,13 +133,27 @@ } else if (_build.future.unstable_singleFetch && url.pathname.endsWith(".data")) { | ||
handlerUrl.pathname = handlerUrl.pathname.replace(/\.data$/, "").replace(/^\/_root$/, "/"); | ||
let matches = matchServerRoutes(routes, handlerUrl.pathname, _build.basename); | ||
response = await handleSingleFetchRequest(serverMode, _build, staticHandler, matches, request, handlerUrl, loadContext, handleError); | ||
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, | ||
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, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError); | ||
response = await handleResourceRequest(serverMode, _build, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError); | ||
} else { | ||
@@ -138,2 +174,24 @@ var _getDevServerHooks2, _getDevServerHooks2$g; | ||
}; | ||
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) { | ||
@@ -146,17 +204,3 @@ try { | ||
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); | ||
let redirectUrl = headers.get("Location"); | ||
headers.set("X-Remix-Redirect", build.basename ? stripBasename(redirectUrl, build.basename) || redirectUrl : redirectUrl); | ||
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); | ||
} | ||
@@ -178,8 +222,8 @@ if (UNSAFE_DEFERRED_SYMBOL in response) { | ||
// network errors that are missing this header | ||
response.headers.set("X-Remix-Response", "yes"); | ||
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; | ||
} | ||
@@ -192,3 +236,3 @@ if (isRouteErrorResponse(error)) { | ||
handleError(errorInstance); | ||
return json(serializeError(errorInstance, serverMode), { | ||
return json$1(serializeError(errorInstance, serverMode), { | ||
status: 500, | ||
@@ -201,4 +245,3 @@ headers: { | ||
} | ||
const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect"); | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, matches, request, handlerUrl, loadContext, handleError) { | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
let { | ||
@@ -208,3 +251,3 @@ result, | ||
status | ||
} = request.method !== "GET" ? await singleFetchAction(request, handlerUrl, staticHandler, loadContext, handleError) : await singleFetchLoaders(request, handlerUrl, staticHandler, matches, loadContext, handleError, serverMode, build); | ||
} = request.method !== "GET" ? await singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetchLoaders(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
@@ -215,4 +258,9 @@ // Mark all successful responses with a header so we can identify in-flight | ||
resultHeaders.set("X-Remix-Response", "yes"); | ||
resultHeaders.set("Content-Type", "text/x-turbo"); | ||
// 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 | ||
@@ -225,134 +273,2 @@ // `activeDeferreds` or anything :) | ||
} | ||
async function singleFetchAction(request, handlerUrl, staticHandler, loadContext, handleError) { | ||
try { | ||
let handlerRequest = new Request(handlerUrl, { | ||
method: request.method, | ||
body: request.body, | ||
headers: request.headers, | ||
signal: request.signal, | ||
...(request.body ? { | ||
duplex: "half" | ||
} : undefined) | ||
}); | ||
let result = await staticHandler.queryRoute(handlerRequest, { | ||
requestContext: loadContext | ||
}); | ||
// Unlike `handleDataRequest`, when singleFetch is enabled, queryRoute does | ||
// let non-Response return values through | ||
if (!isResponse(result)) { | ||
return { | ||
result: { | ||
data: result | ||
}, | ||
headers: new Headers(), | ||
status: 200 | ||
}; | ||
} | ||
if (isRedirectResponse(result)) { | ||
return { | ||
result: getSingleFetchRedirect(result), | ||
headers: result.headers, | ||
status: 200 // Don't trigger a redirect on the `fetch` | ||
}; | ||
} | ||
return { | ||
result: { | ||
data: await unwrapResponse(result) | ||
}, | ||
headers: result.headers, | ||
status: result.status | ||
}; | ||
} catch (error) { | ||
if (isResponse(error)) { | ||
return { | ||
result: { | ||
error: new UNSAFE_ErrorResponseImpl(error.status, error.statusText, await unwrapResponse(error)) | ||
}, | ||
headers: error.headers, | ||
status: error.status | ||
}; | ||
} else { | ||
handleError(error); | ||
return { | ||
result: { | ||
error | ||
}, | ||
headers: new Headers(), | ||
status: isRouteErrorResponse(error) ? error.status : 500 | ||
}; | ||
} | ||
} | ||
} | ||
async function singleFetchLoaders(request, handlerUrl, staticHandler, matches, loadContext, handleError, serverMode, build) { | ||
try { | ||
var _URL$searchParams$get; | ||
let handlerRequest = new Request(handlerUrl, { | ||
headers: request.headers, | ||
signal: request.signal | ||
}); | ||
let loadRouteIds = ((_URL$searchParams$get = new URL(request.url).searchParams.get("_routes")) === null || _URL$searchParams$get === void 0 ? void 0 : _URL$searchParams$get.split(",")) || undefined; | ||
let result = await staticHandler.query(handlerRequest, { | ||
requestContext: loadContext, | ||
loadRouteIds, | ||
skipLoaderErrorBubbling: true | ||
}); | ||
if (isResponse(result)) { | ||
return { | ||
result: { | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(result) | ||
}, | ||
headers: result.headers, | ||
status: 200 // Don't want the `fetch` call to follow the redirect | ||
}; | ||
} | ||
let context = result; | ||
// Sanitize errors outside of development environments | ||
if (context.errors) { | ||
Object.values(context.errors).forEach(err => { | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
if (!isRouteErrorResponse(err) || err.error) { | ||
handleError(err); | ||
} | ||
}); | ||
context.errors = sanitizeErrors(context.errors, serverMode); | ||
} | ||
// Aggregate results based on the matches we intended to load since we get | ||
// `null` values back in `context.loaderData` for routes we didn't load | ||
let results = {}; | ||
let loadedMatches = loadRouteIds ? context.matches.filter(m => m.route.loader && loadRouteIds.includes(m.route.id)) : context.matches; | ||
loadedMatches.forEach(m => { | ||
var _context$loaderData, _context$errors; | ||
let data = (_context$loaderData = context.loaderData) === null || _context$loaderData === void 0 ? void 0 : _context$loaderData[m.route.id]; | ||
let error = (_context$errors = context.errors) === null || _context$errors === void 0 ? void 0 : _context$errors[m.route.id]; | ||
if (error !== undefined) { | ||
results[m.route.id] = { | ||
error | ||
}; | ||
} else if (data !== undefined) { | ||
results[m.route.id] = { | ||
data | ||
}; | ||
} | ||
}); | ||
return { | ||
result: results, | ||
headers: getDocumentHeadersRR(build, context), | ||
status: context.statusCode | ||
}; | ||
} catch (error) { | ||
handleError(error); | ||
return { | ||
result: { | ||
root: { | ||
error | ||
} | ||
}, | ||
headers: new Headers(), | ||
status: 500 | ||
}; | ||
} | ||
} | ||
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) { | ||
@@ -373,2 +289,3 @@ let context; | ||
} | ||
let headers = getDocumentHeaders(build, context); | ||
@@ -378,3 +295,3 @@ // Sanitize errors outside of development environments | ||
Object.values(context.errors).forEach(err => { | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
// @ts-expect-error `err.error` is "private" from users but intended for internal use | ||
if (!isRouteErrorResponse(err) || err.error) { | ||
@@ -386,3 +303,2 @@ handleError(err); | ||
} | ||
let headers = getDocumentHeadersRR(build, context); | ||
@@ -403,3 +319,2 @@ // Server UI state to send to the client. | ||
serverHandoffString: createServerHandoffString({ | ||
url: context.location.pathname, | ||
basename: build.basename, | ||
@@ -460,3 +375,2 @@ criticalCss, | ||
serverHandoffString: createServerHandoffString({ | ||
url: context.location.pathname, | ||
basename: build.basename, | ||
@@ -482,3 +396,3 @@ future: build.future, | ||
} | ||
async function handleResourceRequest(serverMode, staticHandler, routeId, request, loadContext, handleError) { | ||
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
try { | ||
@@ -492,5 +406,10 @@ // Note we keep the routeId here to align with the Remix handling of | ||
}); | ||
if (typeof response === "object") { | ||
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.unstable_singleFetch && !isResponse(response)) { | ||
console.warn(resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId)); | ||
response = json(response); | ||
} | ||
// callRouteLoader/callRouteAction always return responses (w/o single fetch). | ||
@@ -504,4 +423,4 @@ // With single fetch, users should always be Responses from resource routes | ||
// 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; | ||
} | ||
@@ -519,3 +438,3 @@ if (isRouteErrorResponse(error)) { | ||
function errorResponseToJson(errorResponse, serverMode) { | ||
return json(serializeError( | ||
return json$1(serializeError( | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
@@ -550,60 +469,36 @@ errorResponse.error || new Error("Unexpected Server Error"), serverMode), { | ||
} | ||
function getSingleFetchRedirect(response) { | ||
return { | ||
redirect: response.headers.get("Location"), | ||
status: response.status, | ||
revalidate: | ||
// Technically X-Remix-Revalidate isn't needed here - that was an implementation | ||
// detail of ?_data requests as our way to tell the front end to revalidate when | ||
// we didn't have a response body to include that information in. | ||
// With single fetch, we tell the front end via this revalidate boolean field. | ||
// However, we're respecting it for now because it may be something folks have | ||
// used in their own responses | ||
// TODO(v3): Consider removing or making this official public API | ||
response.headers.has("X-Remix-Revalidate") || response.headers.has("Set-Cookie"), | ||
reload: response.headers.has("X-Remix-Reload-Document") | ||
}; | ||
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 | ||
}); | ||
} | ||
// Note: If you change this function please change the corresponding | ||
// decodeViaTurboStream function in server-runtime | ||
function encodeViaTurboStream(data, requestSignal, streamTimeout, serverMode) { | ||
let controller = new AbortController(); | ||
// How long are we willing to wait for all of the promises in `data` to resolve | ||
// before timing out? We default this to 50ms shorter than the default value for | ||
// `ABORT_DELAY` in our built-in `entry.server.tsx` so that once we reject we | ||
// have time to flush the rejections down through React's rendering stream before ` | ||
// we call abort() on that. If the user provides their own it's up to them to | ||
// decouple the aborting of the stream from the aborting of React's renderToPipeableStream | ||
let timeoutId = setTimeout(() => controller.abort(new Error("Server Timeout")), typeof streamTimeout === "number" ? streamTimeout : 4950); | ||
requestSignal.addEventListener("abort", () => clearTimeout(timeoutId)); | ||
return encode(data, { | ||
signal: controller.signal, | ||
plugins: [value => { | ||
// Even though we sanitized errors on context.errors prior to responding, | ||
// we still need to handle this for any deferred data that rejects with an | ||
// Error - as those will not be sanitized yet | ||
if (value instanceof Error) { | ||
let { | ||
name, | ||
message, | ||
stack | ||
} = serverMode === ServerMode.Production ? sanitizeError(value, serverMode) : value; | ||
return ["SanitizedError", name, message, stack]; | ||
} | ||
if (value instanceof UNSAFE_ErrorResponseImpl) { | ||
let { | ||
data, | ||
status, | ||
statusText | ||
} = value; | ||
return ["ErrorResponse", data, status, statusText]; | ||
} | ||
if (value && typeof value === "object" && SingleFetchRedirectSymbol in value) { | ||
return ["SingleFetchRedirect", value[SingleFetchRedirectSymbol]]; | ||
} | ||
}] | ||
// 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 { SingleFetchRedirectSymbol, createRequestHandler }; | ||
export { createRequestHandler }; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
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-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -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; | ||
@@ -95,2 +95,2 @@ let matches = boundaryIdx >= 0 ? context.matches.slice(0, boundaryIdx + 1) : context.matches; | ||
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, redirectDocument } from "./responses"; | ||
export type { SingleFetchResult as UNSAFE_SingleFetchResult, SingleFetchResults as UNSAFE_SingleFetchResults, } from "./server"; | ||
export { createRequestHandler, SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, } from "./server"; | ||
export { defer, json, redirect, redirectDocument, replace } from "./responses"; | ||
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, data as unstable_data, defineLoader as unstable_defineLoader, defineAction as unstable_defineAction, } from "./single-fetch"; | ||
export type { Loader as unstable_Loader, Action as unstable_Action, Serialize as unstable_Serialize, SingleFetchResult as UNSAFE_SingleFetchResult, SingleFetchResults as UNSAFE_SingleFetchResults, } from "./single-fetch"; | ||
export { createRequestHandler } from "./server"; | ||
export { createSession, createSessionStorageFactory, isSession, } from "./sessions"; | ||
@@ -7,0 +8,0 @@ export { createCookieSessionStorageFactory } from "./sessions/cookieStorage"; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -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'); | ||
@@ -37,3 +38,7 @@ var sessions = require('./sessions.js'); | ||
exports.redirectDocument = responses.redirectDocument; | ||
exports.UNSAFE_SingleFetchRedirectSymbol = server.SingleFetchRedirectSymbol; | ||
exports.replace = responses.replace; | ||
exports.UNSAFE_SingleFetchRedirectSymbol = singleFetch.SingleFetchRedirectSymbol; | ||
exports.unstable_data = singleFetch.data; | ||
exports.unstable_defineAction = singleFetch.defineAction; | ||
exports.unstable_defineLoader = singleFetch.defineLoader; | ||
exports.createRequestHandler = server.createRequestHandler; | ||
@@ -40,0 +45,0 @@ exports.createSession = sessions.createSession; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -35,2 +35,9 @@ import { type UNSAFE_DeferredData as DeferredData } from "@remix-run/router"; | ||
/** | ||
* 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. | ||
@@ -37,0 +44,0 @@ * Sets the status code and the `Location` header. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -50,2 +50,12 @@ * Copyright (c) Remix Software Inc. | ||
/** | ||
* 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. | ||
@@ -137,1 +147,2 @@ * Sets the status code and the `Location` header. | ||
exports.redirectDocument = redirectDocument; | ||
exports.replace = replace; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -54,3 +54,3 @@ * Copyright (c) Remix Software Inc. | ||
// though we know it'll always be provided in remix | ||
args => data.callRouteLoaderRR({ | ||
(args, dataStrategyCtx) => data.callRouteLoader({ | ||
request: args.request, | ||
@@ -63,3 +63,3 @@ params: args.params, | ||
}) : undefined, | ||
action: route.module.action ? args => data.callRouteActionRR({ | ||
action: route.module.action ? (args, dataStrategyCtx) => data.callRouteAction({ | ||
request: args.request, | ||
@@ -66,0 +66,0 @@ params: args.params, |
@@ -6,18 +6,1 @@ import type { AppLoadContext } from "./data"; | ||
export declare const createRequestHandler: CreateRequestHandlerFunction; | ||
export declare const SingleFetchRedirectSymbol: unique symbol; | ||
type SingleFetchRedirectResult = { | ||
redirect: string; | ||
status: number; | ||
revalidate: boolean; | ||
reload: boolean; | ||
}; | ||
export type SingleFetchResult = { | ||
data: unknown; | ||
} | { | ||
error: unknown; | ||
} | SingleFetchRedirectResult; | ||
export type SingleFetchResults = { | ||
[key: string]: SingleFetchResult; | ||
[SingleFetchRedirectSymbol]?: SingleFetchRedirectResult; | ||
}; | ||
export {}; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -16,3 +16,2 @@ * Copyright (c) Remix Software Inc. | ||
var router = require('@remix-run/router'); | ||
var turboStream = require('turbo-stream'); | ||
var entry = require('./entry.js'); | ||
@@ -28,2 +27,4 @@ var errors = require('./errors.js'); | ||
var dev = require('./dev.js'); | ||
var singleFetch = require('./single-fetch.js'); | ||
var deprecations = require('./deprecations.js'); | ||
@@ -82,4 +83,3 @@ function derive(build, mode$1) { | ||
let url = new URL(request.url); | ||
let matches = routeMatching.matchServerRoutes(routes, url.pathname, _build.basename); | ||
let params = matches && matches.length > 0 ? matches[0].params : {}; | ||
let params = {}; | ||
let handleError = error => { | ||
@@ -96,2 +96,21 @@ if (mode$1 === mode.ServerMode.Development) { | ||
}; | ||
// 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; | ||
@@ -110,2 +129,5 @@ if (url.searchParams.has("_data")) { | ||
}); | ||
if (responses.isRedirectResponse(response)) { | ||
response = createRemixRedirectResponse(response, _build.basename); | ||
} | ||
} | ||
@@ -115,13 +137,27 @@ } else if (_build.future.unstable_singleFetch && url.pathname.endsWith(".data")) { | ||
handlerUrl.pathname = handlerUrl.pathname.replace(/\.data$/, "").replace(/^\/_root$/, "/"); | ||
let matches = routeMatching.matchServerRoutes(routes, handlerUrl.pathname, _build.basename); | ||
response = await handleSingleFetchRequest(serverMode, _build, staticHandler, matches, request, handlerUrl, loadContext, handleError); | ||
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, | ||
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, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError); | ||
response = await handleResourceRequest(serverMode, _build, staticHandler, matches.slice(-1)[0].route.id, request, loadContext, handleError); | ||
} else { | ||
@@ -142,2 +178,24 @@ var _getDevServerHooks2, _getDevServerHooks2$g; | ||
}; | ||
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) { | ||
@@ -150,17 +208,3 @@ try { | ||
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); | ||
let redirectUrl = headers.get("Location"); | ||
headers.set("X-Remix-Redirect", build.basename ? router.stripBasename(redirectUrl, build.basename) || redirectUrl : redirectUrl); | ||
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); | ||
} | ||
@@ -182,8 +226,8 @@ if (router.UNSAFE_DEFERRED_SYMBOL in response) { | ||
// network errors that are missing this header | ||
response.headers.set("X-Remix-Response", "yes"); | ||
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; | ||
} | ||
@@ -204,4 +248,3 @@ if (router.isRouteErrorResponse(error)) { | ||
} | ||
const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect"); | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, matches, request, handlerUrl, loadContext, handleError) { | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
let { | ||
@@ -211,3 +254,3 @@ result, | ||
status | ||
} = request.method !== "GET" ? await singleFetchAction(request, handlerUrl, staticHandler, loadContext, handleError) : await singleFetchLoaders(request, handlerUrl, staticHandler, matches, loadContext, handleError, serverMode, build); | ||
} = request.method !== "GET" ? await singleFetch.singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetch.singleFetchLoaders(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
@@ -218,7 +261,12 @@ // Mark all successful responses with a header so we can identify in-flight | ||
resultHeaders.set("X-Remix-Response", "yes"); | ||
resultHeaders.set("Content-Type", "text/x-turbo"); | ||
// 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), { | ||
return new Response(singleFetch.encodeViaTurboStream(result, request.signal, build.entry.module.streamTimeout, serverMode), { | ||
status: status || 200, | ||
@@ -228,134 +276,2 @@ headers: resultHeaders | ||
} | ||
async function singleFetchAction(request, handlerUrl, staticHandler, loadContext, handleError) { | ||
try { | ||
let handlerRequest = new Request(handlerUrl, { | ||
method: request.method, | ||
body: request.body, | ||
headers: request.headers, | ||
signal: request.signal, | ||
...(request.body ? { | ||
duplex: "half" | ||
} : undefined) | ||
}); | ||
let result = await staticHandler.queryRoute(handlerRequest, { | ||
requestContext: loadContext | ||
}); | ||
// Unlike `handleDataRequest`, when singleFetch is enabled, queryRoute does | ||
// let non-Response return values through | ||
if (!responses.isResponse(result)) { | ||
return { | ||
result: { | ||
data: result | ||
}, | ||
headers: new Headers(), | ||
status: 200 | ||
}; | ||
} | ||
if (responses.isRedirectResponse(result)) { | ||
return { | ||
result: getSingleFetchRedirect(result), | ||
headers: result.headers, | ||
status: 200 // Don't trigger a redirect on the `fetch` | ||
}; | ||
} | ||
return { | ||
result: { | ||
data: await unwrapResponse(result) | ||
}, | ||
headers: result.headers, | ||
status: result.status | ||
}; | ||
} catch (error) { | ||
if (responses.isResponse(error)) { | ||
return { | ||
result: { | ||
error: new router.UNSAFE_ErrorResponseImpl(error.status, error.statusText, await unwrapResponse(error)) | ||
}, | ||
headers: error.headers, | ||
status: error.status | ||
}; | ||
} else { | ||
handleError(error); | ||
return { | ||
result: { | ||
error | ||
}, | ||
headers: new Headers(), | ||
status: router.isRouteErrorResponse(error) ? error.status : 500 | ||
}; | ||
} | ||
} | ||
} | ||
async function singleFetchLoaders(request, handlerUrl, staticHandler, matches, loadContext, handleError, serverMode, build) { | ||
try { | ||
var _URL$searchParams$get; | ||
let handlerRequest = new Request(handlerUrl, { | ||
headers: request.headers, | ||
signal: request.signal | ||
}); | ||
let loadRouteIds = ((_URL$searchParams$get = new URL(request.url).searchParams.get("_routes")) === null || _URL$searchParams$get === void 0 ? void 0 : _URL$searchParams$get.split(",")) || undefined; | ||
let result = await staticHandler.query(handlerRequest, { | ||
requestContext: loadContext, | ||
loadRouteIds, | ||
skipLoaderErrorBubbling: true | ||
}); | ||
if (responses.isResponse(result)) { | ||
return { | ||
result: { | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(result) | ||
}, | ||
headers: result.headers, | ||
status: 200 // Don't want the `fetch` call to follow the redirect | ||
}; | ||
} | ||
let context = result; | ||
// Sanitize errors outside of development environments | ||
if (context.errors) { | ||
Object.values(context.errors).forEach(err => { | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
if (!router.isRouteErrorResponse(err) || err.error) { | ||
handleError(err); | ||
} | ||
}); | ||
context.errors = errors.sanitizeErrors(context.errors, serverMode); | ||
} | ||
// Aggregate results based on the matches we intended to load since we get | ||
// `null` values back in `context.loaderData` for routes we didn't load | ||
let results = {}; | ||
let loadedMatches = loadRouteIds ? context.matches.filter(m => m.route.loader && loadRouteIds.includes(m.route.id)) : context.matches; | ||
loadedMatches.forEach(m => { | ||
var _context$loaderData, _context$errors; | ||
let data = (_context$loaderData = context.loaderData) === null || _context$loaderData === void 0 ? void 0 : _context$loaderData[m.route.id]; | ||
let error = (_context$errors = context.errors) === null || _context$errors === void 0 ? void 0 : _context$errors[m.route.id]; | ||
if (error !== undefined) { | ||
results[m.route.id] = { | ||
error | ||
}; | ||
} else if (data !== undefined) { | ||
results[m.route.id] = { | ||
data | ||
}; | ||
} | ||
}); | ||
return { | ||
result: results, | ||
headers: headers.getDocumentHeadersRR(build, context), | ||
status: context.statusCode | ||
}; | ||
} catch (error) { | ||
handleError(error); | ||
return { | ||
result: { | ||
root: { | ||
error | ||
} | ||
}, | ||
headers: new Headers(), | ||
status: 500 | ||
}; | ||
} | ||
} | ||
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) { | ||
@@ -376,2 +292,3 @@ let context; | ||
} | ||
let headers$1 = headers.getDocumentHeaders(build, context); | ||
@@ -381,3 +298,3 @@ // Sanitize errors outside of development environments | ||
Object.values(context.errors).forEach(err => { | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
// @ts-expect-error `err.error` is "private" from users but intended for internal use | ||
if (!router.isRouteErrorResponse(err) || err.error) { | ||
@@ -389,3 +306,2 @@ handleError(err); | ||
} | ||
let headers$1 = headers.getDocumentHeadersRR(build, context); | ||
@@ -406,3 +322,2 @@ // Server UI state to send to the client. | ||
serverHandoffString: serverHandoff.createServerHandoffString({ | ||
url: context.location.pathname, | ||
basename: build.basename, | ||
@@ -417,3 +332,3 @@ criticalCss, | ||
...(build.future.unstable_singleFetch ? { | ||
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
@@ -464,3 +379,2 @@ } : null), | ||
serverHandoffString: serverHandoff.createServerHandoffString({ | ||
url: context.location.pathname, | ||
basename: build.basename, | ||
@@ -474,3 +388,3 @@ future: build.future, | ||
...(build.future.unstable_singleFetch ? { | ||
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
@@ -487,3 +401,3 @@ } : null) | ||
} | ||
async function handleResourceRequest(serverMode, staticHandler, routeId, request, loadContext, handleError) { | ||
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
try { | ||
@@ -497,5 +411,10 @@ // Note we keep the routeId here to align with the Remix handling of | ||
}); | ||
if (typeof response === "object") { | ||
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.unstable_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). | ||
@@ -509,4 +428,4 @@ // With single fetch, users should always be Responses from resource routes | ||
// 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; | ||
} | ||
@@ -554,61 +473,36 @@ if (router.isRouteErrorResponse(error)) { | ||
} | ||
function getSingleFetchRedirect(response) { | ||
return { | ||
redirect: response.headers.get("Location"), | ||
status: response.status, | ||
revalidate: | ||
// Technically X-Remix-Revalidate isn't needed here - that was an implementation | ||
// detail of ?_data requests as our way to tell the front end to revalidate when | ||
// we didn't have a response body to include that information in. | ||
// With single fetch, we tell the front end via this revalidate boolean field. | ||
// However, we're respecting it for now because it may be something folks have | ||
// used in their own responses | ||
// TODO(v3): Consider removing or making this official public API | ||
response.headers.has("X-Remix-Revalidate") || response.headers.has("Set-Cookie"), | ||
reload: response.headers.has("X-Remix-Reload-Document") | ||
}; | ||
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 | ||
}); | ||
} | ||
// Note: If you change this function please change the corresponding | ||
// decodeViaTurboStream function in server-runtime | ||
function encodeViaTurboStream(data, requestSignal, streamTimeout, serverMode) { | ||
let controller = new AbortController(); | ||
// How long are we willing to wait for all of the promises in `data` to resolve | ||
// before timing out? We default this to 50ms shorter than the default value for | ||
// `ABORT_DELAY` in our built-in `entry.server.tsx` so that once we reject we | ||
// have time to flush the rejections down through React's rendering stream before ` | ||
// we call abort() on that. If the user provides their own it's up to them to | ||
// decouple the aborting of the stream from the aborting of React's renderToPipeableStream | ||
let timeoutId = setTimeout(() => controller.abort(new Error("Server Timeout")), typeof streamTimeout === "number" ? streamTimeout : 4950); | ||
requestSignal.addEventListener("abort", () => clearTimeout(timeoutId)); | ||
return turboStream.encode(data, { | ||
signal: controller.signal, | ||
plugins: [value => { | ||
// Even though we sanitized errors on context.errors prior to responding, | ||
// we still need to handle this for any deferred data that rejects with an | ||
// Error - as those will not be sanitized yet | ||
if (value instanceof Error) { | ||
let { | ||
name, | ||
message, | ||
stack | ||
} = serverMode === mode.ServerMode.Production ? errors.sanitizeError(value, serverMode) : value; | ||
return ["SanitizedError", name, message, stack]; | ||
} | ||
if (value instanceof router.UNSAFE_ErrorResponseImpl) { | ||
let { | ||
data, | ||
status, | ||
statusText | ||
} = value; | ||
return ["ErrorResponse", data, status, statusText]; | ||
} | ||
if (value && typeof value === "object" && SingleFetchRedirectSymbol in value) { | ||
return ["SingleFetchRedirect", value[SingleFetchRedirectSymbol]]; | ||
} | ||
}] | ||
// 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.SingleFetchRedirectSymbol = SingleFetchRedirectSymbol; | ||
exports.createRequestHandler = createRequestHandler; |
@@ -7,3 +7,2 @@ import type { HydrationState } from "@remix-run/router"; | ||
criticalCss?: string; | ||
url: string; | ||
basename: string | undefined; | ||
@@ -10,0 +9,0 @@ future: FutureConfig; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-419fc7d09 | ||
* @remix-run/server-runtime v0.0.0-experimental-432fae462 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
{ | ||
"name": "@remix-run/server-runtime", | ||
"version": "0.0.0-experimental-419fc7d09", | ||
"version": "0.0.0-experimental-432fae462", | ||
"description": "Server runtime for Remix", | ||
@@ -19,3 +19,3 @@ "bugs": { | ||
"dependencies": { | ||
"@remix-run/router": "0.0.0-experimental-c7dd3d3a", | ||
"@remix-run/router": "1.19.1", | ||
"@types/cookie": "^0.6.0", | ||
@@ -26,3 +26,3 @@ "@web3-storage/multipart-parser": "^1.0.0", | ||
"source-map": "^0.7.3", | ||
"turbo-stream": "^2.0.0" | ||
"turbo-stream": "2.3.0" | ||
}, | ||
@@ -29,0 +29,0 @@ "devDependencies": { |
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
202093
84
5170
+ Added@remix-run/router@1.19.1(transitive)
+ Addedturbo-stream@2.3.0(transitive)
- Removed@remix-run/router@0.0.0-experimental-c7dd3d3a(transitive)
- Removedturbo-stream@2.4.1(transitive)
Updated@remix-run/router@1.19.1
Updatedturbo-stream@2.3.0