@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327

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 (440) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +301 -797
  3. package/dist/bin/rango.js +603 -145
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3750 -1160
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +96 -24
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +85 -6
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +228 -33
  12. package/skills/caching/SKILL.md +336 -19
  13. package/skills/catalog.json +271 -0
  14. package/skills/comparison/SKILL.md +50 -0
  15. package/skills/comparison/agents/openai.yaml +4 -0
  16. package/skills/comparison/references/framework-comparison.md +837 -0
  17. package/skills/composability/SKILL.md +110 -4
  18. package/skills/css/SKILL.md +76 -0
  19. package/skills/debug-manifest/SKILL.md +5 -3
  20. package/skills/defer-hydration/SKILL.md +235 -0
  21. package/skills/document-cache/SKILL.md +87 -56
  22. package/skills/fonts/SKILL.md +1 -1
  23. package/skills/handler-use/SKILL.md +12 -10
  24. package/skills/hooks/SKILL.md +73 -691
  25. package/skills/hooks/data.md +273 -0
  26. package/skills/hooks/handle-and-actions.md +103 -0
  27. package/skills/hooks/navigation.md +110 -0
  28. package/skills/hooks/outlets.md +41 -0
  29. package/skills/hooks/state.md +228 -0
  30. package/skills/hooks/urls.md +135 -0
  31. package/skills/host-router/SKILL.md +129 -27
  32. package/skills/i18n/SKILL.md +276 -0
  33. package/skills/intercept/SKILL.md +75 -19
  34. package/skills/layout/SKILL.md +40 -19
  35. package/skills/links/SKILL.md +247 -17
  36. package/skills/loader/SKILL.md +248 -10
  37. package/skills/middleware/SKILL.md +25 -13
  38. package/skills/migrate-nextjs/SKILL.md +205 -20
  39. package/skills/migrate-react-router/SKILL.md +59 -670
  40. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  41. package/skills/migrate-react-router/component-migration.md +196 -0
  42. package/skills/migrate-react-router/data-and-actions.md +225 -0
  43. package/skills/migrate-react-router/route-mapping.md +271 -0
  44. package/skills/mime-routes/SKILL.md +29 -2
  45. package/skills/observability/SKILL.md +202 -0
  46. package/skills/parallel/SKILL.md +40 -10
  47. package/skills/ppr/SKILL.md +616 -0
  48. package/skills/prerender/SKILL.md +72 -60
  49. package/skills/rango/SKILL.md +318 -26
  50. package/skills/react-compiler/SKILL.md +168 -0
  51. package/skills/response-routes/SKILL.md +138 -49
  52. package/skills/route/SKILL.md +117 -9
  53. package/skills/router-setup/SKILL.md +44 -9
  54. package/skills/scripts/SKILL.md +179 -0
  55. package/skills/server-actions/SKILL.md +776 -0
  56. package/skills/shell-manifest/SKILL.md +185 -0
  57. package/skills/streams-and-websockets/SKILL.md +283 -0
  58. package/skills/tailwind/SKILL.md +28 -4
  59. package/skills/testing/SKILL.md +130 -0
  60. package/skills/testing/bindings.md +103 -0
  61. package/skills/testing/cache-prerender.md +127 -0
  62. package/skills/testing/client-components.md +124 -0
  63. package/skills/testing/e2e-parity.md +125 -0
  64. package/skills/testing/flight.md +91 -0
  65. package/skills/testing/handles.md +131 -0
  66. package/skills/testing/loader.md +128 -0
  67. package/skills/testing/middleware.md +99 -0
  68. package/skills/testing/render-handler.md +122 -0
  69. package/skills/testing/response-routes.md +95 -0
  70. package/skills/testing/reverse-and-types.md +85 -0
  71. package/skills/testing/server-actions.md +107 -0
  72. package/skills/testing/server-tree.md +128 -0
  73. package/skills/testing/setup.md +123 -0
  74. package/skills/theme/SKILL.md +1 -1
  75. package/skills/typesafety/SKILL.md +45 -626
  76. package/skills/typesafety/env-and-bindings.md +254 -0
  77. package/skills/typesafety/generated-files-and-cli.md +335 -0
  78. package/skills/typesafety/params-and-search.md +153 -0
  79. package/skills/typesafety/route-types.md +209 -0
  80. package/skills/use-cache/SKILL.md +74 -15
  81. package/skills/vercel/SKILL.md +128 -0
  82. package/skills/view-transitions/SKILL.md +337 -0
  83. package/src/__augment-tests__/augment.ts +81 -0
  84. package/src/__augment-tests__/augmented.check.ts +116 -0
  85. package/src/__internal.ts +0 -65
  86. package/src/browser/action-coordinator.ts +53 -36
  87. package/src/browser/action-fence.ts +47 -0
  88. package/src/browser/app-shell.ts +39 -0
  89. package/src/browser/connection-warmup.ts +134 -0
  90. package/src/browser/cookie-name.ts +140 -0
  91. package/src/browser/event-controller.ts +252 -158
  92. package/src/browser/history-state.ts +21 -0
  93. package/src/browser/index.ts +3 -3
  94. package/src/browser/invalidate-client-cache.ts +52 -0
  95. package/src/browser/logging.ts +28 -0
  96. package/src/browser/merge-segment-loaders.ts +6 -4
  97. package/src/browser/navigation-bridge.ts +94 -25
  98. package/src/browser/navigation-client.ts +144 -79
  99. package/src/browser/navigation-store-handle.ts +38 -0
  100. package/src/browser/navigation-store.ts +161 -73
  101. package/src/browser/navigation-transaction.ts +9 -59
  102. package/src/browser/network-error-handler.ts +34 -7
  103. package/src/browser/partial-update.ts +183 -144
  104. package/src/browser/prefetch/cache.ts +242 -77
  105. package/src/browser/prefetch/fetch.ts +325 -69
  106. package/src/browser/prefetch/queue.ts +61 -12
  107. package/src/browser/rango-state.ts +158 -76
  108. package/src/browser/react/Link.tsx +58 -20
  109. package/src/browser/react/NavigationProvider.tsx +202 -120
  110. package/src/browser/react/ScrollRestoration.tsx +10 -6
  111. package/src/browser/react/filter-segment-order.ts +66 -7
  112. package/src/browser/react/index.ts +0 -48
  113. package/src/browser/react/location-state-shared.ts +178 -8
  114. package/src/browser/react/location-state.ts +39 -14
  115. package/src/browser/react/use-action.ts +6 -15
  116. package/src/browser/react/use-handle.ts +17 -14
  117. package/src/browser/react/use-href.tsx +8 -1
  118. package/src/browser/react/use-link-status.ts +33 -8
  119. package/src/browser/react/use-navigation.ts +32 -7
  120. package/src/browser/react/use-params.ts +20 -10
  121. package/src/browser/react/use-reverse.ts +106 -0
  122. package/src/browser/react/use-router.ts +25 -3
  123. package/src/browser/react/use-search-params.ts +0 -5
  124. package/src/browser/react/use-segments.ts +11 -21
  125. package/src/browser/response-adapter.ts +99 -8
  126. package/src/browser/rsc-router.tsx +145 -28
  127. package/src/browser/scroll-restoration.ts +37 -22
  128. package/src/browser/segment-reconciler.ts +31 -21
  129. package/src/browser/segment-structure-assert.ts +2 -2
  130. package/src/browser/server-action-bridge.ts +236 -65
  131. package/src/browser/types.ts +102 -9
  132. package/src/browser/validate-redirect-origin.ts +43 -16
  133. package/src/build/collect-fallback-refs.ts +107 -0
  134. package/src/build/generate-manifest.ts +203 -154
  135. package/src/build/generate-route-types.ts +3 -1
  136. package/src/build/index.ts +11 -3
  137. package/src/build/prefix-tree-utils.ts +123 -0
  138. package/src/build/route-trie.ts +152 -21
  139. package/src/build/route-types/ast-route-extraction.ts +15 -8
  140. package/src/build/route-types/codegen.ts +16 -5
  141. package/src/build/route-types/include-resolution.ts +456 -62
  142. package/src/build/route-types/param-extraction.ts +6 -3
  143. package/src/build/route-types/per-module-writer.ts +22 -6
  144. package/src/build/route-types/router-processing.ts +128 -51
  145. package/src/build/route-types/scan-filter.ts +1 -1
  146. package/src/build/route-types/source-scan.ts +216 -0
  147. package/src/build/runtime-discovery.ts +13 -21
  148. package/src/cache/cache-error.ts +104 -0
  149. package/src/cache/cache-key-utils.ts +58 -13
  150. package/src/cache/cache-policy.ts +108 -34
  151. package/src/cache/cache-runtime.ts +421 -58
  152. package/src/cache/cache-scope.ts +187 -96
  153. package/src/cache/cache-tag.ts +149 -0
  154. package/src/cache/cf/cf-base64.ts +33 -0
  155. package/src/cache/cf/cf-cache-constants.ts +127 -0
  156. package/src/cache/cf/cf-cache-store.ts +2202 -372
  157. package/src/cache/cf/cf-cache-types.ts +349 -0
  158. package/src/cache/cf/cf-kv-utils.ts +46 -0
  159. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  160. package/src/cache/cf/index.ts +6 -16
  161. package/src/cache/document-cache.ts +126 -41
  162. package/src/cache/handle-snapshot.ts +70 -0
  163. package/src/cache/index.ts +23 -20
  164. package/src/cache/memory-segment-store.ts +243 -37
  165. package/src/cache/profile-registry.ts +46 -31
  166. package/src/cache/read-through-swr.ts +56 -12
  167. package/src/cache/segment-codec.ts +13 -21
  168. package/src/cache/shell-snapshot.ts +417 -0
  169. package/src/cache/tag-invalidation.ts +230 -0
  170. package/src/cache/types.ts +180 -99
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1127 -0
  173. package/src/client.rsc.tsx +41 -21
  174. package/src/client.tsx +33 -61
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +108 -0
  177. package/src/component-utils.ts +19 -0
  178. package/src/components/DefaultDocument.tsx +8 -2
  179. package/src/context-var.ts +18 -6
  180. package/src/decode-loader-results.ts +52 -0
  181. package/src/defer.ts +185 -0
  182. package/src/deps/ssr.ts +0 -1
  183. package/src/encode-kv.ts +49 -0
  184. package/src/errors.ts +30 -4
  185. package/src/escape-script.ts +52 -0
  186. package/src/handle.ts +67 -37
  187. package/src/handles/MetaTags.tsx +24 -53
  188. package/src/handles/Scripts.tsx +183 -0
  189. package/src/handles/breadcrumbs.ts +35 -8
  190. package/src/handles/deferred-resolution.ts +127 -0
  191. package/src/handles/is-thenable.ts +18 -0
  192. package/src/handles/meta.ts +14 -40
  193. package/src/handles/script.ts +244 -0
  194. package/src/host/cookie-handler.ts +9 -60
  195. package/src/host/errors.ts +13 -22
  196. package/src/host/index.ts +9 -2
  197. package/src/host/pattern-matcher.ts +23 -52
  198. package/src/host/router.ts +107 -99
  199. package/src/host/testing.ts +40 -27
  200. package/src/host/types.ts +37 -4
  201. package/src/host/utils.ts +1 -1
  202. package/src/href-client.ts +137 -22
  203. package/src/index.rsc.ts +97 -12
  204. package/src/index.ts +98 -14
  205. package/src/internal-debug.ts +11 -10
  206. package/src/loader-store.ts +500 -0
  207. package/src/loader.rsc.ts +20 -13
  208. package/src/loader.ts +12 -11
  209. package/src/missing-id-error.ts +68 -0
  210. package/src/outlet-context.ts +1 -1
  211. package/src/outlet-provider.tsx +1 -5
  212. package/src/prerender/param-hash.ts +16 -16
  213. package/src/prerender/store.ts +32 -37
  214. package/src/prerender.ts +78 -10
  215. package/src/redirect-origin.ts +114 -0
  216. package/src/regex-escape.ts +8 -0
  217. package/src/render-error-thrower.tsx +20 -0
  218. package/src/response-utils.ts +62 -0
  219. package/src/reverse.ts +65 -39
  220. package/src/root-error-boundary.tsx +1 -19
  221. package/src/route-content-wrapper.tsx +19 -77
  222. package/src/route-definition/dsl-helpers.ts +304 -309
  223. package/src/route-definition/helper-factories.ts +28 -140
  224. package/src/route-definition/helpers-types.ts +87 -59
  225. package/src/route-definition/index.ts +1 -2
  226. package/src/route-definition/redirect.ts +44 -11
  227. package/src/route-definition/resolve-handler-use.ts +12 -1
  228. package/src/route-definition/use-item-types.ts +29 -0
  229. package/src/route-map-builder.ts +41 -20
  230. package/src/route-types.ts +19 -46
  231. package/src/router/basename.ts +14 -0
  232. package/src/router/content-negotiation.ts +73 -25
  233. package/src/router/error-handling.ts +45 -18
  234. package/src/router/find-match.ts +129 -30
  235. package/src/router/handler-context.ts +27 -42
  236. package/src/router/instrument.ts +355 -0
  237. package/src/router/intercept-resolution.ts +39 -20
  238. package/src/router/lazy-includes.ts +82 -59
  239. package/src/router/loader-resolution.ts +167 -72
  240. package/src/router/logging.ts +0 -6
  241. package/src/router/manifest.ts +74 -40
  242. package/src/router/match-api.ts +80 -55
  243. package/src/router/match-context.ts +0 -22
  244. package/src/router/match-handlers.ts +211 -165
  245. package/src/router/match-middleware/background-revalidation.ts +40 -24
  246. package/src/router/match-middleware/cache-lookup.ts +159 -285
  247. package/src/router/match-middleware/cache-store.ts +64 -52
  248. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  249. package/src/router/match-middleware/segment-resolution.ts +0 -22
  250. package/src/router/match-pipelines.ts +1 -42
  251. package/src/router/match-result.ts +69 -79
  252. package/src/router/metrics.ts +0 -34
  253. package/src/router/middleware-types.ts +7 -134
  254. package/src/router/middleware.ts +298 -172
  255. package/src/router/navigation-snapshot.ts +7 -56
  256. package/src/router/params-util.ts +23 -0
  257. package/src/router/parse-pattern.ts +115 -0
  258. package/src/router/pattern-matching.ts +181 -150
  259. package/src/router/prefetch-cache-ttl.ts +51 -0
  260. package/src/router/prefetch-limits.ts +37 -0
  261. package/src/router/prerender-match.ts +112 -67
  262. package/src/router/preview-match.ts +6 -2
  263. package/src/router/request-classification.ts +50 -69
  264. package/src/router/revalidation.ts +123 -73
  265. package/src/router/route-snapshot.ts +14 -3
  266. package/src/router/router-context.ts +6 -29
  267. package/src/router/router-interfaces.ts +115 -36
  268. package/src/router/router-options.ts +166 -5
  269. package/src/router/router-registry.ts +2 -5
  270. package/src/router/segment-resolution/fresh.ts +131 -86
  271. package/src/router/segment-resolution/helpers.ts +86 -6
  272. package/src/router/segment-resolution/loader-cache.ts +139 -39
  273. package/src/router/segment-resolution/loader-mask.ts +67 -0
  274. package/src/router/segment-resolution/loader-snapshot.ts +251 -0
  275. package/src/router/segment-resolution/revalidation.ts +272 -320
  276. package/src/router/segment-resolution/static-store.ts +19 -5
  277. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  278. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  279. package/src/router/segment-resolution.ts +5 -1
  280. package/src/router/segment-wrappers.ts +6 -5
  281. package/src/router/state-cookie-name.ts +33 -0
  282. package/src/router/substitute-pattern-params.ts +75 -0
  283. package/src/router/telemetry-otel.ts +160 -200
  284. package/src/router/telemetry.ts +105 -20
  285. package/src/router/timeout.ts +0 -20
  286. package/src/router/tracing.ts +215 -0
  287. package/src/router/trie-matching.ts +171 -59
  288. package/src/router/types.ts +9 -63
  289. package/src/router/url-params.ts +57 -0
  290. package/src/router.ts +157 -71
  291. package/src/rsc/full-payload.ts +70 -0
  292. package/src/rsc/handler-context.ts +3 -2
  293. package/src/rsc/handler.ts +291 -217
  294. package/src/rsc/helpers.ts +168 -46
  295. package/src/rsc/index.ts +2 -5
  296. package/src/rsc/json-route-result.ts +38 -0
  297. package/src/rsc/loader-fetch.ts +114 -38
  298. package/src/rsc/manifest-init.ts +29 -42
  299. package/src/rsc/nonce.ts +10 -1
  300. package/src/rsc/origin-guard.ts +39 -25
  301. package/src/rsc/progressive-enhancement.ts +124 -13
  302. package/src/rsc/redirect-guard.ts +100 -0
  303. package/src/rsc/response-cache-serve.ts +238 -0
  304. package/src/rsc/response-error.ts +79 -12
  305. package/src/rsc/response-route-handler.ts +99 -189
  306. package/src/rsc/rsc-rendering.ts +421 -76
  307. package/src/rsc/runtime-warnings.ts +23 -10
  308. package/src/rsc/server-action.ts +282 -116
  309. package/src/rsc/shell-capture.ts +1158 -0
  310. package/src/rsc/shell-serve.ts +150 -0
  311. package/src/rsc/ssr-setup.ts +16 -0
  312. package/src/rsc/transition-gate.ts +89 -0
  313. package/src/rsc/types.ts +53 -5
  314. package/src/runtime-env.ts +18 -0
  315. package/src/search-params.ts +35 -30
  316. package/src/segment-loader-promise.ts +49 -4
  317. package/src/segment-system.tsx +350 -149
  318. package/src/serialize.ts +243 -0
  319. package/src/server/context.ts +208 -51
  320. package/src/server/cookie-parse.ts +32 -0
  321. package/src/server/cookie-store.ts +152 -5
  322. package/src/server/handle-store.ts +21 -38
  323. package/src/server/loader-registry.ts +33 -42
  324. package/src/server/request-context.ts +395 -176
  325. package/src/ssr/index.tsx +458 -178
  326. package/src/ssr/ssr-root.tsx +228 -0
  327. package/src/static-handler.ts +10 -13
  328. package/src/testing/cache-status.ts +162 -0
  329. package/src/testing/collect-handle.ts +46 -0
  330. package/src/testing/dispatch.ts +813 -0
  331. package/src/testing/dom.entry.ts +22 -0
  332. package/src/testing/e2e/fixture.ts +188 -0
  333. package/src/testing/e2e/index.ts +128 -0
  334. package/src/testing/e2e/matchers.ts +35 -0
  335. package/src/testing/e2e/page-helpers.ts +272 -0
  336. package/src/testing/e2e/parity.ts +387 -0
  337. package/src/testing/e2e/server.ts +195 -0
  338. package/src/testing/flight-matchers.ts +97 -0
  339. package/src/testing/flight-normalize.ts +11 -0
  340. package/src/testing/flight-runtime.d.ts +57 -0
  341. package/src/testing/flight-tree.ts +682 -0
  342. package/src/testing/flight.entry.ts +52 -0
  343. package/src/testing/flight.ts +257 -0
  344. package/src/testing/generated-routes.ts +199 -0
  345. package/src/testing/index.ts +105 -0
  346. package/src/testing/internal/context.ts +371 -0
  347. package/src/testing/internal/flight-client-globals.ts +30 -0
  348. package/src/testing/internal/seed-vars.ts +54 -0
  349. package/src/testing/render-handler.ts +357 -0
  350. package/src/testing/render-route.tsx +584 -0
  351. package/src/testing/run-loader.ts +385 -0
  352. package/src/testing/run-middleware.ts +205 -0
  353. package/src/testing/run-transition-when.ts +164 -0
  354. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  355. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  356. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  357. package/src/testing/vitest-stubs/version.ts +5 -0
  358. package/src/testing/vitest.ts +305 -0
  359. package/src/theme/ThemeProvider.tsx +56 -84
  360. package/src/theme/ThemeScript.tsx +7 -9
  361. package/src/theme/constants.ts +52 -13
  362. package/src/theme/index.ts +0 -7
  363. package/src/theme/theme-context.ts +1 -5
  364. package/src/theme/theme-script.ts +22 -21
  365. package/src/theme/use-theme.ts +0 -3
  366. package/src/types/boundaries.ts +0 -35
  367. package/src/types/cache-types.ts +13 -4
  368. package/src/types/error-types.ts +30 -90
  369. package/src/types/global-namespace.ts +54 -41
  370. package/src/types/handler-context.ts +110 -62
  371. package/src/types/index.ts +3 -10
  372. package/src/types/loader-types.ts +11 -9
  373. package/src/types/request-scope.ts +112 -0
  374. package/src/types/route-config.ts +20 -52
  375. package/src/types/route-entry.ts +0 -6
  376. package/src/types/segments.ts +135 -14
  377. package/src/urls/include-helper.ts +19 -64
  378. package/src/urls/include-provider.ts +71 -0
  379. package/src/urls/index.ts +2 -11
  380. package/src/urls/path-helper-types.ts +63 -17
  381. package/src/urls/path-helper.ts +22 -106
  382. package/src/urls/pattern-types.ts +72 -19
  383. package/src/urls/response-types.ts +22 -29
  384. package/src/urls/type-extraction.ts +98 -154
  385. package/src/urls/urls-function.ts +1 -19
  386. package/src/use-loader.tsx +292 -107
  387. package/src/vercel/index.ts +11 -0
  388. package/src/vercel/tracing.ts +88 -0
  389. package/src/vite/debug.ts +185 -0
  390. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  391. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  392. package/src/vite/discovery/discover-routers.ts +127 -86
  393. package/src/vite/discovery/discovery-errors.ts +255 -0
  394. package/src/vite/discovery/gate-state.ts +171 -0
  395. package/src/vite/discovery/prerender-collection.ts +96 -68
  396. package/src/vite/discovery/route-types-writer.ts +40 -84
  397. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  398. package/src/vite/discovery/state.ts +45 -1
  399. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  400. package/src/vite/index.ts +4 -0
  401. package/src/vite/inject-client-debug.ts +88 -0
  402. package/src/vite/plugin-types.ts +210 -10
  403. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  404. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  405. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  406. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  407. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  408. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  409. package/src/vite/plugins/expose-action-id.ts +48 -95
  410. package/src/vite/plugins/expose-id-utils.ts +88 -55
  411. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  412. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  413. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  414. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  415. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  416. package/src/vite/plugins/performance-tracks.ts +26 -25
  417. package/src/vite/plugins/refresh-cmd.ts +1 -1
  418. package/src/vite/plugins/use-cache-transform.ts +73 -83
  419. package/src/vite/plugins/vercel-output.ts +384 -0
  420. package/src/vite/plugins/version-injector.ts +40 -29
  421. package/src/vite/plugins/version-plugin.ts +37 -40
  422. package/src/vite/plugins/virtual-entries.ts +138 -27
  423. package/src/vite/rango.ts +236 -138
  424. package/src/vite/router-discovery.ts +927 -136
  425. package/src/vite/utils/ast-handler-extract.ts +26 -35
  426. package/src/vite/utils/banner.ts +1 -1
  427. package/src/vite/utils/bundle-analysis.ts +10 -15
  428. package/src/vite/utils/client-chunks.ts +184 -0
  429. package/src/vite/utils/directive-prologue.ts +40 -0
  430. package/src/vite/utils/forward-user-plugins.ts +171 -0
  431. package/src/vite/utils/manifest-utils.ts +4 -59
  432. package/src/vite/utils/package-resolution.ts +20 -52
  433. package/src/vite/utils/prerender-utils.ts +71 -43
  434. package/src/vite/utils/shared-utils.ts +142 -43
  435. package/src/browser/action-response-classifier.ts +0 -99
  436. package/src/browser/react/use-client-cache.ts +0 -58
  437. package/src/browser/shallow.ts +0 -40
  438. package/src/handles/index.ts +0 -7
  439. package/src/network-error-thrower.tsx +0 -23
  440. package/src/router/middleware-cookies.ts +0 -55
