@rangojs/router 0.0.0-experimental.002d056c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +5153 -0
  5. package/package.json +177 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +253 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +638 -0
  43. package/src/browser/navigation-client.ts +261 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +582 -0
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +145 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +128 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +368 -0
  55. package/src/browser/react/NavigationProvider.tsx +413 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +464 -0
  79. package/src/browser/scroll-restoration.ts +397 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +547 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +479 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +982 -0
  105. package/src/cache/cf/index.ts +29 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +44 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +281 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +160 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +397 -0
  172. package/src/router/lazy-includes.ts +236 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +251 -0
  175. package/src/router/manifest.ts +269 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +193 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +749 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +320 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1242 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +291 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1006 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +237 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +920 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +109 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +108 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +48 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +363 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +266 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +445 -0
  298. package/src/vite/router-discovery.ts +777 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,402 @@
1
+ import { type ReactNode } from "react";
2
+ import { createHandleStore } from "../server/handle-store.js";
3
+ import { getRequestContext } from "../server/request-context.js";
4
+ import {
5
+ runWithRequestContext,
6
+ type RequestContext,
7
+ } from "../server/request-context.js";
8
+ import { contextGet, contextSet } from "../context-var.js";
9
+ import {
10
+ createPrerenderContext,
11
+ createStaticContext,
12
+ createReverseFunction,
13
+ } from "./handler-context.js";
14
+ import { isPrerenderPassthrough } from "../prerender.js";
15
+ import { isRouteRootScoped } from "../route-map-builder.js";
16
+ import { setupBuildUse } from "./loader-resolution.js";
17
+ import { loadManifest } from "./manifest.js";
18
+ import { traverseBack } from "./pattern-matching.js";
19
+ import type { RouterContext } from "./router-context.js";
20
+ import { runWithRouterContext } from "./router-context.js";
21
+ import type { EntryData, InterceptEntry } from "../server/context";
22
+ import type {
23
+ HandlerContext,
24
+ InternalHandlerContext,
25
+ ResolvedSegment,
26
+ } from "../types";
27
+ import type {
28
+ SerializedSegmentData,
29
+ SegmentHandleData,
30
+ } from "../cache/types.js";
31
+ import type { RouteMatchResult } from "./pattern-matching.js";
32
+
33
+ export interface PrerenderMatchDeps<TEnv = any> {
34
+ findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
35
+ buildRouterContext: () => RouterContext<TEnv>;
36
+ mergedRouteMap: Record<string, string>;
37
+ resolveAllSegments: (
38
+ entries: EntryData[],
39
+ routeKey: string,
40
+ params: Record<string, string>,
41
+ context: HandlerContext<any, TEnv>,
42
+ loaderPromises: Map<string, Promise<any>>,
43
+ options?: { skipLoaders?: boolean },
44
+ ) => Promise<ResolvedSegment[]>;
45
+ }
46
+
47
+ /**
48
+ * Build-time pre-render match. Resolves segments with a BuildContext
49
+ * (no request/env/headers/cookies), skipping middleware and loaders.
50
+ */
51
+ export async function matchForPrerender<TEnv = any>(
52
+ pathname: string,
53
+ params: Record<string, string>,
54
+ deps: PrerenderMatchDeps<TEnv>,
55
+ buildVars?: Record<string, any>,
56
+ isPassthroughRoute?: boolean,
57
+ ): Promise<{
58
+ segments: SerializedSegmentData[];
59
+ handles: Record<string, SegmentHandleData>;
60
+ routeName: string;
61
+ params: Record<string, string>;
62
+ interceptSegments?: SerializedSegmentData[];
63
+ interceptHandles?: Record<string, SegmentHandleData>;
64
+ passthrough?: true;
65
+ } | null> {
66
+ // 1. Find the matching route entry
67
+ const matched = deps.findMatch(pathname);
68
+ if (!matched) return null;
69
+
70
+ // Use params from trie match if available, fall back to provided params
71
+ const matchedParams = matched.params ?? params;
72
+ const matchedPassthroughRoute = isPassthroughRoute ?? matched.pt === true;
73
+
74
+ // Build RouterContext for loadManifest/traverseBack
75
+ const routerCtx = deps.buildRouterContext();
76
+
77
+ return runWithRouterContext(routerCtx, async () => {
78
+ // 2. Load the manifest entry tree
79
+ const manifestEntry = await loadManifest(
80
+ matched.entry,
81
+ matched.routeKey,
82
+ pathname,
83
+ undefined,
84
+ false,
85
+ );
86
+
87
+ // 3. Build ancestor chain [root, ..., route]
88
+ const entries: EntryData[] = [];
89
+ for (const entry of traverseBack(manifestEntry)) {
90
+ entries.push(entry);
91
+ }
92
+
93
+ // 4. Create handle store for collecting handle data
94
+ const handleStore = createHandleStore();
95
+
96
+ // 5. Create a minimal request context with the handle store
97
+ // Shallow-copy getParams vars so each param set is independent
98
+ const variables: Record<string, any> = buildVars ? { ...buildVars } : {};
99
+ const stubRes = new Response(null, { status: 200 });
100
+ const minimalRequestContext: RequestContext<TEnv> = {
101
+ env: {} as TEnv,
102
+ request: new Request("http://prerender" + pathname),
103
+ url: new URL("http://prerender" + pathname),
104
+ originalUrl: new URL("http://prerender" + pathname),
105
+ pathname,
106
+ searchParams: new URLSearchParams(),
107
+ var: variables,
108
+ get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
109
+ set: ((keyOrVar: any, value: any) => {
110
+ contextSet(variables, keyOrVar, value);
111
+ }) as any,
112
+ params: matchedParams,
113
+ res: stubRes,
114
+ cookie: () => undefined,
115
+ cookies: () => ({}),
116
+ setCookie: () => {},
117
+ deleteCookie: () => {},
118
+ header: () => {},
119
+ setStatus: () => {},
120
+ _setStatus: () => {},
121
+ use: (() => {
122
+ throw new Error("use() not available during pre-rendering");
123
+ }) as any,
124
+ method: "GET",
125
+ _handleStore: handleStore,
126
+ waitUntil: () => {},
127
+ onResponse: () => {},
128
+ _onResponseCallbacks: [],
129
+ setLocationState() {},
130
+ _locationState: undefined,
131
+ _reportedErrors: new WeakSet<object>(),
132
+ reverse: createReverseFunction(
133
+ deps.mergedRouteMap,
134
+ matched.routeKey,
135
+ matchedParams,
136
+ matched.routeKey ? isRouteRootScoped(matched.routeKey) : undefined,
137
+ ),
138
+ };
139
+
140
+ return runWithRequestContext(minimalRequestContext, async () => {
141
+ // 6. Create prerender context with synthetic URL.
142
+ // Prerender handlers get params, pathname, url, searchParams, search,
143
+ // reverse, and use(handle) — but no request, env, headers, or cookies.
144
+ const buildCtx = createPrerenderContext<TEnv>(
145
+ matchedParams,
146
+ pathname,
147
+ deps.mergedRouteMap,
148
+ matched.routeKey,
149
+ variables,
150
+ matchedPassthroughRoute,
151
+ );
152
+
153
+ // 7. Wire use() for handles only (loaders throw)
154
+ setupBuildUse(buildCtx);
155
+
156
+ // 8. Resolve all segments with skipLoaders
157
+ const loaderPromises = new Map<string, Promise<any>>();
158
+ const allSegments = await deps.resolveAllSegments(
159
+ entries,
160
+ matched.routeKey,
161
+ matchedParams,
162
+ buildCtx,
163
+ loaderPromises,
164
+ { skipLoaders: true },
165
+ );
166
+
167
+ // 9. Detect passthrough sentinel: handler returned ctx.passthrough()
168
+ for (const seg of allSegments) {
169
+ if (isPrerenderPassthrough(seg.component)) {
170
+ return {
171
+ segments: [],
172
+ handles: {},
173
+ routeName: matched.routeKey,
174
+ params: matchedParams,
175
+ passthrough: true as const,
176
+ };
177
+ }
178
+ }
179
+
180
+ // 10. Filter out any loader segments (belt-and-suspenders)
181
+ const nonLoaderSegments = allSegments.filter((s) => s.type !== "loader");
182
+
183
+ // 11. Wait for handles to settle
184
+ handleStore.seal();
185
+ await handleStore.settled;
186
+
187
+ // 12. Serialize segments using the cache serializer
188
+ const { serializeSegments } = await import("../cache/segment-codec.js");
189
+ const serializedSegments = await serializeSegments(nonLoaderSegments);
190
+
191
+ // 13. Collect handle data per segment (skip segments with no handle data)
192
+ const handles: Record<string, SegmentHandleData> = {};
193
+ for (const seg of nonLoaderSegments) {
194
+ const segHandles = handleStore.getDataForSegment(seg.id);
195
+ if (Object.keys(segHandles).length > 0) {
196
+ handles[seg.id] = segHandles;
197
+ }
198
+ }
199
+
200
+ // Use the trie-level route key (e.g., "docs", "docs.article")
201
+ const routeName = matched.routeKey;
202
+
203
+ // 14. Resolve intercept segments for this route (if any ancestor defines
204
+ // an intercept targeting this route). At build time we skip when()
205
+ // evaluation -- we pre-render all intercepts unconditionally and let
206
+ // runtime matching decide which to serve.
207
+ let interceptSegments: SerializedSegmentData[] | undefined;
208
+ let interceptHandles: Record<string, SegmentHandleData> | undefined;
209
+
210
+ const foundIntercepts: {
211
+ intercept: InterceptEntry;
212
+ entry: EntryData;
213
+ }[] = [];
214
+ let current: EntryData | null = manifestEntry;
215
+ while (current) {
216
+ if (current.intercept && current.intercept.length > 0) {
217
+ for (const ic of current.intercept) {
218
+ if (ic.routeName === matched.routeKey) {
219
+ foundIntercepts.push({ intercept: ic, entry: current });
220
+ }
221
+ }
222
+ }
223
+ if (current.layout && current.layout.length > 0) {
224
+ for (const siblingLayout of current.layout) {
225
+ if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
226
+ for (const ic of siblingLayout.intercept) {
227
+ if (ic.routeName === matched.routeKey) {
228
+ foundIntercepts.push({
229
+ intercept: ic,
230
+ entry: siblingLayout,
231
+ });
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
237
+ current = current.parent;
238
+ }
239
+
240
+ if (foundIntercepts.length > 0) {
241
+ const interceptResolvedSegments: typeof nonLoaderSegments = [];
242
+
243
+ for (const { intercept, entry: parentEntry } of foundIntercepts) {
244
+ // Resolve handler
245
+ const handlerRaw =
246
+ typeof intercept.handler === "function"
247
+ ? intercept.handler(buildCtx)
248
+ : intercept.handler;
249
+ const handlerResolved =
250
+ handlerRaw instanceof Promise ? await handlerRaw : handlerRaw;
251
+ if (handlerResolved instanceof Response) {
252
+ // Handler returned a redirect/response -- skip this intercept
253
+ continue;
254
+ }
255
+ const component: ReactNode = handlerResolved;
256
+
257
+ // Resolve layout (if any)
258
+ let layoutElement: ReactNode | undefined;
259
+ if (intercept.layout) {
260
+ if (typeof intercept.layout === "function") {
261
+ const layoutResult = await intercept.layout(buildCtx);
262
+ if (layoutResult instanceof Response) continue;
263
+ layoutElement = layoutResult;
264
+ } else {
265
+ layoutElement = intercept.layout;
266
+ }
267
+ }
268
+
269
+ interceptResolvedSegments.push({
270
+ id: `${parentEntry.shortCode}.${intercept.slotName}`,
271
+ namespace: `intercept:${intercept.routeName}`,
272
+ type: "parallel" as const,
273
+ index: 0,
274
+ component,
275
+ loading: intercept.loading === false ? null : intercept.loading,
276
+ layout: layoutElement,
277
+ params: matchedParams,
278
+ slot: intercept.slotName,
279
+ belongsToRoute: true,
280
+ parallelName: `intercept:${intercept.routeName}.${intercept.slotName}`,
281
+ });
282
+ }
283
+
284
+ if (interceptResolvedSegments.length > 0) {
285
+ // Wait for handles again (intercept handlers may have called use())
286
+ await handleStore.settled;
287
+ interceptSegments = await serializeSegments(
288
+ interceptResolvedSegments,
289
+ );
290
+ interceptHandles = {};
291
+ for (const seg of interceptResolvedSegments) {
292
+ const segHandles = handleStore.getDataForSegment(seg.id);
293
+ if (Object.keys(segHandles).length > 0) {
294
+ interceptHandles[seg.id] = segHandles;
295
+ }
296
+ }
297
+ }
298
+ }
299
+
300
+ return {
301
+ segments: serializedSegments,
302
+ handles,
303
+ routeName,
304
+ params: matchedParams,
305
+ interceptSegments,
306
+ interceptHandles,
307
+ };
308
+ });
309
+ });
310
+ }
311
+
312
+ /**
313
+ * Render a single Static handler at build time.
314
+ * Creates a minimal BuildContext, calls the handler, and RSC-serializes
315
+ * the component. Returns the encoded Flight string (or null on failure).
316
+ * Used by the Vite plugin to collect static segment data at build time.
317
+ */
318
+ export async function renderStaticSegment<TEnv = any>(
319
+ handler: Function,
320
+ handlerId: string,
321
+ mergedRouteMap: Record<string, string>,
322
+ routeName?: string,
323
+ ): Promise<{ encoded: string; handles: Record<string, unknown[]> } | null> {
324
+ const syntheticUrl = new URL("http://prerender/");
325
+ const syntheticRequest = new Request(syntheticUrl);
326
+
327
+ // Create a HandleStore to capture handle data pushed during rendering
328
+ const handleStore = createHandleStore();
329
+
330
+ // Minimal request context so setupBuildUse can find the HandleStore
331
+ const stubRes = new Response(null, { status: 200 });
332
+ const minimalRequestContext: RequestContext<TEnv> = {
333
+ env: {} as TEnv,
334
+ request: syntheticRequest,
335
+ url: syntheticUrl,
336
+ originalUrl: syntheticUrl,
337
+ pathname: "/",
338
+ searchParams: syntheticUrl.searchParams,
339
+ var: {},
340
+ get: () => undefined as any,
341
+ set: () => {},
342
+ params: {},
343
+ res: stubRes,
344
+ cookie: () => undefined,
345
+ cookies: () => ({}),
346
+ setCookie: () => {},
347
+ deleteCookie: () => {},
348
+ header: () => {},
349
+ setStatus: () => {},
350
+ _setStatus: () => {},
351
+ use: (() => {
352
+ throw new Error("use() not available during static pre-rendering");
353
+ }) as any,
354
+ method: "GET",
355
+ _handleStore: handleStore,
356
+ waitUntil: () => {},
357
+ onResponse: () => {},
358
+ _onResponseCallbacks: [],
359
+ setLocationState() {},
360
+ _locationState: undefined,
361
+ _reportedErrors: new WeakSet<object>(),
362
+ reverse: createReverseFunction(
363
+ mergedRouteMap,
364
+ routeName,
365
+ {},
366
+ routeName ? isRouteRootScoped(routeName) : undefined,
367
+ ),
368
+ };
369
+
370
+ return runWithRequestContext(minimalRequestContext, async () => {
371
+ // Static handlers get only reverse and use(handle) — no URL, params,
372
+ // request, env, headers, or cookies.
373
+ const buildCtx = createStaticContext<TEnv>(mergedRouteMap, routeName);
374
+
375
+ // Set segment ID so handle pushes are keyed correctly
376
+ (buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
377
+ handlerId;
378
+
379
+ setupBuildUse(buildCtx);
380
+
381
+ const raw = await handler(buildCtx);
382
+ const component = raw?.type ? raw : raw;
383
+
384
+ const segment: ResolvedSegment = {
385
+ id: handlerId,
386
+ namespace: handlerId,
387
+ type: "layout",
388
+ index: 0,
389
+ component,
390
+ params: {},
391
+ belongsToRoute: false,
392
+ };
393
+
394
+ const { serializeSegments } = await import("../cache/segment-codec.js");
395
+ const [serialized] = await serializeSegments([segment]);
396
+
397
+ // Collect handle data pushed during rendering
398
+ const handles = handleStore.getDataForSegment(handlerId);
399
+
400
+ return { encoded: serialized.encoded, handles };
401
+ });
402
+ }
@@ -0,0 +1,170 @@
1
+ import { loadManifest } from "./manifest.js";
2
+ import { traverseBack } from "./pattern-matching.js";
3
+ import { collectRouteMiddleware } from "./middleware.js";
4
+ import {
5
+ parseAcceptTypes,
6
+ RSC_RESPONSE_TYPE,
7
+ pickNegotiateVariant,
8
+ } from "./content-negotiation.js";
9
+ import { runWithRouterLogContext, withRouterLogScope } from "./logging.js";
10
+ import type { EntryData } from "../server/context";
11
+ import type { RouteMatchResult } from "./pattern-matching.js";
12
+ import type { MiddlewareFn } from "./middleware.js";
13
+
14
+ export interface PreviewMatchDeps<TEnv = any> {
15
+ findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
16
+ }
17
+
18
+ /**
19
+ * Preview match - returns route middleware without segment resolution.
20
+ * Also returns responseType and handler for response routes (non-RSC short-circuit).
21
+ */
22
+ export async function previewMatch<TEnv = any>(
23
+ request: Request,
24
+ _context: TEnv,
25
+ deps: PreviewMatchDeps<TEnv>,
26
+ ): Promise<{
27
+ routeMiddleware?: Array<{
28
+ handler: MiddlewareFn;
29
+ params: Record<string, string>;
30
+ }>;
31
+ responseType?: string;
32
+ handler?: Function;
33
+ params?: Record<string, string>;
34
+ negotiated?: boolean;
35
+ manifestEntry?: EntryData;
36
+ routeKey?: string;
37
+ } | null> {
38
+ return runWithRouterLogContext(
39
+ { request, transaction: "previewMatch" },
40
+ async () =>
41
+ withRouterLogScope("previewMatch", async () => {
42
+ const url = new URL(request.url);
43
+ const pathname = url.pathname;
44
+
45
+ // Quick route matching
46
+ const matched = deps.findMatch(pathname);
47
+ if (!matched) {
48
+ return null;
49
+ }
50
+
51
+ // Skip redirect check - will be handled in full match
52
+ if (matched.redirectTo) {
53
+ return { routeMiddleware: undefined };
54
+ }
55
+
56
+ // Load manifest (without segment resolution)
57
+ const manifestEntry = await loadManifest(
58
+ matched.entry,
59
+ matched.routeKey,
60
+ pathname,
61
+ undefined, // No metrics store for preview
62
+ false, // isSSR - doesn't matter for preview
63
+ );
64
+
65
+ // Collect route-level middleware from entry tree
66
+ // Includes middleware from orphan layouts (inline layouts within routes)
67
+ const routeMiddleware = collectRouteMiddleware(
68
+ traverseBack(manifestEntry),
69
+ matched.params,
70
+ );
71
+
72
+ // Check for response type (from trie match or manifest entry)
73
+ const responseType =
74
+ matched.responseType ||
75
+ (manifestEntry.type === "route"
76
+ ? manifestEntry.responseType
77
+ : undefined);
78
+
79
+ // Content negotiation: when negotiate variants exist, pick the best
80
+ // handler based on the Accept header. Uses q-values and client order
81
+ // as tiebreaker (matching Express/Hono behavior). RSC routes participate
82
+ // as text/html candidates so browsers naturally get HTML without
83
+ // special-casing.
84
+ if (matched.negotiateVariants && matched.negotiateVariants.length > 0) {
85
+ const acceptEntries = parseAcceptTypes(
86
+ request.headers.get("accept") || "",
87
+ );
88
+
89
+ // Build candidate list preserving definition order.
90
+ // For wildcard (*/*) and no-Accept fallback, the first candidate wins.
91
+ const variants = matched.negotiateVariants;
92
+ let candidates: Array<{ routeKey: string; responseType: string }>;
93
+ if (responseType) {
94
+ // Primary is response-type — include it as a candidate
95
+ candidates = [
96
+ ...variants,
97
+ { routeKey: matched.routeKey, responseType },
98
+ ];
99
+ } else {
100
+ // Primary is RSC — insert as text/html candidate in definition order
101
+ const rscCandidate = {
102
+ routeKey: matched.routeKey,
103
+ responseType: RSC_RESPONSE_TYPE,
104
+ };
105
+ candidates = matched.rscFirst
106
+ ? [rscCandidate, ...variants]
107
+ : [...variants, rscCandidate];
108
+ }
109
+
110
+ const variant = pickNegotiateVariant(acceptEntries, candidates);
111
+
112
+ // If the winner is RSC, fall through to default RSC handling
113
+ if (variant.responseType === RSC_RESPONSE_TYPE) {
114
+ // Fall through — RSC won negotiation
115
+ } else if (responseType && variant.routeKey === matched.routeKey) {
116
+ // Fall through — response-type primary won, already set
117
+ } else {
118
+ const negotiateEntry = await loadManifest(
119
+ matched.entry,
120
+ variant.routeKey,
121
+ pathname,
122
+ undefined,
123
+ false,
124
+ );
125
+ // Recompute middleware from the selected variant's entry tree
126
+ // since different variants can have different middleware chains.
127
+ const variantMiddleware = collectRouteMiddleware(
128
+ traverseBack(negotiateEntry),
129
+ matched.params,
130
+ );
131
+ return {
132
+ routeMiddleware:
133
+ variantMiddleware.length > 0 ? variantMiddleware : undefined,
134
+ responseType: variant.responseType,
135
+ handler:
136
+ negotiateEntry.type === "route"
137
+ ? negotiateEntry.handler
138
+ : undefined,
139
+ params: matched.params,
140
+ negotiated: true,
141
+ manifestEntry: negotiateEntry,
142
+ routeKey: matched.routeKey,
143
+ };
144
+ }
145
+ }
146
+
147
+ // If we passed through the negotiation block (variants exist), mark as
148
+ // negotiated so the handler sets Vary: Accept on the response.
149
+ const hasVariants =
150
+ matched.negotiateVariants && matched.negotiateVariants.length > 0;
151
+ return {
152
+ routeMiddleware:
153
+ routeMiddleware.length > 0 ? routeMiddleware : undefined,
154
+ params: matched.params,
155
+ routeKey: matched.routeKey,
156
+ ...(responseType
157
+ ? {
158
+ responseType,
159
+ handler:
160
+ manifestEntry.type === "route"
161
+ ? manifestEntry.handler
162
+ : undefined,
163
+ manifestEntry,
164
+ }
165
+ : {}),
166
+ ...(hasVariants ? { negotiated: true } : {}),
167
+ };
168
+ }),
169
+ );
170
+ }