vinext 1.3.1 → 1.4.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 (234) hide show
  1. package/README.md +5 -2
  2. package/THIRD_PARTY_LICENSES.md +47 -0
  3. package/dist/_virtual/_rolldown/runtime.js +3 -2
  4. package/dist/build/prerender.js +1 -1
  5. package/dist/build/report.d.ts +0 -14
  6. package/dist/build/report.js +1 -51
  7. package/dist/check.js +1 -1
  8. package/dist/cli.js +3 -0
  9. package/dist/client/dev-error-overlay.js +1 -1
  10. package/dist/client/navigation-runtime.d.ts +13 -1
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/next-config.d.ts +19 -0
  13. package/dist/config/next-config.js +64 -1
  14. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-codec.js +327 -0
  15. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-data.js +123 -0
  16. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/index.js +29 -0
  17. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/internal.js +134 -0
  18. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-codec.js +50 -0
  19. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data-generated.js +455 -0
  20. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data.js +147 -0
  21. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/big5-added.json.js +169 -0
  22. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp936.json.js +2584 -0
  23. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp949.json.js +2220 -0
  24. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp950.json.js +227 -0
  25. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/eucjp.json.js +425 -0
  26. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gb18030-ranges.json.js +427 -0
  27. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gbk-added.json.js +229 -0
  28. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/shiftjis.json.js +292 -0
  29. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf16.js +106 -0
  30. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf7.js +182 -0
  31. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/bom-handling.js +42 -0
  32. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/extend-node.js +147 -0
  33. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/index.js +94 -0
  34. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/streams.js +95 -0
  35. package/dist/deps/.pnpm/safer-buffer@2.1.2/deps/safer-buffer/safer.js +45 -0
  36. package/dist/entries/app-rsc-entry.js +17 -48
  37. package/dist/entries/app-rsc-manifest.js +3 -4
  38. package/dist/entries/app-ssr-entry.js +1 -0
  39. package/dist/entries/pages-server-entry.js +33 -8
  40. package/dist/index.js +83 -70
  41. package/dist/init-cloudflare.d.ts +1 -3
  42. package/dist/init-cloudflare.js +10 -48
  43. package/dist/init-platform.d.ts +2 -0
  44. package/dist/init-platform.js +4 -0
  45. package/dist/plugins/extensionless-dynamic-import.js +6 -2
  46. package/dist/plugins/next-intl-catalog.d.ts +12 -0
  47. package/dist/plugins/next-intl-catalog.js +85 -0
  48. package/dist/plugins/pages-node-externals.d.ts +1 -0
  49. package/dist/plugins/pages-node-externals.js +1 -1
  50. package/dist/plugins/use-cache-callable.d.ts +1 -1
  51. package/dist/plugins/use-cache-callable.js +136 -94
  52. package/dist/routing/app-route-graph.d.ts +6 -0
  53. package/dist/routing/app-route-graph.js +9 -1
  54. package/dist/server/api-handler.js +19 -4
  55. package/dist/server/app-bfcache-identity.js +1 -1
  56. package/dist/server/app-browser-entry.js +79 -63
  57. package/dist/server/app-browser-history-controller.d.ts +5 -4
  58. package/dist/server/app-browser-history-controller.js +93 -15
  59. package/dist/server/app-browser-navigation-controller.d.ts +2 -2
  60. package/dist/server/app-browser-navigation-controller.js +1 -2
  61. package/dist/server/app-browser-rsc-redirect.js +4 -4
  62. package/dist/server/app-browser-server-action-client.d.ts +2 -0
  63. package/dist/server/app-browser-server-action-client.js +4 -3
  64. package/dist/server/app-browser-state.d.ts +2 -4
  65. package/dist/server/app-browser-state.js +9 -4
  66. package/dist/server/app-browser-supplemental-refresh.js +16 -1
  67. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  68. package/dist/server/app-browser-visible-commit.js +2 -12
  69. package/dist/server/app-elements-wire.d.ts +6 -38
  70. package/dist/server/app-elements-wire.js +4 -41
  71. package/dist/server/app-elements.d.ts +2 -2
  72. package/dist/server/app-elements.js +2 -2
  73. package/dist/server/app-history-state.d.ts +2 -2
  74. package/dist/server/app-history-state.js +5 -2
  75. package/dist/server/app-mounted-layouts.d.ts +21 -0
  76. package/dist/server/app-mounted-layouts.js +67 -0
  77. package/dist/server/app-optimistic-routing.d.ts +19 -1
  78. package/dist/server/app-optimistic-routing.js +42 -2
  79. package/dist/server/app-page-boundary-render.js +7 -6
  80. package/dist/server/app-page-boundary.js +2 -2
  81. package/dist/server/app-page-cache-finalizer.d.ts +12 -1
  82. package/dist/server/app-page-cache-finalizer.js +70 -29
  83. package/dist/server/app-page-cache.d.ts +13 -1
  84. package/dist/server/app-page-cache.js +31 -9
  85. package/dist/server/app-page-dispatch.d.ts +5 -10
  86. package/dist/server/app-page-dispatch.js +84 -154
  87. package/dist/server/app-page-element-builder.d.ts +2 -2
  88. package/dist/server/app-page-element-builder.js +10 -9
  89. package/dist/server/app-page-execution.d.ts +17 -52
  90. package/dist/server/app-page-execution.js +99 -95
  91. package/dist/server/app-page-head.d.ts +13 -0
  92. package/dist/server/app-page-head.js +44 -16
  93. package/dist/server/app-page-http-access-fallback-metadata.d.ts +1 -0
  94. package/dist/server/app-page-http-access-fallback-metadata.js +2 -2
  95. package/dist/server/app-page-layout-gate.d.ts +35 -0
  96. package/dist/server/app-page-layout-gate.js +50 -0
  97. package/dist/server/app-page-params.d.ts +0 -1
  98. package/dist/server/app-page-params.js +1 -14
  99. package/dist/server/app-page-probe.d.ts +3 -21
  100. package/dist/server/app-page-probe.js +4 -37
  101. package/dist/server/app-page-render.d.ts +2 -12
  102. package/dist/server/app-page-render.js +89 -220
  103. package/dist/server/app-page-request.d.ts +1 -3
  104. package/dist/server/app-page-request.js +1 -1
  105. package/dist/server/app-page-response.js +3 -3
  106. package/dist/server/app-page-route-wiring.d.ts +13 -6
  107. package/dist/server/app-page-route-wiring.js +67 -61
  108. package/dist/server/app-page-rsc-render-status.d.ts +27 -1
  109. package/dist/server/app-page-rsc-render-status.js +121 -13
  110. package/dist/server/app-page-stream.d.ts +2 -0
  111. package/dist/server/app-page-stream.js +2 -2
  112. package/dist/server/app-pages-bridge.d.ts +4 -0
  113. package/dist/server/app-pages-bridge.js +7 -3
  114. package/dist/server/app-render-dependency.d.ts +11 -4
  115. package/dist/server/app-render-dependency.js +86 -38
  116. package/dist/server/app-request-stage-dispatch.js +1 -1
  117. package/dist/server/app-route-handler-cache.js +2 -2
  118. package/dist/server/app-route-handler-dispatch.js +14 -9
  119. package/dist/server/app-route-handler-execution.js +8 -3
  120. package/dist/server/app-route-handler-policy.d.ts +1 -1
  121. package/dist/server/app-route-handler-policy.js +3 -3
  122. package/dist/server/app-route-handler-response.js +1 -1
  123. package/dist/server/app-route-handler-runtime.js +17 -4
  124. package/dist/server/app-rsc-cache-busting.d.ts +2 -2
  125. package/dist/server/app-rsc-cache-busting.js +4 -4
  126. package/dist/server/app-rsc-error-handler.d.ts +2 -1
  127. package/dist/server/app-rsc-error-handler.js +9 -1
  128. package/dist/server/app-rsc-errors.d.ts +8 -0
  129. package/dist/server/app-rsc-errors.js +8 -1
  130. package/dist/server/app-rsc-handler.d.ts +5 -5
  131. package/dist/server/app-rsc-handler.js +30 -24
  132. package/dist/server/app-rsc-request-normalization.d.ts +0 -4
  133. package/dist/server/app-rsc-request-normalization.js +1 -4
  134. package/dist/server/app-rsc-response-finalizer.js +2 -2
  135. package/dist/server/app-rsc-response-stage.js +5 -3
  136. package/dist/server/app-server-action-execution.d.ts +3 -1
  137. package/dist/server/app-server-action-execution.js +7 -4
  138. package/dist/server/app-ssr-entry.d.ts +2 -0
  139. package/dist/server/app-ssr-entry.js +9 -8
  140. package/dist/server/app-ssr-stream.js +1 -1
  141. package/dist/server/app-visited-response-cache.d.ts +36 -1
  142. package/dist/server/app-visited-response-cache.js +46 -5
  143. package/dist/server/app-worker-stages.d.ts +25 -3
  144. package/dist/server/app-worker-stages.js +45 -6
  145. package/dist/server/artifact-compatibility.d.ts +0 -1
  146. package/dist/server/artifact-compatibility.js +1 -10
  147. package/dist/server/cache-proof.d.ts +1 -115
  148. package/dist/server/cache-proof.js +1 -544
  149. package/dist/server/cacheability-request.js +13 -8
  150. package/dist/server/dev-server.js +4 -3
  151. package/dist/server/headers.d.ts +6 -3
  152. package/dist/server/headers.js +9 -5
  153. package/dist/server/isr-cache.d.ts +8 -2
  154. package/dist/server/isr-cache.js +26 -1
  155. package/dist/server/metadata-route-response.js +32 -15
  156. package/dist/server/multi-stage.d.ts +52 -11
  157. package/dist/server/multi-stage.js +13 -1
  158. package/dist/server/navigation-planner.js +1 -1
  159. package/dist/server/pages-api-route.js +28 -19
  160. package/dist/server/pages-media-type.d.ts +21 -1
  161. package/dist/server/pages-media-type.js +69 -5
  162. package/dist/server/pages-node-compat.js +52 -27
  163. package/dist/server/pages-page-handler.js +11 -20
  164. package/dist/server/pages-request-pipeline.d.ts +8 -0
  165. package/dist/server/pages-request-pipeline.js +2 -2
  166. package/dist/server/pages-request-stage-entry.js +14 -12
  167. package/dist/server/pages-response-stage-entry.js +9 -3
  168. package/dist/server/prod-server.js +5 -1
  169. package/dist/server/request-pipeline.d.ts +6 -0
  170. package/dist/server/request-pipeline.js +21 -1
  171. package/dist/server/shared-cache-identity.d.ts +23 -0
  172. package/dist/server/shared-cache-identity.js +36 -0
  173. package/dist/server/worker-stages.d.ts +16 -3
  174. package/dist/server/worker-stages.js +23 -4
  175. package/dist/shims/cache-handler.js +2 -2
  176. package/dist/shims/cache-request-state.d.ts +17 -20
  177. package/dist/shims/cache-request-state.js +16 -21
  178. package/dist/shims/cache-runtime.js +27 -11
  179. package/dist/shims/cache.d.ts +8 -7
  180. package/dist/shims/cache.js +17 -19
  181. package/dist/shims/cacheability-classification.d.ts +2 -0
  182. package/dist/shims/cdn-cache.d.ts +22 -5
  183. package/dist/shims/client-page-root.js +2 -2
  184. package/dist/shims/dynamic.js +9 -6
  185. package/dist/shims/error-boundary.js +2 -2
  186. package/dist/shims/fetch-cache-state.d.ts +5 -1
  187. package/dist/shims/fetch-cache-state.js +1 -3
  188. package/dist/shims/fetch-cache.d.ts +2 -1
  189. package/dist/shims/fetch-cache.js +34 -24
  190. package/dist/shims/form.js +2 -2
  191. package/dist/shims/headers.d.ts +8 -23
  192. package/dist/shims/headers.js +17 -67
  193. package/dist/shims/internal/navigation-untracked.js +1 -1
  194. package/dist/shims/link.js +3 -1
  195. package/dist/shims/metadata.d.ts +23 -21
  196. package/dist/shims/metadata.js +62 -24
  197. package/dist/shims/navigation-server.js +1 -1
  198. package/dist/shims/navigation.d.ts +74 -7
  199. package/dist/shims/navigation.js +183 -29
  200. package/dist/shims/navigation.react-server.js +2 -2
  201. package/dist/shims/request-state-types.d.ts +2 -2
  202. package/dist/shims/slot.d.ts +12 -0
  203. package/dist/shims/slot.js +37 -9
  204. package/dist/shims/unified-request-context.d.ts +1 -1
  205. package/dist/shims/unified-request-context.js +2 -3
  206. package/dist/utils/cache-control-metadata.d.ts +5 -0
  207. package/dist/utils/cache-control-metadata.js +9 -1
  208. package/dist/utils/project.d.ts +2 -2
  209. package/dist/utils/project.js +3 -16
  210. package/dist/utils/regex.d.ts +5 -0
  211. package/dist/utils/regex.js +11 -1
  212. package/dist/utils/text-stream.d.ts +10 -5
  213. package/dist/utils/text-stream.js +6 -6
  214. package/package.json +2 -1
  215. package/dist/build/layout-classification-types.d.ts +0 -60
  216. package/dist/build/layout-classification-types.js +0 -1
  217. package/dist/build/layout-classification.d.ts +0 -62
  218. package/dist/build/layout-classification.js +0 -98
  219. package/dist/build/route-classification-injector.d.ts +0 -33
  220. package/dist/build/route-classification-injector.js +0 -59
  221. package/dist/build/route-classification-manifest.d.ts +0 -54
  222. package/dist/build/route-classification-manifest.js +0 -143
  223. package/dist/server/app-browser-client-reuse-manifest.d.ts +0 -11
  224. package/dist/server/app-browser-client-reuse-manifest.js +0 -101
  225. package/dist/server/app-layout-param-observation.d.ts +0 -42
  226. package/dist/server/app-layout-param-observation.js +0 -168
  227. package/dist/server/client-reuse-manifest.d.ts +0 -103
  228. package/dist/server/client-reuse-manifest.js +0 -209
  229. package/dist/server/skip-cache-proof.d.ts +0 -60
  230. package/dist/server/skip-cache-proof.js +0 -168
  231. package/dist/server/static-layout-client-reuse-proof.d.ts +0 -14
  232. package/dist/server/static-layout-client-reuse-proof.js +0 -33
  233. package/dist/utils/sorted-array.d.ts +0 -8
  234. package/dist/utils/sorted-array.js +0 -20
