@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430

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 (300) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4474 -867
  5. package/package.json +60 -51
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +334 -72
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +89 -30
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +78 -1
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +318 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +87 -64
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/intercept-utils.ts +52 -0
  38. package/src/browser/link-interceptor.ts +24 -4
  39. package/src/browser/logging.ts +55 -0
  40. package/src/browser/merge-segment-loaders.ts +20 -12
  41. package/src/browser/navigation-bridge.ts +285 -553
  42. package/src/browser/navigation-client.ts +124 -71
  43. package/src/browser/navigation-store.ts +33 -50
  44. package/src/browser/navigation-transaction.ts +295 -0
  45. package/src/browser/network-error-handler.ts +61 -0
  46. package/src/browser/partial-update.ts +258 -308
  47. package/src/browser/prefetch/cache.ts +146 -0
  48. package/src/browser/prefetch/fetch.ts +135 -0
  49. package/src/browser/prefetch/observer.ts +65 -0
  50. package/src/browser/prefetch/policy.ts +42 -0
  51. package/src/browser/prefetch/queue.ts +88 -0
  52. package/src/browser/rango-state.ts +112 -0
  53. package/src/browser/react/Link.tsx +185 -73
  54. package/src/browser/react/NavigationProvider.tsx +51 -11
  55. package/src/browser/react/context.ts +6 -0
  56. package/src/browser/react/filter-segment-order.ts +11 -0
  57. package/src/browser/react/index.ts +12 -12
  58. package/src/browser/react/location-state-shared.ts +95 -53
  59. package/src/browser/react/location-state.ts +60 -15
  60. package/src/browser/react/mount-context.ts +6 -1
  61. package/src/browser/react/nonce-context.ts +23 -0
  62. package/src/browser/react/shallow-equal.ts +27 -0
  63. package/src/browser/react/use-action.ts +29 -51
  64. package/src/browser/react/use-client-cache.ts +5 -3
  65. package/src/browser/react/use-handle.ts +32 -79
  66. package/src/browser/react/use-href.tsx +2 -2
  67. package/src/browser/react/use-link-status.ts +6 -5
  68. package/src/browser/react/use-navigation.ts +22 -63
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +107 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +16 -0
  79. package/src/browser/server-action-bridge.ts +504 -599
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +109 -47
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +235 -24
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +13 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +3 -1
  114. package/src/client.tsx +106 -126
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +15 -29
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/breadcrumbs.ts +66 -0
  123. package/src/handles/index.ts +1 -0
  124. package/src/handles/meta.ts +30 -13
  125. package/src/host/cookie-handler.ts +21 -15
  126. package/src/host/errors.ts +8 -8
  127. package/src/host/index.ts +4 -7
  128. package/src/host/pattern-matcher.ts +27 -27
  129. package/src/host/router.ts +61 -39
  130. package/src/host/testing.ts +8 -8
  131. package/src/host/types.ts +15 -7
  132. package/src/host/utils.ts +1 -1
  133. package/src/href-client.ts +119 -29
  134. package/src/index.rsc.ts +153 -19
  135. package/src/index.ts +211 -30
  136. package/src/internal-debug.ts +11 -0
  137. package/src/loader.rsc.ts +26 -157
  138. package/src/loader.ts +27 -10
  139. package/src/network-error-thrower.tsx +3 -1
  140. package/src/outlet-provider.tsx +45 -0
  141. package/src/prerender/param-hash.ts +37 -0
  142. package/src/prerender/store.ts +185 -0
  143. package/src/prerender.ts +463 -0
  144. package/src/reverse.ts +330 -0
  145. package/src/root-error-boundary.tsx +41 -29
  146. package/src/route-content-wrapper.tsx +7 -4
  147. package/src/route-definition/dsl-helpers.ts +934 -0
  148. package/src/route-definition/helper-factories.ts +200 -0
  149. package/src/route-definition/helpers-types.ts +430 -0
  150. package/src/route-definition/index.ts +52 -0
  151. package/src/route-definition/redirect.ts +93 -0
  152. package/src/route-definition.ts +1 -1428
  153. package/src/route-map-builder.ts +211 -123
  154. package/src/route-name.ts +53 -0
  155. package/src/route-types.ts +59 -8
  156. package/src/router/content-negotiation.ts +116 -0
  157. package/src/router/debug-manifest.ts +72 -0
  158. package/src/router/error-handling.ts +9 -9
  159. package/src/router/find-match.ts +158 -0
  160. package/src/router/handler-context.ts +374 -81
  161. package/src/router/intercept-resolution.ts +395 -0
  162. package/src/router/lazy-includes.ts +234 -0
  163. package/src/router/loader-resolution.ts +215 -122
  164. package/src/router/logging.ts +248 -0
  165. package/src/router/manifest.ts +148 -35
  166. package/src/router/match-api.ts +620 -0
  167. package/src/router/match-context.ts +5 -3
  168. package/src/router/match-handlers.ts +440 -0
  169. package/src/router/match-middleware/background-revalidation.ts +80 -93
  170. package/src/router/match-middleware/cache-lookup.ts +382 -9
  171. package/src/router/match-middleware/cache-store.ts +51 -22
  172. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  173. package/src/router/match-middleware/segment-resolution.ts +24 -6
  174. package/src/router/match-pipelines.ts +10 -45
  175. package/src/router/match-result.ts +34 -28
  176. package/src/router/metrics.ts +235 -15
  177. package/src/router/middleware-cookies.ts +55 -0
  178. package/src/router/middleware-types.ts +222 -0
  179. package/src/router/middleware.ts +324 -367
  180. package/src/router/pattern-matching.ts +211 -43
  181. package/src/router/prerender-match.ts +402 -0
  182. package/src/router/preview-match.ts +170 -0
  183. package/src/router/revalidation.ts +137 -38
  184. package/src/router/router-context.ts +36 -21
  185. package/src/router/router-interfaces.ts +452 -0
  186. package/src/router/router-options.ts +592 -0
  187. package/src/router/router-registry.ts +24 -0
  188. package/src/router/segment-resolution/fresh.ts +570 -0
  189. package/src/router/segment-resolution/helpers.ts +263 -0
  190. package/src/router/segment-resolution/loader-cache.ts +198 -0
  191. package/src/router/segment-resolution/revalidation.ts +1241 -0
  192. package/src/router/segment-resolution/static-store.ts +67 -0
  193. package/src/router/segment-resolution.ts +21 -0
  194. package/src/router/segment-wrappers.ts +289 -0
  195. package/src/router/telemetry-otel.ts +299 -0
  196. package/src/router/telemetry.ts +300 -0
  197. package/src/router/timeout.ts +148 -0
  198. package/src/router/trie-matching.ts +239 -0
  199. package/src/router/types.ts +77 -3
  200. package/src/router.ts +692 -4257
  201. package/src/rsc/handler-context.ts +45 -0
  202. package/src/rsc/handler.ts +764 -754
  203. package/src/rsc/helpers.ts +140 -6
  204. package/src/rsc/index.ts +0 -20
  205. package/src/rsc/loader-fetch.ts +209 -0
  206. package/src/rsc/manifest-init.ts +86 -0
  207. package/src/rsc/nonce.ts +14 -0
  208. package/src/rsc/origin-guard.ts +141 -0
  209. package/src/rsc/progressive-enhancement.ts +379 -0
  210. package/src/rsc/response-error.ts +37 -0
  211. package/src/rsc/response-route-handler.ts +347 -0
  212. package/src/rsc/rsc-rendering.ts +235 -0
  213. package/src/rsc/runtime-warnings.ts +42 -0
  214. package/src/rsc/server-action.ts +348 -0
  215. package/src/rsc/ssr-setup.ts +128 -0
  216. package/src/rsc/types.ts +38 -11
  217. package/src/search-params.ts +230 -0
  218. package/src/segment-system.tsx +25 -13
  219. package/src/server/context.ts +182 -51
  220. package/src/server/cookie-store.ts +190 -0
  221. package/src/server/fetchable-loader-store.ts +37 -0
  222. package/src/server/handle-store.ts +94 -15
  223. package/src/server/loader-registry.ts +15 -56
  224. package/src/server/request-context.ts +430 -70
  225. package/src/server.ts +35 -130
  226. package/src/ssr/index.tsx +100 -31
  227. package/src/static-handler.ts +114 -0
  228. package/src/theme/ThemeProvider.tsx +21 -15
  229. package/src/theme/ThemeScript.tsx +5 -5
  230. package/src/theme/constants.ts +5 -2
  231. package/src/theme/index.ts +4 -14
  232. package/src/theme/theme-context.ts +4 -30
  233. package/src/theme/theme-script.ts +21 -18
  234. package/src/types/boundaries.ts +158 -0
  235. package/src/types/cache-types.ts +198 -0
  236. package/src/types/error-types.ts +192 -0
  237. package/src/types/global-namespace.ts +100 -0
  238. package/src/types/handler-context.ts +687 -0
  239. package/src/types/index.ts +88 -0
  240. package/src/types/loader-types.ts +183 -0
  241. package/src/types/route-config.ts +170 -0
  242. package/src/types/route-entry.ts +102 -0
  243. package/src/types/segments.ts +148 -0
  244. package/src/types.ts +1 -1623
  245. package/src/urls/include-helper.ts +197 -0
  246. package/src/urls/index.ts +53 -0
  247. package/src/urls/path-helper-types.ts +339 -0
  248. package/src/urls/path-helper.ts +329 -0
  249. package/src/urls/pattern-types.ts +95 -0
  250. package/src/urls/response-types.ts +106 -0
  251. package/src/urls/type-extraction.ts +372 -0
  252. package/src/urls/urls-function.ts +98 -0
  253. package/src/urls.ts +1 -802
  254. package/src/use-loader.tsx +85 -77
  255. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  256. package/src/vite/discovery/discover-routers.ts +344 -0
  257. package/src/vite/discovery/prerender-collection.ts +385 -0
  258. package/src/vite/discovery/route-types-writer.ts +258 -0
  259. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  260. package/src/vite/discovery/state.ts +110 -0
  261. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  262. package/src/vite/index.ts +11 -1133
  263. package/src/vite/plugin-types.ts +131 -0
  264. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  265. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  266. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  267. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  268. package/src/vite/plugins/expose-id-utils.ts +287 -0
  269. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  270. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  271. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  272. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  273. package/src/vite/plugins/expose-ids/types.ts +45 -0
  274. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  275. package/src/vite/plugins/refresh-cmd.ts +65 -0
  276. package/src/vite/plugins/use-cache-transform.ts +323 -0
  277. package/src/vite/plugins/version-injector.ts +83 -0
  278. package/src/vite/plugins/version-plugin.ts +254 -0
  279. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  280. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  281. package/src/vite/rango.ts +510 -0
  282. package/src/vite/router-discovery.ts +785 -0
  283. package/src/vite/utils/ast-handler-extract.ts +517 -0
  284. package/src/vite/utils/banner.ts +36 -0
  285. package/src/vite/utils/bundle-analysis.ts +137 -0
  286. package/src/vite/utils/manifest-utils.ts +70 -0
  287. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  288. package/src/vite/utils/prerender-utils.ts +189 -0
  289. package/src/vite/utils/shared-utils.ts +169 -0
  290. package/CLAUDE.md +0 -43
  291. package/src/browser/lru-cache.ts +0 -69
  292. package/src/browser/request-controller.ts +0 -164
  293. package/src/cache/memory-store.ts +0 -253
  294. package/src/href-context.ts +0 -33
  295. package/src/href.ts +0 -255
  296. package/src/server/route-manifest-cache.ts +0 -173
  297. package/src/vite/expose-handle-id.ts +0 -209
  298. package/src/vite/expose-loader-id.ts +0 -426
  299. package/src/vite/expose-location-state-id.ts +0 -177
  300. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -13,13 +13,27 @@
