@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +105 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +357 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
package/src/client.tsx CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  type ClientErrorBoundaryFallbackProps,
14
14
  type ErrorInfo,
15
15
  type LoaderDefinition,
16
- type LoaderFn,
17
16
  type ResolvedSegment,
18
17
  } from "./types";
19
18
  import {
@@ -22,6 +21,83 @@ import {
22
21
  } from "./route-content-wrapper.js";
23
22
  import { OutletProvider } from "./outlet-provider.js";
24
23
  import { MountContextProvider } from "./browser/react/mount-context.js";
24
+ import { getMemoizedContentPromise } from "./segment-content-promise.js";
25
+
26
+ /**
27
+ * Render the content for a named parallel/intercept slot segment.
28
+ *
29
+ * Shared by Outlet (with `name` prop) and ParallelOutlet — both resolve a
30
+ * segment from context.parallel by slot name and then render it through the
31
+ * same layout/loader/mountPath wrapping pipeline.
32
+ */
33
+ function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
34
+ if (!segment) return null;
35
+
36
+ const content: ReactNode =
37
+ segment.loading || segment.component instanceof Promise ? (
38
+ <RouteContentWrapper
39
+ content={getMemoizedContentPromise(segment.component)}
40
+ fallback={segment.loading}
41
+ segmentId={segment.id}
42
+ />
43
+ ) : (
44
+ (segment.component ?? null)
45
+ );
46
+
47
+ const hasOwnLoaders = !!(segment.loaderDataPromise && segment.loaderIds);
48
+ const loaderWrapped = hasOwnLoaders ? (
49
+ <LoaderBoundary
50
+ loaderDataPromise={segment.loaderDataPromise!}
51
+ loaderIds={segment.loaderIds!}
52
+ fallback={segment.loading}
53
+ outletKey={segment.id + "-loader"}
54
+ outletContent={null}
55
+ segment={segment}
56
+ >
57
+ {content}
58
+ </LoaderBoundary>
59
+ ) : null;
60
+
61
+ let result: ReactNode;
62
+ if (segment.layout) {
63
+ // Layout renders immediately; if loaders exist, the LoaderBoundary becomes
64
+ // the outlet content so layout's <Outlet /> suspends until loaders resolve.
65
+ result = (
66
+ <OutletProvider
67
+ content={hasOwnLoaders ? loaderWrapped : content}
68
+ segment={segment}
69
+ >
70
+ {segment.layout}
71
+ </OutletProvider>
72
+ );
73
+ } else if (hasOwnLoaders) {
74
+ // No layout but has loaders — wrap content with LoaderBoundary for useLoader context.
75
+ // Common for intercept routes that use useLoader without a custom layout.
76
+ result = loaderWrapped;
77
+ } else {
78
+ result = content;
79
+ }
80
+
81
+ if (segment.mountPath) {
82
+ return (
83
+ <MountContextProvider value={segment.mountPath}>
84
+ {result}
85
+ </MountContextProvider>
86
+ );
87
+ }
88
+
89
+ return result;
90
+ }
91
+
92
+ function useSlotSegment(
93
+ context: OutletContextValue | null,
94
+ name: `@${string}` | undefined,
95
+ ): ResolvedSegment | null {
96
+ return useMemo(() => {
97
+ if (!name || !context?.parallel) return null;
98
+ return context.parallel.find((seg) => seg.slot === name) ?? null;
99
+ }, [context, name]);
100
+ }
25
101
 
26
102
  /**
27
103
  * Outlet component - renders child content in layouts
@@ -35,6 +111,11 @@ import { MountContextProvider } from "./browser/react/mount-context.js";
35
111
  * the parallel segment with that slot name instead of the default content.
36
112
  * This is used for parallel routes and intercepting routes.
37
113
  *
114
+ * For a named slot, `<Outlet name="@x" />` is equivalent to
115
+ * `<ParallelOutlet name="@x" />` — both run the same resolution + wrapping
116
+ * pipeline. Convention: use bare `<Outlet />` for default content and
117
+ * `<ParallelOutlet name="@x" />` for named slots.
118
+ *
38
119
  * @param name - Optional slot name for parallel/intercept content (must start with @)
39
120
  *
40
121
  * @example
@@ -62,95 +143,10 @@ import { MountContextProvider } from "./browser/react/mount-context.js";
62
143
  */
63
144
  export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
64
145
  const context = useContext(OutletContext);
