@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  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 +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/__internal.ts CHANGED
@@ -9,10 +9,6 @@
9
9
  * adding them to the public API.
10
10
  */
11
11
 
12
- // ============================================================================
13
- // Segment Resolution (Internal)
14
- // ============================================================================
15
-
16
12
  /**
17
13
  * @internal
18
14
  * Internal representation of a resolved route segment.
@@ -26,10 +22,6 @@ export type { ResolvedSegment, SegmentMetadata } from "./types.js";
26
22
  */
27
23
  export type { MatchResult, SlotState } from "./types.js";
28
24
 
29
- // ============================================================================
30
- // Intercept System (Internal)
31
- // ============================================================================
32
-
33
25
  /**
34
26
  * @internal
35
27
  * Context for intercept route selection.
@@ -40,10 +32,6 @@ export type {
40
32
  InterceptWhenFn,
41
33
  } from "./server/context.js";
42
34
 
43
- // ============================================================================
44
- // Browser State (Internal)
45
- // ============================================================================
46
-
47
35
  /**
48
36
  * @internal
49
37
  * RSC protocol payload structure.
@@ -68,10 +56,6 @@ export type {
68
56
  NavigationUpdate,
69
57
  } from "./browser/types.js";
70
58
 
71
- // ============================================================================
72
- // Handle System (Internal)
73
- // ============================================================================
74
-
75
59
  /**
76
60
  * @internal
77
61
  * Internal handle storage mechanism.
@@ -84,41 +68,35 @@ export type { HandleStore, HandleData } from "./server/handle-store.js";
84
68
  */
85
69
  export type { SegmentHandleData } from "./cache/types.js";
86
70
 
87
- // ============================================================================
88
- // Cache Internals
89
- // ============================================================================
90
-
91
71
  /**
92
72
  * @internal
93
73
  * Internal cache entry data structure.
94
74
  */
95
75
  export type {
96
76
  CachedEntryData,
97
- CachedEntryResult,
98
77
  CacheGetResult,
99
78
  SerializedSegmentData,
100
79
  CacheDefaults,
101
80
  } from "./cache/types.js";
102
81
 
103
- // ============================================================================
104
- // Router Context (Internal)
105
- // ============================================================================
106
-
107
82
  /**
108
83
  * @internal
109
84
  * Router context for AsyncLocalStorage.
110
85
  */
111
- export type { RouterContext, RevalidationContext, InterceptResult } from "./router/router-context.js";
112
-
113
- // ============================================================================
114
- // Match Pipeline (Internal)
115
- // ============================================================================
86
+ export type {
87
+ RouterContext,
88
+ RevalidationContext,
89
+ InterceptResult,
90
+ } from "./router/router-context.js";
116
91
 
117
92
  /**
118
93
  * @internal
119
94
  * Route match context during pipeline processing.
120
95
  */
121
- export type { MatchContext, MatchPipelineState } from "./router/match-context.js";
96
+ export type {
97
+ MatchContext,
98
+ MatchPipelineState,
99
+ } from "./router/match-context.js";
122
100
 
123
101
  /**
124
102
  * @internal
@@ -126,10 +104,6 @@ export type { MatchContext, MatchPipelineState } from "./router/match-context.js
126
104
  */
127
105
  export type { RouteMatchResult } from "./router/pattern-matching.js";
128
106
 
129
- // ============================================================================
130
- // Server Context (Internal)
131
- // ============================================================================
132
-
133
107
  /**
134
108
  * @internal
135
109
  * Entry data during route traversal.
@@ -146,20 +120,80 @@ export type {
146
120
  EntryPropSegments,
147
121
  } from "./server/context.js";
148
122
 
149
- // ============================================================================
150
- // Handler Context (Internal)
151
- // ============================================================================
152
-
153
123
  /**
154
124
  * @internal
155
125
  * Internal handler context with additional props for router internals.
156
- * Includes `_originalRequest` and `_currentSegmentId`.
126
+ * Includes `_currentSegmentId` and `_responseType`.
157
127
  */