@@ -31,13 +31,11 @@
31
31
  import { createHandle, type Handle } from "../handle.js";
32
32
  import type {
33
33
  MetaDescriptor,
34
+ MetaDescriptorBase,
34
35
  TitleDescriptor,
35
36
  UnsetDescriptor,
36
37
  } from "../router/types.js";
37
38
 
38
- /**
39
- * Type guard for unset descriptor
40
- */
41
39
  function isUnsetDescriptor(
42
40
  descriptor: MetaDescriptor,
43
41
  ): descriptor is UnsetDescriptor {
@@ -49,9 +47,6 @@ function isUnsetDescriptor(
49
47
  );
50
48
  }
51
49
 
52
- /**
53
- * Type guard for title descriptor (any form)
54
- */
55
50
  function isTitleDescriptor(
56
51
  descriptor: MetaDescriptor,
57
52
  ): descriptor is { title: TitleDescriptor } {
@@ -62,9 +57,6 @@ function isTitleDescriptor(
62
57
  );
63
58
  }
64
59
 
65
- /**
66
- * Type guard for title template descriptor
67
- */
68
60
  function isTitleTemplate(
69
61
  title: TitleDescriptor,
70
62
  ): title is { template: string; default: string } {
@@ -76,21 +68,13 @@ function isTitleTemplate(
76
68
  );
77
69
  }
