@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,525 @@
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 {
24
+ getRequestContext,
25
+ runWithRequestContext,
26
+ } from "../server/request-context.js";
27
+ import { isUnderTestRunner } from "../runtime-env.js";
28
+ import {
29
+ isTainted,
30
+ CACHED_FN_SYMBOL,
31
+ isCachedFunction,
32
+ stampCacheExec,
33
+ unstampCacheExec,
34
+ } from "./taint.js";
35
+
36
+ export { isCachedFunction };
37
+ import { serializeResult, deserializeResult } from "./segment-codec.js";
38
+ import { createHandleStore } from "../server/handle-store.js";
39
+ import {
40
+ restoreHandles,
41
+ encodeHandles,
42
+ decodeHandles,
43
+ } from "./handle-snapshot.js";
44
+ import { startHandleCapture, type HandleCapture } from "./handle-capture.js";
45
+ import { sortedSearchString } from "./cache-key-utils.js";
46
+ import { encodeKV } from "../encode-kv.js";
47
+ import { runBackground } from "./background-task.js";
48
+ import {
49
+ normalizeTags,
50
+ recordRequestTags,
51
+ runWithCacheTagScope,
52
+ } from "./cache-tag.js";
53
+ import { reportCacheError } from "./cache-error.js";
54
+ import type { CacheItemResult } from "./types.js";
55
+
56
+ /**
57
+ * DJB2 hash returning an 8-char hex string. Deterministic across runtimes
58
+ * (no crypto import — cache-runtime runs on the edge). Mirrors prerender's
59
+ * param-hash djb2Hex so binary key parts hash consistently.
60
+ */
61
+ function djb2HexBytes(bytes: Uint8Array): string {
62
+ let hash = 5381;
63
+ for (let i = 0; i < bytes.length; i++) {
64
+ hash = ((hash << 5) + hash + bytes[i]!) >>> 0;
65
+ }
66
+ return hash.toString(16).padStart(8, "0");
67
+ }
68
+
69
+ /**
70
+ * Convert encodeReply result to a stable string key.
71
+ *
72
+ * encodeReply may return a string or FormData. A plain string is already
73
+ * deterministic for a given arg set, so return it verbatim. FormData (emitted
74
+ * whenever a key arg is a typed array / Blob / File / a large object React
75
+ * lazily chunks) carries a per-call RANDOM multipart boundary
76
+ * (`formdata-undici-<random>`); stringifying the whole body via
77
+ * `new Response(formData).text()` would therefore produce a DIFFERENT key on
78
+ * every call, so the cached function would always miss and the store would
79
+ * accumulate one duplicate entry per call (unbounded growth).
80
+ *
81
+ * Instead derive the key from the entries themselves, independent of the
82
+ * boundary: iterate in sorted-key order and, for each value, emit a
83
+ * boundary-free token — `s:<value>` for strings, `b:<size>:<type>:<name>:<hash>`
84
+ * for Blob/File (bytes folded via djb2 so distinct payloads of equal
85
+ * size/type/name still differ). Strings carry an `s:` type tag so a string whose
86
+ * value happens to equal a blob token (e.g. the literal `b:4::a:b:<hash>`) cannot
87
+ * collide with an actual Blob/File entry under the same FormData key. The
88
+ * user-controlled `type`/`name` are percent-encoded before joining so an embedded
89
+ * `:` cannot shift the field boundaries and collide two distinct files (e.g.
90
+ * {name:"a:b",type:""} vs {name:"b",type:":a"}). The result is stable across
91
+ * identical arg sets.
92
+ */
93
+ export async function replyToCacheKey(
94
+ encoded: string | FormData,
95
+ ): Promise<string> {
96
+ if (typeof encoded === "string") return encoded;
97
+
98
+ // Snapshot entries synchronously (forEach avoids relying on FormData's
99
+ // iterator typings), then fold any Blob/File bytes asynchronously.
100
+ const raw: [string, FormDataEntryValue][] = [];
101
+ encoded.forEach((value, key) => {
102
+ raw.push([key, value]);
103
+ });
104
+ const pairs: [string, string][] = [];
105
+ for (const [key, value] of raw) {
106
+ if (typeof value === "string") {
107
+ // Type-tag strings with `s:` so a string equal to a blob token (e.g.
108
+ // `b:4::a:b:<hash>`) cannot collide with a Blob/File entry under the same
109
+ // key (which carries the `b:` tag below).
110
+ pairs.push([key, "s:" + value]);
111
+ } else {
112
+ // Blob/File: fold the bytes into a deterministic, boundary-free token.
113
+ // Percent-encode the user-controlled type/name so an embedded `:` cannot
114
+ // shift the `:`-delimited field boundaries and collide distinct files.
115
+ const buf = await value.arrayBuffer();
116
+ const hash = djb2HexBytes(new Uint8Array(buf));
117
+ const name = "name" in value ? value.name : "";
118
+ const encType = encodeURIComponent(value.type);
119
+ const encName = encodeURIComponent(name);
120
+ pairs.push([key, `b:${value.size}:${encType}:${encName}:${hash}`]);
121
+ }
122
+ }
123
+ return encodeKV(pairs, { sort: true });
124
+ }
125
+
126
+ // Cached-fn ids already warned about running uncached under a test runner, so
127
+ // the test-ergonomics warning fires once per fn rather than once per call.
128
+ const warnedUncachedUnderTest = new Set<string>();
129
+
130
+ // ============================================================================
131
+ // Core: registerCachedFunction
132
+ // ============================================================================
133
+
134
+ /**
135
+ * Register a function as a cached function.
136
+ * Called by the Vite transform for each "use cache" function.
137
+ *
138
+ * @param fn - The original async function
139
+ * @param id - Stable identifier (module path + export name)
140
+ * @param profileName - Cache profile name (from "use cache: profileName" or "default")
141
+ */
142
+ export function registerCachedFunction<T extends (...args: any[]) => any>(
143
+ fn: T,
144
+ id: string,
145
+ profileName: string,
146
+ ): T {
147
+ const wrapped = async function (this: any, ...args: any[]): Promise<any> {
148
+ const requestCtx = getRequestContext();
149
+ const store = requestCtx?._cacheStore;
150
+ const resolvedProfileName = profileName || "default";
151
+
152
+ // Bypass: no store or no getItem support. Still run inside a tag scope so a
153
+ // cacheTag() call inside the function degrades to a no-op rather than
154
+ // throwing "must be called inside a use cache function" - adopting cacheTag()
155
+ // must not hard-fail in apps/tests without an item-capable cache configured.
156
+ // Note: the INSIDE_CACHE_EXEC guard (cookies()/headers()/ctx.set() rejection)
157
+ // is intentionally NOT stamped here. It is a cached-path-only check; in the
158
+ // bypass the body actually executes, so the guarded side effects take effect
159
+ // and nothing is lost on a (non-existent) hit. Same applies to the
160
+ // non-serializable-args bypass below.
161
+ if (!store?.getItem) {
162
+ // Test-ergonomics guard: under a test runner, a "use cache" function that
163
+ // executes with no item-capable store seeded is exercising the UNCACHED
164
+ // path — a green test that proves nothing about caching. Warn once per fn
165
+ // id so the author knows to seed a cacheStore. Advisory (never throws), so
166
+ // a test that DELIBERATELY runs uncached is unaffected. Gated on the test
167
+ // runner (process.env.VITEST, not folded) so production never evaluates it.
168
+ if (isUnderTestRunner() && !warnedUncachedUnderTest.has(id)) {
169
+ warnedUncachedUnderTest.add(id);
170
+ console.warn(
171
+ `[rango] "use cache" function "${id}" executed but no cacheStore was ` +
172
+ `seeded; the cached path is NOT under test (it ran uncached). Pass ` +
173
+ `{ cacheStore, cacheProfiles } to runLoader/runMiddleware/renderHandler/` +
174
+ `runInRequestContext (or configure createRouter({ cache }) for dispatch) ` +
175
+ `to exercise it.`,
176
+ );
177
+ }
178
+ const scoped = runWithCacheTagScope(() => fn.apply(this, args));
179
+ const result = await scoped.result;
180
+ // Still record the runtime tags into the request set so a cacheTag() in an
181
+ // uncached function tags the document, even with no item-capable store.
182
+ recordRequestTags(scoped.tags, requestCtx);
183
+ return result;
184
+ }
185
+
186
+ // Resolve profile strictly from request-scoped config (set by the
187
+ // active router via createRequestContext). No global fallback —
188
+ // global profile state is only for DSL-time cache("profileName").
189
+ const profile = requestCtx?._cacheProfiles?.[resolvedProfileName];
190
+
191
+ if (!profile) {
192
+ throw new Error(
193
+ `[use cache] "${id}" uses unknown cache profile "${resolvedProfileName}". ` +
194
+ `Define it in createRouter({ cacheProfiles: { "${resolvedProfileName}": { ttl: ... } } }).`,
195
+ );
196
+ }
197
+
198
+ // Separate tainted args (ctx, env, req) from key-generating args.
199
+ // For tainted objects that carry route context (params, pathname,
200
+ // searchParams), extract serializable values into the key so
201
+ // different routes, param combinations, and query variants produce
202
+ // distinct cache entries.
203
+ const keyArgs: unknown[] = [];
204
+ let hasTaintedArgs = false;
205
+ for (const arg of args) {
206
+ if (isTainted(arg)) {
207
+ hasTaintedArgs = true;
208
+ const ctx = arg as any;
209
+ if (ctx.params && typeof ctx.params === "object") {
210
+ // Include host to prevent cross-host cache collisions (same
211
+ // pattern as route-level cache-scope.ts key generation).
212
+ if (ctx.url?.host) {
213
+ keyArgs.push(ctx.url.host);
214
+ }
215
+ // Include route name to prevent collisions when the same cached
216
+ // function is reused across routes with identical pathname/params
217
+ // but different local reverse() scope.
218
+ if (ctx._routeName) {
219
+ keyArgs.push(ctx._routeName);
220
+ }
221
+ keyArgs.push(ctx.pathname, ctx.params);
222
+ if (ctx._responseType) {
223
+ keyArgs.push(ctx._responseType);
224
+ }
225
+ // Include user-facing search params (exclude internal _rsc*/__ params)
226
+ if (ctx.searchParams instanceof URLSearchParams) {
227
+ const normalized = sortedSearchString(ctx.searchParams);
228
+ if (normalized) {
229
+ keyArgs.push(normalized);
230
+ }
231
+ }
232
+ }
233
+ } else {
234
+ keyArgs.push(arg);
235
+ }
236
+ }
237
+
238
+ // If tainted args are present, we need the handle store for capture/restore.
239
+ // During late streaming (Suspense boundary resolution), ALS context may be
240
+ // gone. Throw early rather than silently dropping handle side effects.
241
+ if (hasTaintedArgs && !requestCtx?._handleStore) {
242
+ throw new Error(
243
+ `[use cache] "${id}" receives a tainted argument (ctx/env/req) but the ` +
244
+ `HandleStore is not available. This typically happens when a "use cache" ` +
245
+ `function with ctx runs outside the request context (e.g., during late ` +
246
+ `streaming after AsyncLocalStorage context is lost). Move the "use cache" ` +
247
+ `directive to a function that does not receive request-scoped objects, or ` +
248
+ `use the route-level cache() DSL instead.`,
249
+ );
250
+ }
251
+
252
+ // Generate cache key
253
+ let cacheKey: string;
254
+ try {
255
+ if (keyArgs.length > 0) {
256
+ const tempRefs = createClientTemporaryReferenceSet();
257
+ const encoded = await encodeReply(keyArgs as unknown[], {
258
+ temporaryReferences: tempRefs,
259
+ });
260
+ const argsKey = await replyToCacheKey(encoded);
261
+ cacheKey = `use-cache:${id}:${argsKey}`;
262
+ } else {
263
+ cacheKey = `use-cache:${id}`;
264
+ }
265
+ } catch {
266
+ // Non-serializable args: run uncached (within a tag scope so cacheTag()
267
+ // still does not throw). Record runtime tags so the document union still
268
+ // sees them even though this call is not itself cached.
269
+ const scoped = runWithCacheTagScope(() => fn.apply(this, args));
270
+ const result = await scoped.result;
271
+ recordRequestTags(scoped.tags, requestCtx);
272
+ return result;
273
+ }
274
+
275
+ // Cache lookup
276
+ const cached = await store.getItem(cacheKey);
277
+
278
+ // Serve a cached entry on the hit path: deserialize the stored value,
279
+ // replay handle data (gated on tainted args), and surface the entry's tags
280
+ // to the request set (the function did not re-run, so its runtime cacheTag()
281
+ // tags are only available from the stored entry). Shared by the fresh-hit
282
+ // and stale-hit branches; the only divergence is the stale branch scheduling
283
+ // background revalidation, which it does after this returns.
284
+ const serveCached = async (entry: CacheItemResult): Promise<any> => {
285
+ const result = await deserializeResult(entry.value);
286
+ if (entry.handles && hasTaintedArgs) {
287
+ const handleStore = requestCtx?._handleStore;
288
+ if (handleStore) {
289
+ const r = await decodeHandles(entry.handles);
290
+ if (r) restoreHandles(r, handleStore);
291
+ }
292
+ }
293
+ recordRequestTags(entry.tags, requestCtx);
294
+ return result;
295
+ };
296
+
297
+ if (cached && !cached.shouldRevalidate) {
298
+ // Fresh hit: deserialize and return
299
+ try {
300
+ return await serveCached(cached);
301
+ } catch (error) {
302
+ // The stored value is corrupt/partial (failed RSC deserialize). Report
303
+ // it, then fall through to fresh execution - the miss path below re-runs
304
+ // and setItem() overwrites the faulty entry under the same key (self-heal).
305
+ reportCacheError(
306
+ error,
307
+ "cache-corrupt",
308
+ `[use cache] "${id}" fresh-hit`,
309
+ );
310
+ }
311
+ }
312
+
313
+ // foregroundOnAction (opt-in; see CacheProfile.foregroundOnAction): during an
314
+ // action's revalidation render, a stale entry falls through to the foreground
315
+ // miss path below instead of SWR. The flag is set by revalidateAfterAction.
316
+ const foregroundOnActionRevalidation =
317
+ requestCtx?._inActionRevalidation === true &&
318
+ profile.foregroundOnAction === true;
319
+ if (cached?.shouldRevalidate && !foregroundOnActionRevalidation) {
320
+ // Stale hit: return stale value, revalidate in background
321
+ try {
322
+ const result = await serveCached(cached);
323
+ // Background revalidation — must capture handles if tainted args present.
324
+ // Use an isolated handle store so background pushes don't pollute the
325
+ // live response or throw LateHandlePushError on the completed store.
326
+ // Same isolation pattern as route-level background-revalidation.ts.
327
+ runBackground(requestCtx, async () => {
328
+ // The closure-captured requestCtx is reused for the framework's own
329
+ // reads (handle store swap, error reporting) AND, below, to
330
+ // re-establish the request-context ALS around the user fn. ALS context
331
+ // may be gone inside waitUntil: on workerd a waitUntil task runs
332
+ // detached from the request's I/O context, so getRequestContext()
333
+ // inside the cached body would otherwise throw.
334
+ let originalHandleStore:
335
+ | ReturnType<typeof createHandleStore>
336
+ | undefined;
337
+ if (hasTaintedArgs && requestCtx) {
338
+ originalHandleStore = requestCtx._handleStore;
339
+ requestCtx._handleStore = createHandleStore();
340
+ }
341
+ const bgHandleStore = hasTaintedArgs
342
+ ? requestCtx?._handleStore
343
+ : undefined;
344
+ let bgCapture: HandleCapture | undefined;
345
+ let bgStopCapture: (() => void) | undefined;
346
+ if (bgHandleStore) {
347
+ const c = startHandleCapture(bgHandleStore);
348
+ bgCapture = c.capture;
349
+ bgStopCapture = c.stop;
350
+ }
351
+
352
+ // Stamp tainted ARGS only — not requestCtx. The args stamp guards
353
+ // direct ctx method calls (ctx.set, ctx.header, ctx.onResponse, etc.)
354
+ // which is sufficient for correctness.
355
+ //
356
+ // We intentionally skip stamping requestCtx here because:
357
+ // 1. runBackground starts the async task synchronously (before the
358
+ // first await), so stampCacheExec would pollute the shared
359
+ // requestCtx while the foreground pipeline is still running.
360
+ // This causes assertNotInsideCacheExec to fire when cache-store
361
+ // later calls requestCtx.onResponse().
362
+ // 2. requestCtx methods are closure-bound to the original ctx, so
363
+ // neither Object.create() nor a proxy can isolate the stamp.
364
+ // 3. The foreground miss path already stamps requestCtx and catches
365
+ // cookies()/headers() misuse on first execution. The background
366
+ // re-runs the same function with the same request.
367
+ const bgTaintedArgs: unknown[] = [];
368
+ for (const arg of args) {
369
+ if (isTainted(arg)) {
370
+ stampCacheExec(arg as object);
371
+ bgTaintedArgs.push(arg);
372
+ }
373
+ }
374
+
375
+ try {
376
+ // Re-establish the request-context ALS so a "use cache" body that
377
+ // reads the ambient getRequestContext() (e.g.
378
+ // getRequestContext().env.ApiKey) resolves during the background
379
+ // revalidation instead of throwing "called outside of a request
380
+ // context". runWithRequestContext sets the store for fn's
381
+ // synchronous kickoff; its async continuations inherit it.
382
+ const scoped = runWithRequestContext(requestCtx, () =>
383
+ runWithCacheTagScope(() => fn.apply(this, args)),
384
+ );
385
+ const freshResult = await scoped.result;
386
+ bgStopCapture?.();
387
+ // Merge profile/DSL tags with runtime cacheTag() tags, read after
388
+ // awaiting so post-await cacheTag() calls are included. Normalize
389
+ // (drops empty profile tags, matching the invalidate path) + dedupe.
390
+ const freshTags = [
391
+ ...new Set(
392
+ normalizeTags([...(profile.tags ?? []), ...scoped.tags]),
393
+ ),
394
+ ];
395
+ recordRequestTags(freshTags, requestCtx);
396
+ const serialized = await serializeResult(freshResult);
397
+ if (serialized !== null) {
398
+ const encodedHandles = bgCapture?.data
399
+ ? await encodeHandles(bgCapture.data)
400
+ : undefined;
401
+ await store.setItem!(cacheKey, serialized, {
402
+ handles: encodedHandles,
403
+ ttl: profile.ttl,
404
+ swr: profile.swr,
405
+ tags: freshTags.length > 0 ? freshTags : undefined,
406
+ });
407
+ }
408
+ } catch (bgError) {
409
+ bgStopCapture?.();
410
+ // Pass requestCtx explicitly: this runs in a detached background
411
+ // task where the ALS context is gone, so onError can only fire if
412
+ // we hand it the context captured up front.
413
+ reportCacheError(
414
+ bgError,
415
+ "stale-revalidation",
416
+ "[use cache] background revalidation failed",
417
+ requestCtx,
418
+ );
419
+ } finally {
420
+ for (const arg of bgTaintedArgs) {
421
+ unstampCacheExec(arg as object);
422
+ }
423
+ // Restore original handle store
424
+ if (originalHandleStore && requestCtx) {
425
+ requestCtx._handleStore = originalHandleStore;
426
+ }
427
+ }
428
+ });
429
+ return result;
430
+ } catch (error) {
431
+ // Stale value is corrupt/partial; report and fall through to a fresh
432
+ // execution, which overwrites the faulty entry under the same key.
433
+ reportCacheError(
434
+ error,
435
+ "cache-corrupt",
436
+ `[use cache] "${id}" stale-hit`,
437
+ );
438
+ }
439
+ }
440
+
441
+ // Cache miss: execute, serialize, store
442
+ const handleStore = hasTaintedArgs ? requestCtx?._handleStore : undefined;
443
+ let capture: HandleCapture | undefined;
444
+ let stopCapture: (() => void) | undefined;
445
+ if (handleStore && hasTaintedArgs) {
446
+ const c = startHandleCapture(handleStore);
447
+ capture = c.capture;
448
+ stopCapture = c.stop;
449
+ }
450
+
451
+ // Stamp tainted args so ctx.set(), ctx.header(), etc. throw if called
452
+ // inside the cached function body (those side effects are lost on hit).
453
+ // Uses ref-counted stamp/unstamp so overlapping executions
454
+ // sharing the same ctx don't clear each other's guards.
455
+ const taintedArgs: unknown[] = [];
456
+ for (const arg of args) {
457
+ if (isTainted(arg)) {
458
+ stampCacheExec(arg as object);
459
+ taintedArgs.push(arg);
460
+ }
461
+ }
462
+ // Always stamp the ALS RequestContext so cookies()/headers() guards fire
463
+ // even when the cached function receives no tainted args. The guard in
464
+ // cookie-store.ts checks RequestContext, not function args.
465
+ if (requestCtx) {
466
+ stampCacheExec(requestCtx as object);
467
+ }
468
+
469
+ let result: any;
470
+ let scoped: ReturnType<typeof runWithCacheTagScope>;
471
+ try {
472
+ scoped = runWithCacheTagScope(() => fn.apply(this, args));
473
+ result = await scoped.result;
474
+ } finally {
475
+ // Decrement ref count; symbol is deleted when it reaches zero
476
+ for (const arg of taintedArgs) {
477
+ unstampCacheExec(arg as object);
478
+ }
479
+ if (requestCtx) {
480
+ unstampCacheExec(requestCtx as object);
481
+ }
482
+ // Remove this capture token (order-independent, safe for concurrent use)
483
+ stopCapture?.();
484
+ }
485
+
486
+ // Merge profile/DSL tags with runtime cacheTag() tags. Read scoped.tags
487
+ // after awaiting result so post-await cacheTag() calls are included.
488
+ // Normalize (drops empty profile tags, matching the invalidate path) + dedupe.
489
+ const allTags = [
490
+ ...new Set(normalizeTags([...(profile.tags ?? []), ...scoped!.tags])),
491
+ ];
492
+ recordRequestTags(allTags, requestCtx);
493
+
494
+ // Serialize and store — fully non-blocking when waitUntil is available.
495
+ // The response does not need to wait for serialization or the store write.
496
+ const cacheWrite = async () => {
497
+ try {
498
+ const serialized = await serializeResult(result);
499
+ if (serialized !== null) {
500
+ const encodedHandles = capture?.data
501
+ ? await encodeHandles(capture.data)
502
+ : undefined;
503
+ await store.setItem!(cacheKey, serialized, {
504
+ handles: encodedHandles,
505
+ ttl: profile.ttl,
506
+ swr: profile.swr,
507
+ tags: allTags.length > 0 ? allTags : undefined,
508
+ });
509
+ }
510
+ } catch (writeError) {
511
+ requestCtx?._reportBackgroundError?.(writeError, "cache-write");
512
+ }
513
+ };
514
+
515
+ await runBackground(requestCtx, cacheWrite, true);
516
+
517
+ return result;
518
+ };
519
+
520
+ // Brand the wrapper so it can be detected at runtime (e.g., to prevent
521
+ // accidental use as middleware).
522
+ (wrapped as any)[CACHED_FN_SYMBOL] = true;
523
+
524
+ return wrapped as unknown as T;
525
+ }