158
128
  export type { InternalHandlerContext } from "./types.js";
159
129
 
160
- // ============================================================================
161
- // Debug Utilities (Internal)
162
- // ============================================================================
130
+ /**
131
+ * @internal
132
+ * Builds React element trees from route segments.
133
+ */
134
+ export { renderSegments } from "./segment-system.js";
135
+
136
+ /**
137
+ * @internal
138
+ * Error sanitization and network error utilities.
139
+ */
140
+ export { sanitizeError, NetworkError, isNetworkError } from "./errors.js";
141
+
142
+ /**
143
+ * @internal
144
+ * Scoped view of GeneratedRouteMap for Handler<"localName", ScopedRouteMap<"prefix">>.
145
+ */
146
+ export type { ScopedRouteMap } from "./types.js";
147
+
148
+ /**
149
+ * @internal
150
+ * Type-level utilities for reverse URL generation.
151
+ */
152
+ export type { MergeRoutes, SanitizePrefix } from "./reverse.js";
153
+
154
+ /**
155
+ * @internal
156
+ * Individual telemetry event types.
157
+ */
158
+ export type {
159
+ RequestStartEvent,
160
+ RequestEndEvent,
161
+ RequestErrorEvent,
162
+ RequestTimeoutEvent,
163
+ LoaderStartEvent,
164
+ LoaderEndEvent,
165
+ LoaderErrorEvent,
166
+ HandlerErrorEvent,
167
+ CacheDecisionEvent,
168
+ RevalidationDecisionEvent,
169
+ } from "./router/telemetry.js";
170
+
171
+ /**
172
+ * @internal
173
+ * Type guard for prerender handler definitions.
174
+ */
175
+ export { isPrerenderHandler, isPassthroughHandler } from "./prerender.js";
176
+
177
+ /**
178
+ * @internal
179
+ * Type guard for static handler definitions.
180
+ */
181
+ export { isStaticHandler } from "./static-handler.js";
182
+
183
+ /**
184
+ * @internal
185
+ * Sentinel used to tag response-type route entries.
186
+ */
187
+ export { RESPONSE_TYPE } from "./urls.js";
188
+
189
+ /**
190
+ * @internal
191
+ * Debug utilities for route matching performance analysis.
192
+ */
193
+ export {
194
+ enableMatchDebug,
195
+ getMatchDebugStats,
196
+ } from "./router/pattern-matching.js";
163
197
 
