@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * PPR shell capture orchestration (Axis 2, see docs/design/ppr-shell-resume.md).
3
+ *
4
+ * Capture does NOT flow through the HTTP middleware pipeline. The shell-cache
5
+ * middleware sets a `_shellCapture` DESCRIPTOR before its single foreground
6
+ * next(); the render layer (rsc-rendering.ts) reads it after building the served
7
+ * response and calls scheduleShellCapture. The capture then runs as a background
8
+ * task that re-derives the shell via `ctx.router.match()` under its OWN derived
9
+ * request context — fresh handle store, `_shellCaptureRun: true` so loaders mask
10
+ * (loader-mask.ts) and every loader-consuming subtree postpones. It drives the
11
+ * static prerender to a quiescent shell, aborts to freeze the prelude + postponed
12
+ * state, and stores the pair via putShell. Because it uses match() rather than a
13
+ * second next(), the middleware chain (auth, logging, the single-use next() latch)
14
+ * never re-runs — and the capture inherits the foreground's post-middleware
15
+ * context state (variables, cache store) it delegates to.
16
+ */
17
+
18
+ import React from "react";
19
+ import { bufferToBase64 } from "../cache/cf/cf-base64.js";
20
+ import { reportCacheError } from "../cache/cache-error.js";
21
+ import { runBackground } from "../cache/background-task.js";
22
+ import { observePhase, PHASES } from "../router/instrument.js";
23
+ import {
24
+ runWithRequestContext,
25
+ setRequestContextParams,
26
+ type RequestContext,
27
+ } from "../server/request-context.js";
28
+ import { createHandleStore, type HandleStore } from "../server/handle-store.js";
29
+ import type { ShellCacheEntry } from "../cache/types.js";
30
+ import type { HandlerContext } from "./handler-context.js";
31
+ import type { RscPayload, SSRModule } from "./types.js";
32
+ import { buildFullPayload } from "./full-payload.js";
33
+
34
+ /**
35
+ * Task-quantized quiesce: the number of consecutive macrotask hops with zero new
36
+ * Flight bytes that marks the shell "quiet". This replaces the old 50ms
37
+ * wall-clock debounce.
38
+ *
39
+ * The capture Flight render is a REGULAR renderToReadableStream (not a static
40
+ * prerender), so React schedules both its retries and its byte-flush on
41
+ * setTimeout(0) MACROTASKS (verified against the vendored edge production
42
+ * react-server-dom build: pingTask uses scheduleMicrotask only when
43
+ * request.type === PRERENDER, otherwise setTimeout; enqueueFlush is always
44
+ * setTimeout). Masked loaders are the live lane — their rows never emit — so once
45
+ * the shell rows finish flushing the stream goes permanently byte-silent, and K
46
+ * consecutive quiet macrotask hops after the last observed byte declare quiesce.
47
+ *
48
+ * K=2 gives a race window of ~two event-loop turns: shell work still producing
49
+ * bytes keeps resetting the counter; anything not producing bytes within the
50
+ * window (the masked loaders, and any genuinely pending I/O) becomes a hole. The
51
+ * only residual is raw per-request I/O rendered directly in shell (not via a
52
+ * loader) that resolves inside the window — a documented shell anti-pattern; put
53
+ * per-request data in loaders or behind live(). See docs/design/ppr-shell-resume.md.
54
+ */
55
+ const FLIGHT_QUIET_HOPS = 2;
56
+
57
+ /** Default upper bound on the capture prerender wait before forcing the abort. */
58
+ const SHELL_CAPTURE_MAX_WAIT_MS = 5000;
59
+
60
+ /**
61
+ * Module-level in-flight key set: the stampede guard for background captures, and
62
+ * its single owner. One capture runs per key per isolate; concurrent MISS/stale
63
+ * requests for the same key coalesce onto the first (the rest see the key present
64
+ * in scheduleShellCapture and skip). Added when a capture is scheduled and cleared
65
+ * in the task's finally once it settles, so a later request can recapture when TTL
66
+ * rolls. Living here (not split across the middleware) keeps the add/clear
67
+ * lifecycle in one layer.
68
+ */
69
+ const inFlightCaptures = new Set<string>();
70
+
71
+ /**
72
+ * Keys already warned about a refused (null) capture, so the eternal-MISS shape
73
+ * logs once per key per isolate instead of on every request.
74
+ */
75
+ const warnedNullCaptures = new Set<string>();
76
+
77
+ function warnNullCaptureOnce(key: string): void {
78
+ if (warnedNullCaptures.has(key)) return;
79
+ warnedNullCaptures.add(key);
80
+ console.warn(
81
+ `[rango] Shell capture for "${key}" produced no usable shell (empty or ` +
82
+ "not-ready prelude); nothing was stored, so this request stays on MISS. A later " +
83
+ "request re-captures - if the route NEVER flips to HIT, the most common cause is " +
84
+ "a loader route without a route-level loading() boundary: its loader data is " +
85
+ "awaited at tree-build, so under capture's masked loaders no shell exists above " +
86
+ "<body>. Add loading() to the loader route (and keep shell material in a layout) " +
87
+ "to make it PPR-capturable. See docs/design/ppr-shell-resume.md.",
88
+ );
89
+ }
90
+
91
+ export interface FlightCaptureGate {
92
+ /** Identity passthrough of the source stream; feed this to captureShellHTML. */
93
+ stream: ReadableStream<Uint8Array>;
94
+ /**
95
+ * Resolves once the source has been byte-quiet for FLIGHT_QUIET_HOPS macrotask
96
+ * hops (or has closed — the DATA variant). At that instant the gate FREEZES:
97
+ * no further source byte reaches the fizz side, and the readable is left open
98
+ * (never closed / errored) so fizz postpones the still-pending references
99
+ * instead of seeing "Connection closed".
100
+ */
101
+ quiesce: Promise<void>;
102
+ /**
103
+ * Stop the internal macrotask-hop loop. captureShellHTML's maxWaitMs bounds the
104
+ * overall wait; dispose() is the clean shutdown for the pathological case where
105
+ * the source never goes byte-quiet (quiesce never fires), so the hop loop would
106
+ * otherwise keep rescheduling after captureShellHTML has already aborted and
107
+ * returned.
108
+ */
109
+ dispose(): void;
110
+ }
111
+
112
+ /**
113
+ * Wrap the capture Flight stream so the fizz shell prerender reads a stream that
114
+ * (a) forwards the shell rows unchanged, (b) resolves `quiesce` after the rows go
115
+ * byte-silent for FLIGHT_QUIET_HOPS macrotask hops, and (c) FREEZES at that
116
+ * instant — dropping any later byte without closing or erroring the readable, so
117
+ * the pending masked-loader references stay pending and fizz postpones them (the
118
+ * "unclosing stream" property, here for free because the masked rows never emit).
119
+ * Freezing also guarantees no post-quiesce byte — including an error row from any
120
+ * later abort/cancel of the underlying render — can corrupt the frozen prelude.
121
+ *
122
+ * Quiet is measured in TASKS, not wall-clock: after the first byte a macrotask
123
+ * hop loop compares a byte counter each turn and fires after K quiet turns. The
124
+ * hop timers are unref'd so they never keep a Node process alive, and the source
125
+ * closing (no holes) fires quiesce immediately for the DATA variant — the
126
+ * TransformStream then closes the readable, so fizz completes with postponed null.
127
+ */
128
+ export function gateFlightForCapture(
129
+ source: ReadableStream<Uint8Array>,
130
+ quietHops: number = FLIGHT_QUIET_HOPS,
131
+ ): FlightCaptureGate {
132
+ let resolveQuiet!: () => void;
133
+ const quiesce = new Promise<void>((resolve) => {
134
+ resolveQuiet = resolve;
135
+ });
136
+
137
+ let bytesSeen = 0;
138
+ let armed = false;
139
+ let settled = false;
140
+ let disposed = false;
141
+ let frozen = false;
142
+
143
+ const fire = (): void => {
144
+ if (settled) return;
145
+ settled = true;
146
+ frozen = true;
147
+ resolveQuiet();
148
+ };
149
+
150
+ const scheduleHop = (fn: () => void): void => {
151
+ const t = setTimeout(fn, 0);
152
+ // Never let the quiet-detection hop alone keep a Node process alive
153
+ // (no-op on workerd).
154
+ (t as { unref?: () => void }).unref?.();
155
+ };
156
+
157
+ // The hop loop starts only after the first byte, so it can never declare
158
+ // quiesce before fizz has begun pulling rows through the transform.
159
+ const arm = (): void => {
160
+ if (armed || settled || disposed) return;
161
+ armed = true;
162
+ let lastSeen = bytesSeen;
163
+ let quiet = 0;
164
+ const hop = (): void => {
165
+ if (settled || disposed) return;
166
+ if (bytesSeen === lastSeen) {
167
+ quiet += 1;
168
+ if (quiet >= quietHops) {
169
+ fire();
170
+ return;
171
+ }
172
+ } else {
173
+ lastSeen = bytesSeen;
174
+ quiet = 0;
175
+ }
176
+ scheduleHop(hop);
177
+ };
178
+ scheduleHop(hop);
179
+ };
180
+
181
+ const monitor = new TransformStream<Uint8Array, Uint8Array>({
182
+ transform(chunk, controller) {
183
+ // Post-quiesce: drop the byte. Do NOT enqueue and do NOT close/error — the
184
+ // frozen fizz input must stay a fixed byte set behind an open (unclosing)
185
+ // readable so still-pending references postpone.
186
+ if (frozen) return;
187
+ bytesSeen += chunk.length;
188
+ arm();
189
+ controller.enqueue(chunk);
190
+ },
191
+ flush() {
192
+ // Source closed with no freeze => DATA variant (no holes): quiet
193
+ // immediately. The TransformStream then closes the readable, so fizz
194
+ // completes and postponed comes back null.
195
+ fire();
196
+ },
197
+ });
198
+
199
+ return {
200
+ stream: source.pipeThrough(monitor),
201
+ quiesce,
202
+ dispose(): void {
203
+ disposed = true;
204
+ },
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Schedule the background shell capture for a served document. Stampede-guarded:
210
+ * one capture per key per isolate. Runs via runBackground (waitUntil on workerd,
211
+ * fire-and-forget in Node dev), so the served response is never blocked on it. Any
212
+ * error is routed through reportCacheError — capture is best-effort; a failure just
213
+ * means the next request recaptures.
214
+ *
215
+ * Eligibility (nonce/allReady/partial/status/strategy) is decided by the caller
216
+ * (rsc-rendering.ts maybeScheduleShellCapture); this function only owns the
217
+ * stampede guard and the background dispatch.
218
+ */
219
+ export function scheduleShellCapture(
220
+ ctx: HandlerContext<any>,
221
+ request: Request,
222
+ env: any,
223
+ url: URL,
224
+ reqCtx: RequestContext<any>,
225
+ ssrModule: SSRModule,
226
+ descriptor: NonNullable<RequestContext["_shellCapture"]>,
227
+ ): void {
228
+ const key = descriptor.key;
229
+ if (inFlightCaptures.has(key)) return;
230
+ inFlightCaptures.add(key);
231
+ runBackground(reqCtx, async () => {
232
+ try {
233
+ await runShellCapture(
234
+ ctx,
235
+ request,
236
+ env,
237
+ url,
238
+ reqCtx,
239
+ ssrModule,
240
+ descriptor,
241
+ );
242
+ } catch (error) {
243
+ // Detached background task — pass reqCtx so onError still fires when the ALS
244
+ // context is gone. Best-effort: a failure just means the next request
245
+ // recaptures.
246
+ reportCacheError(error, "cache-write", "[ShellCache] capture", reqCtx);
247
+ } finally {
248
+ inFlightCaptures.delete(key);
249
+ }
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Run the shell capture in a DERIVED request context, then store the result.
255
+ *
256
+ * The derived context is `Object.create(reqCtx)` so it inherits the foreground's
257
+ * post-middleware state (variables, cache store, env/request/url, waitUntil) while
258
+ * overriding the render-scoped accumulators as own properties:
259
+ * - _handleStore: a fresh store. The foreground store is already drained to
260
+ * completion (its stream() flipped `completed` on settle) and would throw
261
+ * LateHandlePushError on any re-push. Every downstream reader resolves the
262
+ * store off the ambient context (setupLoaderAccess captures
263
+ * _getRequestContext()._handleStore; trackHandler reads it), so the fresh
264
+ * store on the derived context is what the capture match() writes handles to.
265
+ * - _requestTags: a fresh Set. The capture collects its OWN shell tags here —
266
+ * non-loader tags only, since loaders are masked — which is exactly the tag
267
+ * set a shell entry should be invalidatable by (loader tags belong to holes).
268
+ * - _transitionWhen: a fresh [] so the capture's transition gating is its own.
269
+ * - _shellCaptureRun: true — the switch loaders/cookies/headers guards read.
270
+ * - _shellCapture: the descriptor (informational; putShell target/ttl/swr).
271
+ * - _metricsStore: undefined so the capture never appends to the foreground's
272
+ * (already-finalized) metrics.
273
+ */
274
+ async function runShellCapture(
275
+ ctx: HandlerContext<any>,
276
+ request: Request,
277
+ env: any,
278
+ url: URL,
279
+ reqCtx: RequestContext<any>,
280
+ ssrModule: SSRModule,
281
+ descriptor: NonNullable<RequestContext["_shellCapture"]>,
282
+ ): Promise<void> {
283
+ const freshHandleStore = createHandleStore();
284
+ freshHandleStore.onError = reqCtx._handleStore.onError;
285
+
286
+ const derivedCtx: RequestContext = Object.create(reqCtx);
287
+ derivedCtx._handleStore = freshHandleStore;
288
+ derivedCtx._requestTags = new Set<string>();
289
+ derivedCtx._transitionWhen = [];
290
+ derivedCtx._shellCaptureRun = true;
291
+ derivedCtx._shellCapture = descriptor;
292
+ derivedCtx._metricsStore = undefined;
293
+
294
+ await runWithRequestContext(derivedCtx, async () => {
295
+ const match = await ctx.router.match(request, { env });
296
+ // A route that redirects has no shell to capture — bail (no store write).
297
+ if (match.redirect) return;
298
+ setRequestContextParams(match.params, match.routeName);
299
+
300
+ const payload = buildFullPayload(
301
+ match,
302
+ ctx,
303
+ url,
304
+ derivedCtx,
305
+ freshHandleStore,
306
+ );
307
+ const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
308
+ onError: (error: unknown) => {
309
+ ctx.callOnError(error, "rendering", { request, url, env });
310
+ },
311
+ });
312
+
313
+ // Shell tags = the non-loader request tags the capture render recorded on its
314
+ // own fresh _requestTags. Loaders are masked, so loader cache tags (which
315
+ // belong to the holes, not the shell) are correctly excluded.
316
+ const tags =
317
+ derivedCtx._requestTags.size > 0
318
+ ? [...derivedCtx._requestTags]
319
+ : undefined;
320
+
321
+ await captureAndStoreShell(
322
+ ssrModule,
323
+ rscStream,
324
+ freshHandleStore,
325
+ derivedCtx,
326
+ {
327
+ ...descriptor,
328
+ tags,
329
+ },
330
+ );
331
+ });
332
+ }
333
+
334
+ /**
335
+ * Seal handles, derive the quiesce signal, prerender + abort via the SSR module's
336
+ * captureShellHTML, and store the result. Never throws out of the store write: a
337
+ * failed putShell is routed through reportCacheError so the background task stays
338
+ * best-effort. `ssrModule.captureShellHTML` MUST be present (eligibility is
339
+ * checked before scheduling).
340
+ */
341
+ async function captureAndStoreShell(
342
+ ssrModule: SSRModule,
343
+ rscStream: ReadableStream<Uint8Array>,
344
+ handleStore: HandleStore,
345
+ reqCtx: RequestContext<any>,
346
+ capture: NonNullable<RequestContext["_shellCapture"]>,
347
+ ): Promise<void> {
348
+ const captureShellHTML = ssrModule.captureShellHTML!;
349
+
350
+ // Seal the handle store so the payload's handles generator (resolvedHandleStream
351
+ // -> handleStore.stream()) converges and completes even though masked loaders
352
+ // never resolve. handleStore.settled gates ONLY on tracked HANDLER promises
353
+ // (handleStore.track, via trackHandler) — NOT on deferred handle VALUES pushed
354
+ // through ctx.use(Handle).defer(), which are plain pushed promises. So seal()
355
+ // does not reject or hang on outstanding defers: settled resolves once the
356
+ // handlers settle, and each deferred slot resolves on its own createDeferred
357
+ // timeout (defer.ts, default 10s) or when its resolver fires. A defer whose
358
+ // resolver depends on a masked loader can never fire, so it stays pending until
359
+ // that 10s timeout — longer than maxWaitMs (5s). At the abort the handles
360
+ // generator has not yielded, SsrRoot suspends at the root (consumeAsyncGenerator
361
+ // sits above every boundary), the prelude comes back trivial, and
362
+ // captureShellHTML's sanity gate returns null: the designed fail-safe no-op, not
363
+ // an error. This mirrors the __prerender_collect seal+settled regime, which also
364
+ // excludes loaders. See docs/design/ppr-shell-resume.md ("Loaders and handles").
365
+ handleStore.seal();
366
+
367
+ const gate = gateFlightForCapture(rscStream);
368
+ // Quiesce = handles settled AND the Flight shell rows went task-quiet. Either
369
+ // half stalling is bounded by captureShellHTML's maxWaitMs.
370
+ const quiesce = Promise.all([handleStore.settled, gate.quiesce]).then(
371
+ () => {},
372
+ );
373
+
374
+ try {
375
+ // captureShellHTML CONSUMES the (gated) stream — it is not also SSR'd.
376
+ const result = await observePhase(PHASES.ssr, () =>
377
+ captureShellHTML(gate.stream, {
378
+ quiesce,
379
+ maxWaitMs: SHELL_CAPTURE_MAX_WAIT_MS,
380
+ }),
381
+ );
382
+
383
+ // null = sanity gate refused (trivial/empty prelude, no <body>). Store
384
+ // nothing; the route stays on axis 1 and every future request re-captures to
385
+ // the same refusal, so surface it once per key: the dominant cause is a
386
+ // route shape with no capturable shell — a loader route WITHOUT a route-level
387
+ // loading() boundary awaits its loader data at tree-build (renderSegments'
388
+ // loading-less branch), so the masked loader pins the whole tree above
389
+ // <body>. Silent refusal made that shape an undiagnosable eternal MISS.
390
+ if (result === null) {
391
+ warnNullCaptureOnce(capture.key);
392
+ return;
393
+ }
394
+
395
+ // Store per the flag's key/ttl/swr/tags, into the flag's store: the middleware
396
+ // threads the SAME store it resolved for its getShell read (options.store ??
397
+ // _cacheStore), so a store-attached middleware writes captures where it reads
398
+ // them. The _cacheStore fallback covers a flag armed without a store (tests).
399
+ // reactVersion is read from the same React.version import the middleware
400
+ // validates reads against, so capture and serve always agree.
401
+ const store = capture.store ?? reqCtx._cacheStore;
402
+ if (store?.putShell) {
403
+ try {
404
+ const entry: ShellCacheEntry = {
405
+ // slice() copies just this view's bytes into a fresh ArrayBuffer, so a
406
+ // prelude that is a subarray of a larger backing buffer encodes only its
407
+ // own region — bufferToBase64 reads the whole ArrayBuffer it is handed.
408
+ prelude: bufferToBase64(result.prelude.slice().buffer as ArrayBuffer),
409
+ postponed: result.postponed,
410
+ reactVersion: React.version,
411
+ createdAt: Date.now(),
412
+ };
413
+ await store.putShell(
414
+ capture.key,
415
+ entry,
416
+ capture.ttl,
417
+ capture.swr,
418
+ capture.tags,
419
+ );
420
+ } catch (error) {
421
+ // Best-effort: a failed put must never throw out of the background task.
422
+ reportCacheError(
423
+ error,
424
+ "cache-write",
425
+ "[ShellCache] capture put",
426
+ reqCtx,
427
+ );
428
+ }
429
+ }
430
+ } finally {
431
+ // Stop the hop loop for the pathological never-quiets path (quiesce never
432
+ // fired, capture returned via maxWaitMs). On the normal path the loop already
433
+ // stopped when it fired quiesce; dispose() is then a no-op.
434
+ gate.dispose();
435
+ }
436
+ }
437
+
438
+ // Exported for unit tests that drive the capture core directly.
439
+ export { runShellCapture, captureAndStoreShell };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * SSR Setup Utilities
3
+ *
4
+ * Manages early kickoff and retrieval of SSR module loading and stream mode
5
+ * resolution. Both operations are request-scoped but independent of route
6
+ * matching, so they can run in parallel with segment resolution.
7
+ */
8
+
9
+ import type { HandlerContext } from "./handler-context.js";
10
+ import type { SSRModule } from "./types.js";
11
+ import type { SSRStreamMode } from "../router/router-options.js";
12
+ import type { MetricsStore } from "../server/context.js";
13
+ import { appendMetric } from "../router/metrics.js";
14
+ import { _getRequestContext } from "../server/request-context.js";
15
+
16
+ export type SSRSetup = readonly [SSRModule, SSRStreamMode];
17
+
18
+ /**
19
+ * Key used to stash the early SSR setup promise on request variables.
20
+ * Read back via `getSSRSetup`.
21
+ */
22
+ export const SSR_SETUP_VAR = "__ssrSetup";
23
+
24
+ /**
25
+ * Start loading the SSR module and resolving the stream mode in parallel.
26
+ * When a `getMetricsStore` getter is provided, records individual
27
+ * `ssr:module-load` and `ssr:stream-mode` metrics (the getter is called
28
+ * lazily so stores created after kickoff are still captured). Without a
29
+ * getter the promises run bare — no `.then()` microtasks, no
30
+ * `performance.now()` calls — keeping the non-debug hot path lean.
31
+ */
32
+ export function startSSRSetup<TEnv>(
33
+ ctx: HandlerContext<TEnv>,
34
+ request: Request,
35
+ env: TEnv,
36
+ url: URL,
37
+ getMetricsStore?: () => MetricsStore | undefined,
38
+ ): Promise<SSRSetup> {
39
+ if (!getMetricsStore) {
40
+ return Promise.all([
41
+ ctx.loadSSRModule(),
42
+ ctx.resolveStreamMode(request, env, url),
43
+ ]);
44
+ }
45
+ const start = performance.now();
46
+ return Promise.all([
47
+ ctx.loadSSRModule().then((mod) => {
48
+ appendMetric(
49
+ getMetricsStore(),
50
+ "ssr:module-load",
51
+ start,
52
+ performance.now() - start,
53
+ );
54
+ return mod;
55
+ }),
56
+ ctx.resolveStreamMode(request, env, url).then((mode) => {
57
+ appendMetric(
58
+ getMetricsStore(),
59
+ "ssr:stream-mode",
60
+ start,
61
+ performance.now() - start,
62
+ );
63
+ return mode;
64
+ }),
65
+ ]);
66
+ }
67
+
68
+ /**
69
+ * Retrieve the SSR setup result. Returns the early-kicked-off promise
70
+ * when available (stashed on request variables), otherwise starts a
71
+ * fresh setup.
72
+ */
73
+ export function getSSRSetup<TEnv>(
74
+ ctx: HandlerContext<TEnv>,
75
+ request: Request,
76
+ env: TEnv,
77
+ url: URL,
78
+ metricsStore: MetricsStore | undefined,
79
+ ): Promise<SSRSetup> {
80
+ const early = _getRequestContext()?._variables?.[SSR_SETUP_VAR] as
81
+ | Promise<SSRSetup>
82
+ | undefined;
83
+ if (early) return early;
84
+ return startSSRSetup(
85
+ ctx,
86
+ request,
87
+ env,
88
+ url,
89
+ metricsStore ? () => metricsStore : undefined,
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Classify whether a request may require SSR (HTML rendering).
95
+ *
96
+ * Returns false for requests that are definitively RSC-only, loader fetches,
97
+ * prerender collection, or Accept-based RSC (no text/html). This mirrors
98
+ * the isRscRequest decision in rsc-rendering.ts.
99
+ *
100
+ * Note: response/mime routes are excluded by the caller — this function
101
+ * runs after classifyRequest() determines the request mode.
102
+ */
103
+ export function mayNeedSSR(request: Request, url: URL): boolean {
104
+ if (
105
+ url.searchParams.has("_rsc_partial") ||
106
+ url.searchParams.has("_rsc_action") ||
107
+ request.headers.has("rsc-action") ||
108
+ url.searchParams.has("_rsc_loader") ||
109
+ url.searchParams.has("__rsc") ||
110
+ url.searchParams.has("__prerender_collect")
111
+ ) {
112
+ return false;
113
+ }
114
+
115
+ // Mirror the Accept-based RSC decision from rsc-rendering.ts:
116
+ // if Accept is present and does not include text/html (and no __html override),
117
+ // the response will be RSC, not HTML.
118
+ const accept = request.headers.get("accept");
119
+ if (
120
+ accept &&
121
+ !accept.includes("text/html") &&
122
+ !url.searchParams.has("__html")
123
+ ) {
124
+ return false;
125
+ }
126
+
127
+ return true;
128
+ }
129
+
130
+ // Final render-time decision: is the response an RSC stream (vs HTML)? Distinct
131
+ // from mayNeedSSR, which is a conservative pre-classifier (it treats a missing
132
+ // Accept header as needing SSR; this treats it as RSC).
133
+ export function isRscRequest(
134
+ request: Request,
135
+ url: URL,
136
+ isPartial: boolean,
137
+ ): boolean {
138
+ return (
139
+ isPartial ||
140
+ (!request.headers.get("accept")?.includes("text/html") &&
141
+ !url.searchParams.has("__html")) ||
142
+ url.searchParams.has("__rsc")
143
+ );
144
+ }
@@ -0,0 +1,89 @@
1
+ import type { MatchResult } from "../types.js";
2
+ import type { TransitionWhenContext } from "../types/segments.js";
3
+ import type { getRequestContext } from "../server/request-context.js";
4
+ import { invokeOnError } from "../router/error-handling.js";
5
+ import type { OnErrorCallback } from "../types/error-types.js";
6
+
7
+ /**
8
+ * Apply transition({ when }) gates to a payload's segments.
9
+ *
10
+ * The predicates were collected during resolution (keyed by segment id) and
11
+ * stripped from the serialized config; here — after handlers ran and outside any
12
+ * cache scope — we evaluate each and drop the segment's transition when the
13
+ * predicate does not hold, so the navigation streams its loading fallback
14
+ * instead of holding the previous content. A predicate that throws is reported
15
+ * to the router's onError (phase "rendering") and then treated as "do not hold"
16
+ * (conservative), so a buggy predicate degrades to no transition rather than
17
+ * failing the response.
18
+ *
19
+ * Mutating the segments here is safe: the segment cache stores a serialized copy
20
+ * (segment-codec), written during match() BEFORE this gate runs, so dropping a
21
+ * transition never corrupts a cache entry. The flip side is that a cache hit
22
+ * skips resolution, collects no predicate, and replays the cached transition
23
+ * as-is (it was serialized before the gate) — combining transition({ when })
24
+ * with cache() on the same segment freezes the gate to its cached state, so
25
+ * avoid caching a route whose transition decision is request-dependent.
26
+ *
27
+ * Returns the same array (mutated) for inline use at the payload's `segments`
28
+ * field.
29
+ */
30
+ export function gateTransitions(
31
+ segments: MatchResult["segments"],
32
+ ctx: ReturnType<typeof getRequestContext>,
33
+ onError?: OnErrorCallback,
34
+ ): MatchResult["segments"] {
35
+ const predicates = ctx._transitionWhen;
36
+ if (predicates && predicates.length) {
37
+ for (const { id, when } of predicates) {
38
+ let drop: boolean;
39
+ try {
40
+ // Assemble the ShouldRevalidateFn-shaped predicate context from the
41
+ // request context. Source fields (currentUrl/currentParams/fromRouteName)
42
+ // were stashed at match time from the navigation snapshot; action fields
43
+ // at the action-bearing gate call sites. nextUrl/nextParams/toRouteName/
44
+ // method/get/env come straight off ctx (setRequestContextParams ran
45
+ // before the gate). Source/action fields are undefined when absent —
46
+ // never fabricated (see TransitionWhenContext).
47
+ const whenCtx: TransitionWhenContext = {
48
+ currentUrl: ctx._gateCurrentUrl,
49
+ currentParams: ctx._gateCurrentParams,
50
+ fromRouteName:
51
+ ctx._prevRouteKey as TransitionWhenContext["fromRouteName"],
52
+ nextUrl: ctx.url,
53
+ nextParams: ctx.params,
54
+ toRouteName: ctx.routeName,
55
+ actionId: ctx._gateActionId,
56
+ actionUrl: ctx._gateActionUrl,
57
+ actionResult: ctx._gateActionResult,
58
+ formData: ctx._gateFormData,
59
+ method: ctx.request.method,
60
+ get: ctx.get,
61
+ env: ctx.env,
62
+ };
63
+ drop = when(whenCtx) === false;
64
+ } catch (error) {
65
+ // A throwing predicate must not fail the response: report it and treat
66
+ // the transition as gated off (do not hold). invokeOnError no-ops when
67
+ // onError is undefined.
68
+ drop = true;
69
+ invokeOnError(
70
+ onError,
71
+ error,
72
+ "rendering",
73
+ {
74
+ request: ctx.request,
75
+ url: ctx.url,
76
+ params: ctx.params,
77
+ segmentId: id,
78
+ },
79
+ "RSC",
80
+ );
81
+ }
82
+ if (drop) {
83
+ const seg = segments.find((s) => s.id === id);
84
+ if (seg) seg.transition = undefined;
85
+ }
86
+ }
87
+ }
88
+ return segments;
89
+ }