@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,502 @@
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
+ buildEnv?: TEnv,
58
+ /** Dev-only: check getParams() for passthrough routes to skip unknown params. */
59
+ devMode?: boolean,
60
+ ): Promise<{
61
+ segments: SerializedSegmentData[];
62
+ handles: Record<string, SegmentHandleData>;
63
+ routeName: string;
64
+ params: Record<string, string>;
65
+ interceptSegments?: SerializedSegmentData[];
66
+ interceptHandles?: Record<string, SegmentHandleData>;
67
+ passthrough?: true;
68
+ } | null> {
69
+ // 1. Find the matching route entry
70
+ const matched = deps.findMatch(pathname);
71
+ if (!matched) return null;
72
+
73
+ // Use params from trie match if available, fall back to provided params
74
+ const matchedParams = matched.params ?? params;
75
+ const matchedPassthroughRoute = isPassthroughRoute ?? matched.pt === true;
76
+
77
+ // Build RouterContext for loadManifest/traverseBack
78
+ const routerCtx = deps.buildRouterContext();
79
+
80
+ return runWithRouterContext(routerCtx, async () => {
81
+ // 2. Load the manifest entry tree
82
+ const manifestEntry = await loadManifest(
83
+ matched.entry,
84
+ matched.routeKey,
85
+ pathname,
86
+ undefined,
87
+ false,
88
+ );
89
+
90
+ // 3. Build ancestor chain [root, ..., route]
91
+ const entries: EntryData[] = [];
92
+ for (const entry of traverseBack(manifestEntry)) {
93
+ entries.push(entry);
94
+ }
95
+
96
+ // 3b. Dev-mode passthrough shortcut: if the route is a Passthrough route
97
+ // and has getParams(), check if the matched params are in the known list.
98
+ // In production, only known params are pre-rendered; unknown params fall
99
+ // through to the live handler. Mirror that behavior in dev mode to avoid
100
+ // rendering unknown params with build: true.
101
+ // Vars collected from getParams() probe — merged into render context below.
102
+ let devProbeBuildVars: Record<string, any> | undefined;
103
+
104
+ if (devMode && matchedPassthroughRoute) {
105
+ const routeEntry = entries.find(
106
+ (
107
+ e,
108
+ ): e is EntryData & {
109
+ type: "route";
110
+ prerenderDef: { getParams: (ctx: any) => Promise<any[]> | any[] };
111
+ } =>
112
+ e.type === "route" &&
113
+ !!(e as any).isPassthrough &&
114
+ !!(e as any).prerenderDef?.getParams,
115
+ );
116
+ if (routeEntry) {
117
+ try {
118
+ const probeBuildVars: Record<string, any> = {};
119
+ const knownParamsList = await routeEntry.prerenderDef.getParams({
120
+ build: true as const,
121
+ dev: true,
122
+ set: ((keyOrVar: any, value: any) => {
123
+ contextSet(probeBuildVars, keyOrVar, value);
124
+ }) as any,
125
+ reverse: createReverseFunction(deps.mergedRouteMap),
126
+ get env() {
127
+ if (buildEnv !== undefined) return buildEnv;
128
+ throw new Error(
129
+ "[rsc-router] ctx.env is not available during dev-mode getParams(). " +
130
+ "Configure buildEnv in your rango() plugin options to enable build-time env access.",
131
+ );
132
+ },
133
+ });
134
+ // Compare only the keys returned by getParams — ignore mount params
135
+ // from include() prefixes that aren't part of the handler's params.
136
+ const isKnown = knownParamsList.some((known: Record<string, any>) => {
137
+ const knownKeys = Object.keys(known);
138
+ return knownKeys.every(
139
+ (k) => String(known[k]) === String(matchedParams[k]),
140
+ );
141
+ });
142
+ if (!isKnown) {
143
+ return {
144
+ segments: [],
145
+ handles: {},
146
+ routeName: matched.routeKey,
147
+ params: matchedParams,
148
+ passthrough: true as const,
149
+ };
150
+ }
151
+ // Preserve vars set by getParams() for the render context
152
+ if (
153
+ Object.keys(probeBuildVars).length > 0 ||
154
+ Object.getOwnPropertySymbols(probeBuildVars).length > 0
155
+ ) {
156
+ devProbeBuildVars = probeBuildVars;
157
+ }
158
+ } catch (err: any) {
159
+ // Mirror production semantics (prerender-collection.ts):
160
+ // Skip errors are intentional — treat as passthrough.
161
+ // All other errors propagate so dev surfaces them.
162
+ if (err?.name === "Skip") {
163
+ return {
164
+ segments: [],
165
+ handles: {},
166
+ routeName: matched.routeKey,
167
+ params: matchedParams,
168
+ passthrough: true as const,
169
+ };
170
+ }
171
+ throw err;
172
+ }
173
+ }
174
+ }
175
+
176
+ // 4. Create handle store for collecting handle data
177
+ const handleStore = createHandleStore();
178
+
179
+ // 5. Create a minimal request context with the handle store
180
+ // Shallow-copy getParams vars so each param set is independent.
181
+ // In dev mode, merge vars from the getParams() probe if the caller
182
+ // didn't provide buildVars (production passes them from expandPrerenderRoutes).
183
+ const effectiveBuildVars = buildVars ?? devProbeBuildVars;
184
+ const variables: Record<string, any> = effectiveBuildVars
185
+ ? { ...effectiveBuildVars }
186
+ : {};
187
+ const stubRes = new Response(null, { status: 200 });
188
+ const minimalRequestContext: RequestContext<TEnv> = {
189
+ env: buildEnv ?? ({} as TEnv),
190
+ request: new Request("http://prerender" + pathname),
191
+ url: new URL("http://prerender" + pathname),
192
+ originalUrl: new URL("http://prerender" + pathname),
193
+ pathname,
194
+ searchParams: new URLSearchParams(),
195
+ _variables: variables,
196
+ get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
197
+ set: ((keyOrVar: any, value: any) => {
198
+ contextSet(variables, keyOrVar, value);
199
+ }) as any,
200
+ params: matchedParams,
201
+ res: stubRes,
202
+ cookie: () => undefined,
203
+ cookies: () => ({}),
204
+ setCookie: () => {},
205
+ deleteCookie: () => {},
206
+ header: () => {},
207
+ setStatus: () => {},
208
+ _setStatus: () => {},
209
+ use: (() => {
210
+ throw new Error("use() not available during pre-rendering");
211
+ }) as any,
212
+ method: "GET",
213
+ _handleStore: handleStore,
214
+ waitUntil: () => {},
215
+ onResponse: () => {},
216
+ _onResponseCallbacks: [],
217
+ setLocationState() {},
218
+ _locationState: undefined,
219
+ _renderBarrier: Promise.resolve(),
220
+ _resolveRenderBarrier: () => {},
221
+ _reportedErrors: new WeakSet<object>(),
222
+ reverse: createReverseFunction(
223
+ deps.mergedRouteMap,
224
+ matched.routeKey,
225
+ matchedParams,
226
+ matched.routeKey ? isRouteRootScoped(matched.routeKey) : undefined,
227
+ ),
228
+ };
229
+
230
+ return runWithRequestContext(minimalRequestContext, async () => {
231
+ // 6. Create prerender context with synthetic URL.
232
+ // Prerender handlers get params, pathname, url, searchParams, search,
233
+ // reverse, use(handle), and optionally env (when buildEnv is configured).
234
+ const buildCtx = createPrerenderContext<TEnv>(
235
+ matchedParams,
236
+ pathname,
237
+ deps.mergedRouteMap,
238
+ matched.routeKey,
239
+ variables,
240
+ matchedPassthroughRoute,
241
+ buildEnv,
242
+ devMode,
243
+ );
244
+
245
+ // 7. Wire use() for handles only (loaders throw)
246
+ setupBuildUse(buildCtx);
247
+
248
+ // 8. Resolve all segments with skipLoaders
249
+ const loaderPromises = new Map<string, Promise<any>>();
250
+ const allSegments = await deps.resolveAllSegments(
251
+ entries,
252
+ matched.routeKey,
253
+ matchedParams,
254
+ buildCtx,
255
+ loaderPromises,
256
+ { skipLoaders: true },
257
+ );
258
+
259
+ // 9. Detect passthrough sentinel: handler returned ctx.passthrough()
260
+ for (const seg of allSegments) {
261
+ if (isPrerenderPassthrough(seg.component)) {
262
+ return {
263
+ segments: [],
264
+ handles: {},
265
+ routeName: matched.routeKey,
266
+ params: matchedParams,
267
+ passthrough: true as const,
268
+ };
269
+ }
270
+ }
271
+
272
+ // 10. Filter out any loader segments (belt-and-suspenders)
273
+ const nonLoaderSegments = allSegments.filter((s) => s.type !== "loader");
274
+
275
+ // 11. Wait for handles to settle
276
+ handleStore.seal();
277
+ await handleStore.settled;
278
+
279
+ // 12. Serialize segments using the cache serializer
280
+ const { serializeSegments } = await import("../cache/segment-codec.js");
281
+ const serializedSegments = await serializeSegments(nonLoaderSegments);
282
+
283
+ // 13. Collect handle data per segment (skip segments with no handle data)
284
+ const handles: Record<string, SegmentHandleData> = {};
285
+ for (const seg of nonLoaderSegments) {
286
+ const segHandles = handleStore.getDataForSegment(seg.id);
287
+ if (Object.keys(segHandles).length > 0) {
288
+ handles[seg.id] = segHandles;
289
+ }
290
+ }
291
+
292
+ // Use the trie-level route key (e.g., "docs", "docs.article")
293
+ const routeName = matched.routeKey;
294
+
295
+ // 14. Resolve intercept segments for this route (if any ancestor defines
296
+ // an intercept targeting this route). At build time we skip when()
297
+ // evaluation -- we pre-render all intercepts unconditionally and let
298
+ // runtime matching decide which to serve.
299
+ let interceptSegments: SerializedSegmentData[] | undefined;
300
+ let interceptHandles: Record<string, SegmentHandleData> | undefined;
301
+
302
+ const foundIntercepts: {
303
+ intercept: InterceptEntry;
304
+ entry: EntryData;
305
+ }[] = [];
306
+ let current: EntryData | null = manifestEntry;
307
+ while (current) {
308
+ if (current.intercept && current.intercept.length > 0) {
309
+ for (const ic of current.intercept) {
310
+ if (ic.routeName === matched.routeKey) {
311
+ foundIntercepts.push({ intercept: ic, entry: current });
312
+ }
313
+ }
314
+ }
315
+ if (current.layout && current.layout.length > 0) {
316
+ for (const siblingLayout of current.layout) {
317
+ if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
318
+ for (const ic of siblingLayout.intercept) {
319
+ if (ic.routeName === matched.routeKey) {
320
+ foundIntercepts.push({
321
+ intercept: ic,
322
+ entry: siblingLayout,
323
+ });
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }
329
+ current = current.parent;
330
+ }
331
+
332
+ if (foundIntercepts.length > 0) {
333
+ const interceptResolvedSegments: typeof nonLoaderSegments = [];
334
+
335
+ for (const { intercept, entry: parentEntry } of foundIntercepts) {
336
+ // Resolve handler
337
+ const handlerRaw =
338
+ typeof intercept.handler === "function"
339
+ ? intercept.handler(buildCtx)
340
+ : intercept.handler;
341
+ const handlerResolved =
342
+ handlerRaw instanceof Promise ? await handlerRaw : handlerRaw;
343
+ if (handlerResolved instanceof Response) {
344
+ // Handler returned a redirect/response -- skip this intercept
345
+ continue;
346
+ }
347
+ const component: ReactNode = handlerResolved;
348
+
349
+ // Resolve layout (if any)
350
+ let layoutElement: ReactNode | undefined;
351
+ if (intercept.layout) {
352
+ if (typeof intercept.layout === "function") {
353
+ const layoutResult = await intercept.layout(buildCtx);
354
+ if (layoutResult instanceof Response) continue;
355
+ layoutElement = layoutResult;
356
+ } else {
357
+ layoutElement = intercept.layout;
358
+ }
359
+ }
360
+
361
+ interceptResolvedSegments.push({
362
+ id: `${parentEntry.shortCode}.${intercept.slotName}`,
363
+ namespace: `intercept:${intercept.routeName}`,
364
+ type: "parallel" as const,
365
+ index: 0,
366
+ component,
367
+ loading: intercept.loading === false ? null : intercept.loading,
368
+ layout: layoutElement,
369
+ params: matchedParams,
370
+ slot: intercept.slotName,
371
+ belongsToRoute: true,
372
+ parallelName: `intercept:${intercept.routeName}.${intercept.slotName}`,
373
+ });
374
+ }
375
+
376
+ if (interceptResolvedSegments.length > 0) {
377
+ // Wait for handles again (intercept handlers may have called use())
378
+ await handleStore.settled;
379
+ interceptSegments = await serializeSegments(
380
+ interceptResolvedSegments,
381
+ );
382
+ interceptHandles = {};
383
+ for (const seg of interceptResolvedSegments) {
384
+ const segHandles = handleStore.getDataForSegment(seg.id);
385
+ if (Object.keys(segHandles).length > 0) {
386
+ interceptHandles[seg.id] = segHandles;
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ return {
393
+ segments: serializedSegments,
394
+ handles,
395
+ routeName,
396
+ params: matchedParams,
397
+ interceptSegments,
398
+ interceptHandles,
399
+ };
400
+ });
401
+ });
402
+ }
403
+
404
+ /**
405
+ * Render a single Static handler at build time.
406
+ * Creates a minimal BuildContext, calls the handler, and RSC-serializes
407
+ * the component. Returns the encoded Flight string (or null on failure).
408
+ * Used by the Vite plugin to collect static segment data at build time.
409
+ */
410
+ export async function renderStaticSegment<TEnv = any>(
411
+ handler: Function,
412
+ handlerId: string,
413
+ mergedRouteMap: Record<string, string>,
414
+ routeName?: string,
415
+ buildEnv?: TEnv,
416
+ devMode?: boolean,
417
+ ): Promise<{ encoded: string; handles: Record<string, unknown[]> } | null> {
418
+ const syntheticUrl = new URL("http://prerender/");
419
+ const syntheticRequest = new Request(syntheticUrl);
420
+
421
+ // Create a HandleStore to capture handle data pushed during rendering
422
+ const handleStore = createHandleStore();
423
+
424
+ // Minimal request context so setupBuildUse can find the HandleStore
425
+ const stubRes = new Response(null, { status: 200 });
426
+ const minimalRequestContext: RequestContext<TEnv> = {
427
+ env: buildEnv ?? ({} as TEnv),
428
+ request: syntheticRequest,
429
+ url: syntheticUrl,
430
+ originalUrl: syntheticUrl,
431
+ pathname: "/",
432
+ searchParams: syntheticUrl.searchParams,
433
+ _variables: {},
434
+ get: () => undefined as any,
435
+ set: () => {},
436
+ params: {},
437
+ res: stubRes,
438
+ cookie: () => undefined,
439
+ cookies: () => ({}),
440
+ setCookie: () => {},
441
+ deleteCookie: () => {},
442
+ header: () => {},
443
+ setStatus: () => {},
444
+ _setStatus: () => {},
445
+ use: (() => {
446
+ throw new Error("use() not available during static pre-rendering");
447
+ }) as any,
448
+ method: "GET",
449
+ _handleStore: handleStore,
450
+ waitUntil: () => {},
451
+ onResponse: () => {},
452
+ _onResponseCallbacks: [],
453
+ setLocationState() {},
454
+ _locationState: undefined,
455
+ _renderBarrier: Promise.resolve(),
456
+ _resolveRenderBarrier: () => {},
457
+ _reportedErrors: new WeakSet<object>(),
458
+ reverse: createReverseFunction(
459
+ mergedRouteMap,
460
+ routeName,
461
+ {},
462
+ routeName ? isRouteRootScoped(routeName) : undefined,
463
+ ),
464
+ };
465
+
466
+ return runWithRequestContext(minimalRequestContext, async () => {
467
+ // Static handlers get only reverse, use(handle), and optionally env.
468
+ const buildCtx = createStaticContext<TEnv>(
469
+ mergedRouteMap,
470
+ routeName,
471
+ buildEnv,
472
+ devMode,
473
+ );
474
+
475
+ // Set segment ID so handle pushes are keyed correctly
476
+ (buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
477
+ handlerId;
478
+
479
+ setupBuildUse(buildCtx);
480
+
481
+ const raw = await handler(buildCtx);
482
+ const component = raw?.type ? raw : raw;
483
+
484
+ const segment: ResolvedSegment = {
485
+ id: handlerId,
486
+ namespace: handlerId,
487
+ type: "layout",
488
+ index: 0,
489
+ component,
490
+ params: {},
491
+ belongsToRoute: false,
492
+ };
493
+
494
+ const { serializeSegments } = await import("../cache/segment-codec.js");
495
+ const [serialized] = await serializeSegments([segment]);
496
+
497
+ // Collect handle data pushed during rendering
498
+ const handles = handleStore.getDataForSegment(handlerId);
499
+
500
+ return { encoded: serialized.encoded, handles };
501
+ });
502
+ }
@@ -0,0 +1,98 @@
1
+ import { negotiateRoute } from "./content-negotiation.js";
2
+ import { runWithRouterLogContext, withRouterLogScope } from "./logging.js";
3
+ import type { EntryData } from "../server/context";
4
+ import type { RouteMatchResult } from "./pattern-matching.js";
5
+ import type { MiddlewareFn } from "./middleware.js";
6
+ import { resolveRoute } from "./route-snapshot.js";
7
+
8
+ export interface PreviewMatchDeps<TEnv = any> {
9
+ findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
10
+ }
11
+
12
+ /**
13
+ * Preview match - returns route middleware without segment resolution.
14
+ * Also returns responseType and handler for response routes (non-RSC short-circuit).
15
+ */
16
+ export async function previewMatch<TEnv = any>(
17
+ request: Request,
18
+ _context: TEnv,
19
+ deps: PreviewMatchDeps<TEnv>,
20
+ ): Promise<{
21
+ routeMiddleware?: Array<{
22
+ handler: MiddlewareFn;
23
+ params: Record<string, string>;
24
+ }>;
25
+ responseType?: string;
26
+ handler?: Function;
27
+ params?: Record<string, string>;
28
+ negotiated?: boolean;
29
+ manifestEntry?: EntryData;
30
+ routeKey?: string;
31
+ } | null> {
32
+ return runWithRouterLogContext(
33
+ { request, transaction: "previewMatch" },
34
+ async () =>
35
+ withRouterLogScope("previewMatch", async () => {
36
+ const url = new URL(request.url);
37
+ const pathname = url.pathname;
38
+
39
+ // Route resolution via snapshot (lite mode: skip entries/cacheScope
40
+ // since previewMatch only needs matched, manifestEntry, routeMiddleware,
41
+ // and responseType)
42
+ const result = await resolveRoute<TEnv>(pathname, {
43
+ findMatch: deps.findMatch,
44
+ lite: true,
45
+ });
46
+
47
+ if (!result) {
48
+ return null;
49
+ }
50
+
51
+ // Skip redirect check - will be handled in full match
52
+ if (result.type === "redirect") {
53
+ return { routeMiddleware: undefined };
54
+ }
55
+
56
+ const snapshot = result.snapshot;
57
+ const { matched, manifestEntry, routeMiddleware, responseType } =
58
+ snapshot;
59
+
60
+ const negotiation = await negotiateRoute(request, pathname, snapshot);
61
+ if (negotiation) {
62
+ return {
63
+ routeMiddleware:
64
+ negotiation.routeMiddleware.length > 0
65
+ ? negotiation.routeMiddleware
66
+ : undefined,
67
+ responseType: negotiation.responseType,
68
+ handler: negotiation.handler,
69
+ params: matched.params,
70
+ negotiated: true,
71
+ manifestEntry: negotiation.manifestEntry,
72
+ routeKey: matched.routeKey,
73
+ };
74
+ }
75
+
76
+ // No negotiation or RSC won — return default route info
77
+ const hasVariants =
78
+ matched.negotiateVariants && matched.negotiateVariants.length > 0;
79
+ return {
80
+ routeMiddleware:
81
+ routeMiddleware.length > 0 ? routeMiddleware : undefined,
82
+ params: matched.params,
83
+ routeKey: matched.routeKey,
84
+ ...(responseType
85
+ ? {
86
+ responseType,
87
+ handler:
88
+ manifestEntry.type === "route"
89
+ ? manifestEntry.handler
90
+ : undefined,
91
+ manifestEntry,
92
+ }
93
+ : {}),
94
+ ...(hasVariants ? { negotiated: true } : {}),
95
+ };
96
+ }),
97
+ );
98
+ }