@rangojs/router 0.0.0-experimental.98 → 0.0.0-experimental.98914650

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 (355) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +60 -11
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/intercept/SKILL.md +29 -5
  18. package/skills/layout/SKILL.md +13 -9
  19. package/skills/links/SKILL.md +173 -17
  20. package/skills/loader/SKILL.md +170 -23
  21. package/skills/middleware/SKILL.md +16 -10
  22. package/skills/migrate-nextjs/SKILL.md +38 -16
  23. package/skills/mime-routes/SKILL.md +27 -0
  24. package/skills/observability/SKILL.md +137 -0
  25. package/skills/parallel/SKILL.md +11 -7
  26. package/skills/prerender/SKILL.md +14 -33
  27. package/skills/rango/SKILL.md +250 -26
  28. package/skills/react-compiler/SKILL.md +168 -0
  29. package/skills/response-routes/SKILL.md +114 -47
  30. package/skills/route/SKILL.md +22 -5
  31. package/skills/router-setup/SKILL.md +3 -3
  32. package/skills/server-actions/SKILL.md +78 -42
  33. package/skills/tailwind/SKILL.md +27 -3
  34. package/skills/testing/SKILL.md +129 -0
  35. package/skills/testing/bindings.md +89 -0
  36. package/skills/testing/cache-prerender.md +124 -0
  37. package/skills/testing/client-components.md +122 -0
  38. package/skills/testing/e2e-parity.md +125 -0
  39. package/skills/testing/flight.md +92 -0
  40. package/skills/testing/handles.md +129 -0
  41. package/skills/testing/loader.md +128 -0
  42. package/skills/testing/middleware.md +99 -0
  43. package/skills/testing/render-handler.md +121 -0
  44. package/skills/testing/response-routes.md +95 -0
  45. package/skills/testing/reverse-and-types.md +84 -0
  46. package/skills/testing/server-actions.md +107 -0
  47. package/skills/testing/server-tree.md +128 -0
  48. package/skills/testing/setup.md +120 -0
  49. package/skills/typesafety/SKILL.md +310 -26
  50. package/skills/use-cache/SKILL.md +36 -5
  51. package/skills/vercel/SKILL.md +107 -0
  52. package/skills/view-transitions/SKILL.md +294 -0
  53. package/src/__augment-tests__/augment.ts +81 -0
  54. package/src/__augment-tests__/augmented.check.ts +116 -0
  55. package/src/__internal.ts +0 -65
  56. package/src/browser/action-coordinator.ts +53 -36
  57. package/src/browser/action-fence.ts +47 -0
  58. package/src/browser/app-shell.ts +14 -27
  59. package/src/browser/cookie-name.ts +140 -0
  60. package/src/browser/event-controller.ts +37 -143
  61. package/src/browser/history-state.ts +21 -0
  62. package/src/browser/index.ts +3 -3
  63. package/src/browser/invalidate-client-cache.ts +52 -0
  64. package/src/browser/navigation-bridge.ts +30 -59
  65. package/src/browser/navigation-client.ts +96 -84
  66. package/src/browser/navigation-store-handle.ts +38 -0
  67. package/src/browser/navigation-store.ts +32 -82
  68. package/src/browser/navigation-transaction.ts +9 -59
  69. package/src/browser/partial-update.ts +60 -127
  70. package/src/browser/prefetch/cache.ts +82 -72
  71. package/src/browser/prefetch/fetch.ts +108 -33
  72. package/src/browser/prefetch/queue.ts +6 -3
  73. package/src/browser/rango-state.ts +157 -115
  74. package/src/browser/react/Link.tsx +0 -2
  75. package/src/browser/react/NavigationProvider.tsx +41 -48
  76. package/src/browser/react/ScrollRestoration.tsx +10 -6
  77. package/src/browser/react/filter-segment-order.ts +0 -2
  78. package/src/browser/react/index.ts +0 -48
  79. package/src/browser/react/location-state-shared.ts +166 -8
  80. package/src/browser/react/location-state.ts +39 -14
  81. package/src/browser/react/use-action.ts +6 -15
  82. package/src/browser/react/use-handle.ts +17 -14
  83. package/src/browser/react/use-link-status.ts +0 -4
  84. package/src/browser/react/use-navigation.ts +0 -3
  85. package/src/browser/react/use-params.ts +3 -6
  86. package/src/browser/react/use-reverse.ts +106 -0
  87. package/src/browser/react/use-router.ts +20 -5
  88. package/src/browser/react/use-search-params.ts +0 -5
  89. package/src/browser/react/use-segments.ts +0 -13
  90. package/src/browser/response-adapter.ts +52 -1
  91. package/src/browser/rsc-router.tsx +70 -34
  92. package/src/browser/scroll-restoration.ts +22 -14
  93. package/src/browser/segment-structure-assert.ts +2 -2
  94. package/src/browser/server-action-bridge.ts +168 -44
  95. package/src/browser/types.ts +36 -21
  96. package/src/browser/validate-redirect-origin.ts +43 -16
  97. package/src/build/collect-fallback-refs.ts +107 -0
  98. package/src/build/generate-manifest.ts +60 -35
  99. package/src/build/generate-route-types.ts +3 -0
  100. package/src/build/index.ts +8 -2
  101. package/src/build/prefix-tree-utils.ts +123 -0
  102. package/src/build/route-trie.ts +89 -11
  103. package/src/build/route-types/codegen.ts +4 -4
  104. package/src/build/route-types/include-resolution.ts +1 -1
  105. package/src/build/route-types/param-extraction.ts +6 -3
  106. package/src/build/route-types/per-module-writer.ts +7 -4
  107. package/src/build/route-types/router-processing.ts +122 -22
  108. package/src/build/route-types/scan-filter.ts +1 -1
  109. package/src/build/route-types/source-scan.ts +118 -0
  110. package/src/build/runtime-discovery.ts +9 -20
  111. package/src/cache/cache-error.ts +104 -0
  112. package/src/cache/cache-policy.ts +68 -28
  113. package/src/cache/cache-runtime.ts +134 -32
  114. package/src/cache/cache-scope.ts +100 -74
  115. package/src/cache/cache-tag.ts +98 -0
  116. package/src/cache/cf/cf-cache-store.ts +2255 -238
  117. package/src/cache/cf/index.ts +6 -16
  118. package/src/cache/document-cache.ts +61 -20
  119. package/src/cache/handle-snapshot.ts +63 -0
  120. package/src/cache/index.ts +22 -20
  121. package/src/cache/memory-segment-store.ts +136 -37
  122. package/src/cache/profile-registry.ts +6 -30
  123. package/src/cache/read-through-swr.ts +41 -11
  124. package/src/cache/segment-codec.ts +0 -16
  125. package/src/cache/tag-invalidation.ts +230 -0
  126. package/src/cache/types.ts +33 -100
  127. package/src/cache/vercel/index.ts +11 -0
  128. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  129. package/src/client.rsc.tsx +6 -21
  130. package/src/client.tsx +25 -61
  131. package/src/component-utils.ts +19 -0
  132. package/src/context-var.ts +17 -5
  133. package/src/decode-loader-results.ts +36 -0
  134. package/src/defer.ts +196 -0
  135. package/src/deps/ssr.ts +0 -1
  136. package/src/errors.ts +30 -4
  137. package/src/handle.ts +31 -23
  138. package/src/handles/MetaTags.tsx +0 -14
  139. package/src/handles/breadcrumbs.ts +16 -5
  140. package/src/handles/meta.ts +0 -39
  141. package/src/host/cookie-handler.ts +0 -36
  142. package/src/host/errors.ts +0 -24
  143. package/src/host/index.ts +8 -2
  144. package/src/host/pattern-matcher.ts +7 -50
  145. package/src/host/router.ts +107 -99
  146. package/src/host/testing.ts +40 -27
  147. package/src/host/types.ts +37 -4
  148. package/src/host/utils.ts +1 -1
  149. package/src/href-client.ts +137 -22
  150. package/src/index.rsc.ts +63 -9
  151. package/src/index.ts +64 -9
  152. package/src/internal-debug.ts +2 -4
  153. package/src/loader-store.ts +500 -0
  154. package/src/loader.rsc.ts +20 -13
  155. package/src/loader.ts +12 -11
  156. package/src/missing-id-error.ts +68 -0
  157. package/src/network-error-thrower.tsx +1 -6
  158. package/src/outlet-provider.tsx +1 -5
  159. package/src/prerender/param-hash.ts +10 -11
  160. package/src/prerender/store.ts +32 -37
  161. package/src/prerender.ts +61 -6
  162. package/src/redirect-origin.ts +100 -0
  163. package/src/response-utils.ts +9 -0
  164. package/src/reverse.ts +65 -41
  165. package/src/root-error-boundary.tsx +1 -19
  166. package/src/route-content-wrapper.tsx +7 -72
  167. package/src/route-definition/dsl-helpers.ts +244 -281
  168. package/src/route-definition/helper-factories.ts +29 -139
  169. package/src/route-definition/helpers-types.ts +40 -17
  170. package/src/route-definition/redirect.ts +43 -9
  171. package/src/route-definition/resolve-handler-use.ts +6 -0
  172. package/src/route-definition/use-item-types.ts +32 -0
  173. package/src/route-map-builder.ts +0 -16
  174. package/src/route-types.ts +19 -41
  175. package/src/router/basename.ts +14 -0
  176. package/src/router/content-negotiation.ts +15 -15
  177. package/src/router/error-handling.ts +13 -17
  178. package/src/router/find-match.ts +44 -23
  179. package/src/router/handler-context.ts +4 -42
  180. package/src/router/intercept-resolution.ts +14 -19
  181. package/src/router/lazy-includes.ts +9 -46
  182. package/src/router/loader-resolution.ts +91 -46
  183. package/src/router/logging.ts +0 -6
  184. package/src/router/manifest.ts +18 -29
  185. package/src/router/match-api.ts +0 -20
  186. package/src/router/match-context.ts +0 -22
  187. package/src/router/match-handlers.ts +57 -58
  188. package/src/router/match-middleware/background-revalidation.ts +0 -7
  189. package/src/router/match-middleware/cache-lookup.ts +150 -271
  190. package/src/router/match-middleware/cache-store.ts +3 -33
  191. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  192. package/src/router/match-middleware/segment-resolution.ts +0 -22
  193. package/src/router/match-pipelines.ts +1 -42
  194. package/src/router/match-result.ts +31 -80
  195. package/src/router/metrics.ts +0 -34
  196. package/src/router/middleware-types.ts +0 -116
  197. package/src/router/middleware.ts +118 -133
  198. package/src/router/navigation-snapshot.ts +0 -51
  199. package/src/router/params-util.ts +23 -0
  200. package/src/router/pattern-matching.ts +20 -58
  201. package/src/router/prerender-match.ts +99 -63
  202. package/src/router/preview-match.ts +3 -1
  203. package/src/router/request-classification.ts +28 -62
  204. package/src/router/revalidation.ts +50 -56
  205. package/src/router/route-snapshot.ts +0 -1
  206. package/src/router/router-context.ts +0 -27
  207. package/src/router/router-interfaces.ts +68 -35
  208. package/src/router/router-options.ts +55 -1
  209. package/src/router/router-registry.ts +2 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -63
  211. package/src/router/segment-resolution/helpers.ts +34 -0
  212. package/src/router/segment-resolution/loader-cache.ts +40 -37
  213. package/src/router/segment-resolution/revalidation.ts +203 -285
  214. package/src/router/segment-resolution/static-store.ts +19 -5
  215. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  216. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  217. package/src/router/segment-resolution.ts +4 -1
  218. package/src/router/segment-wrappers.ts +0 -3
  219. package/src/router/state-cookie-name.ts +33 -0
  220. package/src/router/substitute-pattern-params.ts +56 -0
  221. package/src/router/telemetry-otel.ts +0 -20
  222. package/src/router/telemetry.ts +96 -19
  223. package/src/router/timeout.ts +0 -20
  224. package/src/router/trie-matching.ts +87 -47
  225. package/src/router/types.ts +9 -63
  226. package/src/router/url-params.ts +0 -5
  227. package/src/router.ts +80 -41
  228. package/src/rsc/handler-context.ts +3 -2
  229. package/src/rsc/handler.ts +83 -78
  230. package/src/rsc/helpers.ts +93 -5
  231. package/src/rsc/index.ts +1 -1
  232. package/src/rsc/json-route-result.ts +38 -0
  233. package/src/rsc/manifest-init.ts +28 -41
  234. package/src/rsc/origin-guard.ts +39 -25
  235. package/src/rsc/progressive-enhancement.ts +12 -1
  236. package/src/rsc/redirect-guard.ts +99 -0
  237. package/src/rsc/response-error.ts +79 -12
  238. package/src/rsc/response-route-handler.ts +76 -62
  239. package/src/rsc/rsc-rendering.ts +41 -60
  240. package/src/rsc/runtime-warnings.ts +23 -10
  241. package/src/rsc/server-action.ts +62 -67
  242. package/src/rsc/ssr-setup.ts +16 -0
  243. package/src/rsc/types.ts +10 -5
  244. package/src/runtime-env.ts +18 -0
  245. package/src/search-params.ts +4 -20
  246. package/src/segment-loader-promise.ts +14 -2
  247. package/src/segment-system.tsx +199 -142
  248. package/src/serialize.ts +243 -0
  249. package/src/server/context.ts +150 -51
  250. package/src/server/cookie-store.ts +80 -5
  251. package/src/server/handle-store.ts +7 -24
  252. package/src/server/loader-registry.ts +5 -24
  253. package/src/server/request-context.ts +165 -87
  254. package/src/ssr/index.tsx +14 -14
  255. package/src/static-handler.ts +10 -13
  256. package/src/testing/cache-status.ts +162 -0
  257. package/src/testing/collect-handle.ts +40 -0
  258. package/src/testing/dispatch.ts +618 -0
  259. package/src/testing/dom.entry.ts +22 -0
  260. package/src/testing/e2e/fixture.ts +188 -0
  261. package/src/testing/e2e/index.ts +128 -0
  262. package/src/testing/e2e/matchers.ts +35 -0
  263. package/src/testing/e2e/page-helpers.ts +272 -0
  264. package/src/testing/e2e/parity.ts +387 -0
  265. package/src/testing/e2e/server.ts +195 -0
  266. package/src/testing/flight-matchers.ts +97 -0
  267. package/src/testing/flight-normalize.ts +11 -0
  268. package/src/testing/flight-runtime.d.ts +57 -0
  269. package/src/testing/flight-tree.ts +682 -0
  270. package/src/testing/flight.entry.ts +52 -0
  271. package/src/testing/flight.ts +232 -0
  272. package/src/testing/generated-routes.ts +183 -0
  273. package/src/testing/index.ts +99 -0
  274. package/src/testing/internal/context.ts +348 -0
  275. package/src/testing/internal/flight-client-globals.ts +30 -0
  276. package/src/testing/internal/seed-vars.ts +54 -0
  277. package/src/testing/render-handler.ts +330 -0
  278. package/src/testing/render-route.tsx +566 -0
  279. package/src/testing/run-loader.ts +378 -0
  280. package/src/testing/run-middleware.ts +205 -0
  281. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  282. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  283. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  284. package/src/testing/vitest-stubs/version.ts +5 -0
  285. package/src/testing/vitest.ts +305 -0
  286. package/src/theme/ThemeProvider.tsx +0 -52
  287. package/src/theme/ThemeScript.tsx +0 -6
  288. package/src/theme/constants.ts +0 -12
  289. package/src/theme/index.ts +0 -7
  290. package/src/theme/theme-context.ts +1 -5
  291. package/src/theme/theme-script.ts +0 -14
  292. package/src/theme/use-theme.ts +0 -3
  293. package/src/types/boundaries.ts +0 -35
  294. package/src/types/cache-types.ts +13 -4
  295. package/src/types/error-types.ts +30 -90
  296. package/src/types/global-namespace.ts +54 -41
  297. package/src/types/handler-context.ts +97 -22
  298. package/src/types/index.ts +1 -10
  299. package/src/types/loader-types.ts +6 -3
  300. package/src/types/request-scope.ts +0 -19
  301. package/src/types/route-config.ts +6 -50
  302. package/src/types/route-entry.ts +0 -6
  303. package/src/types/segments.ts +18 -14
  304. package/src/urls/include-helper.ts +9 -56
  305. package/src/urls/index.ts +1 -11
  306. package/src/urls/path-helper-types.ts +19 -5
  307. package/src/urls/path-helper.ts +17 -106
  308. package/src/urls/pattern-types.ts +36 -19
  309. package/src/urls/response-types.ts +20 -19
  310. package/src/urls/type-extraction.ts +58 -139
  311. package/src/urls/urls-function.ts +1 -18
  312. package/src/use-loader.tsx +292 -107
  313. package/src/vite/debug.ts +1 -0
  314. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  315. package/src/vite/discovery/discover-routers.ts +95 -82
  316. package/src/vite/discovery/discovery-errors.ts +194 -0
  317. package/src/vite/discovery/prerender-collection.ts +26 -34
  318. package/src/vite/discovery/route-types-writer.ts +40 -84
  319. package/src/vite/discovery/state.ts +39 -1
  320. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  321. package/src/vite/index.ts +4 -0
  322. package/src/vite/plugin-types.ts +185 -10
  323. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  324. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  325. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  326. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  327. package/src/vite/plugins/expose-action-id.ts +4 -75
  328. package/src/vite/plugins/expose-id-utils.ts +3 -54
  329. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  330. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  331. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  332. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  333. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  334. package/src/vite/plugins/performance-tracks.ts +9 -16
  335. package/src/vite/plugins/refresh-cmd.ts +1 -1
  336. package/src/vite/plugins/use-cache-transform.ts +26 -49
  337. package/src/vite/plugins/vercel-output.ts +258 -0
  338. package/src/vite/plugins/version-injector.ts +2 -32
  339. package/src/vite/plugins/version-plugin.ts +32 -23
  340. package/src/vite/plugins/virtual-entries.ts +35 -17
  341. package/src/vite/rango.ts +148 -115
  342. package/src/vite/router-discovery.ts +220 -68
  343. package/src/vite/utils/ast-handler-extract.ts +15 -31
  344. package/src/vite/utils/bundle-analysis.ts +10 -15
  345. package/src/vite/utils/client-chunks.ts +184 -0
  346. package/src/vite/utils/forward-user-plugins.ts +171 -0
  347. package/src/vite/utils/manifest-utils.ts +4 -59
  348. package/src/vite/utils/package-resolution.ts +1 -73
  349. package/src/vite/utils/prerender-utils.ts +0 -35
  350. package/src/vite/utils/shared-utils.ts +95 -43
  351. package/src/browser/action-response-classifier.ts +0 -99
  352. package/src/browser/react/use-client-cache.ts +0 -58
  353. package/src/browser/shallow.ts +0 -40
  354. package/src/handles/index.ts +0 -7
  355. package/src/router/middleware-cookies.ts +0 -55