@@ -13,8 +13,8 @@ import { beginRouteCacheability, isRouteCacheabilityIdentityProbe, isRouteCachea
13
13
  import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, STATIC_CACHE_CONTROL, applyCdnResponseHeaders, hasCdnResponsePolicy, hasExplicitNonCacheableResponsePolicy, isCdnResponsePolicyHeader, shouldUseNextDeployCacheControl } from "./cache-control.js";
14
14
  import { buildMissIsrCacheControl } from "./isr-decision.js";
15
15
  import { isOnDemandRevalidateRequest } from "./revalidation-request.js";
16
- import { coalesceOnDemandRevalidation, isrGet, isrSet, pagesIsrCacheKey, triggerBackgroundRegeneration } from "./isr-cache.js";
17
- import { ensureFetchPatch } from "../shims/fetch-cache.js";
16
+ import { coalesceOnDemandResponse, isrGet, isrSet, pagesIsrCacheKey, triggerBackgroundRegeneration } from "./isr-cache.js";
17
+ import { ensureFetchPatch, setCurrentFetchDraftMode } from "../shims/fetch-cache.js";
18
18
  import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
19
19
  import { getScriptNonceFromHeaderSources } from "./csp.js";
20
20
  import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
@@ -258,23 +258,10 @@ function createPagesPageHandler(opts) {
258
258
  const isStaticPropsRender = isStaticPropsRoute && typeof pageModule.getServerSideProps !== "function";
259
259
  if (!options?.__skipOnDemandCoalesce && !options?.__forcedRoute && !cacheIdentityDiverges && isStaticPropsRoute && isOnDemandRevalidateRequest(request.headers.get("x-prerender-revalidate"))) {
260
260
  const cacheKey = pageIsrCacheKey("pages", routeUrl.split("?")[0]);
261
- const snapshot = await coalesceOnDemandRevalidation(cacheKey, async () => {
262
- const response = await renderPage(request, url, manifest, middlewareHeaders, {
263
- ...options,
264
- __skipOnDemandCoalesce: true
265
- }, initialResponseHeaders);
266
- return {
267
- body: request.method === "HEAD" || response.status === 204 || response.status === 304 ? null : new Uint8Array(await response.arrayBuffer()),
268
- headers: [...response.headers.entries()],
269
- status: response.status,
270
- statusText: response.statusText
271
- };
272
- });
273
- return new Response(snapshot.body?.slice() ?? null, {
274
- headers: snapshot.headers,
275
- status: snapshot.status,
276
- statusText: snapshot.statusText
277
- });
261
+ return coalesceOnDemandResponse(cacheKey, request.method, () => renderPage(request, url, manifest, middlewareHeaders, {
262
+ ...options,
263
+ __skipOnDemandCoalesce: true
264
+ }, initialResponseHeaders));
278
265
  }
279
266
  const routePattern = patternToNextFormat(route.pattern);
280
267
  const renderStatusCode = renderStatusCodeOverride ?? (routePattern === "/404" ? 404 : void 0);
@@ -283,7 +270,10 @@ function createPagesPageHandler(opts) {
283
270
  const renderRouteUrl = isStaticPropsRender && isStatusPageIdentity ? resolvePagesStatusPageRenderPath(statusPagePathname, vinextConfig.trailingSlash, originalRequestUrl.pathname) : isStaticPropsRender ? routeUrl.split("?")[0] : routeUrl;
284
271
  const routerAsPathSource = isStaticPropsRender ? renderRouteUrl : renderAsPath ?? renderRouteUrl;
285
272
  const routerAsPath = i18nConfig ? extractLocaleFromUrl(routerAsPathSource, i18nConfig, locale).url : routerAsPathSource;
286
- const uCtx = createRequestContext({ executionContext: getRequestExecutionContext() });
273
+ const uCtx = createRequestContext({
274
+ executionContext: getRequestExecutionContext(),
275
+ isOnDemandRevalidate: isOnDemandRevalidateRequest(request.headers.get(PRERENDER_REVALIDATE_HEADER))
276
+ });
287
277
  let pageReqRes = null;
288
278
  const pageResponseSent = () => pageReqRes?.res.headersSent === true;
289
279
  const sendsEarly = isStaticPropsRoute && !isOnDemandRevalidateRequest(request.headers.get("x-prerender-revalidate"));
@@ -322,6 +312,7 @@ function createPagesPageHandler(opts) {
322
312
  shouldClear: false
323
313
  };
324
314
  const previewData = preview.data;
315
+ setCurrentFetchDraftMode(previewData !== false);
325
316
  const pagesNextData = {
326
317
  ...pagesReadiness,
327
318
  ...previewData === false ? {} : { isPreview: true }
@@ -84,6 +84,14 @@ export type PagesPipelineDeps = {
84
84
  dataNotFoundResponse?: Response | null;
85
85
  renderPage?: ((request: Request, resolvedUrl: string, options?: PagesRenderOptions, stagedHeaders?: Headers) => Promise<Response>) | null;
86
86
  handleApi?: ((request: Request, apiUrl: string, ctx: unknown, stagedHeaders: Headers) => Promise<Response>) | null;
87
+ /**
88
+ * `handleApi` installs the staged headers on the handler's `res` before user
89
+ * code runs, as Next.js does, so its response already reflects any header the
90
+ * handler replaced and only middleware's status is applied here.
91
+ * Workers leaves this unset: its response stage strips the inherited cookies
92
+ * so they are not stored, and this merge adds them back.
93
+ */
94
+ handleApiAppliesStagedHeaders?: boolean;
87
95
  /**
88
96
  * Optional override for proxying external rewrite destinations.
89
97
  * When supplied, the pipeline calls this instead of proxyExternalRequest(currentRequest, url).
@@ -14,8 +14,8 @@ import { buildMiddlewarePrefetchSkipResponse } from "./pages-data-route.js";
14
14
  import { markRouteCacheabilityDynamic } from "../shims/cacheability-classification.js";
15
15
  import { isOnDemandRevalidateRequest } from "./revalidation-request.js";
16
16
  import { mergeRewriteQuery } from "../utils/query.js";
17
- import { withExternalRewriteResponseHeaders } from "./middleware-response-headers.js";
18
17
  import { setFrameworkRequestRoute } from "./request-tracing.js";
18
+ import { withExternalRewriteResponseHeaders } from "./middleware-response-headers.js";
19
19
  import { applyConfigHeadersToHeaderRecord } from "./config-headers.js";
20
20
  import { finalizeMissingStaticAssetResponse, mergeHeaders } from "./worker-utils.js";
21
21
  //#region src/server/pages-request-pipeline.ts
@@ -310,7 +310,7 @@ async function runPagesRequest(request, deps) {
310
310
  apiRequest = cloneRequestWithUrl(request, apiRequestUrl.toString());
311
311
  }
312
312
  const response = await deps.handleApi(apiRequest, apiLookupUrl, deps.ctx ?? null, headersFromRecord(middlewareHeaders));
313
- const merged = mergeHeaders(response, middlewareHeaders, middlewareStatus);
313
+ const merged = mergeHeaders(response, deps.handleApiAppliesStagedHeaders ? {} : middlewareHeaders, middlewareStatus);
314
314
  if (merged !== response) merged.__vinextStreamedApiResponse = response.__vinextStreamedApiResponse;
315
315
  return {
316
316
  type: "response",
@@ -3,7 +3,7 @@ import { requestContextFromRequest } from "../config/request-context.js";
3
3
  import { hasBasePath, stripBasePath } from "../utils/base-path.js";
4
4
  import { VINEXT_CACHEABILITY_PROBE_QUERY_PARAM, VINEXT_EXPECTED_WORKER_VERSION_HEADER } from "./headers.js";
5
5
  import { notFoundStaticAssetResponse } from "./http-error-responses.js";
6
- import { attachRequestCfMetadata, cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, guardProtocolRelativeUrl } from "./request-pipeline.js";
6
+ import { attachRequestCfMetadata, cloneRequestWithUrl, filterInternalHeaders, guardProtocolRelativeUrl, haveSameHeaders, withRequestHeaders } from "./request-pipeline.js";
7
7
  import { assetPrefixPathname, isNextStaticPath } from "../utils/asset-prefix.js";
8
8
  import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
9
9
  import { runWithExecutionContext } from "../shims/request-context.js";
@@ -22,7 +22,8 @@ import { resolveClientBuildFile } from "./client-build-files.js";
22
22
  import { createWorkerRevalidationContext } from "./worker-revalidation-context.js";
23
23
  import { createWorkerPrerenderDiscoveryContext, createWorkerPrerenderReadinessResponse, isWorkerPrerenderDiscoveryPath } from "./worker-prerender-discovery.js";
24
24
  import { getPagesResponseStageCacheDisposition } from "./pages-response-stage.js";
25
- import "./worker-stages.js";
25
+ import { adapterUsesSharedCacheIdentity, pagesCacheIdentityOrigin, pagesPageSharesAcrossHosts } from "./shared-cache-identity.js";
26
+ import { createSharedPagesPageCacheIdentity } from "./worker-stages.js";
26
27
  import { consumePagesResponseStagePolicyOwner, prependResponseStageAdditiveHeaders, withResponseStageVary, withoutResponseStageVary } from "./response-stage-policy.js";
27
28
  import * as configuredCdnCacheAdapters from "virtual:vinext-cdn-cache-adapter";
28
29
  import { registerConfiguredImageOptimizer } from "virtual:vinext-image-adapters";
@@ -34,11 +35,6 @@ import * as pagesEntry from "virtual:vinext-pages-request-entry";
34
35
  * The public pages-router-entry delegates here. Multi-stage hosts import the
35
36
  * named request handler directly and provide their response-stage dispatcher.
36
37
  */
37
- function haveSameHeaders(first, second) {
38
- const firstEntries = [...first];
39
- const secondEntries = [...second];
40
- return firstEntries.length === secondEntries.length && firstEntries.every(([name, value], index) => name === secondEntries[index]?.[0] && value === secondEntries[index]?.[1]);
41
- }
42
38
  function captureOuterResponsePolicyHeaders(stagedHeaders, transportedPolicyHeaders) {
43
39
  return captureCdnResponsePolicyOverrides(stagedHeaders, new Headers(transportedPolicyHeaders ?? []));
44
40
  }
@@ -151,7 +147,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
151
147
  const expectedWorkerVersion = request.headers.get(VINEXT_EXPECTED_WORKER_VERSION_HEADER);
152
148
  if (expectedWorkerVersion) filteredHeaders.set(VINEXT_EXPECTED_WORKER_VERSION_HEADER, expectedWorkerVersion);
153
149
  }
154
- request = cloneRequestWithHeaders(request, filteredHeaders);
150
+ request = withRequestHeaders(request, filteredHeaders);
155
151
  const url = new URL(request.url);
156
152
  let pathname = url.pathname;
157
153
  if (ctx.isPrerenderPathDiscovery && isWorkerPrerenderDiscoveryPath(pathname)) return trackedDispatchResponseStage(request, {
@@ -162,7 +158,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
162
158
  resolvedRoutePathname: pathname
163
159
  },
164
160
  kind: "pages-prerender-discovery",
165
- protocolVersion: 5,
161
+ protocolVersion: 6,
166
162
  requestHost: url.host,
167
163
  stagedHeaders: null
168
164
  }, { cache: "bypass" }, env, ctx);
@@ -247,6 +243,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
247
243
  stagedHeaders
248
244
  });
