@rangojs/router 0.0.0-experimental.135c6902 → 0.0.0-experimental.136

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 (404) 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 +3386 -1111
  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 +78 -25
  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 +247 -23
  13. package/skills/caching/SKILL.md +281 -11
  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 +289 -53
  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 +914 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +144 -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 +101 -5
  37. package/skills/router-setup/SKILL.md +117 -10
  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 +332 -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 +199 -86
  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 +174 -109
  80. package/src/browser/prefetch/cache.ts +222 -69
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +113 -32
  83. package/src/browser/prefetch/resource-ready.ts +77 -0
  84. package/src/browser/rango-state.ts +158 -76
  85. package/src/browser/react/Link.tsx +102 -15
  86. package/src/browser/react/NavigationProvider.tsx +300 -122
  87. package/src/browser/react/ScrollRestoration.tsx +10 -6
  88. package/src/browser/react/context.ts +7 -2
  89. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  90. package/src/browser/react/filter-segment-order.ts +66 -7
  91. package/src/browser/react/index.ts +0 -48
  92. package/src/browser/react/location-state-shared.ts +178 -8
  93. package/src/browser/react/location-state.ts +39 -14
  94. package/src/browser/react/use-action.ts +6 -15
  95. package/src/browser/react/use-handle.ts +23 -69
  96. package/src/browser/react/use-href.tsx +8 -1
  97. package/src/browser/react/use-link-status.ts +33 -8
  98. package/src/browser/react/use-navigation.ts +32 -7
  99. package/src/browser/react/use-params.ts +20 -10
  100. package/src/browser/react/use-reverse.ts +106 -0
  101. package/src/browser/react/use-router.ts +46 -11
  102. package/src/browser/react/use-search-params.ts +0 -5
  103. package/src/browser/react/use-segments.ts +11 -21
  104. package/src/browser/response-adapter.ts +99 -8
  105. package/src/browser/rsc-router.tsx +125 -28
  106. package/src/browser/scroll-restoration.ts +37 -22
  107. package/src/browser/segment-reconciler.ts +36 -14
  108. package/src/browser/segment-structure-assert.ts +2 -2
  109. package/src/browser/server-action-bridge.ts +222 -61
  110. package/src/browser/types.ts +115 -12
  111. package/src/browser/validate-redirect-origin.ts +43 -16
  112. package/src/build/collect-fallback-refs.ts +107 -0
  113. package/src/build/generate-manifest.ts +65 -40
  114. package/src/build/generate-route-types.ts +5 -1
  115. package/src/build/index.ts +8 -2
  116. package/src/build/prefix-tree-utils.ts +123 -0
  117. package/src/build/route-trie.ts +165 -36
  118. package/src/build/route-types/ast-route-extraction.ts +15 -8
  119. package/src/build/route-types/codegen.ts +16 -5
  120. package/src/build/route-types/include-resolution.ts +125 -24
  121. package/src/build/route-types/param-extraction.ts +6 -3
  122. package/src/build/route-types/per-module-writer.ts +22 -6
  123. package/src/build/route-types/router-processing.ts +260 -94
  124. package/src/build/route-types/scan-filter.ts +9 -2
  125. package/src/build/route-types/source-scan.ts +216 -0
  126. package/src/build/runtime-discovery.ts +9 -20
  127. package/src/cache/cache-error.ts +104 -0
  128. package/src/cache/cache-key-utils.ts +29 -13
  129. package/src/cache/cache-policy.ts +108 -34
  130. package/src/cache/cache-runtime.ts +239 -52
  131. package/src/cache/cache-scope.ts +234 -87
  132. package/src/cache/cache-tag.ts +103 -0
  133. package/src/cache/cf/cf-base64.ts +33 -0
  134. package/src/cache/cf/cf-cache-constants.ts +127 -0
  135. package/src/cache/cf/cf-cache-store.ts +1989 -378
  136. package/src/cache/cf/cf-cache-types.ts +349 -0
  137. package/src/cache/cf/cf-kv-utils.ts +46 -0
  138. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  139. package/src/cache/cf/index.ts +6 -16
  140. package/src/cache/document-cache.ts +89 -21
  141. package/src/cache/handle-snapshot.ts +70 -0
  142. package/src/cache/index.ts +10 -20
  143. package/src/cache/memory-segment-store.ts +136 -37
  144. package/src/cache/profile-registry.ts +46 -31
  145. package/src/cache/read-through-swr.ts +56 -12
  146. package/src/cache/segment-codec.ts +9 -17
  147. package/src/cache/tag-invalidation.ts +230 -0
  148. package/src/cache/taint.ts +55 -0
  149. package/src/cache/types.ts +37 -100
  150. package/src/client.rsc.tsx +44 -21
  151. package/src/client.tsx +119 -290
  152. package/src/cloudflare/index.ts +11 -0
  153. package/src/cloudflare/tracing.ts +109 -0
  154. package/src/component-utils.ts +19 -0
  155. package/src/components/DefaultDocument.tsx +8 -2
  156. package/src/context-var.ts +84 -2
  157. package/src/decode-loader-results.ts +52 -0
  158. package/src/defer.ts +196 -0
  159. package/src/deps/ssr.ts +0 -1
  160. package/src/encode-kv.ts +49 -0
  161. package/src/errors.ts +30 -4
  162. package/src/escape-script.ts +52 -0
  163. package/src/handle.ts +70 -22
  164. package/src/handles/MetaTags.tsx +62 -19
  165. package/src/handles/Scripts.tsx +183 -0
  166. package/src/handles/breadcrumbs.ts +37 -8
  167. package/src/handles/is-thenable.ts +19 -0
  168. package/src/handles/meta.ts +51 -40
  169. package/src/handles/script.ts +244 -0
  170. package/src/host/cookie-handler.ts +9 -60
  171. package/src/host/errors.ts +0 -24
  172. package/src/host/index.ts +8 -2
  173. package/src/host/pattern-matcher.ts +23 -52
  174. package/src/host/router.ts +107 -99
  175. package/src/host/testing.ts +40 -27
  176. package/src/host/types.ts +37 -4
  177. package/src/host/utils.ts +1 -1
  178. package/src/href-client.ts +137 -22
  179. package/src/index.rsc.ts +99 -13
  180. package/src/index.ts +139 -19
  181. package/src/internal-debug.ts +11 -10
  182. package/src/loader-store.ts +500 -0
  183. package/src/loader.rsc.ts +20 -13
  184. package/src/loader.ts +12 -11
  185. package/src/missing-id-error.ts +68 -0
  186. package/src/outlet-context.ts +1 -1
  187. package/src/outlet-provider.tsx +1 -5
  188. package/src/prerender/param-hash.ts +16 -16
  189. package/src/prerender/store.ts +37 -41
  190. package/src/prerender.ts +198 -82
  191. package/src/redirect-origin.ts +100 -0
  192. package/src/regex-escape.ts +8 -0
  193. package/src/render-error-thrower.tsx +20 -0
  194. package/src/response-utils.ts +62 -0
  195. package/src/reverse.ts +65 -15
  196. package/src/root-error-boundary.tsx +1 -19
  197. package/src/route-content-wrapper.tsx +19 -77
  198. package/src/route-definition/dsl-helpers.ts +461 -304
  199. package/src/route-definition/helper-factories.ts +28 -140
  200. package/src/route-definition/helpers-types.ts +149 -74
  201. package/src/route-definition/index.ts +4 -2
  202. package/src/route-definition/redirect.ts +51 -10
  203. package/src/route-definition/resolve-handler-use.ts +160 -0
  204. package/src/route-definition/use-item-types.ts +29 -0
  205. package/src/route-map-builder.ts +0 -16
  206. package/src/route-types.ts +37 -46
  207. package/src/router/basename.ts +14 -0
  208. package/src/router/content-negotiation.ts +164 -17
  209. package/src/router/error-handling.ts +45 -18
  210. package/src/router/find-match.ts +44 -23
  211. package/src/router/handler-context.ts +83 -39
  212. package/src/router/instrument.ts +350 -0
  213. package/src/router/intercept-resolution.ts +48 -24
  214. package/src/router/lazy-includes.ts +15 -52
  215. package/src/router/loader-resolution.ts +274 -56
  216. package/src/router/logging.ts +0 -6
  217. package/src/router/manifest.ts +40 -42
  218. package/src/router/match-api.ts +124 -204
  219. package/src/router/match-context.ts +0 -22
  220. package/src/router/match-handlers.ts +58 -58
  221. package/src/router/match-middleware/background-revalidation.ts +54 -25
  222. package/src/router/match-middleware/cache-lookup.ts +205 -271
  223. package/src/router/match-middleware/cache-store.ts +81 -50
  224. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  225. package/src/router/match-middleware/segment-resolution.ts +45 -14
  226. package/src/router/match-pipelines.ts +1 -42
  227. package/src/router/match-result.ts +93 -39
  228. package/src/router/metrics.ts +5 -34
  229. package/src/router/middleware-types.ts +13 -142
  230. package/src/router/middleware.ts +268 -171
  231. package/src/router/navigation-snapshot.ts +131 -0
  232. package/src/router/params-util.ts +23 -0
  233. package/src/router/pattern-matching.ts +132 -90
  234. package/src/router/prefetch-cache-ttl.ts +51 -0
  235. package/src/router/prefetch-limits.ts +37 -0
  236. package/src/router/prerender-match.ts +195 -56
  237. package/src/router/preview-match.ts +32 -102
  238. package/src/router/request-classification.ts +276 -0
  239. package/src/router/revalidation.ts +123 -73
  240. package/src/router/route-snapshot.ts +244 -0
  241. package/src/router/router-context.ts +3 -27
  242. package/src/router/router-interfaces.ts +129 -35
  243. package/src/router/router-options.ts +202 -15
  244. package/src/router/router-registry.ts +2 -5
  245. package/src/router/segment-resolution/fresh.ts +197 -92
  246. package/src/router/segment-resolution/helpers.ts +115 -30
  247. package/src/router/segment-resolution/loader-cache.ts +76 -39
  248. package/src/router/segment-resolution/revalidation.ts +392 -338
  249. package/src/router/segment-resolution/static-store.ts +19 -5
  250. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  251. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  252. package/src/router/segment-resolution.ts +5 -1
  253. package/src/router/segment-wrappers.ts +6 -5
  254. package/src/router/state-cookie-name.ts +33 -0
  255. package/src/router/substitute-pattern-params.ts +56 -0
  256. package/src/router/telemetry-otel.ts +161 -199
  257. package/src/router/telemetry.ts +96 -19
  258. package/src/router/timeout.ts +0 -20
  259. package/src/router/tracing.ts +206 -0
  260. package/src/router/trie-matching.ts +163 -59
  261. package/src/router/types.ts +10 -63
  262. package/src/router/url-params.ts +44 -0
  263. package/src/router.ts +174 -54
  264. package/src/rsc/handler-context.ts +3 -2
  265. package/src/rsc/handler.ts +660 -518
  266. package/src/rsc/helpers.ts +168 -46
  267. package/src/rsc/index.ts +2 -5
  268. package/src/rsc/json-route-result.ts +38 -0
  269. package/src/rsc/loader-fetch.ts +127 -31
  270. package/src/rsc/manifest-init.ts +33 -42
  271. package/src/rsc/origin-guard.ts +39 -25
  272. package/src/rsc/progressive-enhancement.ts +133 -13
  273. package/src/rsc/redirect-guard.ts +99 -0
  274. package/src/rsc/response-cache-serve.ts +238 -0
  275. package/src/rsc/response-error.ts +79 -12
  276. package/src/rsc/response-route-handler.ts +99 -189
  277. package/src/rsc/rsc-rendering.ts +115 -73
  278. package/src/rsc/runtime-warnings.ts +23 -10
  279. package/src/rsc/server-action.ts +287 -113
  280. package/src/rsc/ssr-setup.ts +18 -2
  281. package/src/rsc/transition-gate.ts +89 -0
  282. package/src/rsc/types.ts +36 -6
  283. package/src/runtime-env.ts +18 -0
  284. package/src/search-params.ts +35 -30
  285. package/src/segment-content-promise.ts +67 -0
  286. package/src/segment-loader-promise.ts +149 -0
  287. package/src/segment-system.tsx +269 -202
  288. package/src/serialize.ts +243 -0
  289. package/src/server/context.ts +235 -51
  290. package/src/server/cookie-parse.ts +32 -0
  291. package/src/server/cookie-store.ts +80 -5
  292. package/src/server/handle-store.ts +40 -38
  293. package/src/server/loader-registry.ts +38 -46
  294. package/src/server/request-context.ts +442 -173
  295. package/src/ssr/index.tsx +24 -16
  296. package/src/static-handler.ts +27 -18
  297. package/src/testing/cache-status.ts +162 -0
  298. package/src/testing/collect-handle.ts +40 -0
  299. package/src/testing/dispatch.ts +701 -0
  300. package/src/testing/dom.entry.ts +22 -0
  301. package/src/testing/e2e/fixture.ts +188 -0
  302. package/src/testing/e2e/index.ts +128 -0
  303. package/src/testing/e2e/matchers.ts +35 -0
  304. package/src/testing/e2e/page-helpers.ts +272 -0
  305. package/src/testing/e2e/parity.ts +387 -0
  306. package/src/testing/e2e/server.ts +195 -0
  307. package/src/testing/flight-matchers.ts +97 -0
  308. package/src/testing/flight-normalize.ts +11 -0
  309. package/src/testing/flight-runtime.d.ts +57 -0
  310. package/src/testing/flight-tree.ts +682 -0
  311. package/src/testing/flight.entry.ts +52 -0
  312. package/src/testing/flight.ts +257 -0
  313. package/src/testing/generated-routes.ts +183 -0
  314. package/src/testing/index.ts +105 -0
  315. package/src/testing/internal/context.ts +371 -0
  316. package/src/testing/internal/flight-client-globals.ts +30 -0
  317. package/src/testing/internal/seed-vars.ts +54 -0
  318. package/src/testing/render-handler.ts +357 -0
  319. package/src/testing/render-route.tsx +581 -0
  320. package/src/testing/run-loader.ts +385 -0
  321. package/src/testing/run-middleware.ts +205 -0
  322. package/src/testing/run-transition-when.ts +164 -0
  323. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  324. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  325. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  326. package/src/testing/vitest-stubs/version.ts +5 -0
  327. package/src/testing/vitest.ts +305 -0
  328. package/src/theme/ThemeProvider.tsx +20 -58
  329. package/src/theme/ThemeScript.tsx +7 -9
  330. package/src/theme/constants.ts +52 -13
  331. package/src/theme/index.ts +0 -7
  332. package/src/theme/theme-context.ts +1 -5
  333. package/src/theme/theme-script.ts +22 -21
  334. package/src/theme/use-theme.ts +0 -3
  335. package/src/types/boundaries.ts +0 -35
  336. package/src/types/cache-types.ts +17 -8
  337. package/src/types/error-types.ts +30 -90
  338. package/src/types/global-namespace.ts +54 -41
  339. package/src/types/handler-context.ts +234 -82
  340. package/src/types/index.ts +3 -10
  341. package/src/types/loader-types.ts +44 -15
  342. package/src/types/request-scope.ts +112 -0
  343. package/src/types/route-config.ts +6 -50
  344. package/src/types/route-entry.ts +12 -7
  345. package/src/types/segments.ts +136 -15
  346. package/src/urls/include-helper.ts +33 -70
  347. package/src/urls/index.ts +1 -11
  348. package/src/urls/path-helper-types.ts +68 -18
  349. package/src/urls/path-helper.ts +57 -111
  350. package/src/urls/pattern-types.ts +48 -19
  351. package/src/urls/response-types.ts +25 -22
  352. package/src/urls/type-extraction.ts +58 -139
  353. package/src/urls/urls-function.ts +1 -19
  354. package/src/use-loader.tsx +346 -89
  355. package/src/vite/debug.ts +185 -0
  356. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  357. package/src/vite/discovery/discover-routers.ts +130 -85
  358. package/src/vite/discovery/discovery-errors.ts +194 -0
  359. package/src/vite/discovery/gate-state.ts +171 -0
  360. package/src/vite/discovery/prerender-collection.ts +214 -132
  361. package/src/vite/discovery/route-types-writer.ts +40 -84
  362. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  363. package/src/vite/discovery/state.ts +57 -4
  364. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  365. package/src/vite/index.ts +6 -0
  366. package/src/vite/inject-client-debug.ts +36 -0
  367. package/src/vite/plugin-types.ts +178 -5
  368. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  369. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  370. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  371. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  372. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  373. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  374. package/src/vite/plugins/expose-action-id.ts +48 -95
  375. package/src/vite/plugins/expose-id-utils.ts +96 -51
  376. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  377. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  378. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  379. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  380. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  381. package/src/vite/plugins/performance-tracks.ts +89 -0
  382. package/src/vite/plugins/refresh-cmd.ts +89 -27
  383. package/src/vite/plugins/use-cache-transform.ts +73 -83
  384. package/src/vite/plugins/version-injector.ts +40 -29
  385. package/src/vite/plugins/version-plugin.ts +37 -40
  386. package/src/vite/plugins/virtual-entries.ts +39 -25
  387. package/src/vite/rango.ts +119 -111
  388. package/src/vite/router-discovery.ts +941 -142
  389. package/src/vite/utils/ast-handler-extract.ts +26 -35
  390. package/src/vite/utils/banner.ts +1 -1
  391. package/src/vite/utils/bundle-analysis.ts +10 -15
  392. package/src/vite/utils/client-chunks.ts +184 -0
  393. package/src/vite/utils/directive-prologue.ts +40 -0
  394. package/src/vite/utils/forward-user-plugins.ts +171 -0
  395. package/src/vite/utils/manifest-utils.ts +4 -59
  396. package/src/vite/utils/package-resolution.ts +20 -52
  397. package/src/vite/utils/prerender-utils.ts +81 -34
  398. package/src/vite/utils/shared-utils.ts +92 -42
  399. package/src/browser/action-response-classifier.ts +0 -99
  400. package/src/browser/react/use-client-cache.ts +0 -58
  401. package/src/browser/shallow.ts +0 -40
  402. package/src/handles/index.ts +0 -7
  403. package/src/network-error-thrower.tsx +0 -23
  404. package/src/router/middleware-cookies.ts +0 -55
@@ -67,10 +67,11 @@
67
67
  * Keep if:
68
68
  * - component !== null (needs rendering)
69
69
  * - type === "loader" (carries data even with null component)
70
+ * - client doesn't have the segment (structurally required parent node)
70
71
  *
71
72
  * Skip if:
72
- * - component === null AND type !== "loader"
73
- * - (Client already has this segment's UI)
73
+ * - component === null AND type !== "loader" AND client has it cached
74
+ * - (Revalidation skip — client already has this segment's UI)
74
75
  *
75
76
  *
76
77
  * INTERCEPT HANDLING
@@ -111,9 +112,6 @@ import type { MatchContext, MatchPipelineState } from "./match-context.js";
111
112
  import { debugLog } from "./logging.js";
112
113
  import { appendMetric } from "./metrics.js";
113
114
 
114
- /**
115
- * Collect all segments from an async generator
116
- */
117
115
  export async function collectSegments(
118
116
  generator: AsyncGenerator<ResolvedSegment>,
119
117
  ): Promise<ResolvedSegment[]> {
@@ -125,8 +123,73 @@ export async function collectSegments(
125
123
  }
126
124
 
127
125
  /**
128
- * Build the final MatchResult from collected segments and context
126
+ * Deduplicate inherited loader segments by loaderId.
127
+ *
128
+ * When a route has loaders and a child layout has parallel slots, the same
129
+ * loader is resolved twice: once for the route and once inherited into the
130
+ * layout (tagged with `_inherited`). The inherited copy is only needed when
131
+ * the route uses `loading()` — in that case, the loader data is inside a
132
+ * LoaderBoundary/Suspense that parallel slots can't reach through. Without
133
+ * loading(), useLoader() traverses parent contexts and finds the data.
129
134
  */
135
+ function deduplicateLoaderSegments(
136
+ segments: ResolvedSegment[],
137
+ logPrefix: string,
138
+ ): { segments: ResolvedSegment[]; removedIds: Set<string> } {
139
+ // Single pass: original (non-inherited) loaderIds, all loaderIds grouped by
140
+ // namespace, and namespaces of segments that declare loading().
141
+ const originalLoaders = new Set<string>();
142
+ const loaderIdsByNamespace = new Map<string, string[]>();
143
+ const namespacesWithLoading = new Set<string>();
144
+ for (const s of segments) {
145
+ if (s.type === "loader" && s.loaderId) {
146
+ if (!s._inherited) originalLoaders.add(s.loaderId);
147
+ const ids = loaderIdsByNamespace.get(s.namespace);
148
+ if (ids) ids.push(s.loaderId);
149
+ else loaderIdsByNamespace.set(s.namespace, [s.loaderId]);
150
+ } else if (
151
+ s.type !== "loader" &&
152
+ s.loading !== undefined &&
153
+ s.loading !== false
154
+ ) {
155
+ namespacesWithLoading.add(s.namespace);
156
+ }
157
+ }
158
+
159
+ const loadersWithLoading = new Set<string>();
160
+ for (const ns of namespacesWithLoading) {
161
+ for (const id of loaderIdsByNamespace.get(ns) ?? []) {
162
+ loadersWithLoading.add(id);
163
+ }
164
+ }
165
+
166
+ const result: ResolvedSegment[] = [];
167
+ const removedIds = new Set<string>();
168
+
169
+ for (const s of segments) {
170
+ if (
171
+ s.type === "loader" &&
172
+ s.loaderId &&
173
+ s._inherited &&
174
+ originalLoaders.has(s.loaderId) &&
175
+ !loadersWithLoading.has(s.loaderId)
176
+ ) {
177
+ removedIds.add(s.id);
178
+ continue;
179
+ }
180
+ result.push(s);
181
+ }
182
+
183
+ if (removedIds.size > 0) {
184
+ debugLog(
185
+ logPrefix,
186
+ `deduped ${removedIds.size} inherited loader segment(s)`,
187
+ );
188
+ }
189
+
190
+ return { segments: result, removedIds };
191
+ }
192
+
130
193
  export function buildMatchResult<TEnv>(
131
194
  allSegments: ResolvedSegment[],
132
195
  ctx: MatchContext<TEnv>,
@@ -140,11 +203,6 @@ export function buildMatchResult<TEnv>(
140
203
  let segmentsToRender: ResolvedSegment[];
141
204
 
142
205
  if (ctx.isFullMatch) {
143
- // Full match (document request) - all segments are rendered
144
- // Deduplicate by segment ID (defense-in-depth). The primary dedup is in
145
- // resolveAllSegments, but this guards against any path that bypasses it.
146
- // include() scopes can produce entries that resolve the same shared layout,
147
- // and duplicate IDs change the client's React tree depth causing remounts.
148
206
  const seen = new Set<string>();
149
207
  segmentsToRender = [];
150
208
  for (const s of allSegments) {
@@ -155,41 +213,44 @@ export function buildMatchResult<TEnv>(
155
213
  }
156
214
  allIds = segmentsToRender.map((s) => s.id);
157
215
  } else {
158
- // Partial match (navigation) - filter and handle intercepts
159
- // When intercepting, tell browser to keep its current segments + add modal
160
- // This prevents the browser from discarding the current page content
161
- // If client sent empty segments (HMR recovery), use segment IDs from allSegments
162
216
  allIds = ctx.interceptResult
163
217
  ? ctx.clientSegmentIds.length > 0
164
218
  ? [...ctx.clientSegmentIds, ...state.interceptSegments.map((s) => s.id)]
165
- : allSegments.map((s) => s.id) // Use actual segments, not matchedIds
219
+ : allSegments.map((s) => s.id)
166
220
  : [...state.matchedIds, ...state.interceptSegments.map((s) => s.id)];
167
221
 
168
- // Deduplicate allIds (defense-in-depth for partial match path)
169
222
  allIds = [...new Set(allIds)];
170
223
 
171
- // Filter out segments with null components (client already has them)
172
- // BUT always include loader segments - they carry data even with null component
224
+ const clientIdSet = new Set(ctx.clientSegmentIds);
173
225
  segmentsToRender = allSegments.filter(
174
- (s) => s.component !== null || s.type === "loader",
226
+ (s) =>
227
+ s.component !== null || s.type === "loader" || !clientIdSet.has(s.id),
175
228
  );
176
229
  }
177
230
 
178
- debugLog(logPrefix, "all segments", {
179
- segments: allSegments.map((s) => ({
180
- id: s.id,
181
- type: s.type,
182
- hasComponent: s.component !== null,
183
- })),
184
- });
185
- debugLog(logPrefix, "segments to render", {
186
- segmentIds: segmentsToRender.map((s) => s.id),
231
+ const { segments: dedupedSegments, removedIds } = deduplicateLoaderSegments(
232
+ segmentsToRender,
233
+ logPrefix,
234
+ );
235
+
236
+ const matchedIds =
237
+ removedIds.size > 0 ? allIds.filter((id) => !removedIds.has(id)) : allIds;
238
+
239
+ const resolvedIds = ctx.isFullMatch
240
+ ? allSegments.map((s) => s.id)
241
+ : allSegments.filter((s) => s._handlerRan).map((s) => s.id);
242
+
243
+ const cleanedSegments = dedupedSegments.map((s) => {
244
+ if (s._handlerRan === undefined) return s;
245
+ const { _handlerRan: _drop, ...rest } = s;
246
+ return rest as ResolvedSegment;
187
247
  });
188
248
 
189
249
  return {
190
- segments: segmentsToRender,
191
- matched: allIds,
192
- diff: segmentsToRender.map((s) => s.id),
250
+ segments: cleanedSegments,
251
+ matched: matchedIds,
252
+ diff: cleanedSegments.map((s) => s.id),
253
+ resolvedIds,
193
254
  params: ctx.matched.params,
194
255
  routeName: ctx.routeKey,
195
256
  slots: Object.keys(state.slots).length > 0 ? state.slots : undefined,
@@ -198,12 +259,6 @@ export function buildMatchResult<TEnv>(
198
259
  };
199
260
  }
200
261
 
201
- /**
202
- * Collect segments from pipeline and build MatchResult
203
- *
204
- * This is the main entry point for building the final result after
205
- * the pipeline has processed all segments.
206
- */
207
262
  export async function collectMatchResult<TEnv>(
208
263
  pipeline: AsyncGenerator<ResolvedSegment>,
209
264
  ctx: MatchContext<TEnv>,
@@ -213,7 +268,6 @@ export async function collectMatchResult<TEnv>(
213
268
 
214
269
  const buildStart = performance.now();
215
270
 
216
- // Update state with collected segments if not already set
217
271
  if (state.segments.length === 0) {
218
272
  state.segments = allSegments;
219
273
  }
@@ -1,9 +1,3 @@
1
- /**
2
- * Router Metrics Utilities
3
- *
4
- * Performance metrics collection and reporting for RSC Router.
5
- */
6
-
7
1
  import type { MetricsStore, PerformanceMetric } from "../server/context";
8
2
 
9
3
  const BASE_INDENT = 2;
@@ -15,7 +9,11 @@ function formatMs(value: number): string {
15
9
  }
16
10
 
17
11
  function sortMetrics(metrics: PerformanceMetric[]): PerformanceMetric[] {
18
- return [...metrics].sort((a, b) => a.startTime - b.startTime);
12
+ return [...metrics].sort((a, b) => {
13
+ if (a.label === "handler:total") return 1;
14
+ if (b.label === "handler:total") return -1;
15
+ return a.startTime - b.startTime;
16
+ });
19
17
  }
20
18
 
21
19
  interface Span {
@@ -63,11 +61,6 @@ function createTimelineAxis(total: number): string {
63
61
  )}${totalLabel}`;
64
62
  }
65
63
 
66
- /**
67
- * Create a metrics store for the request if debugPerformance is enabled.
68
- * An optional `requestStart` timestamp can anchor the store to an earlier
69
- * point (e.g. handler start) so that handler:total has startTime=0.
70
- */
71
64
  export function createMetricsStore(
72
65
  debugPerformance: boolean,
73
66
  requestStart?: number,
@@ -80,9 +73,6 @@ export function createMetricsStore(
80
73
  };
81
74
  }
82
75
 
83
- /**
84
- * Append a metric to the request store using an absolute start timestamp.
85
- */
86
76
  export function appendMetric(
87
77
  metricsStore: MetricsStore | undefined,
88
78
  label: string,
@@ -99,9 +89,6 @@ export function appendMetric(
99
89
  });
100
90
  }
101
91
 
102
- /**
103
- * Log the current request metrics and return the corresponding Server-Timing value.
104
- */
105
92
  export function buildMetricsTiming(
106
93
  method: string,
107
94
  pathname: string,
@@ -112,7 +99,6 @@ export function buildMetricsTiming(
112
99
  return generateServerTiming(metricsStore) || undefined;
113
100
  }
114
101
 
115
- /** Display row produced by merging :pre/:post metric pairs. */
116
102
  interface DisplayRow {
117
103
  label: string;
118
104
  startTime: number;
@@ -121,12 +107,7 @@ interface DisplayRow {
121
107
  spans: Span[];
122
108
  }
123
109
 
124
- /**
125
- * Build display rows from sorted metrics, merging :pre/:post pairs into
126
- * a single row with disjoint timeline segments.
127
- */
128
110
  function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
129
- // Index :pre and :post metrics by their base label
130
111
  const preMap = new Map<string, PerformanceMetric>();
131
112
  const postMap = new Map<string, PerformanceMetric>();
132
113
  const consumed = new Set<PerformanceMetric>();
@@ -206,11 +187,6 @@ function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
206
187
  return rows;
207
188
  }
208
189
 
209
- /**
210
- * Log metrics to console in a formatted way.
211
- * Uses a shared-axis timeline so overlapping work stays visible.
212
- * Merges :pre/:post pairs onto one row with disjoint timeline segments.
213
- */
214
190
  export function logMetrics(
215
191
  method: string,
216
192
  pathname: string,
@@ -262,11 +238,6 @@ export function logMetrics(
262
238
  }
263
239
  }
264
240
 
265
- /**
266
- * Generate Server-Timing header value from metrics
267
- * Format: metric-name;dur=X.XX
268
- * Depth is encoded as a "d{N}-" prefix for nested metrics.
269
- */
270
241
  export function generateServerTiming(metricsStore: MetricsStore): string {
271
242
  return metricsStore.metrics
272
243
  .map((m) => {
@@ -1,10 +1,3 @@
1
- /**
2
- * Middleware Types
3
- *
4
- * Type definitions and interfaces for the middleware system.
5
- * Separated from execution logic for cleaner imports.
6
- */
7
-
8
1
  import type { ContextVar } from "../context-var.js";
9
2
  import type {
10
3
  DefaultReverseRouteMap,
@@ -14,26 +7,22 @@ import type {
14
7
  import type { ScopedReverseFunction } from "../reverse.js";
15
8
  import type { Theme } from "../theme/types.js";
16
9
  import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
10
+ import type { RequestScope } from "../types/request-scope.js";
17
11
 
18
- /**
19
- * Get variable function type
20
- */
21
12
  type GetVariableFn = {
22
13
  <T>(contextVar: ContextVar<T>): T | undefined;
23
14
  <K extends keyof DefaultVars>(key: K): DefaultVars[K];
24
15
  };
25
16
 
26
- /**
27
- * Set variable function type
28
- */
29
17
  type SetVariableFn = {
30
- <T>(contextVar: ContextVar<T>, value: T): void;
31
- <K extends keyof DefaultVars>(key: K, value: DefaultVars[K]): void;
18
+ <T>(contextVar: ContextVar<T>, value: T, options?: { cache?: boolean }): void;
19
+ <K extends keyof DefaultVars>(
20
+ key: K,
21
+ value: DefaultVars[K],
22
+ options?: { cache?: boolean },
23
+ ): void;
32
24
  };
33
25
 
34
- /**
35
- * Cookie options for setting cookies
36
- */
37
26
  export interface CookieOptions {
38
27
  domain?: string;
39
28
  path?: string;
@@ -44,178 +33,60 @@ export interface CookieOptions {
44
33
  sameSite?: "strict" | "lax" | "none";
45
34
  }
46
35
 
47
- /**
48
- * Context passed to middleware
49
- *
50
- * @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
51
- * @template TParams - URL params type (typed for route middleware, Record<string, string> for global middleware)
52
- */
53
36
  export interface MiddlewareContext<
54
37
  TEnv = any,
55
- TParams = Record<string, string>,
56
- > {
57
- /** Original request */
58
- request: Request;
59
-
60
- /** Parsed URL (with internal `_rsc*` params stripped) */
61
- url: URL;
62
-
63
- /**
64
- * The original request URL with all parameters intact, including
65
- * internal `_rsc*` transport params.
66
- */
67
- originalUrl: URL;
68
-
69
- /** URL pathname */
70
- pathname: string;
71
-
72
- /** URL search params */
73
- searchParams: URLSearchParams;
74
-
75
- /** Platform bindings (Cloudflare, etc.) */
76
- env: TEnv;
77
-
78
- /** URL params extracted from route/middleware pattern */
38
+ TParams = Record<string, string | undefined>,
39
+ > extends RequestScope<TEnv> {
79
40
  params: TParams;
80
41
 
81
- /**
82
- * Response headers.
83
- * Before `next()`, returns headers from the shared response stub.
84
- * After `next()`, returns headers from the downstream response.
85
- */
86
42
  readonly headers: Headers;
87
43
 
88
- /** Get a context variable (shared with route handlers) */
89
44
  get: GetVariableFn;
90
45
 
91
- /** Set a context variable (shared with route handlers) */
92
46
  set: SetVariableFn;
93
47
 
94
- /**
95
- * Middleware-injected variables.
96
- * Same shared dictionary as `ctx.get()`/`ctx.set()`.
97
- */
98
- var: DefaultVars;
99
-
100
- /**
101
- * Set a response header - can be called before or after `next()`.
102
- *
103
- * When called before `next()`, headers are queued and merged into the final response.
104
- * When called after `next()`, headers are set directly on the response.
105
- */
106
48
  header(name: string, value: string): void;
107
49
 
108
- /**
109
- * The matched route name, if available and the route has an explicit name.
110
- * Undefined for global middleware (runs before route matching) or unnamed routes.
111
- */
112
50
  routeName?: DefaultRouteName;
113
51
 
114
- /**
115
- * Enable performance metrics for this request.
116
- * When called, granular timing breakdown is logged to console and
117
- * included in the Server-Timing response header, regardless of the
118
- * router-level `debugPerformance` option.
119
- *
120
- * Call **before** `await next()` so the metrics store exists when
121
- * downstream phases (route matching, rendering, SSR) record their
122
- * spans. Calling after `next()` returns still emits `handler:total`
123
- * but misses all upstream metrics.
124
- */
125
52
  debugPerformance(): void;
126
53
 
127
- /**
128
- * Current theme (from cookie or default).
129
- * Only available when theme is enabled in router config.
130
- */
131
54
  theme?: Theme;
132
55
 
133
- /**
134
- * Set the theme (only available when theme is enabled in router config).
135
- * Sets a cookie with the new theme value.
136
- */
137
56
  setTheme?: (theme: Theme) => void;
138
57
 
139
- /**
140
- * Attach location state entries to this response.
141
- * State is delivered to the client via history.pushState and accessible
142
- * through the useLocationState() hook.
143
- */
144
58
  setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
145
59
 
146
- /**
147
- * Generate URLs from route names.
148
- * - `name` — global route, from the named-routes definition
149
- */
150
60
  reverse: ScopedReverseFunction<
151
61
  Record<string, string>,
152
62
  DefaultReverseRouteMap
153
63
  >;
154
64
  }
155
65
 
156
- /**
157
- * Middleware function signature
158
- *
159
- * @template TEnv - Environment type - defaults to any for internal flexibility
160
- * @template TParams - URL params type (typed for route middleware)
161
- *
162
- * When using middleware with global augmentation (RSCRouter.Env), explicitly
163
- * annotate your middleware functions, or the types will be inferred from context:
164
- *
165
- * @example
166
- * ```typescript
167
- * // With explicit annotation (recommended for reusable middleware)
168
- * const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
169
- *
170
- * // Types inferred from router.use() call
171
- * router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
172
- * ```
173
- */
174
- export type MiddlewareFn<TEnv = any, TParams = Record<string, string>> = (
66
+ export type MiddlewareFn<
67
+ TEnv = any,
68
+ TParams = Record<string, string | undefined>,
69
+ > = (
175
70
  ctx: MiddlewareContext<TEnv, TParams>,
176
71
  next: () => Promise<Response>,
177
72
  ) => Response | void | Promise<Response | void>;
178
73
 
179
- /**
180
- * Stored middleware entry with pattern matching info
181
- * @internal - uses any for internal flexibility
182
- */
183
74
  export interface MiddlewareEntry<TEnv = any> {
184
- /** Original pattern string */
185
75
  pattern: string | null;
186
-
187
- /** Compiled regex for matching */
188
76
  regex: RegExp | null;
189
-
190
- /** Param names extracted from pattern */
191
77
  paramNames: string[];
192
-
193
- /** The middleware function */
194
78
  handler: MiddlewareFn<TEnv>;
195
-
196
- /** Mount prefix this middleware is scoped to (null = global) */
197
- mountPrefix: string | null;
198
79
  }
199
80
 
200
- /**
201
- * Mutable response holder - tracks the current response through the middleware chain.
202
- */
203
81
  export interface ResponseHolder {
204
82
  response: Response | null;
205
83
  }
206
84
 
207
- /**
208
- * Entry type for middleware collection
209
- * Matches the shape of EntryData used in router.ts
210
- */
211
85
  export interface MiddlewareCollectableEntry {
212
86
  middleware?: MiddlewareFn<any, any>[];
213
87
  layout?: MiddlewareCollectableEntry[];
214
88
  }
215
89
 
216
- /**
217
- * Collected route middleware with params
218
- */
219
90
  export interface CollectedMiddleware {
220
91
  handler: MiddlewareFn<any, any>;
221
92
  params: Record<string, string>;