@@ -23,10 +23,6 @@ import { negotiateRoute } from "./content-negotiation.js";
23
23
  import { stripInternalParams } from "./handler-context.js";
24
24
  import { resolveRoute, type RouteSnapshot } from "./route-snapshot.js";
25
25
 
26
- // ---------------------------------------------------------------------------
27
- // RequestPlan — discriminated union
28
- // ---------------------------------------------------------------------------
29
-
30
26
  interface RedirectPlan<TEnv = any> {
31
27
  mode: "redirect";
32
28
  route: RouteSnapshot<TEnv>;
@@ -40,6 +36,12 @@ interface VersionMismatchPlan<TEnv = any> {
40
36
  reloadUrl: string;
41
37
  }
42
38
 
39
+ interface AppSwitchReloadPlan {
40
+ mode: "app-switch";
41
+ /** Clean target URL (internal _rsc_* params stripped) to navigate to. */
42
+ reloadUrl: string;
43
+ }
44
+
43
45
  interface ResponseRoutePlan<TEnv = any> {
44
46
  mode: "response";
45
47
  route: RouteSnapshot<TEnv>;
@@ -86,6 +88,7 @@ interface PartialRenderPlan<TEnv = any> {
86
88
  export type RequestPlan<TEnv = any> =
87
89
  | RedirectPlan<TEnv>
88
90
  | VersionMismatchPlan<TEnv>
91
+ | AppSwitchReloadPlan
89
92
  | ResponseRoutePlan<TEnv>
90
93
  | LoaderFetchPlan<TEnv>
91
94
  | PeRenderPlan<TEnv>
@@ -94,12 +97,13 @@ export type RequestPlan<TEnv = any> =
94
97
  | PartialRenderPlan<TEnv>;
95
98
 
96
99
  /**
97
- * Plans that have passed the terminal-check gate (version-mismatch handled)
98
- * and are ready for execution. Always have a `route` field.
100
+ * Plans that have passed the terminal-check gate (version-mismatch and
101
+ * app-switch reloads handled) and are ready for execution. Always have a
102
+ * `route` field.
99
103
  */
100
104
  export type ExecutableRequestPlan<TEnv = any> = Exclude<
101
105
  RequestPlan<TEnv>,
102
- VersionMismatchPlan<TEnv>
106
+ VersionMismatchPlan<TEnv> | AppSwitchReloadPlan
103
107
  >;
104
108
 
105
109
  /**
@@ -116,10 +120,6 @@ export type {
116
120
  PartialRenderPlan,
117
121
  };
118
122
 
119
- // ---------------------------------------------------------------------------
120
- // classifyRequest — the single authoritative classification step
121
- // ---------------------------------------------------------------------------
122
-
123
123
  export interface ClassifyRequestDeps<TEnv = any> {
124
124
  findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
125
125
  routerVersion: string;
@@ -149,15 +149,12 @@ export async function classifyRequest<TEnv = any>(
149
149
  const isAction =
150
150
  request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
151
151
 
152
- // Version mismatch — runs BEFORE route resolution so stale clients
153
- // requesting removed routes get a reload, not a 404.
154
152
  const clientVersion = url.searchParams.get("_rsc_v");
155
153
  if (
156
154
  deps.routerVersion &&
157
155
  clientVersion &&
158
156
  clientVersion !== deps.routerVersion
159
157
  ) {
160
- // Strip internal _rsc_* params so the browser reloads to a clean URL
161
158
  let reloadUrl = stripInternalParams(url).toString();
162
159
  if (isAction) {
163
160
  const referer = request.headers.get("referer");
@@ -167,9 +164,7 @@ export async function classifyRequest<TEnv = any>(
167
164
  if (refererUrl.origin === url.origin) {
168
165
  reloadUrl = referer;
169
166
  }
170
- } catch {
171
- // Malformed referer, fall back to stripped url
172
- }
167
+ } catch {}
173
168
  }
174
169
  }
175
170
 
@@ -179,9 +174,18 @@ export async function classifyRequest<TEnv = any>(
179
174
  };
180
175
  }
181
176
 
182
- // No metricsStore — classification is a lightweight gating step.
183
- // Route-matching and manifest-loading metrics belong in the match path
184
- // (createMatchContextForFull/Partial) which runs the authoritative resolution.
177
+ const clientRouterId = url.searchParams.get("_rsc_rid");
178
+ if (
179
+ clientRouterId &&
180
+ clientRouterId !== deps.routerId &&
181
+ url.searchParams.has("_rsc_partial")
182
+ ) {
183
+ return {
184
+ mode: "app-switch",
185
+ reloadUrl: stripInternalParams(url).toString(),
186
+ };
187
+ }
188
+
185
189
  const result = await resolveRoute<TEnv>(pathname, {
186
190
  findMatch: deps.findMatch,
187
191
  lite: true,
@@ -193,7 +197,6 @@ export async function classifyRequest<TEnv = any>(
193
197
  });
194
198
  }
195
199
 
196
- // Redirect
197
200
  if (result.type === "redirect") {
198
201
  const snapshot: RouteSnapshot<TEnv> = {
199
202
  matched: result as any,
@@ -215,7 +218,6 @@ export async function classifyRequest<TEnv = any>(
215
218
 
216
219
  const snapshot = result.snapshot;
217
220
 
218
- // Response route — non-RSC short-circuit (JSON, streaming, etc.)
219
221
  const responseResult = await classifyResponseRoute(
220
222
  request,
221
223
  pathname,
@@ -225,7 +227,6 @@ export async function classifyRequest<TEnv = any>(
225
227
  return responseResult;
226
228
  }
227
229
 
228
- // Mode detection from request signals
229
230
  const actionId =
230
231
  request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
231
232
  const isLoaderFetch = url.searchParams.has("_rsc_loader");
@@ -243,7 +244,6 @@ export async function classifyRequest<TEnv = any>(
243
244
  return { mode: "loader", route: snapshot };
244
245
  }
245
246
 
246
- // PE detection: POST with form content-type, but not a server action
247
247
  const contentType = request.headers.get("content-type") || "";
248
248
  const isFormSubmission =
249
249
  contentType.includes("multipart/form-data") ||
@@ -252,22 +252,13 @@ export async function classifyRequest<TEnv = any>(
252
252
  return { mode: "pe-render", route: snapshot };
253
253
  }
254
254
 
255
- // App switch: client's routerId doesn't match this router
256
- const clientRouterId = url.searchParams.get("_rsc_rid");
257
- const isAppSwitch = !!(clientRouterId && clientRouterId !== deps.routerId);
258
- const isPartial = url.searchParams.has("_rsc_partial") && !isAppSwitch;
259
-
260
- if (isPartial) {
255
+ if (url.searchParams.has("_rsc_partial")) {
261
256
  return { mode: "partial-render", route: snapshot, negotiated };
262
257
  }
263
258
 
264
259
  return { mode: "full-render", route: snapshot, negotiated };
265
260
  }
266
261
 
267
- // ---------------------------------------------------------------------------
268
- // Content negotiation for response routes
269
- // ---------------------------------------------------------------------------
270
-
271
262
  /**
272
263
  * Check if the route is a response route and perform content negotiation
273
264
  * if negotiate variants exist. Returns a ResponseRoutePlan if the route
@@ -278,33 +269,8 @@ async function classifyResponseRoute<TEnv>(
278
269
  pathname: string,
279
270
  snapshot: RouteSnapshot<TEnv>,
280
271
  ): Promise<ResponseRoutePlan<TEnv> | null> {
281
- const { manifestEntry, responseType } = snapshot;
282
-
283
272
  const negotiation = await negotiateRoute(request, pathname, snapshot);
284
- if (negotiation) {
285
- return {
286
- mode: "response",
287
- route: snapshot,
288
- ...negotiation,
289
- };
290
- }
291
-
292
- // Non-negotiated response route (no variants, or RSC won negotiation)
293
- if (responseType) {
294
- const handler =
295
- manifestEntry.type === "route" ? manifestEntry.handler : undefined;
296
- if (handler) {
297
- return {
298
- mode: "response",
299
- route: snapshot,
300
- handler,
301
- responseType,
302
- negotiated: false,
303
- manifestEntry,
304
- routeMiddleware: snapshot.routeMiddleware,
305
- };
306
- }
307
- }
308
-
309
- return null;
273
+ return negotiation
274
+ ? { mode: "response", route: snapshot, ...negotiation }
275
+ : null;
310
276
  }
@@ -4,7 +4,7 @@
4
4
  * Evaluates whether segments should revalidate based on params, actions, and custom functions.
5
5
  */
6
6
 
7
- import type { ResolvedSegment, HandlerContext } from "../types";
7
+ import type { ResolvedSegment, HandlerContext, ActionRef } from "../types";
8
8
  import type { ActionContext } from "./types";
9
9
  import {
10
10
  debugLog,
@@ -14,21 +14,52 @@ import {
14
14
  import type { RevalidationTraceEntry } from "./logging.js";
15
15
  import { _getRequestContext } from "../server/request-context.js";
16
16
  import { isAutoGeneratedRouteName } from "../route-name.js";
17
+ import { paramsEqual } from "./params-util.js";
17
18
 
18
- function paramsEqual(
19
- a: Record<string, string>,
20
- b: Record<string, string>,
21
- ): boolean {
22
- if (a === b) return true;
23
-
24
- const keysA = Object.keys(a);
25
- if (keysA.length !== Object.keys(b).length) return false;
26
-
27
- for (const key of keysA) {
28
- if (a[key] !== b[key]) return false;
29
- }
19
+ /**
20
+ * Resolve a server-action reference's stable id, mirroring how the action
21
+ * boundary derives `actionContext.actionId` in `rsc/server-action.ts`
22
+ * (`$id ?? $$id`): the file-path `$id` set by the expose-action-id plugin in a
23
+ * production RSC build when present, otherwise React's `$$id`. Resolving both
24
+ * the incoming `actionId` and the reference with the same precedence makes
25
+ * `isAction()` form-agnostic across dev and production.
26
+ */
27
+ function resolveActionRefId(ref: unknown): string | undefined {
28
+ if (ref == null) return undefined;
29
+ const r = ref as { $id?: unknown; $$id?: unknown };
30
+ if (typeof r.$id === "string") return r.$id;
31
+ if (typeof r.$$id === "string") return r.$$id;
32
+ return undefined;
33
+ }
30
34
 
31
- return true;
35
+ /**
36
+ * Build the `isAction()` helper bound to the current action's id. Called with no
37
+ * arguments it answers "is this request an action at all?" (any action) — `true`
38
+ * during action handling, `false` on plain navigation. Called with one or more
39
+ * action references it narrows to those: a single imported action, several
40
+ * (variadic), or any export of a namespace import (`import * as Mod`). Returns
41
+ * `false` when there is no action (plain navigation) or nothing matches.
42
+ */
43
+ function makeIsAction(
44
+ currentActionId: string | undefined,
45
+ ): (...actions: ActionRef[]) => boolean {
46
+ return (...actions: ActionRef[]): boolean => {
47
+ if (!currentActionId) return false;
48
+ // Bare isAction(): an action is in flight (currentActionId is set) and the
49
+ // caller did not narrow to a specific action, so this is "any action".
50
+ if (actions.length === 0) return true;
51
+ for (const action of actions) {
52
+ if (typeof action === "function") {
53
+ if (resolveActionRefId(action) === currentActionId) return true;
54
+ } else if (action && typeof action === "object") {
55
+ // Namespace import: match any export of the module.
56
+ for (const value of Object.values(action)) {
57
+ if (resolveActionRefId(value) === currentActionId) return true;
58
+ }
59
+ }
60
+ }
61
+ return false;
62
+ };
32
63
  }
33
64
 
34
65
  /**
@@ -95,8 +126,6 @@ export async function evaluateRevalidation<TEnv>(
95
126
  const paramsChanged = !paramsEqual(nextParams, prevParams);
96
127
  const searchChanged = prevUrl.search !== nextUrl.search;
97
128
 
98
- // Trace helper: push a structured entry to the request-scoped trace buffer.
99
- // Guarded by isTraceActive() so object construction is skipped in production.
100
129
  function pushTrace(
101
130
  defaultVal: boolean,
102
131
  finalVal: boolean,
@@ -115,43 +144,28 @@ export async function evaluateRevalidation<TEnv>(
115
144
  });
116
145
  }
117
146
 
118
- // Calculate default revalidation based on segment type and request method
119
147
  let defaultShouldRevalidate: boolean;
120
148
  let defaultReason: string;
121
149
 
122
150
  if (defaultOverride) {
123
- // Caller injected the seed (e.g. parallel slot not in clientSegmentIds).
124
- // Skip the type-derived heuristic — caller knows better in this context.
125
151
  defaultShouldRevalidate = defaultOverride.value;
126
152
  defaultReason = defaultOverride.reason;
127
153
  } else if (request.method === "POST") {
128
- // Actions: revalidate segments that belong to the route, skip parent chain
129
154
  if (segment.type === "route") {
130
- // Route segment always revalidates on actions
131
155
  defaultShouldRevalidate = true;
132
156
  defaultReason = "action:route-segment";
133
157
  } else if (segment.type === "loader") {
134
- // Loaders always revalidate on actions - they often contain action-sensitive data
135
- // (e.g., cart count after add-to-cart action)
136
158
  defaultShouldRevalidate = true;
137
159
  defaultReason = "action:loader-segment";
138
160
  } else if (segment.belongsToRoute) {
139
- // Segment belongs to route (orphan layouts/parallels) - revalidate
140
161
  defaultShouldRevalidate = true;
141
162
  defaultReason = "action:belongs-to-route";
142
163
  } else {
143
- // Parent chain segment (shared layouts/parallels) - don't revalidate
144
164
  defaultShouldRevalidate = false;
145
165
  defaultReason = "action:parent-chain-skip";
146
166
  }
147
167
  } else {
148
- // Navigation (GET): Conservative defaults to minimize unnecessary revalidations
149
- // Only the route segment revalidates by default - all others require explicit opt-in
150
-
151
168
  if (segment.type === "route") {
152
- // Route segments revalidate when path params OR search params change.
153
- // Search params (e.g., ?page=2&sort=price) are server-parsed via ctx.search,
154
- // so the handler must re-execute to produce updated content.
155
169
  const routeChanged = paramsChanged || searchChanged;
156
170
  defaultShouldRevalidate = routeChanged;
157
171
  defaultReason = paramsChanged
@@ -167,8 +181,6 @@ export async function evaluateRevalidation<TEnv>(
167
181
  });
168
182
  }
169
183
  } else if (segment.belongsToRoute && (paramsChanged || searchChanged)) {
170
- // Children of the route path (loaders, orphan layouts/parallels)
171
- // revalidate when path params or search params change
172
184
  defaultShouldRevalidate = true;
173
185
  defaultReason = paramsChanged
174
186
  ? "nav:route-child-params-changed"
@@ -180,9 +192,6 @@ export async function evaluateRevalidation<TEnv>(
180
192
  searchChanged,
181
193
  });
182
194
  } else {
183
- // Parent layouts and parallels default to no revalidation
184
- // Cannot assume these segments depend on params without explicit declaration
185
- // Use custom revalidation functions to opt-in when needed
186
195
  defaultShouldRevalidate = false;
187
196
  defaultReason = "nav:non-route-skip";
188
197
  debugLog("revalidation", "non-route segment skipped by default", {
@@ -192,7 +201,6 @@ export async function evaluateRevalidation<TEnv>(
192
201
  }
193
202
  }
194
203
 
195
- // No custom revalidations defined - return default behavior without prev segment
196
204
  if (revalidations.length === 0) {
197
205
  if (defaultShouldRevalidate) {
198
206
  debugLog("revalidation", "default revalidate=true", {
@@ -209,14 +217,10 @@ export async function evaluateRevalidation<TEnv>(
209
217
  return defaultShouldRevalidate;
210
218
  }
211
219
 
212
- // Custom revalidations exist - may need full prev segment
213
- // Lazy load prev segment only if getPrevSegment provided
214
220
  const prevSegment = getPrevSegment ? await getPrevSegment() : null;
215
221
 
216
- // Execute revalidation functions with soft/hard decision pattern
217
222
  let currentSuggestion = defaultShouldRevalidate;
218
223
 
219
- // Compute public route names (filtered: undefined for auto-generated routes)
220
224
  const toRouteName =
221
225
  routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
222
226
  const reqCtx = _getRequestContext();
@@ -240,23 +244,18 @@ export async function evaluateRevalidation<TEnv>(
240
244
  slotName: segment.slot,
241
245
  // Action context (only populated when triggered by server action)
242
246
  actionId: actionContext?.actionId,
247
+ isAction: makeIsAction(actionContext?.actionId),
243
248
  actionUrl: actionContext?.actionUrl,
244
249
  actionResult: actionContext?.actionResult,
245
250
  formData: actionContext?.formData,
246
- method: request.method, // GET for navigation, POST for actions
247
- routeName: toRouteName, // Navigation target route name (filtered)
248
- fromRouteName, // Navigation source route name (filtered)
249
- toRouteName, // Navigation target route name (filtered)
250
- // Stale cache context (only true for background revalidation after stale cache render)
251
+ method: request.method,
252
+ routeName: toRouteName,
253
+ fromRouteName,
254
+ toRouteName,
251
255
  stale,
252
256
  });
253
257
 
254
- // Check return type:
255
- // - boolean: hard decision, short-circuit immediately
256
- // - { defaultShouldRevalidate: boolean }: soft decision, update suggestion and continue
257
- // - null/undefined: use default behavior (equivalent to returning { defaultShouldRevalidate })
258
258
  if (typeof result === "boolean") {
259
- // Hard decision - short-circuit
260
259
  debugLog("revalidation", "hard decision", {
261
260
  segmentId: segment.id,
262
261
  revalidator: name,
@@ -269,7 +268,6 @@ export async function evaluateRevalidation<TEnv>(
269
268
  typeof result === "object" &&
270
269
  "defaultShouldRevalidate" in result
271
270
  ) {
272
- // Soft decision - update suggestion and continue
273
271
  currentSuggestion = result.defaultShouldRevalidate;
274
272
  debugLog("revalidation", "soft decision", {
275
273
  segmentId: segment.id,
@@ -277,18 +275,14 @@ export async function evaluateRevalidation<TEnv>(
277
275
  revalidate: currentSuggestion,
278
276
  });
279
277
  } else if (result === null || result === undefined) {
280
- // Defer to default - equivalent to { defaultShouldRevalidate: currentSuggestion }
281
- // This means "I don't care, use whatever the default is"
282
278
  debugLog("revalidation", "deferred to current default", {
283
279
  segmentId: segment.id,
284
280
  revalidator: name,
285
281
  revalidate: currentSuggestion,
286
282
  });
287
- // currentSuggestion stays the same, continue to next function
288
283
  }
289
284
  }
290
285
 
291
- // All revalidators completed - use final suggestion
292
286
  debugLog("revalidation", "final decision", {
293
287
  segmentId: segment.id,
294
288
  revalidate: currentSuggestion,
@@ -230,7 +230,6 @@ export function createRouteSnapshot<TEnv = any>(
230
230
  entry: {} as any,
231
231
  routeKey: "test",
232
232
  params: {},
233
- optionalParams: new Set(),
234
233
  } as RouteMatchResult<TEnv>,
235
234
  manifestEntry: { type: "route", shortCode: "R0", parent: null } as any,
236
235
  entries: [],
@@ -54,10 +54,8 @@ export interface InterceptResult {
54
54
  * Instead of passing 20+ parameters, middleware calls getRouterContext() to access them.
55
55
  */
56
56
  export interface RouterContext<TEnv = any> {
57
- // Route matching
58
57
  findMatch: (pathname: string) => RouteMatchResult | null;
59
58
 
60
- // Manifest loading
61
59
  loadManifest: (
62
60
  entry: any,
63
61
  routeKey: string,
@@ -66,10 +64,8 @@ export interface RouterContext<TEnv = any> {
66
64
  isSSR?: boolean,
67
65
  ) => Promise<EntryData>;
68
66
 
69
- // Entry traversal
70
67
  traverseBack: (entry: EntryData) => Generator<EntryData>;
71
68
 
72
- // Handler context creation
73
69
  createHandlerContext: (
74
70
  params: Record<string, string>,
75
71
  request: Request,
@@ -83,7 +79,6 @@ export interface RouterContext<TEnv = any> {
83
79
  isPassthroughRoute?: boolean,
84
80
  ) => HandlerContext<any, TEnv>;
85
81
 
86
- // Loader setup
87
82
  setupLoaderAccess: (
88
83
  ctx: HandlerContext<any, TEnv>,
89
84
  loaderPromises: Map<string, Promise<any>>,
@@ -94,7 +89,6 @@ export interface RouterContext<TEnv = any> {
94
89
  loaderPromises: Map<string, Promise<any>>,
95
90
  ) => void;
96
91
 
97
- // Context access
98
92
  getContext: () => {
99
93
  getOrCreateStore: (key: string) => any;
100
94
  runWithStore: <T>(
@@ -105,16 +99,13 @@ export interface RouterContext<TEnv = any> {
105
99
  ) => T;
106
100
  };
107
101
 
108
- // Metrics
109
102
  getMetricsStore: () => MetricsStore | undefined;
110
103
 
111
- // Cache
112
104
  createCacheScope: (
113
105
  cacheConfig: any,
114
106
  parent: CacheScope | null,
115
107
  ) => CacheScope | null;
116
108
 
117
- // Intercept detection
118
109
  findInterceptForRoute: (
119
110
  routeKey: string,
120
111
  parentEntry: EntryData | null,
@@ -122,7 +113,6 @@ export interface RouterContext<TEnv = any> {
122
113
  isAction: boolean,
123
114
  ) => InterceptResult | null;
124
115
 
125
- // Segment resolution (with revalidation)
126
116
  resolveAllSegmentsWithRevalidation: (
127
117
  entries: EntryData[],
128
118
  routeKey: string,
@@ -133,7 +123,6 @@ export interface RouterContext<TEnv = any> {
133
123
  request: Request,
134
124
  prevUrl: URL,
135
125
  nextUrl: URL,
136
- loaderPromises: Map<string, Promise<any>>,
137
126
  actionContext: any | undefined,
138
127
  interceptResult: InterceptResult | null,
139
128
  localRouteName: string,
@@ -166,12 +155,10 @@ export interface RouterContext<TEnv = any> {
166
155
  revalidationContext?: RevalidationContext,
167
156
  ) => Promise<ResolvedSegment[]>;
168
157
 
169
- // Collect with markers
170
158
  collectWithMarkers?: <T>(
171
159
  gen: AsyncGenerator<T | { __type: "id"; id: string }>,
172
160
  ) => Promise<{ items: T[]; matchedIds: string[] }>;
173
161
 
174
- // Revalidation evaluation
175
162
  evaluateRevalidation: (params: {
176
163
  segment: ResolvedSegment;
177
164
  prevParams: Record<string, string>;
@@ -195,7 +182,6 @@ export interface RouterContext<TEnv = any> {
195
182
  | "intercept-loader";
196
183
  }) => Promise<boolean>;
197
184
 
198
- // Request context
199
185
  getRequestContext: () =>
200
186
  | {
201
187
  waitUntil: (fn: () => Promise<void>) => void;
@@ -203,7 +189,6 @@ export interface RouterContext<TEnv = any> {
203
189
  }
204
190
  | undefined;
205
191
 
206
- // Simple segment resolution (without revalidation - for full match)
207
192
  resolveAllSegments: (
208
193
  entries: EntryData[],
209
194
  routeKey: string,
@@ -213,7 +198,6 @@ export interface RouterContext<TEnv = any> {
213
198
  options?: { skipLoaders?: boolean },
214
199
  ) => Promise<ResolvedSegment[]>;
215
200
 
216
- // Generator-based simple resolution
217
201
  resolveAllSegmentsGenerator?: (
218
202
  entries: EntryData[],
219
203
  routeKey: string,
@@ -222,21 +206,17 @@ export interface RouterContext<TEnv = any> {
222
206
  loaderPromises: Map<string, Promise<any>>,
223
207
  ) => AsyncGenerator<ResolvedSegment | { __type: "id"; id: string }>;
224
208
 
225
- // Collect segments from generator
226
209
  collectSegmentsFromGenerator?: <T>(
227
210
  gen: AsyncGenerator<T | { __type: "id"; id: string }>,
228
211
  ) => Promise<T[]>;
229
212
 
230
- // Handle store
231
213
  createHandleStore: () => any;
232
214
 
233
- // Loaders-only resolution (for full match cache hit - no revalidation)
234
215
  resolveLoadersOnly?: (
235
216
  entries: EntryData[],
236
217
  handlerContext: HandlerContext<any, TEnv>,
237
218
  ) => Promise<ResolvedSegment[]>;
238
219
 
239
- // Loaders-only resolution (for cache hit scenarios)
240
220
  resolveLoadersOnlyWithRevalidation?: (
241
221
  entries: EntryData[],
242
222
  handlerContext: HandlerContext<any, TEnv>,
@@ -258,10 +238,8 @@ export interface RouterContext<TEnv = any> {
258
238
  // Telemetry sink (optional, no-op when undefined)
259
239
  telemetry?: TelemetrySink;
260
240
 
261
- // Request ID for telemetry span correlation (set per-request in match handlers)
262
241
  requestId?: string;
263
242
 
264
- // Intercept loaders only (for cache hit + intercept scenarios)
265
243
  resolveInterceptLoadersOnly?: (
266
244
  intercept: InterceptEntry,
267
245
  entry: EntryData,
@@ -284,7 +262,6 @@ export interface RouterContext<TEnv = any> {
284
262
  } | null>;
285
263
  }
286
264
 
287
- // AsyncLocalStorage instance for router context
288
265
  const routerContext = new AsyncLocalStorage<RouterContext<any>>();
289
266
 
290
267
  /**
@@ -308,10 +285,6 @@ export function getRouterContext<TEnv = any>(): RouterContext<TEnv> {
308
285
  *
309
286
  * All async code within fn() can call getRouterContext() to access router closures.
310
287
  * This works across async boundaries thanks to AsyncLocalStorage.
311
- *
312
- * @param deps Router dependencies to make available
313
- * @param fn Function to run with dependencies available
314
- * @returns Result of fn()
315
288
  */
316
289
  export function runWithRouterContext<T, TEnv = any>(
317
290
  deps: RouterContext<TEnv>,