146
+ const namedSegment = useSlotSegment(context, name);
65
147
 
66
- // If name provided, render parallel/intercept content for that slot
67
148
  if (name) {
68
- const segment = context?.parallel?.find((seg) => seg.slot === name) ?? null;
69
-
70
- if (!segment) return null;
71
-
72
- // Determine the content to render
73
- let content: ReactNode;
74
- if (segment.loading || segment.component instanceof Promise) {
75
- // Use RouteContentWrapper to handle Suspense wrapping properly
76
- content = (
77
- <RouteContentWrapper
78
- content={
79
- segment.component instanceof Promise
80
- ? segment.component
81
- : Promise.resolve(segment.component)
82
- }
83
- fallback={segment.loading}
84
- segmentId={segment.id}
85
- />
86
- );
87
- } else {
88
- content = segment.component ?? null;
89
- }
90
-
91
- let result: ReactNode;
92
-
93
- // If segment has a layout, wrap appropriately
94
- if (segment.layout) {
95
- // Check if this segment has loaders that need streaming
96
- // The layout renders immediately, LoaderBoundary becomes the outlet content
97
- // When layout renders <Outlet />, it gets the LoaderBoundary which suspends
98
- if (segment.loaderDataPromise && segment.loaderIds) {
99
- const loaderAwareContent = (
100
- <LoaderBoundary
101
- loaderDataPromise={segment.loaderDataPromise}
102
- loaderIds={segment.loaderIds}
103
- fallback={segment.loading}
104
- outletKey={segment.id + "-loader"}
105
- outletContent={null}
106
- segment={segment}
107
- >
108
- {content}
109
- </LoaderBoundary>
110
- );
111
-
112
- result = (
113
- <OutletProvider content={loaderAwareContent} segment={segment}>
114
- {segment.layout}
115
- </OutletProvider>
116
- );
117
- } else {
118
- // No loaders - wrap in OutletProvider so layout can use <Outlet />
119
- result = (
120
- <OutletProvider content={content} segment={segment}>
121
- {segment.layout}
122
- </OutletProvider>
123
- );
124
- }
125
- } else if (segment.loaderDataPromise && segment.loaderIds) {
126
- // No layout but has loaders - wrap content with LoaderBoundary for useLoader context
127
- // This is common for intercept routes that use useLoader without a custom layout
128
- result = (
129
- <LoaderBoundary
130
- loaderDataPromise={segment.loaderDataPromise}
131
- loaderIds={segment.loaderIds}
132
- fallback={segment.loading}
133
- outletKey={segment.id + "-loader"}
134
- outletContent={null}
135
- segment={segment}
136
- >
137
- {content}
138
- </LoaderBoundary>
139
- );
140
- } else {
141
- result = content;
142
- }
143
-
144
- // Wrap with MountContextProvider for include() scoped parallel/intercept slots
145
- if (segment.mountPath) {
146
- return (
147
- <MountContextProvider value={segment.mountPath}>
148
- {result}
149
- </MountContextProvider>
150
- );
151
- }
152
-
153
- return result;
149
+ return renderSlotContent(namedSegment);
154
150
  }
155
151
 
156
152
  // Default: render child content
@@ -164,6 +160,7 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
164
160
 
165
161
  return content;
166
162
  }
163
+
167
164
  /**
168
165
  * ParallelOutlet component - renders content for a named parallel slot
169
166
  *
@@ -171,6 +168,9 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
171
168
  * is wrapped in Suspense with the loading component as fallback.
172
169
  * This enables streaming and navigation loading states for parallels.
173
170
  *
171
+ * Equivalent to `<Outlet name="@x" />` for a named slot; ParallelOutlet
172
+ * requires `name` and is named-slot-only, which reads clearer at the call site.
173
+ *
174
174
  * @param name - The slot name (must start with @, e.g., "@modal", "@sidebar")
175
175
  *
176
176
  * @example
@@ -188,101 +188,15 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
188
188
  */
189
189
  export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
190
190
  const context = useContext(OutletContext);
191
- const segment = useMemo(() => {
192
- if (!context?.parallel) return null;
193
- return context.parallel.find((seg) => seg.slot === name) ?? null;
194
- }, [context, name]);
191
+ const segment = useSlotSegment(context, name);
195
192
 