78
70
 
79
- /**
80
- * Type guard for absolute title descriptor
81
- */
82
71
  function isAbsoluteTitle(
83
72
  title: TitleDescriptor,
84
73
  ): title is { absolute: string } {
85
74
  return typeof title === "object" && title !== null && "absolute" in title;
86
75
  }
87
76
 
88
- /**
89
- * Get a unique key for a meta descriptor for deduplication.
90
- * Returns undefined for descriptors that shouldn't be deduplicated.
91
- */
92
77
  function getMetaKey(descriptor: MetaDescriptor): string | undefined {
93
- // Skip unset descriptors - they are processed separately
94
78
  if (isUnsetDescriptor(descriptor)) {
95
79
  return undefined;
96
80
  }
@@ -110,13 +94,10 @@ function getMetaKey(descriptor: MetaDescriptor): string | undefined {
110
94
  return `httpEquiv:${descriptor.httpEquiv}`;
111
95
  }
112
96
  if ("script:ld+json" in descriptor) {
113
- // JSON-LD scripts can have multiple, don't dedupe by default
114
97
  return undefined;
115
98
  }
116
99
  if ("tagName" in descriptor) {
117
- // For link tags, dedupe by rel if present
118
100
  if (descriptor.tagName === "link" && "rel" in descriptor) {
119
- // Some link rels should be unique (canonical), others not (stylesheet)
120
101
  const uniqueRels = ["canonical", "icon", "apple-touch-icon"];
121
102
  if (uniqueRels.includes(descriptor.rel as string)) {
122
103
  return `link:${descriptor.rel}`;
@@ -136,9 +117,6 @@ const defaultMetaDescriptors: MetaDescriptor[] = [
136
117
  { name: "viewport", content: "width=device-width, initial-scale=1" },
137
118
  ];
138
119
 
139
- /**
140
- * Helper to add or replace a descriptor in the result array
141
- */
142
120
  function addOrReplace(
143
121
  result: MetaDescriptor[],
144
122
  keyToIndex: Map<string, number>,
@@ -155,9 +133,6 @@ function addOrReplace(
155
133
  }
156
134
  }
157
135
 
158
- /**
159
- * Helper to update indices after removing an element
160
- */
161
136
  function updateIndicesAfterRemoval(
162
137
  keyToIndex: Map<string, number>,
163
138
  removedIndex: number,
@@ -169,17 +144,11 @@ function updateIndicesAfterRemoval(
169
144
  }
170
145
  }
171
146
 
172
- /**
173
- * Collect function for Meta handle.
174
- * Includes default meta descriptors, then deduplicates by key with later routes overriding earlier ones.
175
- * Supports title templates, absolute titles, and unset descriptors.
176
- */
177
147
  function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
178
148
  const result: MetaDescriptor[] = [];
179
149
  const keyToIndex = new Map<string, number>();
180
150
  let titleTemplate: string | undefined;
181
151
 
182
- // Add defaults first so they can be overridden
183
152
  for (const descriptor of defaultMetaDescriptors) {
184
153
  const key = getMetaKey(descriptor);
185
154
  if (key !== undefined) {
@@ -190,7 +159,9 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
190
159
 
191
160
  for (const descriptors of segments) {
192
161
  for (const descriptor of descriptors) {
193
- // Handle unset descriptors
162
+ // Deferred (Promise) descriptors are resolved BEFORE collectMeta runs
163
+ // (resolve-by-default), so every descriptor here is synchronous and
164
+ // participates in key-based dedup + title-templating like any other.
194
165
  if (isUnsetDescriptor(descriptor)) {
195
166
  const keyToRemove = descriptor.unset;
196
167
  if (keyToIndex.has(keyToRemove)) {
@@ -202,14 +173,11 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
202
173
  continue;
203
174
  }
204
175
 
205
- // Handle title descriptors with template/absolute support
206
176
  if (isTitleDescriptor(descriptor)) {
207
177
  const titleValue = descriptor.title;
208
178
 
209
179
  if (isTitleTemplate(titleValue)) {
210
- // Store template for subsequent title descriptors in child segments
211
180
  titleTemplate = titleValue.template;
212
- // Set the default title
213
181
  addOrReplace(
214
182
  result,
215
183
  keyToIndex,
@@ -220,7 +188,6 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
220
188
  }
221
189
 
222
190
  if (isAbsoluteTitle(titleValue)) {
223
- // Absolute title bypasses any template
224
191
  addOrReplace(
225
192
  result,
226
193
  keyToIndex,
@@ -230,9 +197,12 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
230
197
  continue;
231
198
  }
232
199
 
233
- // String title - apply template if one exists
200
+ // Insert the title literally. String.prototype.replace treats the
201
+ // replacement string specially ($&, $`, $', $$, $n), so a title like
202
+ // "Save $5" or one containing "$&" would be mangled. split/join inserts
203
+ // the raw value with no special-character interpretation.
234
204
  const finalTitle = titleTemplate
235
- ? titleTemplate.replace("%s", titleValue as string)
205
+ ? titleTemplate.split("%s").join(titleValue as string)
236
206
  : titleValue;
237
207
  addOrReplace(
238
208
  result,
@@ -243,7 +213,6 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
243
213
  continue;
244
214
  }
245
215
 
246
- // Handle all other descriptors
247
216
  const key = getMetaKey(descriptor);
248
217
  addOrReplace(result, keyToIndex, descriptor, key);
249
218
  }
@@ -257,6 +226,11 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
257
226
  *
258
227
  * Use `ctx.use(Meta)` in route handlers to push meta descriptors.
259
228
  * Use `<MetaTags />` component to render them in the document head.
229
+ *
230
+ * Deferred (Promise) descriptors are resolved before collectMeta runs
231
+ * (resolve-by-default), so they participate in deduplication and `%s`
232
+ * title-templating identically to synchronous descriptors. A descriptor that
233
+ * resolves to `null`/`undefined` (or rejects) is dropped.
260
234
  */
261
235
  export const Meta: Handle<MetaDescriptor, MetaDescriptor[]> = createHandle<
262
236
  MetaDescriptor,
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Built-in Script handle for injecting <script> tags into the document from
3
+ * route/layout handlers.
4
+ *
5
+ * Push from a SERVER handler with `ctx.use(Script)(config)`; render with the
6
+ * `<Scripts />` component (from `@rangojs/router/client`) placed in the Document
7
+ * `<head>` (and optionally a second `<Scripts position="body" />` at the top of
8
+ * `<body>`). This mirrors the Meta / <MetaTags> pair.
9
+ *
10
+ * The request CSP nonce is applied AUTOMATICALLY by <Scripts> to document-rendered
11
+ * scripts; consumers never pass a nonce. (An async script first loaded on a soft
12
+ * navigation is injected client-side without a nonce — it relies on
13
+ * 'strict-dynamic' or a host allowance; see the EXECUTION CONTRACT below and the
14
+ * /scripts skill.) A ScriptConfig is fully serializable (it crosses the
15
+ * server -> client handle-collection boundary), so callbacks like onLoad are NOT
16
+ * supported — a consumer needing them renders their own "use client" script.
17
+ *
18
+ * EXECUTION CONTRACT (see the /scripts skill for the full story):
19
+ * - Inline (`children`) and ordered external (`src`, optional `defer`) scripts
20
+ * are DOCUMENT-LOAD scripts: they execute only when present in the initial HTML
21
+ * response. <Scripts> freezes them after hydration, so a later client (soft)
22
+ * navigation never inserts an inert copy — React creates client-mounted
23
+ * <script> elements via innerHTML, which the HTML spec makes non-executing.
24
+ * - Async external scripts (`src` + `async: true`) are React RESOURCES: they load
25
+ * once when first encountered, including after a soft navigation, deduped by
26
+ * `src`. Use this for a vendor that should load on first visit to a route.
27
+ * - Reusing an `id` shapes the INITIAL document output (last-push-wins); it does
28
+ * not re-run a script during navigation. Per-navigation behavior belongs in a
29
+ * "use client" component or hook (see the GtmPageViews pattern in the demo).
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // External async loader (React resource — loads on first visit, even soft nav):
34
+ * ctx.use(Script)({ id: "stripe", src: "https://js.stripe.com/v3", async: true });
35
+ *
36
+ * // Inline bootstrap that self-injects its loader (GTM/GA4) — keep it inline so
37
+ * // React cannot hoist a declarative loader above the bootstrap:
38
+ * ctx.use(Script)({ id: "gtm", children: gtmBootstrap(containerId) });
39
+ *
40
+ * // External ordered (defer) with vendor attributes (document-load):
41
+ * ctx.use(Script)({
42
+ * id: "plausible",
43
+ * src: "https://plausible.io/js/script.js",
44
+ * defer: true,
45
+ * attributes: { "data-domain": "example.com" },
46
+ * });
47
+ * ```
48
+ */
49
+
50
+ import type { ScriptHTMLAttributes } from "react";
51
+ import { createHandle, type Handle } from "../handle.js";
52
+
53
+ /**
54
+ * Extra attributes forwarded onto the emitted <script>. Typed by React, so the
55
+ * casing is React's (`crossOrigin`, not `crossorigin`) and value shapes are
56
+ * checked at compile time. `data-*` attributes are allowed. Two groups are
57
+ * excluded: the fields the Script handle manages itself (`id`, `src`, `async`,
58
+ * `defer`, `type`, `children`, `nonce`, `dangerouslySetInnerHTML` — set those via
59
+ * the ScriptConfig fields), and ALL `on*` event handlers (`onLoad`, `onError`,
60
+ * …): a ScriptConfig is serialized across the server -> client handle boundary, so
61
+ * a function cannot survive it — render your own "use client" script for callbacks.
62
+ */
63
+ export type ScriptAttributes = Omit<
64
+ ScriptHTMLAttributes<HTMLScriptElement>,
65
+ | "id"
66
+ | "src"
67
+ | "async"
68
+ | "defer"
69
+ | "type"
70
+ | "children"
71
+ | "nonce"
72
+ | "dangerouslySetInnerHTML"
73
+ | `on${string}`
74
+ > & {
75
+ [dataAttr: `data-${string}`]: string | number | boolean | undefined;
76
+ };
77
+
78
+ /** Fields shared by every script shape. */
79
+ interface ScriptConfigBase {
80
+ /**
81
+ * Where <Scripts> renders this script.
82
+ * - "head" (default): the `<head>` <Scripts> site.
83
+ * - "body": the `<Scripts position="body" />` site at the top of <body>.
84
+ * Note: an external `async` script is hoisted into <head> by React regardless.
85
+ */
86
+ position?: "head" | "body";
87
+ /**
88
+ * The `type` attribute, as a free string: "module", "application/ld+json",
89
+ * "text/partytown", etc. Omitted means a classic script.
90
+ */
91
+ type?: string;
92
+ /** Extra React-cased attributes (`data-*`, `crossOrigin`, `integrity`, ...). */
93
+ attributes?: ScriptAttributes;
94
+ }
95
+
96
+ /**
97
+ * Inline script: a raw JS body rendered in place, escaped against `</script>`
98
+ * breakout. DOCUMENT-LOAD only (executes when present in the initial HTML;
99
+ * <Scripts> freezes it after hydration so navigation never inserts an inert
100
+ * copy). `id` is REQUIRED — inline scripts are never deduped by React, so a
101
+ * layout and a child pushing the same bootstrap would inject it twice. It is also
102
+ * rendered as the script's DOM `id`. Forbids `src`/`async`/`defer`. For analytics
103
+ * vendors (GTM/GA4/Segment) the body should
104
+ * create+append its own loader, so the loader is never a separate declarative tag
105
+ * React could hoist out of order.
106
+ */
107
+ export interface InlineScriptConfig extends ScriptConfigBase {
108
+ id: string;
109
+ children: string;
110
+ src?: never;
111
+ async?: never;
112
+ defer?: never;
113
+ }
114
+
115
+ /**
116
+ * External async script: a React-hoisted, `src`-deduped RESOURCE (the
117
+ * fire-and-forget loader case). Loads once when first encountered, including
118
+ * after a soft navigation. Deduped by `src` (matching React); `id` is optional
119
+ * and, when set, is rendered as the DOM `id` (not used as the dedup key here).
120
+ * Forbids `children`/`defer`.
121
+ */
122
+ export interface AsyncScriptConfig extends ScriptConfigBase {
123
+ src: string;
124
+ async: true;
125
+ id?: string;
126
+ children?: never;
127
+ defer?: never;
128
+ }
129
+
130
+ /**
131
+ * External ordered script: in-place, optionally `defer`. DOCUMENT-LOAD only
132
+ * (executes when present in the initial HTML; not re-run on navigation). `id` is
133
+ * optional (the dedup key falls back to `src`) and, when set, is rendered as the
134
+ * DOM `id`. Forbids `children`/`async`.
135
+ */
136
+ export interface OrderedScriptConfig extends ScriptConfigBase {
137
+ src: string;
138
+ defer?: boolean;
139
+ id?: string;
140
+ children?: never;
141
+ async?: never;
142
+ }
143
+
144
+ /**
145
+ * A single script to inject, as a discriminated union — exactly one of:
146
+ * inline (`id` + `children`), external async (`src` + `async: true`), or external
147
+ * ordered (`src`, optional `defer`). Invalid combinations (both `src`+`children`,
148
+ * `async`+`defer`, inline without `id`) are compile errors. The CSP nonce is
149
+ * applied by <Scripts>, never here.
150
+ */
151
+ export type ScriptConfig =
152
+ | InlineScriptConfig
153
+ | AsyncScriptConfig
154
+ | OrderedScriptConfig;
155
+
156
+ /** A config's runtime view, for validating untyped/serialized input. */
157
+ type LooseScriptConfig = {
158
+ id?: string;
159
+ src?: string;
160
+ children?: string;
161
+ async?: boolean;
162
+ defer?: boolean;
163
+ };
164
+
165
+ /**
166
+ * Dev-only validation. The discriminated union makes these states unrepresentable
167
+ * in TypeScript; the runtime checks exist only for untyped JavaScript callers and
168
+ * malformed serialized input, not as the primary contract.
169
+ */
170
+ function validateConfigDev(config: ScriptConfig): void {
171
+ if (process.env.NODE_ENV === "production") return;
172
+ const c = config as LooseScriptConfig;
173
+ if (c.src != null && c.children != null) {
174
+ console.warn(
175
+ `[Script] A config has both "src" and "children"; they are mutually ` +
176
+ `exclusive — "src" wins and the inline body is ignored.`,
177
+ );
178
+ } else if (c.src == null && c.children == null) {
179
+ console.warn(
180
+ `[Script] A config has neither "src" nor "children"; it injects nothing.`,
181
+ );
182
+ } else if (c.src == null && c.id == null) {
183
+ console.warn(
184
+ `[Script] An inline script was pushed without an "id" and cannot be ` +
185
+ `deduplicated. Pass an "id" so a layout + child pushing the same script ` +
186
+ `inject it only once.`,
187
+ );
188
+ }
189
+ if (c.async && c.defer) {
190
+ console.warn(
191
+ `[Script] A config has both "async" and "defer"; they are mutually ` +
192
+ `exclusive.`,
193
+ );
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Accumulate scripts across matched segments, parent -> child, preserving push
199
+ * order, last-push-wins per dedup key (mirroring the Meta handle).
200
+ *
201
+ * Dedup key:
202
+ * - async resources key by `src` ONLY — React itself dedups async scripts by src,
203
+ * so two async configs with different ids but the same src must collapse to one
204
+ * here (last wins) for a single, deterministic winner; otherwise React would
205
+ * silently pick one with undefined attribute precedence.
206
+ * - everything else keys by `id ?? src`.
207
+ *
208
+ * An (untyped) inline script with neither `id` nor `src` cannot be deduplicated;
209
+ * it is kept and validateConfigDev warns.
210
+ */
211
+ function collectScripts(segments: ScriptConfig[][]): ScriptConfig[] {
212
+ const result: ScriptConfig[] = [];
213
+ const keyToIndex = new Map<string, number>();
214
+
215
+ for (const configs of segments) {
216
+ for (const config of configs) {
217
+ validateConfigDev(config);
218
+ const isAsyncResource = config.src != null && config.async === true;
219
+ const key = isAsyncResource ? config.src : (config.id ?? config.src);
220
+ if (key === undefined) {
221
+ result.push(config);
222
+ continue;
223
+ }
224
+ const existing = keyToIndex.get(key);
225
+ if (existing !== undefined) {
226
+ result[existing] = config;
227
+ } else {
228
+ keyToIndex.set(key, result.length);
229
+ result.push(config);
230
+ }
231
+ }
232
+ }
233
+
234
+ return result;
235
+ }
236
+
237
+ /**
238
+ * Built-in handle for injecting scripts. Uses an explicit stable id (built-ins
239
+ * do not rely on the Vite id-injection plugin, which only covers consumer code).
240
+ */
241
+ export const Script: Handle<ScriptConfig, ScriptConfig[]> = createHandle<
242
+ ScriptConfig,
243
+ ScriptConfig[]
244
+ >(collectScripts, "__rsc_router_script__");
@@ -1,9 +1,3 @@
1
- /**
2
- * Cookie Override Handler
3
- *
4
- * Manages cookie-based host override for development environments.
5
- */
6
-
7
1
  import type { HostOverrideConfig } from "./types.js";
8
2
  import type { RouterRequestInput } from "../router/router-interfaces.js";
9
3
  import { matchPattern, parseRequest } from "./pattern-matcher.js";
@@ -12,52 +6,21 @@ import {
12
6
  InvalidHostnameError,
13
7
  HostValidationError,
14
8
  } from "./errors.js";
9
+ import { parseCookiesFromHeader } from "../server/cookie-parse.js";
15
10
 
16
- /**
17
- * Parse cookies from request
18
- */
19
11
  export function parseCookies(request: Request): Record<string, string> {
20
- const cookieHeader = request.headers.get("cookie");
21
- if (!cookieHeader) {
22
- return {};
23
- }
24
-
25
- const cookies: Record<string, string> = {};
26
- const pairs = cookieHeader.split(";");
27
-
28
- for (const pair of pairs) {
29
- const [name, ...rest] = pair.trim().split("=");
30
- if (name && rest.length > 0) {
31
- const value = rest.join("=");
32
- try {
33
- cookies[name] = decodeURIComponent(value);
34
- } catch {
35
- cookies[name] = value;
36
- }
37
- }
38
- }
39
-
40
- return cookies;
12
+ return parseCookiesFromHeader(request.headers.get("cookie"));
41
13
  }
42
14
 
43
- /**
44
- * Get cookie value from request
45
- */
46
15
  export function getCookie(request: Request, name: string): string | undefined {
47
16
  const cookies = parseCookies(request);
48
17
  return cookies[name];
49
18
  }
50
19
 
51
- /**
52
- * Create Set-Cookie header to delete a cookie
53
- */
54
20
  export function createDeleteCookieHeader(name: string): string {
55
21
  return `${name}=; Max-Age=0; Path=/; Secure; HttpOnly`;
56
22
  }
57
23
 
58
- /**
59
- * Create error response with cookie deletion
60
- */
61
24
  export function createCookieErrorResponse(
62
25
  cookieName: string,
63
26
  message: string,
@@ -77,9 +40,6 @@ export function createCookieErrorResponse(
77
40
  );
78
41
  }
79
42
 
80
- /**
81
- * Check if current host is allowed to use override
82
- */
83
43
  export function isHostAllowed(
84
44
  request: Request,
85
45
  allowedHosts: string[],
@@ -95,12 +55,6 @@ export function isHostAllowed(
95
55
  return false;
96
56
  }
97
57
 
98
- /**
99
- * Handle cookie override logic
100
- *
101
- * Returns overridden hostname if valid, original hostname if no override.
102
- * Throws errors for invalid overrides.
103
- */
104
58
  export function handleCookieOverride(
105
59
  request: Request,
106
60
  config: HostOverrideConfig | undefined,
@@ -115,51 +69,46 @@ export function handleCookieOverride(
115
69
  const cookieValue = getCookie(request, cookieName);
116
70
  const { hostname: originalHostname } = parseRequest(request);
117
71
 
118
- // No cookie - return original hostname
119
72
  if (!cookieValue) {
120
73
  return originalHostname;
121
74
  }
122
75
 
123
- // Check if current host is allowed
124
76
  const allowed = isHostAllowed(request, allowedHosts);
125
77
 
126
- // If not allowed, throw error
127
78
  if (!allowed) {
128
79
  throw new HostOverrideNotAllowedError(originalHostname, cookieName, {
129
80
  cause: { cookieValue, currentHost: originalHostname },
130
81
  });
131
82
  }
132
83
 
133
- // If allowed and has custom validation, run it
134
84
  if (validate) {
135
85
  try {
136
86
  const validatedHostname = validate(request, cookieValue, input);
137
87
  return validatedHostname;
138
88
  } catch (error) {
139
- // Wrap in HostValidationError
140
89
  const message = error instanceof Error ? error.message : String(error);
141
90
  throw new HostValidationError(message, error);
142
91
  }
143
92
  }
144
93
 
145
- // Default validation - verify it's a valid hostname using URL constructor
94
+ // URL.hostname ASCII-lowercases the host, so compare the cookie value against
95
+ // its canonical lowercase form (a mixed-case host is valid) and reject only
96
+ // when it carries a path/port. Return the canonical host so downstream
97
+ // matching, which assumes lowercase, sees a consistent value.
146
98
  try {
147
- // Try to construct a URL with the hostname to validate it
148
99
  const testUrl = new URL(`https://${cookieValue}`);
149
100
 
150
- // Ensure the hostname matches what we provided (URL constructor normalizes it)
151
- if (testUrl.hostname !== cookieValue) {
101
+ if (testUrl.hostname !== cookieValue.toLowerCase()) {
152
102
  throw new InvalidHostnameError(cookieValue, {
153
103
  cause: { original: cookieValue, normalized: testUrl.hostname },
154
104
  });
155
105
  }
106
+
107
+ return testUrl.hostname;
156
108
  } catch (error) {
157
- // If URL constructor failed, throw InvalidHostnameError with cause
158
109
  if (error instanceof InvalidHostnameError) {
159
110
  throw error;
160
111
  }
161
112
  throw new InvalidHostnameError(cookieValue, { cause: error });
162
113
  }
163
-
164
- return cookieValue;
165
114
  }
@@ -4,16 +4,10 @@
4
4
  * All host router errors extend HostRouterError for easy instance checking.
5
5
  */
6
6
 
7
- /**
8
- * Error options with cause
9
- */
10
7
  interface ErrorOptions {
11
8
  cause?: unknown;
12
9
  }
13
10
 
14
- /**
15
- * Base error class for all host router errors
16
- */
17
11
  export class HostRouterError extends Error {
18
12
  cause?: unknown;
19
13
 
@@ -27,9 +21,6 @@ export class HostRouterError extends Error {
27
21
  }
28
22
  }
29
23
 
30
- /**
31
- * Error thrown when pattern validation fails
32
- */
33
24
  export class InvalidPatternError extends HostRouterError {
34
25
  constructor(pattern: string, reason: string, options?: ErrorOptions) {
35
26
  super(`Invalid pattern "${pattern}": ${reason}`, options);
@@ -38,9 +29,6 @@ export class InvalidPatternError extends HostRouterError {
38
29
  }
39
30
  }
40
31
 
41
- /**
42
- * Error thrown when cookie override is not allowed
43
- */
44
32
  export class HostOverrideNotAllowedError extends HostRouterError {
45
33
  constructor(currentHost: string, cookieName: string, options?: ErrorOptions) {
46
34
  super(
@@ -52,9 +40,6 @@ export class HostOverrideNotAllowedError extends HostRouterError {
52
40
  }
53
41
  }
54
42
 
55
- /**
56
- * Error thrown when cookie hostname is invalid
57
- */
58
43
  export class InvalidHostnameError extends HostRouterError {
59
44
  constructor(hostname: string, options?: ErrorOptions) {
60
45
  super(`Invalid hostname format: "${hostname}"`, options);
@@ -63,9 +48,6 @@ export class InvalidHostnameError extends HostRouterError {
63
48
  }
64
49
  }
65
50
 
66
- /**
67
- * Error thrown when custom validation fails
68
- */
69
51
  export class HostValidationError extends HostRouterError {
70
52
  constructor(message: string, cause?: unknown) {
71
53
  super(message, { cause });
@@ -74,9 +56,6 @@ export class HostValidationError extends HostRouterError {
74
56
  }
75
57
  }
76
58
 
77
- /**
78
- * Error thrown when no route matches
79
- */
80
59
  export class NoRouteMatchError extends HostRouterError {
81
60
  constructor(hostname: string, pathname: string, options?: ErrorOptions) {
82
61
  super(`No route matched for ${hostname}${pathname}`, options);
@@ -86,8 +65,20 @@ export class NoRouteMatchError extends HostRouterError {
86
65
  }
87
66
 
88
67
  /**
89
- * Error thrown when handler type is invalid
68
+ * True when `err` is a NoRouteMatchError — including one thrown by a
69
+ * DUPLICATED @rangojs/router copy in the module graph (a workspace pinning a
70
+ * second version), whose class identity differs so a bare `instanceof` misses
71
+ * it and an unmatched-host 404 becomes an opaque 500. Use this in worker
72
+ * catch blocks instead of `instanceof`; it also matches by the stable `name`
73
+ * the constructor stamps.
90
74
  */
75
+ export function isNoRouteMatchError(err: unknown): err is NoRouteMatchError {
76
+ return (
77
+ err instanceof NoRouteMatchError ||
78
+ (err instanceof Error && err.name === "NoRouteMatchError")
79
+ );
80
+ }
81
+
91
82
  export class InvalidHandlerError extends HostRouterError {
92
83
  constructor(handler: unknown, options?: ErrorOptions) {
93
84
  super(`Invalid handler type: ${typeof handler}`, options);
package/src/host/index.ts CHANGED
@@ -11,8 +11,8 @@
11
11
  *
12
12
  * const router = createHostRouter();
13
13
  *
14
- * router.host(['.']).map(() => import('./apps/main'));
15
- * router.host(['admin.*']).map(() => import('./apps/admin'));
14
+ * router.host(['.']).lazy(() => import('./apps/main'));
15
+ * router.host(['admin.*']).lazy(() => import('./apps/admin'));
16
16
  *
17
17
  * export default {
18
18
  * fetch(request) {
@@ -20,6 +20,12 @@
20
20
  * }
21
21
  * };
22
22
  * ```
23
+ *
24
+ * The host surface (`Handler`, `Middleware`, `match`, `HostOverrideConfig.validate`)
25
+ * types `input` as `RouterRequestInput<any>` by design: a host router fans out to
26
+ * heterogeneous sub-apps with differing env/vars shapes, so there is no single
27
+ * `TEnv`/`TVars` to thread through. `input.env`/`input.vars` are therefore `any`
28
+ * here; the typed env shape lives on each sub-app's `createRouter<TEnv>()`.
23
29
  */
24
30
 
25
31
  // Core router
@@ -36,6 +42,7 @@ export {
36
42
  InvalidHostnameError,
37
43
  HostValidationError,
38
44
  NoRouteMatchError,
45
+ isNoRouteMatchError,
39
46
  InvalidHandlerError,
40
47
  } from "./errors.js";
41
48