@remix-run/server-runtime
Advanced tools
Comparing version 0.0.0-nightly-430d4b5f8-20240521 to 0.0.0-nightly-435cbd1da-20241118
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
import type { ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs } from "./routeModules"; | ||
import type { ResponseStub } from "./single-fetch"; | ||
/** | ||
@@ -16,3 +15,3 @@ * An object of unknown type for route loaders and actions provided by the | ||
export type AppData = unknown; | ||
export declare function callRouteAction({ loadContext, action, params, request, routeId, singleFetch, response, }: { | ||
export declare function callRouteAction({ loadContext, action, params, request, routeId, singleFetch, }: { | ||
request: Request; | ||
@@ -24,5 +23,4 @@ action: ActionFunction; | ||
singleFetch: boolean; | ||
response?: ResponseStub; | ||
}): Promise<{} | Response | null>; | ||
export declare function callRouteLoader({ loadContext, loader, params, request, routeId, singleFetch, response, }: { | ||
export declare function callRouteLoader({ loadContext, loader, params, request, routeId, singleFetch, }: { | ||
request: Request; | ||
@@ -34,3 +32,2 @@ loader: LoaderFunction; | ||
singleFetch: boolean; | ||
response?: ResponseStub; | ||
}): Promise<{} | Response | null>; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -34,14 +34,8 @@ * Copyright (c) Remix Software Inc. | ||
routeId, | ||
singleFetch, | ||
response | ||
singleFetch | ||
}) { | ||
let result = await action({ | ||
request: stripDataParam(stripIndexParam(request)), | ||
request: singleFetch ? stripRoutesParam(stripIndexParam(request)) : stripDataParam(stripIndexParam(request)), | ||
context: loadContext, | ||
params, | ||
// Only provided when single fetch is enabled, and made available via | ||
// `defineAction` types, not `ActionFunctionArgs` | ||
...(singleFetch ? { | ||
response | ||
} : null) | ||
params | ||
}); | ||
@@ -64,14 +58,8 @@ if (result === undefined) { | ||
routeId, | ||
singleFetch, | ||
response | ||
singleFetch | ||
}) { | ||
let result = await loader({ | ||
request: stripDataParam(stripIndexParam(request)), | ||
request: singleFetch ? stripRoutesParam(stripIndexParam(request)) : stripDataParam(stripIndexParam(request)), | ||
context: loadContext, | ||
params, | ||
// Only provided when single fetch is enabled, and made available via | ||
// `defineLoader` types, not `LoaderFunctionArgs` | ||
...(singleFetch ? { | ||
response | ||
} : null) | ||
params | ||
}); | ||
@@ -138,4 +126,18 @@ if (result === undefined) { | ||
} | ||
function stripRoutesParam(request) { | ||
let url = new URL(request.url); | ||
url.searchParams.delete("_routes"); | ||
let init = { | ||
method: request.method, | ||
body: request.body, | ||
headers: request.headers, | ||
signal: request.signal | ||
}; | ||
if (init.body) { | ||
init.duplex = "half"; | ||
} | ||
return new Request(url.href, init); | ||
} | ||
exports.callRouteAction = callRouteAction; | ||
exports.callRouteLoader = callRouteLoader; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -16,5 +16,5 @@ * Copyright (c) Remix Software Inc. | ||
function resourceRouteJsonWarning(type, routeId) { | ||
return "⚠️ REMIX FUTURE CHANGE: Resource routes will no longer be able to " + "return raw JavaScript objects in v3 when Single Fetch becomes the default. " + "You can prepare for this change at your convenience by wrapping the data " + `returned from your \`${type}\` function in the \`${routeId}\` route with ` + "`json()`. For instructions on making this change see " + "https://remix.run/docs/en/v2.9.2/guides/single-fetch#resource-routes"; | ||
return "⚠️ REMIX FUTURE CHANGE: Externally-accessed resource routes will no longer be " + "able to return raw JavaScript objects or `null` in React Router v7 when " + "Single Fetch becomes the default. You can prepare for this change at your " + `convenience by wrapping the data returned from your \`${type}\` function in ` + `the \`${routeId}\` route with \`json()\`. For instructions on making this ` + "change, see https://remix.run/docs/en/v2.13.1/guides/single-fetch#resource-routes"; | ||
} | ||
exports.resourceRouteJsonWarning = resourceRouteJsonWarning; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -30,3 +30,4 @@ import type { StaticHandlerContext } from "@remix-run/router"; | ||
v3_throwAbortReason: boolean; | ||
unstable_singleFetch: boolean; | ||
v3_lazyRouteDiscovery: boolean; | ||
v3_singleFetch: boolean; | ||
} | ||
@@ -33,0 +34,0 @@ export interface AssetsManifest { |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -30,14 +30,8 @@ * Copyright (c) Remix Software Inc. | ||
routeId, | ||
singleFetch, | ||
response | ||
singleFetch | ||
}) { | ||
let result = await action({ | ||
request: stripDataParam(stripIndexParam(request)), | ||
request: singleFetch ? stripRoutesParam(stripIndexParam(request)) : stripDataParam(stripIndexParam(request)), | ||
context: loadContext, | ||
params, | ||
// Only provided when single fetch is enabled, and made available via | ||
// `defineAction` types, not `ActionFunctionArgs` | ||
...(singleFetch ? { | ||
response | ||
} : null) | ||
params | ||
}); | ||
@@ -60,14 +54,8 @@ if (result === undefined) { | ||
routeId, | ||
singleFetch, | ||
response | ||
singleFetch | ||
}) { | ||
let result = await loader({ | ||
request: stripDataParam(stripIndexParam(request)), | ||
request: singleFetch ? stripRoutesParam(stripIndexParam(request)) : stripDataParam(stripIndexParam(request)), | ||
context: loadContext, | ||
params, | ||
// Only provided when single fetch is enabled, and made available via | ||
// `defineLoader` types, not `LoaderFunctionArgs` | ||
...(singleFetch ? { | ||
response | ||
} : null) | ||
params | ||
}); | ||
@@ -134,3 +122,17 @@ if (result === undefined) { | ||
} | ||
function stripRoutesParam(request) { | ||
let url = new URL(request.url); | ||
url.searchParams.delete("_routes"); | ||
let init = { | ||
method: request.method, | ||
body: request.body, | ||
headers: request.headers, | ||
signal: request.signal | ||
}; | ||
if (init.body) { | ||
init.duplex = "half"; | ||
} | ||
return new Request(url.href, init); | ||
} | ||
export { callRouteAction, callRouteLoader }; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -12,5 +12,5 @@ * Copyright (c) Remix Software Inc. | ||
function resourceRouteJsonWarning(type, routeId) { | ||
return "⚠️ REMIX FUTURE CHANGE: Resource routes will no longer be able to " + "return raw JavaScript objects in v3 when Single Fetch becomes the default. " + "You can prepare for this change at your convenience by wrapping the data " + `returned from your \`${type}\` function in the \`${routeId}\` route with ` + "`json()`. For instructions on making this change see " + "https://remix.run/docs/en/v2.9.2/guides/single-fetch#resource-routes"; | ||
return "⚠️ REMIX FUTURE CHANGE: Externally-accessed resource routes will no longer be " + "able to return raw JavaScript objects or `null` in React Router v7 when " + "Single Fetch becomes the default. You can prepare for this change at your " + `convenience by wrapping the data returned from your \`${type}\` function in ` + `the \`${routeId}\` route with \`json()\`. For instructions on making this ` + "change, see https://remix.run/docs/en/v2.13.1/guides/single-fetch#resource-routes"; | ||
} | ||
export { resourceRouteJsonWarning }; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -13,4 +13,4 @@ * 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, defineAction as unstable_defineAction, defineLoader as unstable_defineLoader } from './single-fetch.js'; | ||
export { defer, json, redirect, redirectDocument, replace } from './responses.js'; | ||
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, data } from './single-fetch.js'; | ||
export { createRequestHandler } from './server.js'; | ||
@@ -17,0 +17,0 @@ export { createSession, createSessionStorageFactory, isSession } from './sessions.js'; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -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'; | ||
@@ -22,2 +22,7 @@ | ||
* | ||
* @deprecated This utility is deprecated in favor of opting into Single Fetch | ||
* via `future.v3_singleFetch` and returning raw objects. This method will be | ||
* removed in React Router v7. If you need to return a JSON Response, you can | ||
* use `Response.json()`. | ||
* | ||
* @see https://remix.run/utils/json | ||
@@ -32,2 +37,6 @@ */ | ||
* | ||
* @deprecated This utility is deprecated in favor of opting into Single Fetch | ||
* via `future.v3_singleFetch` and returning raw objects. This method will be | ||
* removed in React Router v7. | ||
* | ||
* @see https://remix.run/utils/defer | ||
@@ -49,2 +58,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. | ||
@@ -127,2 +146,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-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -56,4 +56,3 @@ * Copyright (c) Remix Software Inc. | ||
routeId: route.id, | ||
singleFetch: future.unstable_singleFetch === true, | ||
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response | ||
singleFetch: future.v3_singleFetch === true | ||
}) : undefined, | ||
@@ -66,4 +65,3 @@ action: route.module.action ? (args, dataStrategyCtx) => callRouteAction({ | ||
routeId: route.id, | ||
singleFetch: future.unstable_singleFetch === true, | ||
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response | ||
singleFetch: future.v3_singleFetch === true | ||
}) : undefined, | ||
@@ -70,0 +68,0 @@ handle: route.module.handle |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -11,3 +11,3 @@ * Copyright (c) Remix Software Inc. | ||
*/ | ||
import { UNSAFE_DEFERRED_SYMBOL, isRouteErrorResponse, json, UNSAFE_ErrorResponseImpl, getStaticContextFromError, stripBasename, createStaticHandler } from '@remix-run/router'; | ||
import { UNSAFE_DEFERRED_SYMBOL, isRouteErrorResponse, json as json$1, UNSAFE_ErrorResponseImpl, getStaticContextFromError, stripBasename, createStaticHandler } from '@remix-run/router'; | ||
import { createEntryRouteModules } from './entry.js'; | ||
@@ -20,6 +20,6 @@ import { serializeError, sanitizeErrors, serializeErrors } from './errors.js'; | ||
import { createRoutes, createStaticHandlerDataRoutes } from './routes.js'; | ||
import { isRedirectResponse, createDeferredReadableStream, isResponse, isRedirectStatusCode, json as json$1 } from './responses.js'; | ||
import { isRedirectResponse, json, createDeferredReadableStream, isResponse } 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 { getSingleFetchRedirect, encodeViaTurboStream, SINGLE_FETCH_REDIRECT_STATUS, singleFetchAction, singleFetchLoaders, SingleFetchRedirectSymbol } from './single-fetch.js'; | ||
import { resourceRouteJsonWarning } from './deprecations.js'; | ||
@@ -79,4 +79,3 @@ | ||
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 => { | ||
@@ -93,5 +92,24 @@ 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) { | ||
if (_build.future.v3_singleFetch) { | ||
handleError(new Error("Warning: Single fetch-enabled apps should not be making ?_data requests, " + "this is likely to break in the future")); | ||
@@ -111,3 +129,3 @@ } | ||
} | ||
} else if (_build.future.unstable_singleFetch && url.pathname.endsWith(".data")) { | ||
} else if (_build.future.v3_singleFetch && url.pathname.endsWith(".data")) { | ||
let handlerUrl = new URL(request.url); | ||
@@ -124,3 +142,3 @@ handlerUrl.pathname = handlerUrl.pathname.replace(/\.data$/, "").replace(/^\/_root$/, "/"); | ||
if (isRedirectResponse(response)) { | ||
let result = getSingleFetchRedirect(response.status, response.headers); | ||
let result = getSingleFetchRedirect(response.status, response.headers, _build.basename); | ||
if (request.method === "GET") { | ||
@@ -132,5 +150,5 @@ result = { | ||
let headers = new Headers(response.headers); | ||
headers.set("Content-Type", "text/x-turbo"); | ||
headers.set("Content-Type", "text/x-script"); | ||
return new Response(encodeViaTurboStream(result, request.signal, _build.entry.module.streamTimeout, serverMode), { | ||
status: 200, | ||
status: SINGLE_FETCH_REDIRECT_STATUS, | ||
headers | ||
@@ -157,2 +175,24 @@ }); | ||
}; | ||
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) { | ||
@@ -182,8 +222,8 @@ try { | ||
// 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; | ||
} | ||
@@ -196,3 +236,3 @@ if (isRouteErrorResponse(error)) { | ||
handleError(errorInstance); | ||
return json(serializeError(errorInstance, serverMode), { | ||
return json$1(serializeError(errorInstance, serverMode), { | ||
status: 500, | ||
@@ -210,3 +250,3 @@ headers: { | ||
status | ||
} = request.method !== "GET" ? await singleFetchAction(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetchLoaders(serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
} = request.method !== "GET" ? await singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetchLoaders(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
@@ -217,4 +257,17 @@ // 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"); | ||
// 304 responses should not have a body | ||
if (status === 304) { | ||
return new Response(null, { | ||
status: 304, | ||
headers: resultHeaders | ||
}); | ||
} | ||
// 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 | ||
@@ -229,7 +282,5 @@ // `activeDeferreds` or anything :) | ||
let context; | ||
let responseStubs = getResponseStubs(); | ||
try { | ||
context = await staticHandler.query(request, { | ||
requestContext: loadContext, | ||
unstable_dataStrategy: build.future.unstable_singleFetch ? getSingleFetchDataStrategy(responseStubs) : undefined | ||
requestContext: loadContext | ||
}); | ||
@@ -245,17 +296,10 @@ } 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); | ||
let headers = getDocumentHeaders(build, context); | ||
// 304 responses should not have a body or a content-type | ||
if (context.statusCode === 304) { | ||
return new Response(null, { | ||
status: 304, | ||
headers | ||
}); | ||
} | ||
@@ -267,3 +311,3 @@ | ||
// @ts-expect-error `err.error` is "private" from users but intended for internal use | ||
if ((!isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { | ||
if (!isRouteErrorResponse(err) || err.error) { | ||
handleError(err); | ||
@@ -289,3 +333,2 @@ } | ||
serverHandoffString: createServerHandoffString({ | ||
url: context.location.pathname, | ||
basename: build.basename, | ||
@@ -295,7 +338,7 @@ criticalCss, | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.unstable_singleFetch ? { | ||
...(!build.future.v3_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.unstable_singleFetch ? { | ||
...(build.future.v3_singleFetch ? { | ||
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
@@ -310,3 +353,3 @@ renderMeta: {} | ||
try { | ||
return await handleDocumentRequestFunction(request, statusCode, headers, entryContext, loadContext); | ||
return await handleDocumentRequestFunction(request, context.statusCode, headers, entryContext, loadContext); | ||
} catch (error) { | ||
@@ -348,11 +391,10 @@ handleError(error); | ||
serverHandoffString: createServerHandoffString({ | ||
url: context.location.pathname, | ||
basename: build.basename, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.unstable_singleFetch ? { | ||
...(!build.future.v3_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.unstable_singleFetch ? { | ||
...(build.future.v3_singleFetch ? { | ||
serverHandoffStream: encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
@@ -372,3 +414,2 @@ renderMeta: {} | ||
try { | ||
let responseStubs = build.future.unstable_singleFetch ? getResponseStubs() : {}; | ||
// Note we keep the routeId here to align with the Remix handling of | ||
@@ -379,30 +420,10 @@ // resource routes which doesn't take ?index into account and just takes | ||
routeId, | ||
requestContext: loadContext, | ||
...(build.future.unstable_singleFetch ? { | ||
unstable_dataStrategy: getSingleFetchResourceRouteDataStrategy({ | ||
responseStubs | ||
}) | ||
} : null) | ||
requestContext: loadContext | ||
}); | ||
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) { | ||
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$1(response, { | ||
status: stub.status, | ||
headers: stub.headers | ||
}); | ||
} | ||
if (build.future.v3_singleFetch && !isResponse(response)) { | ||
console.warn(resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId)); | ||
response = json(response); | ||
} | ||
@@ -418,4 +439,4 @@ | ||
// 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; | ||
} | ||
@@ -433,3 +454,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 | ||
@@ -482,2 +503,18 @@ errorResponse.error || new Error("Unexpected Server Error"), serverMode), { | ||
// Anytime we are setting a header on a `Response` created in the loader/action, | ||
// we have to so it in this manner since in an `undici` world, if the `Response` | ||
// came directly from a `fetch` call, the headers are immutable will throw if | ||
// we try to set a new header. This is a sort of shallow clone of the `Response` | ||
// so we can safely set our own header. | ||
function safelySetHeader(response, name, value) { | ||
let headers = new Headers(response.headers); | ||
headers.set(name, value); | ||
return new Response(response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers, | ||
duplex: response.body ? "half" : undefined | ||
}); | ||
} | ||
export { createRequestHandler }; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -11,11 +11,17 @@ * Copyright (c) Remix Software Inc. | ||
*/ | ||
import { UNSAFE_ErrorResponseImpl, isRouteErrorResponse } from '@remix-run/router'; | ||
import { data as data$1, isRouteErrorResponse, stripBasename, UNSAFE_ErrorResponseImpl } from '@remix-run/router'; | ||
import { encode } from 'turbo-stream'; | ||
import { sanitizeError, sanitizeErrors } from './errors.js'; | ||
import { sanitizeErrors, sanitizeError } from './errors.js'; | ||
import { getDocumentHeaders } from './headers.js'; | ||
import { ServerMode } from './mode.js'; | ||
import { isResponse, isDeferredData, isRedirectStatusCode } from './responses.js'; | ||
import { isResponse, isRedirectStatusCode } from './responses.js'; | ||
const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect"); | ||
const ResponseStubActionSymbol = Symbol("ResponseStubAction"); | ||
function getSingleFetchDataStrategy(responseStubs, { | ||
// We can't use a 3xx status or else the `fetch()` would follow the redirect. | ||
// We need to communicate the redirect back as data so we can act on it in the | ||
// client side router. We use a 202 to avoid any automatic caching we might | ||
// get from a 200 since a "temporary" redirect should not be cached. This lets | ||
// the user control cache behavior via Cache-Control | ||
const SINGLE_FETCH_REDIRECT_STATUS = 202; | ||
function getSingleFetchDataStrategy({ | ||
isActionDataRequest, | ||
@@ -30,63 +36,14 @@ loadRouteIds | ||
if (isActionDataRequest && request.method === "GET") { | ||
return await Promise.all(matches.map(m => m.resolve(async () => ({ | ||
type: "data", | ||
result: null | ||
})))); | ||
return {}; | ||
} | ||
let results = await Promise.all(matches.map(async match => { | ||
let responseStub; | ||
if (request.method !== "GET") { | ||
responseStub = responseStubs[ResponseStubActionSymbol]; | ||
} else { | ||
responseStub = responseStubs[match.route.id]; | ||
} | ||
let result = await match.resolve(async handler => { | ||
// Cast `ResponseStubImpl -> ResponseStub` to hide the symbol in userland | ||
let ctx = { | ||
response: responseStub | ||
}; | ||
// Only run opt-in loaders when fine-grained revalidation is enabled | ||
let data = loadRouteIds && !loadRouteIds.includes(match.route.id) ? null : await handler(ctx); | ||
return { | ||
type: "data", | ||
result: data | ||
}; | ||
}); | ||
// Transfer raw Response status/headers to responseStubs | ||
if (isResponse(result.result)) { | ||
proxyResponseToResponseStub(result.result.status, result.result.headers, responseStub); | ||
} else if (isDeferredData(result.result) && result.result.init) { | ||
proxyResponseToResponseStub(result.result.init.status, new Headers(result.result.init.headers), responseStub); | ||
} | ||
return result; | ||
})); | ||
return results; | ||
// Only run opt-in loaders when fine-grained revalidation is enabled | ||
let matchesToLoad = loadRouteIds ? matches.filter(m => loadRouteIds.includes(m.route.id)) : matches; | ||
let results = await Promise.all(matchesToLoad.map(match => match.resolve())); | ||
return results.reduce((acc, result, i) => Object.assign(acc, { | ||
[matchesToLoad[i].route.id]: result | ||
}), {}); | ||
}; | ||
} | ||
function getSingleFetchResourceRouteDataStrategy({ | ||
responseStubs | ||
}) { | ||
return async ({ | ||
matches | ||
}) => { | ||
let results = await Promise.all(matches.map(async match => { | ||
let responseStub = match.shouldLoad ? responseStubs[match.route.id] : null; | ||
let result = await match.resolve(async handler => { | ||
// Cast `ResponseStubImpl -> ResponseStub` to hide the symbol in userland | ||
let ctx = { | ||
response: responseStub | ||
}; | ||
let data = await handler(ctx); | ||
return { | ||
type: "data", | ||
result: data | ||
}; | ||
}); | ||
return result; | ||
})); | ||
return results; | ||
}; | ||
} | ||
async function singleFetchAction(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
async function singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
try { | ||
@@ -102,7 +59,6 @@ let handlerRequest = new Request(handlerUrl, { | ||
}); | ||
let responseStubs = getResponseStubs(); | ||
let result = await staticHandler.query(handlerRequest, { | ||
requestContext: loadContext, | ||
skipLoaderErrorBubbling: true, | ||
unstable_dataStrategy: getSingleFetchDataStrategy(responseStubs, { | ||
dataStrategy: getSingleFetchDataStrategy({ | ||
isActionDataRequest: true | ||
@@ -116,20 +72,14 @@ }) | ||
return { | ||
result: getSingleFetchRedirect(result.status, result.headers), | ||
result: getSingleFetchRedirect(result.status, result.headers, build.basename), | ||
headers: result.headers, | ||
status: 200 | ||
status: SINGLE_FETCH_REDIRECT_STATUS | ||
}; | ||
} | ||
let context = result; | ||
let singleFetchResult; | ||
let { | ||
statusCode, | ||
headers | ||
} = mergeResponseStubs(context, responseStubs, { | ||
isActionDataRequest: true | ||
}); | ||
if (isRedirectStatusCode(statusCode) && headers.has("Location")) { | ||
let headers = getDocumentHeaders(build, context); | ||
if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) { | ||
return { | ||
result: getSingleFetchRedirect(statusCode, headers), | ||
result: getSingleFetchRedirect(context.statusCode, headers, build.basename), | ||
headers, | ||
status: 200 // Don't want the `fetch` call to follow the redirect | ||
status: SINGLE_FETCH_REDIRECT_STATUS | ||
}; | ||
@@ -142,3 +92,3 @@ } | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
if ((!isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { | ||
if (!isRouteErrorResponse(err) || err.error) { | ||
handleError(err); | ||
@@ -149,6 +99,6 @@ } | ||
} | ||
let singleFetchResult; | ||
if (context.errors) { | ||
let error = Object.values(context.errors)[0]; | ||
singleFetchResult = { | ||
error: isResponseStub(error) ? null : error | ||
error: Object.values(context.errors)[0] | ||
}; | ||
@@ -163,3 +113,3 @@ } else { | ||
headers, | ||
status: statusCode | ||
status: context.statusCode | ||
}; | ||
@@ -178,3 +128,3 @@ } catch (error) { | ||
} | ||
async function singleFetchLoaders(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
async function singleFetchLoaders(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
try { | ||
@@ -187,7 +137,6 @@ var _URL$searchParams$get; | ||
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 responseStubs = getResponseStubs(); | ||
let result = await staticHandler.query(handlerRequest, { | ||
requestContext: loadContext, | ||
skipLoaderErrorBubbling: true, | ||
unstable_dataStrategy: getSingleFetchDataStrategy(responseStubs, { | ||
dataStrategy: getSingleFetchDataStrategy({ | ||
loadRouteIds | ||
@@ -199,20 +148,17 @@ }) | ||
result: { | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(result.status, result.headers) | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(result.status, result.headers, build.basename) | ||
}, | ||
headers: result.headers, | ||
status: 200 // Don't want the `fetch` call to follow the redirect | ||
status: SINGLE_FETCH_REDIRECT_STATUS | ||
}; | ||
} | ||
let context = result; | ||
let { | ||
statusCode, | ||
headers | ||
} = mergeResponseStubs(context, responseStubs); | ||
if (isRedirectStatusCode(statusCode) && headers.has("Location")) { | ||
let headers = getDocumentHeaders(build, context); | ||
if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) { | ||
return { | ||
result: { | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(statusCode, headers) | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(context.statusCode, headers, build.basename) | ||
}, | ||
headers, | ||
status: 200 // Don't want the `fetch` call to follow the redirect | ||
status: SINGLE_FETCH_REDIRECT_STATUS | ||
}; | ||
@@ -225,3 +171,3 @@ } | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
if ((!isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { | ||
if (!isRouteErrorResponse(err) || err.error) { | ||
handleError(err); | ||
@@ -242,11 +188,5 @@ } | ||
if (error !== undefined) { | ||
if (isResponseStub(error)) { | ||
results[m.route.id] = { | ||
error: null | ||
}; | ||
} else { | ||
results[m.route.id] = { | ||
error | ||
}; | ||
} | ||
results[m.route.id] = { | ||
error | ||
}; | ||
} else if (data !== undefined) { | ||
@@ -261,3 +201,3 @@ results[m.route.id] = { | ||
headers, | ||
status: statusCode | ||
status: context.statusCode | ||
}; | ||
@@ -278,102 +218,9 @@ } catch (error) { | ||
} | ||
function isResponseStub(value) { | ||
return value && typeof value === "object" && ResponseStubOperationsSymbol in value; | ||
} | ||
function getResponseStub(status) { | ||
let headers = new Headers(); | ||
let operations = []; | ||
let headersProxy = new Proxy(headers, { | ||
get(target, prop, receiver) { | ||
if (prop === "set" || prop === "append" || prop === "delete") { | ||
return (name, value) => { | ||
operations.push([prop, name, value]); | ||
Reflect.apply(target[prop], target, [name, value]); | ||
}; | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
} | ||
}); | ||
return { | ||
status, | ||
headers: headersProxy, | ||
[ResponseStubOperationsSymbol]: operations | ||
}; | ||
} | ||
function getResponseStubs() { | ||
return new Proxy({}, { | ||
get(responseStubCache, prop) { | ||
let cached = responseStubCache[prop]; | ||
if (!cached) { | ||
responseStubCache[prop] = cached = getResponseStub(); | ||
} | ||
return cached; | ||
} | ||
}); | ||
} | ||
function proxyResponseToResponseStub(status, headers, responseStub) { | ||
if (status != null && responseStub.status == null) { | ||
responseStub.status = status; | ||
function getSingleFetchRedirect(status, headers, basename) { | ||
let redirect = headers.get("Location"); | ||
if (basename) { | ||
redirect = stripBasename(redirect, basename) || redirect; | ||
} | ||
for (let [k, v] of headers) { | ||
if (k.toLowerCase() !== "set-cookie") { | ||
responseStub.headers.set(k, v); | ||
} | ||
} | ||
// Unsure why this is complaining? It's fine in VSCode but fails with tsc... | ||
// @ts-ignore - ignoring instead of expecting because otherwise build fails locally | ||
for (let v of headers.getSetCookie()) { | ||
responseStub.headers.append("Set-Cookie", v); | ||
} | ||
} | ||
function mergeResponseStubs(context, responseStubs, { | ||
isActionDataRequest | ||
} = {}) { | ||
let statusCode = undefined; | ||
let headers = new Headers(); | ||
// Action followed by top-down loaders | ||
let actionStub = responseStubs[ResponseStubActionSymbol]; | ||
let stubs = [actionStub]; | ||
// Nothing to merge at the route level on action data requests | ||
if (!isActionDataRequest) { | ||
stubs.push(...context.matches.map(m => responseStubs[m.route.id])); | ||
} | ||
for (let stub of stubs) { | ||
// Take the highest error/redirect, or the lowest success value - preferring | ||
// action 200's over loader 200s | ||
if ( | ||
// first status found on the way down | ||
statusCode === undefined && stub.status || | ||
// deeper 2xx status found while not overriding the action status | ||
statusCode !== undefined && statusCode < 300 && stub.status && statusCode !== (actionStub === null || actionStub === void 0 ? void 0 : actionStub.status)) { | ||
statusCode = stub.status; | ||
} | ||
// Replay headers operations in order | ||
let ops = stub[ResponseStubOperationsSymbol]; | ||
for (let [op, ...args] of ops) { | ||
// @ts-expect-error | ||
headers[op](...args); | ||
} | ||
} | ||
// If no response stubs set it, use whatever we got back from the router | ||
// context which handles internal ErrorResponse cases like 404/405's where | ||
// we may never run a loader/action | ||
if (statusCode === undefined) { | ||
statusCode = context.statusCode; | ||
} | ||
if (statusCode === undefined) { | ||
statusCode = 200; | ||
} | ||
return { | ||
statusCode, | ||
headers | ||
}; | ||
} | ||
function getSingleFetchRedirect(status, headers) { | ||
return { | ||
redirect: headers.get("Location"), | ||
redirect, | ||
status, | ||
@@ -389,3 +236,4 @@ revalidate: | ||
headers.has("X-Remix-Revalidate") || headers.has("Set-Cookie"), | ||
reload: headers.has("X-Remix-Reload-Document") | ||
reload: headers.has("X-Remix-Reload-Document"), | ||
replace: headers.has("X-Remix-Replace") | ||
}; | ||
@@ -431,27 +279,17 @@ } | ||
} | ||
}] | ||
}], | ||
postPlugins: [value => { | ||
if (!value) return; | ||
if (typeof value !== "object") return; | ||
return ["SingleFetchClassInstance", Object.fromEntries(Object.entries(value))]; | ||
}, () => ["SingleFetchFallback"]] | ||
}); | ||
} | ||
function data(value, init) { | ||
return data$1(value, init); | ||
} | ||
// Backwards-compatible type for Remix v2 where json/defer still use the old types, | ||
// and only non-json/defer returns use the new types. This allows for incremental | ||
// migration of loaders to return naked objects. In the next major version, | ||
// json/defer will be removed so everything will use the new simplified typings. | ||
// prettier-ignore | ||
// eslint-disable-next-line | ||
const ResponseStubOperationsSymbol = Symbol("ResponseStubOperations"); | ||
/** | ||
* A stubbed response to let you set the status/headers of your response from | ||
* loader/action functions | ||
*/ | ||
// loader | ||
let defineLoader = loader => loader; | ||
// action | ||
let defineAction = action => action; | ||
export { ResponseStubOperationsSymbol, SingleFetchRedirectSymbol, defineAction, defineLoader, encodeViaTurboStream, getResponseStubs, getSingleFetchDataStrategy, getSingleFetchRedirect, getSingleFetchResourceRouteDataStrategy, isResponseStub, mergeResponseStubs, singleFetchAction, singleFetchLoaders }; | ||
export { SINGLE_FETCH_REDIRECT_STATUS, SingleFetchRedirectSymbol, data, encodeViaTurboStream, getSingleFetchDataStrategy, getSingleFetchRedirect, singleFetchAction, singleFetchLoaders }; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
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 { defer, json, redirect, redirectDocument, replace } from "./responses"; | ||
export { SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol, data, } from "./single-fetch"; | ||
export type { SingleFetchResult as UNSAFE_SingleFetchResult, SingleFetchResults as UNSAFE_SingleFetchResults, } from "./single-fetch"; | ||
export { createRequestHandler } from "./server"; | ||
@@ -14,2 +14,3 @@ export { createSession, createSessionStorageFactory, isSession, } from "./sessions"; | ||
export type { CreateCookieFunction, CreateCookieSessionStorageFunction, CreateMemorySessionStorageFunction, CreateRequestHandlerFunction, CreateSessionFunction, CreateSessionStorageFunction, IsCookieFunction, IsSessionFunction, JsonFunction, RedirectFunction, } from "./interface"; | ||
export type { Future } from "./future"; | ||
export type { ActionFunction, ActionFunctionArgs, AppLoadContext, Cookie, CookieOptions, CookieParseOptions, CookieSerializeOptions, CookieSignatureOptions, DataFunctionArgs, EntryContext, ErrorResponse, FlashSessionData, HandleDataRequestFunction, HandleDocumentRequestFunction, HeadersArgs, HeadersFunction, HtmlLinkDescriptor, LinkDescriptor, LinksFunction, LoaderFunction, LoaderFunctionArgs, MemoryUploadHandlerFilterArgs, MemoryUploadHandlerOptions, HandleErrorFunction, PageLinkDescriptor, RequestHandler, SerializeFrom, ServerBuild, ServerEntryModule, ServerRuntimeMetaArgs, ServerRuntimeMetaDescriptor, ServerRuntimeMetaFunction, Session, SessionData, SessionIdStorageStrategy, SessionStorage, SignFunction, TypedDeferredData, TypedResponse, UnsignFunction, UploadHandler, UploadHandlerPart, } from "./reexport"; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -37,5 +37,5 @@ * Copyright (c) Remix Software Inc. | ||
exports.redirectDocument = responses.redirectDocument; | ||
exports.replace = responses.replace; | ||
exports.UNSAFE_SingleFetchRedirectSymbol = singleFetch.SingleFetchRedirectSymbol; | ||
exports.unstable_defineAction = singleFetch.defineAction; | ||
exports.unstable_defineLoader = singleFetch.defineLoader; | ||
exports.data = singleFetch.data; | ||
exports.createRequestHandler = server.createRequestHandler; | ||
@@ -42,0 +42,0 @@ exports.createSession = sessions.createSession; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
export type { ErrorResponse } from "@remix-run/router"; | ||
export type { HandleDataRequestFunction, HandleDocumentRequestFunction, HandleErrorFunction, ServerBuild, ServerEntryModule, } from "./build"; | ||
export type { Future } from "./future"; | ||
export type { UploadHandlerPart, UploadHandler } from "./formData"; | ||
@@ -4,0 +5,0 @@ export type { MemoryUploadHandlerOptions, MemoryUploadHandlerFilterArgs, } from "./upload/memoryUploadHandler"; |
@@ -17,2 +17,7 @@ import { type UNSAFE_DeferredData as DeferredData } from "@remix-run/router"; | ||
* | ||
* @deprecated This utility is deprecated in favor of opting into Single Fetch | ||
* via `future.v3_singleFetch` and returning raw objects. This method will be | ||
* removed in React Router v7. If you need to return a JSON Response, you can | ||
* use `Response.json()`. | ||
* | ||
* @see https://remix.run/utils/json | ||
@@ -24,2 +29,6 @@ */ | ||
* | ||
* @deprecated This utility is deprecated in favor of opting into Single Fetch | ||
* via `future.v3_singleFetch` and returning raw objects. This method will be | ||
* removed in React Router v7. | ||
* | ||
* @see https://remix.run/utils/defer | ||
@@ -37,2 +46,9 @@ */ | ||
/** | ||
* 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. | ||
@@ -39,0 +55,0 @@ * Sets the status code and the `Location` header. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -25,2 +25,7 @@ * Copyright (c) Remix Software Inc. | ||
* | ||
* @deprecated This utility is deprecated in favor of opting into Single Fetch | ||
* via `future.v3_singleFetch` and returning raw objects. This method will be | ||
* removed in React Router v7. If you need to return a JSON Response, you can | ||
* use `Response.json()`. | ||
* | ||
* @see https://remix.run/utils/json | ||
@@ -35,2 +40,6 @@ */ | ||
* | ||
* @deprecated This utility is deprecated in favor of opting into Single Fetch | ||
* via `future.v3_singleFetch` and returning raw objects. This method will be | ||
* removed in React Router v7. | ||
* | ||
* @see https://remix.run/utils/defer | ||
@@ -52,2 +61,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 router.replace(url, init); | ||
}; | ||
/** | ||
* A redirect response that will force a document reload to the new location. | ||
@@ -139,1 +158,2 @@ * Sets the status code and the `Location` header. | ||
exports.redirectDocument = redirectDocument; | ||
exports.replace = replace; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -5,3 +5,2 @@ 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> { | ||
@@ -25,3 +24,2 @@ [routeId: string]: RouteModule | undefined; | ||
context: AppLoadContext; | ||
response?: ResponseStub; | ||
}; | ||
@@ -49,3 +47,2 @@ /** | ||
context: AppLoadContext; | ||
response?: ResponseStub; | ||
}; | ||
@@ -52,0 +49,0 @@ /** |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -60,4 +60,3 @@ * Copyright (c) Remix Software Inc. | ||
routeId: route.id, | ||
singleFetch: future.unstable_singleFetch === true, | ||
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response | ||
singleFetch: future.v3_singleFetch === true | ||
}) : undefined, | ||
@@ -70,4 +69,3 @@ action: route.module.action ? (args, dataStrategyCtx) => data.callRouteAction({ | ||
routeId: route.id, | ||
singleFetch: future.unstable_singleFetch === true, | ||
response: dataStrategyCtx === null || dataStrategyCtx === void 0 ? void 0 : dataStrategyCtx.response | ||
singleFetch: future.v3_singleFetch === true | ||
}) : undefined, | ||
@@ -74,0 +72,0 @@ handle: route.module.handle |
import type { Jsonify } from "./jsonify"; | ||
import type { TypedDeferredData, TypedResponse } from "./responses"; | ||
import type { ClientActionFunctionArgs, ClientLoaderFunctionArgs } from "./routeModules"; | ||
import { type SerializeFrom as SingleFetch_SerializeFrom } from "./single-fetch"; | ||
import type { Future } from "./future"; | ||
type SingleFetchEnabled = Future extends { | ||
v3_singleFetch: infer T extends boolean; | ||
} ? T : false; | ||
/** | ||
@@ -11,4 +16,9 @@ * Infer JSON serialized data type returned by a loader or action, while | ||
* `type LoaderData = SerializeFrom<typeof loader>` | ||
* | ||
* @deprecated SerializeFrom is deprecated and will be removed in React Router | ||
* v7. Please use the generics on `useLoaderData`/etc. instead of manually | ||
* deserializing in Remix v2. You can convert to the generated types once you | ||
* migrate to React Router v7. | ||
*/ | ||
export type SerializeFrom<T> = T extends (...args: any[]) => infer Output ? Parameters<T> extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs] ? SerializeClient<Awaited<Output>> : Serialize<Awaited<Output>> : Jsonify<Awaited<T>>; | ||
export type SerializeFrom<T> = SingleFetchEnabled extends true ? SingleFetch_SerializeFrom<T> : T extends (...args: any[]) => infer Output ? Parameters<T> extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs] ? SerializeClient<Awaited<Output>> : Serialize<Awaited<Output>> : Jsonify<Awaited<T>>; | ||
type SerializeClient<Output> = Output extends TypedDeferredData<infer U> ? { | ||
@@ -15,0 +25,0 @@ [K in keyof U as K extends symbol ? never : Promise<any> extends U[K] ? K : never]: DeferValueClient<U[K]>; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -81,4 +81,3 @@ * Copyright (c) Remix Software Inc. | ||
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 => { | ||
@@ -95,5 +94,24 @@ 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) { | ||
if (_build.future.v3_singleFetch) { | ||
handleError(new Error("Warning: Single fetch-enabled apps should not be making ?_data requests, " + "this is likely to break in the future")); | ||
@@ -113,3 +131,3 @@ } | ||
} | ||
} else if (_build.future.unstable_singleFetch && url.pathname.endsWith(".data")) { | ||
} else if (_build.future.v3_singleFetch && url.pathname.endsWith(".data")) { | ||
let handlerUrl = new URL(request.url); | ||
@@ -126,3 +144,3 @@ handlerUrl.pathname = handlerUrl.pathname.replace(/\.data$/, "").replace(/^\/_root$/, "/"); | ||
if (responses.isRedirectResponse(response)) { | ||
let result = singleFetch.getSingleFetchRedirect(response.status, response.headers); | ||
let result = singleFetch.getSingleFetchRedirect(response.status, response.headers, _build.basename); | ||
if (request.method === "GET") { | ||
@@ -134,5 +152,5 @@ result = { | ||
let headers = new Headers(response.headers); | ||
headers.set("Content-Type", "text/x-turbo"); | ||
headers.set("Content-Type", "text/x-script"); | ||
return new Response(singleFetch.encodeViaTurboStream(result, request.signal, _build.entry.module.streamTimeout, serverMode), { | ||
status: 200, | ||
status: singleFetch.SINGLE_FETCH_REDIRECT_STATUS, | ||
headers | ||
@@ -159,2 +177,24 @@ }); | ||
}; | ||
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) { | ||
@@ -184,8 +224,8 @@ try { | ||
// 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; | ||
} | ||
@@ -211,3 +251,3 @@ if (router.isRouteErrorResponse(error)) { | ||
status | ||
} = request.method !== "GET" ? await singleFetch.singleFetchAction(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetch.singleFetchLoaders(serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
} = request.method !== "GET" ? await singleFetch.singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) : await singleFetch.singleFetchLoaders(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError); | ||
@@ -218,4 +258,17 @@ // 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"); | ||
// 304 responses should not have a body | ||
if (status === 304) { | ||
return new Response(null, { | ||
status: 304, | ||
headers: resultHeaders | ||
}); | ||
} | ||
// 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 | ||
@@ -230,7 +283,5 @@ // `activeDeferreds` or anything :) | ||
let context; | ||
let responseStubs = singleFetch.getResponseStubs(); | ||
try { | ||
context = await staticHandler.query(request, { | ||
requestContext: loadContext, | ||
unstable_dataStrategy: build.future.unstable_singleFetch ? singleFetch.getSingleFetchDataStrategy(responseStubs) : undefined | ||
requestContext: loadContext | ||
}); | ||
@@ -246,17 +297,10 @@ } 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); | ||
let headers$1 = headers.getDocumentHeaders(build, context); | ||
// 304 responses should not have a body or a content-type | ||
if (context.statusCode === 304) { | ||
return new Response(null, { | ||
status: 304, | ||
headers: headers$1 | ||
}); | ||
} | ||
@@ -268,3 +312,3 @@ | ||
// @ts-expect-error `err.error` is "private" from users but intended for internal use | ||
if ((!router.isRouteErrorResponse(err) || err.error) && !singleFetch.isResponseStub(err)) { | ||
if (!router.isRouteErrorResponse(err) || err.error) { | ||
handleError(err); | ||
@@ -290,3 +334,2 @@ } | ||
serverHandoffString: serverHandoff.createServerHandoffString({ | ||
url: context.location.pathname, | ||
basename: build.basename, | ||
@@ -296,7 +339,7 @@ criticalCss, | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.unstable_singleFetch ? { | ||
...(!build.future.v3_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.unstable_singleFetch ? { | ||
...(build.future.v3_singleFetch ? { | ||
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
@@ -311,3 +354,3 @@ renderMeta: {} | ||
try { | ||
return await handleDocumentRequestFunction(request, statusCode, headers$1, entryContext, loadContext); | ||
return await handleDocumentRequestFunction(request, context.statusCode, headers$1, entryContext, loadContext); | ||
} catch (error) { | ||
@@ -349,11 +392,10 @@ handleError(error); | ||
serverHandoffString: serverHandoff.createServerHandoffString({ | ||
url: context.location.pathname, | ||
basename: build.basename, | ||
future: build.future, | ||
isSpaMode: build.isSpaMode, | ||
...(!build.future.unstable_singleFetch ? { | ||
...(!build.future.v3_singleFetch ? { | ||
state | ||
} : null) | ||
}), | ||
...(build.future.unstable_singleFetch ? { | ||
...(build.future.v3_singleFetch ? { | ||
serverHandoffStream: singleFetch.encodeViaTurboStream(state, request.signal, build.entry.module.streamTimeout, serverMode), | ||
@@ -373,3 +415,2 @@ renderMeta: {} | ||
try { | ||
let responseStubs = build.future.unstable_singleFetch ? singleFetch.getResponseStubs() : {}; | ||
// Note we keep the routeId here to align with the Remix handling of | ||
@@ -380,30 +421,10 @@ // resource routes which doesn't take ?index into account and just takes | ||
routeId, | ||
requestContext: loadContext, | ||
...(build.future.unstable_singleFetch ? { | ||
unstable_dataStrategy: singleFetch.getSingleFetchResourceRouteDataStrategy({ | ||
responseStubs | ||
}) | ||
} : null) | ||
requestContext: loadContext | ||
}); | ||
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) { | ||
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 | ||
}); | ||
} | ||
if (build.future.v3_singleFetch && !responses.isResponse(response)) { | ||
console.warn(deprecations.resourceRouteJsonWarning(request.method === "GET" ? "loader" : "action", routeId)); | ||
response = responses.json(response); | ||
} | ||
@@ -419,4 +440,4 @@ | ||
// 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; | ||
} | ||
@@ -482,2 +503,18 @@ if (router.isRouteErrorResponse(error)) { | ||
// Anytime we are setting a header on a `Response` created in the loader/action, | ||
// we have to so it in this manner since in an `undici` world, if the `Response` | ||
// came directly from a `fetch` call, the headers are immutable will throw if | ||
// we try to set a new header. This is a sort of shallow clone of the `Response` | ||
// so we can safely set our own header. | ||
function safelySetHeader(response, name, value) { | ||
let headers = new Headers(response.headers); | ||
headers.set(name, value); | ||
return new Response(response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers, | ||
duplex: response.body ? "half" : undefined | ||
}); | ||
} | ||
exports.createRequestHandler = createRequestHandler; |
@@ -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-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
@@ -1,6 +0,8 @@ | ||
import type { ActionFunctionArgs as RRActionArgs, LoaderFunctionArgs as RRLoaderArgs, StaticHandler, unstable_DataStrategyFunction as DataStrategyFunction, StaticHandlerContext } from "@remix-run/router"; | ||
import type { StaticHandler, DataStrategyFunction, UNSAFE_DataWithResponseInit as DataWithResponseInit } from "@remix-run/router"; | ||
import type { ServerBuild } from "./build"; | ||
import type { AppLoadContext } from "./data"; | ||
import { ServerMode } from "./mode"; | ||
import type { TypedDeferredData, TypedResponse } from "./responses"; | ||
import type { SerializeFrom } from "./serialize"; | ||
import type { Jsonify } from "./jsonify"; | ||
import type { ClientActionFunctionArgs, ClientLoaderFunctionArgs } from "./routeModules"; | ||
export declare const SingleFetchRedirectSymbol: unique symbol; | ||
@@ -12,2 +14,3 @@ type SingleFetchRedirectResult = { | ||
reload: boolean; | ||
replace: boolean; | ||
}; | ||
@@ -23,13 +26,8 @@ export type SingleFetchResult = { | ||
}; | ||
export type DataStrategyCtx = { | ||
response: ResponseStub; | ||
}; | ||
export declare function getSingleFetchDataStrategy(responseStubs: ReturnType<typeof getResponseStubs>, { isActionDataRequest, loadRouteIds, }?: { | ||
export declare const SINGLE_FETCH_REDIRECT_STATUS = 202; | ||
export declare function getSingleFetchDataStrategy({ isActionDataRequest, loadRouteIds, }?: { | ||
isActionDataRequest?: boolean; | ||
loadRouteIds?: string[]; | ||
}): DataStrategyFunction; | ||
export declare function getSingleFetchResourceRouteDataStrategy({ responseStubs, }: { | ||
responseStubs: ReturnType<typeof getResponseStubs>; | ||
}): DataStrategyFunction; | ||
export declare function singleFetchAction(serverMode: ServerMode, staticHandler: StaticHandler, request: Request, handlerUrl: URL, loadContext: AppLoadContext, handleError: (err: unknown) => void): Promise<{ | ||
export declare function singleFetchAction(build: ServerBuild, serverMode: ServerMode, staticHandler: StaticHandler, request: Request, handlerUrl: URL, loadContext: AppLoadContext, handleError: (err: unknown) => void): Promise<{ | ||
result: SingleFetchResult; | ||
@@ -39,3 +37,3 @@ headers: Headers; | ||
}>; | ||
export declare function singleFetchLoaders(serverMode: ServerMode, staticHandler: StaticHandler, request: Request, handlerUrl: URL, loadContext: AppLoadContext, handleError: (err: unknown) => void): Promise<{ | ||
export declare function singleFetchLoaders(build: ServerBuild, serverMode: ServerMode, staticHandler: StaticHandler, request: Request, handlerUrl: URL, loadContext: AppLoadContext, handleError: (err: unknown) => void): Promise<{ | ||
result: SingleFetchResults; | ||
@@ -45,47 +43,14 @@ headers: Headers; | ||
}>; | ||
export declare function isResponseStub(value: any): value is ResponseStubImpl; | ||
export declare function getResponseStubs(): Record<string | symbol, ResponseStubImpl>; | ||
export declare function mergeResponseStubs(context: StaticHandlerContext, responseStubs: ReturnType<typeof getResponseStubs>, { isActionDataRequest }?: { | ||
isActionDataRequest?: boolean; | ||
}): { | ||
statusCode: number; | ||
headers: Headers; | ||
}; | ||
export declare function getSingleFetchRedirect(status: number, headers: Headers): SingleFetchRedirectResult; | ||
export declare function getSingleFetchRedirect(status: number, headers: Headers, basename: string | undefined): SingleFetchRedirectResult; | ||
export declare function encodeViaTurboStream(data: any, requestSignal: AbortSignal, streamTimeout: number | undefined, serverMode: ServerMode): ReadableStream<Uint8Array>; | ||
type MaybePromise<T> = T | Promise<T>; | ||
type Serializable = undefined | null | boolean | string | symbol | number | Array<Serializable> | { | ||
export declare function data<T>(value: T, init?: number | ResponseInit): DataWithResponseInit<T>; | ||
type Serializable = undefined | null | boolean | string | symbol | number | bigint | Date | URL | RegExp | Error | ReadonlyArray<Serializable> | Array<Serializable> | { | ||
[key: PropertyKey]: Serializable; | ||
} | bigint | Date | URL | RegExp | Error | Map<Serializable, Serializable> | Set<Serializable> | Promise<Serializable>; | ||
type DataFunctionReturnValue = Serializable | TypedDeferredData<Record<string, unknown>> | TypedResponse<Record<string, unknown>>; | ||
export type Serialize<T extends Loader | Action> = Awaited<ReturnType<T>> extends TypedDeferredData<infer D> ? D : Awaited<ReturnType<T>> extends TypedResponse<Record<string, unknown>> ? SerializeFrom<T> : Awaited<ReturnType<T>>; | ||
export declare const ResponseStubOperationsSymbol: unique symbol; | ||
export type ResponseStubOperation = [ | ||
"set" | "append" | "delete", | ||
string, | ||
string? | ||
]; | ||
/** | ||
* A stubbed response to let you set the status/headers of your response from | ||
* loader/action functions | ||
*/ | ||
export type ResponseStub = { | ||
status: number | undefined; | ||
headers: Headers; | ||
}; | ||
export type ResponseStubImpl = ResponseStub & { | ||
[ResponseStubOperationsSymbol]: ResponseStubOperation[]; | ||
}; | ||
type LoaderArgs = RRLoaderArgs<AppLoadContext> & { | ||
context: AppLoadContext; | ||
response: ResponseStub; | ||
}; | ||
export type Loader = (args: LoaderArgs) => MaybePromise<DataFunctionReturnValue>; | ||
export declare let defineLoader: <T extends Loader>(loader: T) => T; | ||
type ActionArgs = RRActionArgs<AppLoadContext> & { | ||
context: AppLoadContext; | ||
response: ResponseStub; | ||
}; | ||
export type Action = (args: ActionArgs) => MaybePromise<DataFunctionReturnValue>; | ||
export declare let defineAction: <T extends Action>(action: T) => T; | ||
} | Map<Serializable, Serializable> | Set<Serializable> | Promise<Serializable>; | ||
type Serialize<T> = T extends void ? undefined : T extends Serializable ? T : T extends (...args: any[]) => unknown ? undefined : T extends Promise<infer U> ? Promise<Serialize<U>> : T extends Map<infer K, infer V> ? Map<Serialize<K>, Serialize<V>> : T extends Set<infer U> ? Set<Serialize<U>> : T extends [] ? [] : T extends readonly [infer F, ...infer R] ? [Serialize<F>, ...Serialize<R>] : T extends Array<infer U> ? Array<Serialize<U>> : T extends readonly unknown[] ? readonly Serialize<T[number]>[] : T extends Record<any, any> ? { | ||
[K in keyof T]: Serialize<T[K]>; | ||
} : undefined; | ||
type ClientData<T> = T extends TypedResponse<infer U> ? Jsonify<U> : T extends TypedDeferredData<infer U> ? U : T; | ||
type ServerData<T> = T extends TypedResponse<infer U> ? Jsonify<U> : T extends TypedDeferredData<infer U> ? Serialize<U> : T extends DataWithResponseInit<infer U> ? Serialize<U> : Serialize<T>; | ||
export type SerializeFrom<T> = T extends (...args: infer Args) => infer Return ? Args extends [ClientLoaderFunctionArgs | ClientActionFunctionArgs] ? ClientData<Awaited<Return>> : ServerData<Awaited<Return>> : T; | ||
export {}; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -18,2 +18,3 @@ * Copyright (c) Remix Software Inc. | ||
var errors = require('./errors.js'); | ||
var headers = require('./headers.js'); | ||
var mode = require('./mode.js'); | ||
@@ -23,4 +24,9 @@ var responses = require('./responses.js'); | ||
const SingleFetchRedirectSymbol = Symbol("SingleFetchRedirect"); | ||
const ResponseStubActionSymbol = Symbol("ResponseStubAction"); | ||
function getSingleFetchDataStrategy(responseStubs, { | ||
// We can't use a 3xx status or else the `fetch()` would follow the redirect. | ||
// We need to communicate the redirect back as data so we can act on it in the | ||
// client side router. We use a 202 to avoid any automatic caching we might | ||
// get from a 200 since a "temporary" redirect should not be cached. This lets | ||
// the user control cache behavior via Cache-Control | ||
const SINGLE_FETCH_REDIRECT_STATUS = 202; | ||
function getSingleFetchDataStrategy({ | ||
isActionDataRequest, | ||
@@ -35,63 +41,14 @@ loadRouteIds | ||
if (isActionDataRequest && request.method === "GET") { | ||
return await Promise.all(matches.map(m => m.resolve(async () => ({ | ||
type: "data", | ||
result: null | ||
})))); | ||
return {}; | ||
} | ||
let results = await Promise.all(matches.map(async match => { | ||
let responseStub; | ||
if (request.method !== "GET") { | ||
responseStub = responseStubs[ResponseStubActionSymbol]; | ||
} else { | ||
responseStub = responseStubs[match.route.id]; | ||
} | ||
let result = await match.resolve(async handler => { | ||
// Cast `ResponseStubImpl -> ResponseStub` to hide the symbol in userland | ||
let ctx = { | ||
response: responseStub | ||
}; | ||
// Only run opt-in loaders when fine-grained revalidation is enabled | ||
let data = loadRouteIds && !loadRouteIds.includes(match.route.id) ? null : await handler(ctx); | ||
return { | ||
type: "data", | ||
result: data | ||
}; | ||
}); | ||
// Transfer raw Response status/headers to responseStubs | ||
if (responses.isResponse(result.result)) { | ||
proxyResponseToResponseStub(result.result.status, result.result.headers, responseStub); | ||
} else if (responses.isDeferredData(result.result) && result.result.init) { | ||
proxyResponseToResponseStub(result.result.init.status, new Headers(result.result.init.headers), responseStub); | ||
} | ||
return result; | ||
})); | ||
return results; | ||
// Only run opt-in loaders when fine-grained revalidation is enabled | ||
let matchesToLoad = loadRouteIds ? matches.filter(m => loadRouteIds.includes(m.route.id)) : matches; | ||
let results = await Promise.all(matchesToLoad.map(match => match.resolve())); | ||
return results.reduce((acc, result, i) => Object.assign(acc, { | ||
[matchesToLoad[i].route.id]: result | ||
}), {}); | ||
}; | ||
} | ||
function getSingleFetchResourceRouteDataStrategy({ | ||
responseStubs | ||
}) { | ||
return async ({ | ||
matches | ||
}) => { | ||
let results = await Promise.all(matches.map(async match => { | ||
let responseStub = match.shouldLoad ? responseStubs[match.route.id] : null; | ||
let result = await match.resolve(async handler => { | ||
// Cast `ResponseStubImpl -> ResponseStub` to hide the symbol in userland | ||
let ctx = { | ||
response: responseStub | ||
}; | ||
let data = await handler(ctx); | ||
return { | ||
type: "data", | ||
result: data | ||
}; | ||
}); | ||
return result; | ||
})); | ||
return results; | ||
}; | ||
} | ||
async function singleFetchAction(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
async function singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
try { | ||
@@ -107,7 +64,6 @@ let handlerRequest = new Request(handlerUrl, { | ||
}); | ||
let responseStubs = getResponseStubs(); | ||
let result = await staticHandler.query(handlerRequest, { | ||
requestContext: loadContext, | ||
skipLoaderErrorBubbling: true, | ||
unstable_dataStrategy: getSingleFetchDataStrategy(responseStubs, { | ||
dataStrategy: getSingleFetchDataStrategy({ | ||
isActionDataRequest: true | ||
@@ -121,20 +77,14 @@ }) | ||
return { | ||
result: getSingleFetchRedirect(result.status, result.headers), | ||
result: getSingleFetchRedirect(result.status, result.headers, build.basename), | ||
headers: result.headers, | ||
status: 200 | ||
status: SINGLE_FETCH_REDIRECT_STATUS | ||
}; | ||
} | ||
let context = result; | ||
let singleFetchResult; | ||
let { | ||
statusCode, | ||
headers | ||
} = mergeResponseStubs(context, responseStubs, { | ||
isActionDataRequest: true | ||
}); | ||
if (responses.isRedirectStatusCode(statusCode) && headers.has("Location")) { | ||
let headers$1 = headers.getDocumentHeaders(build, context); | ||
if (responses.isRedirectStatusCode(context.statusCode) && headers$1.has("Location")) { | ||
return { | ||
result: getSingleFetchRedirect(statusCode, headers), | ||
headers, | ||
status: 200 // Don't want the `fetch` call to follow the redirect | ||
result: getSingleFetchRedirect(context.statusCode, headers$1, build.basename), | ||
headers: headers$1, | ||
status: SINGLE_FETCH_REDIRECT_STATUS | ||
}; | ||
@@ -147,3 +97,3 @@ } | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
if ((!router.isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { | ||
if (!router.isRouteErrorResponse(err) || err.error) { | ||
handleError(err); | ||
@@ -154,6 +104,6 @@ } | ||
} | ||
let singleFetchResult; | ||
if (context.errors) { | ||
let error = Object.values(context.errors)[0]; | ||
singleFetchResult = { | ||
error: isResponseStub(error) ? null : error | ||
error: Object.values(context.errors)[0] | ||
}; | ||
@@ -167,4 +117,4 @@ } else { | ||
result: singleFetchResult, | ||
headers, | ||
status: statusCode | ||
headers: headers$1, | ||
status: context.statusCode | ||
}; | ||
@@ -183,3 +133,3 @@ } catch (error) { | ||
} | ||
async function singleFetchLoaders(serverMode, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
async function singleFetchLoaders(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) { | ||
try { | ||
@@ -192,7 +142,6 @@ var _URL$searchParams$get; | ||
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 responseStubs = getResponseStubs(); | ||
let result = await staticHandler.query(handlerRequest, { | ||
requestContext: loadContext, | ||
skipLoaderErrorBubbling: true, | ||
unstable_dataStrategy: getSingleFetchDataStrategy(responseStubs, { | ||
dataStrategy: getSingleFetchDataStrategy({ | ||
loadRouteIds | ||
@@ -204,20 +153,17 @@ }) | ||
result: { | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(result.status, result.headers) | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(result.status, result.headers, build.basename) | ||
}, | ||
headers: result.headers, | ||
status: 200 // Don't want the `fetch` call to follow the redirect | ||
status: SINGLE_FETCH_REDIRECT_STATUS | ||
}; | ||
} | ||
let context = result; | ||
let { | ||
statusCode, | ||
headers | ||
} = mergeResponseStubs(context, responseStubs); | ||
if (responses.isRedirectStatusCode(statusCode) && headers.has("Location")) { | ||
let headers$1 = headers.getDocumentHeaders(build, context); | ||
if (responses.isRedirectStatusCode(context.statusCode) && headers$1.has("Location")) { | ||
return { | ||
result: { | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(statusCode, headers) | ||
[SingleFetchRedirectSymbol]: getSingleFetchRedirect(context.statusCode, headers$1, build.basename) | ||
}, | ||
headers, | ||
status: 200 // Don't want the `fetch` call to follow the redirect | ||
headers: headers$1, | ||
status: SINGLE_FETCH_REDIRECT_STATUS | ||
}; | ||
@@ -230,3 +176,3 @@ } | ||
// @ts-expect-error This is "private" from users but intended for internal use | ||
if ((!router.isRouteErrorResponse(err) || err.error) && !isResponseStub(err)) { | ||
if (!router.isRouteErrorResponse(err) || err.error) { | ||
handleError(err); | ||
@@ -247,11 +193,5 @@ } | ||
if (error !== undefined) { | ||
if (isResponseStub(error)) { | ||
results[m.route.id] = { | ||
error: null | ||
}; | ||
} else { | ||
results[m.route.id] = { | ||
error | ||
}; | ||
} | ||
results[m.route.id] = { | ||
error | ||
}; | ||
} else if (data !== undefined) { | ||
@@ -265,4 +205,4 @@ results[m.route.id] = { | ||
result: results, | ||
headers, | ||
status: statusCode | ||
headers: headers$1, | ||
status: context.statusCode | ||
}; | ||
@@ -283,102 +223,9 @@ } catch (error) { | ||
} | ||
function isResponseStub(value) { | ||
return value && typeof value === "object" && ResponseStubOperationsSymbol in value; | ||
} | ||
function getResponseStub(status) { | ||
let headers = new Headers(); | ||
let operations = []; | ||
let headersProxy = new Proxy(headers, { | ||
get(target, prop, receiver) { | ||
if (prop === "set" || prop === "append" || prop === "delete") { | ||
return (name, value) => { | ||
operations.push([prop, name, value]); | ||
Reflect.apply(target[prop], target, [name, value]); | ||
}; | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
} | ||
}); | ||
return { | ||
status, | ||
headers: headersProxy, | ||
[ResponseStubOperationsSymbol]: operations | ||
}; | ||
} | ||
function getResponseStubs() { | ||
return new Proxy({}, { | ||
get(responseStubCache, prop) { | ||
let cached = responseStubCache[prop]; | ||
if (!cached) { | ||
responseStubCache[prop] = cached = getResponseStub(); | ||
} | ||
return cached; | ||
} | ||
}); | ||
} | ||
function proxyResponseToResponseStub(status, headers, responseStub) { | ||
if (status != null && responseStub.status == null) { | ||
responseStub.status = status; | ||
function getSingleFetchRedirect(status, headers, basename) { | ||
let redirect = headers.get("Location"); | ||
if (basename) { | ||
redirect = router.stripBasename(redirect, basename) || redirect; | ||
} | ||
for (let [k, v] of headers) { | ||
if (k.toLowerCase() !== "set-cookie") { | ||
responseStub.headers.set(k, v); | ||
} | ||
} | ||
// Unsure why this is complaining? It's fine in VSCode but fails with tsc... | ||
// @ts-ignore - ignoring instead of expecting because otherwise build fails locally | ||
for (let v of headers.getSetCookie()) { | ||
responseStub.headers.append("Set-Cookie", v); | ||
} | ||
} | ||
function mergeResponseStubs(context, responseStubs, { | ||
isActionDataRequest | ||
} = {}) { | ||
let statusCode = undefined; | ||
let headers = new Headers(); | ||
// Action followed by top-down loaders | ||
let actionStub = responseStubs[ResponseStubActionSymbol]; | ||
let stubs = [actionStub]; | ||
// Nothing to merge at the route level on action data requests | ||
if (!isActionDataRequest) { | ||
stubs.push(...context.matches.map(m => responseStubs[m.route.id])); | ||
} | ||
for (let stub of stubs) { | ||
// Take the highest error/redirect, or the lowest success value - preferring | ||
// action 200's over loader 200s | ||
if ( | ||
// first status found on the way down | ||
statusCode === undefined && stub.status || | ||
// deeper 2xx status found while not overriding the action status | ||
statusCode !== undefined && statusCode < 300 && stub.status && statusCode !== (actionStub === null || actionStub === void 0 ? void 0 : actionStub.status)) { | ||
statusCode = stub.status; | ||
} | ||
// Replay headers operations in order | ||
let ops = stub[ResponseStubOperationsSymbol]; | ||
for (let [op, ...args] of ops) { | ||
// @ts-expect-error | ||
headers[op](...args); | ||
} | ||
} | ||
// If no response stubs set it, use whatever we got back from the router | ||
// context which handles internal ErrorResponse cases like 404/405's where | ||
// we may never run a loader/action | ||
if (statusCode === undefined) { | ||
statusCode = context.statusCode; | ||
} | ||
if (statusCode === undefined) { | ||
statusCode = 200; | ||
} | ||
return { | ||
statusCode, | ||
headers | ||
}; | ||
} | ||
function getSingleFetchRedirect(status, headers) { | ||
return { | ||
redirect: headers.get("Location"), | ||
redirect, | ||
status, | ||
@@ -394,3 +241,4 @@ revalidate: | ||
headers.has("X-Remix-Revalidate") || headers.has("Set-Cookie"), | ||
reload: headers.has("X-Remix-Reload-Document") | ||
reload: headers.has("X-Remix-Reload-Document"), | ||
replace: headers.has("X-Remix-Replace") | ||
}; | ||
@@ -436,39 +284,24 @@ } | ||
} | ||
}] | ||
}], | ||
postPlugins: [value => { | ||
if (!value) return; | ||
if (typeof value !== "object") return; | ||
return ["SingleFetchClassInstance", Object.fromEntries(Object.entries(value))]; | ||
}, () => ["SingleFetchFallback"]] | ||
}); | ||
} | ||
function data(value, init) { | ||
return router.data(value, init); | ||
} | ||
// Backwards-compatible type for Remix v2 where json/defer still use the old types, | ||
// and only non-json/defer returns use the new types. This allows for incremental | ||
// migration of loaders to return naked objects. In the next major version, | ||
// json/defer will be removed so everything will use the new simplified typings. | ||
// prettier-ignore | ||
// eslint-disable-next-line | ||
const ResponseStubOperationsSymbol = Symbol("ResponseStubOperations"); | ||
/** | ||
* A stubbed response to let you set the status/headers of your response from | ||
* loader/action functions | ||
*/ | ||
// loader | ||
let defineLoader = loader => loader; | ||
// action | ||
let defineAction = action => action; | ||
exports.ResponseStubOperationsSymbol = ResponseStubOperationsSymbol; | ||
exports.SINGLE_FETCH_REDIRECT_STATUS = SINGLE_FETCH_REDIRECT_STATUS; | ||
exports.SingleFetchRedirectSymbol = SingleFetchRedirectSymbol; | ||
exports.defineAction = defineAction; | ||
exports.defineLoader = defineLoader; | ||
exports.data = data; | ||
exports.encodeViaTurboStream = encodeViaTurboStream; | ||
exports.getResponseStubs = getResponseStubs; | ||
exports.getSingleFetchDataStrategy = getSingleFetchDataStrategy; | ||
exports.getSingleFetchRedirect = getSingleFetchRedirect; | ||
exports.getSingleFetchResourceRouteDataStrategy = getSingleFetchResourceRouteDataStrategy; | ||
exports.isResponseStub = isResponseStub; | ||
exports.mergeResponseStubs = mergeResponseStubs; | ||
exports.singleFetchAction = singleFetchAction; | ||
exports.singleFetchLoaders = singleFetchLoaders; |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
/** | ||
* @remix-run/server-runtime v0.0.0-nightly-430d4b5f8-20240521 | ||
* @remix-run/server-runtime v0.0.0-nightly-435cbd1da-20241118 | ||
* | ||
@@ -4,0 +4,0 @@ * Copyright (c) Remix Software Inc. |
{ | ||
"name": "@remix-run/server-runtime", | ||
"version": "0.0.0-nightly-430d4b5f8-20240521", | ||
"version": "0.0.0-nightly-435cbd1da-20241118", | ||
"description": "Server runtime for Remix", | ||
@@ -19,3 +19,3 @@ "bugs": { | ||
"dependencies": { | ||
"@remix-run/router": "1.16.1", | ||
"@remix-run/router": "1.21.0", | ||
"@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.4.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
85
204962
5245
+ Added@remix-run/router@1.21.0(transitive)
+ Addedturbo-stream@2.4.0(transitive)
- Removed@remix-run/router@1.16.1(transitive)
- Removedturbo-stream@2.4.1(transitive)
Updated@remix-run/router@1.21.0
Updatedturbo-stream@2.4.0