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

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 +293 -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 +2508 -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 +24 -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-snapshot.ts +368 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +222 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1113 -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 +173 -35
  205. package/src/index.ts +241 -73
  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 +527 -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 +897 -0
  313. package/src/rsc/shell-serve.ts +124 -0
  314. package/src/rsc/ssr-setup.ts +144 -0
  315. package/src/rsc/transition-gate.ts +89 -0
  316. package/src/rsc/types.ts +95 -12
  317. package/src/runtime-env.ts +18 -0
  318. package/src/search-params.ts +99 -82
  319. package/src/segment-content-promise.ts +67 -0
  320. package/src/segment-loader-promise.ts +149 -0
  321. package/src/segment-system.tsx +349 -134
  322. package/src/serialize.ts +243 -0
  323. package/src/server/context.ts +459 -85
  324. package/src/server/cookie-parse.ts +32 -0
  325. package/src/server/cookie-store.ts +310 -0
  326. package/src/server/fetchable-loader-store.ts +11 -6
  327. package/src/server/handle-store.ts +123 -42
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +848 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +443 -135
  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 +76 -98
  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 +44 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +280 -0
  389. package/src/urls/pattern-types.ts +160 -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,527 @@
1
+ /**
2
+ * RSC Rendering Handler (Navigation)
3
+ *
4
+ * Handles RSC rendering for both partial (client-side navigation) and full
5
+ * (initial page load) requests. Includes prerender collection for build-time
6
+ * static generation.
7
+ */
8
+
9
+ import {
10
+ getRequestContext,
11
+ setRequestContextParams,
12
+ runWithRequestContext,
13
+ } from "../server/request-context.js";
14
+ import { SeededShellStore } from "../cache/shell-snapshot.js";
15
+ import { appendMetric } from "../router/metrics.js";
16
+ import { observePhase, PHASES } from "../router/instrument.js";
17
+ import { getSSRSetup, isRscRequest } from "./ssr-setup.js";
18
+ import type { RscPayload } from "./types.js";
19
+ import type { SSRModule } from "./types.js";
20
+ import type { RequestContext } from "../server/request-context.js";
21
+ import {
22
+ createResponseWithMergedHeaders,
23
+ createSimpleRedirectResponse,
24
+ attachLocationStateIfPresent,
25
+ } from "./helpers.js";
26
+ import type { HandlerContext } from "./handler-context.js";
27
+ import { gateTransitions } from "./transition-gate.js";
28
+ import { buildFullPayload } from "./full-payload.js";
29
+ import {
30
+ scheduleShellCapture,
31
+ type ShellCaptureDescriptor,
32
+ } from "./shell-capture.js";
33
+ import {
34
+ SHELL_STATUS_HEADER,
35
+ resolvePprConfig,
36
+ buildShellKey,
37
+ isValidShellHit,
38
+ base64ToBytes,
39
+ hasShellFamily,
40
+ warnShellStoreMissingOnce,
41
+ } from "./shell-serve.js";
42
+ import { reportCacheError } from "../cache/cache-error.js";
43
+ import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
44
+ import type { ShellCacheEntry } from "../cache/types.js";
45
+
46
+ export function handleRscRendering<TEnv>(
47
+ ctx: HandlerContext<TEnv>,
48
+ request: Request,
49
+ env: TEnv,
50
+ url: URL,
51
+ isPartial: boolean,
52
+ handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
53
+ nonce: string | undefined,
54
+ ): Promise<Response> {
55
+ // Instrument the whole render phase once through the unified API: it records
56
+ // the "render:total" perf metric AND opens the "rango.render" span from the
57
+ // same boundary (match -> serialize -> SSR), so the two surfaces agree.
58
+ // Loaders kicked off during matching nest under the span; the SSR HTML pass
59
+ // below opens "rango.ssr" the same way.
60
+ return observePhase(PHASES.render, () =>
61
+ handleRscRenderingInner(
62
+ ctx,
63
+ request,
64
+ env,
65
+ url,
66
+ isPartial,
67
+ handleStore,
68
+ nonce,
69
+ ),
70
+ );
71
+ }
72
+
73
+ async function handleRscRenderingInner<TEnv>(
74
+ ctx: HandlerContext<TEnv>,
75
+ request: Request,
76
+ env: TEnv,
77
+ url: URL,
78
+ isPartial: boolean,
79
+ handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
80
+ nonce: string | undefined,
81
+ ): Promise<Response> {
82
+ const reqCtx = getRequestContext();
83
+
84
+ let payload: RscPayload;
85
+ let hasInterceptSlots = false;
86
+
87
+ // --- Axis 2: integrated PPR shell serve (docs/design/ppr-shell-resume.md) ---
88
+ //
89
+ // COMMIT POINT. This function is the render pass executeRender wraps, so it runs
90
+ // strictly AFTER the whole middleware chain — the global router.use() chain AND
91
+ // route DSL middleware() both wrap it. Any middleware rejection/redirect/401 has
92
+ // already returned before this line, which is what makes a shared shell safe:
93
+ // not a single shell byte can precede a guard decision, on MISS or HIT.
94
+ //
95
+ // PPR is opt-in per PAGE ROUTE via the `ppr` path option (read off the classified
96
+ // route snapshot — the same matched entry match() will resolve). No `ppr` option
97
+ // means pure axis 1: no store read, no capture, no logs, zero cost.
98
+ //
99
+ // On a valid HIT the composed response is committed HERE — the stored prelude
100
+ // bytes flush immediately while match()/segment resolution/Flight render/resume
101
+ // run behind them inside the response stream (ring-3 reads and render setup hide
102
+ // behind wire bytes). On a MISS the request continues as plain axis 1 and a
103
+ // background capture is scheduled after the response is built.
104
+ let pprMiss: {
105
+ descriptor: ShellCaptureDescriptor;
106
+ ssrModule: SSRModule;
107
+ } | null = null;
108
+ if (
109
+ !isPartial &&
110
+ nonce === undefined &&
111
+ request.method === "GET" &&
112
+ !url.searchParams.has("__prerender_collect") &&
113
+ !isRscRequest(request, url, false)
114
+ ) {
115
+ const pprConfig = resolvePprConfig(reqCtx._classifiedRoute?.manifestEntry);
116
+ if (pprConfig) {
117
+ const store = reqCtx._cacheStore;
118
+ const key = buildShellKey(url);
119
+ if (!hasShellFamily(store)) {
120
+ // Declared intent that cannot be honored deserves a diagnostic (unlike an
121
+ // undeclared route, which is silent). Axis 1 after the warning.
122
+ warnShellStoreMissingOnce(key);
123
+ } else {
124
+ // allReady (ssr.resolveStreaming) bypasses PPR entirely: buffering defeats
125
+ // streaming, so bots/SEO crawlers get one complete axis-1 document.
126
+ const [ssrModule, streamMode] = await getSSRSetup(
127
+ ctx,
128
+ request,
129
+ env,
130
+ url,
131
+ reqCtx._metricsStore,
132
+ );
133
+ if (
134
+ streamMode !== "allReady" &&
135
+ ssrModule.resumeShellHTML &&
136
+ ssrModule.captureShellHTML
137
+ ) {
138
+ const descriptor: ShellCaptureDescriptor = {
139
+ key,
140
+ ttl: pprConfig.ttl,
141
+ swr: pprConfig.swr,
142
+ tags: pprConfig.tags,
143
+ store,
144
+ debug: INTERNAL_RANGO_DEBUG,
145
+ };
146
+ let cached: Awaited<ReturnType<typeof store.getShell>> = null;
147
+ try {
148
+ cached = await store.getShell(key);
149
+ } catch (error) {
150
+ // A failing store read degrades to axis 1 (MISS), never a 500.
151
+ reportCacheError(error, "cache-read", "[ShellServe] getShell");
152
+ }
153
+ if (cached && isValidShellHit(cached.entry)) {
154
+ // Stale (SWR) hit: serve the stale shell now, recapture in the
155
+ // background (stampede-guarded + backoff inside scheduleShellCapture).
156
+ if (cached.shouldRevalidate) {
157
+ scheduleShellCapture(
158
+ ctx,
159
+ request,
160
+ env,
161
+ url,
162
+ reqCtx,
163
+ ssrModule,
164
+ descriptor,
165
+ );
166
+ }
167
+ return serveShellHit(
168
+ ctx,
169
+ request,
170
+ env,
171
+ url,
172
+ reqCtx,
173
+ handleStore,
174
+ ssrModule,
175
+ cached.entry,
176
+ );
177
+ }
178
+ // MISS (no entry, invalid reactVersion, or store read failure): axis 1
179
+ // + a background capture scheduled once the response is known servable.
180
+ pprMiss = { descriptor, ssrModule };
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ if (isPartial) {
187
+ // Partial render (navigation)
188
+ const result = await ctx.router.matchPartial(request, { env });
189
+
190
+ if (!result) {
191
+ // Fall back to full render
192
+ const match = await ctx.router.match(request, { env });
193
+ setRequestContextParams(match.params, match.routeName);
194
+
195
+ if (match.redirect) {
196
+ // Partial request: use X-RSC-Redirect header so the client can
197
+ // perform SPA navigation. A raw 308 would be auto-followed by
198
+ // fetch, hitting the target without _rsc_partial.
199
+ return createSimpleRedirectResponse(match.redirect);
200
+ }
201
+
202
+ payload = buildFullPayload(match, ctx, url, reqCtx, handleStore);
203
+ } else {
204
+ setRequestContextParams(result.params, result.routeName);
205
+
206
+ hasInterceptSlots = !!result.slots;
207
+
208
+ payload = {
209
+ metadata: {
210
+ pathname: url.pathname,
211
+ // routerId is serialized on every payload (including within-session
212
+ // ones) so the frontend can read the current app/router identity. It
213
+ // always equals the current app's id: a cross-app navigation is
214
+ // intercepted server-side (X-RSC-Reload) and never delivers a
215
+ // different-router payload to the client.
216
+ routerId: ctx.router.id,
217
+ segments: gateTransitions(
218
+ result.segments,
219
+ reqCtx,
220
+ ctx.router.onError,
221
+ ),
222
+ matched: result.matched,
223
+ diff: result.diff,
224
+ resolvedIds: result.resolvedIds,
225
+ params: result.params,
226
+ isPartial: true,
227
+ slots: result.slots,
228
+ handles: handleStore.stream(),
229
+ version: ctx.version,
230
+ prefetchCacheTTL: ctx.router.prefetchCacheTTL,
231
+ prefetchCacheSize: ctx.router.prefetchCacheSize,
232
+ prefetchConcurrency: ctx.router.prefetchConcurrency,
233
+ stateCookieName: ctx.router.resolvedStateCookieName,
234
+ },
235
+ };
236
+ }
237
+ } else {
238
+ // Full render (initial page load)
239
+ const match = await ctx.router.match(request, { env });
240
+ setRequestContextParams(match.params, match.routeName);
241
+
242
+ if (match.redirect) {
243
+ return createResponseWithMergedHeaders(null, {
244
+ status: 308,
245
+ headers: { Location: match.redirect },
246
+ });
247
+ }
248
+
249
+ // Caching is now handled in router.match() via cache provider in request context
250
+ // match.segments already contains cached or fresh segments as appropriate
251
+
252
+ if (url.searchParams.has("__prerender_collect")) {
253
+ // Build-time prerender collection: serialize segments and handle data
254
+ // to JSON for storage as build artifacts. At runtime the worker
255
+ // deserializes these and feeds them through the normal segment pipeline.
256
+ const nonLoaderSegments = match.segments.filter(
257
+ (s) => s.type !== "loader",
258
+ );
259
+ handleStore.seal();
260
+ await handleStore.settled;
261
+ const { serializeSegments } = await import("../cache/segment-codec.js");
262
+ const serializedSegments = await serializeSegments(nonLoaderSegments);
263
+ const handles: Record<string, Record<string, unknown[]>> = {};
264
+ for (const seg of nonLoaderSegments) {
265
+ const segHandles = handleStore.getDataForSegment(seg.id);
266
+ if (Object.keys(segHandles).length > 0) {
267
+ handles[seg.id] = segHandles;
268
+ }
269
+ }
270
+ return new Response(
271
+ JSON.stringify({
272
+ segments: serializedSegments,
273
+ handles,
274
+ routeName: match.routeName,
275
+ params: match.params,
276
+ }),
277
+ { headers: { "Content-Type": "application/json" } },
278
+ );
279
+ } else {
280
+ payload = buildFullPayload(match, ctx, url, reqCtx, handleStore);
281
+ }
282
+ }
283
+
284
+ // For partial requests, include any server-set location state in the payload.
285
+ // SSR (full page) requests ignore location state since there's no history.state
286
+ // to write to on a fresh page load.
287
+ if (isPartial && payload.metadata) {
288
+ attachLocationStateIfPresent(payload);
289
+ }
290
+
291
+ const metricsStore = reqCtx._metricsStore;
292
+
293
+ // Serialize to RSC stream
294
+ const rscSerializeStart = performance.now();
295
+ const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
296
+ onError: (error: unknown) => {
297
+ ctx.callOnError(error, "rendering", { request, url, env });
298
+ },
299
+ });
300
+ const rscSerializeDur = performance.now() - rscSerializeStart;
301
+ // This measures synchronous stream creation, not end-to-end stream consumption.
302
+ appendMetric(
303
+ metricsStore,
304
+ "rsc-serialize",
305
+ rscSerializeStart,
306
+ rscSerializeDur,
307
+ );
308
+
309
+ if (isRscRequest(request, url, isPartial)) {
310
+ // render:total is recorded by the observePhase wrapper around this function.
311
+ const rscHeaders: Record<string, string> = {
312
+ "content-type": "text/x-component;charset=utf-8",
313
+ vary: "accept, X-Rango-State, X-RSC-Router-Client-Path",
314
+ // Router identity, so the client can verify pre-decode (before importing
315
+ // chunks) that this content payload belongs to its app and refuse a
316
+ // foreign one (cache/proxy/bug). Control-only reload/redirect responses
317
+ // are deliberately NOT stamped. See browser/response-adapter.ts.
318
+ "X-RSC-Router-Id": ctx.router.id,
319
+ };
320
+ // Tell the client's prefetch cache to scope this response to its source
321
+ // URL (instead of the default source-agnostic wildcard). Intercept
322
+ // responses depend on the source page matching an intercept rule, so
323
+ // they must not be reused for navigations from other sources.
324
+ if (hasInterceptSlots) {
325
+ rscHeaders["x-rsc-prefetch-scope"] = "source";
326
+ }
327
+ // Enable browser HTTP caching for prefetch responses only.
328
+ // Requires X-Rango-Prefetch header (sent by Link prefetch fetch),
329
+ // non-intercept context (intercept responses depend on source page),
330
+ // and a configured cache-control value (false disables caching).
331
+ const isPrefetch = request.headers.has("X-Rango-Prefetch");
332
+ if (isPrefetch && isPartial && !hasInterceptSlots) {
333
+ const cc = ctx.router.prefetchCacheControl;
334
+ if (cc) {
335
+ rscHeaders["cache-control"] = cc;
336
+ }
337
+ }
338
+ return createResponseWithMergedHeaders(rscStream, {
339
+ headers: rscHeaders,
340
+ });
341
+ }
342
+
343
+ // Delegate to SSR for HTML response (reuse early setup if available)
344
+ const [ssrModule, streamMode] = await getSSRSetup(
345
+ ctx,
346
+ request,
347
+ env,
348
+ url,
349
+ metricsStore,
350
+ );
351
+
352
+ // ssr-render-html metric + rango.ssr span from one boundary. render:total is
353
+ // recorded by the observePhase wrapper around this function.
354
+ const htmlStream = await observePhase(PHASES.ssr, () =>
355
+ ssrModule.renderHTML(rscStream, {
356
+ nonce,
357
+ streamMode,
358
+ }),
359
+ );
360
+ const response = createResponseWithMergedHeaders(htmlStream, {
361
+ headers: { "content-type": "text/html;charset=utf-8" },
362
+ });
363
+
364
+ // --- Axis 2: PPR shell CAPTURE on MISS (background task; see design doc) ---
365
+ // The ppr route missed its shell above. Schedule the background capture only
366
+ // when the served response is a 200 HTML document (a 404/error render is not a
367
+ // cacheable shell), and tag the response for observability either way. Capture
368
+ // does NOT flow through the HTTP pipeline: scheduleShellCapture re-derives the
369
+ // page via router.match() under a derived context (fresh handle store,
370
+ // _shellCaptureRun: true) — middleware never re-runs; it already ran for this
371
+ // request and guarding is serve-time.
372
+ if (pprMiss) {
373
+ if (
374
+ response.status === 200 &&
375
+ (response.headers.get("content-type") ?? "").includes("text/html")
376
+ ) {
377
+ scheduleShellCapture(
378
+ ctx,
379
+ request,
380
+ env,
381
+ url,
382
+ reqCtx,
383
+ pprMiss.ssrModule,
384
+ pprMiss.descriptor,
385
+ );
386
+ }
387
+ response.headers.set(SHELL_STATUS_HEADER, "MISS");
388
+ }
389
+
390
+ return response;
391
+ }
392
+
393
+ /**
394
+ * Serve a validated shell HIT: commit the composed response NOW — the stored
395
+ * prelude bytes are the first thing on the wire — and run the live tail
396
+ * (match(), fresh loaders, full Flight render for hydration, fizz resume of just
397
+ * the holes) BEHIND them inside the response stream. React relies on HTML-parser
398
+ * foster-parenting for content streamed after the prelude's closing
399
+ * `</body></html>`, so plain byte concatenation is the correct composition.
400
+ *
401
+ * Status and headers are committed at the flush: middleware already ran (their
402
+ * ctx.res headers merge in via createResponseWithMergedHeaders), and route
403
+ * middleware code after its next() can still adjust headers on the returned
404
+ * Response object. A failing hole cannot become a 500/redirect after this point —
405
+ * error UI renders inline via Suspense/error boundaries, the documented PPR
406
+ * constraint.
407
+ *
408
+ * The tail promise is kicked off SYNCHRONOUSLY so match/Flight/resume run inside
409
+ * the current ALS request-context frame (the stream may be pulled by the server
410
+ * adapter outside it).
411
+ */
412
+ function serveShellHit(
413
+ ctx: HandlerContext<any>,
414
+ request: Request,
415
+ env: any,
416
+ url: URL,
417
+ reqCtx: RequestContext<any>,
418
+ handleStore: ReturnType<typeof getRequestContext>["_handleStore"],
419
+ ssrModule: SSRModule,
420
+ entry: ShellCacheEntry,
421
+ ): Response {
422
+ const preludeBytes = base64ToBytes(entry.prelude);
423
+
424
+ const renderTail = async (
425
+ activeCtx: RequestContext<any>,
426
+ ): Promise<ReadableStream<Uint8Array> | { redirect: string }> => {
427
+ const match = await ctx.router.match(request, { env });
428
+ if (match.redirect) return { redirect: match.redirect };
429
+ setRequestContextParams(match.params, match.routeName);
430
+ const payload = buildFullPayload(match, ctx, url, activeCtx, handleStore);
431
+ // Theme fidelity for resume: initialTheme is per-request METADATA (the
432
+ // visitor's cookie), but React resume requires the tree above the holes to
433
+ // match the frozen prelude, which was rendered with the CAPTURE's
434
+ // initialTheme. Replay the captured value into the payload (the SSR resume
435
+ // tree AND client hydration both read it) so the trees agree by
436
+ // construction. The visitor still sees THEIR theme: the FOUC script in the
437
+ // prelude applies it pre-paint from the cookie, and ThemeProvider re-syncs
438
+ // its state from the cookie post-mount.
439
+ if (payload.metadata) {
440
+ payload.metadata.initialTheme = entry.initialTheme as
441
+ | import("../theme/types.js").Theme
442
+ | undefined;
443
+ }
444
+ // Full Flight render per request: hydration needs the whole payload (there
445
+ // is no Flight-side resume — a React limitation, not ours).
446
+ const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
447
+ onError: (error: unknown) => {
448
+ ctx.callOnError(error, "rendering", { request, url, env });
449
+ },
450
+ });
451
+ return observePhase(PHASES.ssr, () =>
452
+ ssrModule.resumeShellHTML!(rscStream, {
453
+ postponed: entry.postponed,
454
+ nonce: undefined,
455
+ }),
456
+ );
457
+ };
458
+
459
+ const tailPromise: Promise<
460
+ ReadableStream<Uint8Array> | { redirect: string }
461
+ > = (async () => {
462
+ // Capture data snapshot seeding (docs/design/ppr-shell-resume.md): the tail
463
+ // is a FULL FRESH render whose payload must match the frozen prelude. If the
464
+ // capture recorded a snapshot, run the tail through a SeededShellStore
465
+ // overlay so every cache-store read the capture pinned returns its
466
+ // capture-time value AS FRESH — the shell region reproduces byte-identically
467
+ // even after the underlying cache entries drifted (expired/recomputed/
468
+ // tag-invalidated). Everything not pinned (the holes — masked loaders were
469
+ // never recorded) falls through to the real store and stays LIVE. The
470
+ // overlay lives on a DERIVED context (own _cacheStore), so the shared reqCtx
471
+ // is untouched; an entry without a snapshot keeps the pre-snapshot behavior.
472
+ if (entry.snapshot && entry.snapshot.length > 0 && reqCtx._cacheStore) {
473
+ const seededCtx: RequestContext<any> = Object.create(reqCtx);
474
+ seededCtx._cacheStore = new SeededShellStore(
475
+ reqCtx._cacheStore,
476
+ entry.snapshot,
477
+ );
478
+ return runWithRequestContext(seededCtx, () => renderTail(seededCtx));
479
+ }
480
+ return renderTail(reqCtx);
481
+ })();
482
+ // The stream below is the only consumer; pre-attach a no-op catch so a tail
483
+ // failure before the stream is pulled never surfaces as an unhandled rejection.
484
+ tailPromise.catch(() => {});
485
+
486
+ const body = new ReadableStream<Uint8Array>({
487
+ async start(controller) {
488
+ controller.enqueue(preludeBytes);
489
+ try {
490
+ const tail = await tailPromise;
491
+ if (tail instanceof ReadableStream) {
492
+ const reader = tail.getReader();
493
+ try {
494
+ for (;;) {
495
+ const { done, value } = await reader.read();
496
+ if (done) break;
497
+ controller.enqueue(value);
498
+ }
499
+ } finally {
500
+ reader.releaseLock();
501
+ }
502
+ } else {
503
+ // Defensive, near-unreachable: a redirecting match cannot have captured
504
+ // a shell (capture bails on redirects), so a HIT on a redirecting URL
505
+ // requires the route to have BECOME redirecting within the shell TTL.
506
+ // The 200 + prelude are already committed; degrade to a client-side
507
+ // replace so the user still lands on the target.
508
+ controller.enqueue(
509
+ new TextEncoder().encode(
510
+ `<script>location.replace(${JSON.stringify(tail.redirect)})</script>`,
511
+ ),
512
+ );
513
+ }
514
+ controller.close();
515
+ } catch (error) {
516
+ controller.error(error);
517
+ }
518
+ },
519
+ });
520
+
521
+ return createResponseWithMergedHeaders(body, {
522
+ headers: {
523
+ "content-type": "text/html;charset=utf-8",
524
+ [SHELL_STATUS_HEADER]: "HIT",
525
+ },
526
+ });
527
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Runtime guardrail warnings (dev-only).
3
+ *
4
+ * W3: PE action redirect / Response handling.
5
+ */
6
+
7
+ import {
8
+ createResponseWithMergedHeaders,
9
+ carryOverRedirectHeaders,
10
+ } from "./helpers.js";
11
+ import { isRedirectResponse } from "../response-utils.js";
12
+
13
+ // W3 -----------------------------------------------------------------------
14
+
15
+ /**
16
+ * Extract a redirect Response from a thrown or returned value.
17
+ * Returns a redirect Response to send to the client, or null if the value
18
+ * is not a redirect Response.
19
+ */
20
+ export function extractRedirectResponse(value: unknown): Response | null {
21
+ if (!(value instanceof Response)) return null;
22
+ if (!isRedirectResponse(value)) return null;
23
+ const location = value.headers.get("Location")!;
24
+ const redirect = createResponseWithMergedHeaders(null, {
25
+ status: value.status,
26
+ headers: { Location: location },
27
+ });
28
+ carryOverRedirectHeaders(value, redirect);
29
+ return redirect;
30
+ }
31
+
32
+ /**
33
+ * Warn when a non-redirect Response is returned from an action during PE.
34
+ */
35
+ export function warnNonRedirectPeResponse(): void {
36
+ console.warn(
37
+ `[rango] Server action returned a non-redirect Response during ` +
38
+ `progressive enhancement (no-JS) request. The Response will be ` +
39
+ `ignored — the page will re-render at the current URL instead.`,
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Warn when a non-redirect Response is returned (not thrown) from an action
45
+ * on the JS (fetch) path. A raw Response cannot be serialized into Flight, so
46
+ * it is discarded — mirroring the PE path. Use `throw redirect('/path')` for
47
+ * redirects.
48
+ */
49
+ export function warnNonRedirectActionResponse(actionId: string): void {
50
+ console.warn(
51
+ `[@rangojs/router] Server action "${actionId}" returned a Response ` +
52
+ `that is not a redirect. Non-redirect Responses cannot be serialized ` +
53
+ `and are ignored. Use \`throw redirect('/path')\` for redirects.`,
54
+ );
55
+ }