vinext 1.2.0 → 1.3.0

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 (283) hide show
  1. package/README.md +13 -10
  2. package/dist/build/client-build-config.d.ts +64 -3
  3. package/dist/build/client-build-config.js +131 -4
  4. package/dist/build/client-reference-groups.d.ts +163 -0
  5. package/dist/build/client-reference-groups.js +534 -0
  6. package/dist/build/module-dependency-cache.d.ts +31 -0
  7. package/dist/build/module-dependency-cache.js +51 -1
  8. package/dist/build/next-client-runtime-manifests.d.ts +10 -1
  9. package/dist/build/next-client-runtime-manifests.js +19 -4
  10. package/dist/build/nitro-public-files.d.ts +24 -0
  11. package/dist/build/nitro-public-files.js +60 -0
  12. package/dist/build/output-root-externals.d.ts +21 -0
  13. package/dist/build/output-root-externals.js +40 -0
  14. package/dist/build/pages-client-assets-module.js +1 -1
  15. package/dist/build/prerender-paths.js +6 -3
  16. package/dist/build/prerender.js +1 -1
  17. package/dist/build/server-actions-encryption-key.d.ts +23 -0
  18. package/dist/build/server-actions-encryption-key.js +101 -0
  19. package/dist/build/ssr-manifest.d.ts +3 -0
  20. package/dist/build/ssr-manifest.js +10 -1
  21. package/dist/check.js +5 -6
  22. package/dist/cli-dev-config.js +3 -3
  23. package/dist/cli.js +1 -1
  24. package/dist/client/client-rewrite-params.d.ts +9 -0
  25. package/dist/client/client-rewrite-params.js +20 -0
  26. package/dist/client/dev-error-overlay.js +2 -1
  27. package/dist/client/navigation-runtime.d.ts +25 -2
  28. package/dist/client/navigation-runtime.js +42 -11
  29. package/dist/client/vinext-next-data.d.ts +0 -1
  30. package/dist/config/config-matchers.d.ts +15 -40
  31. package/dist/config/config-matchers.js +195 -262
  32. package/dist/config/config-source.d.ts +34 -0
  33. package/dist/config/config-source.js +141 -0
  34. package/dist/config/header-params.d.ts +29 -0
  35. package/dist/config/header-params.js +106 -0
  36. package/dist/config/next-config.d.ts +7 -5
  37. package/dist/config/next-config.js +120 -7
  38. package/dist/entries/app-browser-entry.d.ts +14 -1
  39. package/dist/entries/app-browser-entry.js +26 -36
  40. package/dist/entries/app-rsc-entry.d.ts +2 -0
  41. package/dist/entries/app-rsc-entry.js +92 -57
  42. package/dist/entries/app-rsc-manifest.d.ts +10 -0
  43. package/dist/entries/app-rsc-manifest.js +20 -1
  44. package/dist/entries/app-ssr-entry.d.ts +4 -1
  45. package/dist/entries/app-ssr-entry.js +9 -2
  46. package/dist/entries/pages-client-entry.d.ts +1 -0
  47. package/dist/entries/pages-client-entry.js +6 -6
  48. package/dist/entries/pages-server-entry.js +17 -14
  49. package/dist/index.d.ts +11 -0
  50. package/dist/index.js +350 -175
  51. package/dist/init.d.ts +1 -1
  52. package/dist/init.js +103 -6
  53. package/dist/plugins/ast-utils.d.ts +53 -2
  54. package/dist/plugins/ast-utils.js +90 -2
  55. package/dist/plugins/cloudflare-dev-module-urls.d.ts +41 -0
  56. package/dist/plugins/cloudflare-dev-module-urls.js +271 -0
  57. package/dist/plugins/commonjs-esm-facade.js +2 -2
  58. package/dist/plugins/css-module-imports.js +13 -20
  59. package/dist/plugins/dynamic-preload-metadata.js +2 -3
  60. package/dist/plugins/extensionless-dynamic-import.js +2 -3
  61. package/dist/plugins/fonts.d.ts +1 -1
  62. package/dist/plugins/fonts.js +3 -3
  63. package/dist/plugins/ignore-dynamic-requests.js +2 -3
  64. package/dist/plugins/import-meta-url.js +3 -4
  65. package/dist/plugins/instrumentation-client.js +3 -2
  66. package/dist/plugins/middleware-export-validation.js +2 -3
  67. package/dist/plugins/og-harfbuzz.js +2 -3
  68. package/dist/plugins/optimize-imports.d.ts +25 -2
  69. package/dist/plugins/optimize-imports.js +109 -24
  70. package/dist/plugins/pages-node-externals.js +2 -3
  71. package/dist/plugins/remove-console.js +2 -18
  72. package/dist/plugins/require-condition-resolution.js +3 -3
  73. package/dist/plugins/require-context.js +2 -3
  74. package/dist/plugins/rsc-client-reference-loaders.d.ts +45 -1
  75. package/dist/plugins/rsc-client-reference-loaders.js +336 -18
  76. package/dist/plugins/strip-server-exports.js +3 -4
  77. package/dist/plugins/styled-jsx.js +2 -3
  78. package/dist/plugins/use-cache-callable.d.ts +26 -2
  79. package/dist/plugins/use-cache-callable.js +101 -36
  80. package/dist/plugins/worker-image-imports.d.ts +1 -0
  81. package/dist/plugins/worker-image-imports.js +31 -34
  82. package/dist/plugins/worker-use-server.js +38 -14
  83. package/dist/routing/app-route-graph.d.ts +1 -1
  84. package/dist/routing/browser-route-manifest.d.ts +43 -0
  85. package/dist/routing/browser-route-manifest.js +195 -0
  86. package/dist/routing/hashed-route-patterns.d.ts +4 -0
  87. package/dist/routing/hashed-route-patterns.js +22 -0
  88. package/dist/routing/optimistic-route-trie.d.ts +27 -0
  89. package/dist/routing/optimistic-route-trie.js +102 -0
  90. package/dist/routing/route-validation.d.ts +6 -1
  91. package/dist/routing/route-validation.js +26 -10
  92. package/dist/server/api-handler.js +1 -1
  93. package/dist/server/app-action-forwarding.d.ts +8 -0
  94. package/dist/server/app-action-forwarding.js +17 -3
  95. package/dist/server/app-browser-entry.js +149 -62
  96. package/dist/server/app-browser-error.d.ts +3 -1
  97. package/dist/server/app-browser-error.js +7 -2
  98. package/dist/server/app-browser-interception-context.d.ts +2 -2
  99. package/dist/server/app-browser-interception-context.js +1 -1
  100. package/dist/server/app-browser-mpa-navigation.d.ts +1 -0
  101. package/dist/server/app-browser-mpa-navigation.js +6 -5
  102. package/dist/server/app-browser-navigation-controller.d.ts +17 -6
  103. package/dist/server/app-browser-navigation-controller.js +88 -26
  104. package/dist/server/app-browser-prefetch-response.js +2 -2
  105. package/dist/server/app-browser-state.d.ts +2 -2
  106. package/dist/server/app-browser-state.js +1 -1
  107. package/dist/server/app-browser-supplemental-refresh.js +1 -1
  108. package/dist/server/app-browser-visible-commit.d.ts +4 -4
  109. package/dist/server/app-client-reference-preloader.d.ts +23 -0
  110. package/dist/server/app-client-reference-preloader.js +30 -7
  111. package/dist/server/app-fallback-renderer.js +2 -1
  112. package/dist/server/app-middleware.d.ts +6 -0
  113. package/dist/server/app-middleware.js +8 -10
  114. package/dist/server/app-optimistic-routing.d.ts +8 -10
  115. package/dist/server/app-optimistic-routing.js +4 -95
  116. package/dist/server/app-page-boundary-render.js +8 -1
  117. package/dist/server/app-page-cache-render.js +1 -0
  118. package/dist/server/app-page-dispatch.js +3 -1
  119. package/dist/server/app-page-render.js +4 -2
  120. package/dist/server/app-page-rsc-render-status.d.ts +1 -0
  121. package/dist/server/app-page-rsc-render-status.js +1 -0
  122. package/dist/server/app-page-search-params-observation.d.ts +1 -4
  123. package/dist/server/app-page-search-params-observation.js +8 -10
  124. package/dist/server/app-page-stream.d.ts +7 -0
  125. package/dist/server/app-page-stream.js +1 -0
  126. package/dist/server/app-pages-bridge.d.ts +2 -1
  127. package/dist/server/app-pages-bridge.js +12 -8
  128. package/dist/server/app-prerender-endpoints.js +1 -1
  129. package/dist/server/app-prerender-static-params.js +3 -2
  130. package/dist/server/app-render-abort-error.d.ts +19 -0
  131. package/dist/server/app-render-abort-error.js +31 -1
  132. package/dist/server/app-render-dependency.js +42 -2
  133. package/dist/server/app-request-stage-independent-entry.js +7 -7
  134. package/dist/server/app-response-stage-entry.js +5 -5
  135. package/dist/server/app-route-handler-dispatch.js +3 -1
  136. package/dist/server/app-route-handler-execution.d.ts +2 -0
  137. package/dist/server/app-route-handler-execution.js +9 -2
  138. package/dist/server/app-route-handler-middleware-context.d.ts +2 -1
  139. package/dist/server/app-route-handler-middleware-context.js +3 -3
  140. package/dist/server/app-route-handler-response.d.ts +2 -1
  141. package/dist/server/app-route-handler-response.js +2 -2
  142. package/dist/server/app-route-handler-runtime.d.ts +2 -0
  143. package/dist/server/app-route-handler-runtime.js +21 -4
  144. package/dist/server/app-route-module-loader.js +7 -6
  145. package/dist/server/app-route-tree.d.ts +20 -0
  146. package/dist/server/app-route-tree.js +18 -0
  147. package/dist/server/app-router-entry.js +7 -7
  148. package/dist/server/app-rsc-cache-busting.d.ts +16 -0
  149. package/dist/server/app-rsc-cache-busting.js +29 -12
  150. package/dist/server/app-rsc-combined-handler.js +8 -1
  151. package/dist/server/app-rsc-errors.d.ts +7 -0
  152. package/dist/server/app-rsc-errors.js +9 -2
  153. package/dist/server/app-rsc-handler.d.ts +4 -1
  154. package/dist/server/app-rsc-handler.js +151 -135
  155. package/dist/server/app-rsc-request-normalization.js +3 -3
  156. package/dist/server/app-rsc-response-finalizer.d.ts +5 -0
  157. package/dist/server/app-rsc-response-finalizer.js +4 -1
  158. package/dist/server/app-rsc-response-stage.js +5 -3
  159. package/dist/server/app-rsc-route-matching.d.ts +1 -0
  160. package/dist/server/app-rsc-route-matching.js +1 -1
  161. package/dist/server/app-server-action-execution.d.ts +23 -6
  162. package/dist/server/app-server-action-execution.js +51 -57
  163. package/dist/server/app-ssr-entry.d.ts +6 -0
  164. package/dist/server/app-ssr-entry.js +21 -11
  165. package/dist/server/app-ssr-stream.d.ts +7 -0
  166. package/dist/server/app-ssr-stream.js +13 -1
  167. package/dist/server/app-visited-response-cache.js +2 -2
  168. package/dist/server/config-headers.js +13 -4
  169. package/dist/server/defer-until-stream-consumed.d.ts +2 -1
  170. package/dist/server/defer-until-stream-consumed.js +3 -1
  171. package/dist/server/dev-server.js +1 -1
  172. package/dist/server/image-optimization.js +1 -0
  173. package/dist/server/isolate-shared-promise.d.ts +10 -0
  174. package/dist/server/isolate-shared-promise.js +33 -0
  175. package/dist/server/isr-cache.js +3 -2
  176. package/dist/server/metadata-route-response.js +2 -2
  177. package/dist/server/middleware-matcher-pattern.d.ts +0 -1
  178. package/dist/server/middleware-matcher-pattern.js +11 -140
  179. package/dist/server/middleware-matcher.js +5 -6
  180. package/dist/server/middleware-response-headers.d.ts +31 -0
  181. package/dist/server/middleware-response-headers.js +57 -1
  182. package/dist/server/middleware-runtime.js +30 -17
  183. package/dist/server/navigation-planner.d.ts +2 -2
  184. package/dist/server/navigation-planner.js +1 -1
  185. package/dist/server/nitro-public-files-plugin.d.ts +38 -0
  186. package/dist/server/nitro-public-files-plugin.js +106 -0
  187. package/dist/server/nitro-public-files.d.ts +21 -0
  188. package/dist/server/nitro-public-files.js +39 -0
  189. package/dist/server/open-redirect.d.ts +11 -4
  190. package/dist/server/open-redirect.js +11 -4
  191. package/dist/server/pages-api-route.js +2 -2
  192. package/dist/server/pages-asset-tags.d.ts +5 -0
  193. package/dist/server/pages-asset-tags.js +9 -2
  194. package/dist/server/pages-client-assets.d.ts +4 -1
  195. package/dist/server/pages-data-route.js +2 -1
  196. package/dist/server/pages-i18n.d.ts +18 -0
  197. package/dist/server/pages-i18n.js +18 -2
  198. package/dist/server/pages-node-compat.d.ts +5 -3
  199. package/dist/server/pages-node-compat.js +35 -16
  200. package/dist/server/pages-page-data.js +1 -1
  201. package/dist/server/pages-page-handler.js +1 -1
  202. package/dist/server/pages-page-response.js +1 -1
  203. package/dist/server/pages-request-pipeline.d.ts +6 -1
  204. package/dist/server/pages-request-pipeline.js +35 -39
  205. package/dist/server/pages-request-stage-entry.js +20 -10
  206. package/dist/server/pages-response-stage-entry.js +1 -1
  207. package/dist/server/pages-revalidate.js +3 -2
  208. package/dist/server/pages-router-entry.js +2 -2
  209. package/dist/server/prod-server.d.ts +2 -2
  210. package/dist/server/prod-server.js +74 -36
  211. package/dist/server/proxy-trust.d.ts +6 -0
  212. package/dist/server/proxy-trust.js +12 -6
  213. package/dist/server/request-pipeline.d.ts +5 -0
  214. package/dist/server/request-pipeline.js +12 -1
  215. package/dist/server/rsc-stream-hints.d.ts +1 -1
  216. package/dist/server/rsc-stream-hints.js +21 -3
  217. package/dist/server/server-reference-loader.js +10 -1
  218. package/dist/server/static-file-cache.js +15 -3
  219. package/dist/server/worker-image-optimization.js +19 -6
  220. package/dist/server/worker-utils.js +1 -1
  221. package/dist/shims/cache-handler.js +3 -2
  222. package/dist/shims/cache-request-state.d.ts +40 -3
  223. package/dist/shims/cache-request-state.js +70 -5
  224. package/dist/shims/cache-runtime.js +47 -19
  225. package/dist/shims/cache.d.ts +2 -2
  226. package/dist/shims/cache.js +17 -8
  227. package/dist/shims/cdn-purge-routes.d.ts +24 -0
  228. package/dist/shims/cdn-purge-routes.js +77 -0
  229. package/dist/shims/dynamic.js +17 -4
  230. package/dist/shims/form.js +8 -3
  231. package/dist/shims/head.js +1 -1
  232. package/dist/shims/headers.js +2 -2
  233. package/dist/shims/image-props.d.ts +93 -0
  234. package/dist/shims/image-props.js +323 -0
  235. package/dist/shims/image.d.ts +4 -18
  236. package/dist/shims/image.js +4 -319
  237. package/dist/shims/internal/headers-state.d.ts +7 -0
  238. package/dist/shims/internal/headers-state.js +14 -2
  239. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +4 -1
  240. package/dist/shims/internal/hybrid-client-route-owner-direct.js +32 -15
  241. package/dist/shims/internal/hybrid-client-route-owner.js +9 -8
  242. package/dist/shims/link-prefetch.js +5 -4
  243. package/dist/shims/link.js +7 -5
  244. package/dist/shims/navigation.d.ts +1 -1
  245. package/dist/shims/navigation.js +21 -10
  246. package/dist/shims/og.js +35 -10
  247. package/dist/shims/request-context.d.ts +13 -0
  248. package/dist/shims/router.js +28 -52
  249. package/dist/shims/server.d.ts +42 -8
  250. package/dist/shims/server.js +99 -15
  251. package/dist/shims/thenable-params.d.ts +6 -1
  252. package/dist/shims/thenable-params.js +11 -2
  253. package/dist/shims/unified-request-context.d.ts +5 -0
  254. package/dist/shims/unified-request-context.js +2 -0
  255. package/dist/shims/url-utils.d.ts +7 -0
  256. package/dist/shims/url-utils.js +12 -3
  257. package/dist/utils/asset-prefix.d.ts +7 -0
  258. package/dist/utils/asset-prefix.js +10 -1
  259. package/dist/utils/client-build-manifest.js +12 -5
  260. package/dist/utils/client-runtime-metadata.d.ts +1 -0
  261. package/dist/utils/client-runtime-metadata.js +15 -2
  262. package/dist/utils/domain-locale.js +1 -1
  263. package/dist/utils/lazy-chunks.d.ts +4 -0
  264. package/dist/utils/manifest-paths.d.ts +14 -0
  265. package/dist/utils/manifest-paths.js +22 -1
  266. package/dist/utils/path-to-regexp.d.ts +21 -0
  267. package/dist/{server/middleware-path-to-regexp.js → utils/path-to-regexp.js} +14 -11
  268. package/dist/utils/plugin-react-version.d.ts +15 -0
  269. package/dist/utils/plugin-react-version.js +64 -0
  270. package/dist/utils/plugin-rsc-version.d.ts +2 -0
  271. package/dist/utils/plugin-rsc-version.js +28 -2
  272. package/dist/utils/public-routes.d.ts +2 -0
  273. package/dist/utils/public-routes.js +18 -7
  274. package/dist/utils/react-version.d.ts +7 -0
  275. package/dist/utils/react-version.js +39 -16
  276. package/package.json +6 -6
  277. package/dist/build/nitro-route-rules.d.ts +0 -47
  278. package/dist/build/nitro-route-rules.js +0 -79
  279. package/dist/plugins/cache-flight-codec.d.ts +0 -19
  280. package/dist/plugins/cache-flight-codec.js +0 -68
  281. package/dist/server/middleware-path-to-regexp.d.ts +0 -13
  282. package/dist/utils/regex-safety.d.ts +0 -17
  283. package/dist/utils/regex-safety.js +0 -789
