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

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 (312) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +4960 -935
  5. package/package.json +70 -60
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +294 -0
  8. package/skills/caching/SKILL.md +93 -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 +167 -0
  13. package/skills/handler-use/SKILL.md +362 -0
  14. package/skills/hooks/SKILL.md +334 -72
  15. package/skills/host-router/SKILL.md +218 -0
  16. package/skills/intercept/SKILL.md +151 -8
  17. package/skills/layout/SKILL.md +122 -3
  18. package/skills/links/SKILL.md +92 -31
  19. package/skills/loader/SKILL.md +404 -44
  20. package/skills/middleware/SKILL.md +205 -37
  21. package/skills/migrate-nextjs/SKILL.md +560 -0
  22. package/skills/migrate-react-router/SKILL.md +764 -0
  23. package/skills/mime-routes/SKILL.md +128 -0
  24. package/skills/parallel/SKILL.md +263 -1
  25. package/skills/prerender/SKILL.md +685 -0
  26. package/skills/rango/SKILL.md +87 -16
  27. package/skills/response-routes/SKILL.md +411 -0
  28. package/skills/route/SKILL.md +281 -14
  29. package/skills/router-setup/SKILL.md +210 -32
  30. package/skills/tailwind/SKILL.md +129 -0
  31. package/skills/theme/SKILL.md +9 -8
  32. package/skills/typesafety/SKILL.md +328 -89
  33. package/skills/use-cache/SKILL.md +324 -0
  34. package/src/__internal.ts +102 -4
  35. package/src/bin/rango.ts +321 -0
  36. package/src/browser/action-coordinator.ts +97 -0
  37. package/src/browser/action-response-classifier.ts +99 -0
  38. package/src/browser/app-version.ts +14 -0
  39. package/src/browser/event-controller.ts +92 -64
  40. package/src/browser/history-state.ts +80 -0
  41. package/src/browser/intercept-utils.ts +52 -0
  42. package/src/browser/link-interceptor.ts +24 -4
  43. package/src/browser/logging.ts +55 -0
  44. package/src/browser/merge-segment-loaders.ts +20 -12
  45. package/src/browser/navigation-bridge.ts +317 -560
  46. package/src/browser/navigation-client.ts +206 -68
  47. package/src/browser/navigation-store.ts +73 -55
  48. package/src/browser/navigation-transaction.ts +297 -0
  49. package/src/browser/network-error-handler.ts +61 -0
  50. package/src/browser/partial-update.ts +343 -316
  51. package/src/browser/prefetch/cache.ts +216 -0
  52. package/src/browser/prefetch/fetch.ts +206 -0
  53. package/src/browser/prefetch/observer.ts +65 -0
  54. package/src/browser/prefetch/policy.ts +48 -0
  55. package/src/browser/prefetch/queue.ts +160 -0
  56. package/src/browser/prefetch/resource-ready.ts +77 -0
  57. package/src/browser/rango-state.ts +112 -0
  58. package/src/browser/react/Link.tsx +253 -74
  59. package/src/browser/react/NavigationProvider.tsx +87 -11
  60. package/src/browser/react/context.ts +11 -0
  61. package/src/browser/react/filter-segment-order.ts +11 -0
  62. package/src/browser/react/index.ts +12 -12
  63. package/src/browser/react/location-state-shared.ts +95 -53
  64. package/src/browser/react/location-state.ts +60 -15
  65. package/src/browser/react/mount-context.ts +6 -1
  66. package/src/browser/react/nonce-context.ts +23 -0
  67. package/src/browser/react/shallow-equal.ts +27 -0
  68. package/src/browser/react/use-action.ts +29 -51
  69. package/src/browser/react/use-client-cache.ts +5 -3
  70. package/src/browser/react/use-handle.ts +30 -126
  71. package/src/browser/react/use-href.tsx +2 -2
  72. package/src/browser/react/use-link-status.ts +6 -5
  73. package/src/browser/react/use-navigation.ts +44 -65
  74. package/src/browser/react/use-params.ts +65 -0
  75. package/src/browser/react/use-pathname.ts +47 -0
  76. package/src/browser/react/use-router.ts +76 -0
  77. package/src/browser/react/use-search-params.ts +56 -0
  78. package/src/browser/react/use-segments.ts +80 -97
  79. package/src/browser/response-adapter.ts +73 -0
  80. package/src/browser/rsc-router.tsx +214 -58
  81. package/src/browser/scroll-restoration.ts +127 -52
  82. package/src/browser/segment-reconciler.ts +243 -0
  83. package/src/browser/segment-structure-assert.ts +16 -0
  84. package/src/browser/server-action-bridge.ts +510 -603
  85. package/src/browser/shallow.ts +6 -1
  86. package/src/browser/types.ts +141 -48
  87. package/src/browser/validate-redirect-origin.ts +29 -0
  88. package/src/build/generate-manifest.ts +235 -24
  89. package/src/build/generate-route-types.ts +39 -0
  90. package/src/build/index.ts +13 -0
  91. package/src/build/route-trie.ts +291 -0
  92. package/src/build/route-types/ast-helpers.ts +25 -0
  93. package/src/build/route-types/ast-route-extraction.ts +98 -0
  94. package/src/build/route-types/codegen.ts +102 -0
  95. package/src/build/route-types/include-resolution.ts +418 -0
  96. package/src/build/route-types/param-extraction.ts +48 -0
  97. package/src/build/route-types/per-module-writer.ts +128 -0
  98. package/src/build/route-types/router-processing.ts +618 -0
  99. package/src/build/route-types/scan-filter.ts +85 -0
  100. package/src/build/runtime-discovery.ts +231 -0
  101. package/src/cache/background-task.ts +34 -0
  102. package/src/cache/cache-key-utils.ts +44 -0
  103. package/src/cache/cache-policy.ts +125 -0
  104. package/src/cache/cache-runtime.ts +342 -0
  105. package/src/cache/cache-scope.ts +167 -309
  106. package/src/cache/cf/cf-cache-store.ts +571 -17
  107. package/src/cache/cf/index.ts +13 -3
  108. package/src/cache/document-cache.ts +116 -77
  109. package/src/cache/handle-capture.ts +81 -0
  110. package/src/cache/handle-snapshot.ts +41 -0
  111. package/src/cache/index.ts +1 -15
  112. package/src/cache/memory-segment-store.ts +191 -13
  113. package/src/cache/profile-registry.ts +73 -0
  114. package/src/cache/read-through-swr.ts +134 -0
  115. package/src/cache/segment-codec.ts +256 -0
  116. package/src/cache/taint.ts +153 -0
  117. package/src/cache/types.ts +72 -122
  118. package/src/client.rsc.tsx +3 -1
  119. package/src/client.tsx +135 -301
  120. package/src/component-utils.ts +4 -4
  121. package/src/components/DefaultDocument.tsx +5 -1
  122. package/src/context-var.ts +156 -0
  123. package/src/debug.ts +19 -9
  124. package/src/errors.ts +108 -2
  125. package/src/handle.ts +55 -29
  126. package/src/handles/MetaTags.tsx +73 -20
  127. package/src/handles/breadcrumbs.ts +66 -0
  128. package/src/handles/index.ts +1 -0
  129. package/src/handles/meta.ts +30 -13
  130. package/src/host/cookie-handler.ts +21 -15
  131. package/src/host/errors.ts +8 -8
  132. package/src/host/index.ts +4 -7
  133. package/src/host/pattern-matcher.ts +27 -27
  134. package/src/host/router.ts +61 -39
  135. package/src/host/testing.ts +8 -8
  136. package/src/host/types.ts +15 -7
  137. package/src/host/utils.ts +1 -1
  138. package/src/href-client.ts +119 -29
  139. package/src/index.rsc.ts +155 -19
  140. package/src/index.ts +251 -30
  141. package/src/internal-debug.ts +11 -0
  142. package/src/loader.rsc.ts +26 -157
  143. package/src/loader.ts +27 -10
  144. package/src/network-error-thrower.tsx +3 -1
  145. package/src/outlet-provider.tsx +45 -0
  146. package/src/prerender/param-hash.ts +37 -0
  147. package/src/prerender/store.ts +186 -0
  148. package/src/prerender.ts +524 -0
  149. package/src/reverse.ts +354 -0
  150. package/src/root-error-boundary.tsx +41 -29
  151. package/src/route-content-wrapper.tsx +7 -4
  152. package/src/route-definition/dsl-helpers.ts +1121 -0
  153. package/src/route-definition/helper-factories.ts +200 -0
  154. package/src/route-definition/helpers-types.ts +478 -0
  155. package/src/route-definition/index.ts +55 -0
  156. package/src/route-definition/redirect.ts +101 -0
  157. package/src/route-definition/resolve-handler-use.ts +149 -0
  158. package/src/route-definition.ts +1 -1428
  159. package/src/route-map-builder.ts +217 -123
  160. package/src/route-name.ts +53 -0
  161. package/src/route-types.ts +77 -8
  162. package/src/router/content-negotiation.ts +215 -0
  163. package/src/router/debug-manifest.ts +72 -0
  164. package/src/router/error-handling.ts +9 -9
  165. package/src/router/find-match.ts +160 -0
  166. package/src/router/handler-context.ts +438 -86
  167. package/src/router/intercept-resolution.ts +402 -0
  168. package/src/router/lazy-includes.ts +237 -0
  169. package/src/router/loader-resolution.ts +356 -128
  170. package/src/router/logging.ts +251 -0
  171. package/src/router/manifest.ts +163 -35
  172. package/src/router/match-api.ts +555 -0
  173. package/src/router/match-context.ts +5 -3
  174. package/src/router/match-handlers.ts +440 -0
  175. package/src/router/match-middleware/background-revalidation.ts +108 -93
  176. package/src/router/match-middleware/cache-lookup.ts +460 -10
  177. package/src/router/match-middleware/cache-store.ts +98 -26
  178. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  179. package/src/router/match-middleware/segment-resolution.ts +80 -6
  180. package/src/router/match-pipelines.ts +10 -45
  181. package/src/router/match-result.ts +135 -35
  182. package/src/router/metrics.ts +240 -15
  183. package/src/router/middleware-cookies.ts +55 -0
  184. package/src/router/middleware-types.ts +220 -0
  185. package/src/router/middleware.ts +324 -369
  186. package/src/router/navigation-snapshot.ts +182 -0
  187. package/src/router/pattern-matching.ts +211 -43
  188. package/src/router/prerender-match.ts +502 -0
  189. package/src/router/preview-match.ts +98 -0
  190. package/src/router/request-classification.ts +310 -0
  191. package/src/router/revalidation.ts +137 -38
  192. package/src/router/route-snapshot.ts +245 -0
  193. package/src/router/router-context.ts +41 -21
  194. package/src/router/router-interfaces.ts +484 -0
  195. package/src/router/router-options.ts +618 -0
  196. package/src/router/router-registry.ts +24 -0
  197. package/src/router/segment-resolution/fresh.ts +748 -0
  198. package/src/router/segment-resolution/helpers.ts +268 -0
  199. package/src/router/segment-resolution/loader-cache.ts +199 -0
  200. package/src/router/segment-resolution/revalidation.ts +1379 -0
  201. package/src/router/segment-resolution/static-store.ts +67 -0
  202. package/src/router/segment-resolution.ts +21 -0
  203. package/src/router/segment-wrappers.ts +291 -0
  204. package/src/router/telemetry-otel.ts +299 -0
  205. package/src/router/telemetry.ts +300 -0
  206. package/src/router/timeout.ts +148 -0
  207. package/src/router/trie-matching.ts +239 -0
  208. package/src/router/types.ts +78 -3
  209. package/src/router.ts +740 -4252
  210. package/src/rsc/handler-context.ts +45 -0
  211. package/src/rsc/handler.ts +907 -797
  212. package/src/rsc/helpers.ts +140 -6
  213. package/src/rsc/index.ts +0 -20
  214. package/src/rsc/loader-fetch.ts +229 -0
  215. package/src/rsc/manifest-init.ts +90 -0
  216. package/src/rsc/nonce.ts +14 -0
  217. package/src/rsc/origin-guard.ts +141 -0
  218. package/src/rsc/progressive-enhancement.ts +391 -0
  219. package/src/rsc/response-error.ts +37 -0
  220. package/src/rsc/response-route-handler.ts +347 -0
  221. package/src/rsc/rsc-rendering.ts +246 -0
  222. package/src/rsc/runtime-warnings.ts +42 -0
  223. package/src/rsc/server-action.ts +356 -0
  224. package/src/rsc/ssr-setup.ts +128 -0
  225. package/src/rsc/types.ts +46 -11
  226. package/src/search-params.ts +230 -0
  227. package/src/segment-content-promise.ts +67 -0
  228. package/src/segment-loader-promise.ts +122 -0
  229. package/src/segment-system.tsx +134 -36
  230. package/src/server/context.ts +341 -61
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +113 -15
  234. package/src/server/loader-registry.ts +24 -64
  235. package/src/server/request-context.ts +607 -81
  236. package/src/server.ts +35 -130
  237. package/src/ssr/index.tsx +103 -30
  238. package/src/static-handler.ts +126 -0
  239. package/src/theme/ThemeProvider.tsx +21 -15
  240. package/src/theme/ThemeScript.tsx +5 -5
  241. package/src/theme/constants.ts +5 -2
  242. package/src/theme/index.ts +4 -14
  243. package/src/theme/theme-context.ts +4 -30
  244. package/src/theme/theme-script.ts +21 -18
  245. package/src/types/boundaries.ts +158 -0
  246. package/src/types/cache-types.ts +198 -0
  247. package/src/types/error-types.ts +192 -0
  248. package/src/types/global-namespace.ts +100 -0
  249. package/src/types/handler-context.ts +791 -0
  250. package/src/types/index.ts +88 -0
  251. package/src/types/loader-types.ts +210 -0
  252. package/src/types/route-config.ts +170 -0
  253. package/src/types/route-entry.ts +120 -0
  254. package/src/types/segments.ts +150 -0
  255. package/src/types.ts +1 -1623
  256. package/src/urls/include-helper.ts +207 -0
  257. package/src/urls/index.ts +53 -0
  258. package/src/urls/path-helper-types.ts +372 -0
  259. package/src/urls/path-helper.ts +364 -0
  260. package/src/urls/pattern-types.ts +107 -0
  261. package/src/urls/response-types.ts +116 -0
  262. package/src/urls/type-extraction.ts +372 -0
  263. package/src/urls/urls-function.ts +98 -0
  264. package/src/urls.ts +1 -802
  265. package/src/use-loader.tsx +161 -81
  266. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  267. package/src/vite/discovery/discover-routers.ts +348 -0
  268. package/src/vite/discovery/prerender-collection.ts +439 -0
  269. package/src/vite/discovery/route-types-writer.ts +258 -0
  270. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  271. package/src/vite/discovery/state.ts +117 -0
  272. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  273. package/src/vite/index.ts +15 -1133
  274. package/src/vite/plugin-types.ts +103 -0
  275. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  276. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  277. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  278. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  279. package/src/vite/plugins/expose-id-utils.ts +299 -0
  280. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  281. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  282. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  283. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  284. package/src/vite/plugins/expose-ids/types.ts +45 -0
  285. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  286. package/src/vite/plugins/performance-tracks.ts +88 -0
  287. package/src/vite/plugins/refresh-cmd.ts +127 -0
  288. package/src/vite/plugins/use-cache-transform.ts +323 -0
  289. package/src/vite/plugins/version-injector.ts +83 -0
  290. package/src/vite/plugins/version-plugin.ts +266 -0
  291. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  292. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  293. package/src/vite/rango.ts +462 -0
  294. package/src/vite/router-discovery.ts +918 -0
  295. package/src/vite/utils/ast-handler-extract.ts +517 -0
  296. package/src/vite/utils/banner.ts +36 -0
  297. package/src/vite/utils/bundle-analysis.ts +137 -0
  298. package/src/vite/utils/manifest-utils.ts +70 -0
  299. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  300. package/src/vite/utils/prerender-utils.ts +221 -0
  301. package/src/vite/utils/shared-utils.ts +170 -0
  302. package/CLAUDE.md +0 -43
  303. package/src/browser/lru-cache.ts +0 -69
  304. package/src/browser/request-controller.ts +0 -164
  305. package/src/cache/memory-store.ts +0 -253
  306. package/src/href-context.ts +0 -33
  307. package/src/href.ts +0 -255
  308. package/src/server/route-manifest-cache.ts +0 -173
  309. package/src/vite/expose-handle-id.ts +0 -209
  310. package/src/vite/expose-loader-id.ts +0 -426
  311. package/src/vite/expose-location-state-id.ts +0 -177
  312. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,342 @@
