@rangojs/router 0.0.0-experimental.002d056c

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 (305) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +5153 -0
  5. package/package.json +177 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +253 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  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 +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +638 -0
  43. package/src/browser/navigation-client.ts +261 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +582 -0
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +145 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +128 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +368 -0
  55. package/src/browser/react/NavigationProvider.tsx +413 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +464 -0
  79. package/src/browser/scroll-restoration.ts +397 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +547 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +479 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +982 -0
  105. package/src/cache/cf/index.ts +29 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +44 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +281 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +160 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +397 -0
  172. package/src/router/lazy-includes.ts +236 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +251 -0
  175. package/src/router/manifest.ts +269 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +193 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +749 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +320 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1242 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +291 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1006 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +237 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  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 +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +920 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +109 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +108 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +48 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +363 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +266 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +445 -0
  298. package/src/vite/router-discovery.ts +777 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,338 @@
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 and RequestContext so request-scoped
218
+ // reads (cookies, headers) and side effects (ctx.set, etc.)
219
+ // throw inside background revalidation, same as the miss path.
220
+ // Uses ref-counted stamp/unstamp so overlapping executions
221
+ // sharing the same ctx don't clear each other's guards.
222
+ const bgTaintedArgs: unknown[] = [];
223
+ for (const arg of args) {
224
+ if (isTainted(arg)) {
225
+ stampCacheExec(arg as object);
226
+ bgTaintedArgs.push(arg);
227
+ }
228
+ }
229
+ if (requestCtx) {
230
+ stampCacheExec(requestCtx as object);
231
+ }
232
+
233
+ try {
234
+ const freshResult = await fn.apply(this, args);
235
+ bgStopCapture?.();
236
+ const serialized = await serializeResult(freshResult);
237
+ if (serialized !== null) {
238
+ await store.setItem!(cacheKey, serialized, {
239
+ handles: bgCapture?.data,
240
+ ttl: profile.ttl,
241
+ swr: profile.swr,
242
+ tags: profile.tags,
243
+ });
244
+ }
245
+ } catch (bgError) {
246
+ bgStopCapture?.();
247
+ requestCtx?._reportBackgroundError?.(bgError, "stale-revalidation");
248
+ } finally {
249
+ for (const arg of bgTaintedArgs) {
250
+ unstampCacheExec(arg as object);
251
+ }
252
+ if (requestCtx) {
253
+ unstampCacheExec(requestCtx as object);
254
+ }
255
+ // Restore original handle store
256
+ if (originalHandleStore && requestCtx) {
257
+ requestCtx._handleStore = originalHandleStore;
258
+ }
259
+ }
260
+ });
261
+ return result;
262
+ } catch {
263
+ // Deserialization of stale value failed, fall through
264
+ }
265
+ }
266
+
267
+ // Cache miss: execute, serialize, store
268
+ const handleStore = hasTaintedArgs ? requestCtx?._handleStore : undefined;
269
+ let capture: HandleCapture | undefined;
270
+ let stopCapture: (() => void) | undefined;
271
+ if (handleStore && hasTaintedArgs) {
272
+ const c = startHandleCapture(handleStore);
273
+ capture = c.capture;
274
+ stopCapture = c.stop;
275
+ }
276
+
277
+ // Stamp tainted args so ctx.set(), ctx.header(), etc. throw if called
278
+ // inside the cached function body (those side effects are lost on hit).
279
+ // Uses ref-counted stamp/unstamp so overlapping executions
280
+ // sharing the same ctx don't clear each other's guards.
281
+ const taintedArgs: unknown[] = [];
282
+ for (const arg of args) {
283
+ if (isTainted(arg)) {
284
+ stampCacheExec(arg as object);
285
+ taintedArgs.push(arg);
286
+ }
287
+ }
288
+ // Always stamp the ALS RequestContext so cookies()/headers() guards fire
289
+ // even when the cached function receives no tainted args. The guard in
290
+ // cookie-store.ts checks RequestContext, not function args.
291
+ if (requestCtx) {
292
+ stampCacheExec(requestCtx as object);
293
+ }
294
+
295
+ let result: any;
296
+ try {
297
+ result = await fn.apply(this, args);
298
+ } finally {
299
+ // Decrement ref count; symbol is deleted when it reaches zero
300
+ for (const arg of taintedArgs) {
301
+ unstampCacheExec(arg as object);
302
+ }
303
+ if (requestCtx) {
304
+ unstampCacheExec(requestCtx as object);
305
+ }
306
+ // Remove this capture token (order-independent, safe for concurrent use)
307
+ stopCapture?.();
308
+ }
309
+
310
+ // Serialize and store — fully non-blocking when waitUntil is available.
311
+ // The response does not need to wait for serialization or the store write.
312
+ const cacheWrite = async () => {
313
+ try {
314
+ const serialized = await serializeResult(result);
315
+ if (serialized !== null) {
316
+ await store.setItem!(cacheKey, serialized, {
317
+ handles: capture?.data,
318
+ ttl: profile.ttl,
319
+ swr: profile.swr,
320
+ tags: profile.tags,
321
+ });
322
+ }
323
+ } catch (writeError) {
324
+ requestCtx?._reportBackgroundError?.(writeError, "cache-write");
325
+ }
326
+ };
327
+
328
+ await runBackground(requestCtx, cacheWrite, true);
329
+
330
+ return result;
331
+ };
332
+
333
+ // Brand the wrapper so it can be detected at runtime (e.g., to prevent
334
+ // accidental use as middleware).
335
+ (wrapped as any)[CACHED_FN_SYMBOL] = true;
336
+
337
+ return wrapped as unknown as T;
338
+ }