@@ -1,9 +1,7 @@
1
- import { isNextStaticPath } from "../utils/asset-prefix.js";
2
- import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
3
1
  import { VINEXT_PRERENDER_SPECIAL_ERROR_HEADER } from "./headers.js";
4
- import { normalizePath } from "./normalize-path.js";
5
2
  import { readStaticFileSignal, readStaticFileSignalRequestHeaders } from "./static-file-signal.js";
6
3
  import { attachRequestCfMetadata, cloneRequestWithUrl } from "./request-pipeline.js";
4
+ import { isNextStaticPath } from "../utils/asset-prefix.js";
7
5
  import { handleConfiguredImageOptimization, invalidImageSourceResponse, isImageSourceBodyStatus } from "./image-optimization.js";
8
6
  import { createStaticAssetRequest, mergeHeaders } from "./worker-utils.js";
9
7
  import { resolveClientBuildFile } from "./client-build-files.js";
@@ -28,19 +26,34 @@ function cancelResponseBody(response) {
28
26
  body.cancel().catch(() => {});
29
27
  }
30
28
  /**
29
+ * Return the pathname of the `_next/static` file a source's decoded pathname
30
+ * names, or null. Next.js matches the decoded pathname against its
31
+ * `.next/static` files literally, so a `.`, `..` or empty segment names no
32
+ * file. The asset binding decodes each segment once, so each segment is
33
+ * encoded once from the name checked here. Parsing the decoded pathname as a
34
+ * URL again would resolve a segment that decoded to `%2E%2E` as `..`, outside
35
+ * `_next/static`.
36
+ */
37
+ function resolveNextStaticFile(decodedPathname, options) {
38
+ if (!isNextStaticPath(decodedPathname, options.basePath, options.assetPathPrefix)) return null;
39
+ const segments = decodedPathname.split("/").slice(1);
40
+ if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) return null;
41
+ return "/" + segments.map(encodeURIComponent).join("/");
42
+ }
43
+ /**
31
44
  * Fetch a local image source. Build output is fetched from the asset binding,
32
45
  * which serves it before the Worker and its middleware. Any other source is
33
46
  * served only when the application pipeline selects a public file for it.
34
47
  */
