@rangojs/router 0.0.0-experimental.19 → 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 (406) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +291 -61
  3. package/dist/bin/rango.js +544 -143
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3744 -1329
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +67 -13
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +312 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +247 -23
  12. package/skills/caching/SKILL.md +322 -19
  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 +282 -60
  19. package/skills/host-router/SKILL.md +278 -0
  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 +297 -31
  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 +155 -111
  32. package/skills/rango/SKILL.md +251 -23
  33. package/skills/react-compiler/SKILL.md +168 -0
  34. package/skills/response-routes/SKILL.md +123 -48
  35. package/skills/route/SKILL.md +104 -9
  36. package/skills/router-setup/SKILL.md +124 -11
  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 +125 -222
  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 +357 -52
  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 +67 -40
  62. package/src/bin/rango.ts +18 -0
  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 +197 -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/link-interceptor.ts +4 -0
  74. package/src/browser/navigation-bridge.ts +200 -30
  75. package/src/browser/navigation-client.ts +217 -58
  76. package/src/browser/navigation-store-handle.ts +38 -0
  77. package/src/browser/navigation-store.ts +76 -67
  78. package/src/browser/navigation-transaction.ts +18 -66
  79. package/src/browser/network-error-handler.ts +34 -7
  80. package/src/browser/partial-update.ts +187 -112
  81. package/src/browser/prefetch/cache.ts +312 -30
  82. package/src/browser/prefetch/fetch.ts +344 -47
  83. package/src/browser/prefetch/policy.ts +6 -0
  84. package/src/browser/prefetch/queue.ts +126 -20
  85. package/src/browser/prefetch/resource-ready.ts +77 -0
  86. package/src/browser/rango-state.ts +158 -76
  87. package/src/browser/react/Link.tsx +125 -18
  88. package/src/browser/react/NavigationProvider.tsx +135 -120
  89. package/src/browser/react/ScrollRestoration.tsx +10 -6
  90. package/src/browser/react/context.ts +7 -2
  91. package/src/browser/react/filter-segment-order.ts +66 -7
  92. package/src/browser/react/index.ts +0 -48
  93. package/src/browser/react/location-state-shared.ts +178 -8
  94. package/src/browser/react/location-state.ts +39 -14
  95. package/src/browser/react/use-action.ts +6 -15
  96. package/src/browser/react/use-handle.ts +23 -69
  97. package/src/browser/react/use-href.tsx +8 -1
  98. package/src/browser/react/use-link-status.ts +33 -8
  99. package/src/browser/react/use-navigation.ts +32 -7
  100. package/src/browser/react/use-params.ts +20 -10
  101. package/src/browser/react/use-reverse.ts +106 -0
  102. package/src/browser/react/use-router.ts +46 -11
  103. package/src/browser/react/use-search-params.ts +0 -5
  104. package/src/browser/react/use-segments.ts +11 -21
  105. package/src/browser/response-adapter.ts +80 -5
  106. package/src/browser/rsc-router.tsx +226 -75
  107. package/src/browser/scroll-restoration.ts +54 -42
  108. package/src/browser/segment-reconciler.ts +36 -9
  109. package/src/browser/segment-structure-assert.ts +2 -2
  110. package/src/browser/server-action-bridge.ts +619 -442
  111. package/src/browser/types.ts +115 -11
  112. package/src/browser/validate-redirect-origin.ts +43 -16
  113. package/src/build/collect-fallback-refs.ts +107 -0
  114. package/src/build/generate-manifest.ts +65 -40
  115. package/src/build/generate-route-types.ts +7 -1
  116. package/src/build/index.ts +8 -2
  117. package/src/build/prefix-tree-utils.ts +123 -0
  118. package/src/build/route-trie.ts +182 -37
  119. package/src/build/route-types/ast-route-extraction.ts +15 -8
  120. package/src/build/route-types/codegen.ts +16 -5
  121. package/src/build/route-types/include-resolution.ts +125 -24
  122. package/src/build/route-types/param-extraction.ts +6 -3
  123. package/src/build/route-types/per-module-writer.ts +22 -6
  124. package/src/build/route-types/router-processing.ts +392 -106
  125. package/src/build/route-types/scan-filter.ts +9 -2
  126. package/src/build/route-types/source-scan.ts +216 -0
  127. package/src/build/runtime-discovery.ts +9 -20
  128. package/src/cache/cache-error.ts +104 -0
  129. package/src/cache/cache-key-utils.ts +29 -13
  130. package/src/cache/cache-policy.ts +108 -34
  131. package/src/cache/cache-runtime.ts +214 -48
  132. package/src/cache/cache-scope.ts +236 -89
  133. package/src/cache/cache-tag.ts +103 -0
  134. package/src/cache/cf/cf-base64.ts +33 -0
  135. package/src/cache/cf/cf-cache-constants.ts +127 -0
  136. package/src/cache/cf/cf-cache-store.ts +2224 -171
  137. package/src/cache/cf/cf-cache-types.ts +349 -0
  138. package/src/cache/cf/cf-kv-utils.ts +46 -0
  139. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  140. package/src/cache/cf/index.ts +11 -17
  141. package/src/cache/document-cache.ts +89 -27
  142. package/src/cache/handle-snapshot.ts +70 -0
  143. package/src/cache/index.ts +11 -20
  144. package/src/cache/memory-segment-store.ts +136 -37
  145. package/src/cache/profile-registry.ts +31 -31
  146. package/src/cache/read-through-swr.ts +41 -11
  147. package/src/cache/segment-codec.ts +9 -17
  148. package/src/cache/tag-invalidation.ts +230 -0
  149. package/src/cache/taint.ts +55 -0
  150. package/src/cache/types.ts +37 -100
  151. package/src/client.rsc.tsx +45 -21
  152. package/src/client.tsx +120 -336
  153. package/src/cloudflare/index.ts +11 -0
  154. package/src/cloudflare/tracing.ts +109 -0
  155. package/src/component-utils.ts +19 -0
  156. package/src/components/DefaultDocument.tsx +8 -2
  157. package/src/context-var.ts +84 -2
  158. package/src/debug.ts +2 -2
  159. package/src/decode-loader-results.ts +52 -0
  160. package/src/defer.ts +196 -0
  161. package/src/deps/ssr.ts +0 -1
  162. package/src/encode-kv.ts +49 -0
  163. package/src/errors.ts +30 -4
  164. package/src/escape-script.ts +52 -0
  165. package/src/handle.ts +70 -22
  166. package/src/handles/MetaTags.tsx +56 -19
  167. package/src/handles/Scripts.tsx +183 -0
  168. package/src/handles/breadcrumbs.ts +95 -0
  169. package/src/handles/is-thenable.ts +19 -0
  170. package/src/handles/meta.ts +51 -40
  171. package/src/handles/script.ts +244 -0
  172. package/src/host/cookie-handler.ts +9 -60
  173. package/src/host/errors.ts +0 -24
  174. package/src/host/index.ts +8 -5
  175. package/src/host/pattern-matcher.ts +23 -52
  176. package/src/host/router.ts +107 -99
  177. package/src/host/testing.ts +40 -27
  178. package/src/host/types.ts +37 -4
  179. package/src/host/utils.ts +1 -1
  180. package/src/href-client.ts +137 -22
  181. package/src/index.rsc.ts +79 -29
  182. package/src/index.ts +149 -65
  183. package/src/internal-debug.ts +11 -10
  184. package/src/loader-store.ts +500 -0
  185. package/src/loader.rsc.ts +20 -13
  186. package/src/loader.ts +12 -11
  187. package/src/missing-id-error.ts +68 -0
  188. package/src/outlet-context.ts +1 -1
  189. package/src/outlet-provider.tsx +1 -5
  190. package/src/prerender/param-hash.ts +16 -16
  191. package/src/prerender/store.ts +63 -26
  192. package/src/prerender.ts +198 -82
  193. package/src/redirect-origin.ts +100 -0
  194. package/src/regex-escape.ts +8 -0
  195. package/src/render-error-thrower.tsx +20 -0
  196. package/src/response-utils.ts +62 -0
  197. package/src/reverse.ts +65 -15
  198. package/src/root-error-boundary.tsx +1 -19
  199. package/src/route-content-wrapper.tsx +7 -72
  200. package/src/route-definition/dsl-helpers.ts +469 -276
  201. package/src/route-definition/helper-factories.ts +29 -139
  202. package/src/route-definition/helpers-types.ts +113 -37
  203. package/src/route-definition/index.ts +3 -3
  204. package/src/route-definition/redirect.ts +53 -12
  205. package/src/route-definition/resolve-handler-use.ts +161 -0
  206. package/src/route-definition/use-item-types.ts +32 -0
  207. package/src/route-map-builder.ts +7 -17
  208. package/src/route-types.ts +37 -41
  209. package/src/router/basename.ts +14 -0
  210. package/src/router/content-negotiation.ts +164 -17
  211. package/src/router/error-handling.ts +45 -18
  212. package/src/router/find-match.ts +45 -22
  213. package/src/router/handler-context.ts +110 -39
  214. package/src/router/instrument.ts +350 -0
  215. package/src/router/intercept-resolution.ts +50 -24
  216. package/src/router/lazy-includes.ts +19 -53
  217. package/src/router/loader-resolution.ts +274 -56
  218. package/src/router/logging.ts +5 -8
  219. package/src/router/manifest.ts +49 -45
  220. package/src/router/match-api.ts +121 -205
  221. package/src/router/match-context.ts +0 -22
  222. package/src/router/match-handlers.ts +58 -58
  223. package/src/router/match-middleware/background-revalidation.ts +33 -6
  224. package/src/router/match-middleware/cache-lookup.ts +214 -263
  225. package/src/router/match-middleware/cache-store.ts +73 -33
  226. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  227. package/src/router/match-middleware/segment-resolution.ts +52 -18
  228. package/src/router/match-pipelines.ts +1 -42
  229. package/src/router/match-result.ts +104 -49
  230. package/src/router/metrics.ts +217 -26
  231. package/src/router/middleware-types.ts +24 -110
  232. package/src/router/middleware.ts +384 -197
  233. package/src/router/navigation-snapshot.ts +131 -0
  234. package/src/router/params-util.ts +23 -0
  235. package/src/router/pattern-matching.ts +148 -91
  236. package/src/router/prefetch-cache-ttl.ts +51 -0
  237. package/src/router/prerender-match.ts +199 -56
  238. package/src/router/preview-match.ts +32 -102
  239. package/src/router/request-classification.ts +276 -0
  240. package/src/router/revalidation.ts +144 -74
  241. package/src/router/route-snapshot.ts +244 -0
  242. package/src/router/router-context.ts +8 -28
  243. package/src/router/router-interfaces.ts +129 -36
  244. package/src/router/router-options.ts +185 -23
  245. package/src/router/router-registry.ts +2 -5
  246. package/src/router/segment-resolution/fresh.ts +281 -76
  247. package/src/router/segment-resolution/helpers.ts +116 -31
  248. package/src/router/segment-resolution/loader-cache.ts +63 -37
  249. package/src/router/segment-resolution/revalidation.ts +493 -391
  250. package/src/router/segment-resolution/static-store.ts +19 -5
  251. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  252. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  253. package/src/router/segment-resolution.ts +5 -1
  254. package/src/router/segment-wrappers.ts +8 -5
  255. package/src/router/state-cookie-name.ts +33 -0
  256. package/src/router/substitute-pattern-params.ts +56 -0
  257. package/src/router/telemetry-otel.ts +161 -199
  258. package/src/router/telemetry.ts +96 -19
  259. package/src/router/timeout.ts +0 -20
  260. package/src/router/tracing.ts +206 -0
  261. package/src/router/trie-matching.ts +180 -58
  262. package/src/router/types.ts +10 -63
  263. package/src/router/url-params.ts +44 -0
  264. package/src/router.ts +182 -54
  265. package/src/rsc/handler-context.ts +3 -2
  266. package/src/rsc/handler.ts +702 -460
  267. package/src/rsc/helpers.ts +168 -46
  268. package/src/rsc/index.ts +2 -25
  269. package/src/rsc/json-route-result.ts +38 -0
  270. package/src/rsc/loader-fetch.ts +127 -31
  271. package/src/rsc/manifest-init.ts +33 -42
  272. package/src/rsc/origin-guard.ts +39 -25
  273. package/src/rsc/progressive-enhancement.ts +98 -19
  274. package/src/rsc/redirect-guard.ts +99 -0
  275. package/src/rsc/response-cache-serve.ts +238 -0
  276. package/src/rsc/response-error.ts +79 -12
  277. package/src/rsc/response-route-handler.ts +99 -189
  278. package/src/rsc/rsc-rendering.ts +126 -106
  279. package/src/rsc/runtime-warnings.ts +23 -10
  280. package/src/rsc/server-action.ts +269 -114
  281. package/src/rsc/ssr-setup.ts +144 -0
  282. package/src/rsc/types.ts +34 -6
  283. package/src/runtime-env.ts +18 -0
  284. package/src/search-params.ts +49 -41
  285. package/src/segment-content-promise.ts +67 -0
  286. package/src/segment-loader-promise.ts +149 -0
  287. package/src/segment-system.tsx +281 -129
  288. package/src/serialize.ts +243 -0
  289. package/src/server/context.ts +317 -63
  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 +26 -46
  294. package/src/server/request-context.ts +425 -177
  295. package/src/server.ts +6 -0
  296. package/src/ssr/index.tsx +25 -16
  297. package/src/static-handler.ts +27 -18
  298. package/src/testing/cache-status.ts +162 -0
  299. package/src/testing/collect-handle.ts +40 -0
  300. package/src/testing/dispatch.ts +701 -0
  301. package/src/testing/dom.entry.ts +22 -0
  302. package/src/testing/e2e/fixture.ts +188 -0
  303. package/src/testing/e2e/index.ts +128 -0
  304. package/src/testing/e2e/matchers.ts +35 -0
  305. package/src/testing/e2e/page-helpers.ts +272 -0
  306. package/src/testing/e2e/parity.ts +387 -0
  307. package/src/testing/e2e/server.ts +195 -0
  308. package/src/testing/flight-matchers.ts +97 -0
  309. package/src/testing/flight-normalize.ts +11 -0
  310. package/src/testing/flight-runtime.d.ts +57 -0
  311. package/src/testing/flight-tree.ts +682 -0
  312. package/src/testing/flight.entry.ts +52 -0
  313. package/src/testing/flight.ts +257 -0
  314. package/src/testing/generated-routes.ts +183 -0
  315. package/src/testing/index.ts +99 -0
  316. package/src/testing/internal/context.ts +371 -0
  317. package/src/testing/internal/flight-client-globals.ts +30 -0
  318. package/src/testing/internal/seed-vars.ts +54 -0
  319. package/src/testing/render-handler.ts +343 -0
  320. package/src/testing/render-route.tsx +581 -0
  321. package/src/testing/run-loader.ts +385 -0
  322. package/src/testing/run-middleware.ts +205 -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 +3 -19
  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 +236 -88
  340. package/src/types/index.ts +1 -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 +10 -45
  344. package/src/types/route-entry.ts +19 -7
  345. package/src/types/segments.ts +37 -19
  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 +58 -11
  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 -18
  354. package/src/use-loader.tsx +346 -89
  355. package/src/vite/debug.ts +185 -0
  356. package/src/vite/discovery/bundle-postprocess.ts +64 -91
  357. package/src/vite/discovery/discover-routers.ts +147 -88
  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 +247 -145
  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 +61 -13
  364. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  365. package/src/vite/index.ts +10 -3
  366. package/src/vite/inject-client-debug.ts +36 -0
  367. package/src/vite/plugin-types.ts +155 -65
  368. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  369. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  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 +49 -98
  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 +127 -0
  383. package/src/vite/plugins/use-cache-transform.ts +73 -83
  384. package/src/vite/plugins/version-injector.ts +21 -25
  385. package/src/vite/plugins/version-plugin.ts +46 -37
  386. package/src/vite/plugins/virtual-entries.ts +13 -18
  387. package/src/vite/rango.ts +241 -287
  388. package/src/vite/router-discovery.ts +956 -149
  389. package/src/vite/utils/ast-handler-extract.ts +26 -35
  390. package/src/vite/utils/banner.ts +4 -4
  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 +141 -34
  398. package/src/vite/utils/shared-utils.ts +92 -42
  399. package/CLAUDE.md +0 -5
  400. package/src/browser/action-response-classifier.ts +0 -99
  401. package/src/browser/react/use-client-cache.ts +0 -58
  402. package/src/browser/shallow.ts +0 -40
  403. package/src/handles/index.ts +0 -6
  404. package/src/network-error-thrower.tsx +0 -23
  405. package/src/route-definition/route-function.ts +0 -119
  406. 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