249
245
  const isSharedStaticPage = cache === "shared" && matchedPage?.route.dataKind === "static";
246
+ const sharedAcrossHosts = (cache === "shared" || probeMode !== null) && pagesPageSharesAcrossHosts(matchedPage?.route) && adapterUsesSharedCacheIdentity();
250
247
  const transportedPolicyHeaders = isSharedStaticPage ? withoutResponseStageVary(responseStagePolicyHeaders) : withResponseStageVary(responseStagePolicyHeaders, stagedHeaders?.get("Vary"));
251
248
  const responseStageProps = () => ({
252
249
  buildId: pagesEntry.buildId,
@@ -257,17 +254,22 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
257
254
  resolvedRoutePathname: new URL(resolvedUrl, req.url).pathname
258
255
  },
259
256
  kind: "pages-page",
260
- protocolVersion: 5,
257
+ protocolVersion: 6,
261
258
  requestHost: new URL(req.url).host,
262
259
  renderOptions: isDataReq ? {
263
260
  ...options,
264
261
  originalUrl: originalRenderUrl
265
262
  } : options ?? null,
266
263
  resolvedUrl,
264
+ sharedAcrossHosts,
267
265
  stagedHeaders: isSharedStaticPage ? null : [...stagedHeaders ?? new Headers()]
268
266
  });
