@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

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 (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  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 +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -17,8 +17,10 @@ import {
17
17
  type EntryPropSegments,
18
18
  type HelperContext,
19
19
  type InterceptEntry,
20
+ type InterceptConfig,
20
21
  } from "../server/context";
21
22
  import { invariant } from "../errors";
23
+ import { validateUserRouteName } from "../route-name.js";
22
24
  import { isCachedFunction } from "../cache/taint.js";
23
25
  import { RangoContext } from "../server/context";
24
26
  import { isStaticHandler } from "../static-handler.js";
@@ -35,7 +37,6 @@ import type {
35
37
  ErrorBoundaryItem,
36
38
  NotFoundBoundaryItem,
37
39
  LayoutItem,
38
- WhenItem,
39
40
  CacheItem,
40
41
  TransitionItem,
41
42
  UseItems,
@@ -264,34 +265,6 @@ const notFoundBoundary: RouteHelpers<any, any>["notFoundBoundary"] = (
264
265
  return { name, type: "notFoundBoundary" } as NotFoundBoundaryItem;
265
266
  };
266
267
 
267
- /**
268
- * When helper - defines a condition for intercept activation
269
- *
270
- * Only valid inside intercept() use() callback. The when() function
271
- * is captured by the intercept and stored in its `when` array.
272
- * During soft navigation, all when() conditions must return true
273
- * for the intercept to activate.
274
- */
275
- const when: RouteHelpers<any, any>["when"] = (fn) => {
276
- const { store, ctx } = requireDslContext(
277
- "when() must be called inside intercept()",
278
- );
279
-
280
- // The when() function needs to be captured by the intercept's tempParent
281
- // which should have a `when` array. If not present, we're not inside intercept()
282
- const parent = ctx.parent as any;
283
- if (!parent || !("when" in parent)) {
284
- invariant(
285
- false,
286
- "when() can only be used inside intercept() use() callback",
287
- );
288
- }
289
-
290
- const name = `$${store.getNextIndex("when")}`;
291
- parent.when.push(fn);
292
- return { name, type: "when" } as WhenItem;
293
- };
294
-
295
268
  /**
296
269
  * Cache helper - defines caching configuration for segments
297
270
  *
@@ -302,15 +275,15 @@ const when: RouteHelpers<any, any>["when"] = (fn) => {
302
275
  * Supports these call signatures:
303
276
  * - cache() - no args, uses app-level defaults (for loader caching)
304
277
  * - cache(() => [...]) - wraps children with app-level defaults
305
- * - cache('profileName') - uses a named cache profile
306
- * - cache('profileName', () => [...]) - named profile with children
307
278
  * - cache({ ttl: 60 }, () => [...]) - with explicit options
279
+ *
280
+ * Named cache profiles are applied via the `"use cache: <profile>"` directive,
281
+ * not a `cache("profileName")` form in the route tree.
308
282
  */
309
283
  const cache: RouteHelpers<any, any>["cache"] = (
310
284
  optionsOrChildren?:
311
285
  | PartialCacheOptions
312
286
  | false
313
- | string
314
287
  | (() => UseItems<AllUseItems>),
315
288
  maybeChildren?: () => UseItems<AllUseItems>,
316
289
  ) => {
@@ -326,18 +299,6 @@ const cache: RouteHelpers<any, any>["cache"] = (
326
299
  // cache() - no args, use defaults
327
300
  options = {};
328
301
  children = undefined;
329
- } else if (typeof optionsOrChildren === "string") {
330
- // cache('profileName') or cache('profileName', () => [...])
331
- // Resolve from context-scoped profiles (set per-router via HelperContext).
332
- const ctxStore = RangoContext.getStore();
333
- const profile = ctxStore?.cacheProfiles?.[optionsOrChildren];
334
- invariant(
335
- profile,
336
- `cache("${optionsOrChildren}"): unknown cache profile. ` +
337
- `Define it in createRouter({ cacheProfiles: { "${optionsOrChildren}": { ttl: ... } } }).`,
338
- );
339
- options = { ttl: profile.ttl, swr: profile.swr, tags: profile.tags };
340
- children = maybeChildren;
341
302
  } else if (typeof optionsOrChildren === "function") {
342
303
  // cache(() => [...]) - use empty options (will use defaults)
343
304
  options = {};
@@ -393,10 +354,10 @@ const cache: RouteHelpers<any, any>["cache"] = (
393
354
  return { name: namespace, type: "cache" } as CacheItem;
394
355
  }
395
356
 
396
- // Inside a loader() use() callback, only the direct form — cache()/cache(opts)/
397
- // cache("profile") — writes cache config to the loader entry. The wrapper
398
- // form creates a structural cache boundary with its own children scope, which
399
- // has no effect on the loader and would silently no-op.
357
+ // Inside a loader() use() callback, only the direct form — cache()/cache(opts)
358
+ // — writes cache config to the loader entry. The wrapper form creates a
359
+ // structural cache boundary with its own children scope, which has no effect
360
+ // on the loader and would silently no-op.
400
361
  invariant(
401
362
  !(ctx.parent && (ctx.parent as any).type === "loader"),
402
363
  "cache() wrapper form is not valid inside loader() use(). Use cache({...}) without children to configure the loader's cache.",
@@ -536,6 +497,22 @@ const middleware: RouteHelpers<any, any>["middleware"] = (...args: any[]) => {
536
497
  } as MiddlewareItem;
537
498
  };
538
499
 
500
+ // Slot names become part of segment ids: a parallel/intercept slot is encoded
501
+ // as `${shortCode}.${slotName}`, and loader segments append `D${index}.${loaderId}`.
502
+ // A "." in the slot name collides with that separator -- loaderParentId
503
+ // (segment-system.tsx) strips from the FIRST `D<index>.`, so a name like
504
+ // "@D3.foo" is mis-cut to "@" and the loader's data is silently dropped. Reject
505
+ // the dot at definition time so the failure is loud, not a corrupted tree at
506
+ // runtime. (A bare "D" without a trailing dot -- e.g. "@Detail" -- is fine.)
507
+ function assertValidSlotName(slotName: string): void {
508
+ invariant(
509
+ !slotName.includes("."),
510
+ `Slot name "${slotName}" must not contain ".". The dot is a reserved ` +
511
+ `segment-id separator; a name like "@D3.foo" corrupts loader segment-id ` +
512
+ `parsing and silently drops the loader's data. Rename the slot.`,
513
+ );
514
+ }
515
+
539
516
  const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
540
517
  const { store, ctx } = requireDslContext(
541
518
  "parallel() must be called inside urls()",
@@ -551,6 +528,7 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
551
528
  );
552
529
 
553
530
  const slotNames = Object.keys(slots as Record<string, any>) as `@${string}`[];
531
+ for (const slotName of slotNames) assertValidSlotName(slotName);
554
532
 
555
533
  const namespace = `${ctx.namespace}.$${store.getNextIndex("parallel")}`;
556
534
 
@@ -695,8 +673,19 @@ const intercept = (
695
673
  slotName: `@${string}`,
696
674
  routeName: string,
697
675
  handler: any,
676
+ configOrUse?: InterceptConfig | (() => any[]),
698
677
  use?: () => any[],
699
678
  ) => {
679
+ // arg4 discrimination: a function is the use() callback (no config); an object
680
+ // is the config carrying `when`. With config given, the use() callback is
681
+ // arg5. Keeps the no-config form intercept(slot, route, handler, () => [...])
682
+ // working unchanged.
683
+ const config: InterceptConfig | undefined =
684
+ typeof configOrUse === "function" || configOrUse == null
685
+ ? undefined
686
+ : configOrUse;
687
+ const useFn = typeof configOrUse === "function" ? configOrUse : use;
688
+
700
689
  const { store, ctx } = requireDslContext(
701
690
  "intercept() must be called inside urls()",
702
691
  );
@@ -710,6 +699,8 @@ const intercept = (
710
699
  "intercept() cannot be used inside parallel()",
711
700
  );
712
701
 
702
+ assertValidSlotName(slotName);
703
+
713
704
  const namespace = `${ctx.namespace}.$${store.getNextIndex("intercept")}.${slotName}`;
714
705
 
715
706
  // Dot-prefixed = local (add include prefix), unprefixed = global (use as-is)
@@ -732,9 +723,17 @@ const intercept = (
732
723
  when: [], // Selector conditions for conditional interception
733
724
  };
734
725
 
726
+ // Conditional interception: `when` from the config object — a single selector
727
+ // or an array (ALL must return true to activate). Replaces the former when()
728
+ // use-item captured inside the callback.
729
+ if (config?.when) {
730
+ const selectors = Array.isArray(config.when) ? config.when : [config.when];
731
+ entry.when.push(...selectors);
732
+ }
733
+
735
734
  // Merge handler.use defaults with explicit use
736
735
  const handlerUseFn = resolveHandlerUse(handler);
737
- const mergedUse = mergeHandlerUse(handlerUseFn, use, "intercept");
736
+ const mergedUse = mergeHandlerUse(handlerUseFn, useFn, "intercept");
738
737
 
739
738
  // Run merged use callback to collect loaders, revalidate, middleware, etc.
740
739
  if (mergedUse) {
@@ -751,7 +750,6 @@ const intercept = (
751
750
  notFoundBoundary: entry.notFoundBoundary,
752
751
  loader: entry.loader,
753
752
  layout: capturedLayouts, // Capture layout() calls
754
- when: entry.when, // Capture when() conditions
755
753
  get loading() {
756
754
  return entry.loading;
757
755
  },
@@ -890,7 +888,11 @@ const transition = (
890
888
  "transition() must be called inside urls()",
891
889
  );
892
890
 
893
- const name = `$${store.getNextIndex("transition")}`;
891
+ // Allocate a single index for this transition() call (used in all paths),
892
+ // mirroring cache() — the child form uses it for the name, the wrapper form
893
+ // reuses it for the namespace, so no index is burned.
894
+ const transitionIndex = store.getNextIndex("transition");
895
+ const name = `$${transitionIndex}`;
894
896
 
895
897
  if (!children) {
896
898
  // Position 1: child of path() — attach to parent entry
@@ -903,7 +905,7 @@ const transition = (
903
905
  }
904
906
 
905
907
  // Position 2: wrapper — create a transparent layout with transition config
906
- const namespace = `${ctx.namespace}.${store.getNextIndex("transition")}`;
908
+ const namespace = `${ctx.namespace}.${transitionIndex}`;
907
909
  const entry = {
908
910
  ...emptySegmentBase(),
909
911
  id: namespace,
@@ -933,6 +935,12 @@ const route: RouteHelpers<any, any>["route"] = (name, handler, use) => {
933
935
  "route() must be called inside urls()",
934
936
  );
935
937
 
938
+ // Reject names colliding with reserved internal prefixes ($path_, $prefix_),
939
+ // the same guard path() and include() enforce. Without it such a name
940
+ // type-checks on an untyped router, then silently vanishes from generated
941
+ // route-types and public reverse() (isAutoGeneratedRouteName filters it out).
942
+ validateUserRouteName(name);
943
+
936
944
  const namespace = `${ctx.namespace}.${store.getNextIndex("route")}.${name}`;
937
945
 
938
946
  const entry = {
@@ -1096,7 +1104,6 @@ export {
1096
1104
  revalidate,
1097
1105
  parallel,
1098
1106
  intercept,
1099
- when,
1100
1107
  errorBoundary,
1101
1108
  notFoundBoundary,
1102
1109
  route,
@@ -8,7 +8,6 @@ import {
8
8
  revalidate,
9
9
  parallel,
10
10
  intercept,
11
- when,
12
11
  errorBoundary,
13
12
  notFoundBoundary,
14
13
  route,
@@ -40,7 +39,6 @@ function buildRouteHelpers<T extends RouteDefinition, TEnv>(): RouteHelpers<
40
39
  loading,
41
40
  errorBoundary,
42
41
  notFoundBoundary,
43
- when,
44
42
  cache,
45
43
  transition,
46
44
  } as unknown as RouteHelpers<T, TEnv>;
@@ -29,12 +29,10 @@ import type {
29
29
  ParallelUseItem,
30
30
  InterceptUseItem,
31
31
  LoaderUseItem,
32
- WhenItem,
33
32
  CacheItem,
34
33
  TransitionItem,
35
34
  UseItems,
36
35
  } from "../route-types.js";
37
- import type { InterceptWhenFn } from "../server/context";
38
36
 
39
37
  // Re-export route item types for backward compatibility
40
38
  export type {
@@ -52,12 +50,12 @@ export type {
52
50
  RouteUseItem,
53
51
  ParallelUseItem,
54
52
  InterceptUseItem,
55
- WhenItem,
56
53
  CacheItem,
57
54
  } from "../route-types.js";
58
55
 
59
56
  // Re-export intercept selector types for use in handlers
60
57
  export type {
58
+ InterceptConfig,
61
59
  InterceptSelectorContext,
62
60
  InterceptSegmentsState,
63
61
  InterceptWhenFn,
@@ -151,8 +149,11 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
151
149
  * so they take precedence on `loading()` and other last-write-wins
152
150
  * fields.
153
151
  */
154
- parallel: <
155
- TSlots extends Record<
152
+ // Not generic over the slots record: an inferred type parameter makes the
153
+ // object literal an inference site, which suppresses contextual typing of
154
+ // arrow slot handlers (`(ctx) => ...` was implicit any).
155
+ parallel: (
156
+ slots: Record<
156
157
  `@${string}`,
157
158
  | Handler<any, any, TEnv>
158
159
  | ReactNode
@@ -161,8 +162,6 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
161
162
  use?: () => UseItems<ParallelUseItem>;
162
163
  }
163
164
  >,
164
- >(
165
- slots: TSlots,
166
165
  use?: () => UseItems<ParallelUseItem>,
167
166
  ) => ParallelItem;
168
167
  /**
@@ -183,10 +182,26 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
183
182
  * loader(CardModalLoader),
184
183
  * revalidate(() => false),
185
184
  * ])
185
+ *
186
+ * // Conditional activation via the config object's `when` selector
187
+ * intercept("@modal", "card", <CardModal />, {
188
+ * when: ({ from }) => from.pathname.startsWith("/board"),
189
+ * })
190
+ *
191
+ * // Config + other use-items: config is arg 4, use is arg 5
192
+ * intercept(
193
+ * "@modal",
194
+ * "card",
195
+ * <CardModal />,
196
+ * { when: ({ from }) => from.pathname.startsWith("/board") },
197
+ * () => [loader(CardDetailLoader)],
198
+ * )
186
199
  * ```
187
200
  * @param slotName - Named slot (prefixed with @) where intercept renders
188
201
  * @param routeName - Route name to intercept
189
202
  * @param handler - Component or handler for intercepted render
203
+ * @param config - Optional InterceptConfig (e.g. `{ when }`), or the use
204
+ * callback directly when there is no config
190
205
  * @param use - Optional callback for loaders, middleware, revalidate, etc.
191
206
  */
192
207
  intercept: {
@@ -195,6 +210,9 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
195
210
  slotName: `@${string}`,
196
211
  routeName: `.${K}`,
197
212
  handler: ReactNode | Handler<ExtractRouteParams<T, K>, {}, TEnv>,
213
+ config?:
214
+ | import("../server/context.js").InterceptConfig<TEnv>
215
+ | (() => UseItems<InterceptUseItem>),
198
216
  use?: () => UseItems<InterceptUseItem>,
199
217
  ): InterceptItem;
200
218
  // Global: unprefixed, params inferred from global route map
@@ -202,6 +220,9 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
202
220
  slotName: `@${string}`,
203
221
  routeName: K,
204
222
  handler: ReactNode | Handler<K, Rango.GeneratedRouteMap, TEnv>,
223
+ config?:
224
+ | import("../server/context.js").InterceptConfig<TEnv>
225
+ | (() => UseItems<InterceptUseItem>),
205
226
  use?: () => UseItems<InterceptUseItem>,
206
227
  ): InterceptItem;
207
228
  };
@@ -347,40 +368,6 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
347
368
  notFoundBoundary: (
348
369
  fallback: ReactNode | NotFoundBoundaryHandler,
349
370
  ) => NotFoundBoundaryItem;
350
- /**
351
- * Define a condition for when an intercept should activate
352
- *
353
- * Only valid inside intercept() use() callback. When multiple when() calls
354
- * are present, ALL must return true for the intercept to activate.
355
- * If no when() is defined, the intercept always activates on soft navigation.
356
- *
357
- * Context properties:
358
- * - `from` - Source URL (where user is navigating from)
359
- * - `to` - Destination URL (where user is navigating to)
360
- * - `params` - Matched route params
361
- * - `segments` - Client's current segments with `path` and `ids`
362
- *
363
- * ```typescript
364
- * // Only intercept when coming from the board page
365
- * intercept("@modal", "card", <CardModal />, () => [
366
- * when(({ from }) => from.pathname.startsWith("/board")),
367
- * loader(CardDetailLoader),
368
- * ])
369
- *
370
- * // Use segments to check current route context
371
- * intercept("@modal", "card", <CardModal />, () => [
372
- * when(({ segments }) => segments.path[0] === "kanban"),
373
- * ])
374
- *
375
- * // Multiple conditions (AND logic)
376
- * intercept("@modal", "card", <CardModal />, () => [
377
- * when(({ from }) => from.pathname.startsWith("/board")),
378
- * when(({ segments }) => segments.ids.includes("kanban-layout")),
379
- * ])
380
- * ```
381
- * @param fn - Selector function receiving navigation context, returns boolean
382
- */
383
- when: (fn: InterceptWhenFn) => WhenItem;
384
371
  /**
385
372
  * Define cache configuration for segments
386
373
  *
@@ -441,10 +428,8 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
441
428
  cache: {
442
429
  (): CacheItem;
443
430
  (children: () => UseItems<AllUseItems>): CacheItem;
444
- (profileName: string): CacheItem;
445
- (profileName: string, use: () => UseItems<AllUseItems>): CacheItem;
446
431
  (
447
- options: PartialCacheOptions | false,
432
+ options: PartialCacheOptions<TEnv> | false,
448
433
  use?: () => UseItems<AllUseItems>,
449
434
  ): CacheItem;
450
435
  };
@@ -474,6 +459,13 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
474
459
  * transition({}) is startTransition + ViewTransition under the default and
475
460
  * startTransition only when the router sets viewTransition: false.
476
461
  *
462
+ * Conditional hold: pass `when: (ctx) => boolean` to gate the transition per
463
+ * request. It runs server-side AFTER the route handler (so it can read state
464
+ * the handler set via `ctx.get(...)`); returning false drops this transition
465
+ * for the request, so the navigation streams its loading() skeleton instead of
466
+ * holding. This is a post-handler predicate — distinct from intercept()'s
467
+ * match-time `when` config selector (`intercept(slot, route, Comp, { when })`).
468
+ *
477
469
  * ```typescript
478
470
  * // Attach to a single route
479
471
  * path("/about", AboutPage, { name: "about" }, () => [
@@ -490,13 +482,21 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
490
482
  * path("/product/:id", ProductPage, { name: "product" }, () => [
491
483
  * transition({ viewTransition: false }),
492
484
  * ])
485
+ *
486
+ * // Hold only when the handler decided to (post-handler predicate):
487
+ * path("/product/:id", ProductPage, { name: "product" }, () => [
488
+ * transition({ when: (ctx) => ctx.get(KeepScroll) === true }),
489
+ * ])
493
490
  * ```
494
491
  * @param config - ViewTransition configuration (enter, exit, update, share,
495
- * default, name) plus `viewTransition: "auto" | false` to toggle the router
496
- * boundary (createRouter({ viewTransition }) sets the app-wide default)
492
+ * default, name), `viewTransition: "auto" | false` to toggle the router
493
+ * boundary (createRouter({ viewTransition }) sets the app-wide default), and
494
+ * `when: (ctx) => boolean` to gate the transition per request post-handler
497
495
  * @param children - Optional callback returning child routes to wrap
498
496
  */
499
497
  transition: {
498
+ (): TransitionItem;
499
+ (children: () => UseItems<AllUseItems>): TransitionItem;
500
500
  (config: TransitionConfig): TransitionItem;
501
501
  (
502
502
  config: TransitionConfig,
@@ -15,8 +15,8 @@ export type {
15
15
  RouteUseItem,
16
16
  ParallelUseItem,
17
17
  InterceptUseItem,
18
- WhenItem,
19
18
  CacheItem,
19
+ InterceptConfig,
20
20
  InterceptSelectorContext,
21
21
  InterceptSegmentsState,
22
22
  InterceptWhenFn,
@@ -30,7 +30,6 @@ export {
30
30
  revalidate,
31
31
  parallel,
32
32
  intercept,
33
- when,
34
33
  errorBoundary,
35
34
  notFoundBoundary,
36
35
  loader,
@@ -1,9 +1,9 @@
1
1
  import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
2
2
  import {
3
- requireRequestContext,
4
3
  getRequestContext,
5
4
  _getRequestContext,
6
5
  } from "../server/request-context.js";
6
+ import { markExternalRedirect } from "../redirect-origin.js";
7
7
 
8
8
  /**
9
9
  * Create a soft redirect Response for middleware short-circuit
@@ -39,6 +39,11 @@ import {
39
39
  * status: 303,
40
40
  * state: [Flash({ text: "Session expired" })],
41
41
  * });
42
+ *
43
+ * // Off-host redirect (opt out of the same-origin guard). Without
44
+ * // `external: true`, a cross-origin target is blocked and replaced with the
45
+ * // app root, matching the client's open-redirect protection.
46
+ * return redirect('https://accounts.example.com/oauth', { external: true });
42
47
  * ```
43
48
  */
44
49
  export function redirect(url: string, status?: number): Response;
@@ -47,13 +52,18 @@ export function redirect(
47
52
  options: {
48
53
  status?: number;
49
54
  state?: LocationStateEntry | LocationStateEntry[];
55
+ external?: boolean;
50
56
  },
51
57
  ): Response;
52
58
  export function redirect(
53
59
  url: string,
54
60
  statusOrOptions?:
55
61
  | number
56
- | { status?: number; state?: LocationStateEntry | LocationStateEntry[] },
62
+ | {
63
+ status?: number;
64
+ state?: LocationStateEntry | LocationStateEntry[];
65
+ external?: boolean;
66
+ },
57
67
  ): Response {
58
68
  const status =
59
69
  typeof statusOrOptions === "number"
@@ -61,9 +71,11 @@ export function redirect(
61
71
  : (statusOrOptions?.status ?? 302);
62
72
  const state =
63
73
  typeof statusOrOptions === "object" ? statusOrOptions?.state : undefined;
74
+ const external =
75
+ typeof statusOrOptions === "object" ? statusOrOptions?.external : undefined;
64
76
 
65
77
  if (state) {
66
- const ctx = requireRequestContext();
78
+ const ctx = getRequestContext();
67
79
  ctx.setLocationState(state);
68
80
 
69
81
  if (process.env.NODE_ENV !== "production") {
@@ -85,17 +97,38 @@ export function redirect(
85
97
  }
86
98
 
87
99
  // Auto-prefix root-relative URLs with basename for app-local redirects.
100
+ // Treat the URL as already-prefixed when the basename is followed by a path
101
+ // separator, a query, a fragment, or end-of-string, so "/admin?tab=x" and
102
+ // "/admin#frag" are not double-prefixed into "/admin/admin?tab=x".
88
103
  const bn = _getRequestContext()?._basename;
89
104
  let resolvedUrl = url;
90
- if (bn && url.startsWith("/") && !url.startsWith(bn + "/") && url !== bn) {
105
+ if (
106
+ bn &&
107
+ url.startsWith("/") &&
108
+ url !== bn &&
109
+ !url.startsWith(bn + "/") &&
110
+ !url.startsWith(bn + "?") &&
111
+ !url.startsWith(bn + "#")
112
+ ) {
91
113
  resolvedUrl = url === "/" ? bn : bn + url;
92
114
  }
93
115
 
94
- return new Response(null, {
95
- status,
96
- headers: {
97
- Location: resolvedUrl,
98
- "X-RSC-Redirect": "soft",
99
- },
100
- });
116
+ const headers: Record<string, string> = {
117
+ Location: resolvedUrl,
118
+ "X-RSC-Redirect": "soft",
119
+ };
120
+
121
+ const response = new Response(null, { status, headers });
122
+
123
+ // Mark an explicit off-host redirect with an out-of-band brand so the
124
+ // same-origin guard (rsc/redirect-guard.ts) lets it through. The brand is a
125
+ // WeakSet membership on this Response object -- NOT a wire header -- so the
126
+ // opt-in cannot be forged by an attacker-controlled upstream response a
127
+ // proxy-style response route might copy. The internal redirect-rebuild paths
128
+ // transfer the brand; the guard reads and clears it (see markExternalRedirect).
129
+ if (external) {
130
+ markExternalRedirect(response);
131
+ }
132
+
133
+ return response;
101
134
  }
@@ -100,7 +100,6 @@ const MOUNT_SITE_ALLOWED_TYPES: Record<string, Set<string>> = {
100
100
  "notFoundBoundary",
101
101
  "layout",
102
102
  "route",
103
- "when",
104
103
  "transition",
105
104
  ]),
106
105
  // LoaderUseItem — only revalidate + cache can attach to a loader entry
@@ -139,6 +138,12 @@ export function mergeHandlerUse(
139
138
  mountSite: string,
140
139
  ): (() => any[]) | undefined {
141
140
  if (!handlerUse && !explicitUse) return undefined;
141
+ // Validation asymmetry (intentional, pre-1.0): only handler.use() items are
142
+ // checked against the mount-site allow-list (validateHandlerUseItems below).
143
+ // Explicit use() items pass through unvalidated on both the explicit-only
144
+ // branch here and the merged branch, so a structurally-valid-but-prohibited
145
+ // item (e.g. middleware() inside a parallel slot) is not rejected at this seam.
146
+ // Documented rather than enforced for now; revisit before 1.0 (#569).
142
147
  if (!handlerUse) return explicitUse;
143
148
  if (!explicitUse) {
144
149
  return () => {
@@ -1,16 +1,14 @@
1
- import type { AllUseItems, WhenItem } from "../route-types.js";
1
+ import type { AllUseItems } from "../route-types.js";
2
2
 
3
3
  /**
4
4
  * The set of valid use-item `type` discriminants — the single runtime source of
5
5
  * truth for "is this a well-formed use item?" shape validation.
6
6
  *
7
7
  * Declared via a `Record<...>` so that adding a member to the union without
8
- * updating this map is a compile error. `when` is included because when() items
9
- * are valid inside intercept() even though WhenItem is not part of AllUseItems
10
- * (it lives only in InterceptUseItem). This is shape validation only; per-mount-
8
+ * updating this map is a compile error. This is shape validation only; per-mount-
11
9
  * site rules remain the narrower hand-written tables in resolve-handler-use.ts.
12
10
  */
13
- const USE_ITEM_TYPES: Record<AllUseItems["type"] | WhenItem["type"], true> = {
11
+ const USE_ITEM_TYPES: Record<AllUseItems["type"], true> = {
14
12
  layout: true,
15
13
  route: true,
16
14
  middleware: true,
@@ -21,7 +19,6 @@ const USE_ITEM_TYPES: Record<AllUseItems["type"] | WhenItem["type"], true> = {
21
19
  loading: true,
22
20
  errorBoundary: true,
23
21
  notFoundBoundary: true,
24
- when: true,
25
22
  cache: true,
26
23
  transition: true,
27
24
  include: true,