@@ -108,12 +109,9 @@
108
109
  */
109
110
  import type { MatchResult, ResolvedSegment } from "../types.js";
110
111
  import type { MatchContext, MatchPipelineState } from "./match-context.js";
111
- import { generateServerTiming, logMetrics } from "./metrics.js";
112
112
  import { debugLog } from "./logging.js";
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,63 +213,52 @@ 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),
187
- });
231
+ const { segments: dedupedSegments, removedIds } = deduplicateLoaderSegments(
232
+ segmentsToRender,
233
+ logPrefix,
234
+ );
188
235
 
189
- // Output metrics if enabled
190
- let serverTiming: string | undefined;
191
- if (ctx.metricsStore) {
192
- logMetrics(ctx.request.method, ctx.pathname, ctx.metricsStore);
193
- serverTiming = generateServerTiming(ctx.metricsStore);
194
- }
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;
247
+ });
195
248
 
196
249
  return {
197
- segments: segmentsToRender,
198
- matched: allIds,
199
- diff: segmentsToRender.map((s) => s.id),
250
+ segments: cleanedSegments,
251
+ matched: matchedIds,
252
+ diff: cleanedSegments.map((s) => s.id),
253
+ resolvedIds,
200
254
  params: ctx.matched.params,
201
255
  routeName: ctx.routeKey,
202
- serverTiming,
203
256
  slots: Object.keys(state.slots).length > 0 ? state.slots : undefined,
204
257
  routeMiddleware:
205
258
  ctx.routeMiddleware.length > 0 ? ctx.routeMiddleware : undefined,
206
259
  };