164
198
  /**
165
199
  * @internal
@@ -172,4 +206,3 @@ export {
172
206
  type SerializedEntry,
173
207
  type SerializedManifest,
174
208
  } from "./debug.js";
175
-
package/src/bin/rango.ts CHANGED
@@ -1,24 +1,321 @@
1
- import { resolve } from "node:path";
2
- import { findTsFiles, writePerModuleRouteTypesForFile } from "../build/generate-route-types.ts";
1
+ import { resolve, dirname } from "node:path";
2
+ import { readFileSync, statSync, existsSync } from "node:fs";
3
+ import {
4
+ findTsFiles,
5
+ writePerModuleRouteTypesForFile,
6
+ writeCombinedRouteTypes,
7
+ detectUnresolvableIncludes,
8
+ detectUnresolvableIncludesForUrlsFile,
9
+ findNestedRouterConflict,
10
+ formatNestedRouterConflictError,
11
+ type UnresolvableInclude,
12
+ } from "../build/generate-route-types.ts";
3
13
 
4
- const [command, ...args] = process.argv.slice(2);
14
+ const [command, ...rawArgs] = process.argv.slice(2);
5
15
 
6
- if (command === "extract-names") {
7
- const dir = args[0] ?? "./src";
8
- const resolvedDir = resolve(dir);
9
- console.log(`[rango] Scanning ${resolvedDir} for url modules...`);
16
+ if (command === "generate") {
17
+ // Parse flags
18
+ let mode: "default" | "runtime" | "static" = "default";
19
+ let configFile: string | undefined;
20
+ const positionalArgs: string[] = [];
21
+
22
+ for (let i = 0; i < rawArgs.length; i++) {
23
+ const arg = rawArgs[i];
24
+ if (arg === "--runtime") {
25
+ mode = "runtime";
26
+ } else if (arg === "--static") {
27
+ mode = "static";
28
+ } else if (arg === "--config") {
29
+ configFile = rawArgs[++i];
30
+ if (!configFile) {
31
+ console.error("[rango] --config requires a path argument");
32
+ process.exit(1);
33
+ }
34
+ } else if (arg.startsWith("--")) {
35
+ console.error(`[rango] Unknown flag: ${arg}`);
36
+ process.exit(1);
37
+ } else {
38
+ positionalArgs.push(arg);
39
+ }
40
+ }
41
+
42
+ if (positionalArgs.length === 0) {
43
+ console.error(
44
+ "[rango] Usage: rango generate <file|dir> [file2 ...] [--runtime|--static] [--config <path>]",
45
+ );
46
+ process.exit(1);
47
+ }
48
+
49
+ if (configFile && mode !== "runtime") {
50
+ console.warn("[rango] --config is only used with --runtime, ignoring");
51
+ }
52
+
53
+ if (mode === "runtime") {
54
+ // Runtime discovery: dynamically import to avoid loading Vite for static-only usage
55
+ runRuntimeDiscovery(positionalArgs, configFile).catch((err) => {
56
+ console.error(`[rango] Runtime discovery failed: ${err.message}`);
57
+ process.exit(1);
58
+ });
59
+ } else {
60
+ runStaticGeneration(positionalArgs, mode);
61
+ }
62
+ } else {
63
+ if (
64
+ command &&
65
+ command !== "help" &&
66
+ command !== "--help" &&
67
+ command !== "-h"
68
+ ) {
69
+ console.error(`[rango] Unknown command: ${command}\n`);
70
+ }
71
+ console.log(`Usage: rango generate <file|dir> [file2 ...] [--runtime|--static] [--config <path>]
72
+
73
+ Auto-detects file type (createRouter, urls) and generates
74
+ the appropriate .gen.ts route type files.
75
+
76
+ Modes:
77
+ (default) Static parser with error on unresolvable includes
78
+ --runtime Vite-based runtime discovery (100% coverage)
79
+ Requires vite and @vitejs/plugin-rsc
80
+ --static Static parser, accept partial output with warnings
81
+
82
+ Options:
83
+ --config <path> Path to vite.config.ts (--runtime only, auto-detected if omitted)
84
+
85
+ Examples:
86
+ rango generate src/router.tsx
87
+ rango generate src/router.tsx --runtime
88
+ rango generate src/ --static`);
89
+ process.exit(
90
+ command && command !== "help" && command !== "--help" && command !== "-h"
91
+ ? 1
92
+ : 0,
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Walk up from a file path to find the project root (directory containing
98
+ * package.json or vite.config.ts).
99
+ */
100
+ function findProjectRoot(fromPath: string): string {
101
+ let dir = dirname(resolve(fromPath));
102
+ while (dir !== dirname(dir)) {
103
+ if (
104
+ existsSync(resolve(dir, "package.json")) ||
105
+ existsSync(resolve(dir, "vite.config.ts")) ||
106
+ existsSync(resolve(dir, "vite.config.js"))
107
+ ) {
108
+ return dir;
109
+ }
110
+ dir = dirname(dir);
111
+ }
112
+ // Fallback to cwd if no project root found
113
+ return process.cwd();
114
+ }
115
+
116
+ function runStaticGeneration(args: string[], mode: "default" | "static") {
117
+ // Expand args: files are used directly, directories are scanned
118
+ const files: string[] = [];
119
+ for (const arg of args) {
120
+ const resolved = resolve(arg);
121
+ try {
122
+ if (statSync(resolved).isDirectory()) {
123
+ files.push(...findTsFiles(resolved));
124
+ } else {
125
+ files.push(resolved);
126
+ }
127
+ } catch {
128
+ console.warn(`[rango] Skipping ${arg}: not found`);
129
+ }
130
+ }
131
+
132
+ if (files.length === 0) {
133
+ console.log("[rango] No files to process");
134
+ process.exit(0);
135
+ }
136
+
137
+ // Phase 1: Classify files
138
+ const routerFiles: string[] = [];
139
+ const urlsFiles: string[] = [];
10
140
 
11
- const files = findTsFiles(resolvedDir);
12
141
  for (const filePath of files) {
13
- writePerModuleRouteTypesForFile(filePath);
142
+ try {
143
+ const source = readFileSync(filePath, "utf-8");
144
+ if (/\bcreateRouter\s*[<(]/.test(source)) {
145
+ routerFiles.push(filePath);
146
+ }
147
+ if (source.includes("urls(")) {
148
+ urlsFiles.push(filePath);
149
+ }
150
+ } catch (err) {
151
+ console.warn(
152
+ `[rango] Failed to process ${filePath}: ${(err as Error).message}`,
153
+ );
154
+ }
155
+ }
156
+
157
+ // Phase 2: Collect diagnostics from all files BEFORE writing anything
158
+ const allDiagnostics: Array<UnresolvableInclude & { routerFile: string }> =
159
+ [];
160
+
161
+ for (const routerFile of routerFiles) {
162
+ const diagnostics = detectUnresolvableIncludes(routerFile);
163
+ for (const d of diagnostics) {
164
+ allDiagnostics.push({ ...d, routerFile });
165
+ }
166
+ }
167
+
168
+ // Also check standalone urls files not covered by router-level detection
169
+ const routerFileSet = new Set(routerFiles);
170
+ for (const urlsFile of urlsFiles) {
171
+ if (routerFileSet.has(urlsFile)) continue;
172
+ const diagnostics = detectUnresolvableIncludesForUrlsFile(urlsFile);
173
+ for (const d of diagnostics) {
174
+ allDiagnostics.push({ ...d, routerFile: urlsFile });
175
+ }
14
176
  }
15
177
 
16
- console.log(`[rango] Scanned ${files.length} file(s)`);
178
+ // Deduplicate diagnostics (router and urls detection may find the same issue)
179
+ const seen = new Set<string>();
180
+ const uniqueDiagnostics = allDiagnostics.filter((d) => {
181
+ const key = `${d.sourceFile}:${d.pathPrefix}:${d.reason}`;
182
+ if (seen.has(key)) return false;
183
+ seen.add(key);
184
+ return true;
185
+ });
186
+
187
+ if (uniqueDiagnostics.length > 0 && mode === "default") {
188
+ // Hard error: no files written
189
+ console.error("\n[rango] Unresolvable includes detected:\n");
190
+ formatDiagnostics(uniqueDiagnostics);
191
+ console.error(
192
+ "\nThe static parser cannot resolve these includes because they use " +
193
+ "factory functions or dynamic expressions.\n\n" +
194
+ "Options:\n" +
195
+ " rango generate <path> --runtime Use Vite-based discovery (requires vite)\n" +
196
+ " rango generate <path> --static Accept partial output (missing routes above)\n",
197
+ );
198
+ process.exit(1);
199
+ }
200
+
201
+ if (uniqueDiagnostics.length > 0 && mode === "static") {
202
+ // Warning: partial output accepted
203
+ console.warn(
204
+ "\n[rango] Warning: partial output (unresolvable includes):\n",
205
+ );
206
+ formatDiagnostics(uniqueDiagnostics);
207
+ console.warn("");
208
+ }
209
+
210
+ const nestedRouterConflict = findNestedRouterConflict(routerFiles);
211
+ if (nestedRouterConflict) {
212
+ console.error(
213
+ `\n${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}\n`,
214
+ );
215
+ process.exit(1);
216
+ }
217
+
218
+ // Phase 3: Write all outputs (only reached if diagnostics pass or --static)
219
+ for (const urlsFile of urlsFiles) {
220
+ writePerModuleRouteTypesForFile(urlsFile);
221
+ }
222
+
223
+ for (const routerFile of routerFiles) {
224
+ const projectRoot = findProjectRoot(routerFile);
225
+ writeCombinedRouteTypes(projectRoot, [routerFile]);
226
+ }
227
+
228
+ console.log(
229
+ `[rango] Processed ${files.length} file(s)${routerFiles.length ? ` (${routerFiles.length} router)` : ""}`,
230
+ );
17
231
  process.exit(0);
18
- } else {
19
- console.log(`Usage: rango <command>
232
+ }
233
+
234
+ async function runRuntimeDiscovery(args: string[], configFile?: string) {
235
+ // Resolve the entry: find the router file from the arguments
236
+ const files: string[] = [];
237
+ for (const arg of args) {
238
+ const resolved = resolve(arg);
239
+ try {
240
+ if (statSync(resolved).isDirectory()) {
241
+ files.push(...findTsFiles(resolved));
242
+ } else {
243
+ files.push(resolved);
244
+ }
245
+ } catch {
246
+ console.warn(`[rango] Skipping ${arg}: not found`);
247
+ }
248
+ }
249
+
250
+ // Find router files among the inputs
251
+ const routerEntries: string[] = [];
252
+ for (const filePath of files) {
253
+ try {
254
+ const source = readFileSync(filePath, "utf-8");
255
+ if (/\bcreateRouter\s*[<(]/.test(source)) {
256
+ routerEntries.push(filePath);
257
+ }
258
+ // Also generate per-module types for urls files
259
+ if (source.includes("urls(")) {
260
+ writePerModuleRouteTypesForFile(filePath);
261
+ }
262
+ } catch {
263
+ // Skip unreadable files
264
+ }
265
+ }
266
+
267
+ if (routerEntries.length === 0) {
268
+ console.error("[rango] No router files found in the provided paths");
269
+ process.exit(1);
270
+ }
271
+
272
+ const nestedRouterConflict = findNestedRouterConflict(routerEntries);
273
+ if (nestedRouterConflict) {
274
+ console.error(
275
+ `\n${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}\n`,
276
+ );
277
+ process.exit(1);
278
+ }
279
+
280
+ let discoverAndWriteRouteTypes: typeof import("../build/runtime-discovery.ts").discoverAndWriteRouteTypes;
281
+ try {
282
+ const mod = await import("../build/runtime-discovery.ts");
283
+ discoverAndWriteRouteTypes = mod.discoverAndWriteRouteTypes;
284
+ } catch (err: any) {
285
+ if (
286
+ err.code === "ERR_MODULE_NOT_FOUND" ||
287
+ err.code === "MODULE_NOT_FOUND"
288
+ ) {
289
+ console.error(
290
+ "[rango] Runtime discovery requires 'vite' and '@vitejs/plugin-rsc'.\n" +
291
+ "Install them with: pnpm add -D vite @vitejs/plugin-rsc",
292
+ );
293
+ } else {
294
+ console.error(`[rango] Failed to load runtime discovery: ${err.message}`);
295
+ }
296
+ process.exit(1);
297
+ }
20
298
 
21
- Commands:
22
- extract-names [dir] Extract route names from url modules (default: ./src)`);
23
- process.exit(command ? 1 : 0);
299
+ for (const entry of routerEntries) {
300
+ const projectRoot = findProjectRoot(entry);
301
+ const result = await discoverAndWriteRouteTypes({
302
+ root: projectRoot,
303
+ configFile,
304
+ entry,
305
+ });
306
+ console.log(
307
+ `[rango] Runtime discovery: ${result.routerCount} router(s), ${result.routeCount} route(s)`,
308
+ );
309
+ }
310
+ }
311
+
312
+ function formatDiagnostics(
313
+ diagnostics: Array<UnresolvableInclude & { routerFile: string }>,
314
+ ) {
315
+ for (const d of diagnostics) {
316
+ const prefix = d.namePrefix ? `${d.namePrefix}.*` : `${d.pathPrefix}*`;
317
+ console.error(` ${prefix}`);
318
+ console.error(` Reason: ${d.reason} -- ${d.detail}`);
319
+ console.error(` Source: ${d.sourceFile}`);
320
+ }
24
321
  }
@@ -0,0 +1,114 @@
1
+ import type { ActionEntry } from "./event-controller.js";
2
+
3
+ /**
4
+ * Post-reconciliation action outcome (discriminated union). Error and
5
+ * full-update-unsupported cases are handled inline in the bridge before
6
+ * reconciliation; this only covers successfully-reconciled partial responses.
7
+ */
8
+ type ActionScenario =
9
+ | {
10
+ type: "navigated-away";
11
+ historyKeyChanged: boolean;
12
+ onInterceptRoute: boolean;
13
+ }
14
+ | { type: "hmr-missing" }
15
+ | { type: "consolidation-needed"; segmentIds: string[] }
16
+ | { type: "concurrent-skip"; otherFetchingCount: number }
17
+ | { type: "normal" };
18
+
19
+ /**
20
+ * Plain data inputs for classifying a post-reconciliation action outcome.
21
+ * No browser objects or controller references — all values are snapshots.
22
+ */
23
+ export interface ActionOutcomeInput {
24
+ /** This action's unique instance ID */
25
+ handleId: string;
26
+ /** All in-flight action entries (snapshot from event controller) */
27
+ inflightActions: Map<string, ActionEntry>;
28
+ /** Whether any concurrent actions occurred (controller-level shared flag) */
29
+ hadAnyConcurrentActions: boolean;
30
+ /** Segments revalidated by concurrent actions (from tracking set) */
31
+ revalidatedSegments: Set<string>;
32
+ /** window.location.pathname captured at action start */
33
+ actionStartPathname: string;
34
+ /** window.location.pathname at classification time */
35
+ currentPathname: string;
36
+ /** window.history.state?.key captured at action start */
37
+ actionStartLocationKey: string | undefined;
38
+ /** window.history.state?.key at classification time */
39
+ currentLocationKey: string | undefined;
40
+ /** Number of segments after reconciliation */
41
+ reconciledSegmentCount: number;
42
+ /** Number of matched segment IDs from server */
43
+ matchedCount: number;
44
+ /** Current intercept source URL (null when not on intercept route) */
45
+ currentInterceptSource: string | null;
46
+ }
47
+
48
+ // Segment IDs to re-fetch when concurrent actions each revalidated different
49
+ // parts of the tree; null when consolidation does not apply. Returns null while
50
+ // any action is still fetching — consolidation must wait for all to land.
51
+ function computeConsolidationSegments(
52
+ input: ActionOutcomeInput,
53
+ ): string[] | null {
54
+ if (!input.hadAnyConcurrentActions) return null;
55
+ if (input.revalidatedSegments.size === 0) return null;
56
+
57
+ const stillFetchingCount = [...input.inflightActions.values()].filter(
58
+ (a) => a.phase === "fetching",
59
+ ).length;
60
+ if (stillFetchingCount > 0) return null;
61
+
62
+ return Array.from(input.revalidatedSegments);
63
+ }
64
+
65
+ function countOtherFetchingActions(input: ActionOutcomeInput): number {
66
+ let count = 0;
67
+ for (const [, a] of input.inflightActions) {
68
+ if (a.phase === "fetching" && a.id !== input.handleId) {
69
+ count++;
70
+ }
71
+ }
72
+ return count;
73
+ }
74
+
75
+ /**
76
+ * Classify a post-reconciliation action outcome. Ordered priority chain: each
77
+ * case assumes the earlier ones are false (e.g. concurrent-skip only applies on
78
+ * the still-current route, consolidation only once no action is still fetching).
79
+ * The bridge calls this to decide whether to render, skip, consolidate, or refetch.
80
+ */
81
+ export function classifyActionOutcome(
82
+ input: ActionOutcomeInput,
83
+ ): ActionScenario {
84
+ if (
85
+ input.currentPathname !== input.actionStartPathname ||
86
+ input.currentLocationKey !== input.actionStartLocationKey
87
+ ) {
88
+ return {
89
+ type: "navigated-away",
90
+ historyKeyChanged:
91
+ input.currentLocationKey !== input.actionStartLocationKey,
92
+ onInterceptRoute: input.currentInterceptSource !== null,
93
+ };
94
+ }
95
+
96
+ if (input.reconciledSegmentCount < input.matchedCount) {
97
+ return { type: "hmr-missing" };
98
+ }
99
+
100
+ const consolidationSegments = computeConsolidationSegments(input);
101
+ if (consolidationSegments && consolidationSegments.length > 0) {
102
+ return { type: "consolidation-needed", segmentIds: consolidationSegments };
103
+ }
104
+
105
+ const otherFetchingActionCount = countOtherFetchingActions(input);
106
+ if (otherFetchingActionCount > 0) {
107
+ return {
108
+ type: "concurrent-skip",
109
+ otherFetchingCount: otherFetchingActionCount,
110
+ };
111
+ }
112
+
113
+ return { type: "normal" };
114
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Action fence: a refcounted flag that is raised while a server action is in
3
+ * flight and lowered when it resolves.
4
+ *
5
+ * It replaces the eager cache clear the action bridge used to do at action
6
+ * start. While the fence is up, the decision of whether the action invalidated
7
+ * anything is deferred to the response:
8
+ *
9
+ * - prefetch consumption is suspended (a queued prefetch result is not served),
10
+ * - a genuine navigation during the flight fetches with `cache: "no-store"` so
11
+ * it cannot be served stale bytes from the browser's Vary-keyed HTTP cache,
12
+ * - popstate reads are treated as stale-while-revalidate.
13
+ *
14
+ * Nothing is wiped, rotated, or broadcast while the fence is up — that is what
15
+ * keeps a sibling tab from seeing a pre-commit signal. Refcounted so concurrent
16
+ * actions compose: each action raises and lowers its own reference, and the
17
+ * fence is down only when the count reaches zero.
18
+ *
19
+ * The refcount is a single module-level counter, not keyed by routerId.
20
+ * Consumers (navigation-client.ts, prefetch/fetch.ts, navigation-bridge.ts)
21
+ * read it unscoped, so an action in one router would suppress another router's
22
+ * navigation/prefetch caches. This is correct only because two routers cannot
23
+ * coexist in one live document: an SPA navigation crossing a host-router
24
+ * boundary forces a full document reload (src/router/request-classification.ts
25
+ * app-switch terminal), so there is always exactly one live router per
26
+ * document. A future multi-router-in-one-document feature must not silently
27
+ * inherit this global, cross-router cache suppression.
28
+ */
29
+
30
+ let fenceCount = 0;
31
+
32
+ export function enterActionFence(): void {
33
+ fenceCount++;
34
+ }
35
+
36
+ export function exitActionFence(): void {
37
+ if (fenceCount > 0) fenceCount--;
38
+ }
39
+
40
+ export function isActionFenceActive(): boolean {
41
+ return fenceCount > 0;
42
+ }
43
+
44
+ /** Test-only: reset the refcount between cases. */
45
+ export function __resetActionFence(): void {
46
+ fenceCount = 0;
47
+ }