@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17

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 (376) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +198 -44
  3. package/dist/bin/rango.js +287 -105
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3248 -1117
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +73 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +107 -1
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +245 -21
  12. package/skills/caching/SKILL.md +302 -6
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +364 -0
  17. package/skills/hooks/SKILL.md +270 -30
  18. package/skills/host-router/SKILL.md +82 -22
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +49 -5
  21. package/skills/layout/SKILL.md +35 -9
  22. package/skills/links/SKILL.md +249 -17
  23. package/skills/loader/SKILL.md +294 -30
  24. package/skills/middleware/SKILL.md +52 -13
  25. package/skills/migrate-nextjs/SKILL.md +584 -0
  26. package/skills/migrate-react-router/SKILL.md +769 -0
  27. package/skills/mime-routes/SKILL.md +27 -0
  28. package/skills/observability/SKILL.md +137 -0
  29. package/skills/parallel/SKILL.md +203 -7
  30. package/skills/prerender/SKILL.md +123 -100
  31. package/skills/rango/SKILL.md +250 -22
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +122 -47
  34. package/skills/route/SKILL.md +97 -5
  35. package/skills/router-setup/SKILL.md +90 -5
  36. package/skills/server-actions/SKILL.md +775 -0
  37. package/skills/streams-and-websockets/SKILL.md +283 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +129 -0
  40. package/skills/testing/bindings.md +89 -0
  41. package/skills/testing/cache-prerender.md +124 -0
  42. package/skills/testing/client-components.md +122 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +92 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +121 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +120 -0
  54. package/skills/typesafety/SKILL.md +329 -27
  55. package/skills/use-cache/SKILL.md +36 -5
  56. package/skills/view-transitions/SKILL.md +294 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +67 -40
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/app-version.ts +14 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +86 -147
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/link-interceptor.ts +4 -0
  70. package/src/browser/navigation-bridge.ts +148 -19
  71. package/src/browser/navigation-client.ts +187 -67
  72. package/src/browser/navigation-store-handle.ts +38 -0
  73. package/src/browser/navigation-store.ts +76 -67
  74. package/src/browser/navigation-transaction.ts +18 -66
  75. package/src/browser/partial-update.ts +123 -94
  76. package/src/browser/prefetch/cache.ts +214 -36
  77. package/src/browser/prefetch/fetch.ts +260 -38
  78. package/src/browser/prefetch/policy.ts +6 -0
  79. package/src/browser/prefetch/queue.ts +126 -20
  80. package/src/browser/prefetch/resource-ready.ts +77 -0
  81. package/src/browser/rango-state.ts +158 -76
  82. package/src/browser/react/Link.tsx +93 -11
  83. package/src/browser/react/NavigationProvider.tsx +115 -34
  84. package/src/browser/react/ScrollRestoration.tsx +10 -6
  85. package/src/browser/react/context.ts +7 -2
  86. package/src/browser/react/filter-segment-order.ts +49 -7
  87. package/src/browser/react/index.ts +0 -48
  88. package/src/browser/react/location-state-shared.ts +166 -8
  89. package/src/browser/react/location-state.ts +39 -14
  90. package/src/browser/react/use-action.ts +6 -15
  91. package/src/browser/react/use-handle.ts +23 -69
  92. package/src/browser/react/use-link-status.ts +0 -4
  93. package/src/browser/react/use-navigation.ts +22 -5
  94. package/src/browser/react/use-params.ts +20 -10
  95. package/src/browser/react/use-reverse.ts +106 -0
  96. package/src/browser/react/use-router.ts +46 -11
  97. package/src/browser/react/use-search-params.ts +0 -5
  98. package/src/browser/react/use-segments.ts +11 -21
  99. package/src/browser/response-adapter.ts +52 -1
  100. package/src/browser/rsc-router.tsx +215 -76
  101. package/src/browser/scroll-restoration.ts +46 -39
  102. package/src/browser/segment-reconciler.ts +36 -9
  103. package/src/browser/segment-structure-assert.ts +2 -2
  104. package/src/browser/server-action-bridge.ts +176 -50
  105. package/src/browser/types.ts +95 -11
  106. package/src/browser/validate-redirect-origin.ts +43 -16
  107. package/src/build/collect-fallback-refs.ts +107 -0
  108. package/src/build/generate-manifest.ts +65 -40
  109. package/src/build/generate-route-types.ts +5 -0
  110. package/src/build/index.ts +8 -2
  111. package/src/build/prefix-tree-utils.ts +123 -0
  112. package/src/build/route-trie.ts +137 -32
  113. package/src/build/route-types/codegen.ts +4 -4
  114. package/src/build/route-types/include-resolution.ts +9 -2
  115. package/src/build/route-types/param-extraction.ts +6 -3
  116. package/src/build/route-types/per-module-writer.ts +7 -4
  117. package/src/build/route-types/router-processing.ts +278 -96
  118. package/src/build/route-types/scan-filter.ts +9 -2
  119. package/src/build/route-types/source-scan.ts +118 -0
  120. package/src/build/runtime-discovery.ts +9 -20
  121. package/src/cache/cache-error.ts +104 -0
  122. package/src/cache/cache-policy.ts +68 -28
  123. package/src/cache/cache-runtime.ts +149 -43
  124. package/src/cache/cache-scope.ts +148 -81
  125. package/src/cache/cache-tag.ts +98 -0
  126. package/src/cache/cf/cf-cache-store.ts +2550 -93
  127. package/src/cache/cf/index.ts +11 -17
  128. package/src/cache/document-cache.ts +78 -27
  129. package/src/cache/handle-snapshot.ts +63 -0
  130. package/src/cache/index.ts +23 -20
  131. package/src/cache/memory-segment-store.ts +136 -37
  132. package/src/cache/profile-registry.ts +6 -30
  133. package/src/cache/read-through-swr.ts +41 -11
  134. package/src/cache/segment-codec.ts +0 -16
  135. package/src/cache/tag-invalidation.ts +230 -0
  136. package/src/cache/taint.ts +55 -0
  137. package/src/cache/types.ts +33 -100
  138. package/src/cache/vercel/index.ts +11 -0
  139. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  140. package/src/client.rsc.tsx +6 -21
  141. package/src/client.tsx +108 -290
  142. package/src/component-utils.ts +19 -0
  143. package/src/context-var.ts +84 -2
  144. package/src/debug.ts +2 -2
  145. package/src/decode-loader-results.ts +36 -0
  146. package/src/defer.ts +196 -0
  147. package/src/deps/ssr.ts +0 -1
  148. package/src/errors.ts +30 -4
  149. package/src/handle.ts +70 -22
  150. package/src/handles/MetaTags.tsx +0 -14
  151. package/src/handles/breadcrumbs.ts +16 -5
  152. package/src/handles/meta.ts +0 -39
  153. package/src/host/cookie-handler.ts +0 -36
  154. package/src/host/errors.ts +0 -24
  155. package/src/host/index.ts +8 -2
  156. package/src/host/pattern-matcher.ts +7 -50
  157. package/src/host/router.ts +107 -99
  158. package/src/host/testing.ts +40 -27
  159. package/src/host/types.ts +37 -4
  160. package/src/host/utils.ts +1 -1
  161. package/src/href-client.ts +137 -22
  162. package/src/index.rsc.ts +52 -26
  163. package/src/index.ts +100 -38
  164. package/src/internal-debug.ts +2 -4
  165. package/src/loader-store.ts +500 -0
  166. package/src/loader.rsc.ts +20 -13
  167. package/src/loader.ts +12 -11
  168. package/src/missing-id-error.ts +68 -0
  169. package/src/network-error-thrower.tsx +1 -6
  170. package/src/outlet-context.ts +1 -1
  171. package/src/outlet-provider.tsx +1 -5
  172. package/src/prerender/param-hash.ts +10 -11
  173. package/src/prerender/store.ts +37 -41
  174. package/src/prerender.ts +198 -82
  175. package/src/redirect-origin.ts +100 -0
  176. package/src/response-utils.ts +37 -0
  177. package/src/reverse.ts +65 -15
  178. package/src/root-error-boundary.tsx +1 -19
  179. package/src/route-content-wrapper.tsx +7 -72
  180. package/src/route-definition/dsl-helpers.ts +437 -274
  181. package/src/route-definition/helper-factories.ts +29 -139
  182. package/src/route-definition/helpers-types.ts +113 -37
  183. package/src/route-definition/index.ts +3 -0
  184. package/src/route-definition/redirect.ts +52 -10
  185. package/src/route-definition/resolve-handler-use.ts +161 -0
  186. package/src/route-definition/use-item-types.ts +32 -0
  187. package/src/route-map-builder.ts +7 -17
  188. package/src/route-types.ts +37 -41
  189. package/src/router/basename.ts +14 -0
  190. package/src/router/content-negotiation.ts +108 -9
  191. package/src/router/error-handling.ts +13 -17
  192. package/src/router/find-match.ts +45 -22
  193. package/src/router/handler-context.ts +83 -41
  194. package/src/router/intercept-resolution.ts +25 -23
  195. package/src/router/lazy-includes.ts +19 -53
  196. package/src/router/loader-resolution.ts +213 -30
  197. package/src/router/logging.ts +5 -8
  198. package/src/router/manifest.ts +49 -45
  199. package/src/router/match-api.ts +120 -204
  200. package/src/router/match-context.ts +0 -22
  201. package/src/router/match-handlers.ts +58 -58
  202. package/src/router/match-middleware/background-revalidation.ts +27 -6
  203. package/src/router/match-middleware/cache-lookup.ts +205 -249
  204. package/src/router/match-middleware/cache-store.ts +45 -32
  205. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  206. package/src/router/match-middleware/segment-resolution.ts +52 -18
  207. package/src/router/match-pipelines.ts +1 -42
  208. package/src/router/match-result.ts +104 -40
  209. package/src/router/metrics.ts +5 -34
  210. package/src/router/middleware-types.ts +13 -142
  211. package/src/router/middleware.ts +173 -143
  212. package/src/router/navigation-snapshot.ts +131 -0
  213. package/src/router/params-util.ts +23 -0
  214. package/src/router/pattern-matching.ts +109 -63
  215. package/src/router/prerender-match.ts +190 -54
  216. package/src/router/preview-match.ts +32 -102
  217. package/src/router/request-classification.ts +276 -0
  218. package/src/router/revalidation.ts +63 -55
  219. package/src/router/route-snapshot.ts +244 -0
  220. package/src/router/router-context.ts +6 -28
  221. package/src/router/router-interfaces.ts +100 -35
  222. package/src/router/router-options.ts +91 -11
  223. package/src/router/router-registry.ts +2 -5
  224. package/src/router/segment-resolution/fresh.ts +242 -75
  225. package/src/router/segment-resolution/helpers.ts +63 -24
  226. package/src/router/segment-resolution/loader-cache.ts +41 -37
  227. package/src/router/segment-resolution/revalidation.ts +456 -372
  228. package/src/router/segment-resolution/static-store.ts +19 -5
  229. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  230. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  231. package/src/router/segment-resolution.ts +4 -1
  232. package/src/router/segment-wrappers.ts +2 -3
  233. package/src/router/state-cookie-name.ts +33 -0
  234. package/src/router/substitute-pattern-params.ts +56 -0
  235. package/src/router/telemetry-otel.ts +0 -20
  236. package/src/router/telemetry.ts +96 -19
  237. package/src/router/timeout.ts +0 -20
  238. package/src/router/trie-matching.ts +91 -46
  239. package/src/router/types.ts +10 -63
  240. package/src/router/url-params.ts +44 -0
  241. package/src/router.ts +134 -43
  242. package/src/rsc/handler-context.ts +3 -2
  243. package/src/rsc/handler.ts +492 -383
  244. package/src/rsc/helpers.ts +162 -46
  245. package/src/rsc/index.ts +1 -1
  246. package/src/rsc/json-route-result.ts +38 -0
  247. package/src/rsc/loader-fetch.ts +23 -3
  248. package/src/rsc/manifest-init.ts +33 -42
  249. package/src/rsc/origin-guard.ts +39 -25
  250. package/src/rsc/progressive-enhancement.ts +30 -3
  251. package/src/rsc/redirect-guard.ts +99 -0
  252. package/src/rsc/response-error.ts +79 -12
  253. package/src/rsc/response-route-handler.ts +90 -63
  254. package/src/rsc/rsc-rendering.ts +56 -54
  255. package/src/rsc/runtime-warnings.ts +23 -10
  256. package/src/rsc/server-action.ts +74 -67
  257. package/src/rsc/ssr-setup.ts +18 -2
  258. package/src/rsc/types.ts +25 -6
  259. package/src/runtime-env.ts +18 -0
  260. package/src/search-params.ts +4 -20
  261. package/src/segment-content-promise.ts +67 -0
  262. package/src/segment-loader-promise.ts +134 -0
  263. package/src/segment-system.tsx +272 -129
  264. package/src/serialize.ts +243 -0
  265. package/src/server/context.ts +309 -61
  266. package/src/server/cookie-store.ts +80 -5
  267. package/src/server/handle-store.ts +26 -24
  268. package/src/server/loader-registry.ts +10 -28
  269. package/src/server/request-context.ts +338 -126
  270. package/src/ssr/index.tsx +23 -15
  271. package/src/static-handler.ts +27 -18
  272. package/src/testing/cache-status.ts +162 -0
  273. package/src/testing/collect-handle.ts +40 -0
  274. package/src/testing/dispatch.ts +618 -0
  275. package/src/testing/dom.entry.ts +22 -0
  276. package/src/testing/e2e/fixture.ts +188 -0
  277. package/src/testing/e2e/index.ts +128 -0
  278. package/src/testing/e2e/matchers.ts +35 -0
  279. package/src/testing/e2e/page-helpers.ts +272 -0
  280. package/src/testing/e2e/parity.ts +387 -0
  281. package/src/testing/e2e/server.ts +195 -0
  282. package/src/testing/flight-matchers.ts +97 -0
  283. package/src/testing/flight-normalize.ts +11 -0
  284. package/src/testing/flight-runtime.d.ts +57 -0
  285. package/src/testing/flight-tree.ts +682 -0
  286. package/src/testing/flight.entry.ts +52 -0
  287. package/src/testing/flight.ts +232 -0
  288. package/src/testing/generated-routes.ts +183 -0
  289. package/src/testing/index.ts +99 -0
  290. package/src/testing/internal/context.ts +348 -0
  291. package/src/testing/internal/flight-client-globals.ts +30 -0
  292. package/src/testing/internal/seed-vars.ts +54 -0
  293. package/src/testing/render-handler.ts +330 -0
  294. package/src/testing/render-route.tsx +566 -0
  295. package/src/testing/run-loader.ts +378 -0
  296. package/src/testing/run-middleware.ts +205 -0
  297. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  298. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  299. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  300. package/src/testing/vitest-stubs/version.ts +5 -0
  301. package/src/testing/vitest.ts +305 -0
  302. package/src/theme/ThemeProvider.tsx +0 -52
  303. package/src/theme/ThemeScript.tsx +0 -6
  304. package/src/theme/constants.ts +0 -12
  305. package/src/theme/index.ts +0 -7
  306. package/src/theme/theme-context.ts +1 -5
  307. package/src/theme/theme-script.ts +0 -14
  308. package/src/theme/use-theme.ts +0 -3
  309. package/src/types/boundaries.ts +0 -35
  310. package/src/types/cache-types.ts +17 -8
  311. package/src/types/error-types.ts +30 -90
  312. package/src/types/global-namespace.ts +54 -41
  313. package/src/types/handler-context.ts +233 -81
  314. package/src/types/index.ts +1 -10
  315. package/src/types/loader-types.ts +44 -15
  316. package/src/types/request-scope.ts +107 -0
  317. package/src/types/route-config.ts +6 -50
  318. package/src/types/route-entry.ts +19 -7
  319. package/src/types/segments.ts +37 -14
  320. package/src/urls/include-helper.ts +33 -70
  321. package/src/urls/index.ts +1 -11
  322. package/src/urls/path-helper-types.ts +58 -11
  323. package/src/urls/path-helper.ts +57 -111
  324. package/src/urls/pattern-types.ts +48 -19
  325. package/src/urls/response-types.ts +25 -22
  326. package/src/urls/type-extraction.ts +58 -139
  327. package/src/urls/urls-function.ts +1 -18
  328. package/src/use-loader.tsx +346 -89
  329. package/src/vite/debug.ts +185 -0
  330. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  331. package/src/vite/discovery/discover-routers.ts +130 -85
  332. package/src/vite/discovery/discovery-errors.ts +194 -0
  333. package/src/vite/discovery/gate-state.ts +171 -0
  334. package/src/vite/discovery/prerender-collection.ts +192 -99
  335. package/src/vite/discovery/route-types-writer.ts +40 -84
  336. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  337. package/src/vite/discovery/state.ts +51 -6
  338. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  339. package/src/vite/index.ts +8 -0
  340. package/src/vite/plugin-types.ts +187 -69
  341. package/src/vite/plugins/cjs-to-esm.ts +8 -18
  342. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  343. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  344. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  345. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  346. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  347. package/src/vite/plugins/expose-action-id.ts +49 -98
  348. package/src/vite/plugins/expose-id-utils.ts +11 -50
  349. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  350. package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
  351. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  352. package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
  353. package/src/vite/plugins/expose-internal-ids.ts +554 -317
  354. package/src/vite/plugins/performance-tracks.ts +89 -0
  355. package/src/vite/plugins/refresh-cmd.ts +89 -27
  356. package/src/vite/plugins/use-cache-transform.ts +73 -83
  357. package/src/vite/plugins/vercel-output.ts +258 -0
  358. package/src/vite/plugins/version-injector.ts +21 -25
  359. package/src/vite/plugins/version-plugin.ts +41 -20
  360. package/src/vite/plugins/virtual-entries.ts +2 -17
  361. package/src/vite/rango.ts +257 -289
  362. package/src/vite/router-discovery.ts +930 -140
  363. package/src/vite/utils/ast-handler-extract.ts +15 -31
  364. package/src/vite/utils/banner.ts +4 -4
  365. package/src/vite/utils/bundle-analysis.ts +10 -15
  366. package/src/vite/utils/client-chunks.ts +184 -0
  367. package/src/vite/utils/forward-user-plugins.ts +171 -0
  368. package/src/vite/utils/manifest-utils.ts +4 -59
  369. package/src/vite/utils/package-resolution.ts +20 -52
  370. package/src/vite/utils/prerender-utils.ts +27 -29
  371. package/src/vite/utils/shared-utils.ts +92 -42
  372. package/src/browser/action-response-classifier.ts +0 -99
  373. package/src/browser/react/use-client-cache.ts +0 -58
  374. package/src/browser/shallow.ts +0 -40
  375. package/src/handles/index.ts +0 -7
  376. package/src/router/middleware-cookies.ts +0 -55
