vinext 1.0.1 → 1.1.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 (159) hide show
  1. package/README.md +72 -1
  2. package/dist/build/app-page-static-eligibility.d.ts +15 -0
  3. package/dist/build/app-page-static-eligibility.js +82 -0
  4. package/dist/build/prerender-paths.js +3 -78
  5. package/dist/build/prerender.js +33 -8
  6. package/dist/build/server-manifest.d.ts +7 -0
  7. package/dist/build/server-manifest.js +11 -1
  8. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  9. package/dist/cache/cache-adapters-virtual.js +3 -3
  10. package/dist/client/navigation-runtime.d.ts +11 -0
  11. package/dist/client/navigation-runtime.js +1 -1
  12. package/dist/config/config-matchers.js +3 -1
  13. package/dist/config/next-config.d.ts +7 -0
  14. package/dist/config/next-config.js +18 -3
  15. package/dist/entries/app-rsc-entry.d.ts +2 -0
  16. package/dist/entries/app-rsc-entry.js +64 -43
  17. package/dist/index.d.ts +4 -1
  18. package/dist/index.js +59 -30
  19. package/dist/plugins/cache-flight-codec.d.ts +19 -0
  20. package/dist/plugins/cache-flight-codec.js +68 -0
  21. package/dist/plugins/commonjs-esm-facade.d.ts +35 -0
  22. package/dist/plugins/commonjs-esm-facade.js +80 -0
  23. package/dist/plugins/fonts.d.ts +8 -11
  24. package/dist/plugins/fonts.js +71 -34
  25. package/dist/routing/file-matcher.d.ts +1 -0
  26. package/dist/routing/file-matcher.js +1 -1
  27. package/dist/server/api-handler.js +17 -7
  28. package/dist/server/app-browser-entry.js +11 -7
  29. package/dist/server/app-browser-history-controller.d.ts +2 -0
  30. package/dist/server/app-browser-history-controller.js +8 -0
  31. package/dist/server/app-browser-interception-context.js +26 -2
  32. package/dist/server/app-browser-navigation-controller.d.ts +7 -1
  33. package/dist/server/app-browser-navigation-controller.js +93 -10
  34. package/dist/server/app-fallback-renderer.d.ts +2 -0
  35. package/dist/server/app-fallback-renderer.js +1 -0
  36. package/dist/server/app-history-state.d.ts +3 -0
  37. package/dist/server/app-history-state.js +7 -0
  38. package/dist/server/app-middleware.js +2 -1
  39. package/dist/server/app-optimistic-routing.js +1 -1
  40. package/dist/server/app-page-boundary-render.d.ts +5 -0
  41. package/dist/server/app-page-boundary-render.js +6 -4
  42. package/dist/server/app-page-cache-finalizer.d.ts +11 -0
  43. package/dist/server/app-page-cache-finalizer.js +41 -7
  44. package/dist/server/app-page-cache-render.d.ts +18 -0
  45. package/dist/server/app-page-cache-render.js +63 -43
  46. package/dist/server/app-page-cache.d.ts +5 -0
  47. package/dist/server/app-page-cache.js +27 -7
  48. package/dist/server/app-page-dispatch.d.ts +9 -1
  49. package/dist/server/app-page-dispatch.js +47 -30
  50. package/dist/server/app-page-element-builder.d.ts +7 -0
  51. package/dist/server/app-page-element-builder.js +27 -42
  52. package/dist/server/app-page-execution.d.ts +15 -12
  53. package/dist/server/app-page-execution.js +30 -40
  54. package/dist/server/app-page-head.d.ts +5 -0
  55. package/dist/server/app-page-head.js +9 -3
  56. package/dist/server/app-page-probe.d.ts +0 -109
  57. package/dist/server/app-page-probe.js +3 -136
  58. package/dist/server/app-page-render-identity.d.ts +0 -1
  59. package/dist/server/app-page-render-identity.js +1 -1
  60. package/dist/server/app-page-render-observation.js +2 -2
  61. package/dist/server/app-page-render.d.ts +13 -5
  62. package/dist/server/app-page-render.js +219 -119
  63. package/dist/server/app-page-request.d.ts +0 -1
  64. package/dist/server/app-page-request.js +1 -2
  65. package/dist/server/app-page-response.d.ts +4 -3
  66. package/dist/server/app-page-response.js +4 -3
  67. package/dist/server/app-page-route-wiring.d.ts +17 -5
  68. package/dist/server/app-page-route-wiring.js +55 -33
  69. package/dist/server/app-page-rsc-render-status.d.ts +48 -0
  70. package/dist/server/app-page-rsc-render-status.js +54 -0
  71. package/dist/server/app-page-stream.d.ts +15 -8
  72. package/dist/server/app-page-stream.js +6 -0
  73. package/dist/server/app-request-stage-dispatch.js +1 -1
  74. package/dist/server/app-request-stage-independent-entry.js +4 -4
  75. package/dist/server/app-response-header-provenance.d.ts +2 -0
  76. package/dist/server/app-response-header-provenance.js +10 -4
  77. package/dist/server/app-route-handler-dispatch.js +1 -1
  78. package/dist/server/app-router-entry.js +3 -3
  79. package/dist/server/app-rsc-handler.d.ts +4 -0
  80. package/dist/server/app-rsc-handler.js +212 -96
  81. package/dist/server/app-rsc-request-normalization.d.ts +3 -1
  82. package/dist/server/app-rsc-request-normalization.js +4 -2
  83. package/dist/server/app-rsc-response-stage.d.ts +1 -1
  84. package/dist/server/app-rsc-response-stage.js +6 -4
  85. package/dist/server/app-rsc-route-matching.d.ts +9 -1
  86. package/dist/server/app-rsc-route-matching.js +36 -19
  87. package/dist/server/app-server-action-execution.js +1 -1
  88. package/dist/server/app-ssr-entry.d.ts +3 -2
  89. package/dist/server/app-ssr-entry.js +6 -4
  90. package/dist/server/app-ssr-stream.d.ts +6 -1
  91. package/dist/server/app-ssr-stream.js +34 -15
  92. package/dist/server/app-worker-stages.d.ts +2 -2
  93. package/dist/server/app-worker-stages.js +3 -3
  94. package/dist/server/cacheability-request.js +9 -5
  95. package/dist/server/config-headers.js +2 -0
  96. package/dist/server/dev-server.js +12 -2
  97. package/dist/server/headers.d.ts +14 -0
  98. package/dist/server/headers.js +15 -1
  99. package/dist/server/image-optimization.d.ts +16 -0
  100. package/dist/server/image-optimization.js +26 -6
  101. package/dist/server/isr-cache.js +2 -2
  102. package/dist/server/middleware-matcher-pattern.js +8 -4
  103. package/dist/server/middleware-response-headers.js +2 -0
  104. package/dist/server/multi-stage.d.ts +8 -0
  105. package/dist/server/navigation-planner.js +1 -1
  106. package/dist/server/node-response-signal.d.ts +16 -0
  107. package/dist/server/node-response-signal.js +26 -0
  108. package/dist/server/open-redirect.d.ts +47 -0
  109. package/dist/server/open-redirect.js +74 -1
  110. package/dist/server/pages-page-handler.js +21 -4
  111. package/dist/server/pages-request-stage-entry.js +8 -4
  112. package/dist/server/prod-server.d.ts +8 -7
  113. package/dist/server/prod-server.js +153 -69
  114. package/dist/server/request-pipeline.d.ts +20 -8
  115. package/dist/server/request-pipeline.js +21 -7
  116. package/dist/server/response-compression.d.ts +43 -0
  117. package/dist/server/response-compression.js +111 -0
  118. package/dist/server/seed-cache.js +10 -7
  119. package/dist/server/socket-error-backstop.d.ts +14 -0
  120. package/dist/server/socket-error-backstop.js +27 -4
  121. package/dist/shims/cache-adapter-instantiate.d.ts +50 -0
  122. package/dist/shims/cache-adapter-instantiate.js +118 -0
  123. package/dist/shims/cache-callable-runtime.d.ts +2 -40
  124. package/dist/shims/cache-callable-runtime.js +50 -168
  125. package/dist/shims/cache-flight-arguments.d.ts +22 -0
  126. package/dist/shims/cache-flight-arguments.js +56 -0
  127. package/dist/shims/cache-request-state.d.ts +15 -0
  128. package/dist/shims/cache-request-state.js +31 -8
  129. package/dist/shims/cache-runtime.d.ts +6 -19
  130. package/dist/shims/cache-runtime.js +165 -225
  131. package/dist/shims/cache.d.ts +2 -2
  132. package/dist/shims/cache.js +5 -4
  133. package/dist/shims/cacheability-classification.d.ts +7 -0
  134. package/dist/shims/error-boundary-navigation.d.ts +7 -0
  135. package/dist/shims/error-boundary-navigation.js +19 -1
  136. package/dist/shims/error-boundary.d.ts +10 -10
  137. package/dist/shims/error-boundary.js +26 -7
  138. package/dist/shims/fetch-cache.d.ts +6 -0
  139. package/dist/shims/fetch-cache.js +19 -1
  140. package/dist/shims/form.js +1 -1
  141. package/dist/shims/headers.d.ts +13 -0
  142. package/dist/shims/headers.js +47 -4
  143. package/dist/shims/internal/app-page-props-cache-key.d.ts +5 -2
  144. package/dist/shims/internal/app-page-props-cache-key.js +24 -15
  145. package/dist/shims/internal/headers-state.d.ts +0 -1
  146. package/dist/shims/metadata.js +3 -3
  147. package/dist/shims/navigation-errors.d.ts +6 -0
  148. package/dist/shims/navigation-errors.js +11 -1
  149. package/dist/shims/navigation.d.ts +7 -0
  150. package/dist/shims/navigation.js +14 -4
  151. package/dist/shims/request-context.d.ts +9 -0
  152. package/dist/shims/thenable-params.d.ts +4 -0
  153. package/dist/shims/thenable-params.js +25 -3
  154. package/dist/shims/unified-request-context.js +1 -0
  155. package/dist/utils/mdx-scan.d.ts +5 -0
  156. package/dist/utils/mdx-scan.js +8 -1
  157. package/dist/utils/regex-safety.d.ts +10 -0
  158. package/dist/utils/regex-safety.js +70 -18
  159. package/package.json +3 -3
