@rangojs/router 0.0.0-experimental.1b930379 → 0.0.0-experimental.1c0bdfad

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 (400) hide show
  1. package/AGENTS.md +12 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +441 -134
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3453 -1240
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +76 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +64 -2
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +247 -23
  12. package/skills/caching/SKILL.md +318 -15
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/debug-manifest/SKILL.md +4 -2
  16. package/skills/document-cache/SKILL.md +78 -55
  17. package/skills/handler-use/SKILL.md +364 -0
  18. package/skills/hooks/SKILL.md +250 -30
  19. package/skills/host-router/SKILL.md +83 -23
  20. package/skills/i18n/SKILL.md +276 -0
  21. package/skills/intercept/SKILL.md +50 -6
  22. package/skills/layout/SKILL.md +35 -9
  23. package/skills/links/SKILL.md +249 -17
  24. package/skills/loader/SKILL.md +279 -53
  25. package/skills/middleware/SKILL.md +52 -13
  26. package/skills/migrate-nextjs/SKILL.md +584 -0
  27. package/skills/migrate-react-router/SKILL.md +771 -0
  28. package/skills/mime-routes/SKILL.md +28 -1
  29. package/skills/observability/SKILL.md +172 -0
  30. package/skills/parallel/SKILL.md +203 -7
  31. package/skills/prerender/SKILL.md +153 -109
  32. package/skills/rango/SKILL.md +251 -22
  33. package/skills/react-compiler/SKILL.md +168 -0
  34. package/skills/response-routes/SKILL.md +123 -48
  35. package/skills/route/SKILL.md +101 -5
  36. package/skills/router-setup/SKILL.md +116 -8
  37. package/skills/scripts/SKILL.md +179 -0
  38. package/skills/server-actions/SKILL.md +775 -0
  39. package/skills/streams-and-websockets/SKILL.md +283 -0
  40. package/skills/tailwind/SKILL.md +27 -3
  41. package/skills/testing/SKILL.md +129 -0
  42. package/skills/testing/bindings.md +103 -0
  43. package/skills/testing/cache-prerender.md +127 -0
  44. package/skills/testing/client-components.md +124 -0
  45. package/skills/testing/e2e-parity.md +125 -0
  46. package/skills/testing/flight.md +91 -0
  47. package/skills/testing/handles.md +129 -0
  48. package/skills/testing/loader.md +128 -0
  49. package/skills/testing/middleware.md +99 -0
  50. package/skills/testing/render-handler.md +121 -0
  51. package/skills/testing/response-routes.md +95 -0
  52. package/skills/testing/reverse-and-types.md +84 -0
  53. package/skills/testing/server-actions.md +107 -0
  54. package/skills/testing/server-tree.md +128 -0
  55. package/skills/testing/setup.md +123 -0
  56. package/skills/typesafety/SKILL.md +332 -29
  57. package/skills/use-cache/SKILL.md +46 -14
  58. package/skills/view-transitions/SKILL.md +294 -0
  59. package/src/__augment-tests__/augment.ts +81 -0
  60. package/src/__augment-tests__/augmented.check.ts +116 -0
  61. package/src/__internal.ts +1 -66
  62. package/src/browser/action-coordinator.ts +53 -36
  63. package/src/browser/action-fence.ts +47 -0
  64. package/src/browser/app-shell.ts +39 -0
  65. package/src/browser/app-version.ts +14 -0
  66. package/src/browser/connection-warmup.ts +134 -0
  67. package/src/browser/cookie-name.ts +140 -0
  68. package/src/browser/event-controller.ts +197 -150
  69. package/src/browser/history-state.ts +21 -0
  70. package/src/browser/index.ts +3 -3
  71. package/src/browser/invalidate-client-cache.ts +52 -0
  72. package/src/browser/navigation-bridge.ts +111 -31
  73. package/src/browser/navigation-client.ts +201 -67
  74. package/src/browser/navigation-store-handle.ts +38 -0
  75. package/src/browser/navigation-store.ts +76 -67
  76. package/src/browser/navigation-transaction.ts +18 -66
  77. package/src/browser/network-error-handler.ts +34 -7
  78. package/src/browser/partial-update.ts +187 -112
  79. package/src/browser/prefetch/cache.ts +230 -35
  80. package/src/browser/prefetch/fetch.ts +338 -39
  81. package/src/browser/prefetch/queue.ts +126 -20
  82. package/src/browser/prefetch/resource-ready.ts +77 -0
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +111 -16
  85. package/src/browser/react/NavigationProvider.tsx +135 -120
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/filter-segment-order.ts +66 -7
  89. package/src/browser/react/index.ts +0 -48
  90. package/src/browser/react/location-state-shared.ts +178 -8
  91. package/src/browser/react/location-state.ts +39 -14
  92. package/src/browser/react/use-action.ts +6 -15
  93. package/src/browser/react/use-handle.ts +23 -69
  94. package/src/browser/react/use-href.tsx +8 -1
  95. package/src/browser/react/use-link-status.ts +33 -8
  96. package/src/browser/react/use-navigation.ts +32 -7
  97. package/src/browser/react/use-params.ts +20 -10
  98. package/src/browser/react/use-reverse.ts +106 -0
  99. package/src/browser/react/use-router.ts +46 -11
  100. package/src/browser/react/use-search-params.ts +0 -5
  101. package/src/browser/react/use-segments.ts +11 -21
  102. package/src/browser/response-adapter.ts +80 -5
  103. package/src/browser/rsc-router.tsx +218 -76
  104. package/src/browser/scroll-restoration.ts +54 -42
  105. package/src/browser/segment-reconciler.ts +36 -9
  106. package/src/browser/segment-structure-assert.ts +2 -2
  107. package/src/browser/server-action-bridge.ts +222 -61
  108. package/src/browser/types.ts +91 -11
  109. package/src/browser/validate-redirect-origin.ts +43 -16
  110. package/src/build/collect-fallback-refs.ts +107 -0
  111. package/src/build/generate-manifest.ts +65 -40
  112. package/src/build/generate-route-types.ts +5 -1
  113. package/src/build/index.ts +8 -2
  114. package/src/build/prefix-tree-utils.ts +123 -0
  115. package/src/build/route-trie.ts +165 -36
  116. package/src/build/route-types/ast-route-extraction.ts +15 -8
  117. package/src/build/route-types/codegen.ts +16 -5
  118. package/src/build/route-types/include-resolution.ts +125 -24
  119. package/src/build/route-types/param-extraction.ts +6 -3
  120. package/src/build/route-types/per-module-writer.ts +22 -6
  121. package/src/build/route-types/router-processing.ts +272 -96
  122. package/src/build/route-types/scan-filter.ts +9 -2
  123. package/src/build/route-types/source-scan.ts +216 -0
  124. package/src/build/runtime-discovery.ts +9 -20
  125. package/src/cache/cache-error.ts +104 -0
  126. package/src/cache/cache-key-utils.ts +29 -13
  127. package/src/cache/cache-policy.ts +108 -34
  128. package/src/cache/cache-runtime.ts +214 -48
  129. package/src/cache/cache-scope.ts +236 -89
  130. package/src/cache/cache-tag.ts +103 -0
  131. package/src/cache/cf/cf-base64.ts +33 -0
  132. package/src/cache/cf/cf-cache-constants.ts +127 -0
  133. package/src/cache/cf/cf-cache-store.ts +2224 -171
  134. package/src/cache/cf/cf-cache-types.ts +349 -0
  135. package/src/cache/cf/cf-kv-utils.ts +46 -0
  136. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  137. package/src/cache/cf/index.ts +11 -17
  138. package/src/cache/document-cache.ts +89 -27
  139. package/src/cache/handle-snapshot.ts +70 -0
  140. package/src/cache/index.ts +11 -20
  141. package/src/cache/memory-segment-store.ts +136 -37
  142. package/src/cache/profile-registry.ts +31 -31
  143. package/src/cache/read-through-swr.ts +41 -11
  144. package/src/cache/segment-codec.ts +9 -17
  145. package/src/cache/tag-invalidation.ts +230 -0
  146. package/src/cache/taint.ts +55 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +84 -2
  155. package/src/debug.ts +2 -2
  156. package/src/decode-loader-results.ts +52 -0
  157. package/src/defer.ts +196 -0
  158. package/src/deps/ssr.ts +0 -1
  159. package/src/encode-kv.ts +49 -0
  160. package/src/errors.ts +30 -4
  161. package/src/escape-script.ts +52 -0
  162. package/src/handle.ts +70 -22
  163. package/src/handles/MetaTags.tsx +56 -19
  164. package/src/handles/Scripts.tsx +183 -0
  165. package/src/handles/breadcrumbs.ts +37 -8
  166. package/src/handles/is-thenable.ts +19 -0
  167. package/src/handles/meta.ts +51 -40
  168. package/src/handles/script.ts +244 -0
  169. package/src/host/cookie-handler.ts +9 -60
  170. package/src/host/errors.ts +0 -24
  171. package/src/host/index.ts +8 -2
  172. package/src/host/pattern-matcher.ts +23 -52
  173. package/src/host/router.ts +107 -99
  174. package/src/host/testing.ts +40 -27
  175. package/src/host/types.ts +37 -4
  176. package/src/host/utils.ts +1 -1
  177. package/src/href-client.ts +137 -22
  178. package/src/index.rsc.ts +93 -12
  179. package/src/index.ts +133 -15
  180. package/src/internal-debug.ts +11 -10
  181. package/src/loader-store.ts +500 -0
  182. package/src/loader.rsc.ts +20 -13
  183. package/src/loader.ts +12 -11
  184. package/src/missing-id-error.ts +68 -0
  185. package/src/outlet-context.ts +1 -1
  186. package/src/outlet-provider.tsx +1 -5
  187. package/src/prerender/param-hash.ts +16 -16
  188. package/src/prerender/store.ts +37 -41
  189. package/src/prerender.ts +198 -82
  190. package/src/redirect-origin.ts +100 -0
  191. package/src/regex-escape.ts +8 -0
  192. package/src/render-error-thrower.tsx +20 -0
  193. package/src/response-utils.ts +62 -0
  194. package/src/reverse.ts +65 -15
  195. package/src/root-error-boundary.tsx +1 -19
  196. package/src/route-content-wrapper.tsx +7 -72
  197. package/src/route-definition/dsl-helpers.ts +469 -276
  198. package/src/route-definition/helper-factories.ts +29 -139
  199. package/src/route-definition/helpers-types.ts +113 -37
  200. package/src/route-definition/index.ts +3 -0
  201. package/src/route-definition/redirect.ts +53 -12
  202. package/src/route-definition/resolve-handler-use.ts +161 -0
  203. package/src/route-definition/use-item-types.ts +32 -0
  204. package/src/route-map-builder.ts +7 -17
  205. package/src/route-types.ts +37 -41
  206. package/src/router/basename.ts +14 -0
  207. package/src/router/content-negotiation.ts +164 -17
  208. package/src/router/error-handling.ts +45 -18
  209. package/src/router/find-match.ts +45 -22
  210. package/src/router/handler-context.ts +83 -39
  211. package/src/router/instrument.ts +350 -0
  212. package/src/router/intercept-resolution.ts +50 -24
  213. package/src/router/lazy-includes.ts +19 -53
  214. package/src/router/loader-resolution.ts +274 -56
  215. package/src/router/logging.ts +5 -8
  216. package/src/router/manifest.ts +49 -45
  217. package/src/router/match-api.ts +120 -204
  218. package/src/router/match-context.ts +0 -22
  219. package/src/router/match-handlers.ts +58 -58
  220. package/src/router/match-middleware/background-revalidation.ts +33 -6
  221. package/src/router/match-middleware/cache-lookup.ts +214 -263
  222. package/src/router/match-middleware/cache-store.ts +73 -33
  223. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  224. package/src/router/match-middleware/segment-resolution.ts +52 -18
  225. package/src/router/match-pipelines.ts +1 -42
  226. package/src/router/match-result.ts +104 -40
  227. package/src/router/metrics.ts +5 -34
  228. package/src/router/middleware-types.ts +13 -142
  229. package/src/router/middleware.ts +270 -172
  230. package/src/router/navigation-snapshot.ts +131 -0
  231. package/src/router/params-util.ts +23 -0
  232. package/src/router/pattern-matching.ts +132 -90
  233. package/src/router/prefetch-cache-ttl.ts +51 -0
  234. package/src/router/prerender-match.ts +195 -56
  235. package/src/router/preview-match.ts +32 -102
  236. package/src/router/request-classification.ts +276 -0
  237. package/src/router/revalidation.ts +123 -73
  238. package/src/router/route-snapshot.ts +244 -0
  239. package/src/router/router-context.ts +8 -28
  240. package/src/router/router-interfaces.ts +115 -35
  241. package/src/router/router-options.ts +172 -15
  242. package/src/router/router-registry.ts +2 -5
  243. package/src/router/segment-resolution/fresh.ts +264 -77
  244. package/src/router/segment-resolution/helpers.ts +115 -30
  245. package/src/router/segment-resolution/loader-cache.ts +63 -37
  246. package/src/router/segment-resolution/revalidation.ts +474 -385
  247. package/src/router/segment-resolution/static-store.ts +19 -5
  248. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  249. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  250. package/src/router/segment-resolution.ts +5 -1
  251. package/src/router/segment-wrappers.ts +8 -5
  252. package/src/router/state-cookie-name.ts +33 -0
  253. package/src/router/substitute-pattern-params.ts +56 -0
  254. package/src/router/telemetry-otel.ts +161 -199
  255. package/src/router/telemetry.ts +96 -19
  256. package/src/router/timeout.ts +0 -20
  257. package/src/router/tracing.ts +206 -0
  258. package/src/router/trie-matching.ts +163 -59
  259. package/src/router/types.ts +10 -63
  260. package/src/router/url-params.ts +44 -0
  261. package/src/router.ts +163 -55
  262. package/src/rsc/handler-context.ts +3 -2
  263. package/src/rsc/handler.ts +658 -511
  264. package/src/rsc/helpers.ts +168 -46
  265. package/src/rsc/index.ts +2 -5
  266. package/src/rsc/json-route-result.ts +38 -0
  267. package/src/rsc/loader-fetch.ts +127 -31
  268. package/src/rsc/manifest-init.ts +33 -42
  269. package/src/rsc/origin-guard.ts +39 -25
  270. package/src/rsc/progressive-enhancement.ts +77 -11
  271. package/src/rsc/redirect-guard.ts +99 -0
  272. package/src/rsc/response-cache-serve.ts +238 -0
  273. package/src/rsc/response-error.ts +79 -12
  274. package/src/rsc/response-route-handler.ts +99 -189
  275. package/src/rsc/rsc-rendering.ts +105 -72
  276. package/src/rsc/runtime-warnings.ts +23 -10
  277. package/src/rsc/server-action.ts +263 -112
  278. package/src/rsc/ssr-setup.ts +18 -2
  279. package/src/rsc/types.ts +32 -6
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +281 -129
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +309 -61
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +26 -46
  291. package/src/server/request-context.ts +398 -172
  292. package/src/ssr/index.tsx +25 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +99 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +343 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  320. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  321. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  322. package/src/testing/vitest-stubs/version.ts +5 -0
  323. package/src/testing/vitest.ts +305 -0
  324. package/src/theme/ThemeProvider.tsx +20 -58
  325. package/src/theme/ThemeScript.tsx +7 -9
  326. package/src/theme/constants.ts +52 -13
  327. package/src/theme/index.ts +0 -7
  328. package/src/theme/theme-context.ts +1 -5
  329. package/src/theme/theme-script.ts +22 -21
  330. package/src/theme/use-theme.ts +0 -3
  331. package/src/types/boundaries.ts +0 -35
  332. package/src/types/cache-types.ts +17 -8
  333. package/src/types/error-types.ts +30 -90
  334. package/src/types/global-namespace.ts +54 -41
  335. package/src/types/handler-context.ts +233 -81
  336. package/src/types/index.ts +1 -10
  337. package/src/types/loader-types.ts +44 -15
  338. package/src/types/request-scope.ts +112 -0
  339. package/src/types/route-config.ts +6 -50
  340. package/src/types/route-entry.ts +19 -7
  341. package/src/types/segments.ts +37 -14
  342. package/src/urls/include-helper.ts +33 -70
  343. package/src/urls/index.ts +1 -11
  344. package/src/urls/path-helper-types.ts +58 -11
  345. package/src/urls/path-helper.ts +57 -111
  346. package/src/urls/pattern-types.ts +48 -19
  347. package/src/urls/response-types.ts +25 -22
  348. package/src/urls/type-extraction.ts +58 -139
  349. package/src/urls/urls-function.ts +1 -18
  350. package/src/use-loader.tsx +346 -89
  351. package/src/vite/debug.ts +185 -0
  352. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  353. package/src/vite/discovery/discover-routers.ts +130 -85
  354. package/src/vite/discovery/discovery-errors.ts +194 -0
  355. package/src/vite/discovery/gate-state.ts +171 -0
  356. package/src/vite/discovery/prerender-collection.ts +214 -132
  357. package/src/vite/discovery/route-types-writer.ts +40 -84
  358. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  359. package/src/vite/discovery/state.ts +57 -6
  360. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  361. package/src/vite/index.ts +6 -0
  362. package/src/vite/inject-client-debug.ts +36 -0
  363. package/src/vite/plugin-types.ts +155 -65
  364. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  365. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  366. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  367. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  369. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  370. package/src/vite/plugins/expose-action-id.ts +49 -98
  371. package/src/vite/plugins/expose-id-utils.ts +96 -51
  372. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  373. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  374. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  375. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  376. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  377. package/src/vite/plugins/performance-tracks.ts +89 -0
  378. package/src/vite/plugins/refresh-cmd.ts +89 -27
  379. package/src/vite/plugins/use-cache-transform.ts +73 -83
  380. package/src/vite/plugins/version-injector.ts +21 -25
  381. package/src/vite/plugins/version-plugin.ts +46 -37
  382. package/src/vite/plugins/virtual-entries.ts +13 -18
  383. package/src/vite/rango.ts +238 -295
  384. package/src/vite/router-discovery.ts +940 -149
  385. package/src/vite/utils/ast-handler-extract.ts +26 -35
  386. package/src/vite/utils/banner.ts +4 -4
  387. package/src/vite/utils/bundle-analysis.ts +10 -15
  388. package/src/vite/utils/client-chunks.ts +184 -0
  389. package/src/vite/utils/directive-prologue.ts +40 -0
  390. package/src/vite/utils/forward-user-plugins.ts +171 -0
  391. package/src/vite/utils/manifest-utils.ts +4 -59
  392. package/src/vite/utils/package-resolution.ts +20 -52
  393. package/src/vite/utils/prerender-utils.ts +81 -34
  394. package/src/vite/utils/shared-utils.ts +92 -42
  395. package/src/browser/action-response-classifier.ts +0 -99
  396. package/src/browser/react/use-client-cache.ts +0 -58
  397. package/src/browser/shallow.ts +0 -40
  398. package/src/handles/index.ts +0 -7
  399. package/src/network-error-thrower.tsx +0 -23
  400. package/src/router/middleware-cookies.ts +0 -55