13
13
  import { AsyncLocalStorage } from "node:async_hooks";
14
14
  import type { CookieOptions } from "../router/middleware.js";
15
15
  import type { LoaderDefinition, LoaderContext } from "../types.js";
16
+ import type { ScopedReverseFunction } from "../reverse.js";
17
+ import type {
18
+ DefaultEnv,
19
+ DefaultReverseRouteMap,
20
+ DefaultRouteName,
21
+ } from "../types/global-namespace.js";
16
22
  import type { Handle } from "../handle.js";
23
+ import { type ContextVar, contextGet, contextSet } from "../context-var.js";
17
24
  import { createHandleStore, type HandleStore } from "./handle-store.js";
18
25
  import { isHandle } from "../handle.js";
19
- import { track } from "./context.js";
26
+ import { track, type MetricsStore } from "./context.js";
27
+ import { getFetchableLoader } from "./fetchable-loader-store.js";
20
28
  import type { SegmentCacheStore } from "../cache/types.js";
21
29
  import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
22
30
  import { THEME_COOKIE } from "../theme/constants.js";
31
+ import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
32
+ import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
33
+ import { createReverseFunction } from "../router/handler-context.js";
34
+ import { getGlobalRouteMap, isRouteRootScoped } from "../route-map-builder.js";
35
+ import { invariant } from "../errors.js";
36
+ import { isAutoGeneratedRouteName } from "../route-name.js";
23
37
 
