@wular/pnext 0.0.1

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 (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,759 @@
1
+ /**
2
+ * Process-wide revalidation registry backing the next/cache compat surface.
3
+ *
4
+ * pnext renders dynamic routes fresh on every request, so revalidation only
5
+ * has observable meaning for (a) prebuilt static HTML served from the build
6
+ * output and (b) unstable_cache data entries. Paths and tags record the time
7
+ * they were revalidated; consumers compare against their stored-at time.
8
+ */
9
+
10
+ import { AsyncLocalStorage } from 'node:async_hooks';
11
+ import { currentRequest, getWorkUnit } from '../../request/context';
12
+ import { realNow, trackPrerenderCacheFill } from '../../ppr';
13
+ import { cacheHandlerRevalidateTag } from './handler';
14
+ import { modernCacheUpdateTags } from './modern-handler';
15
+ import { escapeRegex } from '../../utils/source';
16
+ import {
17
+ prerenderErrorCollectionActive,
18
+ recordPrerenderError,
19
+ unstableCacheDynamicApiMessage,
20
+ useCacheDynamicApiMessage,
21
+ } from './build-prerender-errors';
22
+
23
+ /**
24
+ * Draft mode (a __prerender_bypass cookie on the active request) bypasses
25
+ * data-cache reads AND writes: every render sees fresh data and leaves the
26
+ * cached values untouched (Next semantics).
27
+ */
28
+ export function draftModeBypassActive() {
29
+ return currentRequest()?.cookies?.has('__prerender_bypass') ?? false;
30
+ }
31
+
32
+ // Cross-instance singletons. The `'use cache'` transform pulls this module (and use-cache.ts) into the
33
+ // per-app server bundle, so an app that uses `use cache` ends up with a SECOND copy of revalidate.ts,
34
+ // distinct from the copy the pnext runtime loads from source. Two copies means two of every
35
+ // module-level Map/counter/ALS - the producer scope a cached render enters would be invisible to the
36
+ // runtime fetch patch, and a revalidatePath() written by one copy's registry would never be seen by the
37
+ // other's staleness reader. Anchor all process-wide state on globalThis so both copies share it.
38
+ const REVALIDATE_GLOBALS = Symbol.for('pnext.compat.revalidateGlobals');
39
+ const USE_CACHE_ENTRIES = Symbol.for('pnext.compat.useCacheEntries');
40
+
41
+ function revalidateGlobal<T>(name: string, create: () => T): T {
42
+ const root = globalThis as Record<PropertyKey, unknown>;
43
+ const container = (root[REVALIDATE_GLOBALS] ??= {}) as Record<string, unknown>;
44
+ if (!(name in container)) container[name] = create();
45
+ return container[name] as T;
46
+ }
47
+
48
+ const revalidatedPaths = revalidateGlobal('revalidatedPaths', () => new Map<string, number>());
49
+ const revalidatedPathReasons = revalidateGlobal(
50
+ 'revalidatedPathReasons',
51
+ () => new Map<string, 'stale' | 'on-demand'>(),
52
+ );
53
+ const revalidatedTags = revalidateGlobal('revalidatedTags', () => new Map<string, number>());
54
+ // Monotonic counters stamped/sampled across the module. Held on a shared object
55
+ // (not module `let`s) so both module copies read and bump the same values:
56
+ // - tagSeq: stamped on every hard tag revalidation. In-memory cache entries
57
+ // ('use cache') compare their production-time sequence against it instead of
58
+ // wall-clock timestamps so read-your-writes works even when the updateTag and
59
+ // the re-read land in the same millisecond. The timestamp maps stay for
60
+ // prebuilt-HTML mtime comparisons, which cross request/process boundaries.
61
+ // - lastPathAt: wall-clock of the most recent path revalidation of ANY route;
62
+ // entries produced OUTSIDE a render (no owning route) fall back to it, so any
63
+ // revalidatePath invalidates them.
64
+ // - events: monotonic count of revalidation events (path or tag). Server
65
+ // actions sample it before/after an action to decide whether the client's
66
+ // view needs a refresh (Next invalidates its client router cache).
67
+ const counters = revalidateGlobal('counters', () => ({ tagSeq: 0, lastPathAt: 0, events: 0 }));
68
+ const revalidatedTagSeq = revalidateGlobal('revalidatedTagSeq', () => new Map<string, number>());
69
+ const revalidatedTagReasons = revalidateGlobal(
70
+ 'revalidatedTagReasons',
71
+ () => new Map<string, 'stale' | 'on-demand'>(),
72
+ );
73
+ const staleTags = revalidateGlobal('staleTags', () => new Map<string, number>());
74
+ const dataCacheInvalidators = revalidateGlobal('dataCacheInvalidators', () => new Set<() => void>());
75
+ export type RevalidationInvalidation =
76
+ | { kind: 'path'; path: string }
77
+ | { kind: 'tag'; tag: string; stale: boolean };
78
+
79
+ const revalidationInvalidators = revalidateGlobal(
80
+ 'revalidationInvalidators',
81
+ () => new Set<(invalidation: RevalidationInvalidation) => void>(),
82
+ );
83
+
84
+ interface RevalidatableUseCacheEntry {
85
+ tags: string[];
86
+ route?: string;
87
+ storedAt: number;
88
+ }
89
+
90
+ function evictUseCacheEntries(invalidation: RevalidationInvalidation) {
91
+ const entries = (globalThis as Record<PropertyKey, unknown>)[USE_CACHE_ENTRIES] as
92
+ | Map<string, RevalidatableUseCacheEntry>
93
+ | undefined;
94
+ if (!entries || (invalidation.kind === 'tag' && invalidation.stale)) return;
95
+ for (const [key, entry] of entries) {
96
+ const matches =
97
+ invalidation.kind === 'tag'
98
+ ? entry.tags.includes(invalidation.tag)
99
+ : pathScopeRevalidated(entry.route, entry.storedAt);
100
+ if (matches) entries.delete(key);
101
+ }
102
+ }
103
+ // Pattern marks: revalidatePath('/x', 'layout') covers the whole subtree, and
104
+ // paths containing dynamic segments ('/blog/[author]') cover every match.
105
+ const revalidatedPatterns = revalidateGlobal(
106
+ 'revalidatedPatterns',
107
+ () => [] as { regex: RegExp; at: number; reason: 'stale' | 'on-demand' }[],
108
+ );
109
+ const revalidatedPatternsLimit = 256;
110
+
111
+ export function revalidationEventCount() {
112
+ return counters.events;
113
+ }
114
+
115
+ /**
116
+ * Signal a client-router refresh without expiring the data cache. refresh()
117
+ * (next/cache) inside a server action re-renders the current route on the
118
+ * client; the action endpoint samples the event count to set the refresh
119
+ * header, so bumping the counter is enough.
120
+ */
121
+ export function bumpRevalidationEvent() {
122
+ counters.events += 1;
123
+ }
124
+
125
+ export function normalizeRevalidatePath(path: string) {
126
+ let pathname = path.split('?')[0] ?? path;
127
+ try {
128
+ pathname = decodeURI(pathname);
129
+ } catch {
130
+ // Keep malformed escape sequences unchanged.
131
+ }
132
+ const trimmed = pathname.replace(/\/+$/, '');
133
+ return trimmed === '' ? '/' : trimmed;
134
+ }
135
+
136
+ export function markPathRevalidated(path: string, type?: 'page' | 'layout') {
137
+ counters.events += 1;
138
+ // fetch/data cache entries carry the route they were produced under (Next's
139
+ // implicit `_N_T_/route` soft tag). Revalidating a path only invalidates the
140
+ // entries owned by matching routes (see pathScopeRevalidated); route-less
141
+ // entries (produced outside a render) fall back to this global timestamp.
142
+ counters.lastPathAt = Date.now();
143
+ const normalized = normalizeRevalidatePath(path);
144
+ void modernCacheUpdateTags([`_N_T_${normalized}`]);
145
+ const reason = getWorkUnit()?.phase === 'action' ? 'stale' : 'on-demand';
146
+ if (normalized.includes('[') || type === 'layout') {
147
+ const source = normalized
148
+ .split('/')
149
+ .map(segment =>
150
+ /^\[?\[\.\.\./.test(segment) ? '.+' : segment.startsWith('[') ? '[^/]+' : escapeRegex(segment),
151
+ )
152
+ .join('/');
153
+ const base = normalized === '/' ? '' : source;
154
+ const regex =
155
+ type === 'layout' ? new RegExp(`^${base}(?:/.*)?$`) : new RegExp(`^${source}$`);
156
+ revalidatedPatterns.push({ regex, at: Date.now(), reason });
157
+ if (revalidatedPatterns.length > revalidatedPatternsLimit) revalidatedPatterns.shift();
158
+ const invalidation = { kind: 'path', path: normalized } as const;
159
+ evictUseCacheEntries(invalidation);
160
+ notifyRevalidationInvalidators(invalidation);
161
+ return;
162
+ }
163
+ revalidatedPaths.set(normalized, Date.now());
164
+ revalidatedPathReasons.set(normalized, reason);
165
+ const invalidation = { kind: 'path', path: normalized } as const;
166
+ evictUseCacheEntries(invalidation);
167
+ notifyRevalidationInvalidators(invalidation);
168
+ }
169
+
170
+ export function markTagRevalidated(tag: string) {
171
+ counters.events += 1;
172
+ revalidatedTags.set(tag, Date.now());
173
+ revalidatedTagSeq.set(tag, ++counters.tagSeq);
174
+ revalidatedTagReasons.set(tag, getWorkUnit()?.phase === 'action' ? 'stale' : 'on-demand');
175
+ // Forward to a configured cache handler so a persistent backend drops the
176
+ // entry too (best-effort; the in-memory marks are authoritative).
177
+ void cacheHandlerRevalidateTag(tag);
178
+ void modernCacheUpdateTags([tag]);
179
+ const invalidation = { kind: 'tag', tag, stale: false } as const;
180
+ evictUseCacheEntries(invalidation);
181
+ notifyRevalidationInvalidators(invalidation);
182
+ }
183
+
184
+ export function markTagStale(tag: string) {
185
+ // Deliberately NOT counted as a revalidation event: a profile-based revalidateTag is
186
+ // stale-while-revalidate - the client keeps showing its current (stale) view and must NOT be told to
187
+ // refresh after the action. Fresh data appears only on the next navigation/refresh, which serves stale
188
+ // and regenerates in the background.
189
+ staleTags.set(tag, Date.now());
190
+ void cacheHandlerRevalidateTag(tag);
191
+ void modernCacheUpdateTags([tag]);
192
+ notifyRevalidationInvalidators({ kind: 'tag', tag, stale: true });
193
+ }
194
+
195
+ // Marks are integer Date.now() while file mtimes carry fractional
196
+ // milliseconds; truncate so a mark landing in the same millisecond as the
197
+ // write still counts as newer (worst case: one redundant regeneration).
198
+ function markedAfter(at: number | undefined, time: number) {
199
+ return at !== undefined && at >= Math.trunc(time) && at > time - 1;
200
+ }
201
+
202
+ export function pathRevalidatedSince(path: string, time: number) {
203
+ const normalized = normalizeRevalidatePath(path);
204
+ if (markedAfter(revalidatedPaths.get(normalized), time)) return true;
205
+ return revalidatedPatterns.some(
206
+ pattern => markedAfter(pattern.at, time) && pattern.regex.test(normalized),
207
+ );
208
+ }
209
+
210
+ export function pathRevalidationReasonSince(
211
+ path: string,
212
+ time: number,
213
+ ): 'stale' | 'on-demand' | undefined {
214
+ const normalized = normalizeRevalidatePath(path);
215
+ if (markedAfter(revalidatedPaths.get(normalized), time)) {
216
+ return revalidatedPathReasons.get(normalized) ?? 'on-demand';
217
+ }
218
+ return revalidatedPatterns.find(
219
+ pattern => markedAfter(pattern.at, time) && pattern.regex.test(normalized),
220
+ )?.reason;
221
+ }
222
+
223
+ /**
224
+ * Whether a cache entry produced under `route` (its owning render's pathname,
225
+ * or undefined when produced outside any render) has been invalidated by a
226
+ * revalidatePath since it was stored at `time`. Route-scoped entries match
227
+ * only their own route (respecting layout/dynamic patterns); route-less
228
+ * entries are invalidated by any path revalidation.
229
+ */
230
+ export function pathScopeRevalidated(route: string | undefined, time: number): boolean {
231
+ if (route === undefined) return markedAfter(counters.lastPathAt || undefined, time);
232
+ return pathRevalidatedSince(route, time);
233
+ }
234
+
235
+ export function tagsRevalidatedSince(tags: readonly string[], time: number) {
236
+ return tags.some(tag => markedAfter(revalidatedTags.get(tag), time));
237
+ }
238
+
239
+ /** Current hard-tag-revalidation sequence, stamped on an entry at production. */
240
+ export function currentTagRevalidationSeq() {
241
+ return counters.tagSeq;
242
+ }
243
+
244
+ /**
245
+ * Identifies the process that produced a resume-data-cache record. Stamped into
246
+ * every RDC record at capture and compared at seed, because `tagSeq` only means
247
+ * something within one process (see rebaseRdcTagSeq).
248
+ */
249
+ export function rdcProcessId(): string {
250
+ return revalidateGlobal(
251
+ 'rdcProcessId',
252
+ () => `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
253
+ );
254
+ }
255
+
256
+ /**
257
+ * Rebase an RDC record's `tagSeq` onto this process's sequence.
258
+ *
259
+ * `tagSeq` counts hard tag revalidations WITHIN a process and resets to 0 on restart, so a sequence
260
+ * serialized by the build (or a previous server process) is meaningless here. A record from a FOREIGN
261
+ * process was produced before anything that happened in this one, so it rebases to 0: any updateTag()
262
+ * of its tags seen by this process invalidates it. Rebasing onto the CURRENT sequence instead hides
263
+ * revalidations that already happened - an RDC seed running on the post-action re-render would restamp
264
+ * the stale record as fresh, so updateTag() in a server action could never surface fresh data.
265
+ *
266
+ * A record this process itself captured keeps its own sequence (clamped, since the counter can be
267
+ * reset), so a shell regenerated right after a revalidation is not immediately re-invalidated by it.
268
+ */
269
+ export function rebaseRdcTagSeq(record: { tagSeq: number; pid?: string }): number {
270
+ if (record.pid !== rdcProcessId()) return 0;
271
+ return Math.min(record.tagSeq, counters.tagSeq);
272
+ }
273
+
274
+ /**
275
+ * Whether any of `tags` was hard-revalidated after `seq` (the sequence sampled
276
+ * when the entry began producing). Sequence-based so it is immune to the
277
+ * millisecond-precision ambiguity that plagues `tagsRevalidatedSince` for
278
+ * in-memory entries: an updateTag() and an immediate re-read in the same
279
+ * request reliably see the invalidation exactly once.
280
+ */
281
+ export function tagsRevalidatedAfterSeq(tags: readonly string[], seq: number) {
282
+ return tags.some(tag => (revalidatedTagSeq.get(tag) ?? 0) > seq);
283
+ }
284
+
285
+ export function tagsRevalidationReasonSince(
286
+ tags: readonly string[],
287
+ time: number,
288
+ ): 'stale' | 'on-demand' | undefined {
289
+ for (const tag of tags) {
290
+ if (markedAfter(revalidatedTags.get(tag), time)) {
291
+ return revalidatedTagReasons.get(tag) ?? 'on-demand';
292
+ }
293
+ }
294
+ return undefined;
295
+ }
296
+
297
+ export function tagsStaleSince(tags: readonly string[], time: number) {
298
+ return tags.some(tag => markedAfter(staleTags.get(tag), time));
299
+ }
300
+
301
+ interface DataCacheEntry {
302
+ value: Promise<unknown>;
303
+ storedAt: number;
304
+ tags: readonly string[];
305
+ revalidateSeconds?: number;
306
+ /** A background stale-while-revalidate refresh is already in flight. */
307
+ refreshing?: boolean;
308
+ /** Pathname of the render that produced this entry (path-revalidation scope). */
309
+ route?: string;
310
+ /**
311
+ * The render cache-meta this entry was produced under: an on-demand
312
+ * regeneration (refreshFetches) re-produces each entry once per render, then
313
+ * repeated reads dedupe against it.
314
+ */
315
+ refreshedIn?: object;
316
+ }
317
+
318
+ const dataCache = revalidateGlobal('dataCache', () => new Map<string, DataCacheEntry>());
319
+ const dataCacheLimit = 1024;
320
+
321
+ function dataCacheEntryExpired(entry: DataCacheEntry) {
322
+ return (
323
+ entry.revalidateSeconds !== undefined &&
324
+ realNow() - entry.storedAt >= entry.revalidateSeconds * 1000
325
+ );
326
+ }
327
+
328
+ export interface DataCacheOptions {
329
+ tags?: string[];
330
+ revalidateSeconds?: number;
331
+ }
332
+
333
+ /**
334
+ * Memoize an async producer under a stable key. On-demand invalidation
335
+ * (revalidateTag / an on-demand-revalidating render) blocks and re-produces;
336
+ * TTL expiry serves the stale value while refreshing in the background
337
+ * (Next's stale-while-revalidate data-cache semantics). Rejected results are
338
+ * evicted so errors don't stick.
339
+ */
340
+ export function cachedData(
341
+ key: string,
342
+ produce: () => Promise<unknown>,
343
+ options: DataCacheOptions = {},
344
+ ): Promise<unknown> {
345
+ if (draftModeBypassActive()) return produce();
346
+ const entry = dataCache.get(key);
347
+ const meta = cacheMetaStorage.getStore();
348
+ // On-demand revalidation re-produces each entry ONCE per render (Next skips the cache read entirely
349
+ // under isOnDemandRevalidate), then dedupes via refreshedIn - but only entries the revalidation
350
+ // actually reached. See the matching gate in use-cache.ts: a still-marked route re-renders many times
351
+ // before its prebuilt HTML is written back.
352
+ const refresh =
353
+ meta?.refreshFetches === true &&
354
+ entry?.refreshedIn !== meta &&
355
+ entry !== undefined &&
356
+ (tagsRevalidatedSince(entry.tags, entry.storedAt) ||
357
+ pathScopeRevalidated(entry.route, entry.storedAt) ||
358
+ tagsStaleSince(entry.tags, entry.storedAt) ||
359
+ dataCacheEntryExpired(entry));
360
+ if (
361
+ entry &&
362
+ !refresh &&
363
+ !tagsRevalidatedSince(entry.tags, entry.storedAt) &&
364
+ !pathScopeRevalidated(entry.route, entry.storedAt)
365
+ ) {
366
+ recordCacheTags(entry.tags);
367
+ if (entry.revalidateSeconds !== undefined) {
368
+ recordCacheRevalidate(entry.revalidateSeconds);
369
+ }
370
+ const tagStale = tagsStaleSince(entry.tags, entry.storedAt);
371
+ if (!tagStale && !dataCacheEntryExpired(entry)) return entry.value;
372
+ // ISR regeneration renders refetch expired entries inline.
373
+ if (cacheMetaStorage.getStore()?.blockingStaleFetches !== true) {
374
+ if (!entry.refreshing) {
375
+ entry.refreshing = true;
376
+ void storeDataCacheEntry(key, produce, options, entry);
377
+ }
378
+ return entry.value;
379
+ }
380
+ }
381
+ return storeDataCacheEntry(key, produce, options);
382
+ }
383
+
384
+ function storeDataCacheEntry(
385
+ key: string,
386
+ produce: () => Promise<unknown>,
387
+ options: DataCacheOptions,
388
+ serveStale?: DataCacheEntry,
389
+ ): Promise<unknown> {
390
+ // Collect the tags of fetches/nested caches produced inside this entry so it
391
+ // inherits them (revalidateTag can then invalidate it), and propagate the
392
+ // aggregate up to an enclosing producer once settled.
393
+ const parentProducerTags = producerTagsStorage.getStore();
394
+ const producerTags = new Set<string>(options.tags ?? []);
395
+ const value = producerTagsStorage.run(producerTags, () =>
396
+ runInsideUnstableCacheProducer(produce),
397
+ );
398
+ // Same contract as the 'use cache' fill: a build prerender's task boundary
399
+ // waits for an in-flight data-cache fill rather than cutting it short, so
400
+ // cached IO still lands in the static shell (core ppr).
401
+ void trackPrerenderCacheFill(value);
402
+ const producingMeta = cacheMetaStorage.getStore();
403
+ const stored: DataCacheEntry = {
404
+ value,
405
+ storedAt: realNow(),
406
+ tags: options.tags ?? [],
407
+ revalidateSeconds: options.revalidateSeconds,
408
+ ...(producingMeta !== undefined ? { refreshedIn: producingMeta } : {}),
409
+ ...(serveStale?.route !== undefined
410
+ ? { route: serveStale.route }
411
+ : producingMeta?.route !== undefined
412
+ ? { route: producingMeta.route }
413
+ : {}),
414
+ };
415
+ const applyInheritedTags = () => {
416
+ stored.tags = [...producerTags];
417
+ if (parentProducerTags) for (const tag of producerTags) parentProducerTags.add(tag);
418
+ };
419
+ if (serveStale) {
420
+ // Background refresh: only replace the stale entry once the new value
421
+ // settles, so concurrent readers keep getting the stale value instantly.
422
+ value.then(
423
+ () => {
424
+ applyInheritedTags();
425
+ dataCache.set(key, stored);
426
+ },
427
+ () => {
428
+ serveStale.refreshing = false;
429
+ },
430
+ );
431
+ return value;
432
+ }
433
+ if (dataCache.size >= dataCacheLimit) {
434
+ const oldest = dataCache.keys().next().value;
435
+ if (oldest !== undefined) dataCache.delete(oldest);
436
+ }
437
+ dataCache.set(key, stored);
438
+ value.then(applyInheritedTags, () => {
439
+ if (dataCache.get(key)?.value === value) dataCache.delete(key);
440
+ });
441
+ return value;
442
+ }
443
+
444
+ export function clearRevalidationState() {
445
+ revalidatedPaths.clear();
446
+ revalidatedPathReasons.clear();
447
+ revalidatedTags.clear();
448
+ revalidatedTagSeq.clear();
449
+ counters.tagSeq = 0;
450
+ revalidatedTagReasons.clear();
451
+ staleTags.clear();
452
+ revalidatedPatterns.length = 0;
453
+ counters.lastPathAt = 0;
454
+ dataCache.clear();
455
+ for (const invalidate of dataCacheInvalidators) invalidate();
456
+ }
457
+
458
+ export function registerDataCacheInvalidator(invalidate: () => void): () => void {
459
+ dataCacheInvalidators.add(invalidate);
460
+ return () => {
461
+ dataCacheInvalidators.delete(invalidate);
462
+ };
463
+ }
464
+
465
+ export function registerRevalidationInvalidator(
466
+ invalidate: (invalidation: RevalidationInvalidation) => void,
467
+ ): () => void {
468
+ revalidationInvalidators.add(invalidate);
469
+ return () => {
470
+ revalidationInvalidators.delete(invalidate);
471
+ };
472
+ }
473
+
474
+ function notifyRevalidationInvalidators(invalidation: RevalidationInvalidation) {
475
+ for (const invalidate of revalidationInvalidators) invalidate(invalidation);
476
+ }
477
+
478
+ /**
479
+ * Cache-meta scope wrapped around a render. Data-cache consumers (patched
480
+ * fetch, unstable_cache, 'use cache') record the tags and revalidate periods
481
+ * they used so the surrounding prerender/ISR write can persist them, and read
482
+ * the route's `fetchCache` segment config for cache-mode defaults.
483
+ */
484
+ export interface RenderCacheMeta {
485
+ tags: Set<string>;
486
+ revalidateSeconds?: number;
487
+ /**
488
+ * Effective `use cache` cacheLife expire/stale windows aggregated across the
489
+ * render's cache scopes (min of each). Recorded alongside revalidateSeconds so
490
+ * a build shell prerender can persist the route's SWR cache-control +
491
+ * x-nextjs-stale-time even when the work-unit stash (used by the live-render
492
+ * response finalizer) is not in scope during the async cache propagation.
493
+ */
494
+ expireSeconds?: number;
495
+ staleSeconds?: number;
496
+ fetchCache?: string;
497
+ /** Route path label for error messages (e.g. invalid fetch revalidate). */
498
+ route?: string;
499
+ /** On-demand revalidation render: data caches must refetch and overwrite. */
500
+ refreshFetches?: boolean;
501
+ /**
502
+ * ISR regeneration render: TTL-expired data-cache entries refetch inline
503
+ * (blocking) instead of serving stale, so the regenerated page carries
504
+ * fresh data instead of lagging one generation behind.
505
+ */
506
+ blockingStaleFetches?: boolean;
507
+ /**
508
+ * The render used an explicit no-store signal (fetch cache:'no-store'/
509
+ * 'no-cache', next.revalidate 0, or unstable_noStore()): the route must not
510
+ * be served from prebuilt output.
511
+ */
512
+ noStore?: boolean;
513
+ /**
514
+ * Static-generation render (build prerender or ISR regeneration): a plain
515
+ * fetch with no explicit cache option defaults to caching (Next's
516
+ * force-cache-during-static-generation semantics) instead of per-request.
517
+ */
518
+ prerender?: boolean;
519
+ /**
520
+ * True when the surrounding render is a route handler. Route handlers are
521
+ * dynamic by default (Next): an explicitly-uncached fetch (cache:'default')
522
+ * opts the handler out of prebuilt static output, whereas a page with the
523
+ * same fetch simply freezes at prerender time.
524
+ */
525
+ handler?: boolean;
526
+ /** True when `dynamic = 'error'` must reject request API access. */
527
+ dynamicError?: boolean;
528
+ /**
529
+ * after() work queued during a static-generation render. There is no response close to flush against,
530
+ * so the render itself drains the queue once `produce` settles - Next's semantics: a build prerender
531
+ * waits for after() callbacks, and a rejection fails that page's prerender. Compat's after() enqueues;
532
+ * nothing in the cache layer knows what the tasks are.
533
+ */
534
+ afterTasks?: (() => Promise<void>)[];
535
+ }
536
+
537
+ const cacheMetaStorage = revalidateGlobal(
538
+ 'cacheMetaStorage',
539
+ () => new AsyncLocalStorage<RenderCacheMeta>(),
540
+ );
541
+
542
+ export function currentRenderCacheMeta() {
543
+ return cacheMetaStorage.getStore();
544
+ }
545
+
546
+ /**
547
+ * True while producing prebuilt static output - a build-time prerender or an ISR regeneration. In that
548
+ * context a plain fetch with no explicit cache option defaults to caching, matching Next's
549
+ * static-generation force-cache default.
550
+ */
551
+ export function currentRenderIsStaticGeneration(): boolean {
552
+ const meta = cacheMetaStorage.getStore();
553
+ return meta?.prerender === true || meta?.blockingStaleFetches === true;
554
+ }
555
+
556
+ export function recordCacheTags(tags: readonly string[]) {
557
+ // Inside an unstable_cache producer, inner fetch/cache tags are inherited by
558
+ // the producing entry (Next semantics: an unstable_cache boundary collects the
559
+ // tags of every fetch made inside it, so revalidateTag invalidates the entry).
560
+ const producerTags = producerTagsStorage.getStore();
561
+ if (producerTags) for (const tag of tags) producerTags.add(tag);
562
+ const meta = cacheMetaStorage.getStore();
563
+ if (!meta) return;
564
+ for (const tag of tags) meta.tags.add(tag);
565
+ }
566
+
567
+ // Hook into the active `use cache` producer scope (registered by use-cache.ts;
568
+ // held on the shared global container so both module copies see it). An
569
+ // explicit revalidate recorded while a `use cache` entry is producing lowers
570
+ // that ENTRY's cacheLife too, matching Next: a `fetch(..., { next:
571
+ // { revalidate: 1 } })` inside 'use cache' bounds the cache entry at 1s, not
572
+ // just the surrounding route.
573
+ const useCacheRevalidateSink = revalidateGlobal('useCacheRevalidateSink', () => ({
574
+ current: undefined as ((seconds: number) => void) | undefined,
575
+ }));
576
+
577
+ export function setUseCacheRevalidateSink(sink: (seconds: number) => void) {
578
+ useCacheRevalidateSink.current = sink;
579
+ }
580
+
581
+ export function recordCacheRevalidate(seconds: number) {
582
+ useCacheRevalidateSink.current?.(seconds);
583
+ const meta = cacheMetaStorage.getStore();
584
+ if (!meta) return;
585
+ meta.revalidateSeconds =
586
+ meta.revalidateSeconds === undefined ? seconds : Math.min(meta.revalidateSeconds, seconds);
587
+ }
588
+
589
+ /** Record a `use cache` entry's expire window on the render meta (min-combined). */
590
+ export function recordCacheExpire(seconds: number) {
591
+ const meta = cacheMetaStorage.getStore();
592
+ if (!meta) return;
593
+ meta.expireSeconds =
594
+ meta.expireSeconds === undefined ? seconds : Math.min(meta.expireSeconds, seconds);
595
+ }
596
+
597
+ /** Record a `use cache` entry's stale window on the render meta (min-combined). */
598
+ export function recordCacheStale(seconds: number) {
599
+ const meta = cacheMetaStorage.getStore();
600
+ if (!meta) return;
601
+ meta.staleSeconds =
602
+ meta.staleSeconds === undefined ? seconds : Math.min(meta.staleSeconds, seconds);
603
+ }
604
+
605
+ export function recordCacheNoStore() {
606
+ // Inside a data-cache producer (unstable_cache / 'use cache') the cache
607
+ // boundary wins: no-store signals do not leak out to the route.
608
+ if (insideProducerStorage.getStore()) return;
609
+ const meta = cacheMetaStorage.getStore();
610
+ if (meta) meta.noStore = true;
611
+ }
612
+
613
+ const insideProducerStorage = revalidateGlobal(
614
+ 'insideProducerStorage',
615
+ () => new AsyncLocalStorage<boolean>(),
616
+ );
617
+ const producerKindStorage = revalidateGlobal(
618
+ 'producerKindStorage',
619
+ () => new AsyncLocalStorage<'use-cache' | 'unstable-cache'>(),
620
+ );
621
+ // Tags accumulated by fetches/nested caches inside the currently-producing
622
+ // unstable_cache entry (see recordCacheTags / storeDataCacheEntry).
623
+ const producerTagsStorage = revalidateGlobal(
624
+ 'producerTagsStorage',
625
+ () => new AsyncLocalStorage<Set<string>>(),
626
+ );
627
+ // A dedicated marker for `unstable_cache` specifically (a subset of the general
628
+ // data-cache-producer scope, which `use cache` also enters). Root params read
629
+ // inside a `use cache` NESTED within an `unstable_cache` are E1140, which needs
630
+ // to tell the two producers apart — the general boolean cannot.
631
+ const insideUnstableCacheStorage = revalidateGlobal(
632
+ 'insideUnstableCacheStorage',
633
+ () => new AsyncLocalStorage<boolean>(),
634
+ );
635
+
636
+ /** Mark fn as running inside a data-cache producer (see recordCacheNoStore). */
637
+ export function runInsideDataCacheProducer<T>(
638
+ fn: () => T,
639
+ kind: 'use-cache' | 'unstable-cache' = 'use-cache',
640
+ ): T {
641
+ return producerKindStorage.run(kind, () => insideProducerStorage.run(true, fn));
642
+ }
643
+
644
+ /** Mark fn as running specifically inside an `unstable_cache` producer. */
645
+ export function runInsideUnstableCacheProducer<T>(fn: () => T): T {
646
+ return insideUnstableCacheStorage.run(true, () => runInsideDataCacheProducer(fn, 'unstable-cache'));
647
+ }
648
+
649
+ /** True while executing inside an unstable_cache / 'use cache' producer. */
650
+ export function insideDataCacheProducer(): boolean {
651
+ return insideProducerStorage.getStore() === true;
652
+ }
653
+
654
+ /** True while executing specifically inside an `unstable_cache` producer. */
655
+ export function insideUnstableCacheProducer(): boolean {
656
+ return insideUnstableCacheStorage.getStore() === true;
657
+ }
658
+
659
+ export function insidePublicUseCacheProducer(): boolean {
660
+ return producerKindStorage.getStore() === 'use-cache';
661
+ }
662
+
663
+ /**
664
+ * Fail a build prerender when a dynamic request API (`cookies()`/`headers()`/
665
+ * `connection()`) is invoked inside a function cached with `unstable_cache()`.
666
+ * Called from the compat request-API seams (next/headers, next/server). Records
667
+ * Next's exact E838 error so the build aborts (register-usecache) and throws so
668
+ * the offending fill unwinds. A no-op outside an armed build prerender and
669
+ * outside an unstable_cache producer, so serving-runtime/dev calls are untouched
670
+ * (there, the same APIs simply read live request data).
671
+ */
672
+ export function assertNoDynamicApiInsideCache(
673
+ api: 'cookies()' | 'headers()' | 'connection()',
674
+ ): void {
675
+ if (!insideDataCacheProducer()) return;
676
+ const route = cacheMetaStorage.getStore()?.route ?? '';
677
+ const unstable = insideUnstableCacheProducer();
678
+ const message = unstable
679
+ ? unstableCacheDynamicApiMessage(route, api)
680
+ : useCacheDynamicApiMessage(route, api);
681
+ // Build-time prerenders record the error for the build log; the throw itself
682
+ // is unconditional — Next rejects dynamic data inside a cache scope at
683
+ // request time too (500 / generic RSC error in prod).
684
+ if (prerenderErrorCollectionActive()) {
685
+ recordPrerenderError({
686
+ kind: unstable ? 'unstable-cache-dynamic' : 'use-cache-dynamic',
687
+ route,
688
+ consoleBlock: `Error: ${message}`,
689
+ });
690
+ }
691
+ throw new Error(message);
692
+ }
693
+
694
+ export async function collectRenderCacheMeta<T>(
695
+ produce: () => Promise<T>,
696
+ options: {
697
+ fetchCache?: string;
698
+ refreshFetches?: boolean;
699
+ blockingStaleFetches?: boolean;
700
+ route?: string;
701
+ prerender?: boolean;
702
+ handler?: boolean;
703
+ dynamicError?: boolean;
704
+ } = {},
705
+ ): Promise<{
706
+ value: T;
707
+ tags: string[];
708
+ revalidateSeconds?: number;
709
+ expireSeconds?: number;
710
+ staleSeconds?: number;
711
+ noStore?: boolean;
712
+ }> {
713
+ const meta: RenderCacheMeta = {
714
+ tags: new Set(),
715
+ fetchCache: options.fetchCache,
716
+ refreshFetches: options.refreshFetches,
717
+ blockingStaleFetches: options.blockingStaleFetches,
718
+ route: options.route,
719
+ prerender: options.prerender,
720
+ handler: options.handler,
721
+ dynamicError: options.dynamicError,
722
+ };
723
+ const value = await cacheMetaStorage.run(meta, produce);
724
+ await drainStaticAfterTasks(meta);
725
+ return {
726
+ value,
727
+ tags: [...meta.tags],
728
+ ...(meta.revalidateSeconds !== undefined ? { revalidateSeconds: meta.revalidateSeconds } : {}),
729
+ ...(meta.expireSeconds !== undefined ? { expireSeconds: meta.expireSeconds } : {}),
730
+ ...(meta.staleSeconds !== undefined ? { staleSeconds: meta.staleSeconds } : {}),
731
+ ...(meta.noStore ? { noStore: true } : {}),
732
+ };
733
+ }
734
+
735
+ /**
736
+ * Run the after() tasks queued during a static-generation render, re-draining
737
+ * so a task can enqueue further tasks (nested after()). Tasks run inside the
738
+ * meta scope so nested after() re-enters this queue. During a build prerender
739
+ * a rejection fails the page like Next: print Next's prerender-error line for
740
+ * the route and rethrow so the build skips/fails the page. Runtime
741
+ * regeneration tasks are queued pre-wrapped to log-and-continue, so a
742
+ * rejection here is only possible on the prerender path.
743
+ */
744
+ async function drainStaticAfterTasks(meta: RenderCacheMeta): Promise<void> {
745
+ while (meta.afterTasks?.length) {
746
+ const tasks = meta.afterTasks;
747
+ meta.afterTasks = [];
748
+ for (const task of tasks) {
749
+ try {
750
+ await cacheMetaStorage.run(meta, task);
751
+ } catch (error) {
752
+ console.error(
753
+ `Error occurred prerendering page "${meta.route ?? '/'}". Read more: https://nextjs.org/docs/messages/prerender-error`,
754
+ );
755
+ throw error;
756
+ }
757
+ }
758
+ }
759
+ }