@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,20 +1,48 @@
1
1
  /**
2
- * OpenTelemetry Adapter for Router Telemetry
2
+ * OpenTelemetry adapters for the router.
3
3
  *
4
- * Maps internal TelemetrySink events to OTel spans. The core router
5
- * remains OTel-agnostic — this adapter bridges the gap by accepting
6
- * a standard OTel Tracer and producing spans/events from it.
4
+ * Two adapters, two surfaces matching the split in instrument.ts:
5
+ *
6
+ * - createOTelTracing(tracer): the OTel adapter for the `tracing` SLOT — the
7
+ * canonical phase-span layer. It bridges observePhase's callback boundary
8
+ * onto OTel's callback-bound `startActiveSpan`, so the router's phase spans
9
+ * (rango.request/middleware/loader/render/ssr) nest by async context and the
10
+ * loader's own OTel spans (db/fetch) land under rango.loader. This is the
11
+ * OTel equivalent of createCloudflareTracing — pass it to
12
+ * `createRouter({ tracing })`.
13
+ *
14
+ * - createOTelSink(tracer): the OTel adapter for the `telemetry` SLOT — a
15
+ * TelemetrySink for the EVENT-shaped facts (handler errors, cache decisions,
16
+ * revalidation decisions, timeouts, origin rejections). It emits one instant
17
+ * OTel span per fact. It deliberately does NOT emit request/loader phase
18
+ * spans: those are owned by the tracing slot (createOTelTracing) so the two
19
+ * layers don't produce duplicate rango.request / rango.loader spans.
20
+ *
21
+ * The core router stays OTel-agnostic — these adapters accept a standard OTel
22
+ * Tracer (structurally typed, no import needed) and bridge the gap.
7
23
  *
8
24
  * Usage:
9
25
  * import { trace } from "@opentelemetry/api";
10
- * import { createOTelSink } from "@rangojs/router";
26
+ * import { createRouter, createOTelTracing, createOTelSink } from "@rangojs/router";
11
27
  *
28
+ * const tracer = trace.getTracer("my-app");
12
29
  * const router = createRouter({
13
- * telemetry: createOTelSink(trace.getTracer("my-app")),
30
+ * tracing: createOTelTracing(tracer), // phase spans (callback-bound)
31
+ * telemetry: createOTelSink(tracer), // discrete-fact instant spans
14
32
  * });
33
+ *
34
+ * Faithful nesting requires an OTel async context manager
35
+ * (AsyncLocalStorageContextManager) configured in your OTel setup — standard for
36
+ * any startActiveSpan-based instrumentation.
15
37
  */
16
38
 
17
39
  import type { TelemetrySink, TelemetryEvent } from "./telemetry.js";
40
+ import { runThenSettle } from "./tracing.js";
41
+ import type {
42
+ RouterTracingConfig,
43
+ SpanRunner,
44
+ TracingToggleOptions,
45
+ } from "./tracing.js";
18
46
 
19
47
  // ---------------------------------------------------------------------------
20
48
  // Minimal OTel-compatible types (structurally typed, no import needed)
@@ -22,21 +50,21 @@ import type { TelemetrySink, TelemetryEvent } from "./telemetry.js";
22
50
 
23
51
  /**
24
52
  * Minimal Span interface compatible with @opentelemetry/api Span.
25
- * Only the methods used by the adapter are declared.
53
+ * Only the methods used by the adapters are declared.
26
54
  */
27
55
  export interface OTelSpan {
28
56
  setAttribute(key: string, value: string | number | boolean): OTelSpan | void;
29
- addEvent(
30
- name: string,
31
- attributes?: Record<string, string | number | boolean>,
32
- ): OTelSpan | void;
33
57
  setStatus(status: { code: number; message?: string }): OTelSpan | void;
34
58
  recordException(exception: Error): void;
35
59
  end(): void;
36
60
  }
37
61
 
38
62
  /**
39
- * Minimal Tracer interface compatible with @opentelemetry/api Tracer.
63
+ * Minimal Tracer interface for the EVENT sink (createOTelSink): only `startSpan`
64
+ * is used (one instant span per discrete fact). Kept narrow so a custom,
65
+ * event-only tracer that does not implement `startActiveSpan` still satisfies it.
66
+ * The real `@opentelemetry/api` Tracer has both methods, so it satisfies this and
67
+ * `OTelActiveSpanTracer` below.
40
68
  */
