vinext 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/README.md +25 -22
  2. package/THIRD_PARTY_LICENSES.md +47 -0
  3. package/dist/_virtual/_rolldown/runtime.js +3 -2
  4. package/dist/build/prerender.js +4 -3
  5. package/dist/build/report.d.ts +0 -14
  6. package/dist/build/report.js +1 -51
  7. package/dist/check.js +4 -7
  8. package/dist/cli.js +3 -0
  9. package/dist/client/dev-error-overlay-store.d.ts +1 -0
  10. package/dist/client/dev-error-overlay.d.ts +2 -0
  11. package/dist/client/dev-error-overlay.js +20 -4
  12. package/dist/client/navigation-runtime.d.ts +13 -1
  13. package/dist/client/navigation-runtime.js +1 -1
  14. package/dist/config/config-matchers.js +2 -0
  15. package/dist/config/destination-params.d.ts +2 -1
  16. package/dist/config/destination-params.js +52 -2
  17. package/dist/config/next-config.d.ts +25 -0
  18. package/dist/config/next-config.js +82 -4
  19. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-codec.js +327 -0
  20. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/dbcs-data.js +123 -0
  21. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/index.js +29 -0
  22. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/internal.js +134 -0
  23. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-codec.js +50 -0
  24. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data-generated.js +455 -0
  25. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/sbcs-data.js +147 -0
  26. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/big5-added.json.js +169 -0
  27. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp936.json.js +2584 -0
  28. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp949.json.js +2220 -0
  29. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/cp950.json.js +227 -0
  30. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/eucjp.json.js +425 -0
  31. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gb18030-ranges.json.js +427 -0
  32. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/gbk-added.json.js +229 -0
  33. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/tables/shiftjis.json.js +292 -0
  34. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf16.js +106 -0
  35. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/encodings/utf7.js +182 -0
  36. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/bom-handling.js +42 -0
  37. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/extend-node.js +147 -0
  38. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/index.js +94 -0
  39. package/dist/deps/.pnpm/iconv-lite@0.4.24/deps/iconv-lite/lib/streams.js +95 -0
  40. package/dist/deps/.pnpm/safer-buffer@2.1.2/deps/safer-buffer/safer.js +45 -0
  41. package/dist/entries/app-rsc-entry.js +17 -49
  42. package/dist/entries/app-rsc-manifest.js +3 -4
  43. package/dist/entries/app-ssr-entry.js +1 -0
  44. package/dist/entries/pages-server-entry.js +36 -9
  45. package/dist/index.js +112 -90
  46. package/dist/init-cloudflare.d.ts +1 -3
  47. package/dist/init-cloudflare.js +10 -48
  48. package/dist/init-platform.d.ts +2 -0
  49. package/dist/init-platform.js +4 -0
  50. package/dist/plugins/extensionless-dynamic-import.js +6 -2
  51. package/dist/plugins/next-intl-catalog.d.ts +12 -0
  52. package/dist/plugins/next-intl-catalog.js +85 -0
  53. package/dist/plugins/pages-node-externals.d.ts +1 -0
  54. package/dist/plugins/pages-node-externals.js +1 -1
  55. package/dist/plugins/strip-server-exports.js +2 -1
  56. package/dist/plugins/use-cache-callable.d.ts +1 -1
  57. package/dist/plugins/use-cache-callable.js +136 -94
  58. package/dist/routing/app-route-graph.d.ts +6 -0
  59. package/dist/routing/app-route-graph.js +9 -1
  60. package/dist/server/api-handler.js +21 -5
  61. package/dist/server/app-bfcache-identity.js +1 -1
  62. package/dist/server/app-browser-entry.js +86 -66
  63. package/dist/server/app-browser-history-controller.d.ts +5 -4
  64. package/dist/server/app-browser-history-controller.js +93 -15
  65. package/dist/server/app-browser-navigation-controller.d.ts +2 -2
  66. package/dist/server/app-browser-navigation-controller.js +1 -2
  67. package/dist/server/app-browser-rsc-redirect.js +7 -7
  68. package/dist/server/app-browser-server-action-client.d.ts +2 -0
  69. package/dist/server/app-browser-server-action-client.js +5 -4
  70. package/dist/server/app-browser-state.d.ts +2 -4
  71. package/dist/server/app-browser-state.js +9 -4
  72. package/dist/server/app-browser-supplemental-refresh.js +16 -1
  73. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  74. package/dist/server/app-browser-visible-commit.js +4 -14
  75. package/dist/server/app-elements-wire.d.ts +6 -38
  76. package/dist/server/app-elements-wire.js +4 -41
  77. package/dist/server/app-elements.d.ts +2 -2
  78. package/dist/server/app-elements.js +2 -2
  79. package/dist/server/app-history-state.d.ts +2 -2
  80. package/dist/server/app-history-state.js +5 -2
  81. package/dist/server/app-mounted-layouts.d.ts +21 -0
  82. package/dist/server/app-mounted-layouts.js +67 -0
  83. package/dist/server/app-optimistic-routing.d.ts +19 -1
  84. package/dist/server/app-optimistic-routing.js +43 -4
  85. package/dist/server/app-page-boundary-render.js +10 -14
  86. package/dist/server/app-page-boundary.js +2 -2
  87. package/dist/server/app-page-cache-finalizer.d.ts +12 -1
  88. package/dist/server/app-page-cache-finalizer.js +70 -29
  89. package/dist/server/app-page-cache.d.ts +13 -1
  90. package/dist/server/app-page-cache.js +31 -9
  91. package/dist/server/app-page-dispatch.d.ts +5 -11
  92. package/dist/server/app-page-dispatch.js +84 -155
  93. package/dist/server/app-page-element-builder.d.ts +2 -2
  94. package/dist/server/app-page-element-builder.js +13 -12
  95. package/dist/server/app-page-execution.d.ts +17 -52
  96. package/dist/server/app-page-execution.js +102 -95
  97. package/dist/server/app-page-head.d.ts +13 -0
  98. package/dist/server/app-page-head.js +44 -16
  99. package/dist/server/app-page-http-access-fallback-metadata.d.ts +1 -0
  100. package/dist/server/app-page-http-access-fallback-metadata.js +2 -2
  101. package/dist/server/app-page-layout-gate.d.ts +35 -0
  102. package/dist/server/app-page-layout-gate.js +50 -0
  103. package/dist/server/app-page-params.d.ts +0 -1
  104. package/dist/server/app-page-params.js +1 -14
  105. package/dist/server/app-page-probe.d.ts +3 -21
  106. package/dist/server/app-page-probe.js +4 -37
  107. package/dist/server/app-page-render.d.ts +2 -13
  108. package/dist/server/app-page-render.js +103 -234
  109. package/dist/server/app-page-request.d.ts +1 -3
  110. package/dist/server/app-page-request.js +1 -1
  111. package/dist/server/app-page-response.js +3 -3
  112. package/dist/server/app-page-route-wiring.d.ts +13 -6
  113. package/dist/server/app-page-route-wiring.js +86 -63
  114. package/dist/server/app-page-rsc-render-status.d.ts +27 -1
  115. package/dist/server/app-page-rsc-render-status.js +121 -13
  116. package/dist/server/app-page-stream.d.ts +12 -8
  117. package/dist/server/app-page-stream.js +4 -3
  118. package/dist/server/app-pages-bridge.d.ts +4 -0
  119. package/dist/server/app-pages-bridge.js +7 -3
  120. package/dist/server/app-render-dependency.d.ts +11 -4
  121. package/dist/server/app-render-dependency.js +86 -38
  122. package/dist/server/app-request-stage-dispatch.js +1 -1
  123. package/dist/server/app-request-stage-independent-entry.js +1 -1
  124. package/dist/server/app-route-handler-cache.js +2 -2
  125. package/dist/server/app-route-handler-dispatch.js +14 -9
  126. package/dist/server/app-route-handler-execution.js +11 -5
  127. package/dist/server/app-route-handler-policy.d.ts +1 -1
  128. package/dist/server/app-route-handler-policy.js +3 -3
  129. package/dist/server/app-route-handler-response.js +1 -1
  130. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  131. package/dist/server/app-route-handler-runtime.js +28 -11
  132. package/dist/server/app-router-entry.js +1 -1
  133. package/dist/server/app-rsc-cache-busting.d.ts +12 -12
  134. package/dist/server/app-rsc-cache-busting.js +31 -72
  135. package/dist/server/app-rsc-error-handler.d.ts +2 -1
  136. package/dist/server/app-rsc-error-handler.js +10 -2
  137. package/dist/server/app-rsc-errors.d.ts +8 -0
  138. package/dist/server/app-rsc-errors.js +11 -1
  139. package/dist/server/app-rsc-handler.d.ts +5 -5
  140. package/dist/server/app-rsc-handler.js +80 -83
  141. package/dist/server/app-rsc-request-normalization.d.ts +10 -16
  142. package/dist/server/app-rsc-request-normalization.js +12 -18
  143. package/dist/server/app-rsc-response-finalizer.js +2 -2
  144. package/dist/server/app-rsc-response-stage.js +9 -15
  145. package/dist/server/app-server-action-execution.d.ts +3 -1
  146. package/dist/server/app-server-action-execution.js +15 -8
  147. package/dist/server/app-ssr-entry.d.ts +8 -0
  148. package/dist/server/app-ssr-entry.js +22 -16
  149. package/dist/server/app-ssr-stream.js +1 -1
  150. package/dist/server/app-visited-response-cache.d.ts +36 -1
  151. package/dist/server/app-visited-response-cache.js +46 -5
  152. package/dist/server/app-worker-stages.d.ts +37 -6
  153. package/dist/server/app-worker-stages.js +81 -9
  154. package/dist/server/artifact-compatibility.d.ts +0 -1
  155. package/dist/server/artifact-compatibility.js +1 -10
  156. package/dist/server/bfcache-identity.d.ts +7 -0
  157. package/dist/server/bfcache-identity.js +10 -1
  158. package/dist/server/cache-proof.d.ts +1 -115
  159. package/dist/server/cache-proof.js +1 -544
  160. package/dist/server/cacheability-manifest.js +1 -2
  161. package/dist/server/cacheability-request.js +13 -8
  162. package/dist/server/dev-origin-check.d.ts +2 -1
  163. package/dist/server/dev-origin-check.js +2 -1
  164. package/dist/server/dev-server.js +6 -4
  165. package/dist/server/headers.d.ts +6 -3
  166. package/dist/server/headers.js +9 -5
  167. package/dist/server/image-optimization.d.ts +5 -0
  168. package/dist/server/image-optimization.js +6 -1
  169. package/dist/server/isr-cache.d.ts +8 -2
  170. package/dist/server/isr-cache.js +30 -2
  171. package/dist/server/metadata-route-response.js +32 -15
  172. package/dist/server/middleware-matcher.js +19 -6
  173. package/dist/server/middleware-runtime.js +6 -2
  174. package/dist/server/multi-stage.d.ts +57 -12
  175. package/dist/server/multi-stage.js +13 -1
  176. package/dist/server/navigation-planner.js +1 -1
  177. package/dist/server/pages-api-route.js +28 -19
  178. package/dist/server/pages-media-type.d.ts +21 -1
  179. package/dist/server/pages-media-type.js +69 -5
  180. package/dist/server/pages-node-compat.d.ts +1 -1
  181. package/dist/server/pages-node-compat.js +54 -27
  182. package/dist/server/pages-page-data.js +2 -1
  183. package/dist/server/pages-page-handler.js +28 -26
  184. package/dist/server/pages-page-response.d.ts +9 -0
  185. package/dist/server/pages-page-response.js +18 -4
  186. package/dist/server/pages-request-pipeline.d.ts +8 -0
  187. package/dist/server/pages-request-pipeline.js +2 -2
  188. package/dist/server/pages-request-stage-entry.js +36 -14
  189. package/dist/server/pages-response-stage-entry.js +9 -3
  190. package/dist/server/pages-router-entry.js +3 -2
  191. package/dist/server/prod-server.d.ts +2 -2
  192. package/dist/server/prod-server.js +47 -9
  193. package/dist/server/request-error-log.d.ts +9 -0
  194. package/dist/server/request-error-log.js +13 -0
  195. package/dist/server/request-pipeline.d.ts +15 -4
  196. package/dist/server/request-pipeline.js +43 -15
  197. package/dist/server/rsc-completion-metadata.js +3 -0
  198. package/dist/server/shared-cache-identity.d.ts +23 -0
  199. package/dist/server/shared-cache-identity.js +36 -0
  200. package/dist/server/static-file-cache.d.ts +9 -0
  201. package/dist/server/static-file-cache.js +13 -2
  202. package/dist/server/worker-stages.d.ts +16 -3
  203. package/dist/server/worker-stages.js +23 -4
  204. package/dist/shims/cache-handler.js +2 -2
  205. package/dist/shims/cache-request-state.d.ts +23 -26
  206. package/dist/shims/cache-request-state.js +22 -57
  207. package/dist/shims/cache-runtime.d.ts +3 -1
  208. package/dist/shims/cache-runtime.js +94 -48
  209. package/dist/shims/cache.d.ts +16 -10
  210. package/dist/shims/cache.js +47 -36
  211. package/dist/shims/cacheability-classification.d.ts +2 -0
  212. package/dist/shims/cdn-cache.d.ts +22 -5
  213. package/dist/shims/client-hook-error.d.ts +2 -0
  214. package/dist/shims/client-hook-error.js +16 -1
  215. package/dist/shims/client-page-root.js +2 -2
  216. package/dist/shims/dynamic.js +9 -6
  217. package/dist/shims/error-boundary.js +2 -2
  218. package/dist/shims/fetch-cache-state.d.ts +5 -1
  219. package/dist/shims/fetch-cache-state.js +1 -3
  220. package/dist/shims/fetch-cache.d.ts +2 -1
  221. package/dist/shims/fetch-cache.js +39 -25
  222. package/dist/shims/form.js +2 -2
  223. package/dist/shims/headers.d.ts +10 -24
  224. package/dist/shims/headers.js +22 -70
  225. package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
  226. package/dist/shims/internal/navigation-untracked.js +1 -1
  227. package/dist/shims/link.js +8 -11
  228. package/dist/shims/metadata.d.ts +23 -21
  229. package/dist/shims/metadata.js +62 -24
  230. package/dist/shims/navigation-server.js +1 -1
  231. package/dist/shims/navigation.d.ts +74 -7
  232. package/dist/shims/navigation.js +184 -31
  233. package/dist/shims/navigation.react-server.js +2 -2
  234. package/dist/shims/request-state-types.d.ts +3 -2
  235. package/dist/shims/slot.d.ts +16 -0
  236. package/dist/shims/slot.js +44 -9
  237. package/dist/shims/unified-request-context.d.ts +7 -1
  238. package/dist/shims/unified-request-context.js +3 -3
  239. package/dist/utils/cache-control-metadata.d.ts +5 -0
  240. package/dist/utils/cache-control-metadata.js +9 -1
  241. package/dist/utils/cache-life-profiles.d.ts +33 -0
  242. package/dist/utils/cache-life-profiles.js +111 -0
  243. package/dist/utils/middleware-request-headers.d.ts +14 -3
  244. package/dist/utils/middleware-request-headers.js +54 -7
  245. package/dist/utils/project.d.ts +2 -2
  246. package/dist/utils/project.js +3 -16
  247. package/dist/utils/regex.d.ts +5 -0
  248. package/dist/utils/regex.js +11 -1
  249. package/dist/utils/text-stream.d.ts +10 -5
  250. package/dist/utils/text-stream.js +6 -6
  251. package/package.json +2 -1
  252. package/dist/build/layout-classification-types.d.ts +0 -60
  253. package/dist/build/layout-classification-types.js +0 -1
  254. package/dist/build/layout-classification.d.ts +0 -62
  255. package/dist/build/layout-classification.js +0 -98
  256. package/dist/build/route-classification-injector.d.ts +0 -33
  257. package/dist/build/route-classification-injector.js +0 -59
  258. package/dist/build/route-classification-manifest.d.ts +0 -54
  259. package/dist/build/route-classification-manifest.js +0 -143
  260. package/dist/server/app-browser-client-reuse-manifest.d.ts +0 -11
  261. package/dist/server/app-browser-client-reuse-manifest.js +0 -101
  262. package/dist/server/app-layout-param-observation.d.ts +0 -42
  263. package/dist/server/app-layout-param-observation.js +0 -168
  264. package/dist/server/client-reuse-manifest.d.ts +0 -103
  265. package/dist/server/client-reuse-manifest.js +0 -209
  266. package/dist/server/skip-cache-proof.d.ts +0 -60
  267. package/dist/server/skip-cache-proof.js +0 -168
  268. package/dist/server/static-layout-client-reuse-proof.d.ts +0 -14
  269. package/dist/server/static-layout-client-reuse-proof.js +0 -33
  270. package/dist/utils/sorted-array.d.ts +0 -8
  271. package/dist/utils/sorted-array.js +0 -20