@@ -8,6 +8,7 @@
8
8
  import type { ReactNode } from "react";
9
9
  import { _getRequestContext } from "../../server/request-context.js";
10
10
  import type { StaticStore } from "../../prerender/store.js";
11
+ import type { SegmentHandleData } from "../../cache/types.js";
11
12
 
12
13
  // Lazy-initialized static store for production Static handler interception.
13
14
  // Remains undefined until first check; null means checked but no manifest.
@@ -19,6 +20,9 @@ let _staticStore: StaticStore | null | undefined =
19
20
  ? undefined
20
21
  : null;
21
22
  let _deserializeComponent: ((encoded: string) => Promise<unknown>) | undefined;
23
+ let _decodeHandleValue:
24
+ | typeof import("../../cache/handle-snapshot.js").decodeHandleValue
25
+ | undefined;
22
26
 
23
27
  async function ensureStaticDeps(): Promise<void> {
24
28
  if (_staticStore === undefined) {
@@ -29,6 +33,9 @@ async function ensureStaticDeps(): Promise<void> {
29
33
  const { deserializeComponent } =
30
34
  await import("../../cache/segment-codec.js");
31
35
  _deserializeComponent = deserializeComponent;
36
+ const { decodeHandleValue } =
37
+ await import("../../cache/handle-snapshot.js");
38
+ _decodeHandleValue = decodeHandleValue;
32
39
  }
33
40
  }
34
41
 
@@ -53,13 +60,20 @@ export async function tryStaticLookup(
53
60
  const entry = await _staticStore.get(handlerId);
54
61
  if (!entry) return undefined;
55
62
 
56
- // Replay handle data captured during build-time rendering.
57
- // The data was keyed by handlerId at build time; replay under segmentId
58
- // so it matches the segment order used by useHandle on the client.
59
- if (entry.handles && Object.keys(entry.handles).length > 0) {
63
+ // Replay handle data captured during build-time rendering. entry.handles is a
64
+ // Flight-encoded string ("" when none) decode before replay so
65
+ // Promise/ReactNode handle values are revived. The data was keyed by handlerId
66
+ // at build time; replay under segmentId so it matches the segment order used
67
+ // by useHandle on the client.
68
+ if (entry.handles && _decodeHandleValue) {
60
69
  const handleStore = _getRequestContext()?._handleStore;
61
70
  if (handleStore) {
62
- handleStore.replaySegmentData(segmentId, entry.handles);
71
+ const segHandles = await _decodeHandleValue<SegmentHandleData>(
72
+ entry.handles,
73
+ );
74
+ if (segHandles) {
75
+ handleStore.replaySegmentData(segmentId, segHandles);
76
+ }
63
77
  }
64
78
  }
65
79
 
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Streamed Handler Telemetry
3
+ *
4
+ * Shared fire-and-forget rejection observer for streamed handler promises,
5
+ * used by both the fresh and revalidation segment-resolution paths. Lives in
6
+ * its own module (never mocked) so the resolution unit tests that mock
7
+ * helpers.js / telemetry.js with explicit export lists do not resolve it to
8
+ * undefined.
9
+ */
10
+
11
+ import type { ReactNode } from "react";
12
+ import { getRouterContext } from "../router-context.js";
13
+ import { resolveSink, safeEmit } from "../telemetry.js";
14
+
15
+ /**
16
+ * Attach a fire-and-forget rejection observer to a streamed handler promise.
17
+ * React catches the actual error via its error boundary; this only emits
18
+ * the handler.error telemetry event.
19
+ */
20
+ export function observeStreamedHandler(
21
+ promise: Promise<ReactNode>,
22
+ segmentId: string,
23
+ segmentType: string,
24
+ pathname?: string,
25
+ routeKey?: string,
26
+ params?: Record<string, string>,
27
+ ): void {
28
+ let routerCtx;
29
+ try {
30
+ routerCtx = getRouterContext();
31
+ } catch {
32
+ return;
33
+ }
34
+ if (!routerCtx?.telemetry) return;
35
+ const sink = resolveSink(routerCtx.telemetry);
36
+ const reqId = routerCtx.requestId;
37
+ promise.catch((err: unknown) => {
38
+ const errorObj = err instanceof Error ? err : new Error(String(err));
39
+ safeEmit(sink, {
40
+ type: "handler.error",
41
+ timestamp: performance.now(),
42
+ requestId: reqId,
43
+ segmentId,
44
+ segmentType,
45
+ error: errorObj,
46
+ handledByBoundary: true,
47
+ pathname,
48
+ routeKey,
49
+ params,
50
+ });
51
+ });
52
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * View-transition boundary default resolution.
3
+ *
4
+ * Kept in its own module (rather than helpers.ts) because several resolution
5
+ * tests mock helpers.ts with an explicit export list; a shared util here is
6
+ * never mocked, so the fresh and revalidation paths always get the real
7
+ * implementation.
8
+ */
9
+
10
+ import type { EntryData } from "../../server/context";
11
+
12
+ /**
13
+ * Resolve the effective `viewTransition` for a segment's transition config.
14
+ *
15
+ * The per-segment value (set via the transition() DSL) always wins. When it is
16
+ * unset, the router-level createRouter({ viewTransition }) default is stamped
17
+ * in so the render gate reads the boundary decision off the segment — server
18
+ * and client, via the serialized segment — without the router option being
19
+ * threaded to the client. Only `false` is ever stamped; an unset (or "auto")
20
+ * value is left untouched because it already means "wrap" at the gate, which
21
+ * also avoids needless object allocation and payload growth. Used by both the
22
+ * fresh and revalidation resolution paths.
23
+ */
24
+ export function applyViewTransitionDefault(
25
+ transition: EntryData["transition"],
26
+ viewTransitionDefault: "auto" | false | undefined,
27
+ ): EntryData["transition"] {
28
+ if (!transition) return transition;
29
+ if (
30
+ transition.viewTransition === undefined &&
31
+ viewTransitionDefault === false
32
+ ) {
33
+ return { ...transition, viewTransition: false };
34
+ }
35
+ return transition;
36
+ }
@@ -1,5 +1,8 @@
1
1
  // Barrel re-export -- see segment-resolution/ for implementations.
2
- export { handleHandlerResult } from "./segment-resolution/helpers.js";
2
+ export {
3
+ handleHandlerResult,
4
+ warnOnStreamedResponse,
5
+ } from "./segment-resolution/helpers.js";
3
6
  export {
4
7
  resolveLoaders,
5
8
  type ResolveSegmentOptions,
@@ -68,7 +68,6 @@ export interface SegmentWrappers<TEnv = any> {
68
68
  request: Request,
69
69
  prevUrl: URL,
70
70
  nextUrl: URL,
71
- loaderPromises: Map<string, Promise<any>>,
72
71
  actionContext:
73
72
  | {
74
73
  actionId?: string;
@@ -192,7 +191,6 @@ export function createSegmentWrappers<TEnv = any>(
192
191
  request: Request,
193
192
  prevUrl: URL,
194
193
  nextUrl: URL,
195
- loaderPromises: Map<string, Promise<any>>,
196
194
  actionContext:
197
195
  | {
198
196
  actionId?: string;
@@ -204,6 +202,7 @@ export function createSegmentWrappers<TEnv = any>(
204
202
  interceptResult: { intercept: InterceptEntry; entry: EntryData } | null,
205
203
  localRouteName: string,
206
204
  pathname: string,
205
+ stale?: boolean,
207
206
  ): ReturnType<typeof _resolveAllSegmentsWithRevalidation> {
208
207
  return _resolveAllSegmentsWithRevalidation(
209
208
  entries,
@@ -215,12 +214,12 @@ export function createSegmentWrappers<TEnv = any>(
215
214
  request,
216
215
  prevUrl,
217
216
  nextUrl,
218
- loaderPromises,
219
217
  actionContext,
220
218
  interceptResult,
221
219
  localRouteName,
222
220
  pathname,
223
221
  segmentDeps,
222
+ stale,
224
223
  );
225
224
  }
226
225
 
@@ -0,0 +1,33 @@
1
+ import { DEFAULT_STATE_COOKIE_PREFIX } from "../browser/cookie-name.js";
2
+
3
+ /**
4
+ * Resolve the rango state cookie name once, server-side, at router init. The
5
+ * resolved string is shipped in payload metadata and the client reads it
6
+ * verbatim, so composition happens in exactly one place.
7
+ *
8
+ * Shape: `{sanitizedPrefix}_{sanitizedRouterId}`. The prefix charset excludes
9
+ * `_` so the FIRST `_` is always the prefix/routerId boundary; that keeps the
10
+ * name injective even though a routerId may legitimately contain `_` (the
11
+ * counter fallback is `router_{n}`). Without that exclusion, prefix
12
+ * `rango-state` + id `router_0` and prefix `rango-state_router` + id `0` would
13
+ * both resolve to `rango-state_router_0` and silently share a cache key.
14
+ */
15
+
16
+ // Prefix excludes `_` so it can never collide with the separator.
17
+ function sanitizePrefix(prefix: string): string {
18
+ return prefix.replace(/[^A-Za-z0-9-]/g, "");
19
+ }
20
+
21
+ // routerId keeps `_` (so `router_0` survives); other illegal chars are dropped.
22
+ function sanitizeRouterId(routerId: string): string {
23
+ return routerId.replace(/[^A-Za-z0-9_-]/g, "");
24
+ }
25
+
26
+ export function resolveStateCookieName(
27
+ prefix: string | undefined,
28
+ routerId: string,
29
+ ): string {
30
+ const sanitized = sanitizePrefix(prefix ?? DEFAULT_STATE_COOKIE_PREFIX);
31
+ const finalPrefix = sanitized || DEFAULT_STATE_COOKIE_PREFIX;
32
+ return `${finalPrefix}_${sanitizeRouterId(routerId)}`;
33
+ }
@@ -0,0 +1,56 @@
1
+ import { encodePathSegment } from "./url-params.js";
2
+
3
+ /**
4
+ * Substitute `:param` placeholders in a route pattern with values from
5
+ * `params`. Two-pass: optional params (`:name?`) first so absent values
6
+ * collapse cleanly, then required params (throws on missing). Constraint
7
+ * syntax (`:name(en|gb)`) is stripped from the result. Trailing-slash
8
+ * patterns like `/blog/` are preserved unless an optional segment was
9
+ * actually omitted.
10
+ *
11
+ * Shared by `ctx.reverse()` (server), `createReverse()` (typed runtime
12
+ * helper), and `useReverse()` (client hook). The behavior must stay
13
+ * identical across all three call sites.
14
+ */
15
+ export function substitutePatternParams(
16
+ pattern: string,
17
+ params: Record<string, string | undefined>,
18
+ routeName: string,
19
+ ): string {
20
+ let result = pattern;
21
+ let hadOmittedOptional = false;
22
+
23
+ result = result.replace(
24
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(\?)/g,
25
+ (_match, key) => {
26
+ const value = params[key as string];
27
+ // The matcher omits absent optional params (so `value` is `undefined`
28
+ // here), but caller-supplied params or `getParams()` shapes may still
29
+ // pass `""` explicitly. Treat both as the absent form.
30
+ if (value === undefined || value === "") {
31
+ hadOmittedOptional = true;
32
+ return "";
33
+ }
34
+ return encodePathSegment(value);
35
+ },
36
+ );
37
+
38
+ result = result.replace(
39
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(?!\?)/g,
40
+ (_match, key) => {
41
+ const value = params[key as string];
42
+ if (value === undefined) {
43
+ throw new Error(`Missing param "${key}" for route "${routeName}"`);
44
+ }
45
+ return encodePathSegment(value);
46
+ },
47
+ );
48
+
49
+ if (hadOmittedOptional) {
50
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
51
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
52
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
53
+ }
54
+
55
+ return result;
56
+ }
@@ -125,10 +125,6 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
125
125
  return {
126
126
  emit(event: TelemetryEvent): void {
127
127
  switch (event.type) {
128
- // -----------------------------------------------------------------
129
- // Request lifecycle
130
- // -----------------------------------------------------------------
131
-
132
128
  case "request.start": {
133
129
  const span = tracer.startSpan("rango.request", {
134
130
  attributes: {
@@ -169,10 +165,6 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
169
165
  break;
170
166
  }
171
167
 
172
- // -----------------------------------------------------------------
173
- // Loader lifecycle
174
- // -----------------------------------------------------------------
175
-
176
168
  case "loader.start": {
177
169
  const span = tracer.startSpan("rango.loader", {
178
170
  attributes: {
@@ -231,10 +223,6 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
231
223
  break;
232
224
  }
233
225
 
234
- // -----------------------------------------------------------------
235
- // Handler errors (instant span)
236
- // -----------------------------------------------------------------
237
-
238
226
  case "handler.error": {
239
227
  const attrs: Record<string, string | number | boolean> = {
240
228
  "rango.handled_by_boundary": event.handledByBoundary,
@@ -257,10 +245,6 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
257
245
  break;
258
246
  }
259
247
 
260
- // -----------------------------------------------------------------
261
- // Cache decision (instant span)
262
- // -----------------------------------------------------------------
263
-
264
248
  case "cache.decision": {
265
249
  const attrs: Record<string, string | number | boolean> = {
266
250
  "http.route": event.pathname,
@@ -277,10 +261,6 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
277
261
  break;
278
262
  }
279
263
 
280
- // -----------------------------------------------------------------
281
- // Revalidation decision (instant span)
282
- // -----------------------------------------------------------------
283
-
284
264
  case "revalidation.decision": {
285
265
  const span = tracer.startSpan("rango.revalidation.decision", {
286
266
  attributes: {
@@ -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;
@@ -90,6 +86,34 @@ export interface HandlerErrorEvent extends BaseEvent {
90
86
  params?: Record<string, string>;
91
87
  }
92
88
 
89
+ /**
90
+ * Per-segment (or coarse route-level) cache status carried on the
91
+ * cache.decision telemetry event and the X-Rango-Cache debug header.
92
+ *
93
+ * v1 is COARSE: the router's pipeline tracks cache decisions at the
94
+ * route/entry level (cacheHit/cacheSource/shouldRevalidate), not per
95
+ * individual segment. The `segments` array therefore contains a single
96
+ * route-level entry keyed by the route key. The shape is forward-compatible
97
+ * with genuine per-segment status if the pipeline later exposes it.
98
+ */
99
+ export type CacheSegmentStatus =
100
+ | "hit"
101
+ | "miss"
102
+ | "stale"
103
+ | "prerendered"
104
+ | "passthrough";
105
+
106
+ export interface CacheSegmentSignal {
107
+ /** Segment id (v1: the route key, since status is route-level). */
108
+ id: string;
109
+ /** Segment type (v1: "route" for the coarse route-level entry). */
110
+ type: string;
111
+ /** Resolved cache status for this segment. */
112
+ cacheStatus: CacheSegmentStatus;
113
+ /** Whether stale-while-revalidate was triggered for this segment. */
114
+ shouldRevalidate?: boolean;
115
+ }
116
+
93
117
  export interface CacheDecisionEvent extends BaseEvent {
94
118
  type: "cache.decision";
95
119
  pathname: string;
@@ -98,6 +122,12 @@ export interface CacheDecisionEvent extends BaseEvent {
98
122
  /** Whether stale-while-revalidate was triggered */
99
123
  shouldRevalidate: boolean;
100
124
  source?: "runtime" | "prerender";
125
+ /**
126
+ * Optional per-segment (v1: coarse route-level) cache status. Present only
127
+ * when telemetry or the debug cache signal is enabled. Optional so existing
128
+ * sinks are unaffected.
129
+ */
130
+ segments?: CacheSegmentSignal[];
101
131
  }
102
132
 
103
133
  export interface RevalidationDecisionEvent extends BaseEvent {
@@ -141,9 +171,70 @@ export type TelemetryEvent =
141
171
  | OriginCheckRejectedEvent;
142
172
 
143
173
  // ---------------------------------------------------------------------------
144
- // Sink interface
174
+ // Cache signal derivation (coarse, route-level)
145
175
  // ---------------------------------------------------------------------------
146
176
 
177
+ /**
178
+ * Derive the coarse, route-level cache status from pipeline cache state.
179
+ *
180
+ * v1 mapping (route-level — see CacheSegmentSignal):
181
+ * - prerender hit -> "prerendered"
182
+ * - runtime hit + shouldRevalidate (SWR) -> "stale"
183
+ * - runtime hit -> "hit"
184
+ * - no hit -> "miss"
185
+ *
186
+ * Note: "passthrough" is a build-time prerender concept (a route opts out of
187
+ * being prerendered for some params). At runtime a passthrough route renders
188
+ * fresh and is indistinguishable from a normal miss in the pipeline state, so
189
+ * v1 reports it as "miss". The "passthrough" status remains in the type union
190
+ * for forward compatibility.
191
+ */
192
+ export function deriveCacheStatus(state: {
193
+ cacheHit: boolean;
194
+ cacheSource?: "runtime" | "prerender";
195
+ shouldRevalidate?: boolean;
196
+ }): CacheSegmentStatus {
197
+ if (state.cacheHit) {
198
+ if (state.cacheSource === "prerender") return "prerendered";
199
+ if (state.shouldRevalidate) return "stale";
200
+ return "hit";
201
+ }
202
+ return "miss";
203
+ }
204
+
205
+ /**
206
+ * Build the coarse route-level cache signal array (a single entry keyed by
207
+ * the route key). Used for both the cache.decision telemetry event and the
208
+ * X-Rango-Cache debug header.
209
+ */
210
+ export function buildCacheSignalSegments(
211
+ routeKey: string,
212
+ state: {
213
+ cacheHit: boolean;
214
+ cacheSource?: "runtime" | "prerender";
215
+ shouldRevalidate?: boolean;
216
+ },
217
+ ): CacheSegmentSignal[] {
218
+ return [
219
+ {
220
+ id: routeKey,
221
+ type: "route",
222
+ cacheStatus: deriveCacheStatus(state),
223
+ shouldRevalidate: !!state.shouldRevalidate,
224
+ },
225
+ ];
226
+ }
227
+
228
+ /**
229
+ * Serialize cache signal segments into the X-Rango-Cache header value:
230
+ * `<segId>=<status>, <segId2>=<status2>`.
231
+ */
232
+ export function formatCacheSignalHeader(
233
+ segments: CacheSegmentSignal[],
234
+ ): string {
235
+ return segments.map((s) => `${s.id}=${s.cacheStatus}`).join(", ");
236
+ }
237
+
147
238
  /**
148
239
  * Telemetry sink receives structured lifecycle events from the router.
149
240
  * Implement this interface to integrate with any observability backend.
@@ -154,10 +245,6 @@ export interface TelemetrySink {
154
245
  emit(event: TelemetryEvent): void;
155
246
  }
156
247
 
157
- // ---------------------------------------------------------------------------
158
- // No-op singleton (zero-cost disabled state)
159
- // ---------------------------------------------------------------------------
160
-
161
248
  const noopSink: TelemetrySink = {
162
249
  emit() {},
163
250
  };
@@ -185,12 +272,6 @@ export function safeEmit(sink: TelemetrySink, event: TelemetryEvent): void {
185
272
  }
186
273
  }
187
274
 
188
- // ---------------------------------------------------------------------------
189
- // Request ID extraction (for span correlation)
190
- // ---------------------------------------------------------------------------
191
-
192
- // Per-request memoization so the same Request object always maps to the
193
- // same ID. WeakMap allows GC when the Request is no longer referenced.
194
275
  const requestIds = new WeakMap<Request, string>();
195
276
  let telemetryRequestCounter = 0;
196
277
 
@@ -224,10 +305,6 @@ export function getRequestId(request: Request): string {
224
305
  return id;
225
306
  }
226
307
 
227
- // ---------------------------------------------------------------------------
228
- // Console sink (built-in, replaces ad-hoc console.log debug traces)
229
- // ---------------------------------------------------------------------------
230
-
231
308
  /**
232
309
  * Built-in console sink that logs events in a structured format.
233
310
  * Designed as the default sink for development / debugging.
@@ -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.