24
38
  /**
25
39
  * Unified request context available via getRequestContext()
@@ -28,15 +42,20 @@ import { THEME_COOKIE } from "../theme/constants.js";
28
42
  * Use this when you need access to request data outside of route handlers.
29
43
  */
30
44
  export interface RequestContext<
31
- TEnv = unknown,
45
+ TEnv = DefaultEnv,
32
46
  TParams = Record<string, string>,
33
47
  > {
34
48
  /** Platform bindings (Cloudflare env, etc.) */
35
49
  env: TEnv;
36
50
  /** Original HTTP request */
37
51
  request: Request;
38
- /** Parsed URL (system params like _rsc* are NOT filtered here) */
52
+ /** Parsed URL (with internal `_rsc*` params stripped) */
39
53
  url: URL;
54
+ /**
55
+ * The original request URL with all parameters intact, including
56
+ * internal `_rsc*` transport params.
57
+ */
58
+ originalUrl: URL;
40
59
  /** URL pathname */
41
60
  pathname: string;
42
61
  /** URL search params (system params like _rsc* are NOT filtered here) */
@@ -44,30 +63,40 @@ export interface RequestContext<
44
63
  /** Variables set by middleware (same as ctx.var) */
45
64
  var: Record<string, any>;
46
65
  /** Get a variable set by middleware */
47
- get: <K extends string>(key: K) => any;
66
+ get: {
67
+ <T>(contextVar: ContextVar<T>): T | undefined;
68
+ <K extends string>(key: K): any;
69
+ };
48
70
  /** Set a variable (shared with middleware and handlers) */
49
- set: <K extends string>(key: K, value: any) => void;
71
+ set: {
72
+ <T>(contextVar: ContextVar<T>, value: T): void;
73
+ <K extends string>(key: K, value: any): void;
74
+ };
50
75
  /**
51
76
  * Route params (populated after route matching)
52
77
  * Initially empty, then set to matched params
53
78
  */
54
79
  params: TParams;
55
- /**
56
- * Stub response for setting headers/cookies
57
- * Headers set here are merged into the final response
58
- */
59
- res: Response;
80
+ /** @internal Stub response for collecting headers/cookies. Use ctx.headers or ctx.header() instead. */
81
+ readonly res: Response;
60
82
 
61
- /** Get a cookie value from the request */
83
+ /** @internal Get a cookie value (effective: request + response mutations). Use cookies().get() instead. */
62
84
  cookie(name: string): string | undefined;
63
- /** Get all cookies from the request */
85
+ /** @internal Get all cookies (effective merged view). Use cookies().getAll() instead. */
64
86
  cookies(): Record<string, string>;
65
- /** Set a cookie on the response */
87
+ /** @internal Set a cookie on the response. Use cookies().set() instead. */
66
88
  setCookie(name: string, value: string, options?: CookieOptions): void;
67
- /** Delete a cookie */
68
- deleteCookie(name: string, options?: Pick<CookieOptions, "domain" | "path">): void;
89
+ /** @internal Delete a cookie. Use cookies().delete() instead. */
90
+ deleteCookie(
91
+ name: string,
92
+ options?: Pick<CookieOptions, "domain" | "path">,
93
+ ): void;
69
94
  /** Set a response header */
70
95
  header(name: string, value: string): void;
96
+ /** Set the response status code */
97
+ setStatus(status: number): void;
98
+ /** @internal Set status bypassing cache-exec guard (for framework error handling) */
99
+ _setStatus(status: number): void;
71
100
 
72
101
  /**
73
102
  * Access loader data or push handle data.
@@ -89,10 +118,12 @@ export interface RequestContext<
89
118
  * ```
90
119
  */
91
120
  use: {
92
- <T, TLoaderParams = any>(loader: LoaderDefinition<T, TLoaderParams>): Promise<T>;
93
- <TData, TAccumulated = TData[]>(handle: Handle<TData, TAccumulated>): (
94
- data: TData | Promise<TData> | (() => Promise<TData>)
95
- ) => void;
121
+ <T, TLoaderParams = any>(
122
+ loader: LoaderDefinition<T, TLoaderParams>,
123
+ ): Promise<T>;
124
+ <TData, TAccumulated = TData[]>(
125
+ handle: Handle<TData, TAccumulated>,
126
+ ): (data: TData | Promise<TData> | (() => Promise<TData>)) => void;
96
127
  };
97
128
 
98
129
  /** HTTP method (GET, POST, PUT, PATCH, DELETE, etc.) */
@@ -104,6 +135,12 @@ export interface RequestContext<
104
135
  /** @internal Cache store for segment caching (optional, used by CacheScope) */
105
136
  _cacheStore?: SegmentCacheStore;
106
137
 
138
+ /** @internal Cache profiles for "use cache" profile resolution (per-router) */
139
+ _cacheProfiles?: Record<
140
+ string,
141
+ import("../cache/profile-registry.js").CacheProfile
142
+ >;
143
+
107
144
  /**
108
145
  * Schedule work to run after the response is sent.
109
146
  * On Cloudflare Workers, uses ctx.waitUntil().
@@ -177,8 +214,99 @@ export interface RequestContext<
177
214
 
178
215
  /** @internal Theme configuration (null if theme not enabled) */
179
216
  _themeConfig?: ResolvedThemeConfig | null;
217
+
218
+ /**
219
+ * Attach location state entries to the current response.
220
+ *
221
+ * For partial (SPA) requests, the state is included in the RSC payload
222
+ * metadata and merged into history.pushState on the client. For redirect
223
+ * responses, the state travels through the redirect payload so the target
224
+ * page can read it via useLocationState.
225
+ *
226
+ * Multiple calls accumulate entries.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * ctx.setLocationState(Flash({ text: "Item saved!" }));
231
+ * ```
232
+ */
233
+ setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
234
+
235
+ /** @internal Accumulated location state entries */
236
+ _locationState?: LocationStateEntry[];
237
+
238
+ /**
239
+ * The matched route name, if the route has an explicit name.
240
+ * Undefined before route matching or for unnamed routes.
241
+ * Includes the namespace prefix from include() (e.g., "blog.post").
242
+ */
243
+ routeName?: DefaultRouteName;
244
+
245
+ /**
246
+ * Generate URLs from route names.
247
+ * Uses the global route map. After route matching, scoped (`.name`) resolution
248
+ * works within the matched include() scope.
249
+ */
250
+ reverse: ScopedReverseFunction<
251
+ Record<string, string>,
252
+ DefaultReverseRouteMap
253
+ >;
254
+
255
+ /** @internal Route name from route matching, used for scoped reverse resolution */
256
+ _routeName?: string;
257
+
258
+ /** @internal Previous route key (from the navigation source), used for revalidation */
259
+ _prevRouteKey?: string;
260
+
261
+ /** @internal Per-request error dedup set for onError reporting */
262
+ _reportedErrors: WeakSet<object>;
263
+
264
+ /**
265
+ * @internal Report a non-fatal background error through the router's
266
+ * onError callback. Wired by the RSC handler / router during request
267
+ * creation. Cache-runtime and other subsystems call this to surface
268
+ * errors without failing the response.
269
+ */
270
+ _reportBackgroundError?: (error: unknown, category: string) => void;
271
+
272
+ /** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
273
+ _debugPerformance?: boolean;
274
+
275
+ /** @internal Request-scoped performance metrics store */
276
+ _metricsStore?: MetricsStore;
180
277
  }