1
+ /**
2
+ * "use cache" Runtime
3
+ *
4
+ * Provides the runtime wrapper for functions marked with "use cache" directive.
5
+ * The Vite transform plugin wraps exports with registerCachedFunction().
6
+ *
7
+ * On cache miss: executes the function, serializes the result via RSC Flight
8
+ * protocol, captures handle data if tainted ctx is detected, and stores in
9
+ * the SegmentCacheStore.
10
+ *
11
+ * On cache hit: deserializes the cached result, restores handle data if present.
12
+ *
13
+ * On stale hit: returns stale data immediately, triggers background
14
+ * re-execution via waitUntil().
15
+ */
16
+
17
+ /// <reference types="@vitejs/plugin-rsc/types" />
18
+
19
+ import {
20
+ encodeReply,
21
+ createClientTemporaryReferenceSet,
22
+ } from "@vitejs/plugin-rsc/rsc";
23
+ import { getRequestContext } from "../server/request-context.js";
24
+ import {
25
+ isTainted,
26
+ CACHED_FN_SYMBOL,
27
+ isCachedFunction,
28
+ stampCacheExec,
29
+ unstampCacheExec,
30
+ } from "./taint.js";
31
+
32
+ export { isCachedFunction };
33
+ import { serializeResult, deserializeResult } from "./segment-codec.js";
34
+ import { createHandleStore } from "../server/handle-store.js";
35
+ import { restoreHandles } from "./handle-snapshot.js";
36
+ import { startHandleCapture, type HandleCapture } from "./handle-capture.js";
37
+ import { sortedSearchString } from "./cache-key-utils.js";
38
+ import { runBackground } from "./background-task.js";
39
+
40
+ /**
41
+ * Convert encodeReply result to a stable string key.
42
+ * encodeReply may return string or FormData — normalize to string.
43
+ */
44
+ async function replyToCacheKey(encoded: string | FormData): Promise<string> {
45
+ if (typeof encoded === "string") return encoded;
46
+ // FormData: convert to Response body, then to string for deterministic key
47
+ const text = await new Response(encoded).text();
48
+ return text;
49
+ }
50
+
51
+ // ============================================================================
52
+ // Core: registerCachedFunction
53
+ // ============================================================================
54
+
55
+ /**
56
+ * Register a function as a cached function.
57
+ * Called by the Vite transform for each "use cache" function.
58
+ *
59
+ * @param fn - The original async function
60
+ * @param id - Stable identifier (module path + export name)
61
+ * @param profileName - Cache profile name (from "use cache: profileName" or "default")
62
+ */
63
+ export function registerCachedFunction<T extends (...args: any[]) => any>(
64
+ fn: T,
65
+ id: string,
66
+ profileName: string,
67
+ ): T {
68
+ const wrapped = async function (this: any, ...args: any[]): Promise<any> {
69
+ const requestCtx = getRequestContext();
70
+ const store = requestCtx?._cacheStore;
71
+ const resolvedProfileName = profileName || "default";
72
+
73
+ // Bypass: no store or no getItem support
74
+ if (!store?.getItem) {
75
+ return fn.apply(this, args);
76
+ }
77
+
78
+ // Resolve profile strictly from request-scoped config (set by the
79
+ // active router via createRequestContext). No global fallback —
80
+ // global profile state is only for DSL-time cache("profileName").
81
+ const profile = requestCtx?._cacheProfiles?.[resolvedProfileName];
82
+
83
+ if (!profile) {
84
+ throw new Error(
85
+ `[use cache] "${id}" uses unknown cache profile "${resolvedProfileName}". ` +
86
+ `Define it in createRouter({ cacheProfiles: { "${resolvedProfileName}": { ttl: ... } } }).`,
87
+ );
88
+ }
89
+
90
+ // Separate tainted args (ctx, env, req) from key-generating args.
91
+ // For tainted objects that carry route context (params, pathname,
92
+ // searchParams), extract serializable values into the key so
93
+ // different routes, param combinations, and query variants produce
94
+ // distinct cache entries.
95
+ const keyArgs: unknown[] = [];
96
+ let hasTaintedArgs = false;
97
+ for (const arg of args) {
98
+ if (isTainted(arg)) {
99
+ hasTaintedArgs = true;
100
+ const ctx = arg as any;
101
+ if (ctx.params && typeof ctx.params === "object") {
102
+ // Include host to prevent cross-host cache collisions (same
103
+ // pattern as route-level cache-scope.ts key generation).
104
+ if (ctx.url?.host) {
105
+ keyArgs.push(ctx.url.host);
106
+ }
107
+ // Include route name to prevent collisions when the same cached
108
+ // function is reused across routes with identical pathname/params
109
+ // but different local reverse() scope.
110
+ if (ctx._routeName) {
111
+ keyArgs.push(ctx._routeName);
112
+ }
113
+ keyArgs.push(ctx.pathname, ctx.params);
114
+ if (ctx._responseType) {
115
+ keyArgs.push(ctx._responseType);
116
+ }
117
+ // Include user-facing search params (exclude internal _rsc*/__ params)
118
+ if (ctx.searchParams instanceof URLSearchParams) {
119
+ const normalized = sortedSearchString(ctx.searchParams);
120
+ if (normalized) {
121
+ keyArgs.push(normalized);
122
+ }
123
+ }
124
+ }
125
+ } else {
126
+ keyArgs.push(arg);
127
+ }
128
+ }
129
+
130
+ // If tainted args are present, we need the handle store for capture/restore.
131
+ // During late streaming (Suspense boundary resolution), ALS context may be
132
+ // gone. Throw early rather than silently dropping handle side effects.
133
+ if (hasTaintedArgs && !requestCtx?._handleStore) {
134
+ throw new Error(
135
+ `[use cache] "${id}" receives a tainted argument (ctx/env/req) but the ` +
136
+ `HandleStore is not available. This typically happens when a "use cache" ` +
137
+ `function with ctx runs outside the request context (e.g., during late ` +
138
+ `streaming after AsyncLocalStorage context is lost). Move the "use cache" ` +
139
+ `directive to a function that does not receive request-scoped objects, or ` +
140
+ `use the route-level cache() DSL instead.`,
141
+ );
142
+ }
143
+
144
+ // Generate cache key
145
+ let cacheKey: string;
146
+ try {
147
+ if (keyArgs.length > 0) {
148
+ const tempRefs = createClientTemporaryReferenceSet();
149
+ const encoded = await encodeReply(keyArgs as unknown[], {
150
+ temporaryReferences: tempRefs,
151
+ });
152
+ const argsKey = await replyToCacheKey(encoded);
153
+ cacheKey = `use-cache:${id}:${argsKey}`;
154
+ } else {
155
+ cacheKey = `use-cache:${id}`;
156
+ }
157
+ } catch {
158
+ // Non-serializable args: run uncached
159
+ return fn.apply(this, args);
160
+ }
161
+
162
+ // Cache lookup
163
+ const cached = await store.getItem(cacheKey);
164
+
165
+ if (cached && !cached.shouldRevalidate) {
166
+ // Fresh hit: deserialize and return
167
+ try {
168
+ const result = await deserializeResult(cached.value);
169
+ // Restore handle data if present
170
+ if (cached.handles && hasTaintedArgs) {
171
+ const handleStore = requestCtx?._handleStore;
172
+ if (handleStore) {
173
+ restoreHandles(cached.handles, handleStore);
174
+ }
175
+ }
176
+ return result;
177
+ } catch {
178
+ // Deserialization failed, fall through to fresh execution
179
+ }
180
+ }
181
+
182
+ if (cached?.shouldRevalidate) {
183
+ // Stale hit: return stale value, revalidate in background
184
+ try {
185
+ const result = await deserializeResult(cached.value);
186
+ if (cached.handles && hasTaintedArgs) {
187
+ const handleStore = requestCtx?._handleStore;
188
+ if (handleStore) {
189
+ restoreHandles(cached.handles, handleStore);
190
+ }
191
+ }
192
+ // Background revalidation — must capture handles if tainted args present.
193
+ // Use an isolated handle store so background pushes don't pollute the
194
+ // live response or throw LateHandlePushError on the completed store.
195
+ // Same isolation pattern as route-level background-revalidation.ts.
196
+ runBackground(requestCtx, async () => {
197
+ // Reuse closure-captured requestCtx instead of calling
198
+ // getRequestContext() — ALS context may be gone inside waitUntil.
199
+ let originalHandleStore:
200
+ | ReturnType<typeof createHandleStore>
201
+ | undefined;
202
+ if (hasTaintedArgs && requestCtx) {
203
+ originalHandleStore = requestCtx._handleStore;
204
+ requestCtx._handleStore = createHandleStore();
205
+ }
206
+ const bgHandleStore = hasTaintedArgs
207
+ ? requestCtx?._handleStore
208
+ : undefined;
209
+ let bgCapture: HandleCapture | undefined;
210
+ let bgStopCapture: (() => void) | undefined;
211
+ if (bgHandleStore) {
212
+ const c = startHandleCapture(bgHandleStore);
213
+ bgCapture = c.capture;
214
+ bgStopCapture = c.stop;
215
+ }
216
+
217
+ // Stamp tainted ARGS only — not requestCtx. The args stamp guards
218
+ // direct ctx method calls (ctx.set, ctx.header, ctx.onResponse, etc.)
219
+ // which is sufficient for correctness.
220
+ //
221
+ // We intentionally skip stamping requestCtx here because:
222
+ // 1. runBackground starts the async task synchronously (before the
223
+ // first await), so stampCacheExec would pollute the shared
224
+ // requestCtx while the foreground pipeline is still running.
225
+ // This causes assertNotInsideCacheExec to fire when cache-store
226
+ // later calls requestCtx.onResponse().
227
+ // 2. requestCtx methods are closure-bound to the original ctx, so
228
+ // neither Object.create() nor a proxy can isolate the stamp.
229
+ // 3. The foreground miss path already stamps requestCtx and catches
230
+ // cookies()/headers() misuse on first execution. The background
231
+ // re-runs the same function with the same request.
232
+ const bgTaintedArgs: unknown[] = [];
233
+ for (const arg of args) {
234
+ if (isTainted(arg)) {
235
+ stampCacheExec(arg as object);
236
+ bgTaintedArgs.push(arg);
237
+ }
238
+ }
239
+
240
+ try {
241
+ const freshResult = await fn.apply(this, args);
242
+ bgStopCapture?.();
243
+ const serialized = await serializeResult(freshResult);
244
+ if (serialized !== null) {
245
+ await store.setItem!(cacheKey, serialized, {
246
+ handles: bgCapture?.data,
247
+ ttl: profile.ttl,
248
+ swr: profile.swr,
249
+ tags: profile.tags,
250
+ });
251
+ }
252
+ } catch (bgError) {
253
+ bgStopCapture?.();
254
+ requestCtx?._reportBackgroundError?.(bgError, "stale-revalidation");
255
+ } finally {
256
+ for (const arg of bgTaintedArgs) {
257
+ unstampCacheExec(arg as object);
258
+ }
259
+ // Restore original handle store
260
+ if (originalHandleStore && requestCtx) {
261
+ requestCtx._handleStore = originalHandleStore;
262
+ }
263
+ }
264
+ });
265
+ return result;
266
+ } catch {
267
+ // Deserialization of stale value failed, fall through
268
+ }
269
+ }
270
+
271
+ // Cache miss: execute, serialize, store
272
+ const handleStore = hasTaintedArgs ? requestCtx?._handleStore : undefined;
273
+ let capture: HandleCapture | undefined;
274
+ let stopCapture: (() => void) | undefined;
275
+ if (handleStore && hasTaintedArgs) {
276
+ const c = startHandleCapture(handleStore);
277
+ capture = c.capture;
278
+ stopCapture = c.stop;
279
+ }
280
+
281
+ // Stamp tainted args so ctx.set(), ctx.header(), etc. throw if called
282
+ // inside the cached function body (those side effects are lost on hit).
283
+ // Uses ref-counted stamp/unstamp so overlapping executions
284
+ // sharing the same ctx don't clear each other's guards.
285
+ const taintedArgs: unknown[] = [];
286
+ for (const arg of args) {
287
+ if (isTainted(arg)) {
288
+ stampCacheExec(arg as object);
289
+ taintedArgs.push(arg);
290
+ }
291
+ }
292
+ // Always stamp the ALS RequestContext so cookies()/headers() guards fire
293
+ // even when the cached function receives no tainted args. The guard in
294
+ // cookie-store.ts checks RequestContext, not function args.
295
+ if (requestCtx) {
296
+ stampCacheExec(requestCtx as object);
297
+ }
298
+
299
+ let result: any;
300
+ try {
301
+ result = await fn.apply(this, args);
302
+ } finally {
303
+ // Decrement ref count; symbol is deleted when it reaches zero
304
+ for (const arg of taintedArgs) {
305
+ unstampCacheExec(arg as object);
306
+ }
307
+ if (requestCtx) {
308
+ unstampCacheExec(requestCtx as object);
309
+ }
310
+ // Remove this capture token (order-independent, safe for concurrent use)
311
+ stopCapture?.();
312
+ }
313
+
314
+ // Serialize and store — fully non-blocking when waitUntil is available.
315
+ // The response does not need to wait for serialization or the store write.
316
+ const cacheWrite = async () => {
317
+ try {
318
+ const serialized = await serializeResult(result);
319
+ if (serialized !== null) {
320
+ await store.setItem!(cacheKey, serialized, {
321
+ handles: capture?.data,
322
+ ttl: profile.ttl,
323
+ swr: profile.swr,
324
+ tags: profile.tags,
325
+ });
326
+ }
327
+ } catch (writeError) {
328
+ requestCtx?._reportBackgroundError?.(writeError, "cache-write");
329
+ }
330
+ };
331
+
332
+ await runBackground(requestCtx, cacheWrite, true);
333
+
334
+ return result;
335
+ };
336
+
337
+ // Brand the wrapper so it can be detected at runtime (e.g., to prevent
338
+ // accidental use as middleware).
339
+ (wrapped as any)[CACHED_FN_SYMBOL] = true;
340
+
341
+ return wrapped as unknown as T;
342
+ }