41
69
  export interface OTelTracer {
42
70
  startSpan(
@@ -47,194 +75,108 @@ export interface OTelTracer {
47
75
  ): OTelSpan;
48
76
  }
49
77
 
78
+ /**
79
+ * Minimal Tracer interface for the PHASE-SPAN adapter (createOTelTracing): only
80
+ * `startActiveSpan` is used (callback-bound, so the span is active for the work
81
+ * and child spans nest). Declared separately from `OTelTracer` so each factory
82
+ * requires exactly the method it calls.
83
+ */
84
+ export interface OTelActiveSpanTracer {
85
+ startActiveSpan<T>(name: string, fn: (span: OTelSpan) => T): T;
86
+ }
87
+
50
88
  // OTel SpanStatusCode constants (mirrors @opentelemetry/api values)
51
- const STATUS_OK = 1;
52
89
  const STATUS_ERROR = 2;
53
90
 
54
91
  // ---------------------------------------------------------------------------
55
- // Span correlation helpers
92
+ // Tracing adapter: phase spans via startActiveSpan (the `tracing` slot)
56
93
  // ---------------------------------------------------------------------------
57
94
 
58
- // Build correlation keys using requestId.
59
- // getRequestId() always returns a value (generated internally when no
60
- // header is present), so concurrent requests to the same path each get
61
- // their own correlation key and never mis-correlate.
62
-
63
- function requestKey(event: {
64
- requestId?: string;
65
- pathname: string;
66
- transaction: string;
67
- }): string {
68
- return `${event.requestId ?? ""}:${event.pathname}:${event.transaction}`;
69
- }
70
-
71
- function loaderKey(event: {
72
- requestId?: string;
73
- segmentId: string;
74
- loaderName: string;
75
- pathname: string;
76
- }): string {
77
- return `${event.requestId ?? ""}:${event.segmentId}:${event.loaderName}:${event.pathname}`;
78
- }
95
+ /**
96
+ * Options for createOTelTracing. Alias of the shared {@link TracingToggleOptions}
97
+ * (`enabled` master switch + per-phase `spans` toggles); the name is public API.
98
+ */
99
+ export type OTelTracingOptions = TracingToggleOptions;
79
100
 
80
- function pushSpan(
81
- map: Map<string, OTelSpan[]>,
82
- key: string,
83
- span: OTelSpan,
84
- ): void {
85
- let stack = map.get(key);
86
- if (!stack) {
87
- stack = [];
88
- map.set(key, stack);
89
- }
90
- stack.push(span);
91
- }
101
+ /**
102
+ * Create the tracing config that maps the router's phases onto OTel spans via
103
+ * `tracer.startActiveSpan`, which runs the work inside the span's active context
104
+ * (so child spans nest) and returns the work's value unchanged. The span ends
105
+ * when that value — or, for async work, the returned promise — settles, matching
106
+ * observePhase's contract. When the work throws or rejects, the exception is
107
+ * recorded and the span status is set to ERROR before it ends, so a failed phase
108
+ * stays visible in the trace (the old createOTelSink error path is preserved here
109
+ * now that phase spans live in this adapter). Pass the result to
110
+ * `createRouter({ tracing })`.
111
+ *
112
+ * @see createCloudflareTracing (`@rangojs/router/cloudflare`) for the same slot
113
+ * using Cloudflare Workers native custom spans.
114
+ */
115
+ export function createOTelTracing(
116
+ tracer: OTelActiveSpanTracer,
117
+ options: OTelTracingOptions = {},
118
+ ): RouterTracingConfig {
119
+ const runner: SpanRunner = <T>(name: string, fn: (span: OTelSpan) => T): T =>
120
+ tracer.startActiveSpan(
121
+ name,
122
+ (span): T =>
123
+ runThenSettle(
124
+ () => fn(span),
125
+ (error) => {
126
+ // On failure record the exception + ERROR status before ending, so a
127
+ // failed phase stays visible in the trace.
128
+ if (error !== undefined) {
129
+ if (error instanceof Error) {
130
+ span.recordException(error);
131
+ span.setStatus({ code: STATUS_ERROR, message: error.message });
132
+ } else {
133
+ span.setStatus({ code: STATUS_ERROR });
134
+ }
135
+ }
136
+ span.end();
137
+ },
138
+ ),
139
+ );
92
140
 
93
- function popSpan(
94
- map: Map<string, OTelSpan[]>,
95
- key: string,
96
- ): OTelSpan | undefined {
97
- const stack = map.get(key);
98
- if (!stack || stack.length === 0) return undefined;
99
- const span = stack.pop()!;
100
- if (stack.length === 0) map.delete(key);
101
- return span;
141
+ return {
142
+ runner,
143
+ enabled: options.enabled ?? true,
144
+ spans: options.spans,
145
+ };
102
146
  }
103
147
 
104
148
  // ---------------------------------------------------------------------------
105
- // Adapter factory
149
+ // Telemetry sink: discrete-fact instant spans (the `telemetry` slot)
106
150
  // ---------------------------------------------------------------------------
107
151
 
108
152
  /**
109
- * Create a TelemetrySink that maps router lifecycle events to OTel spans.
153
+ * Create a TelemetrySink that maps the router's discrete-fact events to instant
154
+ * OTel spans. One span per fact; no duration spans.
155
+ *
156
+ * Fact mapping:
157
+ * - handler.error -> "rango.handler.error" (error)
158
+ * - cache.decision -> "rango.cache.decision"
159
+ * - revalidation.decision -> "rango.revalidation.decision"
160
+ * - request.timeout -> "rango.request.timeout" (error)
161
+ * - request.origin-rejected -> "rango.request.origin-rejected" (error)
110
162
  *
111
- * Span mapping:
112
- * - request.start / request.end / request.error → "rango.request" span
113
- * - loader.start / loader.end / loader.error → "rango.loader" span
114
- * - handler.error → "rango.handler.error" instant span
115
- * - cache.decision → "rango.cache.decision" instant span
116
- * - revalidation.decision → "rango.revalidation.decision" instant span
163
+ * Request and loader PHASE spans are intentionally NOT emitted here — they are
164
+ * owned by the tracing slot (createOTelTracing) so the two layers cannot produce
165
+ * duplicate rango.request / rango.loader spans. request.start/end and
166
+ * loader.start/end events are no-ops for this sink.
117
167
  *
118
168
  * Attributes use the `rango.*` namespace for router-specific data and
119
169
  * `http.method` / `http.route` for HTTP semantics.
120
170
  */
121
171
  export function createOTelSink(tracer: OTelTracer): TelemetrySink {
122
- const requestSpans = new Map<string, OTelSpan[]>();
123
- const loaderSpans = new Map<string, OTelSpan[]>();
172
+ const instant = (
173
+ name: string,
174
+ attributes: Record<string, string | number | boolean>,
175
+ ): OTelSpan => tracer.startSpan(name, { attributes });
124
176
 
125
177
  return {
126
178
  emit(event: TelemetryEvent): void {
127
179
  switch (event.type) {
128
- // -----------------------------------------------------------------
129
- // Request lifecycle
130
- // -----------------------------------------------------------------
131
-
132
- case "request.start": {
133
- const span = tracer.startSpan("rango.request", {
134
- attributes: {
135
- "http.method": event.method,
136
- "http.route": event.pathname,
137
- "rango.transaction": event.transaction,
138
- "rango.is_partial": event.isPartial,
139
- },
140
- });
141
- pushSpan(requestSpans, requestKey(event), span);
142
- break;
143
- }
144
-
145
- case "request.end": {
146
- const span = popSpan(requestSpans, requestKey(event));
147
- if (span) {
148
- span.setAttribute("rango.duration_ms", event.durationMs);
149
- span.setAttribute("rango.segment_count", event.segmentCount);
150
- span.setAttribute("rango.cache.hit", event.cacheHit);
151
- span.setStatus({ code: STATUS_OK });
152
- span.end();
153
- }
154
- break;
155
- }
156
-
157
- case "request.error": {
158
- const span = popSpan(requestSpans, requestKey(event));
159
- if (span) {
160
- span.setAttribute("rango.duration_ms", event.durationMs);
161
- span.setAttribute("rango.phase", event.phase);
162
- span.recordException(event.error);
163
- span.setStatus({
164
- code: STATUS_ERROR,
165
- message: event.error.message,
166
- });
167
- span.end();
168
- }
169
- break;
170
- }
171
-
172
- // -----------------------------------------------------------------
173
- // Loader lifecycle
174
- // -----------------------------------------------------------------
175
-
176
- case "loader.start": {
177
- const span = tracer.startSpan("rango.loader", {
178
- attributes: {
179
- "rango.segment_id": event.segmentId,
180
- "rango.loader_name": event.loaderName,
181
- "http.route": event.pathname,
182
- },
183
- });
184
- pushSpan(loaderSpans, loaderKey(event), span);
185
- break;
186
- }
187
-
188
- case "loader.end": {
189
- const key = loaderKey(event);
190
- const span = popSpan(loaderSpans, key);
191
- if (span) {
192
- span.setAttribute("rango.duration_ms", event.durationMs);
193
- span.setAttribute("rango.loader.ok", event.ok);
194
- span.setStatus({ code: event.ok ? STATUS_OK : STATUS_ERROR });
195
- span.end();
196
- }
197
- break;
198
- }
199
-
200
- case "loader.error": {
201
- const key = loaderKey(event);
202
- const span = popSpan(loaderSpans, key);
203
- if (span) {
204
- span.setAttribute(
205
- "rango.handled_by_boundary",
206
- event.handledByBoundary,
207
- );
208
- span.recordException(event.error);
209
- span.setStatus({
210
- code: STATUS_ERROR,
211
- message: event.error.message,
212
- });
213
- span.end();
214
- } else {
215
- // No matching start — create a standalone error span
216
- const errorSpan = tracer.startSpan("rango.loader", {
217
- attributes: {
218
- "rango.segment_id": event.segmentId,
219
- "rango.loader_name": event.loaderName,
220
- "http.route": event.pathname,
221
- "rango.handled_by_boundary": event.handledByBoundary,
222
- },
223
- });
224
- errorSpan.recordException(event.error);
225
- errorSpan.setStatus({
226
- code: STATUS_ERROR,
227
- message: event.error.message,
228
- });
229
- errorSpan.end();
230
- }
231
- break;
232
- }
233
-
234
- // -----------------------------------------------------------------
235
- // Handler errors (instant span)
236
- // -----------------------------------------------------------------
237
-
238
180
  case "handler.error": {
239
181
  const attrs: Record<string, string | number | boolean> = {
240
182
  "rango.handled_by_boundary": event.handledByBoundary,
@@ -244,23 +186,16 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
244
186
  attrs["rango.segment_type"] = event.segmentType;
245
187
  if (event.pathname) attrs["http.route"] = event.pathname;
246
188
  if (event.routeKey) attrs["rango.route_key"] = event.routeKey;
247
- if (event.params) {
189
+ if (event.params)
248
190
  attrs["rango.params"] = JSON.stringify(event.params);
249
- }
250
191
 
251
- const span = tracer.startSpan("rango.handler.error", {
252
- attributes: attrs,
253
- });
192
+ const span = instant("rango.handler.error", attrs);
254
193
  span.recordException(event.error);
255
194
  span.setStatus({ code: STATUS_ERROR, message: event.error.message });
256
195
  span.end();
257
196
  break;
258
197
  }
259
198
 
260
- // -----------------------------------------------------------------
261
- // Cache decision (instant span)
262
- // -----------------------------------------------------------------
263
-
264
199
  case "cache.decision": {
265
200
  const attrs: Record<string, string | number | boolean> = {
266
201
  "http.route": event.pathname,
@@ -269,30 +204,55 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
269
204
  "rango.cache.should_revalidate": event.shouldRevalidate,
270
205
  };
271
206
  if (event.source) attrs["rango.cache.source"] = event.source;
207
+ instant("rango.cache.decision", attrs).end();
208
+ break;
209
+ }
272
210
 
273
- const span = tracer.startSpan("rango.cache.decision", {
274
- attributes: attrs,
211
+ case "revalidation.decision": {
212
+ instant("rango.revalidation.decision", {
213
+ "rango.segment_id": event.segmentId,
214
+ "http.route": event.pathname,
215
+ "rango.route_key": event.routeKey,
216
+ "rango.revalidate": event.shouldRevalidate,
217
+ }).end();
218
+ break;
219
+ }
220
+
221
+ case "request.timeout": {
222
+ const attrs: Record<string, string | number | boolean> = {
223
+ "rango.phase": event.phase,
224
+ "http.route": event.pathname,
225
+ "rango.duration_ms": event.durationMs,
226
+ "rango.timeout.custom_handler": event.customHandler,
227
+ };
228
+ if (event.routeKey) attrs["rango.route_key"] = event.routeKey;
229
+ if (event.actionId) attrs["rango.action_id"] = event.actionId;
230
+ const span = instant("rango.request.timeout", attrs);
231
+ span.setStatus({
232
+ code: STATUS_ERROR,
233
+ message: `timeout: ${event.phase}`,
275
234
  });
276
235
  span.end();
277
236
  break;
278
237
  }
279
238
 
280
- // -----------------------------------------------------------------
281
- // Revalidation decision (instant span)
282
- // -----------------------------------------------------------------
283
-
284
- case "revalidation.decision": {
285
- const span = tracer.startSpan("rango.revalidation.decision", {
286
- attributes: {
287
- "rango.segment_id": event.segmentId,
288
- "http.route": event.pathname,
289
- "rango.route_key": event.routeKey,
290
- "rango.revalidate": event.shouldRevalidate,
291
- },
292
- });
239
+ case "request.origin-rejected": {
240
+ const attrs: Record<string, string | number | boolean> = {
241
+ "http.method": event.method,
242
+ "http.route": event.pathname,
243
+ "rango.phase": event.phase,
244
+ };
245
+ if (event.origin) attrs["rango.origin"] = event.origin;
246
+ if (event.host) attrs["http.host"] = event.host;
247
+ const span = instant("rango.request.origin-rejected", attrs);
248
+ span.setStatus({ code: STATUS_ERROR, message: "origin rejected" });
293
249
  span.end();
294
250
  break;
295
251
  }
252
+
253
+ // request.start/end/error and loader.start/end/error are phase events;
254
+ // their spans are owned by the tracing slot (createOTelTracing), so this
255
+ // sink ignores them to avoid duplicate rango.request / rango.loader spans.
296
256
  }
297
257
  },
298
258
  };
@@ -14,10 +14,6 @@
14
14
  * - revalidation.decision (revalidation evaluation)
15
15
  */
16
16
 
17
- // ---------------------------------------------------------------------------
18
- // Event types
19
- // ---------------------------------------------------------------------------
20
-
21
17
  interface BaseEvent {
22
18
  /** Monotonic timestamp from performance.now() */
23
19
  timestamp: number;
@@ -42,6 +38,14 @@ export interface RequestEndEvent extends BaseEvent {
42
38
  durationMs: number;
43
39
  segmentCount: number;
44
40
  cacheHit: boolean;
41
+ /**
42
+ * HTTP status when a Response ended the transaction — a thrown-Response
43
+ * short-circuit (redirect / auth gate carries the Response's status, e.g. 302),
44
+ * or dispatch()'s final response status. Absent for a normal render completion:
45
+ * the Response is built after match(), so match()/matchPartial() have no status
46
+ * to stamp there. Lets a sink split 3xx short-circuits from 2xx completions.
47
+ */
48
+ status?: number;
45
49
  }
46
50
 
47
51
  export interface RequestErrorEvent extends BaseEvent {
@@ -239,10 +243,6 @@ export function formatCacheSignalHeader(
239
243
  return segments.map((s) => `${s.id}=${s.cacheStatus}`).join(", ");
240
244
  }
241
245
 
242
- // ---------------------------------------------------------------------------
243
- // Sink interface
244
- // ---------------------------------------------------------------------------
245
-
246
246
  /**
247
247
  * Telemetry sink receives structured lifecycle events from the router.
248
248
  * Implement this interface to integrate with any observability backend.
@@ -253,10 +253,6 @@ export interface TelemetrySink {
253
253
  emit(event: TelemetryEvent): void;
254
254
  }
255
255
 
256
- // ---------------------------------------------------------------------------
257
- // No-op singleton (zero-cost disabled state)
258
- // ---------------------------------------------------------------------------
259
-
260
256
  const noopSink: TelemetrySink = {
261
257
  emit() {},
262
258
  };
@@ -284,12 +280,6 @@ export function safeEmit(sink: TelemetrySink, event: TelemetryEvent): void {
284
280
  }
285
281
  }
286
282
 
287
- // ---------------------------------------------------------------------------
288
- // Request ID extraction (for span correlation)
289
- // ---------------------------------------------------------------------------
290
-
291
- // Per-request memoization so the same Request object always maps to the
292
- // same ID. WeakMap allows GC when the Request is no longer referenced.
293
283
  const requestIds = new WeakMap<Request, string>();
294
284
  let telemetryRequestCounter = 0;
295
285
 
@@ -323,10 +313,6 @@ export function getRequestId(request: Request): string {
323
313
  return id;
324
314
  }
325
315
 
326
- // ---------------------------------------------------------------------------
327
- // Console sink (built-in, replaces ad-hoc console.log debug traces)
328
- // ---------------------------------------------------------------------------
329
-
330
316
  /**
331
317
  * Built-in console sink that logs events in a structured format.
332
318
  * Designed as the default sink for development / debugging.
@@ -342,7 +328,7 @@ export function createConsoleSink(): TelemetrySink {
342
328
  break;
343
329
  case "request.end":
344
330
  console.log(
345
- `[telemetry] ${event.type} ${event.method} ${event.pathname} ${event.durationMs.toFixed(1)}ms segments=${event.segmentCount} cache=${event.cacheHit}`,
331
+ `[telemetry] ${event.type} ${event.method} ${event.pathname} ${event.durationMs.toFixed(1)}ms segments=${event.segmentCount} cache=${event.cacheHit}${event.status !== undefined ? ` status=${event.status}` : ""}`,
346
332
  );
347
333
  break;
348
334
  case "request.error":
@@ -6,10 +6,6 @@
6
6
  * a Promise.race mechanism, returning 504 on expiry.
7
7
  */
8
8
 
9
- // ---------------------------------------------------------------------------
10
- // Public types
11
- // ---------------------------------------------------------------------------
12
-
13
9
  export interface RouterTimeouts {
14
10
  /** Timeout for server action execution (ms). */
15
11
  actionMs?: number;
@@ -35,10 +31,6 @@ export type OnTimeoutCallback<TEnv = any> = (
35
31
  ctx: TimeoutContext<TEnv>,
36
32
  ) => Response | Promise<Response>;
37
33
 
38
- // ---------------------------------------------------------------------------
39
- // Internal resolved form
40
- // ---------------------------------------------------------------------------
41
-
42
34
  export interface ResolvedTimeouts {
43
35
  actionMs: number | undefined;
44
36
  renderStartMs: number | undefined;
@@ -63,10 +55,6 @@ export function resolveTimeouts(
63
55
  };
64
56
  }
65
57
 
66
- // ---------------------------------------------------------------------------
67
- // Error class
68
- // ---------------------------------------------------------------------------
69
-
70
58
  export class RouterTimeoutError extends Error {
71
59
  override name = "RouterTimeoutError" as const;
72
60
  phase: TimeoutPhase;
@@ -81,10 +69,6 @@ export class RouterTimeoutError extends Error {
81
69
  }
82
70
  }
83
71
 
84
- // ---------------------------------------------------------------------------
85
- // Race helper
86
- // ---------------------------------------------------------------------------
87
-
88
72
  type TimeoutResult<T> =
89
73
  | { result: T; timedOut: false }
90
74
  | { timedOut: true; durationMs: number };
@@ -129,10 +113,6 @@ export async function withTimeout<T>(
129
113
  }
130
114
  }
131
115
 
132
- // ---------------------------------------------------------------------------
133
- // Default response
134
- // ---------------------------------------------------------------------------
135
-
136
116
  /**
137
117
  * Create the default 504 response for a timed-out request.
138
118
  * Includes `X-Rango-Timeout-Phase` header for observability.