import { negotiateRoute } from "./content-negotiation.js"; import { runWithRouterLogContext, withRouterLogScope } from "./logging.js"; import type { EntryData } from "../server/context"; import type { RouteMatchResult } from "./pattern-matching.js"; import type { MiddlewareFn } from "./middleware.js"; import { resolveRoute } from "./route-snapshot.js"; export interface PreviewMatchDeps { findMatch: (pathname: string) => RouteMatchResult | null; } /** * Preview match - returns route middleware without segment resolution. * Also returns responseType and handler for response routes (non-RSC short-circuit). */ export async function previewMatch( request: Request, _context: TEnv, deps: PreviewMatchDeps, ): Promise<{ routeMiddleware?: Array<{ handler: MiddlewareFn; params: Record; }>; responseType?: string; handler?: Function; params?: Record; negotiated?: boolean; manifestEntry?: EntryData; routeKey?: string; } | null> { return runWithRouterLogContext( { request, transaction: "previewMatch" }, async () => withRouterLogScope("previewMatch", async () => { const url = new URL(request.url); const pathname = url.pathname; // Route resolution via snapshot (lite mode: skip entries/cacheScope // since previewMatch only needs matched, manifestEntry, routeMiddleware, // and responseType) const result = await resolveRoute(pathname, { findMatch: deps.findMatch, lite: true, }); if (!result) { return null; } // Skip redirect check - will be handled in full match if (result.type === "redirect") { return { routeMiddleware: undefined }; } const snapshot = result.snapshot; const { matched, manifestEntry, routeMiddleware, responseType } = snapshot; const negotiation = await negotiateRoute(request, pathname, snapshot); if (negotiation) { return { routeMiddleware: negotiation.routeMiddleware.length > 0 ? negotiation.routeMiddleware : undefined, responseType: negotiation.responseType, handler: negotiation.handler, params: matched.params, manifestEntry: negotiation.manifestEntry, routeKey: matched.routeKey, // omitted unless a variant negotiated, preserving the prior public // shape (absent for plain response routes, not negotiated:false) ...(negotiation.negotiated ? { negotiated: true } : {}), }; } // No negotiation or RSC won — return default route info const hasVariants = matched.negotiateVariants && matched.negotiateVariants.length > 0; return { routeMiddleware: routeMiddleware.length > 0 ? routeMiddleware : undefined, params: matched.params, routeKey: matched.routeKey, ...(responseType ? { responseType, handler: manifestEntry.type === "route" ? manifestEntry.handler : undefined, manifestEntry, } : {}), ...(hasVariants ? { negotiated: true } : {}), }; }), ); }