207
260
  }
208
261
 
209
- /**
210
- * Collect segments from pipeline and build MatchResult
211
- *
212
- * This is the main entry point for building the final result after
213
- * the pipeline has processed all segments.
214
- */
215
262
  export async function collectMatchResult<TEnv>(
216
263
  pipeline: AsyncGenerator<ResolvedSegment>,
217
264
  ctx: MatchContext<TEnv>,
@@ -219,10 +266,18 @@ export async function collectMatchResult<TEnv>(
219
266
  ): Promise<MatchResult> {
220
267
  const allSegments = await collectSegments(pipeline);
221
268
 
222
- // Update state with collected segments if not already set
269
+ const buildStart = performance.now();
270
+
223
271
  if (state.segments.length === 0) {
224
272
  state.segments = allSegments;
225
273
  }
226
274
 
227
- return buildMatchResult(allSegments, ctx, state);
275
+ const result = buildMatchResult(allSegments, ctx, state);
276
+ appendMetric(
277
+ ctx.metricsStore,
278
+ "collect-result",
279
+ buildStart,
280
+ performance.now() - buildStart,
281
+ );
282
+ return result;
228
283
  }
@@ -1,28 +1,192 @@
1
- /**
2
- * Router Metrics Utilities
3
- *
4
- * Performance metrics collection and reporting for RSC Router.
5
- */
1
+ import type { MetricsStore, PerformanceMetric } from "../server/context";
6
2
 
7
- import type { MetricsStore } from "../server/context";
3
+ const BASE_INDENT = 2;
4
+ const DEPTH_INDENT = 2;
5
+ const TIMELINE_WIDTH = 40;
6
+
7
+ function formatMs(value: number): string {
8
+ return `${value.toFixed(2)}ms`;
9
+ }
10
+
11
+ function sortMetrics(metrics: PerformanceMetric[]): PerformanceMetric[] {
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
+ });
17
+ }
18
+
19
+ interface Span {
20
+ startTime: number;
21
+ duration: number;
22
+ }
23
+
24
+ function renderTimeline(spans: Span[], total: number): string {
25
+ if (TIMELINE_WIDTH <= 0) {
26
+ return "||";
27
+ }
28
+
29
+ const cells = Array(TIMELINE_WIDTH).fill(".");
30
+
31
+ if (!(total > 0)) {
32
+ cells[0] = "#";
33
+ return `|${cells.join("")}|`;
34
+ }
35
+
36
+ for (const span of spans) {
37
+ const start = Math.max(0, span.startTime);
38
+ const end = Math.max(start, span.startTime + span.duration);
39
+ const startColumn = Math.min(
40
+ TIMELINE_WIDTH - 1,
41
+ Math.floor((start / total) * TIMELINE_WIDTH),
42
+ );
43
+ const endColumn = Math.max(
44
+ startColumn + 1,
45
+ Math.min(
46
+ TIMELINE_WIDTH,
47
+ Math.ceil((Math.min(total, end) / total) * TIMELINE_WIDTH),
48
+ ),
49
+ );
50
+
51
+ cells.fill("#", startColumn, endColumn);
52
+ }
53
+
54
+ return `|${cells.join("")}|`;
55
+ }
56
+
57
+ function createTimelineAxis(total: number): string {
58
+ const totalLabel = formatMs(total);
59
+ return `0ms${" ".repeat(
60
+ Math.max(1, TIMELINE_WIDTH - "0ms".length - totalLabel.length),
61
+ )}${totalLabel}`;
62
+ }
8
63
 
