@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
@@ -7,7 +7,11 @@
7
7
 
8
8
  import type { ReactNode } from "react";
9
9
  import { invariant } from "../../errors";
10
- import type { EntryData } from "../../server/context";
10
+ import {
11
+ getParallelEntries,
12
+ getParallelSlotEntries,
13
+ type EntryData,
14
+ } from "../../server/context";
11
15
  import type {
12
16
  HandlerContext,
13
17
  InternalHandlerContext,
@@ -21,53 +25,18 @@ import {
21
25
  tryStaticSlot,
22
26
  resolveLayoutComponent,
23
27
  resolveWithErrorBoundary,
28
+ warnOnStreamedResponse,
29
+ buildLoaderErrorContext,
24
30
  } from "./helpers.js";
31
+ import { applyViewTransitionDefault } from "./view-transition-default.js";
25
32
  import { getRouterContext } from "../router-context.js";
26
- import { resolveSink, safeEmit } from "../telemetry.js";
27
- import { track } from "../../server/context.js";
28
-
29
- // ---------------------------------------------------------------------------
30
- // Streamed handler telemetry
31
- // ---------------------------------------------------------------------------
32
-
33
- /**
34
- * Attach a fire-and-forget rejection observer to a streamed handler promise.
35
- * React catches the actual error via its error boundary; this only emits
36
- * the handler.error telemetry event.
37
- */
38
- function observeStreamedHandler(
39
- promise: Promise<ReactNode>,
40
- segmentId: string,
41
- segmentType: string,
42
- pathname?: string,
43
- routeKey?: string,
44
- params?: Record<string, string>,
45
- ): void {
46
- let routerCtx;
47
- try {
48
- routerCtx = getRouterContext();
49
- } catch {
50
- return;
51
- }
52
- if (!routerCtx?.telemetry) return;
53
- const sink = resolveSink(routerCtx.telemetry);
54
- const reqId = routerCtx.requestId;
55
- promise.catch((err: unknown) => {
56
- const errorObj = err instanceof Error ? err : new Error(String(err));
57
- safeEmit(sink, {
58
- type: "handler.error",
59
- timestamp: performance.now(),
60
- requestId: reqId,
61
- segmentId,
62
- segmentType,
63
- error: errorObj,
64
- handledByBoundary: true,
65
- pathname,
66
- routeKey,
67
- params,
68
- });
69
- });
70
- }
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";
71
40
 
72
41
  // ---------------------------------------------------------------------------
73
42
  // Fresh path (full match, no revalidation)
@@ -91,8 +60,16 @@ export async function resolveLoaders<TEnv>(
91
60
  const hasLoading = "loading" in entry && entry.loading !== undefined;
92
61
  const loadingDisabled = hasLoading && entry.loading === false;
93
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);
69
+
94
70
  if (!loadingDisabled) {
95
- return loaderEntries.map((loaderEntry, i) => {
71
+ // Streaming loaders: promises kick off now, settle during RSC serialization.
72
+ const segments = loaderEntries.map((loaderEntry, i) => {
96
73
  const { loader } = loaderEntry;
97
74
  const segmentId = `${shortCode}D${i}.${loader.$$id}`;
98
75
  return {
@@ -104,40 +81,63 @@ export async function resolveLoaders<TEnv>(
104
81
  params: ctx.params,
105
82
  loaderId: loader.$$id,
106
83
  loaderData: deps.wrapLoaderPromise(
107
- resolveLoaderData(loaderEntry, ctx, ctx.pathname),
84
+ runInsideLoaderScope(() =>
85
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
86
+ ),
108
87
  entry,
109
88
  segmentId,
110
89
  ctx.pathname,
90
+ errorContext,
111
91
  ),
112
92
  belongsToRoute,
113
93
  };
114
94
  });
95
+
96
+ return segments;
115
97
  }
116
98
 
117
99
  // Loading disabled: still start all loaders in parallel, but only emit
118
100
  // settled promises so handlers don't stream loading placeholders.
119
- const pendingLoaderData = loaderEntries.map((loaderEntry) =>
120
- resolveLoaderData(loaderEntry, ctx, ctx.pathname),
121
- );
122
- await Promise.all(pendingLoaderData);
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 };
122
+ });
123
+ await Promise.all(pendingLoaderData.map((p) => p.wrapped));
123
124
 
124
125
  return loaderEntries.map((loaderEntry, i) => {
125
126
  const { loader } = loaderEntry;
126
- const segmentId = `${shortCode}D${i}.${loader.$$id}`;
127
+ const pending = pendingLoaderData[i]!;
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.
127
132
  return {
128
- id: segmentId,
133
+ id: pending.segmentId,
129
134
  namespace: entry.id,
130
135
  type: "loader" as const,
131
136
  index: i,
132
137
  component: null,
133
138
  params: ctx.params,
134
139
  loaderId: loader.$$id,
135
- loaderData: deps.wrapLoaderPromise(
136
- pendingLoaderData[i]!,
137
- entry,
138
- segmentId,
139
- ctx.pathname,
140
- ),
140
+ loaderData: pending.wrapped,
141
141
  belongsToRoute,
142
142
  };
143
143
  });
@@ -149,6 +149,15 @@ export async function resolveLoaders<TEnv>(
149
149
  export interface ResolveSegmentOptions {
150
150
  /** When true, skip resolveLoaders() calls (used for pre-rendering) */
151
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;
152
161
  }
153
162
 
154
163
  /**
@@ -190,14 +199,20 @@ export async function resolveSegment<TEnv>(
190
199
  index: 0,
191
200
  component,
192
201
  loading: entry.loading === false ? null : entry.loading,
193
- transition: entry.transition,
202
+ transition: applyViewTransitionDefault(
203
+ entry.transition,
204
+ deps.viewTransitionDefault,
205
+ ),
194
206
  params,
195
207
  belongsToRoute: false,
196
208
  layoutName: entry.id,
197
209
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
198
210
  });
199
211
 
200
- for (const parallelEntry of entry.parallel) {
212
+ const resolvedParallelEntries = new Set<string>();
213
+ for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
214
+ entry.parallel,
215
+ )) {
201
216
  const parallelSegments = await resolveParallelEntry(
202
217
  parallelEntry,
203
218
  params,
@@ -207,8 +222,11 @@ export async function resolveSegment<TEnv>(
207
222
  deps,
208
223
  options,
209
224
  routeKey,
225
+ [slot],
226
+ !resolvedParallelEntries.has(parallelEntry.id),
210
227
  );
211
228
  segments.push(...parallelSegments);
229
+ resolvedParallelEntries.add(parallelEntry.id);
212
230
  }
213
231
 
214
232
  for (const orphan of entry.layout) {
@@ -244,10 +262,18 @@ export async function resolveSegment<TEnv>(
244
262
  entry.shortCode,
245
263
  );
246
264
  if (component === undefined) {
265
+ // For Passthrough routes at runtime, use the live handler instead of
266
+ // the build handler. At build time (context.build === true), always
267
+ // use the build handler from entry.handler.
268
+ const handler =
269
+ !context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
247
270
  const doneRouteHandler = track(`handler:${entry.id}`, 2);
248
271
  if (entry.loading) {
249
- const result = handleHandlerResult(entry.handler(context));
272
+ const result = handleHandlerResult(
273
+ observeHandler(entry.id, handler, context),
274
+ );
250
275
  if (result instanceof Promise) {
276
+ warnOnStreamedResponse(result, entry.id);
251
277
  result.finally(doneRouteHandler).catch(() => {});
252
278
  const tracked = deps.trackHandler(result, {
253
279
  segmentId: entry.shortCode,
@@ -267,7 +293,9 @@ export async function resolveSegment<TEnv>(
267
293
  component = result;
268
294
  }
269
295
  } else {
270
- component = handleHandlerResult(await entry.handler(context));
296
+ component = handleHandlerResult(
297
+ await observeHandler(entry.id, handler, context),
298
+ );
271
299
  doneRouteHandler();
272
300
  }
273
301
  }
@@ -282,11 +310,15 @@ export async function resolveSegment<TEnv>(
282
310
  deps,
283
311
  options,
284
312
  routeKey,
313
+ entry,
285
314
  );
286
315
  segments.push(...orphanSegments);
287
316
  }
288
317
 
289
- for (const parallelEntry of entry.parallel) {
318
+ const resolvedParallelEntries = new Set<string>();
319
+ for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
320
+ entry.parallel,
321
+ )) {
290
322
  const parallelSegments = await resolveParallelEntry(
291
323
  parallelEntry,
292
324
  params,
@@ -296,8 +328,11 @@ export async function resolveSegment<TEnv>(
296
328
  deps,
297
329
  options,
298
330
  routeKey,
331
+ [slot],
332
+ !resolvedParallelEntries.has(parallelEntry.id),
299
333
  );
300
334
  segments.push(...parallelSegments);
335
+ resolvedParallelEntries.add(parallelEntry.id);
301
336
  }
302
337
 
303
338
  segments.push({
@@ -305,9 +340,12 @@ export async function resolveSegment<TEnv>(
305
340
  namespace: entry.id,
306
341
  type: "route",
307
342
  index: 0,
308
- component,
343
+ component: component ?? null,
309
344
  loading: entry.loading === false ? null : entry.loading,
310
- transition: entry.transition,
345
+ transition: applyViewTransitionDefault(
346
+ entry.transition,
347
+ deps.viewTransitionDefault,
348
+ ),
311
349
  params,
312
350
  belongsToRoute: true,
313
351
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
@@ -331,6 +369,9 @@ export async function resolveOrphanLayout<TEnv>(
331
369
  deps: SegmentResolutionDeps<TEnv>,
332
370
  options?: ResolveSegmentOptions,
333
371
  routeKey?: string,
372
+ /** Parent route entry — its loaders are inherited by the layout so
373
+ * parallel slots inside this layout can access them via useLoader(). */
374
+ parentRouteEntry?: EntryData,
334
375
  ): Promise<ResolvedSegment[]> {
335
376
  invariant(
336
377
  orphan.type === "layout" || orphan.type === "cache",
@@ -346,6 +387,30 @@ export async function resolveOrphanLayout<TEnv>(
346
387
  deps,
347
388
  );
348
389
  segments.push(...loaderSegments);
390
+
391
+ // Inherit parent route's loaders so parallel slots inside this layout
392
+ // can access them via useLoader(). Without this, the route's loaders
393
+ // are only in the route's OutletProvider (rendered as <Outlet /> content),
394
+ // which is a child — not a parent — of the layout's context.
395
+ if (
396
+ parentRouteEntry &&
397
+ parentRouteEntry.loader &&
398
+ parentRouteEntry.loader.length > 0 &&
399
+ Object.keys(orphan.parallel).length > 0
400
+ ) {
401
+ const inheritedLoaders = await resolveLoaders(
402
+ parentRouteEntry,
403
+ context,
404
+ belongsToRoute,
405
+ deps,
406
+ orphan.shortCode,
407
+ );
408
+ // Tag as inherited so buildMatchResult can deduplicate when safe
409
+ for (const s of inheritedLoaders) {
410
+ s._inherited = true;
411
+ }
412
+ segments.push(...inheritedLoaders);
413
+ }
349
414
  }
350
415
 
351
416
  // Handler-first: orphan layout handler executes before its parallels
@@ -364,11 +429,17 @@ export async function resolveOrphanLayout<TEnv>(
364
429
  belongsToRoute,
365
430
  layoutName: orphan.id,
366
431
  loading: orphan.loading === false ? null : orphan.loading,
367
- transition: orphan.transition,
432
+ transition: applyViewTransitionDefault(
433
+ orphan.transition,
434
+ deps.viewTransitionDefault,
435
+ ),
368
436
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
369
437
  });
370
438
 
371
- for (const parallelEntry of orphan.parallel) {
439
+ const resolvedParallelEntries = new Set<string>();
440
+ for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
441
+ orphan.parallel,
442
+ )) {
372
443
  const parallelSegments = await resolveParallelEntry(
373
444
  parallelEntry,
374
445
  params,
@@ -378,8 +449,11 @@ export async function resolveOrphanLayout<TEnv>(
378
449
  deps,
379
450
  options,
380
451
  routeKey,
452
+ [slot],
453
+ !resolvedParallelEntries.has(parallelEntry.id),
381
454
  );
382
455
  segments.push(...parallelSegments);
456
+ resolvedParallelEntries.add(parallelEntry.id);
383
457
  }
384
458
 
385
459
  return segments;
@@ -397,6 +471,8 @@ export async function resolveParallelEntry<TEnv>(
397
471
  deps: SegmentResolutionDeps<TEnv>,
398
472
  options?: ResolveSegmentOptions,
399
473
  routeKey?: string,
474
+ slotNames?: `@${string}`[],
475
+ includeLoaders: boolean = true,
400
476
  ): Promise<ResolvedSegment[]> {
401
477
  invariant(
402
478
  parallelEntry.type === "parallel",
@@ -411,7 +487,12 @@ export async function resolveParallelEntry<TEnv>(
411
487
  | ReactNode
412
488
  >;
413
489
 
414
- for (const [slot, handler] of Object.entries(slots)) {
490
+ const slotsToResolve = slotNames ?? (Object.keys(slots) as `@${string}`[]);
491
+
492
+ for (const slot of slotsToResolve) {
493
+ // Try static lookup first — in production, handler bodies are evicted
494
+ // and replaced with stubs that have no .handler property (undefined).
495
+ // The static store holds the pre-rendered component for these slots.
415
496
  let component: ReactNode | undefined = await tryStaticSlot(
416
497
  parallelEntry,
417
498
  slot,
@@ -419,6 +500,18 @@ export async function resolveParallelEntry<TEnv>(
419
500
  );
420
501
 
421
502
  if (component === undefined) {
503
+ const handler = slots[slot];
504
+ if (handler === undefined) {
505
+ continue;
506
+ }
507
+ // Pin `_currentSegmentId` to the slot's own id so handle pushes from
508
+ // inside the slot handler get their own bucket in the HandleStore.
509
+ // Parent-keying would collapse them into the parent layout's bucket;
510
+ // the partial-update merge then replaces the parent's bucket on a
511
+ // slot-only revalidation and drops layout-pushed Meta/Breadcrumbs.
512
+ // filterSegmentOrder() retains slot ids so the client preserves them.
513
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
514
+ `${parentShortCode}.${slot}`;
422
515
  const doneParallelHandler = track(
423
516
  `handler:${parallelEntry.id}.${slot}`,
424
517
  2,
@@ -427,7 +520,9 @@ export async function resolveParallelEntry<TEnv>(
427
520
  parallelEntry.loading !== undefined && parallelEntry.loading !== false;
428
521
  if (hasLoadingFallback) {
429
522
  const result =
430
- typeof handler === "function" ? handler(context) : handler;
523
+ typeof handler === "function"
524
+ ? observeHandler(`${parallelEntry.id}.${slot}`, handler, context)
525
+ : handler;
431
526
  if (result instanceof Promise) {
432
527
  result.finally(doneParallelHandler).catch(() => {});
433
528
  const tracked = deps.trackHandler(result, {
@@ -449,7 +544,13 @@ export async function resolveParallelEntry<TEnv>(
449
544
  }
450
545
  } else {
451
546
  component =
452
- typeof handler === "function" ? await handler(context) : handler;
547
+ typeof handler === "function"
548
+ ? await observeHandler(
549
+ `${parallelEntry.id}.${slot}`,
550
+ handler,
551
+ context,
552
+ )
553
+ : handler;
453
554
  doneParallelHandler();
454
555
  }
455
556
  }
@@ -461,7 +562,10 @@ export async function resolveParallelEntry<TEnv>(
461
562
  index: 0,
462
563
  component,
463
564
  loading: parallelEntry.loading === false ? null : parallelEntry.loading,
464
- transition: parallelEntry.transition,
565
+ transition: applyViewTransitionDefault(
566
+ parallelEntry.transition,
567
+ deps.viewTransitionDefault,
568
+ ),
465
569
  params,
466
570
  slot,
467
571
  belongsToRoute,
@@ -472,7 +576,7 @@ export async function resolveParallelEntry<TEnv>(
472
576
  });
473
577
  }
474
578
 
475
- if (!parallelEntry.loading && !options?.skipLoaders) {
579
+ if (!options?.skipLoaders && includeLoaders) {
476
580
  const loaderSegments = await resolveLoaders(
477
581
  parallelEntry,
478
582
  context,
@@ -480,6 +584,15 @@ export async function resolveParallelEntry<TEnv>(
480
584
  deps,
481
585
  parentShortCode,
482
586
  );
587
+ // Tag parallel-owned loaders so renderSegments can stream them
588
+ // using the parallel's loading() instead of awaiting on the layout
589
+ const parallelLoading =
590
+ parallelEntry.loading === false ? undefined : parallelEntry.loading;
591
+ if (parallelLoading) {
592
+ for (const seg of loaderSegments) {
593
+ seg.parallelLoading = parallelLoading;
594
+ }
595
+ }
483
596
  segments.push(...loaderSegments);
484
597
  }
485
598
 
@@ -515,6 +628,13 @@ export async function resolveAllSegments<TEnv>(
515
628
  } catch {}
516
629
 
517
630
  for (const entry of entries) {
631
+ // Set ALS flag when entering a cache() boundary so that ctx.get()
632
+ // can guard non-cacheable variable reads. Also guards response-level
633
+ // side effects (headers.set). Persists for all descendant entries.
634
+ if (entry.type === "cache") {
635
+ const store = RangoContext.getStore();
636
+ if (store) store.insideCacheScope = true;
637
+ }
518
638
  const doneEntry = track(`segment:${entry.id}`, 1);
519
639
  const resolvedSegments = await resolveWithErrorBoundary(
520
640
  entry,
@@ -534,6 +654,7 @@ export async function resolveAllSegments<TEnv>(
534
654
  deps,
535
655
  { request: safeRequest, url: context.url, routeKey, telemetry },
536
656
  context.pathname,
657
+ options?.throwOnError,
537
658
  );
538
659
  doneEntry();
539
660
  // Deduplicate by segment ID. include() scopes can produce entries that
@@ -559,11 +680,77 @@ export async function resolveLoadersOnly<TEnv>(
559
680
  deps: SegmentResolutionDeps<TEnv>,
560
681
  ): Promise<ResolvedSegment[]> {
561
682
  const loaderSegments: ResolvedSegment[] = [];
683
+ const seenIds = new Set<string>();
684
+
685
+ async function collectEntryLoaders(
686
+ entry: EntryData,
687
+ belongsToRoute: boolean,
688
+ shortCodeOverride?: string,
689
+ ): Promise<void> {
690
+ // Skip if all loaders from this entry have already been resolved
691
+ // via a parent (e.g., cache boundary wrapping a layout with shared loaders).
692
+ const entryLoaders = entry.loader ?? [];
693
+ const sc = shortCodeOverride ?? entry.shortCode;
694
+ const allAlreadySeen =
695
+ entryLoaders.length > 0 &&
696
+ entryLoaders.every((le, i) =>
697
+ seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
698
+ );
699
+ if (!allAlreadySeen) {
700
+ const segments = await resolveLoaders(
701
+ entry,
702
+ context,
703
+ belongsToRoute,
704
+ deps,
705
+ shortCodeOverride,
706
+ );
707
+ for (const seg of segments) {
708
+ if (!seenIds.has(seg.id)) {
709
+ seenIds.add(seg.id);
710
+ loaderSegments.push(seg);
711
+ }
712
+ }
713
+ }
714
+
715
+ const seenParallelEntryIds = new Set<string>();
716
+ for (const parallelEntry of getParallelEntries(entry.parallel)) {
717
+ if (seenParallelEntryIds.has(parallelEntry.id)) continue;
718
+ seenParallelEntryIds.add(parallelEntry.id);
719
+ await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
720
+ }
721
+
722
+ const childBelongsToRoute = belongsToRoute || entry.type === "route";
723
+ for (const layoutEntry of entry.layout) {
724
+ await collectEntryLoaders(layoutEntry, childBelongsToRoute);
725
+ // Inherit route loaders for orphan layouts with parallels.
726
+ // Resolve directly — do NOT re-enter collectEntryLoaders with the
727
+ // route entry, as that would re-iterate route.layout and loop.
728
+ if (
729
+ entry.type === "route" &&
730
+ entry.loader &&
731
+ entry.loader.length > 0 &&
732
+ Object.keys(layoutEntry.parallel).length > 0
733
+ ) {
734
+ const inherited = await resolveLoaders(
735
+ entry,
736
+ context,
737
+ childBelongsToRoute,
738
+ deps,
739
+ layoutEntry.shortCode,
740
+ );
741
+ for (const seg of inherited) {
742
+ if (!seenIds.has(seg.id)) {
743
+ seenIds.add(seg.id);
744
+ seg._inherited = true;
745
+ loaderSegments.push(seg);
746
+ }
747
+ }
748
+ }
749
+ }
750
+ }
562
751
 
563
752
  for (const entry of entries) {
564
- const belongsToRoute = entry.type === "route";
565
- const segments = await resolveLoaders(entry, context, belongsToRoute, deps);
566
- loaderSegments.push(...segments);
753
+ await collectEntryLoaders(entry, entry.type === "route");
567
754
  }
568
755
 
569
756
  return loaderSegments;