196
- if (!segment) return null;
197
-
198
- // Determine the content to render
199
- let content: ReactNode;
200
- if (segment.loading || segment.component instanceof Promise) {
201
- // Use RouteContentWrapper to handle Suspense wrapping properly
202
- content = (
203
- <RouteContentWrapper
204
- content={
205
- segment.component instanceof Promise
206
- ? segment.component
207
- : Promise.resolve(segment.component)
208
- }
209
- fallback={segment.loading}
210
- segmentId={segment.id}
211
- />
212
- );
213
- } else {
214
- content = segment.component ?? null;
215
- }
216
-
217
- let result: ReactNode;
218
-
219
- // If segment has a layout, wrap appropriately
220
- if (segment.layout) {
221
- // Check if this segment has loaders that need streaming
222
- // The layout renders immediately, LoaderBoundary becomes the outlet content
223
- if (segment.loaderDataPromise && segment.loaderIds) {
224
- const loaderAwareContent = (
225
- <LoaderBoundary
226
- loaderDataPromise={segment.loaderDataPromise}
227
- loaderIds={segment.loaderIds}
228
- fallback={segment.loading}
229
- outletKey={segment.id + "-loader"}
230
- outletContent={null}
231
- segment={segment}
232
- >
233
- {content}
234
- </LoaderBoundary>
235
- );
236
-
237
- result = (
238
- <OutletProvider content={loaderAwareContent} segment={segment}>
239
- {segment.layout}
240
- </OutletProvider>
241
- );
242
- } else {
243
- // No loaders - wrap in OutletProvider so layout can use <Outlet />
244
- result = (
245
- <OutletProvider content={content} segment={segment}>
246
- {segment.layout}
247
- </OutletProvider>
248
- );
249
- }
250
- } else if (segment.loaderDataPromise && segment.loaderIds) {
251
- // No layout but has loaders - wrap content with LoaderBoundary for useLoader context
252
- // This is common for intercept routes that use useLoader without a custom layout
253
- result = (
254
- <LoaderBoundary
255
- loaderDataPromise={segment.loaderDataPromise}
256
- loaderIds={segment.loaderIds}
257
- fallback={segment.loading}
258
- outletKey={segment.id + "-loader"}
259
- outletContent={null}
260
- segment={segment}
261
- >
262
- {content}
263
- </LoaderBoundary>
264
- );
265
- } else {
266
- result = content;
267
- }
268
-
269
- // Wrap with MountContextProvider for include() scoped parallel/intercept slots
270
- if (segment.mountPath) {
271
- return (
272
- <MountContextProvider value={segment.mountPath}>
273
- {result}
274
- </MountContextProvider>
275
- );
276
- }
277
-
278
- return result;
193
+ return renderSlotContent(segment);
279
194
  }
280
195
 
281
196
  // OutletProvider is defined in outlet-provider.tsx to break a circular
282
- // dependency between client.tsx and route-content-wrapper.tsx.
283
- // Imported at the top of this file for local use in Outlet/ParallelOutlet,
284
- // and re-exported here for backwards compatibility.
285
- export { OutletProvider };
197
+ // dependency between client.tsx and route-content-wrapper.tsx. It is imported
198
+ // at the top of this file for local use in Outlet/ParallelOutlet only; it is an
199
+ // internal component and is intentionally not part of the public ./client API.
286
200
 
