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
@@ -1,190 +1,72 @@
1
- import { isThenableObject, isUseCacheFunction, registerCachedFunction as registerCachedFunction$1 } from "./cache-runtime.js";
1
+ import { restoreFlightReply, snapshotFlightReply } from "./cache-flight-arguments.js";
2
+ import { isUseCacheFunction, memoizeInCacheScope, registerCachedFunction as registerCachedFunction$1, replayCachedFunction } from "./cache-runtime.js";
2
3
  import { decryptActionBoundArgs, encryptActionBoundArgs } from "@vitejs/plugin-rsc/utils/encryption-runtime";
4
+ import { createFromReadableStream, renderToReadableStream } from "@vitejs/plugin-rsc/react/rsc";
3
5
  //#region src/shims/cache-callable-runtime.ts
4
6
  const CACHE_CAPTURE_TYPE = "use-cache-captures";
5
- function encryptCacheCaptures(captures) {
6
- return {
7
- type: CACHE_CAPTURE_TYPE,
8
- encrypted: encryptCaptures(captures)
9
- };
7
+ async function encryptArguments(value) {
8
+ return encryptActionBoundArgs(JSON.stringify(value));
9
+ }
10
+ async function decryptArguments(encrypted) {
11
+ const serialized = await decryptActionBoundArgs(Promise.resolve(encrypted));
12
+ if (typeof serialized !== "string") throw new Error("Invalid cache function arguments");
13
+ const value = JSON.parse(serialized);
14
+ if (value?.version !== 1) throw new Error("Invalid cache function arguments");
15
+ return value;
10
16
  }
11
- async function decryptCacheCaptures(value) {
12
- if (!isCacheCaptureEnvelope(value)) return;
13
- return decryptCacheArguments(value.encrypted);
17
+ function encryptCacheCaptures(captures) {
18
+ return memoizedCaptureEnvelope(...captures);
14
19
  }
20
+ const memoizedCaptureEnvelope = memoizeInCacheScope((...captures) => ({
21
+ type: CACHE_CAPTURE_TYPE,
22
+ encrypted: encryptCaptures(captures)
23
+ }));
15
24
  async function encryptCaptures(captures) {
16
- return encryptActionBoundArgs(await encodeCacheArguments(captures, "skip"));
17
- }
18
- async function encryptCacheArguments(args) {
19
- return encryptActionBoundArgs(await encodeCacheArguments(args));
20
- }
21
- async function decryptCacheArguments(encrypted) {
22
- return decodeCacheArguments(await decryptActionBoundArgs(Promise.resolve(encrypted)));
23
- }
24
- function isThenable(value) {
25
- return "then" in value && typeof value.then === "function";
26
- }
27
- function isPlainRecord(value) {
28
- const prototype = Object.getPrototypeOf(value);
29
- return (prototype === Object.prototype || prototype === null) && !("$$typeof" in value);
30
- }
31
- /**
32
- * The arguments hold a getter. Flight reads it again while serializing, and a
33
- * getter can return a different value, such as a new promise, on every read,
34
- * so the recorded promise fields may not match what Flight serializes.
35
- */
36
- var UnreplayableCacheArgumentsError = class extends Error {
37
- constructor() {
38
- super("Cache function arguments with getters cannot be replayed");
39
- }
40
- };
41
- /**
42
- * The own entries of `value` that Flight serializes: every index below an
43
- * array's `length`, including holes and non-enumerable indices, as
44
- * `JSON.stringify` reads them, and the enumerable own keys of anything else.
45
- * A getter, own or inherited through an array hole, is never run.
46
- */
47
- function ownEntries(value, getters) {
48
- const keys = Array.isArray(value) ? Array.from({ length: value.length }, (_, index) => String(index)) : Object.keys(value);
49
- const entries = [];
50
- for (const key of keys) {
51
- if (readsGetter(value, key)) {
52
- if (getters === "reject") throw new UnreplayableCacheArgumentsError();
53
- continue;
54
- }
55
- entries.push([key, Reflect.get(value, key)]);
56
- }
57
- return entries;
58
- }
59
- /**
60
- * Whether reading `key` from `value` runs a getter. An array hole reads
61
- * through the prototype chain, which can hold an indexed data property or
62
- * accessor. A setter-only accessor reads as `undefined` without running code.
63
- */
64
- function readsGetter(value, key) {
65
- for (let target = value; target !== null; target = Reflect.getPrototypeOf(target)) {
66
- const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
67
- if (descriptor) return typeof descriptor.get === "function";
68
- }
69
- return false;
70
- }
71
- /** Members of the values Flight serializes recursively, which can hold params. */
72
- function flightMembers(value, getters) {
73
- if (value instanceof Map) return [...value].flat();
74
- if (value instanceof Set) return [...value];
75
- if (isThenableObject(value) || Array.isArray(value) || isPlainRecord(value)) return ownEntries(value, getters).map(([, member]) => member);
76
- return [];
77
- }
78
- /**
79
- * Collect the own fields of every promise-augmented object in the arguments,
80
- * including ones inside resolved promise values. The arguments are passed to
81
- * Flight as they are, so Flight keeps their shared references and cycles.
82
- * With `getters: "reject"`, throws `UnreplayableCacheArgumentsError` when the
83
- * arguments hold a getter; with `"skip"`, promise fields behind a getter are
84
- * not recorded.
85
- */
86
- async function encodeCacheArguments(args, getters = "reject") {
87
- const settled = /* @__PURE__ */ new Map();
88
- for (;;) {
89
- const thenableObjects = [];
90
- const unsettled = [];
91
- const visited = /* @__PURE__ */ new Set();
92
- const pending = [args];
93
- while (pending.length > 0) {
94
- const value = pending.pop();
95
- if (typeof value !== "object" || value === null || visited.has(value)) continue;
96
- visited.add(value);
97
- const children = flightMembers(value, getters);
98
- if (isThenableObject(value)) {
99
- const fields = Object.fromEntries(ownEntries(value, getters));
100
- thenableObjects.push({
101
- fields,
102
- promise: value
103
- });
25
+ const files = new FormData();
26
+ let nextFileId = 0;
27
+ const temporaryReferences = new class extends WeakMap {
28
+ get(value) {
29
+ let id = super.get(value);
30
+ if (id === void 0 && value instanceof File) {
31
+ id = String(nextFileId++);
32
+ super.set(value, id);
33
+ files.append(id, value);
104
34
  }
105
- if (isThenable(value)) {
106
- const result = settled.get(value);
107
- if (!result) unsettled.push(value);
108
- else if (result.status === "fulfilled") children.push(result.value);
109
- }
110
- for (const child of children) pending.push(child);
35
+ return id;
111
36
  }
112
- if (unsettled.length === 0) return {
113
- args,
114
- thenableObjects
115
- };
116
- const results = await Promise.allSettled(unsettled);
117
- unsettled.forEach((thenable, index) => settled.set(thenable, results[index]));
118
- }
119
- }
120
- /** Restore the promise fields that `encodeCacheArguments` captured before Flight encoding. */
121
- function decodeCacheArguments(value) {
122
- if (typeof value !== "object" || value === null || !("args" in value) || !Array.isArray(value.args) || !("thenableObjects" in value) || !Array.isArray(value.thenableObjects)) throw new Error("Invalid cache function arguments");
123
- adoptFlightThenables(value);
124
- for (const thenableObject of value.thenableObjects) {
125
- if (!isEncodedThenableObject(thenableObject)) throw new Error("Invalid cache function arguments");
126
- Object.assign(thenableObject.promise, thenableObject.fields);
127
- }
128
- return value.args;
37
+ }();
38
+ const stream = renderToReadableStream(captures, { temporaryReferences });
39
+ const bytes = await new Response(stream).arrayBuffer();
40
+ return encryptActionBoundArgs(JSON.stringify({
41
+ version: 1,
42
+ result: Buffer.from(bytes).toString("base64"),
43
+ files: await snapshotFlightReply(files)
44
+ }));
129
45
  }
130
- function isEncodedThenableObject(value) {
131
- if (typeof value !== "object" || value === null || !isPlainRecord(value)) return false;
132
- const { fields, promise } = value;
133
- return typeof fields === "object" && fields !== null && isPlainRecord(fields) && promise instanceof Promise;
134
- }
135
- /**
136
- * Flight decodes promises as React chunks whose own fields are React's
137
- * internal state. Adopt them into native promises, which have no own fields,
138
- * so key serialization sees a promise exactly as the original call did.
139
- * Flight shares one chunk between references to the same promise, so each
140
- * chunk is adopted once, including references inside resolved values.
141
- */
142
- function adoptFlightThenables(value, adopted = /* @__PURE__ */ new WeakMap()) {
143
- if (typeof value !== "object" || value === null) return value;
144
- if (adopted.has(value)) return adopted.get(value);
145
- if (isThenable(value)) {
146
- const promise = Promise.resolve(value).then((resolved) => adoptFlightThenables(resolved, adopted));
147
- promise.catch(() => {});
148
- adopted.set(value, promise);
149
- return promise;
150
- }
151
- if (value instanceof Map) {
152
- adopted.set(value, value);
153
- const entries = [...value];
154
- value.clear();
155
- for (const [key, item] of entries) value.set(adoptFlightThenables(key, adopted), adoptFlightThenables(item, adopted));
156
- return value;
157
- }
158
- if (value instanceof Set) {
159
- adopted.set(value, value);
160
- const items = [...value];
161
- value.clear();
162
- for (const item of items) value.add(adoptFlightThenables(item, adopted));
163
- return value;
164
- }
165
- if (!Array.isArray(value) && !isPlainRecord(value)) return value;
166
- adopted.set(value, value);
167
- for (const key of Object.keys(value)) Reflect.set(value, key, adoptFlightThenables(Reflect.get(value, key), adopted));
168
- return value;
169
- }
170
- function isCacheCaptureEnvelope(value) {
171
- if (typeof value !== "object" || value === null) return false;
172
- if (!("type" in value) || value.type !== CACHE_CAPTURE_TYPE || !("encrypted" in value)) return false;
46
+ async function decryptCacheCaptures(value) {
47
+ if (typeof value !== "object" || value === null || !("type" in value) || value.type !== CACHE_CAPTURE_TYPE || !("encrypted" in value)) return;
173
48
  const encrypted = value.encrypted;
174
- return typeof encrypted === "string" || typeof encrypted === "object" && encrypted !== null && isThenable(encrypted);
49
+ if (typeof encrypted !== "string" && !(typeof encrypted === "object" && encrypted !== null && "then" in encrypted && typeof encrypted.then === "function")) return;
50
+ const serialized = await decryptActionBoundArgs(Promise.resolve(encrypted));
51
+ if (typeof serialized !== "string") throw new Error("Invalid cache capture arguments");
52
+ const payload = JSON.parse(serialized);
53
+ if (payload.version !== 1) throw new Error("Invalid cache capture arguments");
54
+ const files = restoreFlightReply(payload.files);
55
+ if (typeof files === "string") throw new Error("Invalid cache capture files");
56
+ return await createFromReadableStream(new Response(Buffer.from(payload.result, "base64")).body, { temporaryReferences: new Map([...files].map(([id, file]) => [`$${id}`, file])) }, { preserveServerReferences: true });
175
57
  }
176
58
  function registerCachedFunction(fn, id, variant, options) {
177
59
  return registerCachedFunction$1(fn, id, variant, {
178
60
  ...options,
179
61
  decryptCaptures: decryptCacheCaptures,
180
- encodeInvocationArgs: encryptCacheArguments
62
+ encodeInvocation: encryptArguments
181
63
  });
182
64
  }
183
- /** Load and invoke one transformed cache function through its server-reference identity. */
65
+ /** Replay the persisted payload without re-reading or re-encoding caller objects. */
184
66
  async function invokeCacheFunction(invocation, loadServerAction) {
185
67
  const fn = await loadServerAction(invocation.referenceId);
186
68
  if (!isUseCacheFunction(fn)) throw new Error(`Server reference ${invocation.referenceId} is not a cache function`);
187
- await fn(...await decryptCacheArguments(invocation.encryptedArgs));
69
+ await replayCachedFunction(fn, await decryptArguments(invocation.encryptedArgs));
188
70
  }
189
71
  //#endregion
190
- export { decodeCacheArguments, encodeCacheArguments, encryptCacheCaptures, invokeCacheFunction, registerCachedFunction };
72
+ export { encryptCacheCaptures, invokeCacheFunction, registerCachedFunction };
@@ -0,0 +1,22 @@
1
+ //#region src/shims/cache-flight-arguments.d.ts
2
+ /** The persisted argument transport. React owns the object graph and wire protocol. */
3
+ export type CacheFlightArguments = {
4
+ version: 1;
5
+ reply: string | ([string, string] | [string, string, string, number, string])[];
6
+ pagePropsIndex?: number;
7
+ layoutPropsIndex?: number;
8
+ };
9
+ /** Snapshot only the multipart transport, never traverse user arguments. */
10
+ export declare function snapshotFlightReply(reply: string | FormData, pagePropsIndex?: number): Promise<CacheFlightArguments>;
11
+ export declare function restoreFlightReply(args: CacheFlightArguments): string | FormData;
12
+ /**
13
+ * Like Next.js (use-cache-wrapper.ts, `encodeFormData`), a binary entry is keyed
14
+ * by its bytes alone. Its name, type, and timestamp are replayed but not keyed:
15
+ * native FormData gives each Blob wrapper a wall-clock timestamp.
16
+ *
17
+ * Hashed synchronously, as Next.js derives its key without leaving the task: a
18
+ * cache hit must resolve before React flushes the shell, and an async digest
19
+ * waits for a later event-loop turn.
20
+ */
21
+ export declare function flightArgumentsKey(args: CacheFlightArguments): string;
22
+ //#endregion
@@ -0,0 +1,56 @@
1
+ import { createHash } from "node:crypto";
2
+ //#region src/shims/cache-flight-arguments.ts
3
+ const blobType = Object.getOwnPropertyDescriptor(Blob.prototype, "type").get;
4
+ const fileName = Object.getOwnPropertyDescriptor(File.prototype, "name").get;
5
+ const fileTime = Object.getOwnPropertyDescriptor(File.prototype, "lastModified").get;
6
+ /** Snapshot only the multipart transport, never traverse user arguments. */
7
+ async function snapshotFlightReply(reply, pagePropsIndex) {
8
+ if (typeof reply === "string") return {
9
+ version: 1,
10
+ reply,
11
+ pagePropsIndex
12
+ };
13
+ const entries = [];
14
+ for (const [name, value] of reply) entries.push(typeof value === "string" ? [name, value] : [
15
+ name,
16
+ Buffer.from(await Blob.prototype.arrayBuffer.call(value)).toString("base64"),
17
+ fileName.call(value),
18
+ fileTime.call(value),
19
+ blobType.call(value)
20
+ ]);
21
+ return {
22
+ version: 1,
23
+ reply: entries,
24
+ pagePropsIndex
25
+ };
26
+ }
27
+ function restoreFlightReply(args) {
28
+ if (typeof args.reply === "string") return args.reply;
29
+ const reply = new FormData();
30
+ for (const entry of args.reply) {
31
+ const [name, data] = entry;
32
+ reply.append(name, entry.length === 2 ? data : new File([Buffer.from(data, "base64")], entry[2], {
33
+ lastModified: entry[3],
34
+ type: entry[4]
35
+ }));
36
+ }
37
+ return reply;
38
+ }
39
+ /**
40
+ * Like Next.js (use-cache-wrapper.ts, `encodeFormData`), a binary entry is keyed
41
+ * by its bytes alone. Its name, type, and timestamp are replayed but not keyed:
42
+ * native FormData gives each Blob wrapper a wall-clock timestamp.
43
+ *
44
+ * Hashed synchronously, as Next.js derives its key without leaving the task: a
45
+ * cache hit must resolve before React flushes the shell, and an async digest
46
+ * waits for a later event-loop turn.
47
+ */
48
+ function flightArgumentsKey(args) {
49
+ const reply = typeof args.reply === "string" ? args.reply : args.reply.map((entry) => entry.length === 2 ? entry : [entry[0], { bytes: entry[1] }]);
50
+ return createHash("sha256").update(JSON.stringify({
51
+ ...args,
52
+ reply
53
+ })).digest("base64url");
54
+ }
55
+ //#endregion
56
+ export { flightArgumentsKey, restoreFlightReply, snapshotFlightReply };
@@ -15,8 +15,17 @@ export type UnstableCacheObservation = Readonly<{
15
15
  tagCount: number;
16
16
  tagHash: string | null;
17
17
  }>;
18
+ /**
19
+ * Collects the cacheLife claims made anywhere below the scope that installed
20
+ * it, including nested scopes that reset `requestScopedCacheLife`. Shallow
21
+ * scope clones share it by reference.
22
+ */
23
+ export type CacheLifeSink = {
24
+ cacheLife: CacheLifeConfig | null;
25
+ };
18
26
  export type CacheState = {
19
27
  actionRevalidationKind: ActionRevalidationKind;
28
+ cacheLifeSink: CacheLifeSink | null;
20
29
  pendingRevalidatedTags: Set<string>;
21
30
  pendingRevalidations: Set<Promise<void>>;
22
31
  requestScopedCacheLife: CacheLifeConfig | null;
@@ -70,6 +79,12 @@ export declare function _captureRequestScopedCacheLifeAccessors(): {
70
79
  };
71
80
  export declare function recordUnstableCacheObservation(observation: UnstableCacheObservation): void;
72
81
  export declare function _peekUnstableCacheObservations(): UnstableCacheObservation[];
82
+ /**
83
+ * Run `fn` with the request's cache settings, including its revalidation mode,
84
+ * but its own record of cache lives and unstable_cache observations, so work
85
+ * done on the request's behalf after the fact leaves the request untouched.
86
+ */
87
+ export declare function runWithDetachedCacheObservations<T>(fn: () => Promise<T>): Promise<T>;
73
88
  export declare function shouldServeStaleUnstableCacheEntry(): boolean;
74
89
  //#endregion
75
90
  export { _recordUseCacheRootParamRead, _registerCacheContextAccessor, getRegisteredCacheContext };
@@ -47,6 +47,7 @@ const ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC = 1;
47
47
  const ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;
48
48
  const fallbackState = globalState[FALLBACK_KEY] ??= {
49
49
  actionRevalidationKind: ACTION_DID_NOT_REVALIDATE,
50
+ cacheLifeSink: null,
50
51
  pendingRevalidatedTags: /* @__PURE__ */ new Set(),
51
52
  pendingRevalidations: /* @__PURE__ */ new Set(),
52
53
  requestScopedCacheLife: null,
@@ -60,12 +61,14 @@ function getCacheState() {
60
61
  function _runWithCacheState(fn) {
61
62
  if (isInsideUnifiedScope()) return runWithUnifiedStateMutation((context) => {
62
63
  context.actionRevalidationKind = ACTION_DID_NOT_REVALIDATE;
64
+ context.cacheLifeSink = null;
63
65
  context.requestScopedCacheLife = null;
64
66
  context.unstableCacheObservations = /* @__PURE__ */ new Map();
65
67
  context.unstableCacheRevalidation = "foreground";
66
68
  }, fn);
67
69
  const state = {
68
70
  actionRevalidationKind: ACTION_DID_NOT_REVALIDATE,
71
+ cacheLifeSink: null,
69
72
  pendingRevalidatedTags: /* @__PURE__ */ new Set(),
70
73
  pendingRevalidations: /* @__PURE__ */ new Set(),
71
74
  requestScopedCacheLife: null,
@@ -146,15 +149,17 @@ async function _drainPendingRevalidations() {
146
149
  }
147
150
  if (didReject) throw firstRejection;
148
151
  }
152
+ function mergeCacheLife(current, config) {
153
+ if (current === null) return { ...config };
154
+ if (config.stale !== void 0) current.stale = current.stale !== void 0 ? Math.min(current.stale, config.stale) : config.stale;
155
+ if (config.revalidate !== void 0) current.revalidate = current.revalidate !== void 0 ? Math.min(current.revalidate, config.revalidate) : config.revalidate;
156
+ if (config.expire !== void 0) current.expire = current.expire !== void 0 ? Math.min(current.expire, config.expire) : config.expire;
157
+ return current;
158
+ }
149
159
  function _setRequestScopedCacheLife(config) {
150
160
  const state = getCacheState();
151
- if (state.requestScopedCacheLife === null) {
152
- state.requestScopedCacheLife = { ...config };
153
- return;
154
- }
155
- if (config.stale !== void 0) state.requestScopedCacheLife.stale = state.requestScopedCacheLife.stale !== void 0 ? Math.min(state.requestScopedCacheLife.stale, config.stale) : config.stale;
156
- if (config.revalidate !== void 0) state.requestScopedCacheLife.revalidate = state.requestScopedCacheLife.revalidate !== void 0 ? Math.min(state.requestScopedCacheLife.revalidate, config.revalidate) : config.revalidate;
157
- if (config.expire !== void 0) state.requestScopedCacheLife.expire = state.requestScopedCacheLife.expire !== void 0 ? Math.min(state.requestScopedCacheLife.expire, config.expire) : config.expire;
161
+ state.requestScopedCacheLife = mergeCacheLife(state.requestScopedCacheLife, config);
162
+ if (state.cacheLifeSink) state.cacheLifeSink.cacheLife = mergeCacheLife(state.cacheLifeSink.cacheLife, config);
158
163
  }
159
164
  function _peekRequestScopedCacheLife() {
160
165
  const config = getCacheState().requestScopedCacheLife;
@@ -193,8 +198,26 @@ function recordUnstableCacheObservation(observation) {
193
198
  function _peekUnstableCacheObservations() {
194
199
  return [...getCacheState().unstableCacheObservations.values()].sort((a, b) => a.keyHash.localeCompare(b.keyHash));
195
200
  }
201
+ /**
202
+ * Run `fn` with the request's cache settings, including its revalidation mode,
203
+ * but its own record of cache lives and unstable_cache observations, so work
204
+ * done on the request's behalf after the fact leaves the request untouched.
205
+ */
206
+ function runWithDetachedCacheObservations(fn) {
207
+ if (isInsideUnifiedScope()) return runWithUnifiedStateMutation((context) => {
208
+ context.cacheLifeSink = null;
209
+ context.requestScopedCacheLife = null;
210
+ context.unstableCacheObservations = /* @__PURE__ */ new Map();
211
+ }, fn);
212
+ return cacheAls.run({
213
+ ...getCacheState(),
214
+ cacheLifeSink: null,
215
+ requestScopedCacheLife: null,
216
+ unstableCacheObservations: /* @__PURE__ */ new Map()
217
+ }, fn);
218
+ }
196
219
  function shouldServeStaleUnstableCacheEntry() {
197
220
  return getCacheState().unstableCacheRevalidation === "background";
198
221
  }
199
222
  //#endregion
200
- export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, getRegisteredCacheContext, markActionRevalidation, recordUnstableCacheObservation, shouldServeStaleUnstableCacheEntry };
223
+ export { ACTION_DID_REVALIDATE_DYNAMIC_ONLY, ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC, _captureRequestScopedCacheLifeAccessors, _consumeRequestScopedCacheLife, _drainPendingRevalidations, _hasPendingRevalidatedTag, _initRequestScopedCacheState, _markPendingRevalidatedTag, _peekRequestScopedCacheLife, _peekUnstableCacheObservations, _queuePendingRevalidation, _recordUseCacheRootParamRead, _registerCacheContextAccessor, _runWithCacheState, _setRequestScopedCacheLife, cacheLifeProfiles, getAndClearActionRevalidationKind, getRegisteredCacheContext, markActionRevalidation, recordUnstableCacheObservation, runWithDetachedCacheObservations, shouldServeStaleUnstableCacheEntry };
@@ -1,6 +1,7 @@
1
1
  import { CacheLifeConfig } from "./cache-request-state.js";
2
- import { markAppPagePropsForUseCache } from "./internal/app-page-props-cache-key.js";
2
+ import { CacheFlightArguments } from "./cache-flight-arguments.js";
3
3
  //#region src/shims/cache-runtime.d.ts
4
+ export declare function replayCachedFunction(fn: object, args: CacheFlightArguments): Promise<unknown>;
4
5
  /**
5
6
  * Used purely as `cause` for the nested-dynamic cache error: its captured stack
6
7
  * points at the inner "use cache" invocation that propagated a dynamic cache
@@ -52,15 +53,6 @@ export declare function getCacheContext(): CacheContext | null;
52
53
  * Exported for testing.
53
54
  */
54
55
  export declare function buildUseCacheKey(id: string, keySeed: string | undefined, argsKey?: string): string;
55
- /**
56
- * Convert an encodeReply result (string | FormData) to a cache key string.
57
- * For FormData (binary args), produces a deterministic SHA-256 hash over
58
- * the sorted entries. We can't hash `new Response(formData).arrayBuffer()`
59
- * because multipart boundaries are non-deterministic across serializations.
60
- *
61
- * Exported for testing.
62
- */
63
- export declare function replyToCacheKey(reply: string | FormData): Promise<string>;
64
56
  export type PrivateCacheState = {
65
57
  _privateCache: Map<string, unknown> | null;
66
58
  };
@@ -86,7 +78,7 @@ export type RegisterCachedFunctionOptions = {
86
78
  /** Number of declared arguments supplied by the directive transform. */
87
79
  argumentCount?: number;
88
80
  decryptCaptures?: (value: unknown) => Promise<unknown[] | undefined>;
89
- encodeInvocationArgs?: (args: unknown[]) => Promise<string>;
81
+ encodeInvocation?: (args: CacheFlightArguments) => Promise<string>;
90
82
  serverReferenceId?: string;
91
83
  };
92
84
  /**
@@ -99,13 +91,8 @@ export type RegisterCachedFunctionOptions = {
99
91
  * @returns A wrapper function that checks cache before calling the original
100
92
  */
101
93
  export declare function registerCachedFunction<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>, id: string, variant?: string, options?: RegisterCachedFunctionOptions): (...args: TArgs) => Promise<TResult>;
94
+ /** Memoize in one React render without skipping cache guards or scoped serialization. */
95
+ export declare function memoizeInCacheScope<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => TResult): (...args: TArgs) => TResult;
102
96
  /** Whether a loaded server reference is a transformed `"use cache"` function. */
103
97
  export declare function isUseCacheFunction(value: unknown): value is (...args: unknown[]) => Promise<unknown>;
104
- /**
105
- * A thenable (not an array) with own enumerable properties — the
106
- * `Object.assign(Promise.resolve(obj), obj)` pattern Next.js params use. The
107
- * cache key is built from its fields instead of the promise.
108
- */
109
- export declare function isThenableObject(value: object): value is PromiseLike<unknown>;
110
- //#endregion
111
- export { markAppPagePropsForUseCache };
98
+ //#endregion