cdk-nextjs 0.7.5 → 0.7.7
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/.jsii +38 -38
- package/.jsii.tabl.json +1 -1
- package/README.md +12 -3
- package/assets/lambdas/post-deploy/post-deploy.lambda/index.js +31 -13
- package/docs/adapter-runtime.md +1 -1
- package/docs/caching-guide.md +11 -1
- package/docs/harness-coverage.md +11 -9
- package/lib/adapter/adapter.mjs +220 -149
- package/lib/adapter/aws-cache-store.d.ts +242 -7
- package/lib/adapter/aws-cache-store.js +520 -54
- package/lib/adapter/aws-cache-store.test.js +710 -3
- package/lib/adapter/build-outputs.js +23 -1
- package/lib/adapter/build-outputs.test.js +18 -1
- package/lib/adapter/cache-handler.d.ts +4 -3
- package/lib/adapter/cache-handler.js +1 -1
- package/lib/adapter/cache-handler.mjs +552 -91
- package/lib/adapter/cache-handler.test.js +31 -1
- package/lib/adapter/cache-utils.d.ts +68 -2
- package/lib/adapter/cache-utils.js +108 -2
- package/lib/adapter/cache-utils.test.js +45 -1
- package/lib/adapter/init-cache.d.ts +1 -1
- package/lib/adapter/init-cache.js +60 -37
- package/lib/adapter/init-cache.test.js +55 -2
- package/lib/adapter/local-file-cache-handler.d.ts +2 -2
- package/lib/adapter/local-file-cache-handler.js +4 -5
- package/lib/adapter/s3-cache-handler.d.ts +23 -7
- package/lib/adapter/s3-cache-handler.js +63 -27
- package/lib/adapter/s3-cache-handler.test.js +168 -1
- package/lib/adapter/use-cache-common.d.ts +53 -1
- package/lib/adapter/use-cache-common.js +121 -14
- package/lib/adapter/use-cache-common.test.js +138 -1
- package/lib/adapter/use-cache-default-handler.js +36 -6
- package/lib/adapter/use-cache-default-handler.mjs +557 -51
- package/lib/adapter/use-cache-handlers.test.js +247 -7
- package/lib/adapter/use-cache-remote-handler.js +10 -9
- package/lib/adapter/use-cache-remote-handler.mjs +597 -101
- package/lib/lambdas/post-deploy/prune-revalidation-table.d.ts +2 -1
- package/lib/lambdas/post-deploy/prune-revalidation-table.js +35 -13
- package/lib/lambdas/post-deploy/prune-revalidation-table.test.js +19 -1
- package/lib/nextjs-api.js +1 -1
- package/lib/nextjs-build/nextjs-build.d.ts +0 -7
- package/lib/nextjs-build/nextjs-build.js +4 -11
- package/lib/nextjs-build/nextjs-build.test.js +4 -3
- package/lib/nextjs-cache.js +1 -1
- package/lib/nextjs-compute/nextjs-containers.js +1 -1
- package/lib/nextjs-compute/nextjs-functions.js +1 -1
- package/lib/nextjs-distribution.js +1 -1
- package/lib/nextjs-post-deploy.js +1 -1
- package/lib/nextjs-static-assets.js +1 -1
- package/lib/root-constructs/nextjs-base-construct.js +1 -1
- package/lib/root-constructs/nextjs-global-containers.js +1 -1
- package/lib/root-constructs/nextjs-global-functions.js +1 -1
- package/lib/root-constructs/nextjs-regional-containers.js +1 -1
- package/lib/root-constructs/nextjs-regional-functions.js +1 -1
- package/lib/runtime/core.js +18 -4
- package/lib/runtime/core.test.js +17 -1
- package/lib/runtime/image.d.ts +19 -0
- package/lib/runtime/image.js +28 -2
- package/lib/runtime/image.test.js +43 -1
- package/lib/runtime/lambda.mjs +30 -6
- package/lib/runtime/server.mjs +30 -6
- package/lib/runtime/tag-manifest.d.ts +15 -0
- package/lib/runtime/tag-manifest.js +24 -0
- package/lib/runtime/tag-manifest.test.d.ts +1 -0
- package/lib/runtime/tag-manifest.test.js +21 -0
- package/lib/utils/deployment-build-id.d.ts +11 -0
- package/lib/utils/deployment-build-id.js +15 -0
- package/package.json +3 -3
- package/scripts/e2e-harness/README.md +1 -1
|
@@ -4704,14 +4704,14 @@ var init_getProfileName = __esm({
|
|
|
4704
4704
|
});
|
|
4705
4705
|
|
|
4706
4706
|
// node_modules/.pnpm/@smithy+core@3.35.0/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.js
|
|
4707
|
-
import { createHash } from "node:crypto";
|
|
4707
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
4708
4708
|
import { join as join2 } from "node:path";
|
|
4709
4709
|
var getSSOTokenFilepath;
|
|
4710
4710
|
var init_getSSOTokenFilepath = __esm({
|
|
4711
4711
|
"node_modules/.pnpm/@smithy+core@3.35.0/node_modules/@smithy/core/dist-es/submodules/config/shared-ini-file-loader/getSSOTokenFilepath.js"() {
|
|
4712
4712
|
init_getHomeDir();
|
|
4713
4713
|
getSSOTokenFilepath = (id) => {
|
|
4714
|
-
const hasher =
|
|
4714
|
+
const hasher = createHash2("sha1");
|
|
4715
4715
|
const cacheName = hasher.update(id).digest("hex");
|
|
4716
4716
|
return join2(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
|
|
4717
4717
|
};
|
|
@@ -6908,7 +6908,7 @@ var init_serdePlugin = __esm({
|
|
|
6908
6908
|
});
|
|
6909
6909
|
|
|
6910
6910
|
// node_modules/.pnpm/@smithy+core@3.35.0/node_modules/@smithy/core/dist-es/submodules/serde/hash-node/hash-node.js
|
|
6911
|
-
import { createHash as
|
|
6911
|
+
import { createHash as createHash3, createHmac } from "node:crypto";
|
|
6912
6912
|
function castSourceData(toCast, encoding) {
|
|
6913
6913
|
if (Buffer.isBuffer(toCast)) {
|
|
6914
6914
|
return toCast;
|
|
@@ -6942,7 +6942,7 @@ var init_hash_node = __esm({
|
|
|
6942
6942
|
return Promise.resolve(this.hash.digest());
|
|
6943
6943
|
}
|
|
6944
6944
|
reset() {
|
|
6945
|
-
this.hash = this.secret ? createHmac(this.algorithmIdentifier, castSourceData(this.secret)) :
|
|
6945
|
+
this.hash = this.secret ? createHmac(this.algorithmIdentifier, castSourceData(this.secret)) : createHash3(this.algorithmIdentifier);
|
|
6946
6946
|
}
|
|
6947
6947
|
};
|
|
6948
6948
|
}
|
|
@@ -8161,11 +8161,11 @@ var init_Md5Js = __esm({
|
|
|
8161
8161
|
});
|
|
8162
8162
|
|
|
8163
8163
|
// node_modules/.pnpm/@smithy+core@3.35.0/node_modules/@smithy/core/dist-es/submodules/checksum/md5/Md5Node.js
|
|
8164
|
-
import { createHash as
|
|
8164
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
8165
8165
|
function buildNativeClass() {
|
|
8166
8166
|
return class Md5Node {
|
|
8167
8167
|
digestLength = 16;
|
|
8168
|
-
hash =
|
|
8168
|
+
hash = createHash4("md5");
|
|
8169
8169
|
update(data) {
|
|
8170
8170
|
this.hash.update(toUint8Array(data));
|
|
8171
8171
|
}
|
|
@@ -8174,7 +8174,7 @@ function buildNativeClass() {
|
|
|
8174
8174
|
return new Uint8Array(buf2.buffer, buf2.byteOffset, buf2.byteLength);
|
|
8175
8175
|
}
|
|
8176
8176
|
reset() {
|
|
8177
|
-
this.hash =
|
|
8177
|
+
this.hash = createHash4("md5");
|
|
8178
8178
|
}
|
|
8179
8179
|
};
|
|
8180
8180
|
}
|
|
@@ -8185,7 +8185,7 @@ var init_Md5Node = __esm({
|
|
|
8185
8185
|
init_Md5Js();
|
|
8186
8186
|
hasNativeCrypto = (() => {
|
|
8187
8187
|
try {
|
|
8188
|
-
|
|
8188
|
+
createHash4("md5");
|
|
8189
8189
|
return true;
|
|
8190
8190
|
} catch {
|
|
8191
8191
|
return false;
|
|
@@ -8502,7 +8502,7 @@ var init_Sha256Js = __esm({
|
|
|
8502
8502
|
});
|
|
8503
8503
|
|
|
8504
8504
|
// node_modules/.pnpm/@smithy+core@3.35.0/node_modules/@smithy/core/dist-es/submodules/checksum/sha256/Sha256Node.js
|
|
8505
|
-
import { createHash as
|
|
8505
|
+
import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
|
|
8506
8506
|
function buildNativeClass3() {
|
|
8507
8507
|
return class Sha256Node {
|
|
8508
8508
|
digestLength = 32;
|
|
@@ -8536,7 +8536,7 @@ function buildNativeClass3() {
|
|
|
8536
8536
|
this.finished = false;
|
|
8537
8537
|
}
|
|
8538
8538
|
createHash() {
|
|
8539
|
-
return this.secret ? createHmac2("sha256", toBuffer(this.secret)) :
|
|
8539
|
+
return this.secret ? createHmac2("sha256", toBuffer(this.secret)) : createHash5("sha256");
|
|
8540
8540
|
}
|
|
8541
8541
|
};
|
|
8542
8542
|
}
|
|
@@ -8555,7 +8555,7 @@ var init_Sha256Node = __esm({
|
|
|
8555
8555
|
init_Sha256Js();
|
|
8556
8556
|
hasNativeCrypto2 = (() => {
|
|
8557
8557
|
try {
|
|
8558
|
-
|
|
8558
|
+
createHash5("sha256");
|
|
8559
8559
|
return true;
|
|
8560
8560
|
} catch {
|
|
8561
8561
|
return false;
|
|
@@ -26825,7 +26825,7 @@ var require_dist_cjs13 = __commonJS({
|
|
|
26825
26825
|
var { setCredentialFeature: setCredentialFeature2 } = (init_client3(), __toCommonJS(client_exports2));
|
|
26826
26826
|
var { CredentialsProviderError: CredentialsProviderError2, parseKnownFiles: parseKnownFiles2, getProfileName: getProfileName2 } = (init_config2(), __toCommonJS(config_exports));
|
|
26827
26827
|
var { HttpRequest: HttpRequest2 } = (init_protocols(), __toCommonJS(protocols_exports));
|
|
26828
|
-
var { createHash:
|
|
26828
|
+
var { createHash: createHash7, createPrivateKey, createPublicKey, sign: sign3 } = __require("node:crypto");
|
|
26829
26829
|
var { promises } = __require("node:fs");
|
|
26830
26830
|
var { homedir: homedir2 } = __require("node:os");
|
|
26831
26831
|
var { dirname: dirname2, join: join8 } = __require("node:path");
|
|
@@ -26994,7 +26994,7 @@ var require_dist_cjs13 = __commonJS({
|
|
|
26994
26994
|
getTokenFilePath() {
|
|
26995
26995
|
const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join8(homedir2(), ".aws", "login", "cache");
|
|
26996
26996
|
const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
|
|
26997
|
-
const loginSessionSha256 =
|
|
26997
|
+
const loginSessionSha256 = createHash7("sha256").update(loginSessionBytes).digest("hex");
|
|
26998
26998
|
return join8(directory, `${loginSessionSha256}.json`);
|
|
26999
26999
|
}
|
|
27000
27000
|
derToRawSignature(derSignature) {
|
|
@@ -36366,7 +36366,7 @@ var init_Sha1Js = __esm({
|
|
|
36366
36366
|
});
|
|
36367
36367
|
|
|
36368
36368
|
// node_modules/.pnpm/@aws-sdk+checksums@3.1001.1/node_modules/@aws-sdk/checksums/dist-es/submodules/sha/sha1/Sha1Node.js
|
|
36369
|
-
import { createHash as
|
|
36369
|
+
import { createHash as createHash6, createHmac as createHmac3 } from "node:crypto";
|
|
36370
36370
|
function buildNativeClass4() {
|
|
36371
36371
|
return class Sha1Node {
|
|
36372
36372
|
digestLength = 20;
|
|
@@ -36400,7 +36400,7 @@ function buildNativeClass4() {
|
|
|
36400
36400
|
this.finished = false;
|
|
36401
36401
|
}
|
|
36402
36402
|
createHash() {
|
|
36403
|
-
return this.secret ? createHmac3("sha1", toBuffer2(this.secret)) :
|
|
36403
|
+
return this.secret ? createHmac3("sha1", toBuffer2(this.secret)) : createHash6("sha1");
|
|
36404
36404
|
}
|
|
36405
36405
|
};
|
|
36406
36406
|
}
|
|
@@ -36419,7 +36419,7 @@ var init_Sha1Node = __esm({
|
|
|
36419
36419
|
init_Sha1Js();
|
|
36420
36420
|
hasNativeCrypto3 = (() => {
|
|
36421
36421
|
try {
|
|
36422
|
-
|
|
36422
|
+
createHash6("sha1");
|
|
36423
36423
|
return true;
|
|
36424
36424
|
} catch {
|
|
36425
36425
|
return false;
|
|
@@ -74013,6 +74013,9 @@ var require_dist_cjs22 = __commonJS({
|
|
|
74013
74013
|
// src/adapter/cache-handler.ts
|
|
74014
74014
|
var import_debug9 = __toESM(require_src());
|
|
74015
74015
|
|
|
74016
|
+
// src/adapter/cache-utils.ts
|
|
74017
|
+
import { createHash } from "node:crypto";
|
|
74018
|
+
|
|
74016
74019
|
// src/utils/base-path.ts
|
|
74017
74020
|
function normalizeBasePath(basePath) {
|
|
74018
74021
|
return (basePath || "").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
@@ -74077,6 +74080,35 @@ function parseCacheValue(jsonString) {
|
|
|
74077
74080
|
return val;
|
|
74078
74081
|
});
|
|
74079
74082
|
}
|
|
74083
|
+
function cacheKeyFileName(cacheKey) {
|
|
74084
|
+
let cleanCacheKey = cacheKey;
|
|
74085
|
+
if (cacheKey === "/" || cacheKey === "") {
|
|
74086
|
+
cleanCacheKey = "index";
|
|
74087
|
+
} else if (cacheKey.startsWith("/")) {
|
|
74088
|
+
cleanCacheKey = cacheKey.slice(1);
|
|
74089
|
+
}
|
|
74090
|
+
return `${cleanCacheKey}.json`;
|
|
74091
|
+
}
|
|
74092
|
+
function sha256Hex(value) {
|
|
74093
|
+
return createHash("sha256").update(value).digest("hex");
|
|
74094
|
+
}
|
|
74095
|
+
var MAX_S3_KEY_BYTES = 1024;
|
|
74096
|
+
var MAX_BUILD_ID_BYTES = 127;
|
|
74097
|
+
var LONG_KEY_PREFIX = "_long-key";
|
|
74098
|
+
function cacheObjectName(cacheKey) {
|
|
74099
|
+
const name = cacheKeyFileName(cacheKey);
|
|
74100
|
+
if (Buffer.byteLength(name) <= MAX_S3_KEY_BYTES - (MAX_BUILD_ID_BYTES + 1)) {
|
|
74101
|
+
return name;
|
|
74102
|
+
}
|
|
74103
|
+
return `${LONG_KEY_PREFIX}/${sha256Hex(name)}.long`;
|
|
74104
|
+
}
|
|
74105
|
+
var ROUTE_CACHE_KEY_PREFIX = /^route-cache\/[A-Z_]+\/[0-9a-f]{64}\/\$(?=\/)/;
|
|
74106
|
+
function denormalizePagePath(pagePath) {
|
|
74107
|
+
if (pagePath === "/index") {
|
|
74108
|
+
return "/";
|
|
74109
|
+
}
|
|
74110
|
+
return pagePath.startsWith("/index/") ? pagePath.slice("/index".length) : pagePath;
|
|
74111
|
+
}
|
|
74080
74112
|
var NEXT_CACHE_TAGS_HEADER = "x-next-cache-tags";
|
|
74081
74113
|
function headerTags(value) {
|
|
74082
74114
|
const headers = value?.headers;
|
|
@@ -74103,12 +74135,11 @@ var LocalFileCacheHandler = class {
|
|
|
74103
74135
|
debug(`Created local cache directory: ${this.cacheDir}`);
|
|
74104
74136
|
}
|
|
74105
74137
|
/**
|
|
74106
|
-
* Build file path for cache entry
|
|
74107
|
-
*
|
|
74138
|
+
* Build file path for cache entry: {@link cacheObjectName}, the name the
|
|
74139
|
+
* runtime reads it back under (BucketDeployment will add buildId prefix)
|
|
74108
74140
|
*/
|
|
74109
74141
|
buildFilePath(cacheKey) {
|
|
74110
|
-
|
|
74111
|
-
return filePath;
|
|
74142
|
+
return join(this.cacheDir, cacheObjectName(cacheKey));
|
|
74112
74143
|
}
|
|
74113
74144
|
/**
|
|
74114
74145
|
* Read back an entry this build already wrote.
|
|
@@ -74195,6 +74226,7 @@ var import_debug7 = __toESM(require_src());
|
|
|
74195
74226
|
// src/adapter/aws-cache-store.ts
|
|
74196
74227
|
var import_client_dynamodb = __toESM(require_dist_cjs19());
|
|
74197
74228
|
var import_client_s3 = __toESM(require_dist_cjs20());
|
|
74229
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
74198
74230
|
import { join as join6 } from "node:path";
|
|
74199
74231
|
function resolveAwsCacheConfig() {
|
|
74200
74232
|
return {
|
|
@@ -74205,13 +74237,10 @@ function resolveAwsCacheConfig() {
|
|
|
74205
74237
|
};
|
|
74206
74238
|
}
|
|
74207
74239
|
function buildS3Key(buildId, cacheKey) {
|
|
74208
|
-
|
|
74209
|
-
|
|
74210
|
-
|
|
74211
|
-
|
|
74212
|
-
cleanCacheKey = cacheKey.slice(1);
|
|
74213
|
-
}
|
|
74214
|
-
return join6(buildId, `${cleanCacheKey}.json`);
|
|
74240
|
+
return join6(buildId, cacheKeyFileName(cacheKey));
|
|
74241
|
+
}
|
|
74242
|
+
function s3ObjectKey(buildId, cacheKey) {
|
|
74243
|
+
return join6(buildId, cacheObjectName(cacheKey));
|
|
74215
74244
|
}
|
|
74216
74245
|
function markerState(marker, createdAt, now) {
|
|
74217
74246
|
const { revalidatedAt, expiredAt, staleAt } = marker;
|
|
@@ -74267,6 +74296,15 @@ function markerOf(item) {
|
|
|
74267
74296
|
}
|
|
74268
74297
|
var BATCH_GET_MAX_KEYS = 100;
|
|
74269
74298
|
var DEFAULT_TAG_REFRESH_MS = 1e3;
|
|
74299
|
+
var TAG_REFRESH_GRACE_MS = 1e3;
|
|
74300
|
+
var MAX_SORT_KEY_BYTES = 1024;
|
|
74301
|
+
function hashedTag(tag) {
|
|
74302
|
+
return `#${sha256Hex(tag)}`;
|
|
74303
|
+
}
|
|
74304
|
+
var MAX_TAG_BYTES = 768;
|
|
74305
|
+
function sortKeyTag(tag) {
|
|
74306
|
+
return Buffer.byteLength(tag) <= MAX_TAG_BYTES ? tag : hashedTag(tag);
|
|
74307
|
+
}
|
|
74270
74308
|
var TagMarkerTable = class {
|
|
74271
74309
|
constructor(client, tableName, buildId) {
|
|
74272
74310
|
this.client = client;
|
|
@@ -74289,7 +74327,7 @@ var TagMarkerTable = class {
|
|
|
74289
74327
|
TableName: this.tableName,
|
|
74290
74328
|
Key: {
|
|
74291
74329
|
pk: { S: this.buildId },
|
|
74292
|
-
sk: { S: tag }
|
|
74330
|
+
sk: { S: sortKeyTag(tag) }
|
|
74293
74331
|
},
|
|
74294
74332
|
...markerUpdate(now, durations),
|
|
74295
74333
|
ReturnValues: "ALL_NEW"
|
|
@@ -74309,7 +74347,13 @@ var TagMarkerTable = class {
|
|
|
74309
74347
|
const unique = Array.from(new Set(tags));
|
|
74310
74348
|
const markers = /* @__PURE__ */ new Map();
|
|
74311
74349
|
for (let i5 = 0; i5 < unique.length; i5 += BATCH_GET_MAX_KEYS) {
|
|
74312
|
-
|
|
74350
|
+
const tagBySk = new Map(
|
|
74351
|
+
unique.slice(i5, i5 + BATCH_GET_MAX_KEYS).map((tag) => [sortKeyTag(tag), tag])
|
|
74352
|
+
);
|
|
74353
|
+
let keys = Array.from(
|
|
74354
|
+
tagBySk.keys(),
|
|
74355
|
+
(sk) => ({ pk: { S: buildId }, sk: { S: sk } })
|
|
74356
|
+
);
|
|
74313
74357
|
for (let attempt = 0; keys?.length && attempt < 3; attempt++) {
|
|
74314
74358
|
const response = await this.client.send(
|
|
74315
74359
|
new import_client_dynamodb.BatchGetItemCommand({
|
|
@@ -74322,7 +74366,7 @@ var TagMarkerTable = class {
|
|
|
74322
74366
|
})
|
|
74323
74367
|
);
|
|
74324
74368
|
for (const item of response.Responses?.[tableName] ?? []) {
|
|
74325
|
-
const tag = item.sk?.S;
|
|
74369
|
+
const tag = tagBySk.get(item.sk?.S ?? "");
|
|
74326
74370
|
if (tag !== void 0) {
|
|
74327
74371
|
markers.set(tag, markerOf(item));
|
|
74328
74372
|
}
|
|
@@ -74360,11 +74404,13 @@ var RevalidationLog = class {
|
|
|
74360
74404
|
* to {@link LOG_PUT_ATTEMPTS} times.
|
|
74361
74405
|
*/
|
|
74362
74406
|
async put(tag, at, marker) {
|
|
74407
|
+
const skTag = sortKeyTag(tag);
|
|
74363
74408
|
for (let attempt = 1; ; attempt++) {
|
|
74364
74409
|
const item = {
|
|
74365
74410
|
pk: { S: this.pk },
|
|
74366
|
-
sk: { S: `${logSkPrefix(at)}#${
|
|
74367
|
-
ttl: { N: String(Math.ceil((at + REVALIDATION_LOG_TTL_MS) / 1e3)) }
|
|
74411
|
+
sk: { S: `${logSkPrefix(at)}#${skTag}` },
|
|
74412
|
+
ttl: { N: String(Math.ceil((at + REVALIDATION_LOG_TTL_MS) / 1e3)) },
|
|
74413
|
+
...skTag !== tag && { longTag: { S: tag } }
|
|
74368
74414
|
};
|
|
74369
74415
|
for (const field of MARKER_FIELDS) {
|
|
74370
74416
|
const value = marker[field];
|
|
@@ -74409,7 +74455,7 @@ var RevalidationLog = class {
|
|
|
74409
74455
|
":pk": { S: this.pk },
|
|
74410
74456
|
":since": { S: logSkPrefix(Math.max(0, since)) }
|
|
74411
74457
|
},
|
|
74412
|
-
ProjectionExpression: "sk, revalidatedAt, staleAt, expiredAt",
|
|
74458
|
+
ProjectionExpression: "sk, longTag, revalidatedAt, staleAt, expiredAt",
|
|
74413
74459
|
ExclusiveStartKey: startKey
|
|
74414
74460
|
})
|
|
74415
74461
|
);
|
|
@@ -74421,7 +74467,7 @@ var RevalidationLog = class {
|
|
|
74421
74467
|
rows.push({
|
|
74422
74468
|
sk,
|
|
74423
74469
|
at: Number(sk.slice(0, LOG_SK_DIGITS)),
|
|
74424
|
-
tag: sk.slice(LOG_SK_DIGITS + 1),
|
|
74470
|
+
tag: item.longTag?.S ?? sk.slice(LOG_SK_DIGITS + 1),
|
|
74425
74471
|
marker: markerOf(item)
|
|
74426
74472
|
});
|
|
74427
74473
|
}
|
|
@@ -74465,10 +74511,64 @@ var TrackedTagMarkers = class {
|
|
|
74465
74511
|
* result rather than dropped.
|
|
74466
74512
|
*/
|
|
74467
74513
|
pendingRows = /* @__PURE__ */ new Map();
|
|
74514
|
+
/**
|
|
74515
|
+
* Log rows for tags not tracked and not being read, merged by tag, oldest
|
|
74516
|
+
* first: what the log has said about them since {@link completeSince}. Taken
|
|
74517
|
+
* into a tag's marker when it is read or written.
|
|
74518
|
+
*/
|
|
74519
|
+
logOnly = /* @__PURE__ */ new Map();
|
|
74520
|
+
/** See {@link completeSince}. */
|
|
74521
|
+
floor;
|
|
74522
|
+
/**
|
|
74523
|
+
* The future `expiredAt`s of markers dropped since, as `[from, to]`, in
|
|
74524
|
+
* order: once `from` is past, {@link completeSince} moves to `to`, and not
|
|
74525
|
+
* before, since only then does it expire anything. `from` and `to` are the
|
|
74526
|
+
* same until spans are merged for space ({@link laterFloor}).
|
|
74527
|
+
*/
|
|
74528
|
+
laterFloors = [];
|
|
74529
|
+
/** The in-memory entry stores judged by {@link completeSince}. */
|
|
74530
|
+
entryStores = [];
|
|
74531
|
+
/** How many times {@link forget} ran: a read started before one is made again. */
|
|
74532
|
+
forgotten = 0;
|
|
74468
74533
|
/** Tracked tags whose read failed, for the next refresh to read again. */
|
|
74469
74534
|
unread = /* @__PURE__ */ new Set();
|
|
74535
|
+
/**
|
|
74536
|
+
* Set inside a page render {@link catchUp} let go, for that render alone.
|
|
74537
|
+
* See {@link behindInRender}.
|
|
74538
|
+
*/
|
|
74539
|
+
caughtUpRender = new AsyncLocalStorage();
|
|
74540
|
+
/** When `sync` last scanned the entry stores ({@link oldestEntry}). */
|
|
74541
|
+
lastEntryScan = -Infinity;
|
|
74542
|
+
/**
|
|
74543
|
+
* No `logOnly` row is known by it or earlier, so a {@link pruneLogged}
|
|
74544
|
+
* against it or earlier drops nothing: a marker is only ever known by later,
|
|
74545
|
+
* as its `expiredAt` passes.
|
|
74546
|
+
*/
|
|
74547
|
+
prunedAgainst = -Infinity;
|
|
74548
|
+
/** When the last refresh started, or was skipped. */
|
|
74470
74549
|
lastRefresh = -Infinity;
|
|
74550
|
+
/**
|
|
74551
|
+
* The refresh in flight. One left open across a freeze comes back after the
|
|
74552
|
+
* thaw with what it read before, so {@link refresh} waits for it and then
|
|
74553
|
+
* for a fresh one, rather than the runtime waiting for it before the freeze:
|
|
74554
|
+
* that would bill every Lambda invocation for the query's tail.
|
|
74555
|
+
*/
|
|
74471
74556
|
refreshing;
|
|
74557
|
+
/**
|
|
74558
|
+
* When {@link refreshing} last showed the process running: a timer ticks
|
|
74559
|
+
* while it is in flight, and a tick that comes late, or none at all, means
|
|
74560
|
+
* the process was paused - frozen - since it started.
|
|
74561
|
+
*/
|
|
74562
|
+
lastTick = -Infinity;
|
|
74563
|
+
/** Whether a tick came late since {@link refreshing} started. */
|
|
74564
|
+
pausedWhileRefreshing = false;
|
|
74565
|
+
ticker;
|
|
74566
|
+
canFreeze;
|
|
74567
|
+
/**
|
|
74568
|
+
* When the last refresh settled, either way, unless it spanned a pause: what
|
|
74569
|
+
* it knows is from then. See {@link behind}.
|
|
74570
|
+
*/
|
|
74571
|
+
settledAt;
|
|
74472
74572
|
/**
|
|
74473
74573
|
* Where the next log query starts (`clock()`): the last successful one's
|
|
74474
74574
|
* start, less {@link REVALIDATION_LOG_LOOKBACK_MS}.
|
|
@@ -74490,20 +74590,68 @@ var TrackedTagMarkers = class {
|
|
|
74490
74590
|
this.random = options.random ?? Math.random;
|
|
74491
74591
|
this.debug = options.debug ?? (() => {
|
|
74492
74592
|
});
|
|
74593
|
+
this.canFreeze = options.canFreeze ?? false;
|
|
74493
74594
|
const at = this.clock();
|
|
74494
74595
|
this.cursor = at - REVALIDATION_LOG_LOOKBACK_MS;
|
|
74495
74596
|
this.lastLogRead = at;
|
|
74597
|
+
this.settledAt = at;
|
|
74598
|
+
this.floor = markerClock();
|
|
74496
74599
|
}
|
|
74497
74600
|
/** `tag`'s marker as this instance knows it, if it tracks `tag`. */
|
|
74498
74601
|
get(tag) {
|
|
74499
74602
|
return this.tags.get(tag);
|
|
74500
74603
|
}
|
|
74604
|
+
/**
|
|
74605
|
+
* Since when (`markerClock()`) this instance knows of every revalidation of
|
|
74606
|
+
* every tag, tracked or not: tracked ones from their markers and the log,
|
|
74607
|
+
* the others from the log alone (`logOnly`). So an entry created at or after
|
|
74608
|
+
* it can be judged by {@link state} and {@link expiration} without
|
|
74609
|
+
* {@link ensure} reading anything - which matters inside a `'use cache'`
|
|
74610
|
+
* lookup, where Next.js waits for the answer and a read pushes the entry out
|
|
74611
|
+
* of the static stage of a staged render.
|
|
74612
|
+
*
|
|
74613
|
+
* Starts at construction: the log is followed from before it. Moves to now
|
|
74614
|
+
* when the tracked markers are forgotten, and past the revalidations of
|
|
74615
|
+
* whatever else it lets go of - a marker evicted for space, a log row
|
|
74616
|
+
* pruned - which are no longer known: to its `expiredAt` only once that is
|
|
74617
|
+
* past, since a profile's `expire` can be a year out, and moving there at
|
|
74618
|
+
* once would turn the read-free path off for the instance's life.
|
|
74619
|
+
*
|
|
74620
|
+
* A row is pruned only once it is no later than every entry held, so that
|
|
74621
|
+
* costs those entries nothing. An entry stored afterwards with an older
|
|
74622
|
+
* `timestamp` - Next.js stamps the start of its generation, which can
|
|
74623
|
+
* outlast a lookback - is older than this too, and is read first.
|
|
74624
|
+
*
|
|
74625
|
+
* It does not cover a revalidation older than the log's first lookback
|
|
74626
|
+
* whose `expiredAt` is still to come, which would expire an entry created
|
|
74627
|
+
* after it. So the `'use cache'` handler reads an entry's tags in the
|
|
74628
|
+
* background as it stores it.
|
|
74629
|
+
*/
|
|
74630
|
+
get completeSince() {
|
|
74631
|
+
const at = markerClock();
|
|
74632
|
+
while (this.laterFloors.length > 0 && this.laterFloors[0][0] <= at) {
|
|
74633
|
+
this.floor = Math.max(this.floor, this.laterFloors.shift()[1]);
|
|
74634
|
+
}
|
|
74635
|
+
return this.floor;
|
|
74636
|
+
}
|
|
74637
|
+
/**
|
|
74638
|
+
* Register an in-memory store whose entries are judged by
|
|
74639
|
+
* {@link completeSince}, by the `timestamp` of the oldest entry it holds:
|
|
74640
|
+
* `logOnly` keeps the rows that could still apply to one of them, and no
|
|
74641
|
+
* others. With none registered it keeps nothing. Asked with `scan` false,
|
|
74642
|
+
* `oldestTimestamp` may answer early, but never late, without a scan.
|
|
74643
|
+
*/
|
|
74644
|
+
judgeEntriesOf(oldestTimestamp, holdsAny = () => oldestTimestamp(false) < Infinity) {
|
|
74645
|
+
this.entryStores.push({ oldestTimestamp, holdsAny });
|
|
74646
|
+
}
|
|
74501
74647
|
/**
|
|
74502
74648
|
* Track `tag` with `marker`: what this instance just wrote, which it knows
|
|
74503
74649
|
* without reading it back.
|
|
74504
74650
|
*/
|
|
74505
74651
|
set(tag, marker) {
|
|
74506
|
-
this.
|
|
74652
|
+
const logged = this.logOnly.get(tag);
|
|
74653
|
+
this.logOnly.delete(tag);
|
|
74654
|
+
this.remember(tag, mergeMarkers(marker, logged));
|
|
74507
74655
|
this.known(tag, this.clock());
|
|
74508
74656
|
}
|
|
74509
74657
|
/**
|
|
@@ -74556,27 +74704,103 @@ var TrackedTagMarkers = class {
|
|
|
74556
74704
|
const unread = [];
|
|
74557
74705
|
for (const tag of new Set(tags)) {
|
|
74558
74706
|
const inFlight = this.reading.get(tag);
|
|
74559
|
-
if (
|
|
74560
|
-
waits.push(inFlight);
|
|
74561
|
-
} else if (this.tags.has(tag)) {
|
|
74707
|
+
if (this.tags.has(tag)) {
|
|
74562
74708
|
this.touch(tag);
|
|
74709
|
+
} else if (inFlight) {
|
|
74710
|
+
waits.push(inFlight);
|
|
74563
74711
|
} else {
|
|
74564
74712
|
unread.push(tag);
|
|
74565
74713
|
}
|
|
74566
74714
|
}
|
|
74567
74715
|
if (unread.length > 0) {
|
|
74568
|
-
|
|
74569
|
-
for (const tag of unread) {
|
|
74570
|
-
this.reading.delete(tag);
|
|
74571
|
-
}
|
|
74572
|
-
});
|
|
74573
|
-
for (const tag of unread) {
|
|
74574
|
-
this.reading.set(tag, read);
|
|
74575
|
-
}
|
|
74576
|
-
waits.push(read);
|
|
74716
|
+
waits.push(this.startRead(unread));
|
|
74577
74717
|
}
|
|
74578
74718
|
await Promise.all(waits);
|
|
74579
74719
|
}
|
|
74720
|
+
/**
|
|
74721
|
+
* {@link readInto} `tags`, as the read of each that whoever needs one of
|
|
74722
|
+
* them next waits on ({@link reading}). Never rejects: a failed read leaves
|
|
74723
|
+
* the tags tracked.
|
|
74724
|
+
*/
|
|
74725
|
+
startRead(tags) {
|
|
74726
|
+
const read = this.readInto(tags).then(() => {
|
|
74727
|
+
for (const tag of tags) {
|
|
74728
|
+
this.reading.delete(tag);
|
|
74729
|
+
}
|
|
74730
|
+
});
|
|
74731
|
+
for (const tag of tags) {
|
|
74732
|
+
this.reading.set(tag, read);
|
|
74733
|
+
}
|
|
74734
|
+
return read;
|
|
74735
|
+
}
|
|
74736
|
+
/**
|
|
74737
|
+
* Whether the last refresh settled more than `refreshIntervalMs` plus
|
|
74738
|
+
* {@link TAG_REFRESH_GRACE_MS} ago - one that spanned a freeze counts from
|
|
74739
|
+
* when it started: after the instance sat idle or frozen - or always, with
|
|
74740
|
+
* a `refreshIntervalMs` of `0`. Answering from what it knows then could
|
|
74741
|
+
* serve an entry revalidated elsewhere since, however long ago that was, so
|
|
74742
|
+
* `refreshTags` waits for the refresh, and the runtime waits for it before
|
|
74743
|
+
* a page render starts ({@link catchUp}).
|
|
74744
|
+
*
|
|
74745
|
+
* From when it settled, not when it started, so a slow query the runtime
|
|
74746
|
+
* waited for before the render does not leave the instance behind inside
|
|
74747
|
+
* it. A failed one counts too, so one request does not wait for two: the
|
|
74748
|
+
* same "assume the entry is valid" a failed read answers with, until the
|
|
74749
|
+
* next refresh, at most the interval and the grace later.
|
|
74750
|
+
*/
|
|
74751
|
+
get behind() {
|
|
74752
|
+
return this.log !== void 0 && // `0` asks before every check, so every check waits for the answer.
|
|
74753
|
+
(this.refreshIntervalMs === 0 || this.overdue(this.settledAt));
|
|
74754
|
+
}
|
|
74755
|
+
/**
|
|
74756
|
+
* {@link behind}, as `refreshTags` asks it: never inside a page's render
|
|
74757
|
+
* {@link catchUp} caught up, since the runtime settled it for that request
|
|
74758
|
+
* before the render started, and a wait inside a `'use cache'` lookup is
|
|
74759
|
+
* what it settled it to avoid - a render crossing the line partway through
|
|
74760
|
+
* would otherwise wait after all. So a revalidation elsewhere can go unseen
|
|
74761
|
+
* for a render's duration on top of the usual window.
|
|
74762
|
+
*
|
|
74763
|
+
* A route handler, which no catch-up precedes, waits at the plain line.
|
|
74764
|
+
*/
|
|
74765
|
+
get behindInRender() {
|
|
74766
|
+
return this.refreshIntervalMs !== 0 && this.caughtUpRender.getStore() ? false : this.behind;
|
|
74767
|
+
}
|
|
74768
|
+
/**
|
|
74769
|
+
* Whether a registered store ({@link judgeEntriesOf}) holds any entry: one
|
|
74770
|
+
* a refresh could expire. With none there is nothing to wait for even when
|
|
74771
|
+
* the instance is {@link behind}, since whatever is stored from here on is
|
|
74772
|
+
* newer than what the log would say - which covers the first `'use cache'`
|
|
74773
|
+
* call of a process, which creates the handler inside the render.
|
|
74774
|
+
*/
|
|
74775
|
+
get holdsEntries() {
|
|
74776
|
+
return this.entryStores.some((store) => store.holdsAny());
|
|
74777
|
+
}
|
|
74778
|
+
/**
|
|
74779
|
+
* {@link refresh}, waited for when the instance is {@link behind}. The
|
|
74780
|
+
* runtime calls it before handing a page request to Next.js, outside the
|
|
74781
|
+
* staged render whose static stage a wait inside a `'use cache'` lookup
|
|
74782
|
+
* would cut short. Only for the `'use cache'` handler's entries
|
|
74783
|
+
* ({@link judgeEntriesOf}): the other caches wait for the refresh where they
|
|
74784
|
+
* read anyway. Not with a `refreshIntervalMs` of `0` either, where
|
|
74785
|
+
* `refreshTags` asks again regardless. Then `render`, marked as caught up
|
|
74786
|
+
* for ({@link behindInRender}). Rejects only as `render` does.
|
|
74787
|
+
*/
|
|
74788
|
+
async catchUp(render) {
|
|
74789
|
+
const log = (error2) => console.error("Error refreshing cache tags:", error2);
|
|
74790
|
+
if (this.refreshIntervalMs > 0 && this.behind) {
|
|
74791
|
+
if (!this.holdsEntries) {
|
|
74792
|
+
return render();
|
|
74793
|
+
}
|
|
74794
|
+
await this.refresh().catch(log);
|
|
74795
|
+
} else if (this.refreshIntervalMs > 0) {
|
|
74796
|
+
void this.refresh().catch(log);
|
|
74797
|
+
}
|
|
74798
|
+
return this.caughtUpRender.run(true, render);
|
|
74799
|
+
}
|
|
74800
|
+
/** Whether `at` is more than the interval and the grace ago. */
|
|
74801
|
+
overdue(at) {
|
|
74802
|
+
return this.clock() - at > this.refreshIntervalMs + TAG_REFRESH_GRACE_MS;
|
|
74803
|
+
}
|
|
74580
74804
|
/**
|
|
74581
74805
|
* Catch up on revalidations other instances ran, at most once per
|
|
74582
74806
|
* `refreshIntervalMs`. See {@link TrackedTagMarkers} for the cost.
|
|
@@ -74587,6 +74811,9 @@ var TrackedTagMarkers = class {
|
|
|
74587
74811
|
return;
|
|
74588
74812
|
}
|
|
74589
74813
|
if (this.refreshing) {
|
|
74814
|
+
if (this.behind && this.pausedSinceRefreshStarted()) {
|
|
74815
|
+
return this.refreshing.then(() => this.refresh());
|
|
74816
|
+
}
|
|
74590
74817
|
return this.refreshing;
|
|
74591
74818
|
}
|
|
74592
74819
|
const at = this.clock();
|
|
@@ -74594,14 +74821,47 @@ var TrackedTagMarkers = class {
|
|
|
74594
74821
|
return;
|
|
74595
74822
|
}
|
|
74596
74823
|
this.lastRefresh = at;
|
|
74597
|
-
if (this.tags.size === 0) {
|
|
74824
|
+
if (this.tags.size === 0 && this.entryStores.length === 0) {
|
|
74598
74825
|
return;
|
|
74599
74826
|
}
|
|
74827
|
+
this.startTicking();
|
|
74600
74828
|
this.refreshing = this.sync(at, log).finally(() => {
|
|
74829
|
+
this.settledAt = Math.max(
|
|
74830
|
+
this.settledAt,
|
|
74831
|
+
this.pausedSinceRefreshStarted() ? this.lastRefresh : this.clock()
|
|
74832
|
+
);
|
|
74833
|
+
this.stopTicking();
|
|
74601
74834
|
this.refreshing = void 0;
|
|
74602
74835
|
});
|
|
74603
74836
|
return this.refreshing;
|
|
74604
74837
|
}
|
|
74838
|
+
startTicking() {
|
|
74839
|
+
if (!this.canFreeze) {
|
|
74840
|
+
return;
|
|
74841
|
+
}
|
|
74842
|
+
this.lastTick = this.clock();
|
|
74843
|
+
this.pausedWhileRefreshing = false;
|
|
74844
|
+
this.ticker = setInterval(() => {
|
|
74845
|
+
const at = this.clock();
|
|
74846
|
+
if (at - this.lastTick > REFRESH_PAUSE_MS) {
|
|
74847
|
+
this.pausedWhileRefreshing = true;
|
|
74848
|
+
}
|
|
74849
|
+
this.lastTick = at;
|
|
74850
|
+
}, REFRESH_TICK_MS);
|
|
74851
|
+
this.ticker.unref?.();
|
|
74852
|
+
}
|
|
74853
|
+
stopTicking() {
|
|
74854
|
+
clearInterval(this.ticker);
|
|
74855
|
+
this.ticker = void 0;
|
|
74856
|
+
}
|
|
74857
|
+
/**
|
|
74858
|
+
* Whether the process was paused since {@link refreshing} started: a tick
|
|
74859
|
+
* came late, or the next one is overdue (after a thaw, a request can run
|
|
74860
|
+
* before the timer does).
|
|
74861
|
+
*/
|
|
74862
|
+
pausedSinceRefreshStarted() {
|
|
74863
|
+
return this.canFreeze && (this.pausedWhileRefreshing || this.clock() - this.lastTick > REFRESH_PAUSE_MS);
|
|
74864
|
+
}
|
|
74605
74865
|
/**
|
|
74606
74866
|
* Record a revalidation of `tags`: here at once, and in the revalidation
|
|
74607
74867
|
* table for every other instance - the marker row plus a log row per tag.
|
|
@@ -74634,7 +74894,7 @@ var TrackedTagMarkers = class {
|
|
|
74634
74894
|
const at = markerClock();
|
|
74635
74895
|
let state2 = "fresh";
|
|
74636
74896
|
for (const tag of tags) {
|
|
74637
|
-
const marker = this.
|
|
74897
|
+
const marker = this.knownMarker(tag);
|
|
74638
74898
|
if (!marker) {
|
|
74639
74899
|
continue;
|
|
74640
74900
|
}
|
|
@@ -74658,7 +74918,7 @@ var TrackedTagMarkers = class {
|
|
|
74658
74918
|
const at = markerClock();
|
|
74659
74919
|
let latest = 0;
|
|
74660
74920
|
for (const tag of tags) {
|
|
74661
|
-
const marker = this.
|
|
74921
|
+
const marker = this.knownMarker(tag);
|
|
74662
74922
|
if (!marker) {
|
|
74663
74923
|
continue;
|
|
74664
74924
|
}
|
|
@@ -74745,13 +75005,14 @@ var TrackedTagMarkers = class {
|
|
|
74745
75005
|
);
|
|
74746
75006
|
return;
|
|
74747
75007
|
}
|
|
74748
|
-
const
|
|
74749
|
-
|
|
74750
|
-
|
|
74751
|
-
|
|
74752
|
-
|
|
74753
|
-
|
|
74754
|
-
|
|
75008
|
+
const due = this.due(at);
|
|
75009
|
+
if (due.length > 0) {
|
|
75010
|
+
void this.startRead(due);
|
|
75011
|
+
}
|
|
75012
|
+
const result = await log.query(this.cursor).catch((error2) => {
|
|
75013
|
+
console.error("Error reading tag revalidation log:", error2);
|
|
75014
|
+
return void 0;
|
|
75015
|
+
});
|
|
74755
75016
|
if (!result) {
|
|
74756
75017
|
return;
|
|
74757
75018
|
}
|
|
@@ -74762,6 +75023,11 @@ var TrackedTagMarkers = class {
|
|
|
74762
75023
|
);
|
|
74763
75024
|
return;
|
|
74764
75025
|
}
|
|
75026
|
+
const scan = at - this.lastEntryScan > ENTRY_SCAN_INTERVAL_MS;
|
|
75027
|
+
if (scan) {
|
|
75028
|
+
this.lastEntryScan = at;
|
|
75029
|
+
}
|
|
75030
|
+
const oldest = this.oldestEntry(scan);
|
|
74765
75031
|
let applied = 0;
|
|
74766
75032
|
for (const row of result.rows) {
|
|
74767
75033
|
if (this.applied.has(row.sk)) {
|
|
@@ -74776,11 +75042,20 @@ var TrackedTagMarkers = class {
|
|
|
74776
75042
|
mergeMarkers(this.pendingRows.get(row.tag), row.marker)
|
|
74777
75043
|
);
|
|
74778
75044
|
} else {
|
|
75045
|
+
this.rememberLogged(
|
|
75046
|
+
row.tag,
|
|
75047
|
+
mergeMarkers(this.logOnly.get(row.tag), row.marker),
|
|
75048
|
+
oldest
|
|
75049
|
+
);
|
|
74779
75050
|
continue;
|
|
74780
75051
|
}
|
|
74781
75052
|
this.applied.set(row.sk, row.at);
|
|
74782
75053
|
applied++;
|
|
74783
75054
|
}
|
|
75055
|
+
if (oldest > this.prunedAgainst) {
|
|
75056
|
+
this.pruneLogged(oldest);
|
|
75057
|
+
}
|
|
75058
|
+
this.prunedAgainst = oldest;
|
|
74784
75059
|
this.lastLogRead = at;
|
|
74785
75060
|
this.advance(at - REVALIDATION_LOG_LOOKBACK_MS);
|
|
74786
75061
|
this.debug(
|
|
@@ -74796,14 +75071,16 @@ var TrackedTagMarkers = class {
|
|
|
74796
75071
|
const due = [];
|
|
74797
75072
|
for (const tag of this.unread) {
|
|
74798
75073
|
if (due.length >= BATCH_GET_MAX_KEYS) return due;
|
|
74799
|
-
|
|
75074
|
+
if (!this.reading.has(tag)) {
|
|
75075
|
+
due.push(tag);
|
|
75076
|
+
}
|
|
74800
75077
|
}
|
|
74801
75078
|
const jitter = this.resyncIntervalMs * TAG_RESYNC_JITTER;
|
|
74802
75079
|
for (const [tag, dueAt] of this.dueAt) {
|
|
74803
75080
|
if (due.length >= BATCH_GET_MAX_KEYS || dueAt - jitter > at) {
|
|
74804
75081
|
break;
|
|
74805
75082
|
}
|
|
74806
|
-
if (dueAt <= at && !this.unread.has(tag)) {
|
|
75083
|
+
if (dueAt <= at && !this.unread.has(tag) && !this.reading.has(tag)) {
|
|
74807
75084
|
due.push(tag);
|
|
74808
75085
|
}
|
|
74809
75086
|
}
|
|
@@ -74816,10 +75093,19 @@ var TrackedTagMarkers = class {
|
|
|
74816
75093
|
*/
|
|
74817
75094
|
forget(at, why) {
|
|
74818
75095
|
this.debug(`forgetting ${this.tags.size} tracked tags: ${why}`);
|
|
75096
|
+
this.forgotten++;
|
|
75097
|
+
for (const marker of this.tags.values()) {
|
|
75098
|
+
this.raiseFloor(marker);
|
|
75099
|
+
}
|
|
75100
|
+
for (const marker of this.logOnly.values()) {
|
|
75101
|
+
this.raiseFloor(marker);
|
|
75102
|
+
}
|
|
74819
75103
|
this.tags.clear();
|
|
74820
75104
|
this.dueAt.clear();
|
|
74821
75105
|
this.unread.clear();
|
|
74822
75106
|
this.applied.clear();
|
|
75107
|
+
this.logOnly.clear();
|
|
75108
|
+
this.floor = Math.max(this.floor, markerClock());
|
|
74823
75109
|
this.cursor = at - REVALIDATION_LOG_LOOKBACK_MS;
|
|
74824
75110
|
this.lastLogRead = at;
|
|
74825
75111
|
}
|
|
@@ -74845,13 +75131,17 @@ var TrackedTagMarkers = class {
|
|
|
74845
75131
|
* always answered with, rather than a read on every request while DynamoDB
|
|
74846
75132
|
* is unavailable.
|
|
74847
75133
|
*/
|
|
74848
|
-
async readInto(tags) {
|
|
75134
|
+
async readInto(tags, again = false) {
|
|
74849
75135
|
if (tags.length === 0) {
|
|
74850
75136
|
return true;
|
|
74851
75137
|
}
|
|
74852
75138
|
const at = this.clock();
|
|
75139
|
+
const forgotten = this.forgotten;
|
|
74853
75140
|
try {
|
|
74854
75141
|
const read = await this.markers.read(tags);
|
|
75142
|
+
if (this.forgotten !== forgotten && !again) {
|
|
75143
|
+
return await this.readInto(tags, true);
|
|
75144
|
+
}
|
|
74855
75145
|
for (const tag of tags) {
|
|
74856
75146
|
this.unread.delete(tag);
|
|
74857
75147
|
this.remember(
|
|
@@ -74867,6 +75157,9 @@ var TrackedTagMarkers = class {
|
|
|
74867
75157
|
return true;
|
|
74868
75158
|
} catch (error2) {
|
|
74869
75159
|
console.error("Error reading tag markers:", error2);
|
|
75160
|
+
if (this.forgotten !== forgotten && !again) {
|
|
75161
|
+
return this.readInto(tags, true);
|
|
75162
|
+
}
|
|
74870
75163
|
this.trackUnread(tags);
|
|
74871
75164
|
for (const tag of tags) {
|
|
74872
75165
|
this.unread.add(tag);
|
|
@@ -74880,8 +75173,119 @@ var TrackedTagMarkers = class {
|
|
|
74880
75173
|
}
|
|
74881
75174
|
takePendingRows(tag) {
|
|
74882
75175
|
const pending = this.pendingRows.get(tag);
|
|
75176
|
+
const logged = this.logOnly.get(tag);
|
|
74883
75177
|
this.pendingRows.delete(tag);
|
|
74884
|
-
|
|
75178
|
+
this.logOnly.delete(tag);
|
|
75179
|
+
return logged ? mergeMarkers(logged, pending) : pending;
|
|
75180
|
+
}
|
|
75181
|
+
/**
|
|
75182
|
+
* What this instance knows of `tag`: tracked, or from the log alone -
|
|
75183
|
+
* including rows that arrived while its first read is in flight.
|
|
75184
|
+
*/
|
|
75185
|
+
knownMarker(tag) {
|
|
75186
|
+
const tracked = this.tags.get(tag);
|
|
75187
|
+
if (tracked) {
|
|
75188
|
+
return tracked;
|
|
75189
|
+
}
|
|
75190
|
+
const logged = this.logOnly.get(tag);
|
|
75191
|
+
const pending = this.pendingRows.get(tag);
|
|
75192
|
+
return logged || pending ? mergeMarkers(logged, pending) : void 0;
|
|
75193
|
+
}
|
|
75194
|
+
/**
|
|
75195
|
+
* Keep `tag`'s log rows in `logOnly`, unless they cannot apply to an entry
|
|
75196
|
+
* held, `oldest` or newer (see {@link pruneLogged}), as bounded as the
|
|
75197
|
+
* tracked tags. Either way a row let go of takes what it knew with it, so
|
|
75198
|
+
* `completeSince` moves past it.
|
|
75199
|
+
*/
|
|
75200
|
+
rememberLogged(tag, marker, oldest) {
|
|
75201
|
+
this.logOnly.delete(tag);
|
|
75202
|
+
if (knownBy(marker, markerClock()) <= oldest) {
|
|
75203
|
+
this.raiseFloor(marker);
|
|
75204
|
+
return;
|
|
75205
|
+
}
|
|
75206
|
+
this.logOnly.set(tag, marker);
|
|
75207
|
+
for (const [first, dropped] of this.logOnly) {
|
|
75208
|
+
if (this.logOnly.size <= this.maxTrackedTags) {
|
|
75209
|
+
break;
|
|
75210
|
+
}
|
|
75211
|
+
this.logOnly.delete(first);
|
|
75212
|
+
this.raiseFloor(dropped);
|
|
75213
|
+
}
|
|
75214
|
+
}
|
|
75215
|
+
/**
|
|
75216
|
+
* Drop the `logOnly` rows that cannot apply to an entry held, `oldest` or
|
|
75217
|
+
* newer: every revalidation they name so far is no later than it. That
|
|
75218
|
+
* moves `completeSince` no further than `oldest`, which costs the entries
|
|
75219
|
+
* held nothing, and a profile's `expiredAt` still to come waits in
|
|
75220
|
+
* `laterFloors`. Without this, a deployment revalidating many distinct tags
|
|
75221
|
+
* filled `logOnly` with tags no entry here carries, and the evictions past
|
|
75222
|
+
* its bound kept moving `completeSince` up to the present.
|
|
75223
|
+
*/
|
|
75224
|
+
pruneLogged(oldest) {
|
|
75225
|
+
const at = markerClock();
|
|
75226
|
+
for (const [tag, marker] of this.logOnly) {
|
|
75227
|
+
if (knownBy(marker, at) <= oldest) {
|
|
75228
|
+
this.logOnly.delete(tag);
|
|
75229
|
+
this.raiseFloor(marker);
|
|
75230
|
+
}
|
|
75231
|
+
}
|
|
75232
|
+
}
|
|
75233
|
+
/**
|
|
75234
|
+
* The oldest entry timestamp of any registered store, `Infinity` with none:
|
|
75235
|
+
* early, never late, unless `scan`.
|
|
75236
|
+
*/
|
|
75237
|
+
oldestEntry(scan) {
|
|
75238
|
+
let oldest = Infinity;
|
|
75239
|
+
for (const { oldestTimestamp } of this.entryStores) {
|
|
75240
|
+
oldest = Math.min(oldest, oldestTimestamp(scan));
|
|
75241
|
+
}
|
|
75242
|
+
return oldest;
|
|
75243
|
+
}
|
|
75244
|
+
/**
|
|
75245
|
+
* Move `completeSince` past `marker`'s revalidations: what is no longer
|
|
75246
|
+
* known about a tag once its marker is dropped. A future `expiredAt` waits
|
|
75247
|
+
* in `laterFloors` until it is past.
|
|
75248
|
+
*/
|
|
75249
|
+
raiseFloor(marker) {
|
|
75250
|
+
const at = markerClock();
|
|
75251
|
+
this.floor = Math.max(this.floor, knownBy(marker, at));
|
|
75252
|
+
const { expiredAt } = marker;
|
|
75253
|
+
if (expiredAt !== void 0 && expiredAt > Math.max(at, this.floor)) {
|
|
75254
|
+
this.laterFloor(expiredAt);
|
|
75255
|
+
}
|
|
75256
|
+
}
|
|
75257
|
+
/**
|
|
75258
|
+
* Add `time` to `laterFloors`, in order. Past the tracked-tag bound the two
|
|
75259
|
+
* spans closest together are merged: from the first's start,
|
|
75260
|
+
* `completeSince` is the second's end, early - which only costs reads, and
|
|
75261
|
+
* for as short a time as any merge allows. Never later than its time, which
|
|
75262
|
+
* would let an entry expire unseen.
|
|
75263
|
+
*/
|
|
75264
|
+
laterFloor(time2) {
|
|
75265
|
+
const floors = this.laterFloors;
|
|
75266
|
+
let low = 0;
|
|
75267
|
+
let high = floors.length;
|
|
75268
|
+
while (low < high) {
|
|
75269
|
+
const mid = Math.floor((low + high) / 2);
|
|
75270
|
+
if (floors[mid][0] < time2) {
|
|
75271
|
+
low = mid + 1;
|
|
75272
|
+
} else {
|
|
75273
|
+
high = mid;
|
|
75274
|
+
}
|
|
75275
|
+
}
|
|
75276
|
+
if (low > 0 && floors[low - 1][1] >= time2 || floors[low]?.[0] === time2) {
|
|
75277
|
+
return;
|
|
75278
|
+
}
|
|
75279
|
+
floors.splice(low, 0, [time2, time2]);
|
|
75280
|
+
if (floors.length > this.maxTrackedTags) {
|
|
75281
|
+
let merge2 = 0;
|
|
75282
|
+
for (let i5 = 1; i5 < floors.length - 1; i5++) {
|
|
75283
|
+
if (floors[i5 + 1][1] - floors[i5][0] < floors[merge2 + 1][1] - floors[merge2][0]) {
|
|
75284
|
+
merge2 = i5;
|
|
75285
|
+
}
|
|
75286
|
+
}
|
|
75287
|
+
floors.splice(merge2, 2, [floors[merge2][0], floors[merge2 + 1][1]]);
|
|
75288
|
+
}
|
|
74885
75289
|
}
|
|
74886
75290
|
/** Record that `tag`'s marker was known from the table at `at`. */
|
|
74887
75291
|
known(tag, at) {
|
|
@@ -74898,6 +75302,7 @@ var TrackedTagMarkers = class {
|
|
|
74898
75302
|
if (this.tags.size <= this.maxTrackedTags) {
|
|
74899
75303
|
break;
|
|
74900
75304
|
}
|
|
75305
|
+
this.raiseFloor(this.tags.get(oldest));
|
|
74901
75306
|
this.tags.delete(oldest);
|
|
74902
75307
|
this.dueAt.delete(oldest);
|
|
74903
75308
|
this.unread.delete(oldest);
|
|
@@ -74910,7 +75315,18 @@ var TrackedTagMarkers = class {
|
|
|
74910
75315
|
}
|
|
74911
75316
|
}
|
|
74912
75317
|
};
|
|
75318
|
+
function knownBy(marker, at) {
|
|
75319
|
+
const { revalidatedAt, staleAt, expiredAt } = marker;
|
|
75320
|
+
return Math.max(
|
|
75321
|
+
revalidatedAt ?? -Infinity,
|
|
75322
|
+
staleAt ?? -Infinity,
|
|
75323
|
+
expiredAt !== void 0 && expiredAt <= at ? expiredAt : -Infinity
|
|
75324
|
+
);
|
|
75325
|
+
}
|
|
74913
75326
|
var MAX_LOG_PUT_MS = REVALIDATION_LOG_LOOKBACK_MS / 2;
|
|
75327
|
+
var ENTRY_SCAN_INTERVAL_MS = 6e4;
|
|
75328
|
+
var REFRESH_TICK_MS = 250;
|
|
75329
|
+
var REFRESH_PAUSE_MS = 1e3;
|
|
74914
75330
|
function mergeMarkers(local, remote) {
|
|
74915
75331
|
if (!local) {
|
|
74916
75332
|
return remote ?? {};
|
|
@@ -74973,6 +75389,13 @@ var CacheBucket = class {
|
|
|
74973
75389
|
// src/adapter/use-cache-common.ts
|
|
74974
75390
|
var import_client_dynamodb2 = __toESM(require_dist_cjs19());
|
|
74975
75391
|
var import_debug6 = __toESM(require_src());
|
|
75392
|
+
|
|
75393
|
+
// src/runtime/tag-manifest.ts
|
|
75394
|
+
var TAG_MANIFEST_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
75395
|
+
"cdk-nextjs.use-cache.tag-manifest"
|
|
75396
|
+
);
|
|
75397
|
+
|
|
75398
|
+
// src/adapter/use-cache-common.ts
|
|
74976
75399
|
function isBuildPhase() {
|
|
74977
75400
|
return process.env.NEXT_PHASE === "phase-production-build";
|
|
74978
75401
|
}
|
|
@@ -74982,7 +75405,7 @@ function numberFromEnv(name, fallback2) {
|
|
|
74982
75405
|
return Number.isFinite(value) && value >= 0 ? value : fallback2;
|
|
74983
75406
|
}
|
|
74984
75407
|
var DEFAULT_MEMORY_BYTES = 50 * 1024 * 1024;
|
|
74985
|
-
var
|
|
75408
|
+
var TAG_TABLE_REQUEST_TIMEOUT_MS = 3e3;
|
|
74986
75409
|
function sharedTagManifest() {
|
|
74987
75410
|
const global = globalThis;
|
|
74988
75411
|
if (!global[TAG_MANIFEST_SYMBOL]) {
|
|
@@ -74992,12 +75415,24 @@ function sharedTagManifest() {
|
|
|
74992
75415
|
DEFAULT_TAG_REFRESH_MS
|
|
74993
75416
|
);
|
|
74994
75417
|
if (config.tableName && !isBuildPhase()) {
|
|
74995
|
-
const client = new import_client_dynamodb2.DynamoDBClient({
|
|
75418
|
+
const client = new import_client_dynamodb2.DynamoDBClient({
|
|
75419
|
+
region: config.region,
|
|
75420
|
+
// Without `throwOnRequestTimeout` the timeout only logs a warning. It
|
|
75421
|
+
// ends once the response headers arrive: `socketTimeout`, an
|
|
75422
|
+
// inactivity timer kept until the request closes, covers the body.
|
|
75423
|
+
requestHandler: {
|
|
75424
|
+
requestTimeout: TAG_TABLE_REQUEST_TIMEOUT_MS,
|
|
75425
|
+
throwOnRequestTimeout: true,
|
|
75426
|
+
socketTimeout: TAG_TABLE_REQUEST_TIMEOUT_MS
|
|
75427
|
+
}
|
|
75428
|
+
});
|
|
74996
75429
|
global[TAG_MANIFEST_SYMBOL] = new TrackedTagMarkers({
|
|
74997
75430
|
markers: new TagMarkerTable(client, config.tableName, config.buildId),
|
|
74998
75431
|
log: new RevalidationLog(client, config.tableName, config.buildId),
|
|
74999
75432
|
refreshIntervalMs,
|
|
75000
|
-
debug: (0, import_debug6.default)("cdk-nextjs:cache-handler:tags")
|
|
75433
|
+
debug: (0, import_debug6.default)("cdk-nextjs:cache-handler:tags"),
|
|
75434
|
+
// Set in every Lambda runtime; a container is never frozen.
|
|
75435
|
+
canFreeze: process.env.AWS_LAMBDA_FUNCTION_NAME !== void 0
|
|
75001
75436
|
});
|
|
75002
75437
|
} else {
|
|
75003
75438
|
if (!isBuildPhase()) {
|
|
@@ -75353,8 +75788,8 @@ var S3CacheHandler = class {
|
|
|
75353
75788
|
if (!this.config.bucketName) {
|
|
75354
75789
|
return null;
|
|
75355
75790
|
}
|
|
75356
|
-
const
|
|
75357
|
-
const response = await this.bucket.get(
|
|
75791
|
+
const objectKey = s3ObjectKey(this.config.buildId, cacheKey);
|
|
75792
|
+
const response = await this.bucket.get(objectKey);
|
|
75358
75793
|
if (!response) {
|
|
75359
75794
|
this.debug(`S3 CACHE MISS: ${cacheKey}`);
|
|
75360
75795
|
return null;
|
|
@@ -75364,7 +75799,7 @@ var S3CacheHandler = class {
|
|
|
75364
75799
|
this.debug(`S3 CACHE INVALIDATED BY TAG: ${cacheKey}`);
|
|
75365
75800
|
return isFetchCacheGet(ctx) ? null : { lastModified: EXPIRED_LAST_MODIFIED, value: parsedValue.value };
|
|
75366
75801
|
}
|
|
75367
|
-
this.debug(`S3 CACHE HIT: ${cacheKey} (${
|
|
75802
|
+
this.debug(`S3 CACHE HIT: ${cacheKey} (${objectKey})`);
|
|
75368
75803
|
return {
|
|
75369
75804
|
lastModified: parsedValue.lastModified,
|
|
75370
75805
|
value: parsedValue.value
|
|
@@ -75436,6 +75871,8 @@ var S3CacheHandler = class {
|
|
|
75436
75871
|
this.softRevalidatedKeys.add(cacheKey);
|
|
75437
75872
|
}
|
|
75438
75873
|
async set(cacheKey, data, ctx) {
|
|
75874
|
+
const s3Key = buildS3Key(this.config.buildId, cacheKey);
|
|
75875
|
+
const objectKey = s3ObjectKey(this.config.buildId, cacheKey);
|
|
75439
75876
|
try {
|
|
75440
75877
|
if (!data) {
|
|
75441
75878
|
this.debug(`S3 CACHE DELETE: ${cacheKey}`);
|
|
@@ -75443,16 +75880,15 @@ var S3CacheHandler = class {
|
|
|
75443
75880
|
return;
|
|
75444
75881
|
}
|
|
75445
75882
|
this.softRevalidatedKeys.delete(cacheKey);
|
|
75446
|
-
const
|
|
75447
|
-
const tags2 = await this.storedEntryTags(s3Key2, ctx);
|
|
75883
|
+
const tags2 = await this.storedEntryTags(objectKey, ctx);
|
|
75448
75884
|
await this.s3Client.send(
|
|
75449
75885
|
new import_client_s32.DeleteObjectCommand({
|
|
75450
75886
|
Bucket: this.config.bucketName,
|
|
75451
|
-
Key:
|
|
75887
|
+
Key: objectKey
|
|
75452
75888
|
})
|
|
75453
75889
|
);
|
|
75454
|
-
this.debug(`S3 CACHE DELETED: ${
|
|
75455
|
-
await this.deleteDynamoDBTagMappings(
|
|
75890
|
+
this.debug(`S3 CACHE DELETED: ${objectKey}`);
|
|
75891
|
+
await this.deleteDynamoDBTagMappings(s3Key, tags2);
|
|
75456
75892
|
return;
|
|
75457
75893
|
}
|
|
75458
75894
|
const tags = entryTags({ tags: getTags(ctx), value: data });
|
|
@@ -75462,7 +75898,6 @@ var S3CacheHandler = class {
|
|
|
75462
75898
|
if (!this.config.bucketName) {
|
|
75463
75899
|
return;
|
|
75464
75900
|
}
|
|
75465
|
-
const s3Key = this.buildS3Key(cacheKey);
|
|
75466
75901
|
const cacheHandlerValue = {
|
|
75467
75902
|
// On the marker clock, since that is what it is compared with.
|
|
75468
75903
|
lastModified: markerClock(),
|
|
@@ -75473,7 +75908,7 @@ var S3CacheHandler = class {
|
|
|
75473
75908
|
tags
|
|
75474
75909
|
};
|
|
75475
75910
|
const body = serializeCacheValue(cacheEntryWithTags);
|
|
75476
|
-
await this.bucket.putJson(
|
|
75911
|
+
await this.bucket.putJson(objectKey, body);
|
|
75477
75912
|
this.debug(`S3 CACHE STORED: ${cacheKey} (${data.kind})`);
|
|
75478
75913
|
if (this.softRevalidatedKeys.delete(cacheKey)) {
|
|
75479
75914
|
const route = this.s3KeyToInvalidationPath(s3Key);
|
|
@@ -75531,17 +75966,19 @@ var S3CacheHandler = class {
|
|
|
75531
75966
|
* {@link MAX_TAG_QUERY_PAGES}, or no build tag manifest to read.
|
|
75532
75967
|
*/
|
|
75533
75968
|
async tagRoutes(tag) {
|
|
75969
|
+
const skPrefix = this.tagMappingPrefix(tag);
|
|
75534
75970
|
const [{ items, truncated }, buildTags] = await Promise.all([
|
|
75535
|
-
this.queryTagMappings(tag),
|
|
75971
|
+
this.queryTagMappings(tag, skPrefix),
|
|
75536
75972
|
this.buildTagManifest()
|
|
75537
75973
|
]);
|
|
75538
|
-
const prefixLength =
|
|
75974
|
+
const prefixLength = skPrefix.length;
|
|
75539
75975
|
const keyPrefix = this.config.buildId ? `${this.config.buildId}/` : "";
|
|
75540
|
-
const s3Keys = items.
|
|
75541
|
-
|
|
75542
|
-
|
|
75976
|
+
const s3Keys = items.flatMap((item) => {
|
|
75977
|
+
const rest = item.sk?.S?.slice(prefixLength);
|
|
75978
|
+
return rest && rest.startsWith(keyPrefix) ? [item.s3Key?.S ?? rest] : [];
|
|
75979
|
+
});
|
|
75543
75980
|
for (const cacheKey of buildTags?.get(tag) ?? []) {
|
|
75544
|
-
s3Keys.push(this.
|
|
75981
|
+
s3Keys.push(buildS3Key(this.config.buildId, cacheKey));
|
|
75545
75982
|
}
|
|
75546
75983
|
this.debug(
|
|
75547
75984
|
`TAG ${tag}: Found ${s3Keys.length} cache entries to invalidate`
|
|
@@ -75566,7 +76003,8 @@ var S3CacheHandler = class {
|
|
|
75566
76003
|
});
|
|
75567
76004
|
}
|
|
75568
76005
|
/**
|
|
75569
|
-
* Every mapping row for `tag
|
|
76006
|
+
* Every mapping row for `tag` - those whose sort key starts with
|
|
76007
|
+
* `skPrefix`, its {@link tagMappingPrefix} - following `LastEvaluatedKey`, and whether
|
|
75570
76008
|
* {@link MAX_TAG_QUERY_PAGES} cut the walk short.
|
|
75571
76009
|
*
|
|
75572
76010
|
* DynamoDB caps a Query at 1 MB of items regardless of how many match, and
|
|
@@ -75575,7 +76013,7 @@ var S3CacheHandler = class {
|
|
|
75575
76013
|
* first page invalidates only that page's entries while still reporting
|
|
75576
76014
|
* success, which is indistinguishable from revalidation having worked.
|
|
75577
76015
|
*/
|
|
75578
|
-
async queryTagMappings(tag) {
|
|
76016
|
+
async queryTagMappings(tag, skPrefix) {
|
|
75579
76017
|
const items = [];
|
|
75580
76018
|
let exclusiveStartKey;
|
|
75581
76019
|
let pages = 0;
|
|
@@ -75586,7 +76024,7 @@ var S3CacheHandler = class {
|
|
|
75586
76024
|
KeyConditionExpression: "pk = :pk AND begins_with(sk, :skPrefix)",
|
|
75587
76025
|
ExpressionAttributeValues: {
|
|
75588
76026
|
":pk": { S: this.config.buildId },
|
|
75589
|
-
":skPrefix": { S:
|
|
76027
|
+
":skPrefix": { S: skPrefix }
|
|
75590
76028
|
},
|
|
75591
76029
|
ExclusiveStartKey: exclusiveStartKey
|
|
75592
76030
|
})
|
|
@@ -75622,6 +76060,10 @@ var S3CacheHandler = class {
|
|
|
75622
76060
|
if (withoutSuffix.includes("[") || isFetchCacheKey(withoutSuffix)) {
|
|
75623
76061
|
return void 0;
|
|
75624
76062
|
}
|
|
76063
|
+
const routeCacheKey = ROUTE_CACHE_KEY_PREFIX.exec(withoutSuffix);
|
|
76064
|
+
if (routeCacheKey) {
|
|
76065
|
+
return denormalizePagePath(withoutSuffix.slice(routeCacheKey[0].length));
|
|
76066
|
+
}
|
|
75625
76067
|
return withoutSuffix === "index" ? "/" : `/${withoutSuffix}`;
|
|
75626
76068
|
}
|
|
75627
76069
|
/**
|
|
@@ -75728,19 +76170,37 @@ var S3CacheHandler = class {
|
|
|
75728
76170
|
this.cloudFrontConfig.distributionId || this.cloudFrontConfig.distributionIdParameterName
|
|
75729
76171
|
);
|
|
75730
76172
|
}
|
|
75731
|
-
/**
|
|
75732
|
-
|
|
75733
|
-
|
|
76173
|
+
/**
|
|
76174
|
+
* The sort key of `tag`'s mapping row for `s3Key`: `<prefix>s3Key`, or, when
|
|
76175
|
+
* that passes DynamoDB's 1024-byte sort key limit, `<prefix><buildId>/#<sha256>`.
|
|
76176
|
+
* Either way the row carries the key itself in `s3Key`. next 16.3.8's
|
|
76177
|
+
* route-scoped keys are ~88 bytes longer than a pathname, enough to push a
|
|
76178
|
+
* long tag on a long path over - and a rejected `PutItem` left
|
|
76179
|
+
* `revalidateTag` nothing to invalidate, so CloudFront kept the stale page.
|
|
76180
|
+
* The prefix is {@link tagMappingPrefix}'s, which {@link MAX_TAG_BYTES} and
|
|
76181
|
+
* the build ID's cap leave room for the hashed form after.
|
|
76182
|
+
*/
|
|
76183
|
+
tagMappingSortKey(tag, s3Key) {
|
|
76184
|
+
const prefix = this.tagMappingPrefix(tag);
|
|
76185
|
+
const sk = `${prefix}${s3Key}`;
|
|
76186
|
+
return Buffer.byteLength(sk) <= MAX_SORT_KEY_BYTES ? sk : `${prefix}${this.config.buildId}/${hashedTag(s3Key)}`;
|
|
76187
|
+
}
|
|
76188
|
+
/**
|
|
76189
|
+
* What every mapping row of `tag` starts with, and so what
|
|
76190
|
+
* {@link queryTagMappings} matches: `<sortKeyTag(tag)>#`.
|
|
76191
|
+
*/
|
|
76192
|
+
tagMappingPrefix(tag) {
|
|
76193
|
+
return `${sortKeyTag(tag)}#`;
|
|
75734
76194
|
}
|
|
75735
76195
|
async storeDynamoDBTagMappings(s3Key, tags) {
|
|
75736
76196
|
try {
|
|
75737
76197
|
const updatePromises = tags.map(async (tag) => {
|
|
75738
|
-
const
|
|
76198
|
+
const sk = this.tagMappingSortKey(tag, s3Key);
|
|
75739
76199
|
const updateCommand = new import_client_dynamodb3.UpdateItemCommand({
|
|
75740
76200
|
TableName: this.config.tableName,
|
|
75741
76201
|
Key: {
|
|
75742
76202
|
pk: { S: this.config.buildId },
|
|
75743
|
-
sk: { S:
|
|
76203
|
+
sk: { S: sk }
|
|
75744
76204
|
},
|
|
75745
76205
|
// Deliberately no `revalidatedAt`: a mapping row records only that an
|
|
75746
76206
|
// entry carries the tag. Stamping it at write time made every tagged
|
|
@@ -75748,9 +76208,10 @@ var S3CacheHandler = class {
|
|
|
75748
76208
|
// row's timestamp is taken after the entry's `lastModified` — so
|
|
75749
76209
|
// `checkIfRevalidated` deleted healthy entries and the page
|
|
75750
76210
|
// re-rendered on every request.
|
|
75751
|
-
UpdateExpression: "SET createdAt = if_not_exists(createdAt, :now)",
|
|
76211
|
+
UpdateExpression: "SET createdAt = if_not_exists(createdAt, :now), s3Key = :s3Key",
|
|
75752
76212
|
ExpressionAttributeValues: {
|
|
75753
|
-
":now": { N: Date.now().toString() }
|
|
76213
|
+
":now": { N: Date.now().toString() },
|
|
76214
|
+
":s3Key": { S: s3Key }
|
|
75754
76215
|
}
|
|
75755
76216
|
});
|
|
75756
76217
|
await this.dynamoClient.send(updateCommand);
|
|
@@ -75799,7 +76260,7 @@ var S3CacheHandler = class {
|
|
|
75799
76260
|
TableName: this.config.tableName,
|
|
75800
76261
|
Key: {
|
|
75801
76262
|
pk: { S: this.config.buildId },
|
|
75802
|
-
sk: { S:
|
|
76263
|
+
sk: { S: this.tagMappingSortKey(tag, s3Key) }
|
|
75803
76264
|
}
|
|
75804
76265
|
})
|
|
75805
76266
|
);
|