@@ -9,11 +9,13 @@ import { createPprFallbackShellSuspensePromise, trackPprFallbackShellCacheTask }
9
9
  import { markDynamicUsage, throwIfInsideCacheScope } from "./internal/headers-state.js";
10
10
  import { isDraftModeEnabled } from "./headers.js";
11
11
  import { _registerCacheContextAccessor } from "./internal/cache-context-accessor.js";
12
- import { _hasPendingRevalidatedTag, _setRequestScopedCacheLife, cacheLifeProfiles } from "./cache-request-state.js";
13
- import { addCollectedRequestTags, getCurrentFetchSoftTags } from "./fetch-cache.js";
14
- import { makeThenableParams } from "./thenable-params.js";
15
- import { APP_PAGE_USE_CACHE_MARKER, hasUseCachePageMarker, isMarkedAppPagePropsObject, isUseCacheFunctionReference, markAppPagePropsForUseCache } from "./internal/app-page-props-cache-key.js";
12
+ import { _hasPendingRevalidatedTag, _setRequestScopedCacheLife, cacheLifeProfiles, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
13
+ import { addCollectedRequestTags, getCurrentFetchSoftTags, runWithDetachedFetchObservations } from "./fetch-cache.js";
14
+ import { hasUseCacheLayoutMarker, hasUseCachePageMarker, isUseCacheFunctionReference, withoutUseCacheSegmentMarker } from "./internal/app-page-props-cache-key.js";
15
+ import { getFallbackParamsSnapshot, makeThenableParams, restoreThenableParams } from "./thenable-params.js";
16
16
  import { getCurrentRootParams } from "./root-params.js";
17
+ import { flightArgumentsKey, restoreFlightReply, snapshotFlightReply } from "./cache-flight-arguments.js";
18
+ import { cache } from "react";
17
19
  //#region src/shims/cache-runtime.ts
18
20
  /**
19
21
  * "use cache" runtime
@@ -36,14 +38,17 @@ import { getCurrentRootParams } from "./root-params.js";
36
38
  * and all RSC-serializable types — unlike JSON.stringify which silently
37
39
  * drops $$typeof Symbols and function values.
38
40
  *
39
- * When RSC APIs are unavailable (e.g. in unit tests), falls back to
40
- * JSON.stringify/parse with the same stableStringify cache key generation.
41
- *
42
41
  * Cache variants:
43
42
  * - "use cache" — shared cache (default profile)
44
43
  * - "use cache: remote" — shared cache (explicit)
45
44
  * - "use cache: private" — per-request cache (not shared across requests)
46
45
  */
46
+ const replayableFunctions = /* @__PURE__ */ new WeakMap();
47
+ function replayCachedFunction(fn, args) {
48
+ const replay = replayableFunctions.get(fn);
49
+ if (!replay || args.version !== 1) throw new Error("Invalid cache function arguments");
50
+ return replay(args);
51
+ }
47
52
  /** Threshold below which expire is considered "dynamic" (5 minutes in seconds). */
48
53
  const DYNAMIC_EXPIRE = 300;
49
54
  /**
@@ -124,16 +129,9 @@ function buildUseCacheKey(id, keySeed, argsKey) {
124
129
  const scopedId = keySeed ? `build:${encodeURIComponent(keySeed)}:${id}` : id;
125
130
  return argsKey === void 0 ? `use-cache:${scopedId}` : `use-cache:${scopedId}:${argsKey}`;
126
131
  }
127
- const NOT_LOADED = Symbol("not-loaded");
128
- let _rscModule = NOT_LOADED;
129
- async function getRscModule() {
130
- if (_rscModule !== NOT_LOADED) return _rscModule;
131
- try {
132
- _rscModule = await import("@vitejs/plugin-rsc/react/rsc");
133
- } catch {
134
- _rscModule = null;
135
- }
136
- return _rscModule;
132
+ let _rscModule;
133
+ function getRscModule() {
134
+ return _rscModule ??= import("@vitejs/plugin-rsc/react/rsc");
137
135
  }
138
136
  /** Collect a ReadableStream<Uint8Array> into a single Uint8Array. */
139
137
  async function collectStream(stream) {
@@ -171,55 +169,22 @@ function uint8ToStream(bytes) {
171
169
  } });
