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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -1,8 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
3
3
 
4
- // -- Revalidation trace types --
5
-
6
4
  export interface RevalidationTraceEntry {
7
5
  segmentId: string;
8
6
  segmentType: string;
@@ -36,8 +34,6 @@ export interface RevalidationTrace {
36
34
  entries: RevalidationTraceEntry[];
37
35
  }
38
36
 
39
- // -- Log context --
40
-
41
37
  interface RouterLogContext {
42
38
  requestId: string;
43
39
  transactionId: string;
@@ -195,8 +191,6 @@ export function debugWarn(
195
191
  console.warn(`${prefix} ${message}`);
196
192
  }
197
193
 
198
- // -- Revalidation trace helpers --
199
-
200
194
  export function isTraceActive(): boolean {
201
195
  if (!INTERNAL_RANGO_DEBUG) return false;
202
196
  const ctx = routerLogContext.getStore();
@@ -1,9 +1,3 @@
1
- /**
2
- * Router Manifest Loading
3
- *
4
- * Handles lazy loading and validation of route manifests.
5
- */
6
-
7
1
  import { invariant, RouteNotFoundError } from "../errors";
8
2
  import { createRouteHelpers } from "../route-definition";
9
3
  import {
@@ -14,29 +8,38 @@ import {
14
8
  type MetricsStore,
15
9
  } from "../server/context";
16
10
  import MapRootLayout from "../server/root-layout";
11
+ import { joinPrefix } from "./pattern-matching.js";
17
12
  import type { RouteEntry } from "../types";
18
13
  import type { UrlPatterns } from "../urls";
14
+ import {
15
+ isIncludeProvider,
16
+ resolveIncludeModule,
17
+ } from "../urls/include-provider.js";
19
18
  import { VERSION } from "@rangojs/router:version";
20
19
 
20
+ // Tags an error thrown while dynamically importing an async include's module
21
+ // (`() => import("./routes")`) so loadManifest's outer catch re-raises it as a
22
+ // server error rather than masking it as a RouteNotFoundError (404).
23
+ const MODULE_LOAD_FAILURE = Symbol.for("rango.moduleLoadFailure");
24
+
21
25
  // Module-level manifest cache: avoids re-executing DSL handler on every request.
22
26
  // Handler execution is deterministic (components, loaders, middleware are module-level
23
27
  // stable references), so the resulting EntryData tree can be safely cached and reused
24
28
  // across requests within the same isolate.
25
29
  //
26
- // Cache is keyed by (VERSION, mountIndex, routeKey, isSSR). VERSION comes from the
30
+ // Cache is keyed by (VERSION, routerId, mountIndex, routeKey, isSSR). routeKey is
31
+ // REQUIRED in the key: loadManifest() runs the handler with forRoute=routeKey, and
32
+ // path-helper.ts prunes (skips registering) every route except forRoute, so the
33
+ // resulting Store.manifest is pruned to the requested route — NOT the full include.
34
+ // Dropping routeKey would make a sibling route miss and overwrite this entry with its
35
+ // own pruned manifest, so alternating sibling requests would thrash (re-run the
36
+ // handler every time). Running the include handler once per isolate instead of once
37
+ // per route is possible but needs an unpruned manifest cache with prune-on-read — see
38
+ // LP1 in docs/internal/matching-and-lazy-discovery.md. VERSION comes from the
27
39
  // @rangojs/router:version virtual module which Vite invalidates on RSC module HMR.
28
40
  // When VERSION changes, this module re-evaluates and the cache is recreated empty.
29
- // Including VERSION in the key is additional defense against stale entries.
30
41
  const manifestModuleCache = new Map<string, Map<string, EntryData>>();
31
42
 
32
- /**
33
- * Load manifest from route entry with AsyncLocalStorage context
34
- * Handles lazy imports, unwrapping, and validation
35
- *
36
- * Results are cached at module level after first execution. Subsequent calls
37
- * for the same (routeKey, isSSR) within the same isolate return cached data
38
- * without re-executing the DSL handler.
39
- */
40
43
  /**
41
44
  * Clear the module-level manifest cache.
42
45
  * Called on HMR to ensure stale handler references are discarded.
@@ -65,9 +68,11 @@ export async function loadManifest(
65
68
 
66
69
  const mountIndex = entry.mountIndex;
67
70
 
68
- // Check module-level cache (persists across requests within same isolate)
71
+ // Check module-level cache (persists across requests within same isolate).
69
72
  // Include routerId so multi-router setups (host routing) don't share cached
70
73
  // EntryData across routers with overlapping mountIndex + routeKey combinations.
74
+ // routeKey is in the key because loadManifest() builds a manifest pruned to
75
+ // forRoute=routeKey (see path-helper.ts) — see the cache comment above.
71
76
  const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
72
77
  const cached = manifestModuleCache.get(cacheKey);
73
78
  if (cached) {
@@ -77,10 +82,14 @@ export async function loadManifest(
77
82
  Store.mountIndex = mountIndex;
78
83
  Store.isSSR = isSSR;
79
84
  if (metricsStore) Store.metrics = metricsStore;
80
- // Restore cached manifest into Store
81
- for (const [k, v] of cached) {
82
- Store.manifest.set(k, v);
83
- }
85
+ // Alias the request-scoped Store to the cached Map instead of copying it
86
+ // entry-by-entry. At request time this Store is throwaway (no ambient
87
+ // RangoContext store spans classify->match->render, so getOrCreateStore
88
+ // returns a fresh detached store here), so nothing mutates it in place after
89
+ // loadManifest returns. The fresh path below REASSIGNS Store.manifest rather
90
+ // than clearing it in place, so a later fresh load sharing this Store cannot
91
+ // poison the cached Map.
92
+ Store.manifest = cached;
84
93
  pushMetric?.("manifest:cache-hit", cacheStart);
85
94
  return cached.get(routeKey)!;
86
95
  }
@@ -88,22 +97,21 @@ export async function loadManifest(
88
97
  const storeSetupStart = performance.now();
89
98
  const Store = getContext().getOrCreateStore(routeKey);
90
99
 
91
- // Set mount index in store for unique shortCode prefixes
92
100
  Store.mountIndex = mountIndex;
93
-
94
- // Set isSSR flag so loading() can check if we're in SSR
95
101
  Store.isSSR = isSSR;
96
102
 
97
- // Attach metrics store to context if provided
98
103
  if (metricsStore) {
99
104
  Store.metrics = metricsStore;
100
105
  }
101
106
 
102
107
  pushMetric?.("manifest:store-setup", storeSetupStart);
103
108
 
104
- // Clear manifest before rebuilding to prevent stale entry mutations
105
109
  const clearStart = performance.now();
106
- Store.manifest.clear();
110
+ // Reassign rather than clear() in place: a prior cache-hit may have aliased
111
+ // Store.manifest to a shared module-cache Map (see cache-hit branch above),
112
+ // and clearing it in place would poison that cache. A fresh Map isolates this
113
+ // build; the cache still receives an independent copy at the end.
114
+ Store.manifest = new Map();
107
115
  pushMetric?.("manifest:clear", clearStart);
108
116
 
109
117
  try {
@@ -174,9 +182,35 @@ export async function loadManifest(
174
182
  // not exist in the non-lazy (root handler) path and would produce
175
183
  // mismatched shortCodes.
176
184
  if (entry.lazy && entry.lazyPatterns) {
185
+ // Resolve an async include provider (`() => import("./routes")`) before
186
+ // running its handler. The match-time precomputed shortcut can skip
187
+ // evaluateLazyEntry's resolution, so render-time must resolve it here;
188
+ // cache the resolved patterns on the entry so later renders reuse them.
189
+ if (isIncludeProvider(entry.lazyPatterns)) {
190
+ try {
191
+ entry.lazyPatterns = resolveIncludeModule(
192
+ await entry.lazyPatterns(),
193
+ entry.staticPrefix,
194
+ ) as unknown as UrlPatterns<any>;
195
+ } catch (err) {
196
+ // A failed dynamic import of a REAL, matched route's module is a
197
+ // server error, not a missing route. Tag it so the outer catch
198
+ // surfaces it as a 5xx instead of masking it as a
199
+ // RouteNotFoundError — which renders the 404 page (monitoring
200
+ // misses the failure and a CDN could cache the 404).
201
+ if (err && typeof err === "object") {
202
+ (err as Record<PropertyKey, unknown>)[MODULE_LOAD_FAILURE] =
203
+ true;
204
+ }
205
+ throw err;
206
+ }
207
+ }
177
208
  const lazyPatterns = entry.lazyPatterns as UrlPatterns<any>;
178
209
  const includePrefix = (entry as any)._lazyPrefix || "";
179
- const fullPrefix = (lazyContext?.urlPrefix || "") + includePrefix;
210
+ // Slash-collapsing join so a trailing-slash parent prefix does not
211
+ // bake a double slash into the registered route patterns (must match
212
+ // the same join in evaluateLazyEntry / the build-time runWithPrefixes).
213
+ const fullPrefix = joinPrefix(lazyContext?.urlPrefix, includePrefix);
180
214
 
181
215
  if (fullPrefix || lazyContext?.namePrefix) {
182
216
  return runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () =>
@@ -186,28 +220,19 @@ export async function loadManifest(
186
220
  return lazyPatterns.handler();
187
221
  }
188
222
 
189
- // Wrap handler execution in root layout so routes get correct parent
190
- // This ensures all routes are registered with the layout as their parent
191
223
  let promiseResult: Promise<any> | null = null;
192
224
  const wrappedItems = helpers.layout(MapRootLayout, () => {
193
225
  const result = entry.handler();
194
226
  if (result instanceof Promise) {
195
- // Lazy handler detected - capture promise for async handling
196
227
  promiseResult = result;
197
- return []; // Return empty, we'll discard this wrapped result
228
+ return [];
198
229
  }
199
230
  return result;
200
231
  });
201
232
 
202
- // Handle lazy (Promise-based) handlers
203
233
  if (promiseResult !== null) {
204
234
  const load = await (promiseResult as Promise<any>);
205
- if (
206
- load &&
207
- load !== null &&
208
- typeof load === "object" &&
209
- "default" in load
210
- ) {
235
+ if (load && typeof load === "object" && "default" in load) {
211
236
  // Promise<{ default: () => Array }> - e.g., dynamic import
212
237
  if (typeof load.default !== "function") {
213
238
  throw new Error(
@@ -233,7 +258,6 @@ export async function loadManifest(
233
258
  );
234
259
  }
235
260
 
236
- // Inline handler - routes were registered with correct parent inside layout
237
261
  return [wrappedItems].flat(3);
238
262
  },
239
263
  );
@@ -266,6 +290,16 @@ export async function loadManifest(
266
290
 
267
291
  return Store.manifest.get(routeKey)!;
268
292
  } catch (e) {
293
+ // A tagged async-include module-load failure is a server error for a REAL
294
+ // matched route — re-raise it unchanged (becomes a 5xx) instead of masking
295
+ // it as a RouteNotFoundError (which renders/caches a 404).
296
+ if (
297
+ e &&
298
+ typeof e === "object" &&
299
+ (e as Record<PropertyKey, unknown>)[MODULE_LOAD_FAILURE]
300
+ ) {
301
+ throw e;
302
+ }
269
303
  throw new RouteNotFoundError(
270
304
  `Failed to load route handlers for ${path}: ${(e as Error).message}`,
271
305
  {
@@ -1,10 +1,3 @@
1
- /**
2
- * Match API
3
- *
4
- * Extracted from createRouter closure. Contains match context creation functions
5
- * and the matchError function for error boundary resolution.
6
- */
7
-
8
1
  import { CacheScope, createCacheScope } from "../cache/cache-scope.js";
9
2
  import { RouteNotFoundError } from "../errors";
10
3
  import {
@@ -33,6 +26,7 @@ import type { MatchContext } from "./match-context.js";
33
26
  import type { MatchApiDeps, ActionContext } from "./types.js";
34
27
  import {
35
28
  getRequestContext,
29
+ _getRequestContext,
36
30
  setRequestContextPrevRouteKey,
37
31
  } from "../server/request-context.js";
38
32
  import { isAutoGeneratedRouteName } from "../route-name.js";
@@ -54,44 +48,74 @@ export async function createMatchContextForFull<TEnv>(
54
48
  deps: MatchApiDeps<TEnv>,
55
49
  findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
56
50
  ): Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }> {
57
- const url = new URL(request.url);
58
- const pathname = url.pathname;
51
+ const reqCtx = _getRequestContext();
52
+ const cleanUrl = reqCtx?.url ?? stripInternalParams(new URL(request.url));
53
+ const pathname = cleanUrl.pathname;
59
54
 
60
55
  const metricsStore = deps.getMetricsStore();
61
56
 
62
- // Full renders always resolve fresh with isSSR: true because loadManifest
63
- // keys its cache on isSSR and stamps Store.isSSR for downstream behavior.
64
- const result = await resolveRoute<TEnv>(pathname, {
65
- findMatch: (p) => deps.findMatch(p, metricsStore),
66
- metricsStore,
67
- isSSR: true,
68
- });
57
+ const isHmr = !!request.headers.get("X-RSC-HMR");
58
+ // HMR: clear the manifest cache so stale handler references are discarded.
59
+ if (isHmr) {
60
+ clearManifestCache();
61
+ }
62
+
63
+ // Reuse the snapshot classifyRequest already resolved when it is a full-render
64
+ // snapshot (isSSR:true) and HMR has not invalidated it — mirrors the partial
65
+ // path. A partial-mode snapshot (isSSR:false) carries the wrong loading()
66
+ // entries and lives in a different manifest partition, so it is NOT reusable.
67
+ const classified = reqCtx?._classifiedRoute;
68
+ const classifiedRoute =
69
+ !isHmr && classified?.isSSR === true ? classified : undefined;
70
+
71
+ // On the reuse path, findMatch + manifest-loading already ran during
72
+ // classifyRequest (which carries no metricsStore), so only the cheap
73
+ // entries/cacheScope materialization is timed here under a distinct label. On
74
+ // the fresh path, resolveRoute emits route-matching + manifest-loading as usual.
75
+ const routeMatchStart = metricsStore ? performance.now() : 0;
69
76
 
70
- if (!result) {
71
- throw new RouteNotFoundError(`No route matched for ${pathname}`, {
72
- cause: { pathname, method: request.method },
77
+ let snapshot: RouteSnapshot<TEnv>;
78
+ if (classifiedRoute && classifiedRoute.manifestEntry) {
79
+ snapshot = ensureFullRouteSnapshot(classifiedRoute);
80
+ if (metricsStore) {
81
+ metricsStore.metrics.push({
82
+ label: "route-matching:reuse",
83
+ duration: performance.now() - routeMatchStart,
84
+ startTime: routeMatchStart - metricsStore.requestStart,
85
+ });
86
+ }
87
+ } else {
88
+ const result = await resolveRoute<TEnv>(pathname, {
89
+ findMatch: (p) => deps.findMatch(p, metricsStore),
90
+ metricsStore,
91
+ isSSR: true,
73
92
  });
74
- }
75
93
 
76
- if (result.type === "redirect") {
77
- return {
78
- type: "redirect",
79
- redirectUrl: result.redirectTo + url.search,
80
- };
81
- }
94
+ if (!result) {
95
+ throw new RouteNotFoundError(`No route matched for ${pathname}`, {
96
+ cause: { pathname, method: request.method },
97
+ });
98
+ }
99
+
100
+ if (result.type === "redirect") {
101
+ // Preserve the raw query string (including internal _rsc* params) on the
102
+ // redirect target, matching the pre-reuse behavior of `url.search`.
103
+ const rawSearch = (reqCtx?.originalUrl ?? new URL(request.url)).search;
104
+ return {
105
+ type: "redirect",
106
+ redirectUrl: result.redirectTo + rawSearch,
107
+ };
108
+ }
82
109
 
83
- const snapshot = result.snapshot;
110
+ snapshot = result.snapshot;
111
+ }
84
112
 
85
113
  const { matched } = snapshot;
86
114
 
87
- // Backward compat: downstream middleware reads matched.pt
88
115
  if (snapshot.isPassthrough) {
89
116
  matched.pt = true;
90
117
  }
91
118
 
92
- // Clean URL without internal _rsc* params for userland access
93
- const cleanUrl = stripInternalParams(url);
94
-
95
119
  const handlerContext = createHandlerContext(
96
120
  matched.params,
97
121
  request,
@@ -175,8 +199,13 @@ export async function createMatchContextForPartial<TEnv>(
175
199
  findInterceptForRoute: MatchApiDeps<TEnv>["findInterceptForRoute"],
176
200
  actionContext?: ActionContext,
177
201
  ): Promise<MatchContext<TEnv> | null> {
178
- const url = new URL(request.url);
179
- const pathname = url.pathname;
202
+ const reqCtx = _getRequestContext();
203
+ // resolveNavigation needs the raw URL (reads internal _rsc_segments /
204
+ // _rsc_stale params that stripInternalParams removes); the clean URL feeds the
205
+ // handler context. Both come off the request context when present.
206
+ const rawUrl = reqCtx?.originalUrl ?? new URL(request.url);
207
+ const cleanUrl = reqCtx?.url ?? stripInternalParams(rawUrl);
208
+ const pathname = cleanUrl.pathname;
180
209
 
181
210
  const metricsStore = deps.getMetricsStore();
182
211
 
@@ -188,11 +217,13 @@ export async function createMatchContextForPartial<TEnv>(
188
217
  }
189
218
 
190
219
  // Reuse the classified snapshot when available and not invalidated by HMR.
191
- // classifyRequest already called resolveRoute(lite) with isSSR=false, which
192
- // matches the partial path. On HMR, discard to pick up manifest changes.
193
- const classifiedRoute = isHmr
194
- ? undefined
195
- : getRequestContext()?._classifiedRoute;
220
+ // classifyRequest resolves a partial request with isSSR=false, matching this
221
+ // path. Never reuse an isSSR:true (full-render) snapshot its loading()
222
+ // entries and manifest partition are wrong here. On HMR, discard to pick up
223
+ // manifest changes.
224
+ const classified = reqCtx?._classifiedRoute;
225
+ const classifiedRoute =
226
+ !isHmr && classified?.isSSR !== true ? classified : undefined;
196
227
 
197
228
  // Time route matching. On the reuse path, only nav findMatch calls are new
198
229
  // (current-route findMatch and manifest-loading were already timed during
@@ -231,18 +262,13 @@ export async function createMatchContextForPartial<TEnv>(
231
262
  matched.pt = true;
232
263
  }
233
264
 
234
- // Navigation state (prev + intercept-source findMatch calls)
235
- const nav = resolveNavigation(request, url, matched.routeKey, {
265
+ const nav = await resolveNavigation(request, rawUrl, matched.routeKey, {
236
266
  findMatch: deps.findMatch,
237
267
  });
238
268
  if (!nav) {
239
269
  return null;
240
270
  }
241
271
 
242
- // Push route-matching metric. On the fresh path this covers all findMatch
243
- // calls (current + prev + intercept-source). On the reuse path, current-route
244
- // findMatch was already timed during classification, so this only covers
245
- // the nav lookups (prev + intercept-source).
246
272
  if (metricsStore) {
247
273
  const isReuse = !!classifiedRoute;
248
274
  metricsStore.metrics.push({
@@ -259,9 +285,6 @@ export async function createMatchContextForPartial<TEnv>(
259
285
  });
260
286
  }
261
287
 
262
- // Clean URL without internal _rsc* params for userland access
263
- const cleanUrl = stripInternalParams(url);
264
-
265
288
  const handlerContext = createHandlerContext(
266
289
  matched.params,
267
290
  request,
@@ -304,10 +327,11 @@ export async function createMatchContextForPartial<TEnv>(
304
327
  });
305
328
  }
306
329
 
307
- // Store previous route key on the request context for revalidation
308
- // fromRouteName. Uses effectiveFromMatch so intercept-source navigations
309
- // see the intercept origin route, not the plain previous URL route.
310
- setRequestContextPrevRouteKey(nav.effectiveFromMatch?.routeKey);
330
+ setRequestContextPrevRouteKey(
331
+ nav.effectiveFromMatch?.routeKey,
332
+ nav.effectiveFromUrl,
333
+ nav.effectiveFromMatch?.params ?? nav.prevParams,
334
+ );
311
335
 
312
336
  const interceptSelectorContext: InterceptSelectorContext = {
313
337
  from: nav.effectiveFromUrl,
@@ -420,7 +444,7 @@ export async function matchError<TEnv>(
420
444
 
421
445
  debugLog("matchError", "matching error", { pathname });
422
446
 
423
- const matched = deps.findMatch(pathname);
447
+ const matched = await deps.findMatch(pathname);
424
448
  if (!matched) {
425
449
  debugWarn("matchError", "no route matched", { pathname });
426
450
  return null;
@@ -242,25 +242,3 @@ export function createPipelineState(): MatchPipelineState {
242
242
  slots: {},
243
243
  };
244
244
  }
245
-
246
- /**
247
- * Input parameters for createMatchContext
248
- */
249
- export interface CreateMatchContextInput<TEnv = any> {
250
- request: Request;
251
- env: TEnv;
252
- actionContext?: ActionContext;
253
- }
254
-
255
- /**
256
- * Result from createMatchContext - either a context or null (fall back to full match)
257
- */
258
- export type CreateMatchContextResult<TEnv = any> =
259
- | { type: "context"; ctx: MatchContext<TEnv> }
260
- | { type: "fallback"; reason: string }
261
- | { type: "error"; error: Error };
262
-
263
- // Note: createMatchContext() will be implemented in Step J10 when we wire everything together.
264
- // It requires access to RouterContext (findMatch, loadManifest, etc.) which are closure
265
- // functions from createRouter(). The implementation will live in router.ts initially
266
- // and call getRouterContext() to access these dependencies.