@opennextjs/aws 4.1.4 → 4.1.5
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.
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
import { isStale, writeTags } from "../utils/cache";
|
|
2
2
|
import { fromReadableStream, toReadableStream } from "../utils/stream";
|
|
3
3
|
import { debug } from "./logger";
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* In-flight writes are tracked per request, not per module.
|
|
6
|
+
*
|
|
7
|
+
* A module-scope map is per isolate. On a runtime that serves one request per
|
|
8
|
+
* sandbox that is the same as per request, but on a runtime that serves many
|
|
9
|
+
* concurrent requests from one isolate (e.g. Cloudflare Workers) a pending
|
|
10
|
+
* write promise created in request A would be handed to request B. If A's
|
|
11
|
+
* context is torn down before the write settles, B awaits a promise that never
|
|
12
|
+
* resolves, `.finally()` never runs, and the key stays poisoned for the life of
|
|
13
|
+
* the isolate.
|
|
14
|
+
*
|
|
15
|
+
* `RequestCache` is the per-request store the tag caches already use for this.
|
|
16
|
+
* Scoping the map to it keeps the read-coherence a `get` needs for a write in
|
|
17
|
+
* flight from the same render while making a cross-request share impossible.
|
|
18
|
+
* With no ALS store (e.g. at build time) the dedupe is skipped and `get` falls
|
|
19
|
+
* through to the incremental cache.
|
|
20
|
+
*/
|
|
21
|
+
const PENDING_WRITES_CACHE_KEY = "composable-cache:pending-writes";
|
|
22
|
+
const getPendingWritePromiseMap = () => globalThis.__openNextAls
|
|
23
|
+
?.getStore()
|
|
24
|
+
?.requestCache?.getOrCreate(PENDING_WRITES_CACHE_KEY);
|
|
5
25
|
export default {
|
|
6
26
|
async get(cacheKey) {
|
|
7
27
|
try {
|
|
8
28
|
// We first check if we have a pending write for this cache key
|
|
9
29
|
// If we do, we return the pending promise instead of fetching the cache
|
|
10
|
-
|
|
30
|
+
const pendingWritePromiseMap = getPendingWritePromiseMap();
|
|
31
|
+
if (pendingWritePromiseMap?.has(cacheKey)) {
|
|
11
32
|
const stored = pendingWritePromiseMap.get(cacheKey);
|
|
12
33
|
if (stored) {
|
|
13
34
|
return stored.then((entry) => ({
|
|
@@ -69,9 +90,10 @@ export default {
|
|
|
69
90
|
...entry,
|
|
70
91
|
value: await fromReadableStream(entry.value),
|
|
71
92
|
}));
|
|
72
|
-
pendingWritePromiseMap
|
|
93
|
+
const pendingWritePromiseMap = getPendingWritePromiseMap();
|
|
94
|
+
pendingWritePromiseMap?.set(cacheKey, promiseEntry);
|
|
73
95
|
const entry = await promiseEntry.finally(() => {
|
|
74
|
-
pendingWritePromiseMap
|
|
96
|
+
pendingWritePromiseMap?.delete(cacheKey);
|
|
75
97
|
});
|
|
76
98
|
await globalThis.incrementalCache.set(cacheKey, {
|
|
77
99
|
...entry,
|
|
@@ -15,5 +15,10 @@ export function buildNextjsApp(options) {
|
|
|
15
15
|
cp.execSync(command, {
|
|
16
16
|
stdio: "inherit",
|
|
17
17
|
cwd: path.dirname(options.appPackageJsonPath),
|
|
18
|
+
// `setStandaloneBuildMode` mutates `process.env` right before this call.
|
|
19
|
+
// Node forwards those mutations to the child by default, but Bun resolves
|
|
20
|
+
// the child environment from a snapshot taken at startup unless `env` is
|
|
21
|
+
// passed explicitly, so `NEXT_PRIVATE_STANDALONE` never reaches the build.
|
|
22
|
+
env: process.env,
|
|
18
23
|
});
|
|
19
24
|
}
|
|
@@ -38,7 +38,9 @@ async function computeCacheControl(path, body, host, revalidate, lastModified, i
|
|
|
38
38
|
// calculate age
|
|
39
39
|
const age = Math.round((Date.now() - (lastModified ?? 0)) / 1000);
|
|
40
40
|
const hash = (str) => createHash("md5").update(str).digest("hex");
|
|
41
|
-
|
|
41
|
+
// RFC 9110 entity-tag is a quoted opaque-tag. CloudFront drops unquoted ETags
|
|
42
|
+
// when serving compressed objects.
|
|
43
|
+
const etag = `"${hash(body)}"`;
|
|
42
44
|
if (revalidate === 0) {
|
|
43
45
|
// This one should never happen
|
|
44
46
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManifest, } from "../../adapters/config/index.js";
|
|
2
|
+
import { parseSetCookieHeader } from "../../http/util.js";
|
|
2
3
|
import { ISR_HEADER, PRERENDER_REVALIDATE_HEADER } from "../../utils/cacheHeaders.js";
|
|
3
4
|
import { emptyReadableStream } from "../../utils/stream.js";
|
|
4
5
|
import { getQueryFromSearchParams } from "../../overrides/converters/utils.js";
|
|
@@ -112,7 +113,12 @@ export async function handleMiddleware(internalEvent, initialSearch, middlewareL
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
});
|
|
115
|
-
|
|
116
|
+
// Next folds the cookies set through `cookies()` in the middleware into a single
|
|
117
|
+
// comma-joined `set-cookie` header, so each entry here may be compound.
|
|
118
|
+
// Next itself splits them back in `runMiddleware` (`next-server.ts`).
|
|
119
|
+
const setCookies = responseHeaders
|
|
120
|
+
.getSetCookie()
|
|
121
|
+
.flatMap((maybeCompoundCookie) => parseSetCookieHeader(maybeCompoundCookie));
|
|
116
122
|
if (setCookies.length > 0) {
|
|
117
123
|
resHeaders["set-cookie"] = setCookies;
|
|
118
124
|
}
|