@@ -8,7 +8,7 @@
8
8
  * - Error boundary segment creation
9
9
  */
10
10
 
11
- import type { ReactNode } from "react";
11
+ import { createElement, type ReactNode } from "react";
12
12
  import { DataNotFoundError } from "../../errors";
13
13
  import {
14
14
  createErrorInfo,
@@ -19,13 +19,48 @@ import {
19
19
  import { getRequestContext } from "../../server/request-context.js";
20
20
  import { DefaultErrorFallback } from "../../default-error-boundary.js";
21
21
  import type { EntryData } from "../../server/context";
22
- import type { ResolvedSegment, ErrorInfo, HandlerContext } from "../../types";
22
+ import type {
23
+ ResolvedSegment,
24
+ ErrorInfo,
25
+ HandlerContext,
26
+ InternalHandlerContext,
27
+ } from "../../types";
23
28
  import type { SegmentResolutionDeps } from "../types.js";
24
29
  import { debugLog } from "../logging.js";
25
30
  import { tryStaticLookup } from "./static-store.js";
31
+ import { observeHandler } from "../instrument.js";
26
32
  import type { TelemetrySink } from "../telemetry.js";
27
33
  import { resolveSink, safeEmit, getRequestId } from "../telemetry.js";
28
34
 
35
+ /** The errorContext shape wrapLoaderPromise expects as its 5th argument. */
36
+ type LoaderErrorContext<TEnv> = NonNullable<
37
+ Parameters<SegmentResolutionDeps<TEnv>["wrapLoaderPromise"]>[4]
38
+ >;
39
+
40
+ /**
41
+ * Build the errorContext passed to wrapLoaderPromise so a throwing DSL loader
42
+ * fires createRouter({ onError }) (phase "loader") and emits the loader.error
43
+ * telemetry event. wrapLoaderPromise only wires the onError/telemetry path when
44
+ * this 5th argument is present; every real call site previously omitted it, so
45
+ * loaders were the one phase whose failures were silently dropped (handlers,
46
+ * actions, routing, rendering, and fetchable loaders all reported correctly).
47
+ *
48
+ * The fields come off the handler context, which already carries the request,
49
+ * url, params, env, and (on the internal shape) the matched route name.
50
+ */
51
+ export function buildLoaderErrorContext<TEnv>(
52
+ ctx: HandlerContext<any, TEnv>,
53
+ ): LoaderErrorContext<TEnv> {
54
+ const internal = ctx as InternalHandlerContext<any, TEnv>;
55
+ return {
56
+ request: ctx.request,
57
+ url: ctx.url,
58
+ routeKey: internal._routeName,
59
+ params: ctx.params as Record<string, string>,
60
+ env: ctx.env,
61
+ };
62
+ }
63
+
29
64
  // ---------------------------------------------------------------------------
30
65
  // Handler result processing
31
66
  // ---------------------------------------------------------------------------
@@ -52,6 +87,40 @@ export function handleHandlerResult(
52
87
  return result;
53
88
  }
54
89
 
90
+ /**
91
+ * Dev-only: warn when a handler on a route that declares loading() resolves or
92
+ * rejects with a Response (e.g. redirect()).
93
+ *
94
+ * On a non-loading route a returned/thrown Response short-circuits to an HTTP
95
+ * redirect. But when the route declares loading(), the handler result is
96
+ * streamed (not awaited at the resolution boundary), so the Response surfaces
97
+ * only during RSC serialization and is rendered into the stream instead of
98
+ * becoming a 302/308 — a silent failure mode. Issue redirects from middleware,
99
+ * a loader, or a synchronous handler return instead. Compiled out in production.
100
+ */
101
+ export function warnOnStreamedResponse(
102
+ result: Promise<unknown>,
103
+ entryId: string,
104
+ ): void {
105
+ if (process.env.NODE_ENV === "production") return;
106
+ // A Response can surface either as a rejection (handleHandlerResult rethrows a
107
+ // resolved Response) or as a resolved value (the raw parallel-slot handler is
108
+ // not run through handleHandlerResult). Check both so every streamed path is
109
+ // covered. Each handler is an independent observer; it does not consume the
110
+ // rejection for the trackHandler/observeStreamedHandler chains.
111
+ const check = (value: unknown) => {
112
+ if (value instanceof Response) {
113
+ console.warn(
114
+ `[rango] Handler for "${entryId}" returned a Response (e.g. ` +
115
+ `redirect()), but it declares loading(): the Response is rendered ` +
116
+ `into the RSC stream, NOT sent as an HTTP redirect. Issue redirects ` +
117
+ `from middleware, a loader, or a synchronous handler return.`,
118
+ );
119
+ }
120
+ };
121
+ result.then(check, check);
122
+ }
123
+
55
124
  // ---------------------------------------------------------------------------
56
125
  // Static handler interception
57
126
  // ---------------------------------------------------------------------------
@@ -96,11 +165,16 @@ export async function resolveLayoutComponent<TEnv>(
96
165
  entry: EntryData,
97
166
  context: HandlerContext<any, TEnv>,
98
167
  ): Promise<ReactNode> {
99
- const component = await tryStaticHandler(entry, entry.shortCode);
100
- if (component !== undefined) return component;
101
- return typeof entry.handler === "function"
102
- ? handleHandlerResult(await entry.handler(context))
103
- : (entry.handler as ReactNode);
168
+ // Static/prerender hit: no handler runs, so emit no rango.handler span.
169
+ const staticComponent = await tryStaticHandler(entry, entry.shortCode);
170
+ if (staticComponent !== undefined) return staticComponent;
171
+ const handler = entry.handler;
172
+ if (typeof handler !== "function") return handler as ReactNode;
173
+ // Wrap ONLY the handler call in the rango.handler span (the perf metric is owned
174
+ // by track("handler:<id>") at the call site). handleHandlerResult stays OUTSIDE
175
+ // the span so a handler that returns a Response (redirect control flow, which it
176
+ // rethrows) is not recorded as a span error — mirrors the route-handler sites.
177
+ return handleHandlerResult(await observeHandler(entry.id, handler, context));
104
178
  }
105
179
 
106
180
  // ---------------------------------------------------------------------------
@@ -180,34 +254,39 @@ export function catchSegmentError<TEnv>(
180
254
 
181
255
  if (error instanceof DataNotFoundError) {
182
256
  const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
257
+ // Fall back to router's notFound component, then a plain default
258
+ const notFoundOption = deps.notFoundComponent;
259
+ const defaultFallback =
260
+ typeof notFoundOption === "function"
261
+ ? notFoundOption({ pathname: pathname ?? "" })
262
+ : (notFoundOption ?? createElement("h1", null, "Not Found"));
263
+ const effectiveNotFoundFallback = notFoundFallback ?? defaultFallback;
183
264
 
184
- if (notFoundFallback) {
185
- const notFoundInfo = createNotFoundInfo(
186
- error,
187
- entry.shortCode,
188
- entry.type,
189
- pathname,
190
- );
265
+ const notFoundInfo = createNotFoundInfo(
266
+ error,
267
+ entry.shortCode,
268
+ entry.type,
269
+ pathname,
270
+ );
191
271
 
192
- reportError(true, {
193
- notFound: true,
194
- message: notFoundInfo.message,
195
- });
272
+ reportError(true, {
273
+ notFound: true,
274
+ message: notFoundInfo.message,
275
+ });
196
276
 
197
- debugLog("segment", "notFound boundary handled error", {
198
- segmentId: entry.shortCode,
199
- message: notFoundInfo.message,
200
- });
277
+ debugLog("segment", "notFound boundary handled error", {
278
+ segmentId: entry.shortCode,
279
+ message: notFoundInfo.message,
280
+ });
201
281
 
202
- setResponseStatus(404);
282
+ setResponseStatus(404);
203
283
 
204
- return createNotFoundSegment(
205
- notFoundInfo,
206
- notFoundFallback,
207
- entry,
208
- params,
209
- );
210
- }
284
+ return createNotFoundSegment(
285
+ notFoundInfo,
286
+ effectiveNotFoundFallback,
287
+ entry,
288
+ params,
289
+ );
211
290
  }
212
291
 
213
292
  const fallback = deps.findNearestErrorBoundary(entry);
@@ -245,11 +324,17 @@ export async function resolveWithErrorBoundary<TEnv, TResult>(
245
324
  deps: SegmentResolutionDeps<TEnv>,
246
325
  report?: ErrorReportContext,
247
326
  pathname?: string,
327
+ throwOnError?: boolean,
248
328
  ): Promise<TResult> {
249
329
  try {
250
330
  return await resolveFn();
251
331
  } catch (error) {
252
332
  if (error instanceof Response) throw error;
333
+ // Pre-render surfaces render failures to the build instead of baking the
334
+ // error boundary as a frozen 200 (issue #587). A `throw new Skip()` in a
335
+ // render fn also propagates here so the build can skip that URL rather than
336
+ // bake its error page. The live request path leaves throwOnError unset.
337
+ if (throwOnError) throw error;
253
338
  const segment = catchSegmentError(
254
339
  error,
255
340
  entry,
@@ -8,7 +8,7 @@
8
8
  * Cache key resolution (3-tier, matching CacheScope.resolveKey):
9
9
  * 1. options.key(requestCtx) — full override
10
10
  * 2. store.keyGenerator(requestCtx, defaultKey) — store-level modification
11
- * 3. loader:{loaderId}:{pathname}:{sortedParams} — default
11
+ * 3. loader:{loaderId}:{host}{pathname}:{sortedParams} — default
12
12
  *
13
13
  * Values are serialized via RSC Flight (serializeResult/deserializeResult),
14
14
  * supporting ReactNode, Promises, null, and all RSC-serializable types.
@@ -19,7 +19,7 @@
19
19
  */
20
20
 
21
21
  import type { LoaderEntry } from "../../server/context.js";
22
- import type { HandlerContext } from "../../types.js";
22
+ import type { HandlerContext, InternalHandlerContext } from "../../types.js";
23
23
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
24
24
  import { getRequestContext } from "../../server/request-context.js";
25
25
  import { sortedRouteParams } from "../../cache/cache-key-utils.js";
@@ -28,9 +28,11 @@ import {
28
28
  resolveSwrWindow,
29
29
  resolveCacheKey,
30
30
  resolveCacheStore,
31
+ resolveTagsOption,
31
32
  DEFAULT_ROUTE_TTL,
32
33
  } from "../../cache/cache-policy.js";
33
34
  import { readThroughItem } from "../../cache/read-through-swr.js";
35
+ import { recordRequestTags } from "../../cache/cache-tag.js";
34
36
  // Lazy-loaded to avoid pulling @vitejs/plugin-rsc/rsc into modules that
35
37
  // import segment-resolution but never use loader caching.
36
38
  let _serializeResult: typeof import("../../cache/segment-codec.js").serializeResult;
@@ -55,12 +57,13 @@ function debugLoaderCacheLog(message: string): void {
55
57
 
56
58
  function getDefaultLoaderCacheKey(
57
59
  loaderId: string,
60
+ host: string,
58
61
  pathname: string,
59
62
  params: Record<string, string>,
60
63
  ): string {
61
64
  const paramStr = sortedRouteParams(params);
62
65
  const base = paramStr ? `${pathname}:${paramStr}` : pathname;
63
- return `loader:${loaderId}:${base}`;
66
+ return `loader:${loaderId}:${host}${base}`;
64
67
  }
65
68
 
66
69
  /**
@@ -74,7 +77,13 @@ async function resolveLoaderKey(
74
77
  params: Record<string, string>,
75
78
  ): Promise<string> {
76
79
  const options = loaderEntry.cache!.options;
77
- const defaultKey = getDefaultLoaderCacheKey(loaderId, pathname, params);
80
+ // The host is part of the loader cache identity, matching the route-level
81
+ // cache (cache-scope getCacheKeyBase: `${host}${pathname}`) and "use cache"
82
+ // (cache-runtime pushes ctx.url.host). Without it, a multi-tenant host router
83
+ // serving the same pathname for different hosts would leak one host's cached
84
+ // loader data to another.
85
+ const host = getRequestContext()?.url?.host ?? "localhost";
86
+ const defaultKey = getDefaultLoaderCacheKey(loaderId, host, pathname, params);
78
87
  if (options === false) return defaultKey;
79
88
  return resolveCacheKey(options.key, store, defaultKey, "LoaderCache");
80
89
  }
@@ -87,23 +96,8 @@ async function resolveLoaderKey(
87
96
  */
88
97
  function resolveTags(loaderEntry: LoaderEntry): string[] | undefined {
89
98
  const options = loaderEntry.cache?.options;
90
- if (!options || !options.tags) return undefined;
91
-
92
- if (typeof options.tags === "function") {
93
- const requestCtx = getRequestContext();
94
- if (!requestCtx) return undefined;
95
- try {
96
- return options.tags(requestCtx);
97
- } catch (error) {
98
- console.error(
99
- `[LoaderCache] Tags function failed, caching without tags:`,
100
- error,
101
- );
102
- return undefined;
103
- }
104
- }
105
-
106
- return options.tags;
99
+ if (!options) return undefined;
100
+ return resolveTagsOption(options.tags, getRequestContext(), "LoaderCache");
107
101
  }
108
102
 
109
103
  function getLoaderStore(
@@ -119,6 +113,11 @@ function getLoaderStore(
119
113
  *
120
114
  * When the LoaderEntry has no cache config, delegates directly to ctx.use(loader).
121
115
  * When cached, checks store first and stores on miss via waitUntil.
116
+ *
117
+ * Loader metering is NOT done here — it lives at the ctx.use execution funnel
118
+ * (observePhase; see instrument.ts). A cache HIT returns without calling ctx.use,
119
+ * so it emits no loader phase (the loader did not execute; the hit is only a
120
+ * LoaderCache debug log).
122
121
  */
123
122
  export function resolveLoaderData<TEnv>(
124
123
  loaderEntry: LoaderEntry,
@@ -147,15 +146,52 @@ export function resolveLoaderData<TEnv>(
147
146
  }
148
147
 
149
148
  const loaderId = loaderEntry.loader.$$id;
149
+
150
+ // A handler that later awaits this same loader via ctx.use(loader) must get
151
+ // THIS memoized promise, not a fresh execution. Rather than rebind ctx.use
152
+ // once per cached loader (O(N) chained wrappers + a synchronous
153
+ // capture-before-overwrite invariant), install a single stable interceptor on
154
+ // the first cached loader that consults a per-ctx override table, then just
155
+ // prime the table for each subsequent cached loader. The captured pre-
156
+ // interceptor `originalUse` (whatever setup mode installed it) runs the
157
+ // cache-miss execute, so a loader never awaits its own in-flight promise.
158
+ const internal = ctx as InternalHandlerContext<any, TEnv>;
159
+ let overrides = internal._loaderCacheOverrides;
160
+ if (!overrides) {
161
+ overrides = internal._loaderCacheOverrides = new Map();
162
+ const originalUse = ctx.use;
163
+ internal._loaderCacheOriginalUse = originalUse;
164
+ ctx.use = ((item: any) => {
165
+ const cached = overrides!.get(item?.$$id);
166
+ if (cached) return cached;
167
+ return originalUse(item);
168
+ }) as typeof ctx.use;
169
+ }
170
+ const runMiss = internal._loaderCacheOriginalUse!;
171
+
172
+ // Dedup the cache read-through across repeated resolutions of the SAME
173
+ // loaderId in one request. An orphan layout with parallel slots inherits its
174
+ // parent route's loaders, so resolveOrphanLayout (fresh.ts) re-resolves the
175
+ // parent's loaders under a different shortCode — calling resolveLoaderData
176
+ // again for the same loaderId. The cache key (loader:{loaderId}:{host}
177
+ // {pathname}:{sortedParams}) does not include the shortCode and ctx/params
178
+ // are identical, so both resolutions produce the same data. Reuse the already
179
+ // in-flight dataPromise instead of issuing a second getItem/setItem (e.g. a
180
+ // second KV round-trip) for one logical cached loader. The shortCode only
181
+ // affects the emitted segmentId in resolveLoaders, not the cached value.
182
+ const existing = overrides.get(loaderId);
183
+ if (existing) return existing;
184
+
185
+ // Compute ttl/swr/tags only AFTER the dedup short-circuit: a deduped second
186
+ // resolution of the same loaderId (the orphan-layout inheritance path) must
187
+ // not re-run the user tags() callback. These values are only consumed inside
188
+ // the read-through below, so they belong here, past the dedup gate.
150
189
  const ttl = resolveTtl(options.ttl, store.defaults, DEFAULT_ROUTE_TTL);
151
190
  const swrWindow = resolveSwrWindow(options.swr, store.defaults);
152
191
  const swr = swrWindow || undefined;
153
192
  const tags = resolveTags(loaderEntry);
193
+ recordRequestTags(tags);
154
194
 
155
- // Wrap ctx.use() so cache HIT primes the handler's memoization map.
156
- // ctx.use() closes over the match context's loaderPromises (not request context's).
157
- // By intercepting ctx.use(), we inject cached data into the correct map.
158
- const originalUse = ctx.use;
159
195
  const dataPromise = (async () => {
160
196
  const codec = await getCodec();
161
197
  const key = await resolveLoaderKey(
@@ -170,7 +206,7 @@ export function resolveLoaderData<TEnv>(
170
206
  getItem: (k) => store.getItem!(k),
171
207
  setItem: (k, v, o) => store.setItem!(k, v, o),
172
208
  key,
173
- execute: () => originalUse(loaderEntry.loader),
209
+ execute: () => runMiss(loaderEntry.loader),
174
210
  serialize: (d) => codec.serializeResult(d),
175
211
  deserialize: (v) => codec.deserializeResult(v),
176
212
  storeOptions: { ttl, swr, tags },
@@ -182,17 +218,7 @@ export function resolveLoaderData<TEnv>(
182
218
  });
183
219
  })();
184
220
 
185
- // Temporarily replace ctx.use() so the handler's call returns cached data.
186
- // This is needed because ctx.use() closes over the match context's loaderPromises
187
- // map which is separate from the request context. By wrapping use(), we intercept
188
- // the handler's call and return the shared dataPromise.
189
- const wrappedUse = ((item: any) => {
190
- if (item === loaderEntry.loader || item?.$$id === loaderId) {
191
- return dataPromise;
192
- }
193
- return originalUse(item);
194
- }) as typeof ctx.use;
195
- ctx.use = wrappedUse;
221
+ overrides.set(loaderId, dataPromise);
196
222
 
197
223
  return dataPromise;
198
224
  }