181
278
 
279
+ /**
280
+ * Public view of RequestContext, without internal methods and fields.
281
+ *
282
+ * This is the type exported to library consumers. Internal code should
283
+ * use the full RequestContext interface directly.
284
+ */
285
+ export type PublicRequestContext<
286
+ TEnv = DefaultEnv,
287
+ TParams = Record<string, string>,
288
+ > = Omit<
289
+ RequestContext<TEnv, TParams>,
290
+ | "cookie"
291
+ | "cookies"
292
+ | "setCookie"
293
+ | "deleteCookie"
294
+ | "_handleStore"
295
+ | "_cacheStore"
296
+ | "_cacheProfiles"
297
+ | "_onResponseCallbacks"
298
+ | "_themeConfig"
299
+ | "_locationState"
300
+ | "_routeName"
301
+ | "_prevRouteKey"
302
+ | "_reportedErrors"
303
+ | "_reportBackgroundError"
304
+ | "_debugPerformance"
305
+ | "_metricsStore"
306
+ | "_setStatus"
307
+ | "res"
308
+ >;
309
+
182
310
  // AsyncLocalStorage instance for request context
183
311
  const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
184
312
 
@@ -188,16 +316,33 @@ const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
188
316
  */
189
317
  export function runWithRequestContext<TEnv, T>(
190
318
  context: RequestContext<TEnv>,
191
- fn: () => T
319
+ fn: () => T,
192
320
  ): T {
193
321
  return requestContextStorage.run(context, fn);
194
322
  }
195
323
 