269
267
  const isHeadRequest = req.method.toUpperCase() === "HEAD";
270
- const responsePromise = trackedDispatchResponseStage(stageRequest, responseStageProps(), { cache }, env, ctx).then((response) => {
268
+ const props = responseStageProps();
269
+ const responsePromise = trackedDispatchResponseStage(stageRequest, props, cache === "shared" && sharedAcrossHosts ? {
270
+ cache,
271
+ cacheIdentity: createSharedPagesPageCacheIdentity(stageRequest, props, pagesCacheIdentityOrigin(stageRequest.url, i18nConfig))
272
+ } : { cache }, env, ctx).then((response) => {
271
273
  const consumed = consumePagesResponseStagePolicyOwner(response);
272
274
  if (cache === "shared") {
273
275
  if (isSharedStaticPage && stagedHeaders) prependResponseStageAdditiveHeaders(consumed.response.headers, stagedHeaders);
@@ -297,7 +299,7 @@ async function handleRequestImpl(request, env, platformCtx, dispatchResponseStag
297
299
  resolvedRoutePathname: new URL(apiUrl, req.url).pathname
298
300
  },
299
301
  kind: "pages-api",
300
- protocolVersion: 5,
302
+ protocolVersion: 6,
301
303
  requestHost: new URL(req.url).host,
302
304
  stagedHeaders: [...stagedHeaders]
303
305
  });
@@ -1,7 +1,7 @@
1
1
  import { preserveFullyBufferedBodyMetadata } from "./fully-buffered-response.js";
2
2
  import "../shims/unified-request-context.js";
3
3
  import { runWithExecutionContext } from "../shims/request-context.js";
4
- import { beginRouteCacheability } from "../shims/cacheability-classification.js";
4
+ import { beginRouteCacheability, markRouteCacheabilityDynamic } from "../shims/cacheability-classification.js";
5
5
  import { validateCdnRequest } from "./cache-control.js";
6
6
  import { createWorkerRevalidationContext } from "./worker-revalidation-context.js";
7
7
  import { createWorkerPrerenderReadinessResponse } from "./worker-prerender-discovery.js";
@@ -57,6 +57,12 @@ async function renderPagesResponse(request, env, platformCtx, props, stagedHeade
57
57
  ...ctx,
58
58
  isStoredEntryRegeneration: true
59
59
  };
60
+ let runtimeDataKind;
61
+ const runtimeDataKindIsRequestTime = () => {
62
+ if (props.kind !== "pages-page") return false;
63
+ runtimeDataKind ??= pagesEntry.getRuntimePageDataKind(props.resolvedUrl, request);
64
+ return runtimeDataKind === "server" || runtimeDataKind === "initial";
65
+ };
60
66
  const render = async (cacheabilityContext) => {
61
67
  if (props.kind === "pages-prerender-discovery") {
62
68
  const readinessResponse = createWorkerPrerenderReadinessResponse(cacheabilityContext, request);
@@ -77,6 +83,7 @@ async function renderPagesResponse(request, env, platformCtx, props, stagedHeade
77
83
  });
78
84
  }
79
85
  if (typeof pagesEntry.renderPage !== "function") return new Response("This page could not be found", { status: 404 });
86
+ if (props.sharedAcrossHosts && runtimeDataKindIsRequestTime()) runWithExecutionContext(cacheabilityContext, () => markRouteCacheabilityDynamic("getInitialProps render shares a cache entry across hosts"));
80
87
  return stripStreamedHtmlContentLength(await pagesEntry.renderPage(request, props.resolvedUrl, null, cacheabilityContext, renderHeaders, props.renderOptions ?? void 0, initialResponseHeaders));
81
88
  };
82
89
  const handle = async (cacheabilityContext) => stripInheritedResponseStageCookies(await render(cacheabilityContext), renderHeaders);
@@ -94,9 +101,8 @@ async function renderPagesResponse(request, env, platformCtx, props, stagedHeade
94
101
  resolvedRoutePathname: props.cacheability.resolvedRoutePathname
95
102
  }, handle);
96
103
  if (props.kind !== "pages-page") return response;
97
- const runtimeDataKind = pagesEntry.getRuntimePageDataKind(props.resolvedUrl, request);
98
104
  const headers = new Headers(response.headers);
99
- headers.set(PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER, runtimeDataKind === "server" || runtimeDataKind === "initial" ? "request-time" : "static");
105
+ headers.set(PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER, runtimeDataKindIsRequestTime() ? "request-time" : "static");
100
106
  return preserveFullyBufferedBodyMetadata(response, new Response(response.body, {
101
107
  headers,
102
108
  status: response.status,
@@ -1646,7 +1646,11 @@ async function createPagesRouterRequestHandler(options) {
1646
1646
  ...options,
1647
1647
  originalUrl: originalRenderUrl
1648
1648
  }, stagedHeaders?.has("Cache-Control") ? new Headers({ "Cache-Control": stagedHeaders.get("Cache-Control") }) : void 0) : null,
1649
- handleApi: typeof handleApi === "function" ? (request, apiUrl) => handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port), "node") : null,
1649
+ handleApi: typeof handleApi === "function" ? (request, apiUrl, _ctx, stagedHeaders) => {
1650
+ stagedHeaders.delete("Content-Length");
1651
+ return handleApi(request, apiUrl, createNodeExecutionContext(), resolveTrustedNodeRevalidateOrigin(req, host, port), "node", stagedHeaders);
1652
+ } : null,
1653
+ handleApiAppliesStagedHeaders: true,
1650
1654
  serveFilesystemRoute: async (requestPathname, stagedHeaders, phase, resolvedUrl, _request, resolvedPathnameHasBasePath) => {
1651
1655
  if (resolvedPathnameHasBasePath && isImageOptimizationPath(requestPathname)) {
1652
1656
  for (const [name, value] of Object.entries(stagedHeaders)) res.setHeader(name, value);
@@ -181,6 +181,12 @@ export declare function attachRequestCfMetadata(target: Request, source: Request
181
181
  * a RequestInit with best-effort metadata.
182
182
  */
183
183
  export declare function cloneRequestWithHeaders(request: Request, headers: Headers): Request;
184
+ export declare function haveSameHeaders(first: Headers, second: Headers): boolean;
185
+ /**
186
+ * `cloneRequestWithHeaders`, except that a native request whose headers
187
+ * already match is returned as is, so its body stays where it is.
188
+ */
189
+ export declare function withRequestHeaders(request: Request, headers: Headers): Request;
184
190
  /**
185
191
  * Clone a Request while overriding the URL, preserving headers and metadata
186
192
  * when possible.
@@ -407,6 +407,26 @@ function cloneRequestWithHeaders(request, headers) {
407
407
  }
408
408
  return attachRequestCfMetadata(cloned, request);
409
409
  }
410
+ function haveSameHeaders(first, second) {
411
+ const firstEntries = [...first];
412
+ const secondEntries = [...second];
413
+ return firstEntries.length === secondEntries.length && firstEntries.every(([name, value], index) => name === secondEntries[index]?.[0] && value === secondEntries[index]?.[1]);
414
+ }
415
+ function isNativeRequest(request) {
416
+ try {
417
+ Reflect.get(Request.prototype, "url", request);
418
+ return true;
419
+ } catch {
420
+ return false;
421
+ }
422
+ }
423
+ /**
424
+ * `cloneRequestWithHeaders`, except that a native request whose headers
425
+ * already match is returned as is, so its body stays where it is.
426
+ */
427
+ function withRequestHeaders(request, headers) {
428
+ return isNativeRequest(request) && haveSameHeaders(headers, request.headers) ? request : cloneRequestWithHeaders(request, headers);
429
+ }
410
430
  /**
411
431
  * Clone a Request while overriding the URL, preserving headers and metadata
412
432
  * when possible.
@@ -444,4 +464,4 @@ function cloneRequestWithUrl(request, url) {
444
464
  return attachRequestCfMetadata(cloned, request);
445
465
  }
446
466
  //#endregion
447
- export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, validateCsrfOrigin, validateServerActionPayload };
467
+ export { INTERNAL_HEADERS, VINEXT_INTERNAL_HEADERS, attachRequestCfMetadata, canonicalizeRequestPathname, canonicalizeRequestUrlPathname, cloneRequestWithHeaders, cloneRequestWithUrl, createStaticFileSignal, dropEmptyMiddlewareSetCookie, filterInternalHeaders, getRepeatedSlashRedirect, guardProtocolRelativeUrl, hasBasePath, haveSameHeaders, isOpenRedirectShaped, isOriginAllowed, normalizeTrailingSlash, normalizeTrailingSlashPathname, processMiddlewareHeaders, resolvePublicFileRoute, sendRepeatedSlashRedirect, stripBasePath, validateCsrfOrigin, validateServerActionPayload, withRequestHeaders };
@@ -0,0 +1,23 @@
1
+ import { NextI18nConfig } from "../config/next-config.js";
2
+ //#region src/server/shared-cache-identity.d.ts
3
+ /** The core cache identity the configured adapter keys shared dispatches by, if any. */
4
+ export declare function adapterSharedCacheIdentity(): "host-free" | "query-free" | null;
5
+ /** Whether the configured adapter keys shared dispatches by a core cache identity. */
6
+ export declare function adapterUsesSharedCacheIdentity(): boolean;
7
+ /**
8
+ * Whether a Pages page's response is one for every host, as Next.js serves a
9
+ * getStaticProps page and an automatically static page. A page without data
10
+ * functions is rendered per request when its getInitialProps or a custom
11
+ * _app's runs, and may read the host, so it keeps the host in its key.
12
+ */
13
+ export declare function pagesPageSharesAcrossHosts(route: {
14
+ dataKind?: string;
15
+ runsInitialProps?: boolean;
16
+ } | null | undefined): boolean;
17
+ /**
18
+ * The origin a shared Pages page identity names. A static Pages render reads
19
+ * the host only to select a configured i18n domain, so the identity keeps that
20
+ * domain, as vinext's Pages ISR key does, and every other host shares one entry.
21
+ */
22
+ export declare function pagesCacheIdentityOrigin(requestUrl: string, i18nConfig: NextI18nConfig | null | undefined): string;
23
+ //#endregion
@@ -0,0 +1,36 @@
1
+ import { detectDomainLocale, normalizeDomainHostname } from "../utils/domain-locale.js";
2
+ import { getCdnCacheAdapter } from "../shims/cdn-cache.js";
3
+ import { VINEXT_CACHE_IDENTITY_ORIGIN } from "./multi-stage.js";
4
+ //#region src/server/shared-cache-identity.ts
5
+ /** The core cache identity the configured adapter keys shared dispatches by, if any. */
6
+ function adapterSharedCacheIdentity() {
7
+ const adapter = getCdnCacheAdapter();
8
+ if (adapter.requiresCompletedResponseAdmission !== true) return null;
9
+ return adapter.responseStageCacheIdentity ?? null;
10
+ }
11
+ /** Whether the configured adapter keys shared dispatches by a core cache identity. */
12
+ function adapterUsesSharedCacheIdentity() {
13
+ return adapterSharedCacheIdentity() !== null;
14
+ }
15
+ /**
16
+ * Whether a Pages page's response is one for every host, as Next.js serves a
17
+ * getStaticProps page and an automatically static page. A page without data
18
+ * functions is rendered per request when its getInitialProps or a custom
19
+ * _app's runs, and may read the host, so it keeps the host in its key.
20
+ */
21
+ function pagesPageSharesAcrossHosts(route) {
22
+ if (route?.dataKind === "static") return true;
23
+ return route?.dataKind === "none" && route.runsInitialProps !== true;
24
+ }
25
+ /**
26
+ * The origin a shared Pages page identity names. A static Pages render reads
27
+ * the host only to select a configured i18n domain, so the identity keeps that
28
+ * domain, as vinext's Pages ISR key does, and every other host shares one entry.
29
+ */
30
+ function pagesCacheIdentityOrigin(requestUrl, i18nConfig) {
31
+ const domainLocale = detectDomainLocale(i18nConfig?.domains, new URL(requestUrl).hostname);
32
+ if (!domainLocale) return VINEXT_CACHE_IDENTITY_ORIGIN;
33
+ return `${domainLocale.http ? "http" : "https"}://${normalizeDomainHostname(domainLocale.domain)}`;
34
+ }
35
+ //#endregion
36
+ export { adapterSharedCacheIdentity, adapterUsesSharedCacheIdentity, pagesCacheIdentityOrigin, pagesPageSharesAcrossHosts };
@@ -1,7 +1,7 @@
1
- import { VinextResponseStageCacheability, VinextResponseStageTransport } from "./multi-stage.js";
1
+ import { VinextResponseStageCacheability, VinextResponseStageDispatchOptions, VinextResponseStageTransport } from "./multi-stage.js";
2
2
  import { PagesRenderOptions } from "./pages-request-pipeline.js";
3
3
  //#region src/server/worker-stages.d.ts
4
- export declare const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 5;
4
+ export declare const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 6;
5
5
  export declare const PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER = "x-vinext-pages-response-stage-policy-owner";
6
6
  export type PagesResponseStagePolicyOwner = "request-time" | "static";
7
7
  type PagesResponseStageEnvelope = {
@@ -14,10 +14,16 @@ type PagesResponseStageEnvelope = {
14
14
  stagedHeaders: Array<[string, string]> | null;
15
15
  };
16
16
  /** Serializable Pages page render delegated to a cacheable Worker stage. */
17
- type PagesPageResponseStageProps = PagesResponseStageEnvelope & {
17
+ export type PagesPageResponseStageProps = PagesResponseStageEnvelope & {
18
18
  kind: "pages-page";
19
19
  renderOptions: PagesRenderOptions | null;
20
20
  resolvedUrl: string;
21
+ /**
22
+ * The shared cache entry answers every host, as Next.js keys a static page
23
+ * by its path, so a getInitialProps render of a page without
24
+ * getStaticProps, which reads the request, is not stored.
25
+ */
26
+ sharedAcrossHosts: boolean;
21
27
  };
22
28
  /** Serializable Pages API dispatch delegated to a cacheable Worker stage. */
23
29
  type PagesApiResponseStageProps = PagesResponseStageEnvelope & {
@@ -39,6 +45,13 @@ export type WorkerResponseStageProps = PagesApiResponseStageProps | PagesPageRes
39
45
  * response as Next.js.
40
46
  */
41
47
  export type DispatchWorkerResponseStage = VinextResponseStageTransport<WorkerResponseStageProps>;
48
+ type PagesPageResponseStageCacheIdentity = NonNullable<VinextResponseStageDispatchOptions["cacheIdentity"]>;
49
+ /**
50
+ * Build the cache identity of a shared Pages page dispatch: the dispatched
51
+ * request and props under `origin`, so every host that names it shares one
52
+ * entry.
53
+ */
54
+ export declare function createSharedPagesPageCacheIdentity(request: Request, props: PagesPageResponseStageProps, origin: string): PagesPageResponseStageCacheIdentity;
42
55
  export declare function isPagesResponseStageProps(value: unknown): value is WorkerResponseStageProps;
43
56
  //#endregion
44
57
  export {};
@@ -1,6 +1,25 @@
1
+ import { withCacheIdentityOrigin } from "./multi-stage.js";
1
2
  //#region src/server/worker-stages.ts
2
- const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 5;
3
+ const PAGES_RESPONSE_STAGE_PROTOCOL_VERSION = 6;
3
4
  const PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER = "x-vinext-pages-response-stage-policy-owner";
5
+ /**
6
+ * Build the cache identity of a shared Pages page dispatch: the dispatched
7
+ * request and props under `origin`, so every host that names it shares one
8
+ * entry.
9
+ */
10
+ function createSharedPagesPageCacheIdentity(request, props, origin) {
11
+ return {
12
+ props: {
13
+ ...props,
14
+ requestHost: new URL(origin).host
15
+ },
16
+ regenerateFromDispatch: true,
17
+ request: new Request(withCacheIdentityOrigin(request.url, origin), {
18
+ headers: new Headers(request.headers),
19
+ method: request.method
20
+ })
21
+ };
22
+ }
4
23
  function isSerializedHeaders(value) {
5
24
  return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string" && typeof entry[1] === "string");
6
25
  }
@@ -12,14 +31,14 @@ function isResponseStageCacheability(value) {
12
31
  function isPagesResponseStageProps(value) {
13
32
  if (!value || typeof value !== "object") return false;
14
33
  const props = value;
15
- if (props.protocolVersion !== 5 || props.buildId !== null && typeof props.buildId !== "string" || !isResponseStageCacheability(props.cacheability) || typeof props.requestHost !== "string" || props.requestHost.length === 0 || props.stagedHeaders !== null && !isSerializedHeaders(props.stagedHeaders)) return false;
34
+ if (props.protocolVersion !== 6 || props.buildId !== null && typeof props.buildId !== "string" || !isResponseStageCacheability(props.cacheability) || typeof props.requestHost !== "string" || props.requestHost.length === 0 || props.stagedHeaders !== null && !isSerializedHeaders(props.stagedHeaders)) return false;
16
35
  if (props.kind === "pages-api") return typeof props.apiUrl === "string";
17
36
  if (props.kind === "pages-prerender-discovery") return true;
18
- if (props.kind !== "pages-page" || typeof props.resolvedUrl !== "string") return false;
37
+ if (props.kind !== "pages-page" || typeof props.resolvedUrl !== "string" || typeof props.sharedAcrossHosts !== "boolean") return false;
19
38
  if (props.renderOptions === null) return true;
20
39
  if (!props.renderOptions || typeof props.renderOptions !== "object") return false;
21
40
  const options = props.renderOptions;
22
41
  return (options.isDataReq === void 0 || typeof options.isDataReq === "boolean") && (options.renderErrorPageOnMiss === void 0 || typeof options.renderErrorPageOnMiss === "boolean") && (options.originalUrl === void 0 || typeof options.originalUrl === "string");
23
42
  }
24
43
  //#endregion
25
- export { PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER, PAGES_RESPONSE_STAGE_PROTOCOL_VERSION, isPagesResponseStageProps };
44
+ export { PAGES_RESPONSE_STAGE_POLICY_OWNER_HEADER, PAGES_RESPONSE_STAGE_PROTOCOL_VERSION, createSharedPagesPageCacheIdentity, isPagesResponseStageProps };
@@ -1,5 +1,5 @@
1
1
  import { shareIsolatePromise } from "../server/isolate-shared-promise.js";
2
- import { readCacheControlNumberField, readCacheControlRevalidateField } from "../utils/cache-control-metadata.js";
2
+ import { readCacheControlNumberField, readCacheControlRevalidateField, readCacheTimestamp } from "../utils/cache-control-metadata.js";
3
3
  //#region src/shims/cache-handler.ts
4
4
  var NoOpCacheHandler = class {
5
5
  async get(_key, _ctx) {
@@ -122,7 +122,7 @@ var MemoryCacheHandler = class {
122
122
  if (data && "revalidate" in data && typeof data.revalidate === "number") effectiveRevalidate = data.revalidate;
123
123
  else if (data && "revalidate" in data && data.revalidate === false) effectiveRevalidate ??= false;
124
124
  if (effectiveRevalidate === 0) return;
125
- const now = Date.now();
125
+ const now = readCacheTimestamp(ctx, Date.now());
126
126
  const revalidateAt = typeof effectiveRevalidate === "number" && effectiveRevalidate > 0 ? now + effectiveRevalidate * 1e3 : null;
127
127
  const expireAt = typeof effectiveExpire === "number" && effectiveExpire > 0 ? now + effectiveExpire * 1e3 : null;
128
128
  const cacheControl = typeof effectiveRevalidate === "number" || effectiveRevalidate === false ? {
@@ -8,21 +8,6 @@ export type CacheLifeConfig = {
8
8
  export declare const cacheLifeProfiles: Record<string, CacheLifeConfig>;
9
9
  export type UnstableCacheRevalidationMode = "foreground" | "background";
10
10
  export type ActionRevalidationKind = 0 | 1 | 2;
11
- export type UnstableCacheObservation = Readonly<{
12
- kind: "unstable_cache";
13
- keyHash: string;
14
- revalidate: number | false | null;
15
- tagCount: number;
16
- tagHash: string | null;
17
- }>;
18
- /**
19
- * Collects the cacheLife claims made anywhere below the scope that installed
20
- * it, including nested scopes that reset `requestScopedCacheLife`. Shallow
21
- * scope clones share it by reference.
22
- */
23
- export type CacheLifeSink = {
24
- cacheLife: CacheLifeConfig | null;
25
- };
26
11
  type TagInvalidationDurations = {
27
12
  expire?: number;
28
13
  } | undefined;
@@ -34,12 +19,17 @@ export type PendingCdnTagInvalidation = Readonly<{
34
19
  }>;
35
20
  export type CacheState = {
36
21
  actionRevalidationKind: ActionRevalidationKind;
37
- cacheLifeSink: CacheLifeSink | null;
22
+ /**
23
+ * The request is an authorized on-demand revalidation (`res.revalidate()`).
24
+ * Like Next.js's `workStore.isOnDemandRevalidate`, the data caches then skip
25
+ * their reads but still execute and store fresh results. Set once when the
26
+ * request context is created; nested scopes inherit it.
27
+ */
28
+ isOnDemandRevalidate: boolean;
38
29
  pendingCdnTagInvalidations: PendingCdnTagInvalidation[];
39
30
  pendingRevalidatedTags: Set<string>;
40
31
  pendingRevalidations: Set<Promise<void>>;
41
32
  requestScopedCacheLife: CacheLifeConfig | null;
42
- unstableCacheObservations: Map<string, UnstableCacheObservation>;
43
33
  unstableCacheRevalidation: UnstableCacheRevalidationMode;
44
34
  };
45
35
  export declare const ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC = 1;
@@ -47,6 +37,15 @@ export declare const ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;
47
37
  export declare function _runWithCacheState<T>(fn: () => Promise<T>): Promise<T>;
48
38
  export declare function _runWithCacheState<T>(fn: () => T | Promise<T>): T | Promise<T>;
49
39
  export declare function _initRequestScopedCacheState(): void;
40
+ /**
41
+ * Whether the current request is an authorized on-demand revalidation, so the
42
+ * data caches must skip their reads (not their writes). Mirrors Next.js's
43
+ * `workStore.isOnDemandRevalidate` checks in use-cache-wrapper, patch-fetch
44
+ * and unstable-cache.
45
+ *
46
+ * @internal
47
+ */
48
+ export declare function _isOnDemandRevalidate(): boolean;
50
49
  export declare function markActionRevalidation(kind: ActionRevalidationKind): void;
51
50
  export declare function getAndClearActionRevalidationKind(): ActionRevalidationKind;
52
51
  /** @internal */
@@ -114,11 +113,9 @@ export declare function _captureRequestScopedCacheLifeAccessors(): {
114
113
  consume: () => CacheLifeConfig | null;
115
114
  peek: () => CacheLifeConfig | null;
116
115
  };
117
- export declare function recordUnstableCacheObservation(observation: UnstableCacheObservation): void;
118
- export declare function _peekUnstableCacheObservations(): UnstableCacheObservation[];
119
116
  /**
120
117
  * Run `fn` with the request's cache settings, including its revalidation mode,
121
- * but its own record of cache lives and unstable_cache observations, so work
118
+ * but its own record of cache lives, so work
122
119
  * done on the request's behalf after the fact leaves the request untouched.
123
120
  */
124
121
  export declare function runWithDetachedCacheObservations<T>(fn: () => Promise<T>): Promise<T>;
@@ -48,12 +48,11 @@ const ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC = 1;
48
48
  const ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;
49
49
  const fallbackState = globalState[FALLBACK_KEY] ??= {
50
50
  actionRevalidationKind: ACTION_DID_NOT_REVALIDATE,
51
- cacheLifeSink: null,
51
+ isOnDemandRevalidate: false,
52
52
  pendingCdnTagInvalidations: [],
53
53
  pendingRevalidatedTags: /* @__PURE__ */ new Set(),
54
54
  pendingRevalidations: /* @__PURE__ */ new Set(),
55
55
  requestScopedCacheLife: null,
56
- unstableCacheObservations: /* @__PURE__ */ new Map(),
57
56
  unstableCacheRevalidation: "foreground"
58
57
  };
59
58
  function getCacheState() {
@@ -63,19 +62,16 @@ function getCacheState() {
63
62
  function _runWithCacheState(fn) {
64
63
  if (isInsideUnifiedScope()) return runWithUnifiedStateMutation((context) => {
65
64
  context.actionRevalidationKind = ACTION_DID_NOT_REVALIDATE;
66
- context.cacheLifeSink = null;
67
65
  context.requestScopedCacheLife = null;
68
- context.unstableCacheObservations = /* @__PURE__ */ new Map();
69
66
  context.unstableCacheRevalidation = "foreground";
70
67
  }, fn);
71
68
  const state = {
72
69
  actionRevalidationKind: ACTION_DID_NOT_REVALIDATE,
73
- cacheLifeSink: null,
70
+ isOnDemandRevalidate: false,
74
71
  pendingCdnTagInvalidations: [],
75
72
  pendingRevalidatedTags: /* @__PURE__ */ new Set(),
76
73
  pendingRevalidations: /* @__PURE__ */ new Set(),
77
74
  requestScopedCacheLife: null,
78
- unstableCacheObservations: /* @__PURE__ */ new Map(),
79
75
  unstableCacheRevalidation: "foreground"
80
76
  };
81
77
  return cacheAls.run(state, fn);
@@ -84,7 +80,17 @@ function _initRequestScopedCacheState() {
84
80
  const state = getCacheState();
85
81
  state.actionRevalidationKind = ACTION_DID_NOT_REVALIDATE;
86
82
  state.requestScopedCacheLife = null;
87
- state.unstableCacheObservations = /* @__PURE__ */ new Map();
83
+ }
84
+ /**
85
+ * Whether the current request is an authorized on-demand revalidation, so the
86
+ * data caches must skip their reads (not their writes). Mirrors Next.js's
87
+ * `workStore.isOnDemandRevalidate` checks in use-cache-wrapper, patch-fetch
88
+ * and unstable-cache.
89
+ *
90
+ * @internal
91
+ */
92
+ function _isOnDemandRevalidate() {
93
+ return getCacheState().isOnDemandRevalidate === true;
88
94
  }
89
95
  function markActionRevalidation(kind) {
90
96
  if (getHeadersAccessPhase() !== "action") return;
@@ -224,7 +230,6 @@ function mergeCacheLife(current, config) {
224
230
  function _setRequestScopedCacheLife(config) {
225
231
  const state = getCacheState();
226
232
  state.requestScopedCacheLife = mergeCacheLife(state.requestScopedCacheLife, config);
227
- if (state.cacheLifeSink) state.cacheLifeSink.cacheLife = mergeCacheLife(state.cacheLifeSink.cacheLife, config);
228
233
  }
229
234
  function _peekRequestScopedCacheLife() {
230
235
  const config = getCacheState().requestScopedCacheLife;
@@ -257,32 +262,22 @@ function _captureRequestScopedCacheLifeAccessors() {
257
262
  }
258
263
  };
259
264
  }
260
- function recordUnstableCacheObservation(observation) {
261
- getCacheState().unstableCacheObservations.set(observation.keyHash, observation);
262
- }
263
- function _peekUnstableCacheObservations() {
264
- return [...getCacheState().unstableCacheObservations.values()].sort((a, b) => a.keyHash.localeCompare(b.keyHash));
265
- }
266
265
  /**
267
266
  * Run `fn` with the request's cache settings, including its revalidation mode,
268
- * but its own record of cache lives and unstable_cache observations, so work
267
+ * but its own record of cache lives, so work
269
268
  * done on the request's behalf after the fact leaves the request untouched.
270
269
  */
271
270
  function runWithDetachedCacheObservations(fn) {
272
271
  if (isInsideUnifiedScope()) return runWithUnifiedStateMutation((context) => {
273
- context.cacheLifeSink = null;
274
272
  context.requestScopedCacheLife = null;
275
- context.unstableCacheObservations = /* @__PURE__ */ new Map();
276
273
  }, fn);
277
274
  return cacheAls.run({
278
275
  ...getCacheState(),
279
- cacheLifeSink: null,
280
- requestScopedCacheLife: null,
281
- unstableCacheObservations: /* @__PURE__ */ new Map()
276
+ requestScopedCacheLife: null
282
277
  }, fn);
283
278
  }
284
279
  function shouldServeStaleUnstableCacheEntry() {
285
280
  return getCacheState().unstableCacheRevalidation === "background";
286
281
  }
287
282
  //#endregion
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 };
283
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _isOnDemandRevalidate, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, getRegisteredCacheContext, markActionRevalidation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry };