@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847

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