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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -1,15 +1,8 @@
1
1
  /// <reference types="vite/types/importMeta.d.ts" />
2
- /**
3
- * Middleware Execution
4
- *
5
- * True middleware that wraps the entire RSC handler.
6
- * - `await next()` returns actual Response
7
- * - Can modify response headers
8
- * - Can catch errors from RSC rendering
9
- * - Forgiving API: if middleware doesn't return, original response is used
10
- */
11
2
 
12
3
  import { contextGet, contextSet } from "../context-var.js";
4
+ import { escapeRegExp } from "../regex-escape.js";
5
+ import { parsePattern as parseRoutePattern } from "./pattern-matching.js";
13
6
  import { safeDecodeURIComponent } from "./url-params.js";
14
7
  import { fireAndForgetWaitUntil } from "../types/request-scope.js";
15
8
  import type {
@@ -21,28 +14,29 @@ import type {
21
14
  ResponseHolder,
22
15
  } from "./middleware-types.js";
23
16
  import { _getRequestContext } from "../server/request-context.js";
17
+ import {
18
+ EXTERNAL_REDIRECT_MARKER,
19
+ isExternalRedirect,
20
+ markExternalRedirect,
21
+ } from "../redirect-origin.js";
24
22
  import { isAutoGeneratedRouteName } from "../route-name.js";
25
23
  import { appendMetric, createMetricsStore } from "./metrics.js";
24
+ import { observePhase, PHASES } from "./instrument.js";
26
25
  import { stripInternalParams } from "./handler-context.js";
27
26
  import { isWebSocketUpgradeResponse } from "../response-utils.js";
28
27
 
29
- // Re-export types and cookie utilities for backward compatibility
28
+ // Re-export types consumed through this module's path.
30
29
  export type {
31
30
  CookieOptions,
32
- CollectedMiddleware,
33
- MiddlewareCollectableEntry,
34
31
  MiddlewareContext,
35
32
  MiddlewareEntry,
36
33
  MiddlewareFn,
37
- ResponseHolder,
38
34
  } from "./middleware-types.js";
39
- export { parseCookies, serializeCookie } from "./middleware-cookies.js";
40
35
 
41
36
  const MIDDLEWARE_METRIC_DEPTH = 1;
42
- /** Ignore post-next() durations below this threshold (measurement noise). */
43
37
  const POST_METRIC_MIN_DURATION_MS = 0.01;
44
38
 
45
- function getMiddlewareMetricBase<TEnv>(
39
+ function getMiddlewareMetricLabel<TEnv>(
46
40
  entry: MiddlewareEntry<TEnv>,
47
41
  ordinal: number,
48
42
  ): string {
@@ -50,24 +44,49 @@ function getMiddlewareMetricBase<TEnv>(
50
44
  const scope = entry.pattern ?? "*";
51
45
 
52
46
  if (handlerName) {
53
- return `${handlerName}@${scope}`;
47
+ return `middleware:${handlerName}@${scope}`;
54
48
  }
55
49
 
56
- return `${scope}#${ordinal + 1}`;
57
- }
58
-
59
- function getMiddlewareMetricLabel<TEnv>(
60
- entry: MiddlewareEntry<TEnv>,
61
- ordinal: number,
62
- ): string {
63
- return `middleware:${getMiddlewareMetricBase(entry, ordinal)}`;
50
+ return `middleware:${scope}#${ordinal + 1}`;
64
51
  }
65
52
 
66
53
  /**
67
- * Parse a route pattern into regex and param names
68
- * Supports: *, /path, /path/*, /path/:param, /path/:param/*
54
+ * Compile a middleware scope pattern to a regex + param names.
55
+ *
56
+ * Middleware scopes reuse the route pattern parser (`parsePattern` from
57
+ * pattern-matching.ts), so they support the same param forms as routes —
58
+ * optional (`:x?`), constrained (`:x(en|gb)`), and suffix (`:x.html`) — in
59
+ * addition to the trailing-`*` wildcard middleware relies on. Before this
60
+ * unification the middleware-side parser handled only static, bare `:param`,
61
+ * and trailing `*`, so e.g. `router.use("/:locale(en|gb)/*", mw)` silently
62
+ * named the param "locale(en|gb)" and never enforced the constraint.
63
+ *
64
+ * Middleware matching semantics deliberately differ from route matching, so we
65
+ * emit the regex here rather than route through `compilePattern`:
66
+ * - `*` alone matches every path (`/^.*$/`).
67
+ * - A trailing `*` segment is an OPTIONAL subtree match (`(?:/.*)?`): `/admin/*`
68
+ * matches `/admin`, `/admin/`, and `/admin/users`. It contributes no param
69
+ * name (unlike route wildcards, which capture `*`).
70
+ * - A NON-trailing `*` is also OPTIONAL (`(?:/.*)?`), matching zero-or-more
71
+ * intermediate segments: `/a/<star>/b` matches both `/a/b` and `/a/x/b`. This
72
+ * mirrors the pre-unification parser, which compiled every `*` part as
73
+ * optional regardless of position.
74
+ * - A pattern without a trailing `*` tolerates a trailing slash (`/?$`).
75
+ * - Constraints are baked into the regex as an alternation so `matchMiddleware`
76
+ * (a bare `regex.test`) enforces them without extra validation. Constraint
77
+ * values are matched against the raw (still URL-encoded) path segment, which
78
+ * matches the pre-unification middleware behavior (it never decoded for
79
+ * matching); the constraint string is regex-escaped so values like `en.gb`
80
+ * are treated literally.
81
+ *
82
+ * The route segment parser only recognizes `/`-prefixed segments, but the
83
+ * pre-unification middleware parser split on `/` and dropped empty parts, so a
84
+ * leading slash was irrelevant: `use("admin/*")` and `use("/admin/*")` scoped
85
+ * identically. Normalize a non-`*` pattern to have a leading slash before
86
+ * parsing so that behavior is preserved (without it, `parseRoutePattern("admin/*")`
87
+ * drops the static `admin` and the scope explodes to every path).
69
88
  */
70
- export function parsePattern(pattern: string): {
89
+ export function compileMiddlewarePattern(pattern: string): {
71
90
  regex: RegExp;
72
91
  paramNames: string[];
73
92
  } {
@@ -75,45 +94,56 @@ export function parsePattern(pattern: string): {
75
94
  return { regex: /^.*$/, paramNames: [] };
76
95
  }
77
96
 
97
+ const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
98
+ const segments = parseRoutePattern(normalizedPattern);
78
99
  const paramNames: string[] = [];
79
100
  let regexStr = "^";
80
-
81
- const parts = pattern.split("/").filter(Boolean);
82
-
83
- for (let i = 0; i < parts.length; i++) {
84
- const part = parts[i];
85
-
86
- if (part === "*") {
87
- // Wildcard - match rest of path
88
- regexStr += "(?:/.*)?";
89
- } else if (part.startsWith(":")) {
90
- // Param
91
- const paramName = part.slice(1);
92
- paramNames.push(paramName);
93
- regexStr += "/([^/]+)";
101
+ let hasTrailingWildcard = false;
102
+
103
+ for (let i = 0; i < segments.length; i++) {
104
+ const segment = segments[i];
105
+
106
+ if (segment.type === "wildcard") {
107
+ if (segment.value === "*") {
108
+ // Bare `*`: optional subtree match, no capture (parity with the original
109
+ // middleware parser). A trailing `*` matches the subtree; a non-trailing
110
+ // `*` matches zero-or-more intermediate segments, so `/a/<star>/b` still
111
+ // matches `/a/b`.
112
+ regexStr += "(?:/.*)?";
113
+ } else {
114
+ // Named catch-all `:name+` / `:name*`: capture the remainder under the
115
+ // name so a scoping middleware sees `ctx.params.<name>`, and respect the
116
+ // one-or-more arity (`+` must not match the bare prefix), mirroring the
117
+ // route matcher instead of collapsing to the bare-`*` subtree.
118
+ paramNames.push(segment.value);
119
+ regexStr += segment.oneOrMore ? "/(.+)" : "(?:/(.*))?";
120
+ }
121
+ if (i === segments.length - 1) {
122
+ hasTrailingWildcard = true;
123
+ }
124
+ } else if (segment.type === "param") {
125
+ paramNames.push(segment.value);
126
+ const suffixPattern = segment.suffix ? escapeRegExp(segment.suffix) : "";
127
+ const valuePattern = segment.constraint
128
+ ? `(${segment.constraint.map(escapeRegExp).join("|")})`
129
+ : "([^/]+)";
130
+ if (segment.optional) {
131
+ regexStr += `(?:/${valuePattern}${suffixPattern})?`;
132
+ } else {
133
+ regexStr += `/${valuePattern}${suffixPattern}`;
134
+ }
94
135
  } else {
95
- // Literal
96
- regexStr += "/" + escapeRegex(part);
136
+ // Static literal
137
+ regexStr += "/" + escapeRegExp(segment.value);
97
138
  }
98
139
  }
99
140
 
100
- // If pattern doesn't end with *, match exact or with trailing segments
101
- if (!pattern.endsWith("*")) {
102
- regexStr += "/?$";
103
- } else {
104
- regexStr += "$";
105
- }
141
+ // Without a trailing `*`, match exactly with an optional trailing slash.
142
+ regexStr += hasTrailingWildcard ? "$" : "/?$";
106
143
 
107
144
  return { regex: new RegExp(regexStr), paramNames };
108
145
  }
109
146
 
110
- /**
111
- * Escape special regex characters
112
- */
113
- function escapeRegex(str: string): string {
114
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
115
- }
116
-
117
147
  /**
118
148
  * Extract params from a pathname using a pattern's regex and param names.
119
149
  *
@@ -156,7 +186,15 @@ export function createMiddlewareContext<TEnv>(
156
186
  search?: Record<string, unknown>,
157
187
  ) => string,
158
188
  ): MiddlewareContext<TEnv> {
159
- const url = stripInternalParams(new URL(request.url));
189
+ // Reuse the request context's clean URL when available. Each middleware still
190
+ // gets its OWN URL clone (a middleware may mutate ctx.url / ctx.searchParams),
191
+ // but cloning the already-clean URL is cheaper than re-parsing + re-stripping
192
+ // request.url per middleware. Fall back to parsing when no ALS context.
193
+ const reqCtx = _getRequestContext();
194
+ const ctxUrl = reqCtx?.url;
195
+ const url = ctxUrl
196
+ ? new URL(ctxUrl)
197
+ : stripInternalParams(new URL(request.url));
160
198
 
161
199
  // Track the initial response to detect pre/post-next() phase.
162
200
  // Before next(): responseHolder.response === initialResponse (the stub).
@@ -187,12 +225,11 @@ export function createMiddlewareContext<TEnv>(
187
225
  return responseHolder.response;
188
226
  };
189
227
 
190
- // Capture reqCtx once: the request-scoped platform fields
228
+ // reqCtx captured once above: the request-scoped platform fields
191
229
  // (originalUrl, executionContext, waitUntil) are immutable per request,
192
230
  // so snapshotting beats re-reading ALS on every access. The lazy getters
193
231
  // below (routeName, theme, setTheme) stay lazy because those can change
194
232
  // during `await next()`.
195
- const reqCtx = _getRequestContext();
196
233
  return {
197
234
  request,
198
235
  url,
@@ -261,9 +298,13 @@ export function createMiddlewareContext<TEnv>(
261
298
 
262
299
  reverse:
263
300
  reverse ??
264
- ((name: string) => {
301
+ ((
302
+ name: string,
303
+ _params?: Record<string, string>,
304
+ _search?: Record<string, unknown>,
305
+ ) => {
265
306
  throw new Error(
266
- `ctx.reverse() is not available - route map was not provided to middleware context`,
307
+ `ctx.reverse(${JSON.stringify(name)}) is not available: no route map is bound to this middleware context.`,
267
308
  );
268
309
  }),
269
310
 
@@ -271,7 +312,10 @@ export function createMiddlewareContext<TEnv>(
271
312
  const reqCtx = _getRequestContext();
272
313
  if (reqCtx) {
273
314
  reqCtx._debugPerformance = true;
274
- reqCtx._metricsStore ??= createMetricsStore(true);
315
+ // Anchor to the true request entry (reqCtx._handlerStart) so phases that
316
+ // began before this opt-in report non-negative offsets; undefined falls
317
+ // back to performance.now() in createMetricsStore.
318
+ reqCtx._metricsStore ??= createMetricsStore(true, reqCtx._handlerStart);
275
319
  }
276
320
  },
277
321
  };
@@ -297,9 +341,16 @@ export function matchMiddleware<TEnv>(
297
341
  continue;
298
342
  }
299
343
 
300
- // Check if pathname matches
301
- if (entry.regex.test(pathname)) {
302
- const params = extractParams(pathname, entry.regex, entry.paramNames);
344
+ // Run the scope regex ONCE per entry. The old code ran test() then, on a
345
+ // hit, extractParams' match() — a second full pass over the same string for
346
+ // every matching entry on the per-request hot path. The regexes carry no
347
+ // `g` flag, so there is no lastIndex statefulness across this single match.
348
+ const m = pathname.match(entry.regex);
349
+ if (m) {
350
+ const params: Record<string, string> = {};
351
+ for (let i = 0; i < entry.paramNames.length; i++) {
352
+ params[entry.paramNames[i]] = safeDecodeURIComponent(m[i + 1] || "");
353
+ }
303
354
  matches.push({ entry, params });
304
355
  }
305
356
  }
@@ -316,6 +367,10 @@ function mergeStubHeaders(
316
367
  stubOverridesNonCookie: boolean,
317
368
  ): void {
318
369
  stub.forEach((value, name) => {
370
+ // The reserved external-redirect marker is internal and never a trust
371
+ // signal; never copy a stub value (e.g. a stray ctx.header() call) onto a
372
+ // browser-facing response. The opt-in is the out-of-band brand.
373
+ if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
319
374
  if (name.toLowerCase() === "set-cookie") {
320
375
  target.append(name, value);
321
376
  } else if (stubOverridesNonCookie || !target.has(name)) {
@@ -341,12 +396,50 @@ function mergeReqCtxStub(
341
396
  }
342
397
  }
343
398
  reqCtx.res.headers.forEach((value, name) => {
399
+ // Never propagate the reserved external-redirect marker (see mergeStubHeaders).
400
+ if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
344
401
  if (name !== "set-cookie" && !target.has(name)) {
345
402
  target.set(name, value);
346
403
  }
347
404
  });
348
405
  }
349
406
 
407
+ // Clone `base` with stub headers merged into a fresh Headers (the clone keeps
408
+ // the body mutable for post-next() modifications). Set-Cookie is always
409
+ // appended; other headers obey stubOverridesNonCookie (see mergeStubHeaders).
410
+ // mergeReqCtx folds in RequestContext stub cookies/headers; the intercept
411
+ // short-circuit path passes false (its reqCtx headers are not merged here),
412
+ // which is the one deliberate divergence between the call sites.
413
+ function mergeResponse(
414
+ base: Response,
415
+ stub: Headers,
416
+ opts: { stubOverridesNonCookie: boolean; mergeReqCtx: boolean },
417
+ ): Response {
418
+ const mergedHeaders = new Headers(base.headers);
419
+ // The reserved external-redirect marker is never a trust signal and must never
420
+ // reach the browser. The guard strips it on 3xx redirects; strip it here too so
421
+ // a forged value cannot ride a non-3xx middleware response (which the 3xx-only
422
+ // guard would not touch) to the client. The opt-in is the out-of-band brand.
423
+ mergedHeaders.delete(EXTERNAL_REDIRECT_MARKER);
424
+ mergeStubHeaders(mergedHeaders, stub, opts.stubOverridesNonCookie);
425
+ if (opts.mergeReqCtx) {
426
+ mergeReqCtxStub(mergedHeaders, _getRequestContext());
427
+ }
428
+ const merged = new Response(base.body, {
429
+ status: base.status,
430
+ statusText: base.statusText,
431
+ headers: mergedHeaders,
432
+ });
433
+ // Transfer the out-of-band external-redirect brand across this rebuild: a
434
+ // middleware short-circuit `return redirect(url, { external: true })` reaches
435
+ // the open-redirect guard only after this merge, and the brand lives on the
436
+ // Response object, not in its headers.
437
+ if (isExternalRedirect(base)) {
438
+ markExternalRedirect(merged);
439
+ }
440
+ return merged;
441
+ }
442
+
350
443
  /**
351
444
  * Execute middleware chain
352
445
  *
@@ -355,7 +448,7 @@ function mergeReqCtxStub(
355
448
  * - `ctx.headers` available before and after `await next()`
356
449
  * - `ctx.header()` shorthand for setting a single header
357
450
  * - Forgiving: if middleware doesn't return, uses the downstream response
358
- * - Short-circuit: return Response to stop chain
451
+ * - Short-circuit: return OR throw a Response to stop chain
359
452
  * - Error catching: try/catch around `next()` works
360
453
  */
361
454
  export async function executeMiddleware<TEnv>(
@@ -385,20 +478,16 @@ export async function executeMiddleware<TEnv>(
385
478
  // End of chain - call actual RSC handler
386
479
  const response = await finalHandler();
387
480
 
388
- const mergedHeaders = new Headers(response.headers);
389
- mergeStubHeaders(mergedHeaders, stubResponse.headers, true);
390
- mergeReqCtxStub(mergedHeaders, _getRequestContext());
391
-
392
481
  if (isWebSocketUpgradeResponse(response)) {
393
482
  responseHolder.response = response;
394
483
  return response;
395
484
  }
396
485
 
397
- // Clone response with merged headers (mutable for post-next() modifications)
398
- responseHolder.response = new Response(response.body, {
399
- status: response.status,
400
- statusText: response.statusText,
401
- headers: mergedHeaders,
486
+ // Chain ran to completion: stub headers overwrite (stubOverridesNonCookie)
487
+ // and reqCtx stub headers are merged in.
488
+ responseHolder.response = mergeResponse(response, stubResponse.headers, {
489
+ stubOverridesNonCookie: true,
490
+ mergeReqCtx: true,
402
491
  });
403
492
 
404
493
  return responseHolder.response;
@@ -456,22 +545,36 @@ export async function executeMiddleware<TEnv>(
456
545
  return nextPromise;
457
546
  };
458
547
 
548
+ // Wrap the middleware (including its downstream next() chain) in its span
549
+ // via the unified phase API. metric:false — the middleware's perf metric is
550
+ // its exclusive pre/post own-time, recorded directly above and below, finer
551
+ // than a single wrap. Spans nest by async context, so this onions
552
+ // middleware-over-middleware and the core handler underneath. Pass-through
553
+ // when neither surface is active.
459
554
  let result: Response | void;
460
555
  try {
461
- result = await entry.handler(ctx, wrappedNext);
462
- } catch (error) {
463
- // Thrown Response is short-circuit control flow, not an error.
464
- // Fall through to the `if (result instanceof Response)` branch below
465
- // so stub headers and request-context cookies merge as they do for
466
- // an explicit `return new Response(...)`. Real errors propagate.
467
- if (error instanceof Response) {
468
- result = error;
469
- } else {
470
- finishMiddleware();
471
- throw error;
472
- }
556
+ result = await observePhase(PHASES.middleware(metricLabel), async () => {
557
+ try {
558
+ return await entry.handler(ctx, wrappedNext);
559
+ } catch (error) {
560
+ // Thrown Response is short-circuit control flow, not an error
561
+ // absorb it INSIDE the span so the tracing runner settles the
562
+ // rango.middleware span as success, not STATUS_ERROR (every auth
563
+ // redirect would otherwise inflate trace error rates). Returning it
564
+ // routes through the `if (result instanceof Response)` branch below,
565
+ // so stub headers and request-context cookies merge identically to an
566
+ // explicit `return new Response(...)`. Segment handlers already follow
567
+ // this convention (segment-resolution/helpers.ts keeps result handling
568
+ // outside the span). Real errors propagate past the span.
569
+ if (error instanceof Response) return error;
570
+ throw error;
571
+ }
572
+ });
573
+ } finally {
574
+ // Settle the middleware own-time metric once on both the success and
575
+ // error paths (idempotent guard in finishMiddleware).
576
+ finishMiddleware();
473
577
  }
474
- finishMiddleware();
475
578
 
476
579
  // Record post-next() processing time when middleware did work after
477
580
  // the downstream chain resolved (e.g. adding headers, logging).
@@ -490,20 +593,18 @@ export async function executeMiddleware<TEnv>(
490
593
 
491
594
  // Explicit return takes precedence (middleware short-circuit).
492
595
  // Merge stub headers (from ctx.header before this point) and
493
- // RequestContext stub headers (from ctx.setCookie) into the
596
+ // RequestContext stub headers (from cookies().set()) into the
494
597
  // returned Response so they are not lost.
495
598
  if (result instanceof Response) {
496
599
  if (isWebSocketUpgradeResponse(result)) {
497
600
  responseHolder.response = result;
498
601
  return result;
499
602
  }
500
- const mergedHeaders = new Headers(result.headers);
501
- mergeStubHeaders(mergedHeaders, stubResponse.headers, false);
502
- mergeReqCtxStub(mergedHeaders, _getRequestContext());
503
- const merged = new Response(result.body, {
504
- status: result.status,
505
- statusText: result.statusText,
506
- headers: mergedHeaders,
603
+ // Explicit short-circuit: the returned Response's own headers win
604
+ // (stubOverridesNonCookie=false); reqCtx stub headers still merge in.
605
+ const merged = mergeResponse(result, stubResponse.headers, {
606
+ stubOverridesNonCookie: false,
607
+ mergeReqCtx: true,
507
608
  });
508
609
  responseHolder.response = merged;
509
610
  return merged;
@@ -564,7 +665,7 @@ export async function executeMiddleware<TEnv>(
564
665
  *
565
666
  * Intercepts use a shared stubResponse from the request context. This function:
566
667
  * - Runs middleware in sequence with a simple next() chain
567
- * - Returns Response if any middleware short-circuits (returns Response or redirects BEFORE next())
668
+ * - Returns Response if any middleware short-circuits (returns OR throws a Response, or redirects, BEFORE next())
568
669
  * - Returns null if all middleware calls next() - headers set after next() remain on stubResponse
569
670
  *
570
671
  * @param middlewares - Array of middleware functions
@@ -602,6 +703,7 @@ export async function executeInterceptMiddleware<TEnv>(
602
703
  return stubResponse;
603
704
  }
604
705
 
706
+ const ordinal = index;
605
707
  const middleware = middlewares[index++];
606
708
  const ctx = createMiddlewareContext(
607
709
  request,
@@ -623,18 +725,30 @@ export async function executeInterceptMiddleware<TEnv>(
623
725
  return next();
624
726
  };
625
727
 
626
- let result: Response | void;
627
- try {
628
- result = await middleware(ctx, guardedNext);
629
- } catch (error) {
630
- // Thrown Response is short-circuit control flow, parity with the
631
- // explicit-return path below. Real errors propagate.
632
- if (error instanceof Response) {
633
- result = error;
634
- } else {
635
- throw error;
636
- }
637
- }
728
+ // Span-wrap each intercept middleware as rango.middleware (metric:false —
729
+ // intercept runs inside the render phase already metered by render:total, so
730
+ // it contributes a span but no separate perf metric). Bare MiddlewareFns
731
+ // have no pattern, so the label is scoped to "*" like a pattern-less entry.
732
+ const label = getMiddlewareMetricLabel(
733
+ { handler: middleware, pattern: null } as MiddlewareEntry<TEnv>,
734
+ ordinal,
735
+ );
736
+
737
+ const result: Response | void = await observePhase(
738
+ PHASES.middleware(label),
739
+ async () => {
740
+ try {
741
+ return await middleware(ctx, guardedNext);
742
+ } catch (error) {
743
+ // Thrown Response is short-circuit control flow, parity with the
744
+ // explicit-return path below. Absorb it INSIDE the span so the tracing
745
+ // runner settles rango.middleware as success, not STATUS_ERROR (same
746
+ // reasoning as executeMiddleware's main chain). Real errors propagate.
747
+ if (error instanceof Response) return error;
748
+ throw error;
749
+ }
750
+ },
751
+ );
638
752
 
639
753
  if (result instanceof Response) {
640
754
  earlyResponse = result;
@@ -658,15 +772,13 @@ export async function executeInterceptMiddleware<TEnv>(
658
772
  });
659
773
 
660
774
  if (hasStubHeaders) {
661
- // Clone and merge headers from stub into early response.
662
- // Only fill in missing headers — the returned Response's explicit
663
- // headers take precedence, matching executeMiddleware behavior.
664
- const mergedHeaders = new Headers(response.headers);
665
- mergeStubHeaders(mergedHeaders, stubResponse.headers, false);
666
- return new Response(response.body, {
667
- status: response.status,
668
- statusText: response.statusText,
669
- headers: mergedHeaders,
775
+ // Only fill in missing headers the returned Response's explicit headers
776
+ // take precedence (stubOverridesNonCookie=false), matching executeMiddleware.
777
+ // mergeReqCtx=false: the intercept path deliberately does NOT merge reqCtx
778
+ // stub headers here (pinned by intercept-middleware-headers.test.ts).
779
+ return mergeResponse(response, stubResponse.headers, {
780
+ stubOverridesNonCookie: false,
781
+ mergeReqCtx: false,
670
782
  });
671
783
  }
672
784
  return response;
@@ -707,7 +819,6 @@ export async function executeLoaderMiddleware<TEnv>(
707
819
  regex: null,
708
820
  paramNames: [],
709
821
  handler,
710
- mountPrefix: null,
711
822
  } as MiddlewareEntry<TEnv>,
712
823
  params,
713
824
  }));