172
170
  }
173
171
  async function serializeCacheResult(result, rsc) {
174
- if (rsc) {
175
- let serializationError;
176
- const [returnStream, savedStream] = rsc.renderToReadableStream(result, { onError(error) {
177
- serializationError ??= { value: error };
178
- } }).tee();
179
- try {
180
- const [deserializedResult, bytes] = await Promise.all([rsc.createFromReadableStream(returnStream, {}, { preserveServerReferences: true }), collectStream(savedStream)]);
181
- return {
182
- result: deserializedResult,
183
- cacheEntry: serializationError ? null : {
184
- body: uint8ToBase64(bytes),
185
- headers: { [VINEXT_RSC_MARKER_HEADER]: "1" }
186
- }
187
- };
188
- } catch (error) {
189
- throw serializationError?.value ?? error;
190
- }
191
- }
192
- const body = JSON.stringify(result);
193
- return body === void 0 ? null : {
194
- result,
195
- cacheEntry: {
196
- body,
197
- headers: {}
198
- }
199
- };
200
- }
201
- /**
202
- * Convert an encodeReply result (string | FormData) to a cache key string.
203
- * For FormData (binary args), produces a deterministic SHA-256 hash over
204
- * the sorted entries. We can't hash `new Response(formData).arrayBuffer()`
205
- * because multipart boundaries are non-deterministic across serializations.
206
- *
207
- * Exported for testing.
208
- */
209
- async function replyToCacheKey(reply) {
210
- if (typeof reply === "string") return reply;
211
- const entries = [...reply.entries()];
212
- const valStr = (v) => typeof v === "string" ? v : v.name;
213
- entries.sort((a, b) => a[0].localeCompare(b[0]) || valStr(a[1]).localeCompare(valStr(b[1])));
214
- const parts = [];
215
- for (const [name, value] of entries) if (typeof value === "string") parts.push(`${name}=s:${value}`);
216
- else {
217
- const bytes = new Uint8Array(await value.arrayBuffer());
218
- parts.push(`${name}=b:${value.type}:${value.size}:${Buffer.from(bytes).toString("base64")}`);
172
+ let serializationError;
173
+ const [returnStream, savedStream] = rsc.renderToReadableStream(result, { onError(error) {
174
+ serializationError ??= { value: error };
175
+ } }).tee();
176
+ try {
177
+ const [deserializedResult, bytes] = await Promise.all([rsc.createFromReadableStream(returnStream, {}, { preserveServerReferences: true }), collectStream(savedStream)]);
178
+ return {
179
+ result: deserializedResult,
180
+ cacheEntry: serializationError ? null : {
181
+ body: uint8ToBase64(bytes),
182
+ headers: { [VINEXT_RSC_MARKER_HEADER]: "1" }
183
+ }
184
+ };
185
+ } catch (error) {
186
+ throw serializationError?.value ?? error;
219
187
  }
220
- const payload = new TextEncoder().encode(parts.join("\0"));
221
- const hashBuffer = await crypto.subtle.digest("SHA-256", payload);
222
- return Buffer.from(new Uint8Array(hashBuffer)).toString("base64url");
223
188
  }
224
189
  /**
225
190
  * Resolve collected cacheLife configs into a single effective config.
@@ -301,6 +266,17 @@ function propagateRootParamNamesToParent(names) {
301
266
  if (!parent || parent.variant === "private") return;
302
267
  for (const name of names) parent.readRootParamNames?.add(name);
303
268
  }
269
+ function isServableCacheState(cacheState) {
270
+ if (cacheState === "expired") return false;
271
+ if (cacheState !== "stale") return true;
272
+ return shouldServeStaleUnstableCacheEntry() && process.env.VINEXT_PRERENDER !== "1" && !isRouteCacheabilityProbe();
273
+ }
274
+ function regenerateInBackground(id, regenerate) {
275
+ const regeneration = runWithDetachedCacheObservations(() => runWithDetachedFetchObservations(() => cacheContextStorage.exit(regenerate))).then(() => void 0, (error) => {
276
+ console.error(`[vinext] use cache: background regeneration failed for ${id}:`, error);
277
+ });
278
+ if (isInsideUnifiedScope()) getRequestContext().executionContext?.waitUntil(regeneration);
279
+ }
304
280
  /**
305
281
  * Register a function as a cached function. This is called by the Vite
306
282
  * transform for each "use cache" function.
@@ -315,7 +291,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
315
291
  const omitAppPageSearchParams = cacheVariant !== "private";
316
292
  const cacheFunctionId = options.serverReferenceId ? JSON.stringify([id, options.serverReferenceId]) : id;
317
293
  const isDev = typeof process !== "undefined" && process.env.NODE_ENV === "development";
318
- const cachedFn = (...args) => {
294
+ const invoke = (args, replay) => {
319
295
  if (cacheVariant === "private") {
320
296
  const parentCtx = cacheContextStorage.getStore();
321
297
  if (parentCtx && parentCtx.variant !== "private") throwPrivateUseCacheInsidePublicUseCacheError();
@@ -346,40 +322,60 @@ function registerCachedFunction(fn, id, variant, options = {}) {
346
322
  }
347
323
  }
348
324
  return trackPprFallbackShellCacheTask(async () => {
349
- const rsc = await getRscModule();
325
+ const codec = await getRscModule();
326
+ const clientReferences = codec.createClientTemporaryReferenceSet();
327
+ const serverReferences = codec.createTemporaryReferenceSet();
328
+ const rsc = {
329
+ ...codec,
330
+ renderToReadableStream: (value, options) => codec.renderToReadableStream(value, {
331
+ ...options,
332
+ temporaryReferences: serverReferences
333
+ }),
334
+ createFromReadableStream: (stream, options, context) => codec.createFromReadableStream(stream, {
335
+ ...options,
336
+ temporaryReferences: clientReferences
337
+ }, context)
338
+ };
350
339
  const keySeed = getUseCacheKeySeed();
351
- const captures = options.decryptCaptures ? await options.decryptCaptures(args[0]) : void 0;
340
+ const captures = !replay && options.decryptCaptures ? await options.decryptCaptures(args[0]) : void 0;
352
341
  const hasCaptureEnvelope = captures !== void 0;
353
- const pagePropsArgIndex = args.findIndex(hasUseCachePageMarker);
342
+ const limitedArgs = options.argumentCount === void 0 ? args : hasCaptureEnvelope ? [args[0], ...args.slice(1, 1 + options.argumentCount)] : args.slice(0, options.argumentCount);
343
+ const pagePropsArgIndex = replay?.pagePropsIndex ?? limitedArgs.findIndex(hasUseCachePageMarker);
344
+ const layoutPropsArgIndex = replay?.layoutPropsIndex ?? limitedArgs.findIndex(hasUseCacheLayoutMarker);
354
345
  const isPageInvocation = pagePropsArgIndex !== -1;
355
- const invocationArgs = isPageInvocation ? replaceArgument(args, pagePropsArgIndex, withoutUseCachePageMarker(args[pagePropsArgIndex])) : args;
356
- const admittedArgs = options.argumentCount === void 0 ? invocationArgs : hasCaptureEnvelope ? [invocationArgs[0], ...invocationArgs.slice(1, 1 + options.argumentCount)] : invocationArgs.slice(0, options.argumentCount);
346
+ const segmentPropsIndex = isPageInvocation ? pagePropsArgIndex : layoutPropsArgIndex;
347
+ const admittedArgs = segmentPropsIndex !== -1 && !replay ? replaceArgument(limitedArgs, segmentPropsIndex, withoutUseCacheSegmentMarker(limitedArgs[segmentPropsIndex], isPageInvocation)) : limitedArgs;
357
348
  const executionArgs = hasCaptureEnvelope ? [captures, ...admittedArgs.slice(1)] : admittedArgs;
349
+ const outerParams = !replay && segmentPropsIndex !== -1 ? executionArgs[segmentPropsIndex].params : void 0;
350
+ const fallbackParams = getFallbackParamsSnapshot(outerParams);
351
+ const serializationArgs = fallbackParams ? replaceArgument(executionArgs, segmentPropsIndex, {
352
+ ...executionArgs[segmentPropsIndex],
353
+ params: fallbackParams
354
+ }) : executionArgs;
358
355
  const pagePropsIndex = omitAppPageSearchParams && isPageInvocation ? pagePropsArgIndex : void 0;
359
- const callArgs = pagePropsIndex === void 0 ? executionArgs : withErroringPageSearchParams(executionArgs, pagePropsIndex);
360
- let cacheKey;
361
- try {
362
- const processedArgs = executionArgs.length > 0 ? unwrapThenableObjectArray(executionArgs, {
363
- pagePropsIndex,
364
- omitMarkedAppPageSearchParams: omitAppPageSearchParams
365
- }) : [];
366
- if (rsc && executionArgs.length > 0) {
367
- const tempRefs = rsc.createClientTemporaryReferenceSet();
368
- const encoded = await rsc.encodeReply(processedArgs, { temporaryReferences: tempRefs });
369
- cacheKey = buildUseCacheKey(cacheFunctionId, keySeed, await replyToCacheKey(encoded));
370
- } else {
371
- const argsKey = processedArgs.length > 0 ? stableStringify(processedArgs) : void 0;
372
- cacheKey = buildUseCacheKey(cacheFunctionId, keySeed, argsKey);
373
- }
374
- } catch {
375
- return (await executeWithContext(fn, callArgs, cacheVariant)).result;
356
+ const keyArgs = pagePropsIndex === void 0 ? serializationArgs : toReplayablePageArgs(serializationArgs, pagePropsIndex);
357
+ const flightArguments = replay ?? await snapshotFlightReply(await rsc.encodeReply(keyArgs, { temporaryReferences: clientReferences }), isPageInvocation ? pagePropsArgIndex : void 0);
358
+ if (!replay && layoutPropsArgIndex !== -1) flightArguments.layoutPropsIndex = layoutPropsArgIndex;
359
+ let cacheKey = buildUseCacheKey(cacheFunctionId, keySeed, flightArgumentsKey(flightArguments));
360
+ let callArgs = await rsc.decodeReply(restoreFlightReply(flightArguments), { temporaryReferences: serverReferences });
361
+ if (segmentPropsIndex !== -1) {
362
+ const props = callArgs[segmentPropsIndex];
363
+ callArgs = replaceArgument(callArgs, segmentPropsIndex, {
364
+ ...props,
365
+ params: restoreThenableParams(await props.params, outerParams)
366
+ });
367
+ if (pagePropsIndex !== void 0) callArgs = withErroringPageSearchParams(callArgs, pagePropsIndex);
376
368
  }
369
+ if (fallbackParams) return (await executeWithContext(fn, callArgs, cacheVariant, rsc)).result;
377
370
  if (cacheVariant === "private") {
378
371
  const privateCache = _getPrivateState()._privateCache;
379
372
  const privateHit = privateCache.get(cacheKey);
380
- if (privateHit !== void 0) return privateHit;
373
+ if (privateHit !== void 0) {
374
+ if (privateHit.serialized) return rsc.createFromReadableStream(uint8ToStream(base64ToUint8(privateHit.serialized.body)), {}, { preserveServerReferences: true });
375
+ return privateHit.result;
376
+ }
381
377
  const execution = await executeWithContext(fn, callArgs, cacheVariant, rsc);
382
- if (execution.cacheable) privateCache.set(cacheKey, execution.result);
378
+ if (execution.cacheable) privateCache.set(cacheKey, execution);
383
379
  return execution.result;
384
380
  }
385
381
  if (isDev || isDraftModeEnabled()) return (await executeWithContext(fn, callArgs, cacheVariant, rsc)).result;
@@ -399,7 +395,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
399
395
  console.error("[vinext] use cache: handler.get failed; treating as a cache miss:", error);
400
396
  }
401
397
  const redirectValue = existing?.value;
402
- if (isRootParamRedirect(existing) && existing?.cacheState !== "stale" && rootParams && redirectValue?.kind === "FETCH" && !_hasPendingRevalidatedTag([...redirectValue.tags ?? [], ...softTags])) {
398
+ if (isRootParamRedirect(existing) && isServableCacheState(existing?.cacheState) && rootParams && redirectValue?.kind === "FETCH" && !_hasPendingRevalidatedTag([...redirectValue.tags ?? [], ...softTags])) {
403
399
  cacheKey = coarseCacheKey + computeRootParamsCacheKeySuffix(rootParams, addKnownRootParamNames(id, rootParamNamesFromTags(redirectValue.tags)));
404
400
  try {
405
401
  existing = await handler.get(cacheKey, {
@@ -411,78 +407,80 @@ function registerCachedFunction(fn, id, variant, options = {}) {
411
407
  existing = null;
412
408
  }
413
409
  }
414
- if (existing?.value && existing.value.kind === "FETCH" && existing.cacheState !== "stale" && !_hasPendingRevalidatedTag([...existing.value.tags ?? [], ...softTags])) try {
415
- propagateRootParamNamesToParent(knownRootParamsByFunctionId.get(id));
416
- propagateCacheTagsToRequest(existing.value.tags);
417
- if (rsc && existing.value.data.headers["x-vinext-rsc"] === "1") {
418
- const stream = uint8ToStream(base64ToUint8(existing.value.data.body));
419
- const result = await rsc.createFromReadableStream(stream, {}, { preserveServerReferences: true });
420
- recordRequestScopedCacheControl(existing.cacheControl);
421
- return result;
410
+ const generate = async (propagate) => {
411
+ const { result, ctx, effectiveLife, collectedResult } = await runCachedFunctionWithContext(fn, callArgs, cacheVariant, (value) => serializeCacheResult(value, rsc));
412
+ const rootParamNames = ctx.readRootParamNames && ctx.readRootParamNames.size > 0 ? addKnownRootParamNames(id, ctx.readRootParamNames) : knownRootParamsByFunctionId.get(id);
413
+ if (propagate) {
414
+ recordRequestScopedCacheLife(effectiveLife);
415
+ propagateCacheTagsToRequest(ctx.tags);
422
416
  }
423
- const result = JSON.parse(existing.value.data.body);
424
- recordRequestScopedCacheControl(existing.cacheControl);
425
- return result;
426
- } catch {}
427
- const { result, ctx, effectiveLife, collectedResult } = await runCachedFunctionWithContext(fn, callArgs, cacheVariant, (value) => serializeCacheResult(value, rsc));
428
- const rootParamNames = ctx.readRootParamNames && ctx.readRootParamNames.size > 0 ? addKnownRootParamNames(id, ctx.readRootParamNames) : knownRootParamsByFunctionId.get(id);
429
- recordRequestScopedCacheLife(effectiveLife);
430
- propagateCacheTagsToRequest(ctx.tags);
431
- const revalidateSeconds = effectiveLife.revalidate ?? cacheLifeProfiles.default.revalidate ?? 900;
432
- if (collectedResult?.cacheEntry) try {
433
- let cacheFunctionInvocation;
434
- if (options.serverReferenceId && options.encodeInvocationArgs) try {
435
- cacheFunctionInvocation = {
436
- encryptedArgs: await options.encodeInvocationArgs(pagePropsIndex === void 0 ? admittedArgs : toReplayablePageArgs(admittedArgs, pagePropsIndex)),
437
- referenceId: options.serverReferenceId,
438
- rootParams: Object.fromEntries(Object.entries(rootParams ?? {}).filter((entry) => entry[1] !== void 0)),
439
- softTags
440
- };
441
- } catch {}
442
- const serialized = collectedResult.cacheEntry;
443
- const cacheValue = {
444
- kind: "FETCH",
445
- data: {
446
- headers: serialized.headers,
447
- body: serialized.body,
448
- url: cacheKey
449
- },
450
- tags: ctx.tags,
451
- revalidate: revalidateSeconds
452
- };
453
- const cacheContext = {
454
- fetchCache: true,
455
- tags: ctx.tags,
456
- ...cacheFunctionInvocation ? { cacheFunctionInvocation } : {},
457
- cacheControl: {
458
- revalidate: revalidateSeconds,
459
- expire: effectiveLife.expire,
460
- stale: effectiveLife.stale
461
- }
462
- };
463
- if (rootParamNames && rootParamNames.size > 0 && rootParams) {
464
- const specificCacheKey = coarseCacheKey + computeRootParamsCacheKeySuffix(rootParams, rootParamNames);
465
- const redirectTags = [...ctx.tags, ...[...rootParamNames].map((name) => ROOT_PARAM_TAG_PREFIX + name)];
466
- await handler.set(coarseCacheKey, {
417
+ const revalidateSeconds = effectiveLife.revalidate ?? cacheLifeProfiles.default.revalidate ?? 900;
418
+ if (collectedResult?.cacheEntry) try {
419
+ let cacheFunctionInvocation;
420
+ if (options.serverReferenceId && options.encodeInvocation) try {
421
+ cacheFunctionInvocation = {
422
+ encryptedArgs: await options.encodeInvocation(flightArguments),
423
+ referenceId: options.serverReferenceId,
424
+ rootParams: Object.fromEntries(Object.entries(rootParams ?? {}).filter((entry) => entry[1] !== void 0)),
425
+ softTags
426
+ };
427
+ } catch {}
428
+ const serialized = collectedResult.cacheEntry;
429
+ const cacheValue = {
467
430
  kind: "FETCH",
468
431
  data: {
469
- headers: { [ROOT_PARAM_REDIRECT_HEADER]: "1" },
470
- body: "",
471
- url: coarseCacheKey
432
+ headers: serialized.headers,
433
+ body: serialized.body,
434
+ url: cacheKey
472
435
  },
473
- tags: redirectTags,
436
+ tags: ctx.tags,
474
437
  revalidate: revalidateSeconds
475
- }, {
476
- ...cacheContext,
477
- tags: redirectTags
478
- });
479
- cacheValue.data.url = specificCacheKey;
480
- await handler.set(specificCacheKey, cacheValue, cacheContext);
481
- } else await handler.set(cacheKey, cacheValue, cacheContext);
438
+ };
439
+ const cacheContext = {
440
+ fetchCache: true,
441
+ tags: ctx.tags,
442
+ ...cacheFunctionInvocation ? { cacheFunctionInvocation } : {},
443
+ cacheControl: {
444
+ revalidate: revalidateSeconds,
445
+ expire: effectiveLife.expire,
446
+ stale: effectiveLife.stale
447
+ }
448
+ };
449
+ if (rootParamNames && rootParamNames.size > 0 && rootParams) {
450
+ const specificCacheKey = coarseCacheKey + computeRootParamsCacheKeySuffix(rootParams, rootParamNames);
451
+ const redirectTags = [...ctx.tags, ...[...rootParamNames].map((name) => ROOT_PARAM_TAG_PREFIX + name)];
452
+ await handler.set(coarseCacheKey, {
453
+ kind: "FETCH",
454
+ data: {
455
+ headers: { [ROOT_PARAM_REDIRECT_HEADER]: "1" },
456
+ body: "",
457
+ url: coarseCacheKey
458
+ },
459
+ tags: redirectTags,
460
+ revalidate: revalidateSeconds
461
+ }, {
462
+ ...cacheContext,
463
+ tags: redirectTags
464
+ });
465
+ cacheValue.data.url = specificCacheKey;
466
+ await handler.set(specificCacheKey, cacheValue, cacheContext);
467
+ } else await handler.set(cacheKey, cacheValue, cacheContext);
468
+ } catch {}
469
+ return collectedResult ? collectedResult.result : result;
470
+ };
471
+ if (existing?.value && existing.value.kind === "FETCH" && existing.value.data.headers["x-vinext-rsc"] === "1" && isServableCacheState(existing.cacheState) && !_hasPendingRevalidatedTag([...existing.value.tags ?? [], ...softTags])) try {
472
+ propagateRootParamNamesToParent(knownRootParamsByFunctionId.get(id));
473
+ propagateCacheTagsToRequest(existing.value.tags);
474
+ const result = await rsc.createFromReadableStream(uint8ToStream(base64ToUint8(existing.value.data.body)), {}, { preserveServerReferences: true });
475
+ recordRequestScopedCacheControl(existing.cacheControl);
476
+ if (existing.cacheState === "stale") regenerateInBackground(id, () => generate(false));
477
+ return result;
482
478
  } catch {}
483
- return collectedResult ? collectedResult.result : result;
479
+ return generate(true);
484
480
  }, cacheVariant);
485
481
  };
482
+ const cachedFn = memoizeInCacheScope((...args) => invoke(args));
483
+ replayableFunctions.set(cachedFn, (data) => invoke([], data));
486
484
  Object.defineProperty(cachedFn, "length", {
487
485
  value: fn.length,
488
486
  configurable: true
@@ -491,6 +489,11 @@ function registerCachedFunction(fn, id, variant, options = {}) {
491
489
  if (options.acceptsSecondArgument !== void 0) Reflect.set(cachedFn, USE_CACHE_ACCEPTS_SECOND_ARGUMENT_SYMBOL, options.acceptsSecondArgument);
492
490
  return cachedFn;
493
491
  }
492
+ /** Memoize in one React render without skipping cache guards or scoped serialization. */
493
+ function memoizeInCacheScope(fn) {
494
+ const memoized = cache((_parent, _workUnit, _unstable, ...args) => fn(...args));
495
+ return (...args) => memoized(cacheContextStorage.getStore(), workUnitAsyncStorage.getStore(), unstableCacheContextStorage.getStore() === true, ...args);
496
+ }
494
497
  /** @internal Symbol used to identify "use cache" wrapper functions. */
495
498
  const USE_CACHE_FUNCTION_SYMBOL = Symbol.for("vinext.useCacheFunction");
496
499
  /** @internal Symbol carrying transform-derived cached function argument metadata. */
@@ -555,11 +558,12 @@ function propagateCacheTagsToRequest(tags) {
555
558
  addCollectedRequestTags(tags);
556
559
  }
557
560
  async function executeWithContext(fn, args, variant, rsc) {
558
- const { result, ctx: _ctx, effectiveLife, collectedResult } = await runCachedFunctionWithContext(fn, args, variant, rsc ? (value) => serializeCacheResult(value, rsc) : void 0);
561
+ const { result, ctx: _ctx, effectiveLife, collectedResult } = await runCachedFunctionWithContext(fn, args, variant, (value) => serializeCacheResult(value, rsc));
559
562
  recordRequestScopedCacheLife(effectiveLife);
560
563
  return {
561
564
  result: collectedResult ? collectedResult.result : result,
562
- cacheable: collectedResult?.cacheEntry !== null
565
+ cacheable: collectedResult?.cacheEntry !== null,
566
+ serialized: collectedResult?.cacheEntry
563
567
  };
564
568
  }
565
569
  async function runCachedFunctionWithContext(fn, args, variant, collectResult) {
@@ -605,31 +609,6 @@ async function runCachedFunctionWithContext(fn, args, variant, collectResult) {
605
609
  collectedResult
606
610
  };
607
611
  }
608
- function unwrapThenableObjects(value, options) {
609
- if (value === null || value === void 0 || typeof value !== "object") return value;
610
- const childOptions = { omitMarkedAppPageSearchParams: options.omitMarkedAppPageSearchParams };
611
- if (Array.isArray(value)) return value.map((item) => unwrapThenableObjects(item, childOptions));
612
- if (isThenableObject(value)) {
613
- const plain = {};
614
- for (const key of Object.keys(value)) plain[key] = unwrapThenableObjects(value[key], childOptions);
615
- return plain;
616
- }
617
- if (typeof value.then === "function") return value;
618
- const result = {};
619
- for (const key of Object.keys(value)) {
620
- if (key === "searchParams" && (options.omitAppPageSearchParamsAtRoot || options.omitMarkedAppPageSearchParams && isMarkedAppPagePropsObject(value))) continue;
621
- result[key] = unwrapThenableObjects(value[key], childOptions);
622
- }
623
- return result;
624
- }
625
- /**
626
- * A thenable (not an array) with own enumerable properties — the
627
- * `Object.assign(Promise.resolve(obj), obj)` pattern Next.js params use. The
628
- * cache key is built from its fields instead of the promise.
629
- */
630
- function isThenableObject(value) {
631
- return !Array.isArray(value) && "then" in value && typeof value.then === "function" && Object.keys(value).length > 0;
632
- }
633
612
  function isPagePropsObject(value) {
634
613
  return value !== null && typeof value === "object" && !Array.isArray(value);
635
614
  }
@@ -638,26 +617,17 @@ function replaceArgument(args, index, value) {
638
617
  result[index] = value;
639
618
  return result;
640
619
  }
641
- /** Remove the `$$isPage` invocation marker before props reach the key or user code. */
642
- function withoutUseCachePageMarker(props) {
643
- const { [APP_PAGE_USE_CACHE_MARKER]: _marker, ...pageProps } = props;
644
- return isMarkedAppPagePropsObject(props) ? markAppPagePropsForUseCache(pageProps) : pageProps;
645
- }
646
620
  /**
647
621
  * Response Store replay args for a public page cache: drop `searchParams` from
648
622
  * the page props at `index` (after a capture envelope, if any), as Next.js does
649
- * for the serialized arguments (use-cache-wrapper.ts, `isPageSegmentFunction`),
650
- * and keep the `$$isPage` marker so a replay regains page semantics, including
651
- * the erroring searchParams fallback.
623
+ * for the serialized arguments (use-cache-wrapper.ts, `isPageSegmentFunction`).
624
+ * The replay regains page semantics from `CacheFlightArguments.pagePropsIndex`.
652
625
  */
653
626
  function toReplayablePageArgs(args, index) {
654
627
  const props = args[index];
655
628
  if (!isPagePropsObject(props)) return [...args];
656
629
  const { searchParams: _searchParams, ...pageProps } = props;
657
- return replaceArgument(args, index, {
658
- ...pageProps,
659
- [APP_PAGE_USE_CACHE_MARKER]: true
660
- });
630
+ return replaceArgument(args, index, pageProps);
661
631
  }
662
632
  /**
663
633
  * Give page props at `index` without `searchParams` (a Response Store replay of
@@ -673,35 +643,5 @@ function withErroringPageSearchParams(args, index) {
673
643
  searchParams: makeThenableParams({}, { observeParamAccess: () => throwIfInsideCacheScope("searchParams") })
674
644
  });
675
645
  }
676
- function unwrapThenableObjectArray(values, options) {
677
- return values.map((value, index) => unwrapThenableObjects(value, {
678
- omitAppPageSearchParamsAtRoot: index === options.pagePropsIndex,
679
- omitMarkedAppPageSearchParams: options.omitMarkedAppPageSearchParams
680
- }));
681
- }
682
- function stableStringify(value, seen) {
683
- if (value === void 0) return "undefined";
684
- if (value === null) return "null";
685
- if (typeof value === "function") throw new Error("Cannot serialize function");
686
- if (typeof value === "symbol") throw new Error("Cannot serialize symbol");
687
- if (Array.isArray(value)) {
688
- if (!seen) seen = /* @__PURE__ */ new Set();
689
- if (seen.has(value)) throw new Error("Circular reference");
690
- seen.add(value);
691
- const result = "[" + value.map((v) => stableStringify(v, seen)).join(",") + "]";
692
- seen.delete(value);
693
- return result;
694
- }
695
- if (typeof value === "object" && value !== null) {
696
- if (value instanceof Date) return `Date(${value.getTime()})`;
697
- if (!seen) seen = /* @__PURE__ */ new Set();
698
- if (seen.has(value)) throw new Error("Circular reference");
699
- seen.add(value);
700
- const result = "{" + Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(value[k], seen)}`).join(",") + "}";
701
- seen.delete(value);
702
- return result;
703
- }
704
- return JSON.stringify(value);
705
- }
706
646
  //#endregion
707
- export { NestedDynamicUseCacheError, buildUseCacheKey, cacheContextStorage, clearPrivateCache, getCacheContext, isThenableObject, isUseCacheFunction, markAppPagePropsForUseCache, registerCachedFunction, replyToCacheKey, runWithPrivateCache };
647
+ export { NestedDynamicUseCacheError, buildUseCacheKey, cacheContextStorage, clearPrivateCache, getCacheContext, isUseCacheFunction, memoizeInCacheScope, registerCachedFunction, replayCachedFunction, runWithPrivateCache };
@@ -1,6 +1,6 @@
1
1
  import { ExecutionContextLike, getRequestExecutionContext, runWithExecutionContext } from "./request-context.js";
2
2
  import { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext } from "./internal/cache-context-accessor.js";
3
- import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheLifeConfig, CacheState, UnstableCacheObservation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, recordUnstableCacheObservation, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
3
+ import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheLifeConfig, CacheLifeSink, CacheState, UnstableCacheObservation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
4
4
  import { CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, configureMemoryCacheHandler, getCacheHandler, getDataCacheHandler, registerDataCacheHandler, registerLazyDataCacheHandler, setCacheHandler, setDataCacheHandler } from "./cache-handler.js";
5
5
  //#region src/shims/cache.d.ts
6
6
  /**
@@ -142,4 +142,4 @@ type UnstableCacheOptions = {
142
142
  */
143
143
  export declare function unstable_cache<T extends (...args: any[]) => Promise<any>>(fn: T, keyParts?: string[], options?: UnstableCacheOptions): T;
144
144
  //#endregion
145
- export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CacheLifeConfig, CacheState, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, type ExecutionContextLike, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, UnstableCacheObservation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, markActionRevalidation, unstable_noStore as noStore, recordUnstableCacheObservation, registerDataCacheHandler, registerLazyDataCacheHandler, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry };
145
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, ActionRevalidationKind, CacheControlMetadata, CacheHandler, CacheHandlerContext, CacheHandlerValue, CacheLifeConfig, CacheLifeSink, CacheState, CachedAppPageValue, CachedFetchValue, CachedImageValue, CachedPagesValue, CachedRedirectValue, CachedRouteValue, type ExecutionContextLike, IncrementalCacheValue, MemoryCacheHandler, NoOpCacheHandler, UnstableCacheObservation, UnstableCacheRevalidationMode, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, markActionRevalidation, unstable_noStore as noStore, recordUnstableCacheObservation, registerDataCacheHandler, registerLazyDataCacheHandler, runWithDetachedCacheObservations, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry };
@@ -11,8 +11,8 @@ import { makeHangingPromise } from "./internal/make-hanging-promise.js";
11
11
  import { getHeadersAccessPhase, markDynamicUsage } from "./internal/headers-state.js";
12
12
  import { isDraftModeEnabled } from "./headers.js";
13
13
  import { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext } from "./internal/cache-context-accessor.js";
14
- import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, recordUnstableCacheObservation, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
15
- import { addCollectedRequestTags, getCurrentFetchSoftTags } from "./fetch-cache.js";
14
+ import { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry } from "./cache-request-state.js";
15
+ import { addCollectedRequestTags, getCurrentFetchSoftTags, runWithDetachedFetchObservations } from "./fetch-cache.js";
16
16
  //#region src/shims/cache.ts
17
17
  /**
18
18
  * next/cache shim
@@ -322,6 +322,7 @@ const UNSTABLE_CACHE_KEY_PREFIX = "unstable_cache:v2";
322
322
  * a direct import (avoiding circular dependencies).
323
323
  */
324
324
  const _unstableCacheAls = getOrCreateAls("vinext.unstableCache.als");
325
+ const _useCacheContextAls = getOrCreateAls("vinext.cacheRuntime.contextAls");
325
326
  function serializeUnstableCacheResult(value) {
326
327
  return JSON.stringify(value === void 0 ? { undef: true } : { v: value });
327
328
  }
@@ -362,7 +363,7 @@ function waitUntilUnstableCacheRevalidation(promise) {
362
363
  function scheduleUnstableCacheBackgroundRevalidation(cacheKey, refresh) {
363
364
  const pending = getPendingUnstableCacheRevalidations();
364
365
  if (pending.has(cacheKey)) return;
365
- const trackedRevalidation = refresh().then(() => void 0).catch((err) => {
366
+ const trackedRevalidation = runWithDetachedCacheObservations(() => runWithDetachedFetchObservations(() => _useCacheContextAls.exit(refresh))).then(() => void 0).catch((err) => {
366
367
  console.error(`[vinext] unstable_cache background revalidation failed for ${cacheKey}:`, err);
367
368
  }).finally(() => {
368
369
  if (pending.get(cacheKey) === trackedRevalidation) pending.delete(cacheKey);
@@ -436,4 +437,4 @@ function unstable_cache(fn, keyParts, options) {
436
437
  return cachedFn;
437
438
  }
438
439
  //#endregion
439
- export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, MemoryCacheHandler, NoOpCacheHandler, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLife, cacheLifeProfiles, cacheTag, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, io, isInsideUnstableCacheScope, markActionRevalidation, unstable_noStore as noStore, unstable_noStore, recordUnstableCacheObservation, refresh, registerDataCacheHandler, registerLazyDataCacheHandler, revalidatePath, revalidateTag, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
440
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, MemoryCacheHandler, NoOpCacheHandler, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLife, cacheLifeProfiles, cacheTag, configureMemoryCacheHandler, getAndClearActionRevalidationKind, getCacheHandler, getDataCacheHandler, getRegisteredCacheContext, getRequestExecutionContext, io, isInsideUnstableCacheScope, markActionRevalidation, unstable_noStore as noStore, unstable_noStore, recordUnstableCacheObservation, refresh, registerDataCacheHandler, registerLazyDataCacheHandler, revalidatePath, revalidateTag, runWithDetachedCacheObservations, runWithExecutionContext, setCacheHandler, setDataCacheHandler, shouldServeStaleUnstableCacheEntry, unstable_cache, unstable_cacheLife, unstable_cacheTag, unstable_io, updateTag };
@@ -10,6 +10,13 @@ export type RouteCacheabilityOutcome = {
10
10
  searchParamsUnread?: true;
11
11
  /** A transient classification failure that may succeed on another bounded attempt. */
12
12
  retryable?: true;
13
+ /**
14
+ * App page RSC: the status and headers that replace the streamed 200 of a
15
+ * render whose special error rejected its document's shell, once the
16
+ * response is complete.
17
+ */
18
+ status?: number;
19
+ headers?: Readonly<Record<string, string>>;
13
20
  tags?: readonly string[];
14
21
  };
15
22
  export type RouteCacheabilityState = {
@@ -2,4 +2,11 @@ import { AppRouterInstance } from "./internal/app-router-context.js";
2
2
  //#region src/shims/error-boundary-navigation.d.ts
3
3
  export declare function useErrorBoundaryPathname(): string;
4
4
  export declare function useErrorBoundaryRouter(): AppRouterInstance;
5
+ /**
6
+ * Whether following a redirect() would refetch the URL the browser is at,
7
+ * which renders the same redirect again. As in the navigation planner, only the
8
+ * exact URL refreshes the page; any hash change, including removing the
9
+ * current hash, only scrolls.
10
+ */
11
+ export declare function isRedirectToCurrentUrl(redirect: string): boolean;
5
12
  //#endregion
@@ -1,6 +1,7 @@
1
1
  import { stripBasePath } from "../utils/base-path.js";
2
2
  import { markPprFallbackShellDynamicBoundary } from "./ppr-fallback-shell.js";
3
3
  import { AppRouterContext } from "./internal/app-router-context.js";
4
+ import { toCanonicalBrowserNavigationHref, toSameOriginAppPath } from "./url-utils.js";
4
5
  import { getNavigationContext } from "./navigation-context-state.js";
5
6
  import "./navigation-server.js";
6
7
  import * as React$1 from "react";
@@ -8,6 +9,7 @@ import * as React$1 from "react";
8
9
  const CLIENT_NAVIGATION_STATE_KEY = Symbol.for("vinext.clientNavigationState");
9
10
  const CLIENT_NAVIGATION_RENDER_CONTEXT_KEY = Symbol.for("vinext.clientNavigationRenderContext");
10
11
  const BASE_PATH = process.env.__NEXT_ROUTER_BASEPATH ?? "";
12
+ const TRAILING_SLASH = process.env.__VINEXT_TRAILING_SLASH === "true";
11
13
  function getClientNavigationState() {
12
14
  return globalThis[CLIENT_NAVIGATION_STATE_KEY];
13
15
  }
@@ -40,5 +42,21 @@ function useErrorBoundaryRouter() {
40
42
  if (router === null) throw new Error("invariant expected app router to be mounted");
41
43
  return router;
42
44
  }
45
+ /**
46
+ * Whether following a redirect() would refetch the URL the browser is at,
47
+ * which renders the same redirect again. As in the navigation planner, only the
48
+ * exact URL refreshes the page; any hash change, including removing the
49
+ * current hash, only scrolls.
50
+ */
51
+ function isRedirectToCurrentUrl(redirect) {
52
+ if (typeof window === "undefined") return false;
53
+ try {
54
+ const current = new URL(window.location.href);
55
+ const target = new URL(toCanonicalBrowserNavigationHref(toSameOriginAppPath(redirect, BASE_PATH) ?? redirect, current.href, BASE_PATH, TRAILING_SLASH), current.href);
56
+ return target.hash === current.hash && target.origin === current.origin && target.pathname === current.pathname && target.search === current.search;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
43
61
  //#endregion
44
- export { useErrorBoundaryPathname, useErrorBoundaryRouter };
62
+ export { isRedirectToCurrentUrl, useErrorBoundaryPathname, useErrorBoundaryRouter };