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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +293 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2508 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +24 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-snapshot.ts +368 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +222 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1113 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +173 -35
  205. package/src/index.ts +241 -73
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +527 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +897 -0
  313. package/src/rsc/shell-serve.ts +124 -0
  314. package/src/rsc/ssr-setup.ts +144 -0
  315. package/src/rsc/transition-gate.ts +89 -0
  316. package/src/rsc/types.ts +95 -12
  317. package/src/runtime-env.ts +18 -0
  318. package/src/search-params.ts +99 -82
  319. package/src/segment-content-promise.ts +67 -0
  320. package/src/segment-loader-promise.ts +149 -0
  321. package/src/segment-system.tsx +349 -134
  322. package/src/serialize.ts +243 -0
  323. package/src/server/context.ts +459 -85
  324. package/src/server/cookie-parse.ts +32 -0
  325. package/src/server/cookie-store.ts +310 -0
  326. package/src/server/fetchable-loader-store.ts +11 -6
  327. package/src/server/handle-store.ts +123 -42
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +848 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +443 -135
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +76 -98
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +44 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +280 -0
  389. package/src/urls/pattern-types.ts +160 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Router Discovery
3
+ *
4
+ * Core discovery logic: imports the user's entry file via the RSC
5
+ * environment's module runner, generates manifests for each discovered
6
+ * router, and builds route tries for O(path_length) matching.
7
+ */
8
+
9
+ import {
10
+ buildCombinedRouteMapForRouterFile,
11
+ formatNestedRouterConflictError,
12
+ findNestedRouterConflict,
13
+ } from "../../build/generate-route-types.js";
14
+ // Pure data transforms over generateManifestFull's output. Imported directly
15
+ // from source (not the public ./build barrel, and not the runner) because they
16
+ // are realm-independent: buildRouteTrie/buildPerRouterTrie operate on plain
17
+ // manifest data, and collectFallbackClientRefs keys on the global-registry
18
+ // Symbol.for("react.client.reference"), so it detects client references in a
19
+ // boundary tree regardless of which realm imported the walker. Only
20
+ // generateManifestFull must stay on the runner (it invokes user handlers via
21
+ // RangoContext from the runner realm) — see the runner.import below.
22
+ import { buildRouteTrie, buildPerRouterTrie } from "../../build/route-trie.js";
23
+ import { collectFallbackClientRefs } from "../../build/collect-fallback-refs.js";
24
+ import {
25
+ flattenLeafEntries,
26
+ buildRouteToStaticPrefix,
27
+ } from "../utils/manifest-utils.js";
28
+ import type { DiscoveryState, PrecomputedEntry } from "./state.js";
29
+ import {
30
+ expandPrerenderRoutes,
31
+ renderStaticHandlers,
32
+ } from "./prerender-collection.js";
33
+ import {
34
+ resolveHostRouterHandlers,
35
+ DiscoveryError,
36
+ type CaughtDiscoveryError,
37
+ } from "./discovery-errors.js";
38
+ import { createRangoDebugger, timed, NS } from "../debug.js";
39
+ import { computeProductionHash } from "../plugins/client-ref-hashing.js";
40
+
41
+ const debug = createRangoDebugger(NS.discovery);
42
+
43
+ /**
44
+ * Import the user's entry via RSC runner, generate manifests for each
45
+ * discovered router, build route tries, and optionally run prerender
46
+ * expansion and static handler rendering (build mode only).
47
+ *
48
+ * Returns the imported `@rangojs/router/server` module so the caller
49
+ * can access the RouterRegistry and manifest setters.
50
+ */
51
+ export async function discoverRouters(
52
+ state: DiscoveryState,
53
+ rscEnv: any,
54
+ ): Promise<any> {
55
+ if (!state.resolvedEntryPath) return;
56
+
57
+ // Import the entry file via RSC environment.
58
+ // For node preset: this is the router file (createRouter() registers in RouterRegistry).
59
+ // For cloudflare preset: this is the worker entry (which imports the router).
60
+ await timed(debug, "inner: import entry", () =>
61
+ rscEnv.runner.import(state.resolvedEntryPath),
62
+ );
63
+
64
+ // Import the router package to access the registry
65
+ const serverMod = await timed(
66
+ debug,
67
+ "inner: import @rangojs/router/server",
68
+ () => rscEnv.runner.import("@rangojs/router/server"),
69
+ );
70
+ let registry: Map<string, any> = serverMod.RouterRegistry;
71
+
72
+ if (!registry || registry.size === 0) {
73
+ // No RSC routers found directly. Check for host routers with lazy handlers
74
+ // that need to be resolved to trigger sub-app createRouter() calls.
75
+ //
76
+ // Handler failures are collected rather than swallowed: when the registry
77
+ // is still empty afterwards, these errors (typically a sub-app whose router
78
+ // module failed to import) are the most likely cause and are surfaced in
79
+ // the terminal "No routers found" error below.
80
+ const discoveryErrors: CaughtDiscoveryError[] = [];
81
+ try {
82
+ const hostRegistry: Map<string, any> | undefined =
83
+ serverMod.HostRouterRegistry;
84
+
85
+ if (hostRegistry && hostRegistry.size > 0) {
86
+ console.log(
87
+ `[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
88
+ );
89
+
90
+ const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
91
+ discoveryErrors.push(...handlerErrors);
92
+ for (const { context, error } of handlerErrors) {
93
+ debug?.("caught error while resolving %s: %O", context, error);
94
+ }
95
+
96
+ // Re-read RouterRegistry - sub-app createRouter() calls should have populated it
97
+ const freshServerMod = await rscEnv.runner.import(
98
+ "@rangojs/router/server",
99
+ );
100
+ const freshRegistry: Map<string, any> = freshServerMod.RouterRegistry;
101
+
102
+ if (freshRegistry && freshRegistry.size > 0) {
103
+ // Update references so the manifest generation below uses the fresh data
104
+ Object.assign(serverMod, freshServerMod);
105
+ registry = freshRegistry;
106
+ }
107
+ }
108
+ } catch (error) {
109
+ // Host-router discovery is best-effort; record the failure so it can be
110
+ // surfaced if no routers are found.
111
+ discoveryErrors.push({ context: "host-router discovery", error });
112
+ }
113
+
114
+ // If still no routers after host router resolution, fail
115
+ if (!registry || registry.size === 0) {
116
+ throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
117
+ }
118
+ }
119
+
120
+ // generateManifestFull must run in the RSC runner realm: it invokes the
121
+ // user's urlpatterns.handler() via RangoContext, consuming router instances
122
+ // from the runner. The trie/fallback-ref builders are pure transforms over
123
+ // its output and are imported directly from source above.
124
+ const buildMod = await timed(
125
+ debug,
126
+ "inner: import @rangojs/router/build",
127
+ () => rscEnv.runner.import("@rangojs/router/build"),
128
+ );
129
+ const generateManifestFull = buildMod.generateManifestFull;
130
+
131
+ debug?.("inner: found %d router(s) in registry", registry.size);
132
+
133
+ const nestedRouterConflict = findNestedRouterConflict(
134
+ [...registry.values()]
135
+ .map((router) => router.__sourceFile)
136
+ .filter(
137
+ (sourceFile): sourceFile is string => typeof sourceFile === "string",
138
+ ),
139
+ );
140
+ if (nestedRouterConflict) {
141
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
142
+ }
143
+
144
+ // Build into local variables first. Only commit to state after the
145
+ // full pass succeeds, so a failed re-discovery preserves the last
146
+ // known-good state instead of leaving it partially wiped.
147
+ const newMergedRouteManifest: Record<string, string> = {};
148
+ const newMergedPrecomputedEntries: PrecomputedEntry[] = [];
149
+ const newPerRouterManifests: typeof state.perRouterManifests = [];
150
+ const newPerRouterManifestDataMap = new Map<string, any>();
151
+ const newPerRouterPrecomputedMap = new Map<string, PrecomputedEntry[]>();
152
+ const newPerRouterTrieMap = new Map<string, any>();
153
+ let mergedRouteAncestry: Record<string, string[]> = {};
154
+ let mergedRouteTrailingSlash: Record<string, string> = {};
155
+
156
+ let routerMountIndex = 0;
157
+ // Collect all manifests for trie building (avoid re-running generateManifest)
158
+ const allManifests: Array<{ id: string; manifest: any }> = [];
159
+
160
+ // Built-in clientChunks context (present only when the built-in strategy is
161
+ // active). Collect the production hashes of "use client" error/notFound
162
+ // fallback modules so the strategy can route them into app-fallback.
163
+ const clientChunkCtx = state.opts?.clientChunkCtx;
164
+ const collectClientFallbackRef = clientChunkCtx
165
+ ? (refKey: string) =>
166
+ clientChunkCtx.fallbackRefs.add(
167
+ computeProductionHash(state.projectRoot, refKey),
168
+ )
169
+ : undefined;
170
+ // Router-level boundary defaults (`createRouter({ defaultErrorBoundary, ... })`)
171
+ // are NOT in EntryData, so generateManifestFull's walk misses them. Collect any
172
+ // "use client" default boundary directly off the router instance. The value is
173
+ // commonly a handler function wrapping the client boundary in server providers,
174
+ // so collectFallbackClientRefs invokes + walks the tree. The walker keys on the
175
+ // global-registry Symbol.for("react.client.reference"), so it detects client
176
+ // references in a runner-realm boundary tree even when imported here directly.
177
+ const collectFromBoundaryNode = (node: unknown): void => {
178
+ if (collectClientFallbackRef) {
179
+ collectFallbackClientRefs(node, collectClientFallbackRef);
180
+ }
181
+ };
182
+
183
+ const manifestGenStart = debug ? performance.now() : 0;
184
+ for (const [id, router] of registry) {
185
+ if (!router.urlpatterns || !generateManifestFull) {
186
+ continue;
187
+ }
188
+
189
+ const manifest = await generateManifestFull(
190
+ router.urlpatterns,
191
+ routerMountIndex,
192
+ {
193
+ ...(router.__basename ? { urlPrefix: router.__basename } : {}),
194
+ ...(collectClientFallbackRef ? { collectClientFallbackRef } : {}),
195
+ },
196
+ );
197
+ routerMountIndex++;
198
+ allManifests.push({ id, manifest });
199
+
200
+ // Router-level "use client" boundary defaults -> app-fallback (the
201
+ // route-tree errorBoundary()/notFoundBoundary() helpers are already
202
+ // collected inside generateManifestFull via collectClientFallbackRef).
203
+ if (collectClientFallbackRef) {
204
+ collectFromBoundaryNode(router.__defaultErrorBoundary);
205
+ collectFromBoundaryNode(router.__defaultNotFoundBoundary);
206
+ collectFromBoundaryNode(router.__notFound);
207
+ }
208
+
209
+ const routeCount = Object.keys(manifest.routeManifest).length;
210
+ const staticRoutes = Object.values(manifest.routeManifest).filter(
211
+ (p: any) => !p.includes(":") && !p.includes("*"),
212
+ ).length;
213
+ const dynamicRoutes = routeCount - staticRoutes;
214
+
215
+ // Merge into the combined manifest
216
+ Object.assign(newMergedRouteManifest, manifest.routeManifest);
217
+
218
+ // Compute factory-only prefixes: dot-prefixed groups in the runtime
219
+ // manifest that the static parser cannot see. These are routes created
220
+ // by factory functions (e.g. createDocsPatterns()) and should always be
221
+ // supplemented on file change since HMR won't re-discover them.
222
+ let factoryOnlyPrefixes: Set<string> | undefined;
223
+ if (router.__sourceFile) {
224
+ const staticParsed = buildCombinedRouteMapForRouterFile(
225
+ router.__sourceFile,
226
+ );
227
+ const staticNames = new Set(Object.keys(staticParsed.routes));
228
+ factoryOnlyPrefixes = new Set<string>();
229
+ for (const name of Object.keys(manifest.routeManifest)) {
230
+ if (staticNames.has(name)) continue;
231
+ const dotIdx = name.indexOf(".");
232
+ if (dotIdx <= 0) continue;
233
+ const prefix = name.substring(0, dotIdx + 1);
234
+ if ([...staticNames].some((n) => n.startsWith(prefix))) continue;
235
+ factoryOnlyPrefixes.add(prefix);
236
+ }
237
+ if (factoryOnlyPrefixes.size === 0) factoryOnlyPrefixes = undefined;
238
+ }
239
+
240
+ newPerRouterManifests.push({
241
+ id,
242
+ routeManifest: manifest.routeManifest,
243
+ routeSearchSchemas: manifest.routeSearchSchemas,
244
+ sourceFile: router.__sourceFile,
245
+ factoryOnlyPrefixes,
246
+ });
247
+
248
+ // Merge ancestry (internal field, used only for trie building)
249
+ if (manifest._routeAncestry) {
250
+ Object.assign(mergedRouteAncestry, manifest._routeAncestry);
251
+ }
252
+ // Merge trailing slash config
253
+ if (manifest.routeTrailingSlash) {
254
+ Object.assign(mergedRouteTrailingSlash, manifest.routeTrailingSlash);
255
+ }
256
+
257
+ // Flatten prefix tree leaf nodes into precomputed entries.
258
+ // Leaf nodes (no children) can have their routes used directly by
259
+ // evaluateLazyEntry() without running the handler at runtime.
260
+ // Walk once into a per-router array, then fold it into the merged array;
261
+ // the merged and per-router entries are identical, so a second walk is
262
+ // redundant. Append order is preserved within and across routers.
263
+ const routerPrecomputed: PrecomputedEntry[] = [];
264
+ flattenLeafEntries(
265
+ manifest.prefixTree,
266
+ manifest.routeManifest,
267
+ routerPrecomputed,
268
+ );
269
+ newMergedPrecomputedEntries.push(...routerPrecomputed);
270
+
271
+ // Store per-router manifest and precomputed entries for isolated virtual modules.
272
+ newPerRouterManifestDataMap.set(id, manifest.routeManifest);
273
+ newPerRouterPrecomputedMap.set(id, routerPrecomputed);
274
+
275
+ console.log(
276
+ `[rango] Router "${id}" -> ${routeCount} routes ` +
277
+ `(${staticRoutes} static, ${dynamicRoutes} dynamic)`,
278
+ );
279
+ }
280
+
281
+ // Warn if multiple routers use auto-generated IDs (router_0, router_1, ...).
282
+ // Auto-IDs are assigned by counter and depend on module evaluation order,
283
+ // which can differ between build time and runtime (especially with dynamic
284
+ // imports in host routers). This causes per-router data to be loaded into
285
+ // the wrong router at runtime.
286
+ if (registry.size > 1) {
287
+ const autoIds = [...registry.keys()].filter((id) =>
288
+ /^router_\d+$/.test(id),
289
+ );
290
+ if (autoIds.length > 1) {
291
+ console.warn(
292
+ `[rango] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
293
+ `In multi-router setups, each createRouter() must have an explicit \`id\` option ` +
294
+ `to ensure per-router manifest data is matched correctly at runtime. ` +
295
+ `Example: createRouter({ id: "site", ... })`,
296
+ );
297
+ }
298
+ }
299
+
300
+ debug?.(
301
+ "inner: generated manifests for %d router(s) (%sms)",
302
+ allManifests.length,
303
+ (performance.now() - manifestGenStart).toFixed(1),
304
+ );
305
+
306
+ // Build route trie from merged manifest + ancestry
307
+ let newMergedRouteTrie: any = null;
308
+ const trieStart = debug ? performance.now() : 0;
309
+ if (Object.keys(newMergedRouteManifest).length > 0) {
310
+ if (mergedRouteAncestry) {
311
+ // Build routeToStaticPrefix from saved manifests
312
+ const routeToStaticPrefix: Record<string, string> = {};
313
+ for (const { manifest } of allManifests) {
314
+ // Root-level routes have empty static prefix
315
+ for (const name of Object.keys(manifest.routeManifest)) {
316
+ if (!(name in routeToStaticPrefix)) {
317
+ routeToStaticPrefix[name] = "";
318
+ }
319
+ }
320
+ buildRouteToStaticPrefix(manifest.prefixTree, routeToStaticPrefix);
321
+ }
322
+
323
+ // Collect prerender route names and response type routes from all manifests
324
+ const prerenderRouteNames = new Set<string>();
325
+ const passthroughRouteNames = new Set<string>();
326
+ const mergedResponseTypeRoutes: Record<string, string> = {};
327
+ for (const { manifest } of allManifests) {
328
+ if (manifest.prerenderRoutes) {
329
+ for (const name of manifest.prerenderRoutes) {
330
+ prerenderRouteNames.add(name);
331
+ }
332
+ }
333
+ if (manifest.passthroughRoutes) {
334
+ for (const name of manifest.passthroughRoutes) {
335
+ passthroughRouteNames.add(name);
336
+ }
337
+ }
338
+ if (manifest.responseTypeRoutes) {
339
+ Object.assign(mergedResponseTypeRoutes, manifest.responseTypeRoutes);
340
+ }
341
+ }
342
+
343
+ // buildRouteTrie reads these via ?.has / ?.[] — empty is observationally
344
+ // identical to undefined, so no empty->undefined coercion is needed.
345
+ newMergedRouteTrie = buildRouteTrie(
346
+ newMergedRouteManifest,
347
+ mergedRouteAncestry,
348
+ routeToStaticPrefix,
349
+ mergedRouteTrailingSlash,
350
+ prerenderRouteNames,
351
+ passthroughRouteNames,
352
+ mergedResponseTypeRoutes,
353
+ );
354
+
355
+ // Build per-router tries for multi-router isolation. Uses the single
356
+ // shared buildPerRouterTrie so the production serialized trie is built by
357
+ // exactly the same code as the dev/HMR runtime rebuild (manifest-init.ts).
358
+ // Returns null for route-less manifests (route-trie.ts).
359
+ for (const { id, manifest } of allManifests) {
360
+ const perRouterTrie = buildPerRouterTrie(manifest);
361
+ if (perRouterTrie) {
362
+ newPerRouterTrieMap.set(id, perRouterTrie);
363
+ }
364
+ }
365
+ }
366
+ }
367
+
368
+ debug?.(
369
+ "inner: trie build done (%sms)",
370
+ (performance.now() - trieStart).toFixed(1),
371
+ );
372
+
373
+ // Commit all local state to the shared discovery state atomically.
374
+ // This ensures a failed re-discovery (e.g. from a transient module
375
+ // evaluation error) preserves the last known-good state.
376
+ state.mergedRouteManifest = newMergedRouteManifest;
377
+ state.mergedPrecomputedEntries = newMergedPrecomputedEntries;
378
+ state.perRouterManifests = newPerRouterManifests;
379
+ state.perRouterManifestDataMap = newPerRouterManifestDataMap;
380
+ state.perRouterPrecomputedMap = newPerRouterPrecomputedMap;
381
+ state.perRouterTrieMap = newPerRouterTrieMap;
382
+ state.mergedRouteTrie = newMergedRouteTrie;
383
+
384
+ // Expand prerender routes and render static handlers (build mode only)
385
+ await expandPrerenderRoutes(state, rscEnv, registry, allManifests);
386
+ await renderStaticHandlers(state, rscEnv, registry);
387
+
388
+ return serverMod;
389
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Router discovery error aggregation.
3
+ *
4
+ * During host-router discovery the lazy mounts registered by a host router are
5
+ * invoked to trigger each sub-app's createRouter() registration. Some mount
6
+ * failures are expected in the temporary discovery server context (a sub-app may
7
+ * reference runtime-only bindings), so each is invoked defensively and its error
8
+ * is collected rather than thrown.
9
+ *
10
+ * Previously these errors were discarded with an empty `catch {}`. When a real
11
+ * failure - typically a sub-app whose router module fails to import - left the
12
+ * registry empty, discovery reported the misleading "No routers found" message
13
+ * with no trace of the underlying cause. The collected errors are now surfaced
14
+ * via the `DiscoveryError` thrown at the end of discovery (issue #499).
15
+ *
16
+ * Which entries to invoke is taken from the consumer's declared intent, not
17
+ * inferred from the function's shape. A host route is registered either with
18
+ * `.map((request) => Response)` (an inline request handler, `kind: "handler"`)
19
+ * or `.lazy(() => import("./sub-app"))` (a lazy mount, `kind: "lazy"`). Only
20
+ * `kind === "lazy"` entries are invoked here; inline handlers are never invoked
21
+ * during discovery (they need a Request and register no routers). Because a lazy
22
+ * entry is known to be a module loader, ANY failure it produces - a synchronous
23
+ * throw or a rejected promise - is a genuine discovery failure and is collected.
24
+ */
25
+
26
+ /** An error caught (and previously swallowed) while resolving host routers. */
27
+ export interface CaughtDiscoveryError {
28
+ /** Human-readable description of where the error was caught. */
29
+ context: string;
30
+ /** The caught value (an Error or otherwise). */
31
+ error: unknown;
32
+ }
33
+
34
+ /**
35
+ * Minimal shape of a host registry entry needed for mount resolution.
36
+ * Mirrors the runtime HostRouterRegistry value without coupling to its type.
37
+ */
38
+ interface HostRegistryRoute {
39
+ handler?: unknown;
40
+ kind?: string;
41
+ }
42
+ interface HostRegistryEntry {
43
+ routes: HostRegistryRoute[];
44
+ fallback?: HostRegistryRoute | null;
45
+ }
46
+
47
+ /** Indent every non-empty line of `text` by `pad`. */
48
+ function indent(text: string, pad: string): string {
49
+ return text
50
+ .split("\n")
51
+ .map((line) => (line.length > 0 ? pad + line : line))
52
+ .join("\n");
53
+ }
54
+
55
+ /**
56
+ * Invoke a single lazy mount to trigger its sub-app import (and createRouter()
57
+ * registration), collecting any failure under `context`. The entry is known to
58
+ * be a loader (`kind === "lazy"`), so both a synchronous throw and a rejected
59
+ * promise are genuine failures - no shape heuristics are needed.
60
+ */
61
+ async function invokeLazyMount(
62
+ loader: () => unknown,
63
+ context: string,
64
+ errors: CaughtDiscoveryError[],
65
+ ): Promise<void> {
66
+ try {
67
+ await loader();
68
+ } catch (error) {
69
+ errors.push({ context, error });
70
+ }
71
+ }
72
+
73
+ /** Whether a registry route is a `.lazy()` mount with an invokable loader. */
74
+ function isLazyMount(
75
+ route: HostRegistryRoute | null | undefined,
76
+ ): route is { handler: () => unknown; kind: "lazy" } {
77
+ return (
78
+ !!route && route.kind === "lazy" && typeof route.handler === "function"
79
+ );
80
+ }
81
+
82
+ /**
83
+ * Invoke every lazy mount in the host registry to trigger sub-app
84
+ * createRouter() registration, collecting (not throwing) any failures.
85
+ *
86
+ * Only `.lazy()` entries are invoked; `.map()` inline request handlers are
87
+ * skipped (they need a Request and register no routers). Failures are returned
88
+ * rather than thrown because some mounts legitimately fail in the temporary
89
+ * discovery server context; the caller decides whether the failures matter,
90
+ * which is only when discovery finds no routers at all.
91
+ */
92
+ export async function resolveHostRouterHandlers(
93
+ hostRegistry: Map<string, HostRegistryEntry>,
94
+ ): Promise<CaughtDiscoveryError[]> {
95
+ const errors: CaughtDiscoveryError[] = [];
96
+
97
+ for (const [hostId, entry] of hostRegistry) {
98
+ for (const route of entry.routes) {
99
+ if (isLazyMount(route)) {
100
+ await invokeLazyMount(
101
+ route.handler,
102
+ `host "${hostId}" route handler`,
103
+ errors,
104
+ );
105
+ }
106
+ }
107
+ if (isLazyMount(entry.fallback)) {
108
+ await invokeLazyMount(
109
+ entry.fallback.handler,
110
+ `host "${hostId}" fallback handler`,
111
+ errors,
112
+ );
113
+ }
114
+ }
115
+
116
+ return errors;
117
+ }
118
+
119
+ /**
120
+ * Build the terminal "No routers found" message, appending any errors caught
121
+ * during host-router discovery so the real cause is visible.
122
+ *
123
+ * The aggregated errors are inlined into the message (in addition to being
124
+ * attached via `cause` on `DiscoveryError`) so they survive every caller: the
125
+ * dev/HMR paths log `err.message`, and the build path re-throws using
126
+ * `err.stack`, which begins with the message. None of those callers traverse
127
+ * `cause`, so the message must carry the detail. Each error includes its stack
128
+ * when available.
129
+ */
130
+ export function formatNoRoutersError(
131
+ entryPath: string | undefined,
132
+ errors: CaughtDiscoveryError[],
133
+ ): string {
134
+ const base = `[rango] No routers found in registry after importing ${entryPath}`;
135
+ if (errors.length === 0) {
136
+ return base;
137
+ }
138
+
139
+ const formatted = errors
140
+ .map(({ context, error }) => {
141
+ const err = error instanceof Error ? error : new Error(String(error));
142
+ const detail = err.stack ?? err.message;
143
+ return ` - while resolving ${context}:\n${indent(detail, " ")}`;
144
+ })
145
+ .join("\n");
146
+
147
+ return (
148
+ `${base}\n\n` +
149
+ `${errors.length} error(s) were caught during host-router discovery and ` +
150
+ `likely explain why no routers were registered:\n${formatted}`
151
+ );
152
+ }
153
+
154
+ /**
155
+ * Reduce the caught errors to an `ErrorOptions.cause`: a single failure becomes
156
+ * the direct cause; multiple failures are wrapped in an `AggregateError` so
157
+ * each underlying error remains reachable. No errors -> no cause.
158
+ */
159
+ function toCause(errors: CaughtDiscoveryError[]): unknown {
160
+ if (errors.length === 0) return undefined;
161
+ if (errors.length === 1) return errors[0].error;
162
+ return new AggregateError(
163
+ errors.map((e) => e.error),
164
+ "Multiple host-router handlers failed during discovery",
165
+ );
166
+ }
167
+
168
+ /**
169
+ * Thrown when router discovery completes without finding any routers.
170
+ *
171
+ * Carries the entry path and the individual failures caught while resolving
172
+ * host-router lazy handlers. The formatted detail is embedded in `message` (for
173
+ * callers that log `err.message`/`err.stack`) and the underlying error(s) are
174
+ * also attached via `cause` (a single failure directly, multiple wrapped in an
175
+ * `AggregateError`) for cause-aware tooling such as the Vite error overlay.
176
+ */
177
+ export class DiscoveryError extends Error {
178
+ /** The entry file that was imported before discovery gave up. */
179
+ readonly entryPath: string | undefined;
180
+ /** Individual failures caught while resolving host-router handlers. */
181
+ readonly caught: CaughtDiscoveryError[];
182
+
183
+ constructor(entryPath: string | undefined, caught: CaughtDiscoveryError[]) {
184
+ super(formatNoRoutersError(entryPath, caught));
185
+ const cause = toCause(caught);
186
+ if (cause !== undefined) {
187
+ this.cause = cause;
188
+ }
189
+ this.name = "DiscoveryError";
190
+ this.entryPath = entryPath;
191
+ this.caught = caught;
192
+ Object.setPrototypeOf(this, DiscoveryError.prototype);
193
+ }
194
+ }
195
+
196
+ /** How the dev caller should surface a discovery failure. */
197
+ export interface DiscoveryFailureReport {
198
+ level: "error" | "warn";
199
+ message: string;
200
+ }
201
+
202
+ /**
203
+ * Decide how to surface a dev-boot discovery failure in the terminal.
204
+ *
205
+ * The bare "no routers found" case (a DiscoveryError with no caught host-handler
206
+ * failures) is ambiguous. It is either:
207
+ * - a genuine misconfiguration — the entry never calls createRouter(), or the
208
+ * configured entry path is wrong; or
209
+ * - a transient artifact of a Vite dependency re-optimization racing with boot
210
+ * discovery (a module read before the entry import resolves to the
211
+ * pre-optimize copy of the runner graph while createRouter() populated the
212
+ * post-optimize copy — see router-discovery.ts).
213
+ *
214
+ * We can only tell them apart when the caller observed the dep optimizer's
215
+ * `browserHash` change across the discovery attempt (`reoptimizeObserved`): a
216
+ * reload-causing re-optimization landed mid-flight, so the empty read was
217
+ * transient and the app self-heals per-request (handler.ts builds the trie from
218
+ * the router's live urlpatterns) with discovery re-running on the next boot.
219
+ *
220
+ * A DiscoveryError that DOES carry caught host-handler failures already embeds
221
+ * the real cause in its message, and any non-DiscoveryError is a hard failure;
222
+ * both stay loud with full detail.
223
+ */
224
+ export function describeDiscoveryFailure(
225
+ err: unknown,
226
+ opts: { reoptimizeObserved?: boolean } = {},
227
+ ): DiscoveryFailureReport {
228
+ if (err instanceof DiscoveryError && err.caught.length === 0) {
229
+ const entry = err.entryPath ?? "the router entry";
230
+ if (opts.reoptimizeObserved) {
231
+ return {
232
+ level: "warn",
233
+ message:
234
+ `[rango] No routers found while Vite was re-optimizing dependencies on ` +
235
+ `dev boot. This is transient: routes are served per-request and ` +
236
+ `discovery re-runs automatically, so it clears on the next boot. If ` +
237
+ `routes still 404, confirm ${entry} calls createRouter().`,
238
+ };
239
+ }
240
+ return {
241
+ level: "error",
242
+ message:
243
+ `${err.message}\n` +
244
+ ` Ensure ${entry} calls createRouter() at module top level and that the ` +
245
+ `configured router entry path is correct.`,
246
+ };
247
+ }
248
+
249
+ const e = err as { stack?: string; message?: string };
250
+ const detail = e?.stack ?? e?.message ?? String(err);
251
+ return {
252
+ level: "error",
253
+ message: `[rango] Router discovery failed: ${detail}`,
254
+ };
255
+ }