287
201
  /**
288
202
  * Hook to access outlet content programmatically
@@ -303,67 +217,16 @@ export function useOutlet(): ReactNode {
303
217
  return context?.content ?? null;
304
218
  }
305
219
 
306
- // Loader hooks - re-exported from dedicated file
307
220
  export {
308
221
  useLoader,
309
222
  useFetchLoader,
223
+ useRefreshLoaders,
310
224
  type LoadFunction,
311
225
  type UseLoaderResult,
312
226
  type UseFetchLoaderResult,
313
227
  type UseLoaderOptions,
314
228
  } from "./use-loader.js";
315
229
 
316
- /**
317
- * Client-safe createLoader factory
318
- *
319
- * Creates a loader definition that can be used with useLoader().
320
- * This is the client-side version that only stores the $$id - the function
321
- * is ignored since loaders only execute on the server.
322
- *
323
- * The $$id is injected by the exposeLoaderId Vite plugin. In most cases,
324
- * you should import the loader directly from the server file rather than
325
- * creating a reference manually.
326
- *
327
- * @param fn - Loader function (ignored on client, kept for API compatibility)
328
- * @param _fetchable - Optional fetchable flag (ignored on client)
329
- * @param __injectedId - $$id injected by Vite plugin
330
- *
331
- * @example
332
- * ```tsx
333
- * "use client";
334
- * import { useLoader } from "rsc-router/client";
335
- * import { CartLoader } from "../loaders/cart"; // Import from server file
336
- *
337
- * export function CartIcon() {
338
- * const cart = useLoader(CartLoader);
339
- * return <span>Cart ({cart?.items.length ?? 0})</span>;
340
- * }
341
- * ```
342
- */
343
- // Overload 1: With function only (not fetchable)
344
- export function createLoader<T>(
345
- fn: LoaderFn<T, Record<string, string | undefined>, any>,
346
- ): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
347
-
348
- // Overload 2: With function and fetchable flag
349
- export function createLoader<T>(
350
- fn: LoaderFn<T, Record<string, string | undefined>, any>,
351
- fetchable: true,
352
- ): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
353
-
354
- // Implementation - function is ignored at runtime on client
355
- // The $$id is injected by Vite plugin as hidden third parameter
356
- export function createLoader(
357
- _fn: LoaderFn<any, Record<string, string | undefined>, any>,
358
- _fetchable?: true,
359
- __injectedId?: string,
360
- ): LoaderDefinition<any, Record<string, string | undefined>> {
361
- return {
362
- __brand: "loader",
363
- $$id: __injectedId || "",
364
- };
365
- }
366
-
367
230
  /**
368
231
  * Props for the ErrorBoundary component
369
232
  */
@@ -472,81 +335,75 @@ export class ErrorBoundary extends Component<
472
335
  }
473
336
  }
474
337
 
475
- // ============================================================================
476
- // Re-exports from browser/react for convenience
477
- // These are the most commonly used client-side navigation utilities
478
- // ============================================================================
479
-
480
- // Navigation hooks
481
338
  export { useNavigation } from "./browser/react/use-navigation.js";
482
339
  export { useRouter } from "./browser/react/use-router.js";
483
340
  export { usePathname } from "./browser/react/use-pathname.js";
484
341
  export { useSearchParams } from "./browser/react/use-search-params.js";
485
342
  export { useParams } from "./browser/react/use-params.js";
343
+ // CSP nonce for the active request, for userland components that inject their
344
+ // own <script>/<style> into the document head (analytics, GTM, inline init).
345
+ // Returns the nonce during SSR and undefined in the browser; render the tag
346
+ // server-side so the nonce lands in the SSR HTML and hydration stays clean.
347
+ export { useNonce } from "./browser/react/nonce-context.js";
486
348
  export type {
487
349
  RouterInstance,
488
350
  RouterNavigateOptions,
489
351
  ReadonlyURLSearchParams,
352
+ ActionState,
353
+ ActionLifecycleState,
490
354
  } from "./browser/types.js";
491
355
 
492
- // Action state tracking hook
493
356
  export {
494
357
  useAction,
495
358
  type ServerActionFunction,
496
359
  } from "./browser/react/use-action.js";
497
360
 
498
- // Segments state hook
499
361
  export {
500
362
  useSegments,
501
363
  type SegmentsState,
502
364
  } from "./browser/react/use-segments.js";
503
365
 
504
- // Client cache controls hook
505
- export {
506
- useClientCache,
507
- type ClientCacheControls,
508
- } from "./browser/react/use-client-cache.js";
509
-
510
- // Provider
511
366
  export {
512
367
  NavigationProvider,
513
368
  type NavigationProviderProps,
514
369
  } from "./browser/react/NavigationProvider.js";
515
370
 
516
- // Link component
517
371
  export {
518
372
  Link,
519
373
  type LinkProps,
520
374
  type PrefetchStrategy,
521
375
  type StateOrGetter,
376
+ type LinkState,
522
377
  } from "./browser/react/Link.js";
523
378
 
524
- // Link status hook
525
379
  export {
526
380
  useLinkStatus,
527
381
  type LinkStatus,
528
382
  } from "./browser/react/use-link-status.js";
529
383
 
530
- // Scroll restoration
531
384
  export {
532
385
  ScrollRestoration,
533
386
  useScrollRestoration,
534
387
  type ScrollRestorationProps,
535
388
  } from "./browser/react/ScrollRestoration.js";
536
389
 
537
- // Handle API - for accumulating data across route segments
538
- export { createHandle, isHandle, type Handle } from "./handle.js";
539
-
540
- // Handle data hook
390
+ export { type Handle } from "./handle.js";
541
391
  export { useHandle } from "./browser/react/use-handle.js";