35
48
  async function fetchWorkerImageSource(imageUrl, request, options) {
36
49
  const sourceUrl = new URL(imageUrl, request.url);
37
- let sourcePathname;
50
+ let decodedPathname;
38
51
  try {
39
- sourcePathname = normalizePath(normalizePathnameForRouteMatchStrict(sourceUrl.pathname));
52
+ decodedPathname = decodeURIComponent(sourceUrl.pathname);
40
53
  } catch {
41
54
  return invalidImageSourceResponse();
42
55
  }
43
- const buildAssetPath = isNextStaticPath(sourcePathname, options.basePath, options.assetPathPrefix) ? sourcePathname : resolveClientBuildFile(sourceUrl.pathname, options.basePath, options.clientBuildFiles);
56
+ const buildAssetPath = resolveNextStaticFile(decodedPathname, options) ?? resolveClientBuildFile(sourceUrl.pathname, options.basePath, options.clientBuildFiles);
44
57
  if (buildAssetPath !== null) return options.assets.fetch(new Request(new URL(buildAssetPath, request.url)));
45
58
  const sourceResponse = await options.fetchPublicFile(attachRequestCfMetadata(new Request(sourceUrl, { signal: request.signal }), request));
46
59
  if (sourceResponse && isImageSourceBodyStatus(sourceResponse.status)) return sourceResponse;
@@ -108,7 +108,7 @@ async function resolveStaticAssetSignal(signalResponse, options) {
108
108
  * overrides, so a removed `Range` or replaced validator reaches the asset fetch.
109
109
  */
110
110
  function createStaticAssetRequest(assetPath, sourceRequest, headers = null) {
111
- const assetUrl = new URL(assetPath, sourceRequest.url);
111
+ const assetUrl = new URL(assetPath.replace(/[?#]/g, (character) => encodeURIComponent(character)), sourceRequest.url);
112
112
  if (sourceRequest.method === "GET" || sourceRequest.method === "HEAD") return headers ? new Request(new Request(assetUrl, sourceRequest), { headers }) : new Request(assetUrl, sourceRequest);
113
113
  return new Request(assetUrl, {
114
114
  method: sourceRequest.method,
@@ -1,3 +1,4 @@
1
+ import { shareIsolatePromise } from "../server/isolate-shared-promise.js";
1
2
  import { readCacheControlNumberField, readCacheControlRevalidateField } from "../utils/cache-control-metadata.js";
2
3
  //#region src/shims/cache-handler.ts
3
4
  var NoOpCacheHandler = class {
@@ -167,7 +168,7 @@ var LazyDataCacheHandler = class {
167
168
  this.load = load;
168
169
  }
169
170
  resolve() {
170
- return this.resolved ??= this.load().then(() => {
171
+ return this.resolved ??= shareIsolatePromise(this.load().then(() => {
171
172
  const registered = globalHandlers[HANDLER_KEY];
172
173
  if (registered && registered !== this) {
173
174
  this.loadedHandler = registered;
@@ -179,7 +180,7 @@ var LazyDataCacheHandler = class {
179
180
  delete globalHandlers[LAZY_HANDLER_KEY];
180
181
  this.loadedHandler = fallback;
181
182
  return fallback;
182
- });
183
+ }));
183
184
  }
184
185
  async get(key, ctx) {
185
186
  return (await this.resolve()).get(key, ctx);
@@ -23,9 +23,19 @@ export type UnstableCacheObservation = Readonly<{
23
23
  export type CacheLifeSink = {
24
24
  cacheLife: CacheLifeConfig | null;
25
25
  };
26
+ type TagInvalidationDurations = {
27
+ expire?: number;
28
+ } | undefined;
29
+ /** A CDN purge waiting for the request boundary, after its data-cache invalidation. */
30
+ export type PendingCdnTagInvalidation = Readonly<{
31
+ tag: string;
32
+ durations: TagInvalidationDurations;
33
+ dataInvalidation: Promise<void>;
34
+ }>;
26
35
  export type CacheState = {
27
36
  actionRevalidationKind: ActionRevalidationKind;
28
37
  cacheLifeSink: CacheLifeSink | null;
38
+ pendingCdnTagInvalidations: PendingCdnTagInvalidation[];
29
39
  pendingRevalidatedTags: Set<string>;
30
40
  pendingRevalidations: Set<Promise<void>>;
31
41
  requestScopedCacheLife: CacheLifeConfig | null;
@@ -56,9 +66,36 @@ export declare function _hasPendingRevalidatedTag(tags: readonly string[]): bool
56
66
  */
57
67
  export declare function _queuePendingRevalidation(promise: Promise<void>): boolean;
58
68
  /**
59
- * Await and clear every cache invalidation queued in the current request.
60
- * Clearing before awaiting also lets a later drain observe work enqueued by
61
- * an async continuation while this batch is settling.
69
+ * Defer the CDN purge for `tag` to the current Server Action or Route Handler
70
+ * boundary, so the request sends one adapter call per cacheLife profile like
71
+ * Next.js' `executeRevalidates`. Edge purges are rate limited (Workers Cache
72
+ * allows 5 requests a minute per account), so per-call purges run out fast.
73
+ *
74
+ * Only those two phases defer: their executors always drain before the
75
+ * request finishes. Once the response has closed, `after()` callbacks run
76
+ * past the last drain, so they purge immediately too. Everywhere else returns
77
+ * false and the caller purges immediately.
78
+ *
79
+ * @internal
80
+ */
81
+ export declare function _queuePendingCdnTagInvalidation(tag: string, durations: TagInvalidationDurations, dataInvalidation: Promise<void>): boolean;
82
+ /**
83
+ * Ask the CDN adapter to invalidate `tags`, logging rather than rethrowing a
84
+ * failure. The data cache is already invalidated by then, so a failed edge
85
+ * purge leaves edge-cached responses stale until they expire, and failing the
86
+ * request would not change that. It would only report a committed Server
87
+ * Action write as a failure.
88
+ *
89
+ * @internal
90
+ */
91
+ export declare function _revalidateCdnTags(tags: string | string[], durations?: {
92
+ expire?: number;
93
+ }): Promise<void>;
94
+ /**
95
+ * Await and clear every cache invalidation queued in the current request,
96
+ * then send the deferred CDN purges in batches. Clearing before awaiting also
97
+ * lets a later drain observe work enqueued by an async continuation while
98
+ * this batch is settling.
62
99
  *
63
100
  * @internal
64
101
  */
@@ -1,5 +1,6 @@
1
1
  import { getOrCreateAls } from "./internal/als-registry.js";
2
2
  import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation } from "./unified-request-context.js";
3
+ import { getCdnCacheAdapter } from "./cdn-cache.js";
3
4
  import { getHeadersAccessPhase } from "./internal/headers-state.js";
4
5
  import { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext } from "./internal/cache-context-accessor.js";
5
6
  //#region src/shims/cache-request-state.ts
@@ -48,6 +49,7 @@ const ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;
48
49
  const fallbackState = globalState[FALLBACK_KEY] ??= {
49
50
  actionRevalidationKind: ACTION_DID_NOT_REVALIDATE,
50
51
  cacheLifeSink: null,
52
+ pendingCdnTagInvalidations: [],
51
53
  pendingRevalidatedTags: /* @__PURE__ */ new Set(),
52
54
  pendingRevalidations: /* @__PURE__ */ new Set(),
53
55
  requestScopedCacheLife: null,
@@ -69,6 +71,7 @@ function _runWithCacheState(fn) {
69
71
  const state = {
70
72
  actionRevalidationKind: ACTION_DID_NOT_REVALIDATE,
71
73
  cacheLifeSink: null,
74
+ pendingCdnTagInvalidations: [],
72
75
  pendingRevalidatedTags: /* @__PURE__ */ new Set(),
73
76
  pendingRevalidations: /* @__PURE__ */ new Set(),
74
77
  requestScopedCacheLife: null,
@@ -128,9 +131,69 @@ function _queuePendingRevalidation(promise) {
128
131
  return true;
129
132
  }
130
133
  /**
131
- * Await and clear every cache invalidation queued in the current request.
132
- * Clearing before awaiting also lets a later drain observe work enqueued by
133
- * an async continuation while this batch is settling.
134
+ * Defer the CDN purge for `tag` to the current Server Action or Route Handler
135
+ * boundary, so the request sends one adapter call per cacheLife profile like
136
+ * Next.js' `executeRevalidates`. Edge purges are rate limited (Workers Cache
137
+ * allows 5 requests a minute per account), so per-call purges run out fast.
138
+ *
139
+ * Only those two phases defer: their executors always drain before the
140
+ * request finishes. Once the response has closed, `after()` callbacks run
141
+ * past the last drain, so they purge immediately too. Everywhere else returns
142
+ * false and the caller purges immediately.
143
+ *
144
+ * @internal
145
+ */
146
+ function _queuePendingCdnTagInvalidation(tag, durations, dataInvalidation) {
147
+ const phase = getHeadersAccessPhase();
148
+ if (phase !== "action" && phase !== "route-handler") return false;
149
+ if (isInsideUnifiedScope() && getRequestContext().afterContext.responseClosed) return false;
150
+ getCacheState().pendingCdnTagInvalidations.push({
151
+ tag,
152
+ durations,
153
+ dataInvalidation
154
+ });
155
+ return true;
156
+ }
157
+ /**
158
+ * Ask the CDN adapter to invalidate `tags`, logging rather than rethrowing a
159
+ * failure. The data cache is already invalidated by then, so a failed edge
160
+ * purge leaves edge-cached responses stale until they expire, and failing the
161
+ * request would not change that. It would only report a committed Server
162
+ * Action write as a failure.
163
+ *
164
+ * @internal
165
+ */
166
+ async function _revalidateCdnTags(tags, durations) {
167
+ try {
168
+ await getCdnCacheAdapter().revalidateTag(tags, durations);
169
+ } catch (error) {
170
+ console.error("[vinext] CDN cache invalidation failed; edge-cached responses may stay stale until they expire:", error);
171
+ }
172
+ }
173
+ async function flushCdnTagInvalidations(invalidations) {
174
+ if (invalidations.length === 0) return;
175
+ const settled = await Promise.allSettled(invalidations.map(({ dataInvalidation }) => dataInvalidation));
176
+ const batches = /* @__PURE__ */ new Map();
177
+ invalidations.forEach(({ tag, durations }, index) => {
178
+ if (settled[index].status === "rejected") return;
179
+ const key = durations === void 0 ? "" : JSON.stringify(durations);
180
+ let batch = batches.get(key);
181
+ if (!batch) {
182
+ batch = {
183
+ durations,
184
+ tags: /* @__PURE__ */ new Set()
185
+ };
186
+ batches.set(key, batch);
187
+ }
188
+ batch.tags.add(tag);
189
+ });
190
+ await Promise.all([...batches.values()].map(({ durations, tags }) => _revalidateCdnTags([...tags], durations)));
191
+ }
192
+ /**
193
+ * Await and clear every cache invalidation queued in the current request,
194
+ * then send the deferred CDN purges in batches. Clearing before awaiting also
195
+ * lets a later drain observe work enqueued by an async continuation while
196
+ * this batch is settling.
134
197
  *
135
198
  * @internal
136
199
  */
@@ -138,14 +201,16 @@ async function _drainPendingRevalidations() {
138
201
  const state = getCacheState();
139
202
  let didReject = false;
140
203
  let firstRejection;
141
- while (state.pendingRevalidations.size > 0) {
204
+ while (state.pendingRevalidations.size > 0 || state.pendingCdnTagInvalidations.length > 0) {
142
205
  const pending = [...state.pendingRevalidations];
143
206
  state.pendingRevalidations.clear();
207
+ const cdnInvalidations = state.pendingCdnTagInvalidations.splice(0);
144
208
  const results = await Promise.allSettled(pending);
145
209
  for (const result of results) if (result.status === "rejected" && !didReject) {
146
210
  didReject = true;
147
211
  firstRejection = result.reason;
148
212
  }
213
+ await flushCdnTagInvalidations(cdnInvalidations);
149
214
  }
150
215
  if (didReject) throw firstRejection;
151
216
  }
@@ -220,4 +285,4 @@ function shouldServeStaleUnstableCacheEntry() {
220
285
  return getCacheState().unstableCacheRevalidation === "background";
221
286
  }
222
287
  //#endregion
223
- export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, getRegisteredCacheContext, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry };
288
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, getRegisteredCacheContext, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry };
@@ -3,17 +3,19 @@ import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation }
3
3
  import { VINEXT_RSC_MARKER_HEADER } from "../server/headers.js";
4
4
  import { workUnitAsyncStorage } from "./internal/work-unit-async-storage.js";
5
5
  import { isRouteCacheabilityProbe, recordRouteCacheabilityProbeBailout } from "./cacheability-classification.js";
6
+ import { shareIsolatePromise } from "../server/isolate-shared-promise.js";
6
7
  import { getDataCacheHandler } from "./cache-handler.js";
7
8
  import { addCollectedRequestTags, getCurrentFetchSoftTags, runWithDetachedFetchObservations } from "./fetch-cache-state.js";
8
9
  import { suppressHangingPromiseAbortRejections } from "./internal/make-hanging-promise.js";
9
10
  import { createPprFallbackShellSuspensePromise, trackPprFallbackShellCacheTask } from "./ppr-fallback-shell.js";
10
- import { markDynamicUsage, throwIfInsideCacheScope } from "./internal/headers-state.js";
11
+ import { createUseCacheAccessError, markDynamicUsage, recordInvalidDynamicUsageError } from "./internal/headers-state.js";
11
12
  import { isDraftModeEnabled } from "./headers.js";
12
13
  import { _registerCacheContextAccessor } from "./internal/cache-context-accessor.js";
13
14
  import { _hasPendingRevalidatedTag, _setRequestScopedCacheLife, cacheLifeProfiles, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
14
15
  import "./fetch-cache.js";
15
16
  import { hasUseCacheLayoutMarker, hasUseCachePageMarker, isUseCacheFunctionReference, withoutUseCacheSegmentMarker } from "./internal/app-page-props-cache-key.js";
16
- import { getFallbackParamsSnapshot, makeThenableParams, restoreThenableParams } from "./thenable-params.js";
17
+ import { isWellKnownProperty } from "./internal/thenable-well-known-properties.js";
18
+ import { getFallbackParamsSnapshot, restoreThenableParams } from "./thenable-params.js";
17
19
  import { getCurrentRootParams } from "./root-params.js";
18
20
  import { flightArgumentsKey, restoreFlightReply, snapshotFlightReply } from "./cache-flight-arguments.js";
19
21
  import { cache } from "react";
@@ -132,7 +134,7 @@ function buildUseCacheKey(id, keySeed, argsKey) {
132
134
  }
133
135
  let _rscModule;
134
136
  function getRscModule() {
135
- return _rscModule ??= import("@vitejs/plugin-rsc/react/rsc");
137
+ return _rscModule ??= shareIsolatePromise(import("@vitejs/plugin-rsc/react/rsc"));
136
138
  }
137
139
  /** Collect a ReadableStream<Uint8Array> into a single Uint8Array. */
138
140
  async function collectStream(stream) {
@@ -365,9 +367,9 @@ function registerCachedFunction(fn, id, variant, options = {}) {
365
367
  ...props,
366
368
  params: restoreThenableParams(await props.params, outerParams)
367
369
  });
368
- if (pagePropsIndex !== void 0) callArgs = withErroringPageSearchParams(callArgs, pagePropsIndex);
369
370
  }
370
- if (fallbackParams) return (await executeWithContext(fn, callArgs, cacheVariant, rsc)).result;
371
+ const callFn = segmentPropsIndex !== -1 && pagePropsIndex !== void 0 ? withErroringPageSearchParams(fn, pagePropsIndex) : fn;
372
+ if (fallbackParams) return (await executeWithContext(callFn, callArgs, cacheVariant, rsc)).result;
371
373
  if (cacheVariant === "private") {
372
374
  const privateCache = _getPrivateState()._privateCache;
373
375
  const privateHit = privateCache.get(cacheKey);
@@ -375,11 +377,11 @@ function registerCachedFunction(fn, id, variant, options = {}) {
375
377
  if (privateHit.serialized) return rsc.createFromReadableStream(uint8ToStream(base64ToUint8(privateHit.serialized.body)), {}, { preserveServerReferences: true });
376
378
  return privateHit.result;
377
379
  }
378
- const execution = await executeWithContext(fn, callArgs, cacheVariant, rsc);
380
+ const execution = await executeWithContext(callFn, callArgs, cacheVariant, rsc);
379
381
  if (execution.cacheable) privateCache.set(cacheKey, execution);
380
382
  return execution.result;
381
383
  }
382
- if (isDev || isDraftModeEnabled()) return (await executeWithContext(fn, callArgs, cacheVariant, rsc)).result;
384
+ if (isDev || isDraftModeEnabled()) return (await executeWithContext(callFn, callArgs, cacheVariant, rsc)).result;
383
385
  const handler = getDataCacheHandler();
384
386
  const rootParams = getCurrentRootParams();
385
387
  const knownRootParamNames = knownRootParamsByFunctionId.get(id);
@@ -409,7 +411,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
409
411
  }
410
412
  }
411
413
  const generate = async (propagate) => {
412
- const { result, ctx, effectiveLife, collectedResult } = await runCachedFunctionWithContext(fn, callArgs, cacheVariant, (value) => serializeCacheResult(value, rsc));
414
+ const { result, ctx, effectiveLife, collectedResult } = await runCachedFunctionWithContext(callFn, callArgs, cacheVariant, (value) => serializeCacheResult(value, rsc));
413
415
  const rootParamNames = ctx.readRootParamNames && ctx.readRootParamNames.size > 0 ? addKnownRootParamNames(id, ctx.readRootParamNames) : knownRootParamsByFunctionId.get(id);
414
416
  if (propagate) {
415
417
  recordRequestScopedCacheLife(effectiveLife);
@@ -631,18 +633,44 @@ function toReplayablePageArgs(args, index) {
631
633
  return replaceArgument(args, index, pageProps);
632
634
  }
633
635
  /**
634
- * Give page props at `index` without `searchParams` (a Response Store replay of
635
- * args encoded by `toReplayablePageArgs`) a value that throws on
636
- * access inside the cache scope, like Next.js's
637
- * `makeErroringSearchParamsForUseCache`, instead of `undefined`.
636
+ * Run `fn` with an erroring `searchParams` in the page props at `index` when
637
+ * they have none (they are omitted from a public page cache's arguments, and
638
+ * from a Response Store replay of them), instead of `undefined`.
638
639
  */
639
- function withErroringPageSearchParams(args, index) {
640
- const props = args[index];
641
- if (!isPagePropsObject(props) || "searchParams" in props) return args;
642
- return replaceArgument(args, index, {
643
- ...props,
644
- searchParams: makeThenableParams({}, { observeParamAccess: () => throwIfInsideCacheScope("searchParams") })
645
- });
640
+ function withErroringPageSearchParams(fn, index) {
641
+ return (...args) => {
642
+ const props = args[index];
643
+ if (!isPagePropsObject(props) || "searchParams" in props) return fn(...args);
644
+ return fn(...replaceArgument(args, index, {
645
+ ...props,
646
+ searchParams: makeErroringPageSearchParams()
647
+ }));
648
+ };
649
+ }
650
+ function isNextWellKnownProperty(prop) {
651
+ return prop !== "value" && prop !== "error" && isWellKnownProperty(prop);
652
+ }
653
+ /**
654
+ * Ported from Next.js's `makeErroringSearchParamsForUseCache`
655
+ * (server/request/search-params.ts): reading `then` or a param throws, and
656
+ * everything else reads the underlying promise, with methods bound to it.
657
+ */
658
+ function makeErroringPageSearchParams() {
659
+ const cacheContext = cacheContextStorage.getStore() ?? null;
660
+ const requestContext = isInsideUnifiedScope() ? getRequestContext() : null;
661
+ const promise = Promise.resolve({});
662
+ return new Proxy(promise, { get(target, prop, receiver) {
663
+ if (!Object.hasOwn(target, prop) && typeof prop === "string" && (prop === "then" || !isNextWellKnownProperty(prop))) {
664
+ const error = createUseCacheAccessError("searchParams");
665
+ if (cacheContext) cacheContext.invalidDynamicUsageError ??= error;
666
+ const activeCacheContext = cacheContextStorage.getStore();
667
+ if (activeCacheContext && activeCacheContext !== cacheContext) activeCacheContext.invalidDynamicUsageError ??= error;
668
+ if (requestContext) recordInvalidDynamicUsageError(requestContext, error);
669
+ throw error;
670
+ }
671
+ const value = Reflect.get(target, prop, receiver);
672
+ return typeof value === "function" ? value.bind(target) : value;
673
+ } });
646
674
  }
647
675
  //#endregion
648
676
  export { NestedDynamicUseCacheError, buildUseCacheKey, cacheContextStorage, clearPrivateCache, getCacheContext, isUseCacheFunction, memoizeInCacheScope, registerCachedFunction, replayCachedFunction, runWithPrivateCache };
@@ -1,6 +1,6 @@
1
1
  import { ExecutionContextLike, getRequestExecutionContext, runWithExecutionContext } from "./request-context.js";
2
2
  import { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext } from "./internal/cache-context-accessor.js";
3
- import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheLifeConfig, CacheLifeSink, CacheState, UnstableCacheObservation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
3
+ import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheLifeConfig, CacheLifeSink, CacheState, PendingCdnTagInvalidation, UnstableCacheObservation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
4
4
  import { CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, configureMemoryCacheHandler, getCacheHandler, getDataCacheHandler, registerDataCacheHandler, registerLazyDataCacheHandler, setCacheHandler, setDataCacheHandler } from "./cache-handler.js";
5
5
  //#region src/shims/cache.d.ts
6
6
  /**
@@ -142,4 +142,4 @@ type UnstableCacheOptions = {
142
142
  */
143
143
  export declare function unstable_cache<T extends (...args: any[]) => Promise<any>>(fn: T, keyParts?: string[], options?: UnstableCacheOptions): T;
144
144
  //#endregion
145
- export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CacheLifeConfig, CacheLifeSink, CacheState, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, type ExecutionContextLike, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, UnstableCacheObservation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, markActionRevalidation, unstable_noStore as noStore, recordUnstableCacheObservation, registerDataCacheHandler, registerLazyDataCacheHandler, runWithDetachedCacheObservations, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry };
145
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CacheLifeConfig, CacheLifeSink, CacheState, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, type ExecutionContextLike, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, PendingCdnTagInvalidation, UnstableCacheObservation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, markActionRevalidation, unstable_noStore as noStore, recordUnstableCacheObservation, registerDataCacheHandler, registerLazyDataCacheHandler, runWithDetachedCacheObservations, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry };
@@ -3,7 +3,6 @@ import { getRequestContext, isInsideUnifiedScope } from "./unified-request-conte
3
3
  import { getRequestExecutionContext, runWithExecutionContext } from "./request-context.js";
4
4
  import { isInsideWorkStoreScope } from "./internal/work-store-scope.js";
5
5
  import { workUnitAsyncStorage } from "./internal/work-unit-async-storage.js";
6
- import { getCdnCacheAdapter } from "./cdn-cache.js";
7
6
  import { isStagedCacheabilityProbeActive } from "./cacheability-classification.js";
8
7
  import { MemoryCacheHandler, NoOpCacheHandler, configureMemoryCacheHandler, getCacheHandler, getDataCacheHandler, registerDataCacheHandler, registerLazyDataCacheHandler, setCacheHandler, setDataCacheHandler } from "./cache-handler.js";
9
8
  import { encodeCacheTag, encodeCacheTags } from "../utils/encode-cache-tag.js";
@@ -13,7 +12,8 @@ import { makeHangingPromise } from "./internal/make-hanging-promise.js";
13
12
  import { getHeadersAccessPhase, markDynamicUsage } from "./internal/headers-state.js";
14
13
  import { isDraftModeEnabled } from "./headers.js";
15
14
  import { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext } from "./internal/cache-context-accessor.js";
16
- import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
15
+ import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
16
+ import { isPathKnownNotEdgeCached } from "./cdn-purge-routes.js";
17
17
  //#region src/shims/cache.ts
18
18
  /**
19
19
  * next/cache shim
@@ -69,7 +69,7 @@ function revalidateTag(tag, profile) {
69
69
  if (!profile || !durations || durations.expire === 0) markActionRevalidation(1);
70
70
  const encodedTag = encodeCacheTag(tag);
71
71
  _markPendingRevalidatedTag(encodedTag);
72
- return scheduleRevalidation(_invalidateEncodedTag(encodedTag, durations));
72
+ return scheduleTagInvalidation(encodedTag, durations);
73
73
  }
74
74
  /**
75
75
  * Invalidate one already-encoded tag across both cache layers.
@@ -79,10 +79,18 @@ function revalidateTag(tag, profile) {
79
79
  * default CDN adapter), THEN ask the CDN adapter to purge its edge (a no-op for
80
80
  * the default adapter). Purging the edge before the store is invalidated would
81
81
  * let the edge re-fetch and re-cache stale data.
82
+ *
83
+ * The data cache is invalidated right away, so reads later in the same action
84
+ * see it. Inside a Server Action or Route Handler the purge waits for the
85
+ * request boundary, which sends one batched adapter call per profile.
82
86
  */
83
- async function _invalidateEncodedTag(encoded, durations) {
87
+ function scheduleTagInvalidation(encoded, durations) {
88
+ const dataInvalidation = invalidateDataCacheTag(encoded, durations);
89
+ if (_queuePendingCdnTagInvalidation(encoded, durations, dataInvalidation)) return scheduleRevalidation(dataInvalidation);
90
+ return scheduleRevalidation(dataInvalidation.then(() => _revalidateCdnTags(encoded, durations)));
91
+ }
92
+ async function invalidateDataCacheTag(encoded, durations) {
84
93
  await getDataCacheHandler().revalidateTag(encoded, durations);
85
- await getCdnCacheAdapter().revalidateTag(encoded, durations);
86
94
  }
87
95
  /**
88
96
  * Revalidate cached data associated with a specific path.
@@ -109,7 +117,8 @@ function revalidatePath(path, type) {
109
117
  const tag = type ? `_N_T_${stem}/${type}` : `_N_T_${stem || "/"}`;
110
118
  const encodedTag = encodeCacheTag(tag);
111
119
  _markPendingRevalidatedTag(encodedTag);
112
- return scheduleRevalidation(_invalidateEncodedTag(encodedTag));
120
+ if (isPathKnownNotEdgeCached(stem || "/")) return scheduleRevalidation(invalidateDataCacheTag(encodedTag));
121
+ return scheduleTagInvalidation(encodedTag);
113
122
  }
114
123
  /**
115
124
  * No-op shim for API compatibility.
@@ -140,7 +149,7 @@ function updateTag(tag) {
140
149
  markActionRevalidation(1);
141
150
  const encodedTag = encodeCacheTag(tag);
142
151
  _markPendingRevalidatedTag(encodedTag);
143
- return scheduleRevalidation(_invalidateEncodedTag(encodedTag));
152
+ return scheduleTagInvalidation(encodedTag);
144
153
  }
145
154
  /**
146
155
  * Opt out of static rendering and indicate a particular component should not be cached.
@@ -464,4 +473,4 @@ function unstable_cache(fn, keyParts, options) {
464
473
  return cachedFn;
465
474
  }
466
475
  //#endregion
467
- export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, MemoryCacheHandler, NoOpCacheHandler, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLife, cacheLifeProfiles, cacheTag, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, io, isInsideUnstableCacheScope, markActionRevalidation, unstable_noStore as noStore, unstable_noStore, recordUnstableCacheObservation, refresh, registerDataCacheHandler, registerLazyDataCacheHandler, revalidatePath, revalidateTag, runWithDetachedCacheObservations, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
476
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, MemoryCacheHandler, NoOpCacheHandler, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLife, cacheLifeProfiles, cacheTag, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, io, isInsideUnstableCacheScope, markActionRevalidation, unstable_noStore as noStore, unstable_noStore, recordUnstableCacheObservation, refresh, registerDataCacheHandler, registerLazyDataCacheHandler, revalidatePath, revalidateTag, runWithDetachedCacheObservations, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
@@ -0,0 +1,24 @@
1
+ //#region src/shims/cdn-purge-routes.d.ts
2
+ type CdnPurgeRoute = {
3
+ patternParts: readonly string[];
4
+ routeSegments: readonly string[];
5
+ };
6
+ type CdnPurgeRoutes = {
7
+ metadataRoutes: readonly (Omit<CdnPurgeRoute, "patternParts"> & {
8
+ patternParts: readonly string[] | null;
9
+ servedUrl: string;
10
+ })[];
11
+ routes: readonly (CdnPurgeRoute & {
12
+ forceDynamic?: boolean;
13
+ })[];
14
+ };
15
+ /** Register the App Router's routes. Apps with a Pages Router never register. */
16
+ export declare function registerCdnPurgeRoutes(routes: CdnPurgeRoutes): void;
17
+ /**
18
+ * Whether no edge-cacheable response can carry the implicit tags of
19
+ * `revalidatePath(path)`, for any `type`. Paths that need encoding, the
20
+ * `/index` alias, and paths ending in a derived tag suffix (`/docs/page` is
21
+ * also the `/docs` page tag) always purge.
22
+ */
23
+ export declare function isPathKnownNotEdgeCached(path: string): boolean;
24
+ //#endregion
@@ -0,0 +1,77 @@
1
+ import { matchRoutePatternPrefix, routePatternParts } from "../routing/route-pattern.js";
2
+ //#region src/shims/cdn-purge-routes.ts
3
+ /**
4
+ * Build-time route facts that let `revalidatePath()` skip a CDN purge no
5
+ * edge-cached response could match.
6
+ *
7
+ * Edge purges are rate limited (Workers Cache allows 5 a minute per account),
8
+ * and a `dynamic = "force-dynamic"` App route never stores a response the
9
+ * edge can serve. The check is conservative: it only skips a path when at
10
+ * least one App route could serve it and every such route is force-dynamic.
11
+ * The data cache is always invalidated.
12
+ */
13
+ const CDN_PURGE_ROUTES_KEY = Symbol.for("vinext.cdnPurgeRoutes");
14
+ const globals = globalThis;
15
+ const DERIVED_TAG_SUFFIXES = /* @__PURE__ */ new Set([
16
+ "layout",
17
+ "page",
18
+ "route"
19
+ ]);
20
+ /** Register the App Router's routes. Apps with a Pages Router never register. */
21
+ function registerCdnPurgeRoutes(routes) {
22
+ globals[CDN_PURGE_ROUTES_KEY] = routes;
23
+ }
24
+ /** Whether some URL matched by `patternParts` starts with `pathParts`. */
25
+ function canServeUnder(pathParts, patternParts) {
26
+ const last = patternParts.at(-1) ?? "";
27
+ const endsInCatchAll = last.startsWith(":") && (last.endsWith("+") || last.endsWith("*"));
28
+ if (patternParts.length < pathParts.length && !endsInCatchAll) return false;
29
+ return matchRoutePatternPrefix(pathParts, patternParts.slice(0, pathParts.length));
30
+ }
31
+ /**
32
+ * Whether a response from `route` could carry a `revalidatePath(path)` tag.
33
+ * Implicit tags use either the URL (`/blog/a`) or the route's segments
34
+ * (`/(site)/blog/[slug]`), so check both as prefixes.
35
+ */
36
+ function couldCarryPathTag(pathParts, route) {
37
+ return canServeUnder(pathParts, route.patternParts) || canServeUnder(pathParts, routePatternParts(route.routeSegments.join("/")));
38
+ }
39
+ /**
40
+ * Whether a metadata route, or a child it generates (`/icon/small` from
41
+ * `generateImageMetadata`, `/sitemap/0.xml` from `generateSitemaps`), could
42
+ * carry a `revalidatePath(path)` tag.
43
+ */
44
+ function metadataCouldCarryPathTag(pathParts, route) {
45
+ const patternParts = route.patternParts ?? routePatternParts(route.servedUrl);
46
+ const childParts = [
47
+ ...patternParts.slice(0, -1),
48
+ (patternParts.at(-1) ?? "").replace(/\.[^.]*$/, ""),
49
+ ":children*"
50
+ ];
51
+ return couldCarryPathTag(pathParts, {
52
+ patternParts,
53
+ routeSegments: route.routeSegments
54
+ }) || canServeUnder(pathParts, childParts);
55
+ }
56
+ /**
57
+ * Whether no edge-cacheable response can carry the implicit tags of
58
+ * `revalidatePath(path)`, for any `type`. Paths that need encoding, the
59
+ * `/index` alias, and paths ending in a derived tag suffix (`/docs/page` is
60
+ * also the `/docs` page tag) always purge.
61
+ */
62
+ function isPathKnownNotEdgeCached(path) {
63
+ const registered = globals[CDN_PURGE_ROUTES_KEY];
64
+ if (!registered || path === "/index" || /[^\x21-\x7e]|%/.test(path)) return false;
65
+ const pathParts = path.split("/").filter(Boolean);
66
+ if (DERIVED_TAG_SUFFIXES.has(pathParts.at(-1) ?? "")) return false;
67
+ if (registered.metadataRoutes.some((route) => metadataCouldCarryPathTag(pathParts, route))) return false;
68
+ let matched = false;
69
+ for (const route of registered.routes) {
70
+ if (!couldCarryPathTag(pathParts, route)) continue;
71
+ if (route.forceDynamic !== true) return false;
72
+ matched = true;
73
+ }
74
+ return matched;
75
+ }
76
+ //#endregion
77
+ export { isPathKnownNotEdgeCached, registerCdnPurgeRoutes };
@@ -52,13 +52,26 @@ function normalizeDynamicOptions(dynamicInput, options) {
52
52
  ...options
53
53
  };
54
54
  }
55
- function createLazyComponent(loader) {
56
- return React.lazy(async () => {
55
+ function createLazyComponent(loader, share = (load) => load) {
56
+ return React.lazy(() => share((async () => {
57
57
  const mod = await loader();
58
58
  if (hasDefaultExport(mod)) return mod;
59
59
  return { default: mod };
60
- });
60
+ })()));
61
61
  }
62
+ const SHARE_ISOLATE_PROMISE_KEY = Symbol.for("vinext.shareIsolatePromise");
63
+ /**
64
+ * The server React.lazy is created once per dynamic() call, so every request
65
+ * in the isolate renders the same load. On Workers, the request that starts it
66
+ * owns its I/O; if that request is canceled first, the load never settles and
67
+ * every later render of the component hangs. The server installs
68
+ * `shareIsolatePromise` on a global symbol, which keeps the starting request
69
+ * alive until the load settles, without this shim importing request state.
70
+ */
71
+ const shareServerLoad = (load) => {
72
+ const share = globalThis[SHARE_ISOLATE_PROMISE_KEY];
73
+ return share ? share(load) : load;
74
+ };
62
75
  function useRetryableLazyComponent(loader, initialLazyComponent) {
63
76
  const [LazyComponent, setLazyComponent] = React.useState(() => initialLazyComponent);
64
77
  const [retryKey, setRetryKey] = React.useState(0);
@@ -218,7 +231,7 @@ function dynamic(dynamicInput, options) {
218
231
  AsyncServerDynamic.displayName = "DynamicAsyncServer";
219
232
  return AsyncServerDynamic;
220
233
  }
221
- const LazyServer = createLazyComponent(loader);
234
+ const LazyServer = createLazyComponent(loader, shareServerLoad);
222
235
  const ServerDynamic = (props) => {
223
236
  const isAppRouterTree = useIsAppRouterTree();
224
237
  const fallback = LoadingComponent ? React.createElement(LoadingComponent, createDynamicLoadingProps()) : null;