@opennextjs/aws 3.10.1 → 3.10.2
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.
- package/dist/adapters/config/index.js +5 -0
- package/dist/build/patch/astCodePatcher.d.ts +14 -4
- package/dist/build/patch/astCodePatcher.js +30 -19
- package/dist/core/routing/cacheInterceptor.js +20 -12
- package/dist/overrides/cdnInvalidation/cloudfront.js +1 -1
- package/dist/overrides/incrementalCache/fs-dev.js +1 -1
- package/dist/overrides/incrementalCache/multi-tier-ddb-s3.js +2 -2
- package/dist/overrides/incrementalCache/s3-lite.js +2 -2
- package/dist/overrides/incrementalCache/s3.js +2 -2
- package/dist/overrides/tagCache/dynamodb-lite.js +6 -6
- package/dist/overrides/tagCache/dynamodb-nextMode.js +8 -3
- package/dist/overrides/tagCache/dynamodb.js +4 -4
- package/dist/overrides/tagCache/fs-dev-nextMode.js +5 -1
- package/dist/overrides/tagCache/fs-dev.js +9 -5
- package/dist/plugins/edge.js +1 -0
- package/package.json +1 -1
|
@@ -22,5 +22,10 @@ export const AppPathRoutesManifest =
|
|
|
22
22
|
/* @__PURE__ */ loadAppPathRoutesManifest(NEXT_DIR);
|
|
23
23
|
export const FunctionsConfigManifest =
|
|
24
24
|
/* @__PURE__ */ loadFunctionsConfigManifest(NEXT_DIR);
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated Do not use NEXT_BUILD_ID above Next 16.2, as it could be a fixed value if used in conjonction with deploymentId
|
|
27
|
+
* We keep it for backward compatibility
|
|
28
|
+
*/
|
|
25
29
|
process.env.NEXT_BUILD_ID = BuildId;
|
|
30
|
+
process.env.OPEN_NEXT_BUILD_ID = NextConfig.deploymentId ?? BuildId;
|
|
26
31
|
process.env.NEXT_PREVIEW_MODE_ID = PrerenderManifest?.preview?.previewModeId;
|
|
@@ -11,9 +11,11 @@ export type RuleConfig = NapiConfig & {
|
|
|
11
11
|
/**
|
|
12
12
|
* Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Notes:
|
|
15
|
+
* - The rule must not contain a `transform` field
|
|
16
|
+
* - The rule may contain a `fix` field, which is used to generate the edits.
|
|
17
|
+
* If not provided, no edits will be generated and the matches will be returned as is.
|
|
18
|
+
* - You can use https://ast-grep.github.io/playground.html to create and verify rules.
|
|
17
19
|
*
|
|
18
20
|
* @param rule The rule. Either a yaml string or an instance of `RuleConfig`
|
|
19
21
|
* @param root The root node
|
|
@@ -31,9 +33,17 @@ export declare function applyRule(rule: string | RuleConfig, root: SgNode, { onc
|
|
|
31
33
|
*
|
|
32
34
|
* @param path The file path
|
|
33
35
|
* @param lang The language to parse. Defaults to TypeScript.
|
|
34
|
-
* @returns The root for the file.
|
|
36
|
+
* @returns The root node for the file.
|
|
35
37
|
*/
|
|
36
38
|
export declare function parseFile(path: string, lang?: Lang): SgNode;
|
|
39
|
+
/**
|
|
40
|
+
* Parse a code string and obtain its root.
|
|
41
|
+
*
|
|
42
|
+
* @param code The code string
|
|
43
|
+
* @param lang The language to parse. Defaults to TypeScript.
|
|
44
|
+
* @returns The root node for the code string.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseCode(code: string, lang?: Lang): SgNode;
|
|
37
47
|
/**
|
|
38
48
|
* Patches the code from by applying the rule.
|
|
39
49
|
*
|
|
@@ -5,9 +5,11 @@ import yaml from "yaml";
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Notes:
|
|
9
|
+
* - The rule must not contain a `transform` field
|
|
10
|
+
* - The rule may contain a `fix` field, which is used to generate the edits.
|
|
11
|
+
* If not provided, no edits will be generated and the matches will be returned as is.
|
|
12
|
+
* - You can use https://ast-grep.github.io/playground.html to create and verify rules.
|
|
11
13
|
*
|
|
12
14
|
* @param rule The rule. Either a yaml string or an instance of `RuleConfig`
|
|
13
15
|
* @param root The root node
|
|
@@ -19,24 +21,23 @@ export function applyRule(rule, root, { once = false } = {}) {
|
|
|
19
21
|
if (ruleConfig.transform) {
|
|
20
22
|
throw new Error("transform is not supported");
|
|
21
23
|
}
|
|
22
|
-
if (!ruleConfig.fix) {
|
|
23
|
-
throw new Error("no fix to apply");
|
|
24
|
-
}
|
|
25
|
-
const fix = ruleConfig.fix;
|
|
26
24
|
const matches = once
|
|
27
25
|
? [root.find(ruleConfig)].filter((m) => m !== null)
|
|
28
26
|
: root.findAll(ruleConfig);
|
|
29
27
|
const edits = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
const fix = ruleConfig.fix;
|
|
29
|
+
if (fix !== undefined) {
|
|
30
|
+
matches.forEach((match) => {
|
|
31
|
+
edits.push(match.replace(
|
|
32
|
+
// Replace known placeholders by their value
|
|
33
|
+
fix
|
|
34
|
+
.replace(/\$\$\$([A-Z0-9_]+)/g, (_m, name) => match
|
|
35
|
+
.getMultipleMatches(name)
|
|
36
|
+
.map((n) => n.text())
|
|
37
|
+
.join(""))
|
|
38
|
+
.replace(/\$([A-Z0-9_]+)/g, (m, name) => match.getMatch(name)?.text() ?? m)));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
40
41
|
return { edits, matches };
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
@@ -44,10 +45,20 @@ export function applyRule(rule, root, { once = false } = {}) {
|
|
|
44
45
|
*
|
|
45
46
|
* @param path The file path
|
|
46
47
|
* @param lang The language to parse. Defaults to TypeScript.
|
|
47
|
-
* @returns The root for the file.
|
|
48
|
+
* @returns The root node for the file.
|
|
48
49
|
*/
|
|
49
50
|
export function parseFile(path, lang = Lang.TypeScript) {
|
|
50
|
-
return
|
|
51
|
+
return parseCode(readFileSync(path, { encoding: "utf-8" }), lang);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parse a code string and obtain its root.
|
|
55
|
+
*
|
|
56
|
+
* @param code The code string
|
|
57
|
+
* @param lang The language to parse. Defaults to TypeScript.
|
|
58
|
+
* @returns The root node for the code string.
|
|
59
|
+
*/
|
|
60
|
+
export function parseCode(code, lang = Lang.TypeScript) {
|
|
61
|
+
return parse(lang, code).root();
|
|
51
62
|
}
|
|
52
63
|
/**
|
|
53
64
|
* Patches the code from by applying the rule.
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { NextConfig, PrerenderManifest } from "../../adapters/config/index";
|
|
3
3
|
import { emptyReadableStream, toReadableStream } from "../../utils/stream";
|
|
4
4
|
import { isBinaryContentType } from "../../utils/binary";
|
|
5
|
-
import { getTagsFromValue, hasBeenRevalidated } from "../../utils/cache";
|
|
5
|
+
import { getTagsFromValue, hasBeenRevalidated, isStale } from "../../utils/cache";
|
|
6
6
|
import { debug, error } from "../../adapters/logger";
|
|
7
7
|
import { localizePath } from "./i18n";
|
|
8
8
|
import { generateMessageGroupId } from "./queue";
|
|
@@ -21,7 +21,7 @@ const VARY_HEADER = "RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Rou
|
|
|
21
21
|
const NEXT_SEGMENT_PREFETCH_HEADER = "next-router-segment-prefetch";
|
|
22
22
|
const NEXT_PRERENDER_HEADER = "x-nextjs-prerender";
|
|
23
23
|
const NEXT_POSTPONED_HEADER = "x-nextjs-postponed";
|
|
24
|
-
async function computeCacheControl(path, body, host, revalidate, lastModified) {
|
|
24
|
+
async function computeCacheControl(path, body, host, revalidate, lastModified, isStaleFromTagCache = false) {
|
|
25
25
|
let finalRevalidate = CACHE_ONE_YEAR;
|
|
26
26
|
const existingRoute = Object.entries(PrerenderManifest?.routes ?? {}).find((p) => p[0] === path)?.[1];
|
|
27
27
|
if (revalidate === undefined && existingRoute) {
|
|
@@ -46,19 +46,23 @@ async function computeCacheControl(path, body, host, revalidate, lastModified) {
|
|
|
46
46
|
etag,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
// SSG uses one year cache
|
|
50
|
+
const isSSG = finalRevalidate === CACHE_ONE_YEAR;
|
|
51
|
+
const remainingTtl = Math.max(finalRevalidate - age, 1);
|
|
52
|
+
const isStaleFromTime = !isSSG && remainingTtl === 1;
|
|
53
|
+
const isStale = isStaleFromTime || isStaleFromTagCache;
|
|
54
|
+
if (!isSSG || isStaleFromTagCache) {
|
|
55
|
+
const sMaxAge = isStaleFromTagCache ? 1 : remainingTtl;
|
|
51
56
|
debug("sMaxAge", {
|
|
52
57
|
finalRevalidate,
|
|
53
58
|
age,
|
|
54
59
|
lastModified,
|
|
55
60
|
revalidate,
|
|
61
|
+
isStaleFromTagCache,
|
|
56
62
|
});
|
|
57
|
-
const isStale = sMaxAge === 1;
|
|
58
63
|
if (isStale) {
|
|
59
64
|
let url = NextConfig.trailingSlash ? `${path}/` : path;
|
|
60
65
|
if (NextConfig.basePath) {
|
|
61
|
-
// We need to add the basePath to the url
|
|
62
66
|
url = `${NextConfig.basePath}${url}`;
|
|
63
67
|
}
|
|
64
68
|
await globalThis.queue.send({
|
|
@@ -108,7 +112,7 @@ function getBodyForAppRouter(event, cachedValue) {
|
|
|
108
112
|
return { body: cachedValue.rsc, additionalHeaders: {} };
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
|
-
async function generateResult(event, localizedPath, cachedValue, lastModified) {
|
|
115
|
+
async function generateResult(event, localizedPath, cachedValue, lastModified, isStaleFromTagCache = false) {
|
|
112
116
|
debug("Returning result from experimental cache");
|
|
113
117
|
let body = "";
|
|
114
118
|
let type = "application/octet-stream";
|
|
@@ -134,7 +138,7 @@ async function generateResult(event, localizedPath, cachedValue, lastModified) {
|
|
|
134
138
|
else {
|
|
135
139
|
throw new Error("generateResult called with unsupported cache value type, only 'app' and 'page' are supported");
|
|
136
140
|
}
|
|
137
|
-
const cacheControl = await computeCacheControl(localizedPath, body, event.headers.host, cachedValue.revalidate, lastModified);
|
|
141
|
+
const cacheControl = await computeCacheControl(localizedPath, body, event.headers.host, cachedValue.revalidate, lastModified, isStaleFromTagCache);
|
|
138
142
|
return {
|
|
139
143
|
type: "core",
|
|
140
144
|
// Sometimes other status codes can be cached, like 404. For these cases, we should return the correct status code
|
|
@@ -213,10 +217,10 @@ export async function cacheInterceptor(event) {
|
|
|
213
217
|
if (!cachedData?.value) {
|
|
214
218
|
return event;
|
|
215
219
|
}
|
|
220
|
+
const tags = getTagsFromValue(cachedData.value);
|
|
216
221
|
// We need to check the tag cache now
|
|
217
222
|
if (cachedData.value?.type === "app" ||
|
|
218
223
|
cachedData.value?.type === "route") {
|
|
219
|
-
const tags = getTagsFromValue(cachedData.value);
|
|
220
224
|
const _hasBeenRevalidated = cachedData.shouldBypassTagCache
|
|
221
225
|
? false
|
|
222
226
|
: await hasBeenRevalidated(localizedPath, tags, cachedData);
|
|
@@ -224,13 +228,17 @@ export async function cacheInterceptor(event) {
|
|
|
224
228
|
return event;
|
|
225
229
|
}
|
|
226
230
|
}
|
|
231
|
+
// Check if the cache entry is stale (valid but needs background revalidation)
|
|
232
|
+
const _isStale = cachedData.shouldBypassTagCache
|
|
233
|
+
? false
|
|
234
|
+
: await isStale(localizedPath, tags, cachedData.lastModified ?? Date.now());
|
|
227
235
|
const host = event.headers.host;
|
|
228
236
|
switch (cachedData?.value?.type) {
|
|
229
237
|
case "app":
|
|
230
238
|
case "page":
|
|
231
|
-
return generateResult(event, localizedPath, cachedData.value, cachedData.lastModified);
|
|
239
|
+
return generateResult(event, localizedPath, cachedData.value, cachedData.lastModified, _isStale);
|
|
232
240
|
case "redirect": {
|
|
233
|
-
const cacheControl = await computeCacheControl(localizedPath, "", host, cachedData.value.revalidate, cachedData.lastModified);
|
|
241
|
+
const cacheControl = await computeCacheControl(localizedPath, "", host, cachedData.value.revalidate, cachedData.lastModified, _isStale);
|
|
234
242
|
return {
|
|
235
243
|
type: "core",
|
|
236
244
|
statusCode: cachedData.value.meta?.status ?? 307,
|
|
@@ -244,7 +252,7 @@ export async function cacheInterceptor(event) {
|
|
|
244
252
|
};
|
|
245
253
|
}
|
|
246
254
|
case "route": {
|
|
247
|
-
const cacheControl = await computeCacheControl(localizedPath, cachedData.value.body, host, cachedData.value.revalidate, cachedData.lastModified);
|
|
255
|
+
const cacheControl = await computeCacheControl(localizedPath, cachedData.value.body, host, cachedData.value.revalidate, cachedData.lastModified, _isStale);
|
|
248
256
|
const isBinary = isBinaryContentType(String(cachedData.value.meta?.headers?.["content-type"]));
|
|
249
257
|
return {
|
|
250
258
|
type: "core",
|
|
@@ -11,7 +11,7 @@ export default {
|
|
|
11
11
|
? [`/${path}`, `/${path}?_rsc=*`]
|
|
12
12
|
: [
|
|
13
13
|
`/${path}`,
|
|
14
|
-
`/_next/data/${process.env.
|
|
14
|
+
`/_next/data/${process.env.OPEN_NEXT_BUILD_ID}${path === "/" ? "/index" : `/${path}`}.json*`,
|
|
15
15
|
];
|
|
16
16
|
});
|
|
17
17
|
await cloudfront.send(new CreateInvalidationCommand({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getMonorepoRelativePath } from "../../utils/normalize-path";
|
|
4
|
-
const buildId = process.env.
|
|
4
|
+
const buildId = process.env.OPEN_NEXT_BUILD_ID;
|
|
5
5
|
const basePath = path.join(getMonorepoRelativePath(), `cache/${buildId}`);
|
|
6
6
|
const getCacheKey = (key) => {
|
|
7
7
|
return path.join(basePath, `${key}.cache`);
|
|
@@ -24,8 +24,8 @@ const awsFetch = (body, type = "get") => {
|
|
|
24
24
|
});
|
|
25
25
|
};
|
|
26
26
|
const buildDynamoKey = (key) => {
|
|
27
|
-
const {
|
|
28
|
-
return `__meta_${
|
|
27
|
+
const { OPEN_NEXT_BUILD_ID } = process.env;
|
|
28
|
+
return `__meta_${OPEN_NEXT_BUILD_ID}_${key}`;
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
31
|
* This cache implementation uses a multi-tier cache with a local cache, a DynamoDB metadata cache and an S3 cache.
|
|
@@ -26,8 +26,8 @@ const awsFetch = async (key, options) => {
|
|
|
26
26
|
return customFetchClient(client)(url, options);
|
|
27
27
|
};
|
|
28
28
|
function buildS3Key(key, extension) {
|
|
29
|
-
const { CACHE_BUCKET_KEY_PREFIX,
|
|
30
|
-
return path.posix.join(CACHE_BUCKET_KEY_PREFIX ?? "", extension === "fetch" ? "__fetch" : "",
|
|
29
|
+
const { CACHE_BUCKET_KEY_PREFIX, OPEN_NEXT_BUILD_ID } = process.env;
|
|
30
|
+
return path.posix.join(CACHE_BUCKET_KEY_PREFIX ?? "", extension === "fetch" ? "__fetch" : "", OPEN_NEXT_BUILD_ID ?? "", extension === "fetch" ? key : `${key}.${extension}`);
|
|
31
31
|
}
|
|
32
32
|
const incrementalCache = {
|
|
33
33
|
async get(key, cacheType) {
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
|
|
3
3
|
import { awsLogger } from "../../adapters/logger";
|
|
4
4
|
import { parseNumberFromEnv } from "../../adapters/util";
|
|
5
|
-
const { CACHE_BUCKET_REGION, CACHE_BUCKET_KEY_PREFIX,
|
|
5
|
+
const { CACHE_BUCKET_REGION, CACHE_BUCKET_KEY_PREFIX, OPEN_NEXT_BUILD_ID, CACHE_BUCKET_NAME, } = process.env;
|
|
6
6
|
function parseS3ClientConfigFromEnv() {
|
|
7
7
|
return {
|
|
8
8
|
region: CACHE_BUCKET_REGION,
|
|
@@ -12,7 +12,7 @@ function parseS3ClientConfigFromEnv() {
|
|
|
12
12
|
}
|
|
13
13
|
const s3Client = new S3Client(parseS3ClientConfigFromEnv());
|
|
14
14
|
function buildS3Key(key, extension) {
|
|
15
|
-
return path.posix.join(CACHE_BUCKET_KEY_PREFIX ?? "", extension === "fetch" ? "__fetch" : "",
|
|
15
|
+
return path.posix.join(CACHE_BUCKET_KEY_PREFIX ?? "", extension === "fetch" ? "__fetch" : "", OPEN_NEXT_BUILD_ID ?? "", extension === "fetch" ? key : `${key}.${extension}`);
|
|
16
16
|
}
|
|
17
17
|
const incrementalCache = {
|
|
18
18
|
async get(key, cacheType) {
|
|
@@ -34,10 +34,10 @@ const awsFetch = (body, type = "query") => {
|
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
36
|
function buildDynamoKey(key) {
|
|
37
|
-
const {
|
|
37
|
+
const { OPEN_NEXT_BUILD_ID } = process.env;
|
|
38
38
|
// FIXME: We should probably use something else than path.join here
|
|
39
39
|
// this could transform some fetch cache key into a valid path
|
|
40
|
-
return path.posix.join(
|
|
40
|
+
return path.posix.join(OPEN_NEXT_BUILD_ID ?? "", key);
|
|
41
41
|
}
|
|
42
42
|
function buildDynamoObject(path, tags, revalidatedAt, stale, expire) {
|
|
43
43
|
const obj = {
|
|
@@ -56,7 +56,7 @@ const tagCache = {
|
|
|
56
56
|
if (globalThis.openNextConfig.dangerous?.disableTagCache) {
|
|
57
57
|
return [];
|
|
58
58
|
}
|
|
59
|
-
const { CACHE_DYNAMO_TABLE,
|
|
59
|
+
const { CACHE_DYNAMO_TABLE, OPEN_NEXT_BUILD_ID } = process.env;
|
|
60
60
|
const store = globalThis.__openNextAls.getStore();
|
|
61
61
|
const cache = store?.requestCache.getOrCreate("dynamoDb:getByPath");
|
|
62
62
|
if (cache?.has(path)) {
|
|
@@ -80,7 +80,7 @@ const tagCache = {
|
|
|
80
80
|
const tags = Items?.map((item) => item.tag.S ?? "") ?? [];
|
|
81
81
|
debug("tags for path", path, tags);
|
|
82
82
|
// We need to remove the buildId from the path
|
|
83
|
-
const resultTags = tags.map((tag) => tag.replace(`${
|
|
83
|
+
const resultTags = tags.map((tag) => tag.replace(`${OPEN_NEXT_BUILD_ID}/`, ""));
|
|
84
84
|
cache?.set(path, resultTags);
|
|
85
85
|
return resultTags;
|
|
86
86
|
}
|
|
@@ -94,7 +94,7 @@ const tagCache = {
|
|
|
94
94
|
if (globalThis.openNextConfig.dangerous?.disableTagCache) {
|
|
95
95
|
return [];
|
|
96
96
|
}
|
|
97
|
-
const { CACHE_DYNAMO_TABLE,
|
|
97
|
+
const { CACHE_DYNAMO_TABLE, OPEN_NEXT_BUILD_ID } = process.env;
|
|
98
98
|
const store = globalThis.__openNextAls.getStore();
|
|
99
99
|
const cache = store?.requestCache.getOrCreate("dynamoDb:getByTag");
|
|
100
100
|
if (cache?.has(tag)) {
|
|
@@ -115,7 +115,7 @@ const tagCache = {
|
|
|
115
115
|
}
|
|
116
116
|
const { Items } = (await result.json());
|
|
117
117
|
// We need to remove the buildId from the path
|
|
118
|
-
const paths = Items?.map(({ path: { S: key } }) => key?.replace(`${
|
|
118
|
+
const paths = Items?.map(({ path: { S: key } }) => key?.replace(`${OPEN_NEXT_BUILD_ID}/`, "") ?? "") ?? [];
|
|
119
119
|
cache?.set(tag, paths);
|
|
120
120
|
return paths;
|
|
121
121
|
}
|
|
@@ -33,10 +33,10 @@ const awsFetch = (body, type = "query") => {
|
|
|
33
33
|
});
|
|
34
34
|
};
|
|
35
35
|
function buildDynamoKey(key) {
|
|
36
|
-
const {
|
|
36
|
+
const { OPEN_NEXT_BUILD_ID } = process.env;
|
|
37
37
|
// FIXME: We should probably use something else than path.join here
|
|
38
38
|
// this could transform some fetch cache key into a valid path
|
|
39
|
-
return path.posix.join(
|
|
39
|
+
return path.posix.join(OPEN_NEXT_BUILD_ID ?? "", "_tag", key);
|
|
40
40
|
}
|
|
41
41
|
// We use the same key for both path and tag
|
|
42
42
|
// That's mostly for compatibility reason so that it's easier to use this with existing infra
|
|
@@ -157,7 +157,12 @@ export default {
|
|
|
157
157
|
const compute = (item) => {
|
|
158
158
|
if (!item?.stale?.N)
|
|
159
159
|
return false;
|
|
160
|
-
|
|
160
|
+
const revalidatedAt = Number.parseInt(item.revalidatedAt?.N ?? "0");
|
|
161
|
+
// A tag is stale when both its stale timestamp and its revalidatedAt are newer than the page.
|
|
162
|
+
// revalidatedAt > lastModified ensures the revalidation that set this stale window happened
|
|
163
|
+
// after the page was generated, preventing a stale signal from a previous ISR cycle.
|
|
164
|
+
return (revalidatedAt > (lastModified ?? 0) &&
|
|
165
|
+
Number.parseInt(item.stale.N) >= (lastModified ?? 0));
|
|
161
166
|
};
|
|
162
167
|
const { uncachedTags, hasMatch } = checkItemsCache(tags, itemsCache, compute);
|
|
163
168
|
if (hasMatch)
|
|
@@ -3,7 +3,7 @@ import { BatchWriteItemCommand, DynamoDBClient, QueryCommand, } from "@aws-sdk/c
|
|
|
3
3
|
import { awsLogger, debug, error } from "../../adapters/logger";
|
|
4
4
|
import { chunk, parseNumberFromEnv } from "../../adapters/util";
|
|
5
5
|
import { MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT, getDynamoBatchWriteCommandConcurrency, } from "./constants";
|
|
6
|
-
const { CACHE_BUCKET_REGION, CACHE_DYNAMO_TABLE,
|
|
6
|
+
const { CACHE_BUCKET_REGION, CACHE_DYNAMO_TABLE, OPEN_NEXT_BUILD_ID } = process.env;
|
|
7
7
|
function parseDynamoClientConfigFromEnv() {
|
|
8
8
|
return {
|
|
9
9
|
region: CACHE_BUCKET_REGION,
|
|
@@ -15,7 +15,7 @@ const dynamoClient = new DynamoDBClient(parseDynamoClientConfigFromEnv());
|
|
|
15
15
|
function buildDynamoKey(key) {
|
|
16
16
|
// FIXME: We should probably use something else than path.join here
|
|
17
17
|
// this could transform some fetch cache key into a valid path
|
|
18
|
-
return path.posix.join(
|
|
18
|
+
return path.posix.join(OPEN_NEXT_BUILD_ID ?? "", key);
|
|
19
19
|
}
|
|
20
20
|
function buildDynamoObject(path, tags, revalidatedAt, stale, expire) {
|
|
21
21
|
const obj = {
|
|
@@ -53,7 +53,7 @@ const tagCache = {
|
|
|
53
53
|
const tags = result.Items?.map((item) => item.tag.S ?? "") ?? [];
|
|
54
54
|
debug("tags for path", path, tags);
|
|
55
55
|
// We need to remove the buildId from the path
|
|
56
|
-
const resultTags = tags.map((tag) => tag.replace(`${
|
|
56
|
+
const resultTags = tags.map((tag) => tag.replace(`${OPEN_NEXT_BUILD_ID}/`, ""));
|
|
57
57
|
cache?.set(path, resultTags);
|
|
58
58
|
return resultTags;
|
|
59
59
|
}
|
|
@@ -83,7 +83,7 @@ const tagCache = {
|
|
|
83
83
|
},
|
|
84
84
|
}));
|
|
85
85
|
// We need to remove the buildId from the path
|
|
86
|
-
const paths = Items?.map(({ path: { S: key } }) => key?.replace(`${
|
|
86
|
+
const paths = Items?.map(({ path: { S: key } }) => key?.replace(`${OPEN_NEXT_BUILD_ID}/`, "") ?? "") ?? [];
|
|
87
87
|
cache?.set(tag, paths);
|
|
88
88
|
return paths;
|
|
89
89
|
}
|
|
@@ -47,7 +47,11 @@ export default {
|
|
|
47
47
|
if (!tagData || typeof tagData.stale !== "number") {
|
|
48
48
|
return false;
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
// A tag is stale when both its stale timestamp and its revalidatedAt are newer than the page.
|
|
51
|
+
// revalidatedAt > lastModified ensures the revalidation that set this stale window happened
|
|
52
|
+
// after the page was generated, preventing a stale signal from a previous ISR cycle.
|
|
53
|
+
return (tagData.revalidatedAt > (lastModified ?? 0) &&
|
|
54
|
+
tagData.stale >= (lastModified ?? 0));
|
|
51
55
|
});
|
|
52
56
|
debug("isStale result:", hasStaleTag);
|
|
53
57
|
return hasStaleTag;
|
|
@@ -4,9 +4,9 @@ import { getMonorepoRelativePath } from "../../utils/normalize-path";
|
|
|
4
4
|
const tagFile = path.join(getMonorepoRelativePath(), "dynamodb-provider/dynamodb-cache.json");
|
|
5
5
|
const tagContent = fs.readFileSync(tagFile, "utf-8");
|
|
6
6
|
let tags = JSON.parse(tagContent);
|
|
7
|
-
const {
|
|
7
|
+
const { OPEN_NEXT_BUILD_ID } = process.env;
|
|
8
8
|
function buildKey(key) {
|
|
9
|
-
return path.posix.join(
|
|
9
|
+
return path.posix.join(OPEN_NEXT_BUILD_ID ?? "", key);
|
|
10
10
|
}
|
|
11
11
|
const tagCache = {
|
|
12
12
|
name: "fs-dev",
|
|
@@ -14,12 +14,12 @@ const tagCache = {
|
|
|
14
14
|
getByPath: async (path) => {
|
|
15
15
|
return tags
|
|
16
16
|
.filter((tagPathMapping) => tagPathMapping.path.S === buildKey(path))
|
|
17
|
-
.map((tag) => tag.tag.S.replace(`${
|
|
17
|
+
.map((tag) => tag.tag.S.replace(`${OPEN_NEXT_BUILD_ID}/`, ""));
|
|
18
18
|
},
|
|
19
19
|
getByTag: async (tag) => {
|
|
20
20
|
return tags
|
|
21
21
|
.filter((tagPathMapping) => tagPathMapping.tag.S === buildKey(tag))
|
|
22
|
-
.map((tagEntry) => tagEntry.path.S.replace(`${
|
|
22
|
+
.map((tagEntry) => tagEntry.path.S.replace(`${OPEN_NEXT_BUILD_ID}/`, ""));
|
|
23
23
|
},
|
|
24
24
|
getLastModified: async (path, lastModified) => {
|
|
25
25
|
// Check if any tag has expired
|
|
@@ -46,7 +46,11 @@ const tagCache = {
|
|
|
46
46
|
return false;
|
|
47
47
|
if (!entry.stale?.N)
|
|
48
48
|
return false;
|
|
49
|
-
|
|
49
|
+
// A tag is stale when both its stale timestamp and its revalidatedAt are newer than the page.
|
|
50
|
+
// revalidatedAt > lastModified ensures the revalidation that set this stale window happened
|
|
51
|
+
// after the page was generated, preventing a stale signal from a previous ISR cycle.
|
|
52
|
+
return (Number.parseInt(entry.revalidatedAt.N) > (lastModified ?? 0) &&
|
|
53
|
+
Number.parseInt(entry.stale.N) > (lastModified ?? 0));
|
|
50
54
|
});
|
|
51
55
|
},
|
|
52
56
|
writeTags: async (newTags) => {
|
package/dist/plugins/edge.js
CHANGED
|
@@ -145,6 +145,7 @@ ${contents}
|
|
|
145
145
|
export const PagesManifest = ${JSON.stringify(PagesManifest)};
|
|
146
146
|
|
|
147
147
|
process.env.NEXT_BUILD_ID = BuildId;
|
|
148
|
+
process.env.OPEN_NEXT_BUILD_ID = NextConfig.deploymentId ?? BuildId;
|
|
148
149
|
process.env.NEXT_PREVIEW_MODE_ID = PrerenderManifest?.preview?.previewModeId;
|
|
149
150
|
`;
|
|
150
151
|
return { contents };
|