@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -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 +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  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 +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  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 +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -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 +105 -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 +357 -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/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
@@ -19,61 +19,24 @@ import type {
19
19
  } from "../../types";
20
20
  import type { SegmentResolutionDeps } from "../types.js";
21
21
  import { resolveLoaderData } from "./loader-cache.js";
22
- import { _getRequestContext } from "../../server/request-context.js";
23
- import { appendMetric } from "../metrics.js";
24
22
  import {
25
23
  handleHandlerResult,
26
24
  tryStaticHandler,
27
25
  tryStaticSlot,
28
26
  resolveLayoutComponent,
29
27
  resolveWithErrorBoundary,
28
+ warnOnStreamedResponse,
29
+ buildLoaderErrorContext,
30
30
  } from "./helpers.js";
31
+ import { applyViewTransitionDefault } from "./view-transition-default.js";
31
32
  import { getRouterContext } from "../router-context.js";
32
- import { resolveSink, safeEmit } from "../telemetry.js";
33
- import { track, RSCRouterContext } from "../../server/context.js";
34
-
35
- // ---------------------------------------------------------------------------
36
- // Streamed handler telemetry
37
- // ---------------------------------------------------------------------------
38
-
39
- /**
40
- * Attach a fire-and-forget rejection observer to a streamed handler promise.
41
- * React catches the actual error via its error boundary; this only emits
42
- * the handler.error telemetry event.
43
- */
44
- function observeStreamedHandler(
45
- promise: Promise<ReactNode>,
46
- segmentId: string,
47
- segmentType: string,
48
- pathname?: string,
49
- routeKey?: string,
50
- params?: Record<string, string>,
51
- ): void {
52
- let routerCtx;
53
- try {
54
- routerCtx = getRouterContext();
55
- } catch {
56
- return;
57
- }
58
- if (!routerCtx?.telemetry) return;
59
- const sink = resolveSink(routerCtx.telemetry);
60
- const reqId = routerCtx.requestId;
61
- promise.catch((err: unknown) => {
62
- const errorObj = err instanceof Error ? err : new Error(String(err));
63
- safeEmit(sink, {
64
- type: "handler.error",
65
- timestamp: performance.now(),
66
- requestId: reqId,
67
- segmentId,
68
- segmentType,
69
- error: errorObj,
70
- handledByBoundary: true,
71
- pathname,
72
- routeKey,
73
- params,
74
- });
75
- });
76
- }
33
+ import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
34
+ import { observeHandler } from "../instrument.js";
35
+ import {
36
+ track,
37
+ RangoContext,
38
+ runInsideLoaderScope,
39
+ } from "../../server/context.js";
77
40
 
78
41
  // ---------------------------------------------------------------------------
79
42
  // Fresh path (full match, no revalidation)