@@ -1,6 +1,7 @@
1
1
  import { getOrCreateAls } from "./internal/als-registry.js";
2
2
  import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation } from "./unified-request-context.js";
3
3
  import { VINEXT_RSC_MARKER_HEADER } from "../server/headers.js";
4
+ import { isInsideWorkStoreScope } from "./internal/work-store-scope.js";
4
5
  import { workUnitAsyncStorage } from "./internal/work-unit-async-storage.js";
5
6
  import { isRouteCacheabilityProbe, recordRouteCacheabilityProbeBailout } from "./cacheability-classification.js";
6
7
  import { shareIsolatePromise } from "../server/isolate-shared-promise.js";
@@ -11,7 +12,7 @@ import { createPprFallbackShellSuspensePromise, trackPprFallbackShellCacheTask }
11
12
  import { createUseCacheAccessError, markDynamicUsage, recordInvalidDynamicUsageError } from "./internal/headers-state.js";
12
13
  import { isDraftModeEnabled } from "./headers.js";
13
14
  import { _registerCacheContextAccessor } from "./internal/cache-context-accessor.js";
14
- import { _hasPendingRevalidatedTag, _setRequestScopedCacheLife, cacheLifeProfiles, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
15
+ import { _hasPendingRevalidatedTag, _isOnDemandRevalidate, _setRequestScopedCacheLife, cacheLifeProfiles, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
15
16
  import "./fetch-cache.js";
16
17
  import { hasUseCacheLayoutMarker, hasUseCachePageMarker, isUseCacheFunctionReference, withoutUseCacheSegmentMarker } from "./internal/app-page-props-cache-key.js";
17
18
  import { isWellKnownProperty } from "./internal/thenable-well-known-properties.js";
@@ -189,22 +190,46 @@ async function serializeCacheResult(result, rsc) {
189
190
  throw serializationError?.value ?? error;
190
191
  }
191
192
  }
193
+ const CACHE_LIFE_FIELDS = [
194
+ "stale",
195
+ "revalidate",
196
+ "expire"
197
+ ];
198
+ /** Per field, the smallest value any config sets. */
199
+ function minimumCacheLife(configs) {
200
+ const result = {};
201
+ for (const config of configs) for (const field of CACHE_LIFE_FIELDS) {
202
+ const value = config[field];
203
+ const current = result[field];
204
+ if (value !== void 0 && (current === void 0 || value < current)) result[field] = value;
205
+ }
206
+ return result;
207
+ }
192
208
  /**
193
- * Resolve collected cacheLife configs into a single effective config.
194
- * The "minimum-wins" rule: if multiple cacheLife() calls are made,
195
- * each field takes the smallest value across all calls.
209
+ * Resolve a cache scope's effective life, like Next.js's `collectResult`
210
+ * (use-cache-wrapper.ts). A field set by `cacheLife()` takes the minimum
211
+ * across those calls and wins over nested caches. Any other field takes the
212
+ * minimum of the default profile and the nested caches' lives.
196
213
  */
197
- function resolveCacheLife(configs) {
198
- if (configs.length === 0) return { ...cacheLifeProfiles.default };
199
- if (configs.length === 1) return { ...configs[0] };
214
+ function resolveCacheLife(ctx) {
215
+ const explicit = minimumCacheLife(ctx.lifeConfigs);
216
+ const collected = minimumCacheLife([cacheLifeProfiles.default, ...ctx.nestedLifeConfigs]);
200
217
  const result = {};
201
- for (const config of configs) {
202
- if (config.stale !== void 0) result.stale = result.stale !== void 0 ? Math.min(result.stale, config.stale) : config.stale;
203
- if (config.revalidate !== void 0) result.revalidate = result.revalidate !== void 0 ? Math.min(result.revalidate, config.revalidate) : config.revalidate;
204
- if (config.expire !== void 0) result.expire = result.expire !== void 0 ? Math.min(result.expire, config.expire) : config.expire;
218
+ for (const field of CACHE_LIFE_FIELDS) {
219
+ const value = explicit[field] ?? collected[field];
220
+ if (value !== void 0) result[field] = value;
205
221
  }
206
222
  return result;
207
223
  }
224
+ /**
225
+ * Hand a finished cache scope's life to what encloses it, like Next.js's
226
+ * `propagateCacheEntryMetadata`: an enclosing cache collects it as a nested
227
+ * life, and only an outermost cache reaches the request.
228
+ */
229
+ function propagateCacheLife(parentCtx, life) {
230
+ if (parentCtx) parentCtx.nestedLifeConfigs.push(life);
231
+ else _setRequestScopedCacheLife(life);
232
+ }
208
233
  const _PRIVATE_FALLBACK_KEY = Symbol.for("vinext.cacheRuntime.privateFallback");
209
234
  const _g = globalThis;
210
235
  const _privateAls = getOrCreateAls("vinext.cacheRuntime.privateAls");
@@ -389,7 +414,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
389
414
  if (knownRootParamNames && rootParams) cacheKey += computeRootParamsCacheKeySuffix(rootParams, knownRootParamNames);
390
415
  const softTags = getCurrentFetchSoftTags();
391
416
  let existing = null;
392
- if (!_hasPendingRevalidatedTag(softTags)) try {
417
+ if (!_hasPendingRevalidatedTag(softTags) && !_isOnDemandRevalidate()) try {
393
418
  existing = await handler.get(cacheKey, {
394
419
  kind: "FETCH",
395
420
  softTags
@@ -398,8 +423,10 @@ function registerCachedFunction(fn, id, variant, options = {}) {
398
423
  console.error("[vinext] use cache: handler.get failed; treating as a cache miss:", error);
399
424
  }
400
425
  const redirectValue = existing?.value;
426
+ let staleRedirect;
401
427
  if (isRootParamRedirect(existing) && isServableCacheState(existing?.cacheState) && rootParams && redirectValue?.kind === "FETCH" && !_hasPendingRevalidatedTag([...redirectValue.tags ?? [], ...softTags])) {
402
428
  cacheKey = coarseCacheKey + computeRootParamsCacheKeySuffix(rootParams, addKnownRootParamNames(id, rootParamNamesFromTags(redirectValue.tags)));
429
+ if (existing?.cacheState) staleRedirect = existing;
403
430
  try {
404
431
  existing = await handler.get(cacheKey, {
405
432
  kind: "FETCH",
@@ -410,13 +437,11 @@ function registerCachedFunction(fn, id, variant, options = {}) {
410
437
  existing = null;
411
438
  }
412
439
  }
413
- const generate = async (propagate) => {
440
+ const generate = async (propagate, staleEntry) => {
441
+ const timestamp = Date.now();
414
442
  const { result, ctx, effectiveLife, collectedResult } = await runCachedFunctionWithContext(callFn, callArgs, cacheVariant, (value) => serializeCacheResult(value, rsc));
415
443
  const rootParamNames = ctx.readRootParamNames && ctx.readRootParamNames.size > 0 ? addKnownRootParamNames(id, ctx.readRootParamNames) : knownRootParamsByFunctionId.get(id);
416
- if (propagate) {
417
- recordRequestScopedCacheLife(effectiveLife);
418
- propagateCacheTagsToRequest(ctx.tags);
419
- }
444
+ if (propagate) propagateCacheTagsToRequest(ctx.tags);
420
445
  const revalidateSeconds = effectiveLife.revalidate ?? cacheLifeProfiles.default.revalidate ?? 900;
421
446
  if (collectedResult?.cacheEntry) try {
422
447
  let cacheFunctionInvocation;
@@ -442,6 +467,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
442
467
  const cacheContext = {
443
468
  fetchCache: true,
444
469
  tags: ctx.tags,
470
+ timestamp,
445
471
  ...cacheFunctionInvocation ? { cacheFunctionInvocation } : {},
446
472
  cacheControl: {
447
473
  revalidate: revalidateSeconds,
@@ -449,25 +475,39 @@ function registerCachedFunction(fn, id, variant, options = {}) {
449
475
  stale: effectiveLife.stale
450
476
  }
451
477
  };
478
+ let write;
452
479
  if (rootParamNames && rootParamNames.size > 0 && rootParams) {
453
480
  const specificCacheKey = coarseCacheKey + computeRootParamsCacheKeySuffix(rootParams, rootParamNames);
454
481
  const redirectTags = [...ctx.tags, ...[...rootParamNames].map((name) => ROOT_PARAM_TAG_PREFIX + name)];
455
- await handler.set(coarseCacheKey, {
456
- kind: "FETCH",
457
- data: {
458
- headers: { [ROOT_PARAM_REDIRECT_HEADER]: "1" },
459
- body: "",
460
- url: coarseCacheKey
461
- },
462
- tags: redirectTags,
463
- revalidate: revalidateSeconds
464
- }, {
482
+ try {
483
+ await handler.set(coarseCacheKey, {
484
+ kind: "FETCH",
485
+ data: {
486
+ headers: { [ROOT_PARAM_REDIRECT_HEADER]: "1" },
487
+ body: "",
488
+ url: coarseCacheKey
489
+ },
490
+ tags: redirectTags,
491
+ revalidate: revalidateSeconds
492
+ }, {
493
+ ...cacheContext,
494
+ tags: redirectTags,
495
+ ...coarseCacheKey === cacheKey ? staleEntry && { staleEntry } : staleRedirect && { staleEntry: staleRedirect }
496
+ });
497
+ } catch {}
498
+ cacheValue.data.url = specificCacheKey;
499
+ write = handler.set(specificCacheKey, cacheValue, {
465
500
  ...cacheContext,
466
- tags: redirectTags
501
+ ...staleEntry && specificCacheKey === cacheKey ? { staleEntry } : {}
467
502
  });
468
- cacheValue.data.url = specificCacheKey;
469
- await handler.set(specificCacheKey, cacheValue, cacheContext);
470
- } else await handler.set(cacheKey, cacheValue, cacheContext);
503
+ } else write = handler.set(cacheKey, cacheValue, {
504
+ ...cacheContext,
505
+ ...staleEntry ? { staleEntry } : {}
506
+ });
507
+ const stored = write.then(() => void 0, () => {});
508
+ if (propagate && isInsideWorkStoreScope()) {
509
+ if (isInsideUnifiedScope()) getRequestContext().executionContext?.waitUntil(stored);
510
+ } else await stored;
471
511
  } catch {}
472
512
  return collectedResult ? collectedResult.result : result;
473
513
  };
@@ -476,10 +516,13 @@ function registerCachedFunction(fn, id, variant, options = {}) {
476
516
  propagateCacheTagsToRequest(existing.value.tags);
477
517
  const result = await rsc.createFromReadableStream(uint8ToStream(base64ToUint8(existing.value.data.body)), {}, { preserveServerReferences: true });
478
518
  recordRequestScopedCacheControl(existing.cacheControl);
479
- if (existing.cacheState === "stale") regenerateInBackground(id, () => generate(false));
519
+ if (existing.cacheState === "stale") {
520
+ const staleEntry = existing;
521
+ regenerateInBackground(id, () => generate(false, staleEntry));
522
+ }
480
523
  return result;
481
524
  } catch {}
482
- return generate(true);
525
+ return generate(true, existing?.cacheState ? existing : void 0);
483
526
  }, cacheVariant);
484
527
  };
485
528
  const cachedFn = memoizeInCacheScope((...args) => invoke(args));
@@ -525,16 +568,12 @@ function recordRequestScopedCacheControl(cacheControl) {
525
568
  stale: cacheControl.stale
526
569
  };
527
570
  const parentCtx = cacheContextStorage.getStore();
528
- parentCtx?.lifeConfigs.push(life);
571
+ propagateCacheLife(parentCtx, life);
529
572
  if (parentCtx && parentCtx.variant !== "private" && (cacheControl.revalidate === 0 || cacheControl.expire !== void 0 && cacheControl.expire < DYNAMIC_EXPIRE)) {
530
573
  const error = new NestedDynamicUseCacheError();
531
574
  if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(error, recordRequestScopedCacheControl);
532
575
  parentCtx.dynamicNestedCacheError ??= error;
533
576
  }
534
- _setRequestScopedCacheLife(life);
535
- }
536
- function recordRequestScopedCacheLife(cacheLife) {
537
- _setRequestScopedCacheLife(cacheLife);
538
577
  }
539
578
  /**
540
579
  * Bubble a `"use cache"` scope's tags toward where they can drive invalidation.
@@ -561,8 +600,7 @@ function propagateCacheTagsToRequest(tags) {
561
600
  addCollectedRequestTags(tags);
562
601
  }
563
602
  async function executeWithContext(fn, args, variant, rsc) {
564
- const { result, ctx: _ctx, effectiveLife, collectedResult } = await runCachedFunctionWithContext(fn, args, variant, (value) => serializeCacheResult(value, rsc));
565
- recordRequestScopedCacheLife(effectiveLife);
603
+ const { result, collectedResult } = await runCachedFunctionWithContext(fn, args, variant, (value) => serializeCacheResult(value, rsc));
566
604
  return {
567
605
  result: collectedResult ? collectedResult.result : result,
568
606
  cacheable: collectedResult?.cacheEntry !== null,
@@ -579,6 +617,7 @@ async function runCachedFunctionWithContext(fn, args, variant, collectResult) {
579
617
  const ctx = {
580
618
  tags: [],
581
619
  lifeConfigs: [],
620
+ nestedLifeConfigs: [],
582
621
  variant: variant || "default",
583
622
  readRootParamNames: /* @__PURE__ */ new Set(),
584
623
  hasExplicitRevalidate: false,
@@ -588,19 +627,26 @@ async function runCachedFunctionWithContext(fn, args, variant, collectResult) {
588
627
  };
589
628
  let collectedResult;
590
629
  const workUnitType = variant === "private" ? "private-cache" : "cache";
591
- const result = await workUnitAsyncStorage.run({ type: workUnitType }, () => cacheContextStorage.run(ctx, async () => {
592
- const value = await fn(...args);
593
- if (collectResult) collectedResult = await collectResult(value, ctx);
594
- return value;
595
- }));
630
+ let result;
631
+ let effectiveLife;
632
+ try {
633
+ result = await workUnitAsyncStorage.run({ type: workUnitType }, () => cacheContextStorage.run(ctx, async () => {
634
+ if (!collectResult) return await fn(...args);
635
+ let value;
636
+ const lazyValue = { then: (onFulfilled, onRejected) => (value ??= new Promise((resolve) => resolve(fn(...args)))).then(onFulfilled, onRejected) };
637
+ collectedResult = await collectResult(lazyValue, ctx);
638
+ return await lazyValue;
639
+ }));
640
+ } finally {
641
+ effectiveLife = resolveCacheLife(ctx);
642
+ propagateCacheLife(parentCtx, effectiveLife);
643
+ if (parentCtx && eagerError && (effectiveLife.revalidate === 0 || effectiveLife.expire !== void 0 && effectiveLife.expire < DYNAMIC_EXPIRE)) parentCtx.dynamicNestedCacheError ??= eagerError;
644
+ }
596
645
  if (ctx.invalidDynamicUsageError) throw ctx.invalidDynamicUsageError;
597
- const effectiveLife = resolveCacheLife(ctx.lifeConfigs);
598
646
  if (parentCtx) {
599
- parentCtx.lifeConfigs.push(effectiveLife);
600
647
  for (const tag of ctx.tags) if (!parentCtx.tags.includes(tag)) parentCtx.tags.push(tag);
601
648
  if (parentCtx.variant !== "private") for (const name of ctx.readRootParamNames ?? []) parentCtx.readRootParamNames?.add(name);
602
649
  }
603
- if (parentCtx && eagerError && (effectiveLife.revalidate === 0 || effectiveLife.expire !== void 0 && effectiveLife.expire < DYNAMIC_EXPIRE)) parentCtx.dynamicNestedCacheError ??= eagerError;
604
650
  if (typeof process !== "undefined" && (process.env.VINEXT_PRERENDER === "1" || process.env.NODE_ENV === "development") && ctx.dynamicNestedCacheError) {
605
651
  if (effectiveLife.revalidate === 0 && !ctx.hasExplicitRevalidate) throw new Error(getNestedCacheZeroRevalidateErrorMessage(), { cause: ctx.dynamicNestedCacheError });
606
652
  if (effectiveLife.expire !== void 0 && effectiveLife.expire < DYNAMIC_EXPIRE && !ctx.hasExplicitExpire) throw new Error(getNestedCacheShortExpireErrorMessage(), { cause: ctx.dynamicNestedCacheError });
@@ -1,7 +1,8 @@
1
+ import { CacheLifeConfig } from "../utils/cache-life-profiles.js";
1
2
  import { ExecutionContextLike, getRequestExecutionContext, runWithExecutionContext } from "./request-context.js";
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, 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
3
  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";
4
+ import { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext } from "./internal/cache-context-accessor.js";
5
+ import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheState, PendingCdnTagInvalidation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _isOnDemandRevalidate, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
5
6
  //#region src/shims/cache.d.ts
6
7
  /**
7
8
  * Revalidate cached data associated with a specific cache tag.
@@ -61,10 +62,11 @@ export declare function updateTag(tag: string): undefined;
61
62
  /**
62
63
  * Opt out of static rendering and indicate a particular component should not be cached.
63
64
  *
64
- * In Next.js, calling noStore() inside a Server Component ensures the component
65
- * is dynamically rendered. In our implementation, this is a no-op since we don't
66
- * have the same static/dynamic rendering split — all server rendering is on-demand.
67
- * It's provided for API compatibility so apps importing it don't break.
65
+ * Without Cache Components this marks the render dynamic, so the page is not
66
+ * stored. With Cache Components it does nothing, as in Next.js: its prerenders
67
+ * ignore it, and on a request it doesn't make the page dynamic, so the page
68
+ * stays static and cacheable.
69
+ * Source: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/web/spec-extension/unstable-no-store.ts
68
70
  */
69
71
  export declare function unstable_noStore(): void;
70
72
  /**
@@ -100,12 +102,16 @@ export declare function unstable_io(): Promise<void>;
100
102
  /**
101
103
  * Set the cache lifetime for a "use cache" function.
102
104
  *
103
- * Accepts either a built-in profile name (e.g., "hours", "days") or a custom
104
- * configuration object. In Next.js, this only works inside "use cache" functions.
105
+ * Accepts either a profile name (a built-in like "hours", or one configured
106
+ * in next.config `cacheLife`) or a custom configuration object; an unknown
107
+ * profile name throws, like Next.js. In Next.js, this only works inside
108
+ * "use cache" functions.
105
109
  *
106
110
  * When called inside a "use cache" function, this sets the cache TTL.
107
111
  * The "minimum-wins" rule applies: if called multiple times, the shortest
108
- * duration for each field wins.
112
+ * duration for each field wins. Like Next.js, only the fields a call sets are
113
+ * explicit: the others resolve from the default profile and any nested caches
114
+ * (see `resolveCacheLife` in cache-runtime.ts).
109
115
  *
110
116
  * When called outside a "use cache" context, this is a validated no-op.
111
117
  */
@@ -142,4 +148,4 @@ type UnstableCacheOptions = {
142
148
  */
143
149
  export declare function unstable_cache<T extends (...args: any[]) => Promise<any>>(fn: T, keyParts?: string[], options?: UnstableCacheOptions): T;
144
150
  //#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, 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 };
151
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, type CacheLifeConfig, CacheState, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, type ExecutionContextLike, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, PendingCdnTagInvalidation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _isOnDemandRevalidate, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, markActionRevalidation, unstable_noStore as noStore, registerDataCacheHandler, registerLazyDataCacheHandler, runWithDetachedCacheObservations, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry };
@@ -6,13 +6,13 @@ import { workUnitAsyncStorage } from "./internal/work-unit-async-storage.js";
6
6
  import { isStagedCacheabilityProbeActive } from "./cacheability-classification.js";
7
7
  import { MemoryCacheHandler, NoOpCacheHandler, configureMemoryCacheHandler, getCacheHandler, getDataCacheHandler, registerDataCacheHandler, registerLazyDataCacheHandler, setCacheHandler, setDataCacheHandler } from "./cache-handler.js";
8
8
  import { encodeCacheTag, encodeCacheTags } from "../utils/encode-cache-tag.js";
9
- import { fnv1a64, sha256Hex } from "../utils/hash.js";
9
+ import { sha256Hex } from "../utils/hash.js";
10
10
  import { addCollectedRequestTags, getCurrentFetchSoftTags, runWithDetachedFetchObservations } from "./fetch-cache-state.js";
11
11
  import { makeHangingPromise } from "./internal/make-hanging-promise.js";
12
12
  import { getHeadersAccessPhase, markDynamicUsage } from "./internal/headers-state.js";
13
13
  import { isDraftModeEnabled } from "./headers.js";
14
14
  import { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext } from "./internal/cache-context-accessor.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";
15
+ import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _isOnDemandRevalidate, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _queuePendingCdnTagInvalidation, _queuePendingRevalidation, _revalidateCdnTags, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
16
16
  import { isPathKnownNotEdgeCached } from "./cdn-purge-routes.js";
17
17
  //#region src/shims/cache.ts
18
18
  /**
@@ -154,13 +154,15 @@ function updateTag(tag) {
154
154
  /**
155
155
  * Opt out of static rendering and indicate a particular component should not be cached.
156
156
  *
157
- * In Next.js, calling noStore() inside a Server Component ensures the component
158
- * is dynamically rendered. In our implementation, this is a no-op since we don't
159
- * have the same static/dynamic rendering split — all server rendering is on-demand.
160
- * It's provided for API compatibility so apps importing it don't break.
157
+ * Without Cache Components this marks the render dynamic, so the page is not
158
+ * stored. With Cache Components it does nothing, as in Next.js: its prerenders
159
+ * ignore it, and on a request it doesn't make the page dynamic, so the page
160
+ * stays static and cacheable.
161
+ * Source: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/web/spec-extension/unstable-no-store.ts
161
162
  */
162
163
  function unstable_noStore() {
163
164
  if (isInsideUnstableCacheScope()) return;
165
+ if (String(process.env.__NEXT_CACHE_COMPONENTS) === "true") return;
164
166
  markDynamicUsage();
165
167
  }
166
168
  /**
@@ -228,29 +230,34 @@ let _unstableIoWarned = false;
228
230
  /**
229
231
  * Set the cache lifetime for a "use cache" function.
230
232
  *
231
- * Accepts either a built-in profile name (e.g., "hours", "days") or a custom
232
- * configuration object. In Next.js, this only works inside "use cache" functions.
233
+ * Accepts either a profile name (a built-in like "hours", or one configured
234
+ * in next.config `cacheLife`) or a custom configuration object; an unknown
235
+ * profile name throws, like Next.js. In Next.js, this only works inside
236
+ * "use cache" functions.
233
237
  *
234
238
  * When called inside a "use cache" function, this sets the cache TTL.
235
239
  * The "minimum-wins" rule applies: if called multiple times, the shortest
236
- * duration for each field wins.
240
+ * duration for each field wins. Like Next.js, only the fields a call sets are
241
+ * explicit: the others resolve from the default profile and any nested caches
242
+ * (see `resolveCacheLife` in cache-runtime.ts).
237
243
  *
238
244
  * When called outside a "use cache" context, this is a validated no-op.
239
245
  */
240
246
  function cacheLife(profile) {
241
247
  let resolvedConfig;
242
248
  if (typeof profile === "string") {
243
- if (!cacheLifeProfiles[profile]) {
244
- console.warn(`[vinext] cacheLife: unknown profile "${profile}". Available profiles: ${Object.keys(cacheLifeProfiles).join(", ")}`);
245
- return;
249
+ const configuredProfile = Object.hasOwn(cacheLifeProfiles, profile) ? cacheLifeProfiles[profile] : void 0;
250
+ if (configuredProfile === void 0) {
251
+ if (Object.hasOwn(cacheLifeProfiles, profile.trim())) throw new Error(`Unknown \`cacheLife()\` profile "${profile}" is not configured in next.config.js\nDid you mean "${profile.trim()}" without the spaces?`);
252
+ throw new Error(`Unknown \`cacheLife()\` profile "${profile}" is not configured in next.config.js\nmodule.exports = {
253
+ cacheLife: {
254
+ "${profile}": ...\n }
255
+ }`);
246
256
  }
247
- resolvedConfig = { ...cacheLifeProfiles[profile] };
257
+ resolvedConfig = { ...configuredProfile };
248
258
  } else if (typeof profile === "object" && profile !== null) {
249
259
  if (profile.expire !== void 0 && profile.revalidate !== void 0 && profile.expire < profile.revalidate) console.warn("[vinext] cacheLife: expire must be >= revalidate");
250
- resolvedConfig = {
251
- ...cacheLifeProfiles.default,
252
- ...profile
253
- };
260
+ resolvedConfig = { ...profile };
254
261
  } else return;
255
262
  try {
256
263
  const ctx = getRegisteredCacheContext();
@@ -258,11 +265,13 @@ function cacheLife(profile) {
258
265
  ctx.lifeConfigs.push(resolvedConfig);
259
266
  if (resolvedConfig.revalidate !== void 0) ctx.hasExplicitRevalidate = true;
260
267
  if (resolvedConfig.expire !== void 0) ctx.hasExplicitExpire = true;
261
- _setRequestScopedCacheLife(resolvedConfig);
262
268
  return;
263
269
  }
264
270
  } catch {}
265
- _setRequestScopedCacheLife(resolvedConfig);
271
+ _setRequestScopedCacheLife(typeof profile === "string" ? resolvedConfig : {
272
+ ...cacheLifeProfiles.default,
273
+ ...profile
274
+ });
266
275
  }
267
276
  /**
268
277
  * Tag a "use cache" function's cached result for on-demand revalidation.
@@ -325,7 +334,7 @@ function unstable_cacheTag(...tags) {
325
334
  }
326
335
  return cacheTag(...tags);
327
336
  }
328
- const UNSTABLE_CACHE_KEY_PREFIX = "unstable_cache:v2";
337
+ const UNSTABLE_CACHE_KEY_PREFIX = "unstable_cache:v3";
329
338
  /**
330
339
  * AsyncLocalStorage to track whether we're inside an unstable_cache() callback.
331
340
  * Stored on globalThis via Symbol so headers.ts can detect the scope without
@@ -394,7 +403,7 @@ function scheduleUnstableCacheBackgroundRevalidation(cacheKey, refresh) {
394
403
  pending.set(cacheKey, trackedRevalidation);
395
404
  waitUntilUnstableCacheRevalidation(trackedRevalidation);
396
405
  }
397
- async function refreshUnstableCacheResult(fn, args, keyedArgs, cacheKey, tags, revalidateSeconds) {
406
+ async function refreshUnstableCacheResult(fn, args, keyedArgs, cacheKey, tags, revalidateSeconds, staleEntry, deferWrite) {
398
407
  const argsUnchanged = serializeArgsOrNull(args) === keyedArgs;
399
408
  const result = await _unstableCacheAls.run(true, () => _unstableCacheCallbackAls.run({ useCacheContext: _useCacheContextAls.getStore() }, () => fn(...args)));
400
409
  if (!argsUnchanged) return result;
@@ -408,11 +417,18 @@ async function refreshUnstableCacheResult(fn, args, keyedArgs, cacheKey, tags, r
408
417
  tags,
409
418
  revalidate: typeof revalidateSeconds === "number" ? revalidateSeconds : false
410
419
  };
411
- await getDataCacheHandler().set(cacheKey, cacheValue, {
412
- fetchCache: true,
413
- tags,
414
- revalidate: revalidateSeconds
420
+ const write = new Promise((resolve) => {
421
+ resolve(getDataCacheHandler().set(cacheKey, cacheValue, {
422
+ fetchCache: true,
423
+ tags,
424
+ revalidate: revalidateSeconds,
425
+ ...staleEntry ? { staleEntry } : {}
426
+ }));
427
+ }).catch((err) => {
428
+ console.error("[vinext] unstable_cache write error:", err);
415
429
  });
430
+ if (deferWrite) getRequestExecutionContext()?.waitUntil(write);
431
+ else await write;
416
432
  return result;
417
433
  }
418
434
  function serializeArgsOrNull(args) {
@@ -439,16 +455,10 @@ function unstable_cache(fn, keyParts, options) {
439
455
  const keyedArgs = JSON.stringify(args);
440
456
  const invocationKey = `${fixedKey}${keyedArgs}`;
441
457
  addCollectedRequestTags(tags);
442
- recordUnstableCacheObservation({
443
- kind: "unstable_cache",
444
- keyHash: fnv1a64(invocationKey),
445
- revalidate: typeof revalidateSeconds === "number" ? revalidateSeconds : revalidateSeconds === false ? false : null,
446
- tagCount: tags.length,
447
- tagHash: tags.length > 0 ? fnv1a64(JSON.stringify(tags)) : null
448
- });
449
458
  const cacheKey = `${UNSTABLE_CACHE_KEY_PREFIX}:${await sha256Hex(invocationKey)}`;
450
459
  const isDraftMode = isDraftModeEnabled();
451
- if (!isDraftMode && !isNestedUnstableCacheCall()) {
460
+ let staleEntry;
461
+ if (!isDraftMode && !isNestedUnstableCacheCall() && !_isOnDemandRevalidate()) {
452
462
  const softTags = getCurrentFetchSoftTags();
453
463
  const existing = _hasPendingRevalidatedTag([...tags, ...softTags]) ? null : await getDataCacheHandler().get(cacheKey, {
454
464
  kind: "FETCH",
@@ -457,10 +467,11 @@ function unstable_cache(fn, keyParts, options) {
457
467
  });
458
468
  if (existing?.value && existing.value.kind === "FETCH") {
459
469
  const cached = tryDeserializeUnstableCacheResult(existing.value.data.body);
470
+ if (existing.cacheState === "stale") staleEntry = existing;
460
471
  if (cached.ok) {
461
472
  if (existing.cacheState === "stale") {
462
473
  if (shouldServeStaleUnstableCacheEntry()) {
463
- scheduleUnstableCacheBackgroundRevalidation(cacheKey, () => refreshUnstableCacheResult(fn, args, keyedArgs, cacheKey, tags, revalidateSeconds));
474
+ scheduleUnstableCacheBackgroundRevalidation(cacheKey, () => refreshUnstableCacheResult(fn, args, keyedArgs, cacheKey, tags, revalidateSeconds, staleEntry, false));
464
475
  return cached.value;
465
476
  }
466
477
  } else return cached.value;
@@ -468,9 +479,9 @@ function unstable_cache(fn, keyParts, options) {
468
479
  }
469
480
  }
470
481
  if (isDraftMode) return await _unstableCacheAls.run(true, () => fn(...args));
471
- return await refreshUnstableCacheResult(fn, args, keyedArgs, cacheKey, tags, revalidateSeconds);
482
+ return await refreshUnstableCacheResult(fn, args, keyedArgs, cacheKey, tags, revalidateSeconds, staleEntry, !staleEntry && isInsideWorkStoreScope());
472
483
  };
473
484
  return cachedFn;
474
485
  }
475
486
  //#endregion
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 };
487
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, MemoryCacheHandler, NoOpCacheHandler, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _isOnDemandRevalidate, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _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, refresh, registerDataCacheHandler, registerLazyDataCacheHandler, revalidatePath, revalidateTag, runWithDetachedCacheObservations, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
@@ -17,6 +17,8 @@ export type RouteCacheabilityOutcome = {
17
17
  */
18
18
  status?: number;
19
19
  headers?: Readonly<Record<string, string>>;
20
+ /** App pages: the client-router stale time from the completed render's cacheLife. */
21
+ staleTimeSeconds?: number;
20
22
  tags?: readonly string[];
21
23
  };
22
24
  export type RouteCacheabilityState = {
@@ -32,6 +32,12 @@ export type CdnCacheableHeaderInput = {
32
32
  */
33
33
  tags?: readonly string[];
34
34
  };
35
+ /** A page's document, rendered from an RSC request's payload. */
36
+ export type CdnAppPageDocument = {
37
+ html: string;
38
+ /** The renderer's own `Link` header. */
39
+ linkHeader?: string;
40
+ };
35
41
  export type CdnResponsePolicy = {
36
42
  /** Whether a provider-specific response header controls shared caching. */
37
43
  isHeader(name: string): boolean;
@@ -76,12 +82,15 @@ export type CdnCacheAdapter = {
76
82
  */
77
83
  readonly requiresCompletedResponseAdmission?: boolean;
78
84
  /**
79
- * This adapter's response-stage transport keys shared App page dispatches by
80
- * `VinextResponseStageDispatchOptions.cacheIdentity` when core supplies one.
81
- * Core supplies it only when completed-response admission is also required,
82
- * because that admission is what makes a query-free identity safe.
85
+ * This adapter's response-stage transport keys shared dispatches by
86
+ * `VinextResponseStageDispatchOptions.cacheIdentity` when core supplies
87
+ * one. Core supplies it only when completed-response admission is also
88
+ * required, because that admission is what makes a host-free identity, and
89
+ * a page's query-free one, safe. `"query-free"` also drops the user query
90
+ * from App page identities; `"host-free"` keeps every dispatch's query, for
91
+ * a transport whose cache keys the dispatched query itself.
83
92
  */
84
- readonly responseStageCacheIdentity?: "query-free";
93
+ readonly responseStageCacheIdentity?: "host-free" | "query-free";
85
94
  /**
86
95
  * Optionally return a foreground page response while completed-response
87
96
  * admission continues on an independent body branch. Returning `null` keeps
@@ -90,6 +99,14 @@ export type CdnCacheAdapter = {
90
99
  deferCompletedPageResponseAdmission?(response: Response, complete: (response: Response) => Promise<Response>): Response | null;
91
100
  /** Capture the full-route RSC side stream while admitting a completed HTML response. */
92
101
  captureAppPageRscData?(rscData: Promise<ArrayBuffer>): void;
102
+ /**
103
+ * Store the page's document beside an RSC request's completed response, as
104
+ * Next.js keeps a page's document and RSC payload in one entry. Returns the
105
+ * callback admission calls with the document the render rendered from its
106
+ * payload, or with `null` when the page has no document to store, or
107
+ * `undefined` when this render's response stores no document.
108
+ */
109
+ captureAppPageDocument?(): ((document: CdnAppPageDocument | null) => void) | undefined;
93
110
  /**
94
111
  * Whether this adapter sends the 304 for a Pages render whose If-None-Match
95
112
  * matches. When it does, the render returns the page, so an adapter that
@@ -9,4 +9,6 @@
9
9
  */
10
10
  export declare function buildClientHookErrorMessage(hookName: string): string;
11
11
  export declare function throwClientHookError(hookName: string): never;
12
+ /** Rewrites that TypeError into the actionable message, like Next.js's formatServerError. */
13
+ export declare function rewriteClientHookError(error: Error): void;
12
14
  //#endregion
@@ -13,5 +13,20 @@ function buildClientHookErrorMessage(hookName) {
13
13
  function throwClientHookError(hookName) {
14
14
  throw new Error(buildClientHookErrorMessage(hookName));
15
15
  }
16
+ const clientHookPattern = /\b(useState|useEffect|useReducer|useRef|useContext|useLayoutEffect|useInsertionEffect|useSyncExternalStore|useTransition|useImperativeHandle|useDeferredValue|useActionState|useOptimistic|useEffectEvent)\b.*is not a function/;
17
+ /** Rewrites that TypeError into the actionable message, like Next.js's formatServerError. */
18
+ function rewriteClientHookError(error) {
19
+ const match = error.message.match(clientHookPattern);
20
+ if (!match) return;
21
+ const message = buildClientHookErrorMessage(match[1]);
22
+ try {
23
+ if (error.stack) {
24
+ const lines = error.stack.split("\n");
25
+ lines[0] = message;
26
+ error.stack = lines.join("\n");
27
+ }
28
+ error.message = message;
29
+ } catch {}
30
+ }
16
31
  //#endregion
17
- export { buildClientHookErrorMessage, throwClientHookError };
32
+ export { buildClientHookErrorMessage, rewriteClientHookError, throwClientHookError };
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { searchParamsToRecord } from "../utils/query.js";
3
3
  import { getNavigationContext } from "./navigation-context-state.js";
4
+ import { getClientNavigationRenderContext, useSearchParams } from "./navigation.js";
4
5
  import "./navigation-server.js";
5
- import { isOwnPropertyCheck, isWellKnownProperty } from "./internal/thenable-well-known-properties.js";
6
6
  import { RenderedSearchContext } from "./slot.js";
7
- import { getClientNavigationRenderContext, useSearchParams } from "./navigation.js";
7
+ import { isOwnPropertyCheck, isWellKnownProperty } from "./internal/thenable-well-known-properties.js";
8
8
  import { createElement, use, useMemo } from "react";
9
9
  //#region src/shims/client-page-root.tsx
10
10
  /**