@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
@@ -41,6 +41,109 @@ import {
41
41
  } from "./telemetry.js";
42
42
  import { _getRequestContext } from "../server/request-context.js";
43
43
 
44
+ /**
45
+ * Per-call telemetry lifecycle emitter for match()/matchPartial(). Each method
46
+ * reproduces the exact event object the two functions used to emit inline and is
47
+ * gated on the same per-call flag (`enabled` = the request's `emitTelemetry`), so
48
+ * a PPR shell-capture run (enabled=false) emits nothing while a foreground run
49
+ * emits byte-identical events. Extracted so the two transactions can't drift;
50
+ * pinned by thrown-response-telemetry.test.ts and
51
+ * shell-capture-telemetry-suppression.test.ts.
52
+ */
53
+ interface LifecycleEmitter {
54
+ start(): void;
55
+ end(segmentCount: number, cacheHit: boolean, status?: number): void;
56
+ cacheDecision(
57
+ routeKey: string,
58
+ state: {
59
+ cacheHit: boolean;
60
+ cacheSource?: "runtime" | "prerender";
61
+ shouldRevalidate?: boolean;
62
+ },
63
+ segments: CacheSegmentSignal[],
64
+ ): void;
65
+ error(error: Error, phase: string): void;
66
+ }
67
+
68
+ function createLifecycleEmitter(args: {
69
+ enabled: boolean;
70
+ sink: TelemetrySink;
71
+ requestId: string | undefined;
72
+ method: string;
73
+ pathname: string;
74
+ transaction: "match" | "matchPartial";
75
+ isPartial: boolean;
76
+ matchStart: number;
77
+ }): LifecycleEmitter {
78
+ return {
79
+ start(): void {
80
+ if (!args.enabled) return;
81
+ safeEmit(args.sink, {
82
+ type: "request.start",
83
+ timestamp: args.matchStart,
84
+ requestId: args.requestId,
85
+ method: args.method,
86
+ pathname: args.pathname,
87
+ transaction: args.transaction,
88
+ isPartial: args.isPartial,
89
+ });
90
+ },
91
+ end(segmentCount: number, cacheHit: boolean, status?: number): void {
92
+ if (!args.enabled) return;
93
+ safeEmit(args.sink, {
94
+ type: "request.end",
95
+ timestamp: performance.now(),
96
+ requestId: args.requestId,
97
+ method: args.method,
98
+ pathname: args.pathname,
99
+ transaction: args.transaction,
100
+ durationMs: performance.now() - args.matchStart,
101
+ segmentCount,
102
+ cacheHit,
103
+ // Only a thrown-Response short-circuit passes a status; a normal render
104
+ // completion omits it (the Response is built after match()).
105
+ ...(status !== undefined && { status }),
106
+ });
107
+ },
108
+ cacheDecision(
109
+ routeKey: string,
110
+ state: {
111
+ cacheHit: boolean;
112
+ cacheSource?: "runtime" | "prerender";
113
+ shouldRevalidate?: boolean;
114
+ },
115
+ segments: CacheSegmentSignal[],
116
+ ): void {
117
+ if (!args.enabled) return;
118
+ safeEmit(args.sink, {
119
+ type: "cache.decision",
120
+ timestamp: performance.now(),
121
+ requestId: args.requestId,
122
+ pathname: args.pathname,
123
+ routeKey,
124
+ hit: state.cacheHit,
125
+ shouldRevalidate: !!state.shouldRevalidate,
126
+ source: state.cacheSource,
127
+ segments,
128
+ });
129
+ },
130
+ error(error: Error, phase: string): void {
131
+ if (!args.enabled) return;
132
+ safeEmit(args.sink, {
133
+ type: "request.error",
134
+ timestamp: performance.now(),
135
+ requestId: args.requestId,
136
+ method: args.method,
137
+ pathname: args.pathname,
138
+ transaction: args.transaction,
139
+ error,
140
+ phase,
141
+ durationMs: performance.now() - args.matchStart,
142
+ });
143
+ },
144
+ };
145
+ }
146
+
44
147
  export interface MatchHandlerDeps<TEnv = any> {
45
148
  buildRouterContext: () => RouterContext<TEnv>;
46
149
  callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
@@ -89,20 +192,6 @@ export interface MatchHandlers<TEnv = any> {
89
192
  negotiated?: boolean;
90
193
  manifestEntry?: EntryData;
91
194
  } | null>;
92
- createMatchContextForFull: (
93
- request: Request,
94
- env: TEnv,
95
- ) => Promise<MatchContext<TEnv> | { type: "redirect"; redirectUrl: string }>;
96
- createMatchContextForPartial: (
97
- request: Request,
98
- env: TEnv,
99
- actionContext?: {
100
- actionId?: string;
101
- actionUrl?: URL;
102
- actionResult?: any;
103
- formData?: FormData;
104
- },
105
- ) => Promise<MatchContext<TEnv> | null>;
106
195
  }