196
324
  /**
197
325
  * Get the current request context
198
- * Returns undefined if not running within a request context
326
+ * Throws if called outside of a request context
327
+ */
328
+ export function getRequestContext<TEnv = DefaultEnv>(): RequestContext<TEnv> {
329
+ const ctx = requestContextStorage.getStore() as
330
+ | RequestContext<TEnv>
331
+ | undefined;
332
+ invariant(
333
+ ctx,
334
+ "getRequestContext() called outside of a request context. " +
335
+ "This function must be called from within a route handler, loader, middleware, " +
336
+ "server action, or server component.",
337
+ );
338
+ return ctx;
339
+ }
340
+
341
+ /**
342
+ * @internal Get the request context without throwing — for internal code that
343
+ * may run outside a request context (cache stores, optional handle lookups, etc.)
199
344
  */
200
- export function getRequestContext<TEnv = unknown>():
345
+ export function _getRequestContext<TEnv = DefaultEnv>():
201
346
  | RequestContext<TEnv>
202
347
  | undefined {
203
348
  return requestContextStorage.getStore() as RequestContext<TEnv> | undefined;
@@ -205,28 +350,67 @@ export function getRequestContext<TEnv = unknown>():
205
350
 
206
351
  /**
207
352
  * Update params on the current request context
208
- * Called after route matching to populate route params
353
+ * Called after route matching to populate route params and route name
209
354
  */
210
- export function setRequestContextParams(params: Record<string, string>): void {
355
+ export function setRequestContextParams(
356
+ params: Record<string, string>,
357
+ routeName?: string,
358
+ ): void {
211
359
  const ctx = requestContextStorage.getStore();
212
360
  if (ctx) {
213
361
  ctx.params = params;
362
+ if (routeName !== undefined) {
363
+ ctx._routeName = routeName;
364
+ ctx.routeName = (
365
+ routeName && !isAutoGeneratedRouteName(routeName)
366
+ ? routeName
367
+ : undefined
368
+ ) as DefaultRouteName | undefined;
369
+ }
370
+ // Update reverse with scoped resolution now that route is known
371
+ ctx.reverse = createReverseFunction(
372
+ getGlobalRouteMap(),
373
+ routeName,
374
+ params,
375
+ routeName ? isRouteRootScoped(routeName) : undefined,
376
+ );
214
377
  }
215
378
  }
216
379
 
217
380
  /**
218
- * Get the current request context, throwing if not available
219
- * Use this when context is required (e.g., in loader actions)
381
+ * Store the previous route key on the request context.
382
+ * Called during partial-match context creation to make the navigation source
383
+ * route key available for revalidation and intercept evaluation.
384
+ * @internal
220
385
  */
221
- export function requireRequestContext<TEnv = unknown>(): RequestContext<TEnv> {
222
- const ctx = getRequestContext<TEnv>();
223
- if (!ctx) {
224
- throw new Error(
225
- "Request context not available. This function must be called from within a server action " +
226
- "executed through the RSC handler."
227
- );
386
+ export function setRequestContextPrevRouteKey(
387
+ prevRouteKey: string | undefined,
388
+ ): void {
389
+ const ctx = requestContextStorage.getStore();
390
+ if (ctx && prevRouteKey !== undefined) {
391
+ ctx._prevRouteKey = prevRouteKey;
228
392
  }
229
- return ctx;
393
+ }
394
+
395
+ /**
396
+ * Get accumulated location state entries from the current request context.
397
+ * Returns undefined if no state has been set.
398
+ *
399
+ * @internal Used by the RSC handler to include state in payload metadata.
400
+ */
401
+ export function getLocationState(): LocationStateEntry[] | undefined {
402
+ const ctx = getRequestContext();
403
+ return ctx?._locationState;
404
+ }
405
+
406
+ /**
407
+ * Get the current request context, throwing if not available
408
+ * @deprecated Use getRequestContext() directly — it now throws if outside context
409
+ */
410
+ export function requireRequestContext<
411
+ TEnv = DefaultEnv,
412
+ >(): RequestContext<TEnv> {
413
+ return getRequestContext<TEnv>();
230
414
  }
231
415
 
232
416
  /**
@@ -245,8 +429,15 @@ export interface CreateRequestContextOptions<TEnv> {
245
429
  request: Request;
246
430
  url: URL;
247
431
  variables: Record<string, any>;
432
+ /** Optional initial response stub headers/status to seed effective cookie reads */
433
+ initialResponse?: Response;
248
434
  /** Optional cache store for segment caching (used by CacheScope) */
249
435
  cacheStore?: SegmentCacheStore;
436
+ /** Optional cache profiles for "use cache" resolution (per-router) */
437
+ cacheProfiles?: Record<
438
+ string,
439
+ import("../cache/profile-registry.js").CacheProfile
440
+ >;
250
441
  /** Optional Cloudflare execution context for waitUntil support */
251
442
  executionContext?: ExecutionContext;
252
443
  /** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
@@ -262,20 +453,37 @@ export interface CreateRequestContextOptions<TEnv> {
262
453
  * - Passed to handlers as ctx
263
454
  */
264
455
  export function createRequestContext<TEnv>(
265
- options: CreateRequestContextOptions<TEnv>
456
+ options: CreateRequestContextOptions<TEnv>,
266
457
  ): RequestContext<TEnv> {
267
- const { env, request, url, variables, cacheStore, executionContext, themeConfig } = options;
458
+ const {
459
+ env,
460
+ request,
461
+ url,
462
+ variables,
463
+ initialResponse,
464
+ cacheStore,
465
+ cacheProfiles,
466
+ executionContext,
467
+ themeConfig,
468
+ } = options;
268
469
  const cookieHeader = request.headers.get("Cookie");
269
470
  let parsedCookies: Record<string, string> | null = null;
270
471
 
271
- // Create stub response for collecting headers/cookies
272
- const stubResponse = new Response(null, { status: 200 });
472
+ // Create stub response for collecting headers/cookies.
473
+ // All cookie/header mutations go here; cookie reads derive from it.
474
+ let stubResponse = initialResponse
475
+ ? new Response(null, {
476
+ status: initialResponse.status,
477
+ statusText: initialResponse.statusText,
478
+ headers: new Headers(initialResponse.headers),
479
+ })
480
+ : new Response(null, { status: 200 });
273
481
 
274
482
  // Create handle store and loader memoization for this request
275
483
  const handleStore = createHandleStore();
276
484
  const loaderPromises = new Map<string, Promise<any>>();
277
485
 
278
- // Lazy parse cookies
486
+ // Lazy parse cookies from the original Cookie header
279
487
  const getParsedCookies = (): Record<string, string> => {
280
488
  if (!parsedCookies) {
281
489
  parsedCookies = parseCookiesFromHeader(cookieHeader);
@@ -283,11 +491,35 @@ export function createRequestContext<TEnv>(
283
491
  return parsedCookies;
284
492
  };
285
493
 
494
+ // Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
495
+ let responseCookieCache: Map<string, string | null> | null = null;
496
+ const getResponseCookies = (): Map<string, string | null> => {
497
+ if (!responseCookieCache) {
498
+ responseCookieCache = parseResponseCookies(stubResponse);
499
+ }
500
+ return responseCookieCache;
501
+ };
502
+ const invalidateResponseCookieCache = () => {
503
+ responseCookieCache = null;
504
+ };
505
+
506
+ // Effective cookie read: response stub Set-Cookie wins, then original header.
507
+ // The stub IS the source of truth for same-request mutations.
508
+ const effectiveCookie = (name: string): string | undefined => {
509
+ const mutations = getResponseCookies();
510
+ if (mutations.has(name)) {
511
+ const v = mutations.get(name);
512
+ return v === null ? undefined : v;
513
+ }
514
+ return getParsedCookies()[name];
515
+ };
516
+
286
517
  // Theme helpers (only used when themeConfig is provided)
287
518
  const getTheme = (): Theme | undefined => {
288
519
  if (!themeConfig) return undefined;
289
520
 
290
- const stored = getParsedCookies()[themeConfig.storageKey];
521
+ // Use overlay-aware read so setTheme() in the same request is reflected
522
+ const stored = effectiveCookie(themeConfig.storageKey);
291
523
  if (stored) {
292
524
  // Validate stored value
293
525
  if (stored === "system" && themeConfig.enableSystem) {
@@ -305,19 +537,22 @@ export function createRequestContext<TEnv>(
305
537
 
306
538
  // Validate theme value
307
539
  if (theme !== "system" && !themeConfig.themes.includes(theme)) {
308
- console.warn(`[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`);
540
+ console.warn(
541
+ `[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`,
542
+ );
309
543
  return;
310
544
  }
311
545
 
312
- // Set cookie
546
+ // Write to stub — effectiveCookie() will pick it up on next read
313
547
  stubResponse.headers.append(
314
548
  "Set-Cookie",
315
549
  serializeCookieValue(themeConfig.storageKey, theme, {
316
550
  path: THEME_COOKIE.path,
317
551
  maxAge: THEME_COOKIE.maxAge,
318
552
  sameSite: THEME_COOKIE.sameSite,
319
- })
553
+ }),
320
554
  );
555
+ invalidateResponseCookieCache();
321
556
  };
322
557
 
323
558
  // Build the context object first (without use), then add use
@@ -325,45 +560,91 @@ export function createRequestContext<TEnv>(
325
560
  env,
326
561
  request,
327
562
  url,
563
+ originalUrl: new URL(request.url),
328
564
  pathname: url.pathname,
329
565
  searchParams: url.searchParams,
330
566
  var: variables,
331
- get: <K extends string>(key: K) => variables[key],
332
- set: <K extends string>(key: K, value: any) => {
333
- variables[key] = value;
334
- },
567
+ get: ((keyOrVar: any) =>
568
+ contextGet(variables, keyOrVar)) as RequestContext<TEnv>["get"],
569
+ set: ((keyOrVar: any, value: any) => {
570
+ assertNotInsideCacheExec(ctx, "set");
571
+ contextSet(variables, keyOrVar, value);
572
+ }) as RequestContext<TEnv>["set"],
335
573
  params: {} as Record<string, string>,
336
- res: stubResponse,
574
+
575
+ get res(): Response {
576
+ return stubResponse;
577
+ },
578
+ set res(_: Response) {
579
+ throw new Error(
580
+ "ctx.res is read-only. Use ctx.header() to set response headers, or cookies() for cookie mutations.",
581
+ );
582
+ },
337
583
 
338
584
  cookie(name: string): string | undefined {
339
- return getParsedCookies()[name];
585
+ return effectiveCookie(name);
340
586
  },
341
587
 
342
588
  cookies(): Record<string, string> {
343
- return { ...getParsedCookies() };
589
+ const parsed = getParsedCookies();
590
+ const mutations = getResponseCookies();
591
+ if (mutations.size === 0) return { ...parsed };
592
+ // Build result without delete (avoids V8 dictionary-mode de-opt)
593
+ const deleted = new Set<string>();
594
+ for (const [k, v] of mutations) {
595
+ if (v === null) deleted.add(k);
596
+ }
597
+ const result: Record<string, string> = {};
598
+ for (const key of Object.keys(parsed)) {
599
+ if (!deleted.has(key)) result[key] = parsed[key];
600
+ }
601
+ for (const [k, v] of mutations) {
602
+ if (v !== null) result[k] = v;
603
+ }
604
+ return result;
344
605
  },
345
606
 
346
607
  setCookie(name: string, value: string, options?: CookieOptions): void {
608
+ assertNotInsideCacheExec(ctx, "setCookie");
347
609
  stubResponse.headers.append(
348
610
  "Set-Cookie",
349
- serializeCookieValue(name, value, options)
611
+ serializeCookieValue(name, value, options),
350
612
  );
613
+ invalidateResponseCookieCache();
351
614
  },
352
615
 
353
616
  deleteCookie(
354
617
  name: string,
355
- options?: Pick<CookieOptions, "domain" | "path">
618
+ options?: Pick<CookieOptions, "domain" | "path">,
356
619
  ): void {
620
+ assertNotInsideCacheExec(ctx, "deleteCookie");
357
621
  stubResponse.headers.append(
358
622
  "Set-Cookie",
359
- serializeCookieValue(name, "", { ...options, maxAge: 0 })
623
+ serializeCookieValue(name, "", { ...options, maxAge: 0 }),
360
624
  );
625
+ invalidateResponseCookieCache();
361
626
  },
362
627
 
363
628
  header(name: string, value: string): void {
629
+ assertNotInsideCacheExec(ctx, "header");
364
630
  stubResponse.headers.set(name, value);
365
631
  },
366
632
 
633
+ setStatus(status: number): void {
634
+ assertNotInsideCacheExec(ctx, "setStatus");
635
+ stubResponse = new Response(null, {
636
+ status,
637
+ headers: stubResponse.headers,
638
+ });
639
+ },
640
+
641
+ _setStatus(status: number): void {
642
+ stubResponse = new Response(null, {
643
+ status,
644
+ headers: stubResponse.headers,
645
+ });
646
+ },
647
+
367
648
  // Placeholder - will be replaced below
368
649
  use: null as any,
369
650
 
@@ -371,6 +652,7 @@ export function createRequestContext<TEnv>(
371
652
 
372
653
  _handleStore: handleStore,
373
654
  _cacheStore: cacheStore,
655
+ _cacheProfiles: cacheProfiles,
374
656
 
375
657
  waitUntil(fn: () => Promise<void>): void {
376
658
  if (executionContext?.waitUntil) {
@@ -378,20 +660,44 @@ export function createRequestContext<TEnv>(
378
660
  executionContext.waitUntil(fn());
379
661
  } else {
380
662
  // Node.js / dev: fire-and-forget with error logging
381
- fn().catch((err) => console.error("[waitUntil] Background task failed:", err));
663
+ fn().catch((err) =>
664
+ console.error("[waitUntil] Background task failed:", err),
665
+ );
382
666
  }
383
667
  },
384
668
 
385
669
  _onResponseCallbacks: [],
386
670
 
387
671
  onResponse(callback: (response: Response) => Response): void {
672
+ assertNotInsideCacheExec(ctx, "onResponse");
388
673
  this._onResponseCallbacks.push(callback);
389
674
  },
390
675
 
391
676
  // Theme properties (only set when themeConfig is provided)
392
- theme: themeConfig ? getTheme() : undefined,
393
- setTheme: themeConfig ? setTheme : undefined,
677
+ get theme() {
678
+ return themeConfig ? getTheme() : undefined;
679
+ },
680
+ setTheme: themeConfig
681
+ ? (theme: Theme) => {
682
+ assertNotInsideCacheExec(ctx, "setTheme");
683
+ setTheme(theme);
684
+ }
685
+ : undefined,
394
686
  _themeConfig: themeConfig,
687
+
688
+ setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void {
689
+ assertNotInsideCacheExec(ctx, "setLocationState");
690
+ const arr = Array.isArray(entries) ? entries : [entries];
691
+ this._locationState = this._locationState
692
+ ? [...this._locationState, ...arr]
693
+ : arr;
694
+ },
695
+ _locationState: undefined,
696
+
697
+ _reportedErrors: new WeakSet<object>(),
698
+ _metricsStore: undefined,
699
+
700
+ reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
395
701
  };
396
702
 
397
703
  // Now create use() with access to ctx
@@ -401,14 +707,53 @@ export function createRequestContext<TEnv>(
401
707
  getContext: () => ctx,
402
708
  });
403
709
 
710
+ // Brand with taint symbol so "use cache" excludes ctx from cache keys
711
+ (ctx as any)[NOCACHE_SYMBOL] = true;
404
712
  return ctx;
405
713
  }
406
714
 
715
+ /**
716
+ * Parse Set-Cookie headers from a response into effective cookie state.
717
+ * Returns a map of cookie name -> value (string) or name -> null (deleted).
718
+ * Last-write-wins: later Set-Cookie entries for the same name overwrite earlier ones.
719
+ * Max-Age=0 is treated as a delete.
720
+ */
721
+ const MAX_AGE_ZERO_RE = /;\s*Max-Age\s*=\s*0/i;
722
+
723
+ function parseResponseCookies(response: Response): Map<string, string | null> {
724
+ const result = new Map<string, string | null>();
725
+ const setCookies = response.headers.getSetCookie();
726
+
727
+ for (const header of setCookies) {
728
+ // First segment before ';' is the name=value pair
729
+ const semiIdx = header.indexOf(";");
730
+ const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
731
+ const eqIdx = pair.indexOf("=");
732
+ if (eqIdx === -1) continue;
733
+
734
+ let name: string;
735
+ let value: string;
736
+ try {
737
+ name = decodeURIComponent(pair.substring(0, eqIdx).trim());
738
+ value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
739
+ } catch {
740
+ // Malformed encoding — skip this entry
741
+ continue;
742
+ }
743
+
744
+ // Max-Age=0 means the cookie is being deleted
745
+ const isDeleted = MAX_AGE_ZERO_RE.test(header);
746
+ result.set(name, isDeleted ? null : value);
747
+ }
748
+
749
+ return result;
750
+ }
751
+
407
752
  /**
408
753
  * Parse cookies from Cookie header
409
754
  */
410
755
  function parseCookiesFromHeader(
411
- cookieHeader: string | null
756
+ cookieHeader: string | null,
412
757
  ): Record<string, string> {
413
758
  if (!cookieHeader) return {};
414
759
 
@@ -418,7 +763,13 @@ function parseCookiesFromHeader(
418
763
  for (const pair of pairs) {
419
764
  const [name, ...rest] = pair.trim().split("=");
420
765
  if (name) {
421
- cookies[name] = decodeURIComponent(rest.join("="));
766
+ const raw = rest.join("=");
767
+ try {
768
+ cookies[name] = decodeURIComponent(raw);
769
+ } catch {
770
+ // Malformed percent-encoded value (e.g. %zz, %2) - fall back to raw value
771
+ cookies[name] = raw;
772
+ }
422
773
  }
423
774
  }
424
775
 
@@ -431,7 +782,7 @@ function parseCookiesFromHeader(
431
782
  function serializeCookieValue(
432
783
  name: string,
433
784
  value: string,
434
- options: CookieOptions = {}
785
+ options: CookieOptions = {},
435
786
  ): string {
436
787
  let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
437
788
 
@@ -463,7 +814,7 @@ export interface CreateUseFunctionOptions<TEnv> {
463
814
  * - For handles: returns a push function to add handle data
464
815
  */
465
816
  export function createUseFunction<TEnv>(
466
- options: CreateUseFunctionOptions<TEnv>
817
+ options: CreateUseFunctionOptions<TEnv>,
467
818
  ): RequestContext["use"] {
468
819
  const { handleStore, loaderPromises, getContext } = options;
469
820
 
@@ -477,16 +828,19 @@ export function createUseFunction<TEnv>(
477
828
  if (!segmentId) {
478
829
  throw new Error(
479
830
  `Handle "${handle.$$id}" used outside of handler context. ` +
480
- `Handles must be used within route/layout handlers.`
831
+ `Handles must be used within route/layout handlers.`,
481
832
  );
482
833
  }
483
834
 
484
835
  // Return a push function bound to this handle and segment
485
- return (dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
836
+ return (
837
+ dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>),
838
+ ) => {
486
839
  // If it's a function, call it immediately to get the promise
487
- const valueOrPromise = typeof dataOrFn === "function"
488
- ? (dataOrFn as () => Promise<unknown>)()
489
- : dataOrFn;
840
+ const valueOrPromise =
841
+ typeof dataOrFn === "function"
842
+ ? (dataOrFn as () => Promise<unknown>)()
843
+ : dataOrFn;
490
844
 
491
845
  // Push directly - promises will be serialized by RSC and streamed
492
846
  handleStore.push(handle.$$id, segmentId, valueOrPromise);
@@ -504,8 +858,6 @@ export function createUseFunction<TEnv>(
504
858
  // Get loader function - either from loader object or fetchable registry
505
859
  let loaderFn = loader.fn;
506
860
  if (!loaderFn) {
507
- // Lazy import to avoid circular dependency
508
- const { getFetchableLoader } = require("../loader.rsc.js");
509
861
  const fetchable = getFetchableLoader(loader.$$id);
510
862
  if (fetchable) {
511
863
  loaderFn = fetchable.fn;
@@ -514,7 +866,7 @@ export function createUseFunction<TEnv>(
514
866
 
515
867
  if (!loaderFn) {
516
868
  throw new Error(
517
- `Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build.`
869
+ `Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build.`,
518
870
  );
519
871
  }
520
872
 
@@ -523,25 +875,33 @@ export function createUseFunction<TEnv>(
523
875
  // Create loader context with recursive use() support
524
876
  const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
525
877
  params: ctx.params,
878
+ routeParams: (ctx.params ?? {}) as Record<string, string>,
526
879
  request: ctx.request,
527
880
  searchParams: ctx.searchParams,
881
+ search: (ctx as any).search ?? {},
528
882
  pathname: ctx.pathname,
529
883
  url: ctx.url,
530
884
  env: ctx.env as any,
531
885
  var: ctx.var as any,
532
886
  get: ctx.get as any,
533
887
  use: <TDep, TDepParams = any>(
534
- dep: LoaderDefinition<TDep, TDepParams>
888
+ dep: LoaderDefinition<TDep, TDepParams>,
535
889
  ): Promise<TDep> => {
536
890
  // Recursive call - will start dep loader if not already started
537
891
  return ctx.use(dep);
538
892
  },
539
893
  method: "GET",
540
894
  body: undefined,
895
+ reverse: createReverseFunction(
896
+ getGlobalRouteMap(),
897
+ ctx._routeName,
898
+ ctx.params as Record<string, string>,
899
+ ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
900
+ ),
541
901
  };
542
902
 
543
903
  // Start loader execution with tracking
544
- const doneLoader = track(`loader:${loader.$$id}`);
904
+ const doneLoader = track(`loader:${loader.$$id}`, 2);
545
905
  const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
546
906
  doneLoader();
547
907
  });