9
- /**
10
- * Create a metrics store for the request if debugPerformance is enabled
11
- */
12
64
  export function createMetricsStore(
13
65
  debugPerformance: boolean,
66
+ requestStart?: number,
14
67
  ): MetricsStore | undefined {
15
68
  if (!debugPerformance) return undefined;
16
69
  return {
17
70
  enabled: true,
18
- requestStart: performance.now(),
71
+ requestStart: requestStart ?? performance.now(),
19
72
  metrics: [],
20
73
  };
21
74
  }
22
75
 
23
- /**
24
- * Log metrics to console in a formatted way
25
- */
76
+ export function appendMetric(
77
+ metricsStore: MetricsStore | undefined,
78
+ label: string,
79
+ start: number,
80
+ duration: number,
81
+ depth?: number,
82
+ ): void {
83
+ if (!metricsStore) return;
84
+ metricsStore.metrics.push({
85
+ label,
86
+ duration,
87
+ startTime: start - metricsStore.requestStart,
88
+ depth,
89
+ });
90
+ }
91
+
92
+ export function buildMetricsTiming(
93
+ method: string,
94
+ pathname: string,
95
+ metricsStore: MetricsStore | undefined,
96
+ ): string | undefined {
97
+ if (!metricsStore) return undefined;
98
+ logMetrics(method, pathname, metricsStore);
99
+ return generateServerTiming(metricsStore) || undefined;
100
+ }
101
+
102
+ interface DisplayRow {
103
+ label: string;
104
+ startTime: number;
105
+ duration: number;
106
+ depth: number | undefined;
107
+ spans: Span[];
108
+ }
109
+
110
+ function buildDisplayRows(sorted: PerformanceMetric[]): DisplayRow[] {
111
+ const preMap = new Map<string, PerformanceMetric>();
112
+ const postMap = new Map<string, PerformanceMetric>();
113
+ const consumed = new Set<PerformanceMetric>();
114
+
115
+ for (const m of sorted) {
116
+ if (m.label.endsWith(":pre")) {
117
+ preMap.set(m.label.slice(0, -4), m);
118
+ } else if (m.label.endsWith(":post")) {
119
+ postMap.set(m.label.slice(0, -5), m);
120
+ }
121
+ }
122
+
123
+ const rows: DisplayRow[] = [];
124
+
125
+ for (const m of sorted) {
126
+ if (consumed.has(m)) continue;
127
+
128
+ if (m.label.endsWith(":pre")) {
129
+ const base = m.label.slice(0, -4);
130
+ const post = postMap.get(base);
131
+ if (post) {
132
+ // Merge into a single row with two disjoint spans
133
+ consumed.add(m);
134
+ consumed.add(post);
135
+ rows.push({
136
+ label: base,
137
+ startTime: m.startTime,
138
+ duration: m.duration + post.duration,
139
+ depth: m.depth,
140
+ spans: [
141
+ { startTime: m.startTime, duration: m.duration },
142
+ { startTime: post.startTime, duration: post.duration },
143
+ ],
144
+ });
145
+ continue;
146
+ }
147
+ // Lone :pre — display with base label
148
+ consumed.add(m);
149
+ rows.push({
150
+ label: base,
151
+ startTime: m.startTime,
152
+ duration: m.duration,
153
+ depth: m.depth,
154
+ spans: [{ startTime: m.startTime, duration: m.duration }],
155
+ });
156
+ continue;
157
+ }
158
+
159
+ if (m.label.endsWith(":post")) {
160
+ const base = m.label.slice(0, -5);
161
+ if (preMap.has(base)) {
162
+ // Already consumed as part of the pair above
163
+ continue;
164
+ }
165
+ // Lone :post — display with base label
166
+ consumed.add(m);
167
+ rows.push({
168
+ label: base,
169
+ startTime: m.startTime,
170
+ duration: m.duration,
171
+ depth: m.depth,
172
+ spans: [{ startTime: m.startTime, duration: m.duration }],
173
+ });
174
+ continue;
175
+ }
176
+
177
+ // Regular metric
178
+ rows.push({
179
+ label: m.label,
180
+ startTime: m.startTime,
181
+ duration: m.duration,
182
+ depth: m.depth,
183
+ spans: [{ startTime: m.startTime, duration: m.duration }],
184
+ });
185
+ }
186
+
187
+ return rows;
188
+ }
189
+
26
190
  export function logMetrics(
27
191
  method: string,
28
192
  pathname: string,
@@ -30,32 +194,59 @@ export function logMetrics(
30
194
  ): void {
31
195
  const total = performance.now() - metricsStore.requestStart;
32
196
 
33
- // Find max label length for alignment
34
- const maxLabelLen = Math.max(
35
- ...metricsStore.metrics.map((m) => m.label.length),
36
- 20,
197
+ const sorted = sortMetrics(metricsStore.metrics);
198
+ const displayRows = buildDisplayRows(sorted);
199
+
200
+ const labels = displayRows.map(
201
+ (r) =>
202
+ `${" ".repeat(BASE_INDENT + (r.depth ?? 0) * DEPTH_INDENT)}${r.label}`,
203
+ );
204
+ const startValues = displayRows.map((r) => formatMs(r.startTime));
205
+ const durationValues = displayRows.map((r) => formatMs(r.duration));
206
+ const startWidth = Math.max(
207
+ "start".length,
208
+ ...startValues.map((v) => v.length),
209
+ );
210
+ const durationWidth = Math.max(
211
+ "dur".length,
212
+ ...durationValues.map((v) => v.length),
213
+ );
214
+ const spanWidth = Math.max(
215
+ "span".length,
216
+ ...labels.map((label) => label.length),
217
+ 22,
218
+ );
219
+ const timelinePadding = " ".repeat(
220
+ startWidth + 2 + durationWidth + 2 + spanWidth + 2,
221
+ );
222
+
223
+ console.log(`[RSC Perf] ${method} ${pathname} (${total.toFixed(2)}ms)`);
224
+ console.log(
225
+ `${"start".padStart(startWidth)} ${"dur".padStart(durationWidth)} ${"span".padEnd(spanWidth)} timeline`,
37
226
  );
227
+ console.log(`${timelinePadding}${createTimelineAxis(total)}`);
38
228
 
39
- console.log(`[RSC Perf] ${method} ${pathname} (${total.toFixed(1)}ms)`);
229
+ for (let index = 0; index < displayRows.length; index++) {
230
+ const row = displayRows[index];
231
+ const label = labels[index].padEnd(spanWidth);
232
+ const start = formatMs(row.startTime).padStart(startWidth);
233
+ const duration = formatMs(row.duration).padStart(durationWidth);
40
234
 
41
- for (const m of metricsStore.metrics) {
42
- const paddedLabel = m.label.padEnd(maxLabelLen);
43
- console.log(` ${paddedLabel} ${m.duration.toFixed(1)}ms`);
235
+ console.log(
236
+ `${start} ${duration} ${label} ${renderTimeline(row.spans, total)}`,
237
+ );
44
238
  }
45
239
  }
46
240
 
47
- /**
48
- * Generate Server-Timing header value from metrics
49
- * Format: metric-name;dur=X.XX
50
- */
51
241
  export function generateServerTiming(metricsStore: MetricsStore): string {
52
242
  return metricsStore.metrics
53
243
  .map((m) => {
54
244
  // Convert label to valid Server-Timing name (alphanumeric and hyphens)
55
- const name = m.label
245
+ const base = m.label
56
246
  .replace(/:/g, "-")
57
247
  .replace(/[^a-zA-Z0-9-]/g, "")
58
248
  .toLowerCase();
249
+ const name = m.depth ? `d${m.depth}-${base}` : base;
59
250
  return `${name};dur=${m.duration.toFixed(2)}`;
60
251
  })
61
252
  .join(", ");
@@ -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,
@@ -12,26 +5,24 @@ import type {
12
5
  DefaultVars,
13
6
  } from "../types/global-namespace.js";
14
7
  import type { ScopedReverseFunction } from "../reverse.js";
8
+ import type { Theme } from "../theme/types.js";
9
+ import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
10
+ import type { RequestScope } from "../types/request-scope.js";
15
11
 
16
- /**
17
- * Get variable function type
18
- */
19
12
  type GetVariableFn = {
20
13
  <T>(contextVar: ContextVar<T>): T | undefined;
21
14
  <K extends keyof DefaultVars>(key: K): DefaultVars[K];
22
15
  };
23
16
 
24
- /**
25
- * Set variable function type
26
- */
27
17
  type SetVariableFn = {
28
- <T>(contextVar: ContextVar<T>, value: T): void;
29
- <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;
30
24
  };
31
25
 
32
- /**
33
- * Cookie options for setting cookies
34
- */
35
26
  export interface CookieOptions {
36
27
  domain?: string;
37
28
  path?: string;
@@ -42,137 +33,60 @@ export interface CookieOptions {
42
33
  sameSite?: "strict" | "lax" | "none";
43
34
  }
44
35
 
45
- /**
46
- * Context passed to middleware
47
- *
48
- * @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
49
- * @template TParams - URL params type (typed for route middleware, Record<string, string> for global middleware)
50
- */
51
36
  export interface MiddlewareContext<
52
37
  TEnv = any,
53
- TParams = Record<string, string>,
54
- > {
55
- /** Original request */
56
- request: Request;
57
-
58
- /** Parsed URL */
59
- url: URL;
60
-
61
- /** URL pathname */
62
- pathname: string;
63
-
64
- /** URL search params */
65
- searchParams: URLSearchParams;
66
-
67
- /** Platform bindings (Cloudflare, etc.) */
68
- env: TEnv;
69
-
70
- /** URL params extracted from route/middleware pattern */
38
+ TParams = Record<string, string | undefined>,
39
+ > extends RequestScope<TEnv> {
71
40
  params: TParams;
72
41
 
73
- /**
74
- * Response stub (read-only). Before `next()`, returns the shared response stub
75
- * where headers and cookies accumulate. After `next()`, returns the downstream response.
76
- *
77
- * Use `ctx.header()` to set response headers, or `cookies()` for cookie mutations.
78
- * To replace the response entirely, return a new `Response` from the middleware.
79
- */
80
- readonly res: Response;
42
+ readonly headers: Headers;
81
43
 
82
- /** Get a context variable (shared with route handlers) */
83
44
  get: GetVariableFn;
84
45
 
85
- /** Set a context variable (shared with route handlers) */
86
46
  set: SetVariableFn;
87
47
 
88
- /**
89
- * Set a response header - can be called before or after `next()`
90
- *
91
- * When called before `next()`, headers are queued and merged into the final response.
92
- * When called after `next()`, headers are set directly on the response.
93
- * Shorthand for `ctx.res.headers.set()`.
94
- */
95
48
  header(name: string, value: string): void;
96
49
 
97
- /**
98
- * The matched route name, if available and the route has an explicit name.
99
- * Undefined for global middleware (runs before route matching) or unnamed routes.
100
- */
101
50
  routeName?: DefaultRouteName;
102
51
 
103
- /**
104
- * Generate URLs from route names.
105
- * - `name` — global route, from the named-routes definition
106
- */
52
+ debugPerformance(): void;
53
+
54
+ theme?: Theme;
55
+
56
+ setTheme?: (theme: Theme) => void;
57
+
58
+ setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
59
+
107
60
  reverse: ScopedReverseFunction<
108
61
  Record<string, string>,
109
62
  DefaultReverseRouteMap
110
63
  >;
111
64
  }
112
65
 
113
- /**
114
- * Middleware function signature
115
- *
116
- * @template TEnv - Environment type - defaults to any for internal flexibility
117
- * @template TParams - URL params type (typed for route middleware)
118
- *
119
- * When using middleware with global augmentation (RSCRouter.Env), explicitly
120
- * annotate your middleware functions, or the types will be inferred from context:
121
- *
122
- * @example
123
- * ```typescript
124
- * // With explicit annotation (recommended for reusable middleware)
125
- * const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
126
- *
127
- * // Types inferred from router.use() call
128
- * router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
129
- * ```
130
- */
131
- export type MiddlewareFn<TEnv = any, TParams = Record<string, string>> = (
66
+ export type MiddlewareFn<
67
+ TEnv = any,
68
+ TParams = Record<string, string | undefined>,
69
+ > = (
132
70
  ctx: MiddlewareContext<TEnv, TParams>,
133
71
  next: () => Promise<Response>,
134
72
  ) => Response | void | Promise<Response | void>;
135
73
 
136
- /**
137
- * Stored middleware entry with pattern matching info
138
- * @internal - uses any for internal flexibility
139
- */
140
74
  export interface MiddlewareEntry<TEnv = any> {
141
- /** Original pattern string */
142
75
  pattern: string | null;
143
-
144
- /** Compiled regex for matching */
145
76
  regex: RegExp | null;
146
-
147
- /** Param names extracted from pattern */
148
77
  paramNames: string[];
149
-
150
- /** The middleware function */
151
78
  handler: MiddlewareFn<TEnv>;
152
-
153
- /** Mount prefix this middleware is scoped to (null = global) */
154
- mountPrefix: string | null;
155
79
  }
156
80
 
157
- /**
158
- * Mutable response holder - allows ctx.res to be updated after next() is called
159
- */
160
81
  export interface ResponseHolder {
161
82
  response: Response | null;
162
83
  }
163
84
 
164
- /**
165
- * Entry type for middleware collection
166
- * Matches the shape of EntryData used in router.ts
167
- */
168
85
  export interface MiddlewareCollectableEntry {
169
86
  middleware?: MiddlewareFn<any, any>[];
170
87
  layout?: MiddlewareCollectableEntry[];
171
88
  }
172
89
 
173
- /**
174
- * Collected route middleware with params
175
- */
176
90
  export interface CollectedMiddleware {
177
91
  handler: MiddlewareFn<any, any>;
178
92
  params: Record<string, string>;