107
196
 
108
197
  /**
@@ -123,9 +212,6 @@ export function createMatchHandlers<TEnv = any>(
123
212
  const hasTelemetry = !!deps.telemetry;
124
213
  const telemetry = resolveSink(deps.telemetry);
125
214
  const cacheSignalEnabled = !!deps.cacheSignalEnabled;
126
- // Compute the coarse cache signal when EITHER telemetry needs it (for the
127
- // cache.decision event) OR the debug header gate is on. When neither is set,
128
- // this is never called — zero extra work on the hot path.
129
215
  const buildSignal = (
130
216
  routeKey: string,
131
217
  state: {
@@ -134,8 +220,6 @@ export function createMatchHandlers<TEnv = any>(
134
220
  shouldRevalidate?: boolean;
135
221
  },
136
222
  ): CacheSegmentSignal[] => buildCacheSignalSegments(routeKey, state);
137
- // Stash the signal on the request context for the response path to emit as
138
- // the X-Rango-Cache header. Only when the debug gate is on.
139
223
  const recordSignalIfEnabled = (segments: CacheSegmentSignal[]): void => {
140
224
  if (!cacheSignalEnabled) return;
141
225
  const reqCtx = _getRequestContext();
@@ -173,53 +257,44 @@ export function createMatchHandlers<TEnv = any>(
173
257
  );
174
258
  }
175
259
 
176
- /**
177
- * Match request and return segments (document/SSR requests)
178
- *
179
- * Uses generator middleware pipeline for clean separation of concerns:
180
- * - cache-lookup: Check cache first
181
- * - segment-resolution: Resolve segments on cache miss
182
- * - cache-store: Store results in cache
183
- * - background-revalidation: SWR revalidation
184
- */
185
260
  async function match(request: Request, env: TEnv): Promise<MatchResult> {
186
- const requestId = hasTelemetry ? getRequestId(request) : undefined;
261
+ // Silence telemetry for the PPR background shell capture: it re-runs match()
262
+ // under a derived request context flagged _shellCaptureRun (shell-capture.ts
263
+ // attemptCapture), re-using the foreground Request — a second request.start/
264
+ // cache.decision/request.end stamped with the same WeakMap-keyed requestId
265
+ // would double-count dashboards. Derived here (inside the capture's active
266
+ // request-context ALS) so the read sees the derived context, not module state.
267
+ const emitTelemetry =
268
+ hasTelemetry && !_getRequestContext()?._shellCaptureRun;
269
+ const requestId = emitTelemetry ? getRequestId(request) : undefined;
187
270
  return runWithRouterLogContext({ request, transaction: "match" }, () => {
188
271
  const routerCtx = buildRouterContext();
272
+ // Also mute in-pipeline observeEvent emitters (revalidation.decision,
273
+ // cache-lookup's cache.decision) which read routerCtx.telemetry.
274
+ if (!emitTelemetry) routerCtx.telemetry = undefined;
189
275
  routerCtx.requestId = requestId;
190
276
  return runWithRouterContext(routerCtx, async () =>
191
277
  withRouterLogScope("match", async () => {
192
278
  const matchStart = performance.now();
193
- const pathname = new URL(request.url).pathname;
194
- if (hasTelemetry) {
195
- safeEmit(telemetry, {
196
- type: "request.start",
197
- timestamp: matchStart,
198
- requestId,
199
- method: request.method,
200
- pathname,
201
- transaction: "match",
202
- isPartial: false,
203
- });
204
- }
279
+ const pathname =
280
+ _getRequestContext()?.url?.pathname ??
281
+ new URL(request.url).pathname;
282
+ const emitter = createLifecycleEmitter({
283
+ enabled: emitTelemetry,
284
+ sink: telemetry,
285
+ requestId,
286
+ method: request.method,
287
+ pathname,
288
+ transaction: "match",
289
+ isPartial: false,
290
+ matchStart,
291
+ });
292
+ emitter.start();
205
293
 
206
294
  const result = await createMatchContextForFull(request, env);
207
295
 
208
- // Handle redirect case
209
296
  if ("type" in result && result.type === "redirect") {
210
- if (hasTelemetry) {
211
- safeEmit(telemetry, {
212
- type: "request.end",
213
- timestamp: performance.now(),
214
- requestId,
215
- method: request.method,
216
- pathname,
217
- transaction: "match",
218
- durationMs: performance.now() - matchStart,
219
- segmentCount: 0,
220
- cacheHit: false,
221
- });
222
- }
297
+ emitter.end(0, false);
223
298
  return {
224
299
  segments: [],
225
300
  matched: [],
@@ -239,52 +314,26 @@ export function createMatchHandlers<TEnv = any>(
239
314
  if (hasTelemetry || cacheSignalEnabled) {
240
315
  const signalSegments = buildSignal(ctx.routeKey, state);
241
316
  recordSignalIfEnabled(signalSegments);
242
- if (hasTelemetry) {
243
- safeEmit(telemetry, {
244
- type: "cache.decision",
245
- timestamp: performance.now(),
246
- requestId,
247
- pathname,
248
- routeKey: ctx.routeKey,
249
- hit: state.cacheHit,
250
- shouldRevalidate: !!state.shouldRevalidate,
251
- source: state.cacheSource,
252
- segments: signalSegments,
253
- });
254
- }
255
- }
256
- if (hasTelemetry) {
257
- safeEmit(telemetry, {
258
- type: "request.end",
259
- timestamp: performance.now(),
260
- requestId,
261
- method: request.method,
262
- pathname,
263
- transaction: "match",
264
- durationMs: performance.now() - matchStart,
265
- segmentCount: matchResult.segments.length,
266
- cacheHit: state.cacheHit,
267
- });
317
+ emitter.cacheDecision(ctx.routeKey, state, signalSegments);
268
318
  }
319
+ emitter.end(matchResult.segments.length, state.cacheHit);
269
320
  return matchResult;
270
321
  } catch (error) {
271
- if (hasTelemetry) {
272
- const errorObj =
273
- error instanceof Error ? error : new Error(String(error));
274
- safeEmit(telemetry, {
275
- type: "request.error",
276
- timestamp: performance.now(),
277
- requestId,
278
- method: request.method,
279
- pathname,
280
- transaction: "match",
281
- error: errorObj,
282
- phase: error instanceof Response ? "redirect" : "routing",
283
- durationMs: performance.now() - matchStart,
284
- });
322
+ if (error instanceof Response) {
323
+ // A thrown Response (middleware short-circuit — redirect / auth
324
+ // gate) is a COMPLETED request from the consumer's seat, not an
325
+ // error: emit request.end (the same shape the non-thrown redirect
326
+ // result above already emits), never request.error with a
327
+ // synthetic "[object Response]" error. Rethrow so the caller
328
+ // drives the redirect. Carry the Response's status so a sink can
329
+ // tell a 3xx short-circuit from a 2xx completion.
330
+ emitter.end(0, false, error.status);
331
+ throw error;
285
332
  }
286
- if (error instanceof Response) throw error;
287
- // Report unhandled errors during full match pipeline
333
+ emitter.error(
334
+ error instanceof Error ? error : new Error(String(error)),
335
+ "routing",
336
+ );
288
337
  callOnError(error, "routing", {
289
338
  request,
290
339
  url: ctx.url,
@@ -319,42 +368,39 @@ export function createMatchHandlers<TEnv = any>(
319
368
  );
320
369
  }
321
370
 
322
- /**
323
- * Match partial request with revalidation
324
- *
325
- * Uses generator middleware pipeline for clean separation of concerns:
326
- * - cache-lookup: Check cache first
327
- * - segment-resolution: Resolve segments on cache miss
328
- * - intercept-resolution: Handle intercept routes
329
- * - cache-store: Store results in cache
330
- * - background-revalidation: SWR revalidation
331
- */
332
371
  async function matchPartial(
333
372
  request: Request,
334
373
  context: TEnv,
335
374
  actionContext?: ActionContext,
336
375
  ): Promise<MatchResult | null> {
337
- const partialRequestId = hasTelemetry ? getRequestId(request) : undefined;
376
+ // See match() above: the PPR shell capture re-runs matchPartial() under a
377
+ // _shellCaptureRun context and must stay invisible to the sink.
378
+ const emitTelemetry =
379
+ hasTelemetry && !_getRequestContext()?._shellCaptureRun;
380
+ const partialRequestId = emitTelemetry ? getRequestId(request) : undefined;
338
381
  return runWithRouterLogContext(
339
382
  { request, transaction: "matchPartial" },
340
383
  () => {
341
384
  const routerCtx = buildRouterContext();
385
+ if (!emitTelemetry) routerCtx.telemetry = undefined;
342
386
  routerCtx.requestId = partialRequestId;
343
387
  return runWithRouterContext(routerCtx, async () =>
344
388
  withRouterLogScope("matchPartial", async () => {
345
389
  const matchStart = performance.now();
346
- const pathname = new URL(request.url).pathname;
347
- if (hasTelemetry) {
348
- safeEmit(telemetry, {
349
- type: "request.start",
350
- timestamp: matchStart,
351
- requestId: partialRequestId,
352
- method: request.method,
353
- pathname,
354
- transaction: "matchPartial",
355
- isPartial: true,
356
- });
357
- }
390
+ const pathname =
391
+ _getRequestContext()?.url?.pathname ??
392
+ new URL(request.url).pathname;
393
+ const emitter = createLifecycleEmitter({
394
+ enabled: emitTelemetry,
395
+ sink: telemetry,
396
+ requestId: partialRequestId,
397
+ method: request.method,
398
+ pathname,
399
+ transaction: "matchPartial",
400
+ isPartial: true,
401
+ matchStart,
402
+ });
403
+ emitter.start();
358
404
 
359
405
  const ctx = await createMatchContextForPartial(
360
406
  request,
@@ -362,19 +408,7 @@ export function createMatchHandlers<TEnv = any>(
362
408
  actionContext,
363
409
  );
364
410
  if (!ctx) {
365
- if (hasTelemetry) {
366
- safeEmit(telemetry, {
367
- type: "request.end",
368
- timestamp: performance.now(),
369
- requestId: partialRequestId,
370
- method: request.method,
371
- pathname,
372
- transaction: "matchPartial",
373
- durationMs: performance.now() - matchStart,
374
- segmentCount: 0,
375
- cacheHit: false,
376
- });
377
- }
411
+ emitter.end(0, false);
378
412
  return null;
379
413
  }
380
414
 
@@ -401,54 +435,25 @@ export function createMatchHandlers<TEnv = any>(
401
435
  if (hasTelemetry || cacheSignalEnabled) {
402
436
  const signalSegments = buildSignal(ctx.routeKey, state);
403
437
  recordSignalIfEnabled(signalSegments);
404
- if (hasTelemetry) {
405
- safeEmit(telemetry, {
406
- type: "cache.decision",
407
- timestamp: performance.now(),
408
- requestId: partialRequestId,
409
- pathname,
410
- routeKey: ctx.routeKey,
411
- hit: state.cacheHit,
412
- shouldRevalidate: !!state.shouldRevalidate,
413
- source: state.cacheSource,
414
- segments: signalSegments,
415
- });
416
- }
417
- }
418
- if (hasTelemetry) {
419
- safeEmit(telemetry, {
420
- type: "request.end",
421
- timestamp: performance.now(),
422
- requestId: partialRequestId,
423
- method: request.method,
424
- pathname,
425
- transaction: "matchPartial",
426
- durationMs: performance.now() - matchStart,
427
- segmentCount: matchResult.segments.length,
428
- cacheHit: state.cacheHit,
429
- });
438
+ emitter.cacheDecision(ctx.routeKey, state, signalSegments);
430
439
  }
440
+ emitter.end(matchResult.segments.length, state.cacheHit);
431
441
  return matchResult;
432
442
  } catch (error) {
433
443
  flushRevalidationTrace();
434
- if (hasTelemetry) {
435
- const errorObj =
436
- error instanceof Error ? error : new Error(String(error));
437
- const phase = actionContext ? "action" : "revalidation";
438
- safeEmit(telemetry, {
439
- type: "request.error",
440
- timestamp: performance.now(),
441
- requestId: partialRequestId,
442
- method: request.method,
443
- pathname,
444
- transaction: "matchPartial",
445
- error: errorObj,
446
- phase: error instanceof Response ? "redirect" : phase,
447
- durationMs: performance.now() - matchStart,
448
- });
444
+ if (error instanceof Response) {
445
+ // A thrown Response (middleware short-circuit — redirect / auth
446
+ // gate) is a COMPLETED request, not an error: emit request.end
447
+ // (parity with match()), never request.error. Rethrow so the
448
+ // caller drives the redirect. Carry the Response's status so a
449
+ // sink can tell a 3xx short-circuit from a 2xx completion.
450
+ emitter.end(0, false, error.status);
451
+ throw error;
449
452
  }
450
- if (error instanceof Response) throw error;
451
- // Report unhandled errors during partial match pipeline
453
+ emitter.error(
454
+ error instanceof Error ? error : new Error(String(error)),
455
+ actionContext ? "action" : "revalidation",
456
+ );
452
457
  callOnError(error, actionContext ? "action" : "revalidation", {
453
458
  request,
454
459
  url: ctx.url,
@@ -477,7 +482,5 @@ export function createMatchHandlers<TEnv = any>(
477
482
  matchPartial: matchPartial,
478
483
  matchError: matchError,
479
484
  previewMatch: previewMatch,
480
- createMatchContextForFull: createMatchContextForFull,
481
- createMatchContextForPartial: createMatchContextForPartial,
482
485
  };
483
486
  }
@@ -105,6 +105,10 @@ import { getRouterContext } from "../router-context.js";
105
105
  import type { GeneratorMiddleware } from "./cache-lookup.js";
106
106
  import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
107
107
  import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
108
+ import {
109
+ runWithRequestContext,
110
+ type RequestContext,
111
+ } from "../../server/request-context.js";
108
112
 
109
113
  /**
110
114
  * Creates background revalidation middleware
@@ -168,8 +172,6 @@ export function withBackgroundRevalidation<TEnv>(
168
172
  requestCtx._handleStore = createHandleStore();
169
173
 
170
174
  try {
171
- // Create fresh handler context and loader promises to avoid
172
- // reusing memoized results from the foreground pass
173
175
  const freshHandlerContext = createHandlerContext(
174
176
  ctx.matched.params,
175
177
  ctx.request,
@@ -185,32 +187,46 @@ export function withBackgroundRevalidation<TEnv>(
185
187
  const freshLoaderPromises = new Map<string, Promise<any>>();
186
188
  setupLoaderAccess(freshHandlerContext, freshLoaderPromises);
187
189
 
188
- // Resolve all segments fresh (without revalidation logic)
189
- // to ensure complete components for caching.
190
- // Skip DSL loaders they are never cached (cacheRoute filters them)
191
- // and are always resolved fresh on each request.
192
- const freshSegments = await ctx.Store.run(() =>
193
- resolveAllSegments(
194
- ctx.entries,
195
- ctx.routeKey,
196
- ctx.matched.params,
197
- freshHandlerContext,
198
- freshLoaderPromises,
199
- { skipLoaders: true },
200
- ),
190
+ // Re-establish the request-context ALS around the re-render. ctx.Store
191
+ // is a different ALS (DSL build context); on workerd a waitUntil task
192
+ // runs detached from the request's I/O context, so a handler/component
193
+ // that reads the ambient getRequestContext() during this background
194
+ // re-render would otherwise throw "called outside of a request context".
195
+ const freshSegments = await runWithRequestContext(
196
+ requestCtx as RequestContext<TEnv>,
197
+ () =>
198
+ ctx.Store.run(() =>
199
+ resolveAllSegments(
200
+ ctx.entries,
201
+ ctx.routeKey,
202
+ ctx.matched.params,
203
+ freshHandlerContext,
204
+ freshLoaderPromises,
205
+ { skipLoaders: true },
206
+ ),
207
+ ),
201
208
  );
202
209
 
203
- // Also resolve intercept segments fresh if applicable
204
210
  let freshInterceptSegments: ResolvedSegment[] = [];
205
211
  if (ctx.interceptResult) {
206
- freshInterceptSegments = await ctx.Store.run(() =>
207
- resolveInterceptEntry(
208
- ctx.interceptResult!.intercept,
209
- ctx.interceptResult!.entry,
210
- ctx.matched.params,
211
- freshHandlerContext,
212
- true,
213
- ),
212
+ freshInterceptSegments = await runWithRequestContext(
213
+ requestCtx as RequestContext<TEnv>,
214
+ () =>
215
+ ctx.Store.run(() =>
216
+ resolveInterceptEntry(
217
+ ctx.interceptResult!.intercept,
218
+ ctx.interceptResult!.entry,
219
+ ctx.matched.params,
220
+ freshHandlerContext,
221
+ true,
222
+ undefined,
223
+ // Skip intercept middleware: this is a post-response background
224
+ // re-render to refresh a stale cached route. The foreground
225
+ // already ran the middleware; re-running it would double its
226
+ // side effects and a short-circuit Response would abort the write.
227
+ { skipMiddleware: true },
228
+ ),
229
+ ),
214
230
  );
215
231
  }
216
232