@remix-run/server-runtime
Advanced tools
Comparing version 0.0.0-experimental-1a1e5b488 to 0.0.0-experimental-21befc955
@@ -38,2 +38,3 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from "./routeModules"; | ||
handleError?: HandleErrorFunction; | ||
streamTimeout?: number; | ||
} |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
import type { ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs } from "./routeModules"; | ||
import type { ResponseStub } from "./single-fetch"; | ||
/** | ||
@@ -15,3 +16,3 @@ * An object of unknown type for route loaders and actions provided by the | ||
export type AppData = unknown; | ||
export declare function callRouteActionRR({ loadContext, action, params, request, routeId, }: { | ||
export declare function callRouteAction({ loadContext, action, params, request, routeId, singleFetch, response, }: { | ||
request: Request; | ||
@@ -22,4 +23,6 @@ action: ActionFunction; | ||
routeId: string; | ||
}): Promise<Response>; | ||
export declare function callRouteLoaderRR({ loadContext, loader, params, request, routeId, }: { | ||
singleFetch: boolean; | ||
response?: ResponseStub; | ||
}): Promise<{} | Response | null>; | ||
export declare function callRouteLoader({ loadContext, loader, params, request, routeId, singleFetch, response, }: { | ||
request: Request; | ||
@@ -30,2 +33,4 @@ loader: LoaderFunction; | ||
routeId: string; | ||
}): Promise<import("@remix-run/router").UNSAFE_DeferredData | Response>; | ||
singleFetch: boolean; | ||
response?: ResponseStub; | ||
}): Promise<{} | Response | null>; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -28,3 +28,3 @@ * Copyright (c) Remix Software Inc. | ||
async function callRouteActionRR({ | ||
async function callRouteAction({ | ||
loadContext, | ||
@@ -34,3 +34,5 @@ action, | ||
request, | ||
routeId | ||
routeId, | ||
singleFetch, | ||
response | ||
}) { | ||
@@ -40,3 +42,8 @@ let result = await action({ | ||
context: loadContext, | ||
params | ||
params, | ||
// Only provided when single fetch is enabled, and made available via | ||
// `defineAction` types, not `ActionFunctionArgs` | ||
...(singleFetch ? { | ||
response | ||
} : null) | ||
}); | ||
@@ -46,5 +53,10 @@ if (result === undefined) { | ||
} | ||
// Allow naked object returns when single fetch is enabled | ||
if (singleFetch) { | ||
return result; | ||
} | ||
return responses.isResponse(result) ? result : responses.json(result); | ||
} | ||
async function callRouteLoaderRR({ | ||
async function callRouteLoader({ | ||
loadContext, | ||
@@ -54,3 +66,5 @@ loader, | ||
request, | ||
routeId | ||
routeId, | ||
singleFetch, | ||
response | ||
}) { | ||
@@ -60,3 +74,8 @@ let result = await loader({ | ||
context: loadContext, | ||
params | ||
params, | ||
// Only provided when single fetch is enabled, and made available via | ||
// `defineLoader` types, not `LoaderFunctionArgs` | ||
...(singleFetch ? { | ||
response | ||
} : null) | ||
}); | ||
@@ -72,2 +91,7 @@ if (result === undefined) { | ||
} | ||
// Allow naked object returns when single fetch is enabled | ||
if (singleFetch) { | ||
return result; | ||
} | ||
return responses.isResponse(result) ? result : responses.json(result); | ||
@@ -120,3 +144,3 @@ } | ||
exports.callRouteActionRR = callRouteActionRR; | ||
exports.callRouteLoaderRR = callRouteLoaderRR; | ||
exports.callRouteAction = callRouteAction; | ||
exports.callRouteLoader = callRouteLoader; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -10,2 +10,13 @@ import type { StaticHandlerContext } from "@remix-run/router"; | ||
serverHandoffString?: string; | ||
serverHandoffStream?: ReadableStream<Uint8Array>; | ||
renderMeta?: { | ||
didRenderScripts?: boolean; | ||
streamCache?: Record<number, Promise<void> & { | ||
result?: { | ||
done: boolean; | ||
value: string; | ||
}; | ||
error?: unknown; | ||
}>; | ||
}; | ||
staticHandlerContext: StaticHandlerContext; | ||
@@ -12,0 +23,0 @@ future: FutureConfig; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -24,3 +24,3 @@ * Copyright (c) Remix Software Inc. | ||
async function callRouteActionRR({ | ||
async function callRouteAction({ | ||
loadContext, | ||
@@ -30,3 +30,5 @@ action, | ||
request, | ||
routeId | ||
routeId, | ||
singleFetch, | ||
response | ||
}) { | ||
@@ -36,3 +38,8 @@ let result = await action({ | ||
context: loadContext, | ||
params | ||
params, | ||
// Only provided when single fetch is enabled, and made available via | ||
// `defineAction` types, not `ActionFunctionArgs` | ||
...(singleFetch ? { | ||
response | ||
} : null) | ||
}); | ||
@@ -42,5 +49,10 @@ if (result === undefined) { | ||
} | ||
// Allow naked object returns when single fetch is enabled | ||
if (singleFetch) { | ||
return result; | ||
} | ||
return isResponse(result) ? result : json(result); | ||
} | ||
async function callRouteLoaderRR({ | ||
async function callRouteLoader({ | ||
loadContext, | ||
@@ -50,3 +62,5 @@ loader, | ||
request, | ||
routeId | ||
routeId, | ||
singleFetch, | ||
response | ||
}) { | ||
@@ -56,3 +70,8 @@ let result = await loader({ | ||
context: loadContext, | ||
params | ||
params, | ||
// Only provided when single fetch is enabled, and made available via | ||
// `defineLoader` types, not `LoaderFunctionArgs` | ||
...(singleFetch ? { | ||
response | ||
} : null) | ||
}); | ||
@@ -68,2 +87,7 @@ if (result === undefined) { | ||
} | ||
// Allow naked object returns when single fetch is enabled | ||
if (singleFetch) { | ||
return result; | ||
} | ||
return isResponse(result) ? result : json(result); | ||
@@ -116,2 +140,2 @@ } | ||
export { callRouteActionRR, callRouteLoaderRR }; | ||
export { callRouteAction, callRouteLoader }; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -14,2 +14,3 @@ * Copyright (c) Remix Software Inc. | ||
export { defer, json, redirect, redirectDocument } from './responses.js'; | ||
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, defineAction as unstable_defineAction, defineLoader as unstable_defineLoader } from './single-fetch.js'; | ||
export { createRequestHandler } from './server.js'; | ||
@@ -16,0 +17,0 @@ export { createSession, createSessionStorageFactory, isSession } from './sessions.js'; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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, | ||
@@ -57,5 +57,7 @@ params: args.params, | ||
loader: route.module.loader, | ||
routeId: route.id | ||
routeId: route.id, | ||
singleFetch: future.unstable_singleFetch === true, | ||
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response | ||
}) : undefined, | ||
action: route.module.action ? args => callRouteActionRR({ | ||
action: route.module.action ? (args, dataStrategyCtx) => callRouteAction({ | ||
request: args.request, | ||
@@ -65,3 +67,5 @@ params: args.params, | ||
action: route.module.action, | ||
routeId: route.id | ||
routeId: route.id, | ||
singleFetch: future.unstable_singleFetch === true, | ||
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response | ||
}) : undefined, | ||
@@ -68,0 +72,0 @@ handle: route.module.handle |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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, sanitizeErrors, serializeErrors } from './errors.js'; | ||
import { getDocumentHeadersRR } from './headers.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, isRedirectStatusCode } from './responses.js'; | ||
import { createServerHandoffString } from './serverHandoff.js'; | ||
import { getDevServerHooks } from './dev.js'; | ||
import { getSingleFetchRedirect, encodeViaTurboStream, singleFetchAction, singleFetchLoaders, getResponseStubs, getSingleFetchDataStrategy, mergeResponseStubs, isResponseStub, getSingleFetchResourceRouteDataStrategy, ResponseStubOperationsSymbol, SingleFetchRedirectSymbol } from './single-fetch.js'; | ||
import { resourceRouteJsonWarning } from './deprecations.js'; | ||
@@ -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,4 +92,26 @@ 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; | ||
if (url.searchParams.has("_data")) { | ||
if (_build.future.unstable_singleFetch) { | ||
handleError(new Error("Warning: Single fetch-enabled apps should not be making ?_data requests, " + "this is likely to break in the future")); | ||
} | ||
let routeId = url.searchParams.get("_data"); | ||
@@ -103,2 +125,5 @@ response = await handleDataRequest(serverMode, _build, staticHandler, routeId, request, loadContext, handleError); | ||
}); | ||
if (isRedirectResponse(response)) { | ||
response = createRemixRedirectResponse(response, _build.basename); | ||
} | ||
} | ||
@@ -108,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); | ||
if (request.method === "GET") { | ||
result = { | ||
[SingleFetchRedirectSymbol]: result | ||
}; | ||
} | ||
let headers = new Headers(response.headers); | ||
headers.set("Content-Type", "text/x-turbo"); | ||
return new Response(encodeViaTurboStream(result, request.signal, _build.entry.module.streamTimeout, serverMode), { | ||
status: 200, | ||
headers | ||
}); | ||
} | ||
} | ||
} else if (matches && matches[matches.length - 1].route.module.default == null && 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 { | ||
@@ -135,2 +174,26 @@ var _getDevServerHooks2, _getDevServerHooks2$g; | ||
}; | ||
async function handleManifestRequest(build, routes, url) { | ||
let data = { | ||
patches: {}, | ||
notFoundPaths: [] | ||
}; | ||
let paths = url.searchParams.getAll("paths"); | ||
if (paths.length > 0) { | ||
for (let path of paths) { | ||
let matches = matchServerRoutes(routes, path, build.basename); | ||
if (matches) { | ||
for (let match of matches) { | ||
let routeId = match.route.id; | ||
data.patches[routeId] = build.assets.routes[routeId]; | ||
} | ||
} else { | ||
data.notFoundPaths.push(path); | ||
} | ||
} | ||
return json(data); // Override the TypedResponse stuff | ||
} | ||
return new Response("Invalid Request", { | ||
status: 400 | ||
}); | ||
} | ||
async function handleDataRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
@@ -143,17 +206,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); | ||
} | ||
@@ -183,5 +232,3 @@ if (UNSAFE_DEFERRED_SYMBOL in response) { | ||
if (isRouteErrorResponse(error)) { | ||
if (error) { | ||
handleError(error); | ||
} | ||
handleError(error); | ||
return errorResponseToJson(error, serverMode); | ||
@@ -191,3 +238,3 @@ } | ||
handleError(errorInstance); | ||
return json(serializeError(errorInstance, serverMode), { | ||
return json$1(serializeError(errorInstance, serverMode), { | ||
status: 500, | ||
@@ -200,4 +247,8 @@ headers: { | ||
} | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, matches, request, handlerUrl, loadContext, handleError) { | ||
let [result, headers] = request.method !== "GET" ? await singleFetchAction(request, handlerUrl, staticHandler, loadContext, handleError) : await singleFetchLoaders(handlerUrl, new URL(request.url).searchParams.get("_routes"), staticHandler, matches, loadContext, handleError, serverMode, build); | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
let { | ||
result, | ||
headers, | ||
status | ||
} = request.method !== "GET" ? await singleFetchAction(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetchLoaders(serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
@@ -212,134 +263,14 @@ // Mark all successful responses with a header so we can identify in-flight | ||
// `activeDeferreds` or anything :) | ||
return new Response(encode(result, [value => { | ||
if (value instanceof UNSAFE_ErrorResponseImpl) { | ||
return ["ErrorResponse", { | ||
...value | ||
}]; | ||
} | ||
}]), { | ||
return new Response(encodeViaTurboStream(result, request.signal, build.entry.module.streamTimeout, serverMode), { | ||
status: status || 200, | ||
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 response = await staticHandler.queryRoute(handlerRequest, { | ||
requestContext: loadContext | ||
}); | ||
// callRouteLoader/callRouteAction always return responses | ||
invariant(isResponse(response), "Expected a Response to be returned from queryRoute"); | ||
if (isRedirectResponse(response)) { | ||
return [{ | ||
redirect: response.headers.get("Location"), | ||
status: response.status, | ||
revalidate: response.headers.has("X-Remix-Revalidate"), | ||
reload: response.headers.has("X-Remix-Reload-Document") | ||
}, response.headers]; | ||
} | ||
return [{ | ||
data: await unwrapResponse(response), | ||
status: response.status | ||
}, response.headers]; | ||
} catch (err) { | ||
handleError(err); | ||
let error = isResponse(err) ? new UNSAFE_ErrorResponseImpl(err.status, err.statusText, await unwrapResponse(err)) : err; | ||
return [{ | ||
error | ||
}, new Headers()]; | ||
} | ||
} | ||
async function singleFetchLoaders(handlerUrl, routesToLoad, staticHandler, matches, loadContext, handleError, serverMode, build) { | ||
let context; | ||
try { | ||
let handlerRequest = new Request(handlerUrl); | ||
let loadRouteIds = routesToLoad ? routesToLoad.split(",") : undefined; | ||
let result = await staticHandler.query(handlerRequest, { | ||
requestContext: loadContext, | ||
loadRouteIds | ||
}); | ||
if (isResponse(result)) { | ||
var _matches$find; | ||
// We don't really know which loader this came from, so just stick it at | ||
// a known match | ||
let routeId = (matches === null || matches === void 0 ? void 0 : (_matches$find = matches.find(m => routesToLoad ? routesToLoad.split(",").includes(m.route.id) : m.route.module.loader)) === null || _matches$find === void 0 ? void 0 : _matches$find.route.id) || "root"; | ||
return [{ | ||
[routeId]: { | ||
redirect: result.headers.get("Location"), | ||
status: result.status, | ||
revalidate: result.headers.has("X-Remix-Revalidate"), | ||
reload: result.headers.has("X-Remix-Reload-Document") | ||
} | ||
}, result.headers]; | ||
} | ||
context = result; | ||
} catch (error) { | ||
handleError(error); | ||
return [{ | ||
root: { | ||
error | ||
} | ||
}, new Headers()]; | ||
} | ||
// 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); | ||
// TODO: Feels hacky - we need to un-bubble errors here since they'll be | ||
// bubbled client side. Probably better to throw a flag on query() to not | ||
// do this in the first place | ||
let mostRecentError = null; | ||
for (let match of context.matches) { | ||
var _build$assets$routes$; | ||
let routeId = match.route.id; | ||
if (context.errors[routeId] !== undefined) { | ||
mostRecentError = [routeId, context.errors[routeId]]; | ||
} | ||
if ((_build$assets$routes$ = build.assets.routes[routeId]) !== null && _build$assets$routes$ !== void 0 && _build$assets$routes$.hasLoader && context.loaderData[routeId] === undefined && mostRecentError) { | ||
context.errors[mostRecentError[0]] = undefined; | ||
context.errors[routeId] = mostRecentError[1]; | ||
mostRecentError = null; | ||
} | ||
} | ||
} | ||
// 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 = routesToLoad ? context.matches.filter(m => m.route.loader && routesToLoad.split(",").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 [results, getDocumentHeadersRR(build, context)]; | ||
} | ||
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) { | ||
let context; | ||
let responseStubs = getResponseStubs(); | ||
try { | ||
context = await staticHandler.query(request, { | ||
requestContext: loadContext | ||
requestContext: loadContext, | ||
unstable_dataStrategy: build.future.unstable_singleFetch ? getSingleFetchDataStrategy(responseStubs) : undefined | ||
}); | ||
@@ -355,2 +286,18 @@ } catch (error) { | ||
} | ||
let statusCode; | ||
let headers; | ||
if (build.future.unstable_singleFetch) { | ||
let merged = mergeResponseStubs(context, responseStubs); | ||
statusCode = merged.statusCode; | ||
headers = merged.headers; | ||
if (isRedirectStatusCode(statusCode) && headers.has("Location")) { | ||
return new Response(null, { | ||
status: statusCode, | ||
headers | ||
}); | ||
} | ||
} else { | ||
statusCode = context.statusCode; | ||
headers = getDocumentHeaders(build, context); | ||
} | ||
@@ -360,4 +307,4 @@ // 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 | ||
if (!isRouteErrorResponse(err) || err.error) { | ||
// @ts-expect-error `err.error` is "private" from users but intended for internal use | ||
if ((!isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { | ||
handleError(err); | ||
@@ -368,3 +315,11 @@ } | ||
} | ||
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 = { | ||
@@ -379,10 +334,12 @@ manifest: build.assets, | ||
criticalCss, | ||
state: { | ||
loaderData: context.loaderData, | ||
actionData: context.actionData, | ||
errors: serializeErrors(context.errors, serverMode) | ||
}, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.unstable_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.unstable_singleFetch ? { | ||
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
} : null), | ||
future: build.future, | ||
@@ -394,3 +351,3 @@ isSpaMode: build.isSpaMode, | ||
try { | ||
return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext); | ||
return await handleDocumentRequestFunction(request, statusCode, headers, entryContext, loadContext); | ||
} catch (error) { | ||
@@ -419,3 +376,11 @@ handleError(error); | ||
// 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 = { | ||
@@ -427,10 +392,12 @@ ...entryContext, | ||
basename: build.basename, | ||
state: { | ||
loaderData: context.loaderData, | ||
actionData: context.actionData, | ||
errors: serializeErrors(context.errors, serverMode) | ||
}, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode | ||
}) | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.unstable_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.unstable_singleFetch ? { | ||
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
} : null) | ||
}; | ||
@@ -445,4 +412,5 @@ try { | ||
} | ||
async function handleResourceRequest(serverMode, staticHandler, routeId, request, loadContext, handleError) { | ||
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
try { | ||
let responseStubs = build.future.unstable_singleFetch ? getResponseStubs() : {}; | ||
// Note we keep the routeId here to align with the Remix handling of | ||
@@ -453,5 +421,34 @@ // resource routes which doesn't take ?index into account and just takes | ||
routeId, | ||
requestContext: loadContext | ||
requestContext: loadContext, | ||
...(build.future.unstable_singleFetch ? { | ||
unstable_dataStrategy: getSingleFetchResourceRouteDataStrategy({ | ||
responseStubs | ||
}) | ||
} : null) | ||
}); | ||
// callRouteLoader/callRouteAction always return responses | ||
if (typeof response === "object") { | ||
invariant(!(UNSAFE_DEFERRED_SYMBOL in response), `You cannot return a \`defer()\` response from a Resource Route. Did you ` + `forget to export a default UI component from the "${routeId}" route?`); | ||
} | ||
if (build.future.unstable_singleFetch) { | ||
let stub = responseStubs[routeId]; | ||
if (isResponse(response)) { | ||
// If a response was returned, we use it's status and we merge our | ||
// response stub headers onto it | ||
let ops = stub[ResponseStubOperationsSymbol]; | ||
for (let [op, ...args] of ops) { | ||
// @ts-expect-error | ||
response.headers[op](...args); | ||
} | ||
} else { | ||
console.warn(resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId)); | ||
// Otherwise we create a json Response using the stub | ||
response = json(response, { | ||
status: stub.status, | ||
headers: stub.headers | ||
}); | ||
} | ||
} | ||
// callRouteLoader/callRouteAction always return responses (w/o single fetch). | ||
// With single fetch, users should always be Responses from resource routes | ||
invariant(isResponse(response), "Expected a Response to be returned from queryRoute"); | ||
@@ -477,3 +474,3 @@ return response; | ||
function errorResponseToJson(errorResponse, serverMode) { | ||
return json(serializeError( | ||
return json$1(serializeError( | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
@@ -508,3 +505,20 @@ errorResponse.error || new Error("Unexpected Server Error"), serverMode), { | ||
} | ||
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 }; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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 { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, defineLoader as unstable_defineLoader, defineAction as unstable_defineAction, } from "./single-fetch"; | ||
export type { Loader as unstable_Loader, Action as unstable_Action, Serialize as unstable_Serialize, SingleFetchResult as UNSAFE_SingleFetchResult, SingleFetchResults as UNSAFE_SingleFetchResults, } from "./single-fetch"; | ||
export { createRequestHandler } from "./server"; | ||
@@ -5,0 +7,0 @@ export { createSession, createSessionStorageFactory, isSession, } from "./sessions"; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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,2 +38,5 @@ var sessions = require('./sessions.js'); | ||
exports.redirectDocument = responses.redirectDocument; | ||
exports.UNSAFE_SingleFetchRedirectSymbol = singleFetch.SingleFetchRedirectSymbol; | ||
exports.unstable_defineAction = singleFetch.defineAction; | ||
exports.unstable_defineLoader = singleFetch.defineLoader; | ||
exports.createRequestHandler = server.createRequestHandler; | ||
@@ -39,0 +43,0 @@ exports.createSession = sessions.createSession; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -5,2 +5,3 @@ import type { ActionFunction as RRActionFunction, ActionFunctionArgs as RRActionFunctionArgs, AgnosticRouteMatch, LoaderFunction as RRLoaderFunction, LoaderFunctionArgs as RRLoaderFunctionArgs, Location, Params } from "@remix-run/router"; | ||
import type { SerializeFrom } from "./serialize"; | ||
import type { ResponseStub } from "./single-fetch"; | ||
export interface RouteModules<RouteModule> { | ||
@@ -24,2 +25,3 @@ [routeId: string]: RouteModule | undefined; | ||
context: AppLoadContext; | ||
response?: ResponseStub; | ||
}; | ||
@@ -47,2 +49,3 @@ /** | ||
context: AppLoadContext; | ||
response?: ResponseStub; | ||
}; | ||
@@ -201,2 +204,3 @@ /** | ||
HydrateFallback?: any; | ||
Layout?: any; | ||
default: any; | ||
@@ -203,0 +207,0 @@ handle?: RouteHandle; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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, | ||
@@ -60,5 +60,7 @@ params: args.params, | ||
loader: route.module.loader, | ||
routeId: route.id | ||
routeId: route.id, | ||
singleFetch: future.unstable_singleFetch === true, | ||
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response | ||
}) : undefined, | ||
action: route.module.action ? args => data.callRouteActionRR({ | ||
action: route.module.action ? (args, dataStrategyCtx) => data.callRouteAction({ | ||
request: args.request, | ||
@@ -68,3 +70,5 @@ params: args.params, | ||
action: route.module.action, | ||
routeId: route.id | ||
routeId: route.id, | ||
singleFetch: future.unstable_singleFetch === true, | ||
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response | ||
}) : undefined, | ||
@@ -71,0 +75,0 @@ handle: route.module.handle |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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,4 +96,26 @@ 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; | ||
if (url.searchParams.has("_data")) { | ||
if (_build.future.unstable_singleFetch) { | ||
handleError(new Error("Warning: Single fetch-enabled apps should not be making ?_data requests, " + "this is likely to break in the future")); | ||
} | ||
let routeId = url.searchParams.get("_data"); | ||
@@ -107,2 +129,5 @@ response = await handleDataRequest(serverMode, _build, staticHandler, routeId, request, loadContext, handleError); | ||
}); | ||
if (responses.isRedirectResponse(response)) { | ||
response = createRemixRedirectResponse(response, _build.basename); | ||
} | ||
} | ||
@@ -112,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); | ||
if (request.method === "GET") { | ||
result = { | ||
[singleFetch.SingleFetchRedirectSymbol]: result | ||
}; | ||
} | ||
let headers = new Headers(response.headers); | ||
headers.set("Content-Type", "text/x-turbo"); | ||
return new Response(singleFetch.encodeViaTurboStream(result, request.signal, _build.entry.module.streamTimeout, serverMode), { | ||
status: 200, | ||
headers | ||
}); | ||
} | ||
} | ||
} else if (matches && matches[matches.length - 1].route.module.default == null && 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 { | ||
@@ -139,2 +178,26 @@ var _getDevServerHooks2, _getDevServerHooks2$g; | ||
}; | ||
async function handleManifestRequest(build, routes, url) { | ||
let data = { | ||
patches: {}, | ||
notFoundPaths: [] | ||
}; | ||
let paths = url.searchParams.getAll("paths"); | ||
if (paths.length > 0) { | ||
for (let path of paths) { | ||
let matches = routeMatching.matchServerRoutes(routes, path, build.basename); | ||
if (matches) { | ||
for (let match of matches) { | ||
let routeId = match.route.id; | ||
data.patches[routeId] = build.assets.routes[routeId]; | ||
} | ||
} else { | ||
data.notFoundPaths.push(path); | ||
} | ||
} | ||
return responses.json(data); // Override the TypedResponse stuff | ||
} | ||
return new Response("Invalid Request", { | ||
status: 400 | ||
}); | ||
} | ||
async function handleDataRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
@@ -147,17 +210,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); | ||
} | ||
@@ -187,5 +236,3 @@ if (router.UNSAFE_DEFERRED_SYMBOL in response) { | ||
if (router.isRouteErrorResponse(error)) { | ||
if (error) { | ||
handleError(error); | ||
} | ||
handleError(error); | ||
return errorResponseToJson(error, serverMode); | ||
@@ -203,4 +250,8 @@ } | ||
} | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, matches, request, handlerUrl, loadContext, handleError) { | ||
let [result, headers] = request.method !== "GET" ? await singleFetchAction(request, handlerUrl, staticHandler, loadContext, handleError) : await singleFetchLoaders(handlerUrl, new URL(request.url).searchParams.get("_routes"), staticHandler, matches, loadContext, handleError, serverMode, build); | ||
async function handleSingleFetchRequest(serverMode, build, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
let { | ||
result, | ||
headers, | ||
status | ||
} = request.method !== "GET" ? await singleFetch.singleFetchAction(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetch.singleFetchLoaders(serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
@@ -215,134 +266,14 @@ // Mark all successful responses with a header so we can identify in-flight | ||
// `activeDeferreds` or anything :) | ||
return new Response(turboStream.encode(result, [value => { | ||
if (value instanceof router.UNSAFE_ErrorResponseImpl) { | ||
return ["ErrorResponse", { | ||
...value | ||
}]; | ||
} | ||
}]), { | ||
return new Response(singleFetch.encodeViaTurboStream(result, request.signal, build.entry.module.streamTimeout, serverMode), { | ||
status: status || 200, | ||
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 response = await staticHandler.queryRoute(handlerRequest, { | ||
requestContext: loadContext | ||
}); | ||
// callRouteLoader/callRouteAction always return responses | ||
invariant["default"](responses.isResponse(response), "Expected a Response to be returned from queryRoute"); | ||
if (responses.isRedirectResponse(response)) { | ||
return [{ | ||
redirect: response.headers.get("Location"), | ||
status: response.status, | ||
revalidate: response.headers.has("X-Remix-Revalidate"), | ||
reload: response.headers.has("X-Remix-Reload-Document") | ||
}, response.headers]; | ||
} | ||
return [{ | ||
data: await unwrapResponse(response), | ||
status: response.status | ||
}, response.headers]; | ||
} catch (err) { | ||
handleError(err); | ||
let error = responses.isResponse(err) ? new router.UNSAFE_ErrorResponseImpl(err.status, err.statusText, await unwrapResponse(err)) : err; | ||
return [{ | ||
error | ||
}, new Headers()]; | ||
} | ||
} | ||
async function singleFetchLoaders(handlerUrl, routesToLoad, staticHandler, matches, loadContext, handleError, serverMode, build) { | ||
let context; | ||
try { | ||
let handlerRequest = new Request(handlerUrl); | ||
let loadRouteIds = routesToLoad ? routesToLoad.split(",") : undefined; | ||
let result = await staticHandler.query(handlerRequest, { | ||
requestContext: loadContext, | ||
loadRouteIds | ||
}); | ||
if (responses.isResponse(result)) { | ||
var _matches$find; | ||
// We don't really know which loader this came from, so just stick it at | ||
// a known match | ||
let routeId = (matches === null || matches === void 0 ? void 0 : (_matches$find = matches.find(m => routesToLoad ? routesToLoad.split(",").includes(m.route.id) : m.route.module.loader)) === null || _matches$find === void 0 ? void 0 : _matches$find.route.id) || "root"; | ||
return [{ | ||
[routeId]: { | ||
redirect: result.headers.get("Location"), | ||
status: result.status, | ||
revalidate: result.headers.has("X-Remix-Revalidate"), | ||
reload: result.headers.has("X-Remix-Reload-Document") | ||
} | ||
}, result.headers]; | ||
} | ||
context = result; | ||
} catch (error) { | ||
handleError(error); | ||
return [{ | ||
root: { | ||
error | ||
} | ||
}, new Headers()]; | ||
} | ||
// 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); | ||
// TODO: Feels hacky - we need to un-bubble errors here since they'll be | ||
// bubbled client side. Probably better to throw a flag on query() to not | ||
// do this in the first place | ||
let mostRecentError = null; | ||
for (let match of context.matches) { | ||
var _build$assets$routes$; | ||
let routeId = match.route.id; | ||
if (context.errors[routeId] !== undefined) { | ||
mostRecentError = [routeId, context.errors[routeId]]; | ||
} | ||
if ((_build$assets$routes$ = build.assets.routes[routeId]) !== null && _build$assets$routes$ !== void 0 && _build$assets$routes$.hasLoader && context.loaderData[routeId] === undefined && mostRecentError) { | ||
context.errors[mostRecentError[0]] = undefined; | ||
context.errors[routeId] = mostRecentError[1]; | ||
mostRecentError = null; | ||
} | ||
} | ||
} | ||
// 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 = routesToLoad ? context.matches.filter(m => m.route.loader && routesToLoad.split(",").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 [results, headers.getDocumentHeadersRR(build, context)]; | ||
} | ||
async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, criticalCss) { | ||
let context; | ||
let responseStubs = singleFetch.getResponseStubs(); | ||
try { | ||
context = await staticHandler.query(request, { | ||
requestContext: loadContext | ||
requestContext: loadContext, | ||
unstable_dataStrategy: build.future.unstable_singleFetch ? singleFetch.getSingleFetchDataStrategy(responseStubs) : undefined | ||
}); | ||
@@ -358,2 +289,18 @@ } catch (error) { | ||
} | ||
let statusCode; | ||
let headers$1; | ||
if (build.future.unstable_singleFetch) { | ||
let merged = singleFetch.mergeResponseStubs(context, responseStubs); | ||
statusCode = merged.statusCode; | ||
headers$1 = merged.headers; | ||
if (responses.isRedirectStatusCode(statusCode) && headers$1.has("Location")) { | ||
return new Response(null, { | ||
status: statusCode, | ||
headers: headers$1 | ||
}); | ||
} | ||
} else { | ||
statusCode = context.statusCode; | ||
headers$1 = headers.getDocumentHeaders(build, context); | ||
} | ||
@@ -363,4 +310,4 @@ // 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 | ||
if (!router.isRouteErrorResponse(err) || err.error) { | ||
// @ts-expect-error `err.error` is "private" from users but intended for internal use | ||
if ((!router.isRouteErrorResponse(err) || err.error) && !singleFetch.isResponseStub(err)) { | ||
handleError(err); | ||
@@ -371,3 +318,11 @@ } | ||
} | ||
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 = { | ||
@@ -382,10 +337,12 @@ manifest: build.assets, | ||
criticalCss, | ||
state: { | ||
loaderData: context.loaderData, | ||
actionData: context.actionData, | ||
errors: errors.serializeErrors(context.errors, serverMode) | ||
}, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.unstable_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.unstable_singleFetch ? { | ||
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
} : null), | ||
future: build.future, | ||
@@ -397,3 +354,3 @@ isSpaMode: build.isSpaMode, | ||
try { | ||
return await handleDocumentRequestFunction(request, context.statusCode, headers$1, entryContext, loadContext); | ||
return await handleDocumentRequestFunction(request, statusCode, headers$1, entryContext, loadContext); | ||
} catch (error) { | ||
@@ -422,3 +379,11 @@ handleError(error); | ||
// 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 = { | ||
@@ -430,10 +395,12 @@ ...entryContext, | ||
basename: build.basename, | ||
state: { | ||
loaderData: context.loaderData, | ||
actionData: context.actionData, | ||
errors: errors.serializeErrors(context.errors, serverMode) | ||
}, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode | ||
}) | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.unstable_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.unstable_singleFetch ? { | ||
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
renderMeta: {} | ||
} : null) | ||
}; | ||
@@ -448,4 +415,5 @@ try { | ||
} | ||
async function handleResourceRequest(serverMode, staticHandler, routeId, request, loadContext, handleError) { | ||
async function handleResourceRequest(serverMode, build, staticHandler, routeId, request, loadContext, handleError) { | ||
try { | ||
let responseStubs = build.future.unstable_singleFetch ? singleFetch.getResponseStubs() : {}; | ||
// Note we keep the routeId here to align with the Remix handling of | ||
@@ -456,5 +424,34 @@ // resource routes which doesn't take ?index into account and just takes | ||
routeId, | ||
requestContext: loadContext | ||
requestContext: loadContext, | ||
...(build.future.unstable_singleFetch ? { | ||
unstable_dataStrategy: singleFetch.getSingleFetchResourceRouteDataStrategy({ | ||
responseStubs | ||
}) | ||
} : null) | ||
}); | ||
// callRouteLoader/callRouteAction always return responses | ||
if (typeof response === "object") { | ||
invariant["default"](!(router.UNSAFE_DEFERRED_SYMBOL in response), `You cannot return a \`defer()\` response from a Resource Route. Did you ` + `forget to export a default UI component from the "${routeId}" route?`); | ||
} | ||
if (build.future.unstable_singleFetch) { | ||
let stub = responseStubs[routeId]; | ||
if (responses.isResponse(response)) { | ||
// If a response was returned, we use it's status and we merge our | ||
// response stub headers onto it | ||
let ops = stub[singleFetch.ResponseStubOperationsSymbol]; | ||
for (let [op, ...args] of ops) { | ||
// @ts-expect-error | ||
response.headers[op](...args); | ||
} | ||
} else { | ||
console.warn(deprecations.resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId)); | ||
// Otherwise we create a json Response using the stub | ||
response = responses.json(response, { | ||
status: stub.status, | ||
headers: stub.headers | ||
}); | ||
} | ||
} | ||
// callRouteLoader/callRouteAction always return responses (w/o single fetch). | ||
// With single fetch, users should always be Responses from resource routes | ||
invariant["default"](responses.isResponse(response), "Expected a Response to be returned from queryRoute"); | ||
@@ -510,3 +507,20 @@ return response; | ||
} | ||
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 | ||
}); | ||
} | ||
exports.createRequestHandler = createRequestHandler; |
@@ -5,3 +5,3 @@ import type { HydrationState } from "@remix-run/router"; | ||
export declare function createServerHandoffString<T>(serverHandoff: { | ||
state: ValidateShape<T, HydrationState>; | ||
state?: ValidateShape<T, HydrationState>; | ||
criticalCss?: string; | ||
@@ -8,0 +8,0 @@ url: string; |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -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-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-experimental-1a1e5b488 | ||
* @remix-run/server-runtime v0.0.0-experimental-21befc955 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
{ | ||
"name": "@remix-run/server-runtime", | ||
"version": "0.0.0-experimental-1a1e5b488", | ||
"version": "0.0.0-experimental-21befc955", | ||
"description": "Server runtime for Remix", | ||
@@ -19,3 +19,3 @@ "bugs": { | ||
"dependencies": { | ||
"@remix-run/router": "0.0.0-experimental-5bedc168", | ||
"@remix-run/router": "0.0.0-experimental-9c60e757e", | ||
"@types/cookie": "^0.6.0", | ||
@@ -26,3 +26,3 @@ "@web3-storage/multipart-parser": "^1.0.0", | ||
"source-map": "^0.7.3", | ||
"turbo-stream": "^1.2.1" | ||
"turbo-stream": "^2.0.0" | ||
}, | ||
@@ -49,3 +49,6 @@ "devDependencies": { | ||
"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
84
5544
213636
+ Added@remix-run/router@0.0.0-experimental-9c60e757e(transitive)
+ Addedturbo-stream@2.4.1(transitive)
- Removed@remix-run/router@0.0.0-experimental-5bedc168(transitive)
- Removedturbo-stream@1.2.1(transitive)
Updatedturbo-stream@^2.0.0