392
+ // Type a deferred-aware consumer narrows: an accumulated entry may be a Promise
393
+ // (a `ctx.use(Handle).defer()` slot) until it resolves.
394
+ export type { DeferredHandleEntry } from "./defer.js";
542
395
 
543
- // Built-in handles
544
396
  export { Meta } from "./handles/meta.js";
545
397
  export { MetaTags } from "./handles/MetaTags.js";
546
398
  export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
399
+ export {
400
+ Script,
401
+ type ScriptConfig,
402
+ type ScriptAttributes,
403
+ } from "./handles/script.js";
404
+ export { Scripts } from "./handles/Scripts.js";
547
405
  export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
548
406
 
549
- // Location state - type-safe navigation state
550
407
  export {
551
408
  createLocationState,
552
409
  useLocationState,
@@ -555,47 +412,19 @@ export {
555
412
  type LocationStateOptions,
556
413
  } from "./browser/react/location-state.js";
557
414
 
558
- // Type-safe href for client-side path validation
559
- export {
560
- href,
561
- type ValidPaths,
562
- type PatternToPath,
563
- type PathResponse,
564
- } from "./href-client.js";
415
+ // Ambient Rango.Path / Rango.PathResponse types (declared in href-client.ts)
416
+ export { href, type PatternToPath } from "./href-client.js";
565
417
 
566
- // Response envelope types for consuming JSON response routes
567
- export type { ResponseEnvelope, ResponseError } from "./urls.js";
418
+ // RFC 9457 error type for JSON response routes
419
+ export type { ProblemDetails } from "./urls.js";
568
420
 
569
- /**
570
- * Type guard for checking if a response envelope contains an error.
571
- *
572
- * @example
573
- * ```typescript
574
- * const result: ResponseEnvelope<Product> = await fetch(url).then(r => r.json());
575
- * if (isResponseError(result)) {
576
- * console.log(result.error.message, result.error.code);
577
- * return;
578
- * }
579
- * result.data // fully typed as Product
580
- * ```
581
- */
582
- export function isResponseError<T>(
583
- result: import("./urls.js").ResponseEnvelope<T>,
584
- ): result is import("./urls.js").ResponseEnvelope<T> & {
585
- error: import("./urls.js").ResponseError;
586
- } {
587
- return result.error !== undefined;
588
- }
589
-
590
- // Mount context for include() scoped components
591
421
  export { useMount } from "./browser/react/use-mount.js";
592
422
  export { MountContext } from "./browser/react/mount-context.js";
593
423
 
594
- // Mount-aware href hook - auto-prefixes paths with include() mount
595
424
  export { useHref } from "./browser/react/use-href.js";
596
425
 
597
- // Type-safe scoped reverse function for scopedReverse<typeof patterns>()
598
- export type { ScopedReverseFunction } from "./reverse.js";
426
+ export { useReverse } from "./browser/react/use-reverse.js";
427
+
428
+ export type { ScopedReverseFunction, LocalReverseFunction } from "./reverse.js";
599
429
 
600
- // Loader definition type - for typing loader props in client components
601
430
  export type { LoaderDefinition } from "./types.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Cloudflare preset integration surface for @rangojs/router.
3
+ *
4
+ * Imported via `@rangojs/router/cloudflare`. Cloudflare-only helpers live here
5
+ * so Node/other-platform consumers never pull them in.
6
+ */
7
+
8
+ export {
9
+ createCloudflareTracing,
10
+ type CloudflareTracingOptions,
11
+ } from "./tracing.js";
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Cloudflare custom-spans integration.
3
+ *
4
+ * Bridges the router's performance phases (request, middleware, action,
5
+ * loaders, handler, render, ssr) onto Cloudflare Workers custom spans so they show
6
+ * up in the trace waterfall and OpenTelemetry exports next to the platform's
7
+ * automatic spans (KV reads, D1 queries, fetch calls), with correct nesting.
8
+ *
9
+ * Usage (Cloudflare preset only):
10
+ *
11
+ * import { createRouter } from "@rangojs/router";
12
+ * import { createCloudflareTracing } from "@rangojs/router/cloudflare";
13
+ *
14
+ * export const router = createRouter({
15
+ * tracing: createCloudflareTracing(),
16
+ * });
17
+ *
18
+ * The runner reads `executionContext.tracing` (the same object as
19
+ * `import { tracing } from "cloudflare:workers"`) at call time. It is therefore
20
+ * dependency-free: no `cloudflare:workers` import and no hard dependency on
21
+ * `@cloudflare/workers-types`, matching the convention in cache/cf. When the
22
+ * worker is not running on a tracing-enabled Cloudflare runtime — Node, dev
23
+ * without a tracing destination, an older runtime — `executionContext.tracing`
24
+ * is undefined and every span call falls through to the work directly, so the
25
+ * request behaves exactly as if tracing were off. Whether spans are actually
26
+ * recorded is governed by the `observability`/tracing block in wrangler config.
27
+ *
28
+ * Span duration note: enterSpan ends a span when its callback's returned value
29
+ * (or promise) settles. For the streaming phases (request/render/ssr) that is at
30
+ * stream CONSTRUCTION, not body-drain. Instrumentation is best-effort and never
31
+ * wraps or buffers the response body, so it cannot regress streaming or latency.
32
+ * A loader/Suspense child that resolves mid-stream therefore keeps a rango.loader
33
+ * span that can extend past its render parent — overlapping spans are valid. Uses
34
+ * only the typed enterSpan API; spans bound work up to stream-handoff, matching
35
+ * the co-emitted perf metric.
36
+ */
37
+
38
+ import { _getRequestContext } from "../server/request-context.js";
39
+ import {
40
+ type RouterTracingConfig,
41
+ type SpanRunner,
42
+ type TracePhaseToggles,
43
+ NOOP_TRACE_SPAN,
44
+ } from "../router/tracing.js";
45
+
46
+ /**
47
+ * Minimal local view of Cloudflare's `Span`. Declared here (not imported from
48
+ * `@cloudflare/workers-types`) to avoid a hard type dependency.
49
+ */
50
+ interface CloudflareSpan {
51
+ readonly isTraced: boolean;
52
+ setAttribute(key: string, value?: boolean | number | string): void;
53
+ }
54
+
55
+ /** Minimal local view of Cloudflare's `Tracing` (only enterSpan is used). */
56
+ interface CloudflareTracing {
57
+ enterSpan<T>(name: string, callback: (span: CloudflareSpan) => T): T;
58
+ }
59
+
60
+ /** Options for createCloudflareTracing. */
61
+ export interface CloudflareTracingOptions {
62
+ /** Master switch. Defaults to true. */
63
+ enabled?: boolean;
64
+ /** Per-phase span toggles. Omitted phases default to enabled. */
65
+ spans?: TracePhaseToggles;
66
+ }
67
+
68
+ /**
69
+ * Resolve the per-request Cloudflare tracer from the active execution context.
70
+ * Returns undefined off-Cloudflare or when tracing is not enabled for the
71
+ * worker, in which case the caller runs the work without a span.
72
+ */
73
+ function getRequestTracer(): CloudflareTracing | undefined {
74
+ const executionContext = _getRequestContext()?.executionContext as
75
+ | { tracing?: CloudflareTracing }
76
+ | undefined;
77
+ const tracing = executionContext?.tracing;
78
+ return tracing && typeof tracing.enterSpan === "function"
79
+ ? tracing
80
+ : undefined;
81
+ }
82
+
83
+ const cloudflareSpanRunner: SpanRunner = (name, fn) => {
84
+ const tracer = getRequestTracer();
85
+ if (!tracer) return fn(NOOP_TRACE_SPAN);
86
+ // enterSpan runs the callback now and ends the span when its return value
87
+ // (or returned promise) settles; spans nest by JS async context. fn's param
88
+ // is the narrower TraceSpan, so the wider CloudflareSpan satisfies it directly.
89
+ return tracer.enterSpan(name, fn);
90
+ };
91
+
92
+ /**
93
+ * Create the tracing config for a Cloudflare router. Pass the result to
94
+ * `createRouter({ tracing })`. Spans are emitted for the request, middleware,
95
+ * action, loaders, handler, render, and ssr phases; pass `spans` to turn
96
+ * individual phases off.
97
+ *
98
+ * @see createOTelTracing (`@rangojs/router`) for the same slot on any platform
99
+ * with an OpenTelemetry SDK.
100
+ */
101
+ export function createCloudflareTracing(
102
+ options: CloudflareTracingOptions = {},
103
+ ): RouterTracingConfig {
104
+ return {
105
+ runner: cloudflareSpanRunner,
106
+ enabled: options.enabled ?? true,
107
+ spans: options.spans,
108
+ };
109
+ }