@@ -96,7 +59,13 @@ export async function resolveLoaders<TEnv>(
96
59
  const shortCode = shortCodeOverride ?? entry.shortCode;
97
60
  const hasLoading = "loading" in entry && entry.loading !== undefined;
98
61
  const loadingDisabled = hasLoading && entry.loading === false;
99
- const ms = _getRequestContext()?._metricsStore;
62
+
63
+ // Error context for wrapLoaderPromise: without it, a throwing DSL loader never
64
+ // fires createRouter({ onError }) (phase "loader") nor emits the loader.error
65
+ // telemetry event — wrapLoaderPromise only builds the onError/telemetry path
66
+ // when errorContext is supplied. Built from ctx so the live render path reports
67
+ // loader failures the same way handlers/actions/routing/fetchable-loaders do.
68
+ const errorContext = buildLoaderErrorContext(ctx);
100
69
 
101
70
  if (!loadingDisabled) {
102
71
  // Streaming loaders: promises kick off now, settle during RSC serialization.
@@ -112,10 +81,13 @@ export async function resolveLoaders<TEnv>(
112
81
  params: ctx.params,
113
82
  loaderId: loader.$$id,
114
83
  loaderData: deps.wrapLoaderPromise(
115
- resolveLoaderData(loaderEntry, ctx, ctx.pathname),
84
+ runInsideLoaderScope(() =>
85
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
86
+ ),
116
87
  entry,
117
88
  segmentId,
118
89
  ctx.pathname,
90
+ errorContext,
119
91
  ),
120
92
  belongsToRoute,
121
93
  };
@@ -126,44 +98,46 @@ export async function resolveLoaders<TEnv>(
126
98
 
127
99
  // Loading disabled: still start all loaders in parallel, but only emit
128
100
  // settled promises so handlers don't stream loading placeholders.
129
- const pendingLoaderData = loaderEntries.map((loaderEntry) => {
130
- const start = performance.now();
131
- const promise = resolveLoaderData(loaderEntry, ctx, ctx.pathname);
132
- return { promise, start, loaderId: loaderEntry.loader.$$id };
101
+ //
102
+ // Wrap each loader promise with wrapLoaderPromise BEFORE awaiting. The wrapped
103
+ // promise resolves to a LoaderDataResult and never rejects, routing a failed
104
+ // loader to its own per-loader error boundary. Awaiting the RAW promises here
105
+ // instead would (1) propagate a rejection to the segment-level boundary,
106
+ // collapsing the whole entry and discarding successful sibling data, and
107
+ // (2) leave the other in-flight raw promises without a .catch, producing
108
+ // unhandled rejections. Mirrors the loading path and intercept-resolution.
109
+ const pendingLoaderData = loaderEntries.map((loaderEntry, i) => {
110
+ const { loader } = loaderEntry;
111
+ const segmentId = `${shortCode}D${i}.${loader.$$id}`;
112
+ const wrapped = deps.wrapLoaderPromise(
113
+ runInsideLoaderScope(() =>
114
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
115
+ ),
116
+ entry,
117
+ segmentId,
118
+ ctx.pathname,
119
+ errorContext,
120
+ );
121
+ return { wrapped, segmentId };
133
122
  });
134
- await Promise.all(pendingLoaderData.map((p) => p.promise));
123
+ await Promise.all(pendingLoaderData.map((p) => p.wrapped));
135
124
 
136
125
  return loaderEntries.map((loaderEntry, i) => {
137
126
  const { loader } = loaderEntry;
138
- const segmentId = `${shortCode}D${i}.${loader.$$id}`;
139
127
  const pending = pendingLoaderData[i]!;
140
- if (ms && !ms.metrics.some((m) => m.label === `loader:${loader.$$id}`)) {
141
- // All loaders ran in parallel via Promise.all — each span covers
142
- // from its own kickoff to the batch settlement, giving a ceiling
143
- // on that loader's contribution to the overall wait.
144
- const batchEnd = performance.now();
145
- appendMetric(
146
- ms,
147
- `loader:${loader.$$id}`,
148
- pending.start,
149
- batchEnd - pending.start,
150
- 2,
151
- );
152
- }
128
+ // The "loader:<id>" perf metric is recorded by observePhase at the single
129
+ // loader-metering site (useLoader, reached via ctx.use during
130
+ // resolveLoaderData), with the real per-loader duration rather than a
131
+ // Promise.all batch ceiling.
153
132
  return {
154
- id: segmentId,
133
+ id: pending.segmentId,
155
134
  namespace: entry.id,
156
135
  type: "loader" as const,
157
136
  index: i,
158
137
  component: null,
159
138
  params: ctx.params,
160
139
  loaderId: loader.$$id,
161
- loaderData: deps.wrapLoaderPromise(
162
- pending.promise,
163
- entry,
164
- segmentId,
165
- ctx.pathname,
166
- ),
140
+ loaderData: pending.wrapped,
167
141
  belongsToRoute,
168
142
  };
169
143
  });
@@ -175,6 +149,15 @@ export async function resolveLoaders<TEnv>(
175
149
  export interface ResolveSegmentOptions {
176
150
  /** When true, skip resolveLoaders() calls (used for pre-rendering) */
177
151
  skipLoaders?: boolean;
152
+ /**
153
+ * When true, a thrown render error is re-thrown instead of being converted
154
+ * into an error-boundary segment. Set only by the pre-render path so a
155
+ * build-time render failure (and a `throw new Skip()` inside a render fn)
156
+ * surfaces to the build instead of being silently baked into a frozen error
157
+ * page served as a 200 (issue #587). The live request path leaves this unset,
158
+ * so error boundaries keep catching at request time.
159
+ */
160
+ throwOnError?: boolean;
178
161
  }
179
162
 
180
163
  /**
@@ -216,7 +199,11 @@ export async function resolveSegment<TEnv>(
216
199
  index: 0,
217
200
  component,
218
201
  loading: entry.loading === false ? null : entry.loading,
219
- transition: entry.transition,
202
+ transition: applyViewTransitionDefault(
203
+ entry.transition,
204
+ deps.viewTransitionDefault,
205
+ entry.shortCode,
206
+ ),
220
207
  params,
221
208
  belongsToRoute: false,
222
209
  layoutName: entry.id,
@@ -276,10 +263,18 @@ export async function resolveSegment<TEnv>(
276
263
  entry.shortCode,
277
264
  );
278
265
  if (component === undefined) {
266
+ // For Passthrough routes at runtime, use the live handler instead of
267
+ // the build handler. At build time (context.build === true), always
268
+ // use the build handler from entry.handler.
269
+ const handler =
270
+ !context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
279
271
  const doneRouteHandler = track(`handler:${entry.id}`, 2);
280
272
  if (entry.loading) {
281
- const result = handleHandlerResult(entry.handler(context));
273
+ const result = handleHandlerResult(
274
+ observeHandler(entry.id, handler, context),
275
+ );
282
276
  if (result instanceof Promise) {
277
+ warnOnStreamedResponse(result, entry.id);
283
278
  result.finally(doneRouteHandler).catch(() => {});
284
279
  const tracked = deps.trackHandler(result, {
285
280
  segmentId: entry.shortCode,
@@ -299,7 +294,9 @@ export async function resolveSegment<TEnv>(
299
294
  component = result;
300
295
  }
301
296
  } else {
302
- component = handleHandlerResult(await entry.handler(context));
297
+ component = handleHandlerResult(
298
+ await observeHandler(entry.id, handler, context),
299
+ );
303
300
  doneRouteHandler();
304
301
  }
305
302
  }
@@ -314,6 +311,7 @@ export async function resolveSegment<TEnv>(
314
311
  deps,
315
312
  options,
316
313
  routeKey,
314
+ entry,
317
315
  );
318
316
  segments.push(...orphanSegments);
319
317
  }
@@ -345,7 +343,11 @@ export async function resolveSegment<TEnv>(
345
343
  index: 0,
346
344
  component: component ?? null,
347
345
  loading: entry.loading === false ? null : entry.loading,
348
- transition: entry.transition,
346
+ transition: applyViewTransitionDefault(
347
+ entry.transition,
348
+ deps.viewTransitionDefault,
349
+ entry.shortCode,
350
+ ),
349
351
  params,
350
352
  belongsToRoute: true,
351
353
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
@@ -369,6 +371,9 @@ export async function resolveOrphanLayout<TEnv>(
369
371
  deps: SegmentResolutionDeps<TEnv>,
370
372
  options?: ResolveSegmentOptions,
371
373
  routeKey?: string,
374
+ /** Parent route entry — its loaders are inherited by the layout so
375
+ * parallel slots inside this layout can access them via useLoader(). */
376
+ parentRouteEntry?: EntryData,
372
377
  ): Promise<ResolvedSegment[]> {
373
378
  invariant(
374
379
  orphan.type === "layout" || orphan.type === "cache",
@@ -384,6 +389,30 @@ export async function resolveOrphanLayout<TEnv>(
384
389
  deps,
385
390
  );
386
391
  segments.push(...loaderSegments);
392
+
393
+ // Inherit parent route's loaders so parallel slots inside this layout
394
+ // can access them via useLoader(). Without this, the route's loaders
395
+ // are only in the route's OutletProvider (rendered as <Outlet /> content),
396
+ // which is a child — not a parent — of the layout's context.
397
+ if (
398
+ parentRouteEntry &&
399
+ parentRouteEntry.loader &&
400
+ parentRouteEntry.loader.length > 0 &&
401
+ Object.keys(orphan.parallel).length > 0
402
+ ) {
403
+ const inheritedLoaders = await resolveLoaders(
404
+ parentRouteEntry,
405
+ context,
406
+ belongsToRoute,
407
+ deps,
408
+ orphan.shortCode,
409
+ );
410
+ // Tag as inherited so buildMatchResult can deduplicate when safe
411
+ for (const s of inheritedLoaders) {
412
+ s._inherited = true;
413
+ }
414
+ segments.push(...inheritedLoaders);
415
+ }
387
416
  }
388
417
 
389
418
  // Handler-first: orphan layout handler executes before its parallels
@@ -402,7 +431,11 @@ export async function resolveOrphanLayout<TEnv>(
402
431
  belongsToRoute,
403
432
  layoutName: orphan.id,
404
433
  loading: orphan.loading === false ? null : orphan.loading,
405
- transition: orphan.transition,
434
+ transition: applyViewTransitionDefault(
435
+ orphan.transition,
436
+ deps.viewTransitionDefault,
437
+ orphan.shortCode,
438
+ ),
406
439
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
407
440
  });
408
441
 
@@ -474,6 +507,14 @@ export async function resolveParallelEntry<TEnv>(
474
507
  if (handler === undefined) {
475
508
  continue;
476
509
  }
510
+ // Pin `_currentSegmentId` to the slot's own id so handle pushes from
511
+ // inside the slot handler get their own bucket in the HandleStore.
512
+ // Parent-keying would collapse them into the parent layout's bucket;
513
+ // the partial-update merge then replaces the parent's bucket on a
514
+ // slot-only revalidation and drops layout-pushed Meta/Breadcrumbs.
515
+ // filterSegmentOrder() retains slot ids so the client preserves them.
516
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
517
+ `${parentShortCode}.${slot}`;
477
518
  const doneParallelHandler = track(
478
519
  `handler:${parallelEntry.id}.${slot}`,
479
520
  2,
@@ -482,7 +523,9 @@ export async function resolveParallelEntry<TEnv>(
482
523
  parallelEntry.loading !== undefined && parallelEntry.loading !== false;
483
524
  if (hasLoadingFallback) {
484
525
  const result =
485
- typeof handler === "function" ? handler(context) : handler;
526
+ typeof handler === "function"
527
+ ? observeHandler(`${parallelEntry.id}.${slot}`, handler, context)
528
+ : handler;
486
529
  if (result instanceof Promise) {
487
530
  result.finally(doneParallelHandler).catch(() => {});
488
531
  const tracked = deps.trackHandler(result, {
@@ -504,7 +547,13 @@ export async function resolveParallelEntry<TEnv>(
504
547
  }
505
548
  } else {
506
549
  component =
507
- typeof handler === "function" ? await handler(context) : handler;
550
+ typeof handler === "function"
551
+ ? await observeHandler(
552
+ `${parallelEntry.id}.${slot}`,
553
+ handler,
554
+ context,
555
+ )
556
+ : handler;
508
557
  doneParallelHandler();
509
558
  }
510
559
  }
@@ -516,7 +565,11 @@ export async function resolveParallelEntry<TEnv>(
516
565
  index: 0,
517
566
  component,
518
567
  loading: parallelEntry.loading === false ? null : parallelEntry.loading,
519
- transition: parallelEntry.transition,
568
+ transition: applyViewTransitionDefault(
569
+ parallelEntry.transition,
570
+ deps.viewTransitionDefault,
571
+ `${parentShortCode}.${slot}`,
572
+ ),
520
573
  params,
521
574
  slot,
522
575
  belongsToRoute,
@@ -583,7 +636,7 @@ export async function resolveAllSegments<TEnv>(
583
636
  // can guard non-cacheable variable reads. Also guards response-level
584
637
  // side effects (headers.set). Persists for all descendant entries.
585
638
  if (entry.type === "cache") {
586
- const store = RSCRouterContext.getStore();
639
+ const store = RangoContext.getStore();
587
640
  if (store) store.insideCacheScope = true;
588
641
  }
589
642
  const doneEntry = track(`segment:${entry.id}`, 1);
@@ -605,6 +658,7 @@ export async function resolveAllSegments<TEnv>(
605
658
  deps,
606
659
  { request: safeRequest, url: context.url, routeKey, telemetry },
607
660
  context.pathname,
661
+ options?.throwOnError,
608
662
  );
609
663
  doneEntry();
610
664
  // Deduplicate by segment ID. include() scopes can produce entries that
@@ -672,6 +726,30 @@ export async function resolveLoadersOnly<TEnv>(
672
726
  const childBelongsToRoute = belongsToRoute || entry.type === "route";
673
727
  for (const layoutEntry of entry.layout) {
674
728
  await collectEntryLoaders(layoutEntry, childBelongsToRoute);
729
+ // Inherit route loaders for orphan layouts with parallels.
730
+ // Resolve directly — do NOT re-enter collectEntryLoaders with the
731
+ // route entry, as that would re-iterate route.layout and loop.
732
+ if (
733
+ entry.type === "route" &&
734
+ entry.loader &&
735
+ entry.loader.length > 0 &&
736
+ Object.keys(layoutEntry.parallel).length > 0
737
+ ) {
738
+ const inherited = await resolveLoaders(
739
+ entry,
740
+ context,
741
+ childBelongsToRoute,
742
+ deps,
743
+ layoutEntry.shortCode,
744
+ );
745
+ for (const seg of inherited) {
746
+ if (!seenIds.has(seg.id)) {
747
+ seenIds.add(seg.id);
748
+ seg._inherited = true;
749
+ loaderSegments.push(seg);
750
+ }
751
+ }
752
+ }
675
753
  }
676
754
  }
677
755
 
@@ -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
  // ---------------------------------------------------------------------------
@@ -250,11 +324,17 @@ export async function resolveWithErrorBoundary<TEnv, TResult>(
250
324
  deps: SegmentResolutionDeps<TEnv>,
251
325
  report?: ErrorReportContext,
252
326
  pathname?: string,
327
+ throwOnError?: boolean,
253
328
  ): Promise<TResult> {
254
329
  try {
255
330
  return await resolveFn();
256
331
  } catch (error) {
257
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;
258
338
  const segment = catchSegmentError(
259
339
  error,
260
340
  entry,