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
|
@@ -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: createHash6, createPrivateKey, createPublicKey, sign: sign2 } = __require("node:crypto");
|
|
26829
26829
|
var { promises } = __require("node:fs");
|
|
26830
26830
|
var { homedir: homedir2 } = __require("node:os");
|
|
26831
26831
|
var { dirname, join: join5 } = __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 ?? join5(homedir2(), ".aws", "login", "cache");
|
|
26996
26996
|
const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
|
|
26997
|
-
const loginSessionSha256 =
|
|
26997
|
+
const loginSessionSha256 = createHash6("sha256").update(loginSessionBytes).digest("hex");
|
|
26998
26998
|
return join5(directory, `${loginSessionSha256}.json`);
|
|
26999
26999
|
}
|
|
27000
27000
|
derToRawSignature(derSignature) {
|
|
@@ -34169,7 +34169,16 @@ var import_client_dynamodb2 = __toESM(require_dist_cjs19());
|
|
|
34169
34169
|
var import_debug5 = __toESM(require_src());
|
|
34170
34170
|
|
|
34171
34171
|
// src/adapter/aws-cache-store.ts
|
|
34172
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
34172
34173
|
var import_client_dynamodb = __toESM(require_dist_cjs19());
|
|
34174
|
+
|
|
34175
|
+
// src/adapter/cache-utils.ts
|
|
34176
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
34177
|
+
function sha256Hex(value) {
|
|
34178
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
34179
|
+
}
|
|
34180
|
+
|
|
34181
|
+
// src/adapter/aws-cache-store.ts
|
|
34173
34182
|
function resolveAwsCacheConfig() {
|
|
34174
34183
|
return {
|
|
34175
34184
|
bucketName: process.env.CDK_NEXTJS_CACHE_BUCKET_NAME || "",
|
|
@@ -34232,6 +34241,14 @@ function markerOf(item) {
|
|
|
34232
34241
|
}
|
|
34233
34242
|
var BATCH_GET_MAX_KEYS = 100;
|
|
34234
34243
|
var DEFAULT_TAG_REFRESH_MS = 1e3;
|
|
34244
|
+
var TAG_REFRESH_GRACE_MS = 1e3;
|
|
34245
|
+
function hashedTag(tag) {
|
|
34246
|
+
return `#${sha256Hex(tag)}`;
|
|
34247
|
+
}
|
|
34248
|
+
var MAX_TAG_BYTES = 768;
|
|
34249
|
+
function sortKeyTag(tag) {
|
|
34250
|
+
return Buffer.byteLength(tag) <= MAX_TAG_BYTES ? tag : hashedTag(tag);
|
|
34251
|
+
}
|
|
34235
34252
|
var TagMarkerTable = class {
|
|
34236
34253
|
constructor(client, tableName, buildId) {
|
|
34237
34254
|
this.client = client;
|
|
@@ -34254,7 +34271,7 @@ var TagMarkerTable = class {
|
|
|
34254
34271
|
TableName: this.tableName,
|
|
34255
34272
|
Key: {
|
|
34256
34273
|
pk: { S: this.buildId },
|
|
34257
|
-
sk: { S: tag }
|
|
34274
|
+
sk: { S: sortKeyTag(tag) }
|
|
34258
34275
|
},
|
|
34259
34276
|
...markerUpdate(now, durations),
|
|
34260
34277
|
ReturnValues: "ALL_NEW"
|
|
@@ -34274,7 +34291,13 @@ var TagMarkerTable = class {
|
|
|
34274
34291
|
const unique = Array.from(new Set(tags));
|
|
34275
34292
|
const markers = /* @__PURE__ */ new Map();
|
|
34276
34293
|
for (let i5 = 0; i5 < unique.length; i5 += BATCH_GET_MAX_KEYS) {
|
|
34277
|
-
|
|
34294
|
+
const tagBySk = new Map(
|
|
34295
|
+
unique.slice(i5, i5 + BATCH_GET_MAX_KEYS).map((tag) => [sortKeyTag(tag), tag])
|
|
34296
|
+
);
|
|
34297
|
+
let keys = Array.from(
|
|
34298
|
+
tagBySk.keys(),
|
|
34299
|
+
(sk) => ({ pk: { S: buildId }, sk: { S: sk } })
|
|
34300
|
+
);
|
|
34278
34301
|
for (let attempt = 0; keys?.length && attempt < 3; attempt++) {
|
|
34279
34302
|
const response = await this.client.send(
|
|
34280
34303
|
new import_client_dynamodb.BatchGetItemCommand({
|
|
@@ -34287,7 +34310,7 @@ var TagMarkerTable = class {
|
|
|
34287
34310
|
})
|
|
34288
34311
|
);
|
|
34289
34312
|
for (const item of response.Responses?.[tableName] ?? []) {
|
|
34290
|
-
const tag = item.sk?.S;
|
|
34313
|
+
const tag = tagBySk.get(item.sk?.S ?? "");
|
|
34291
34314
|
if (tag !== void 0) {
|
|
34292
34315
|
markers.set(tag, markerOf(item));
|
|
34293
34316
|
}
|
|
@@ -34325,11 +34348,13 @@ var RevalidationLog = class {
|
|
|
34325
34348
|
* to {@link LOG_PUT_ATTEMPTS} times.
|
|
34326
34349
|
*/
|
|
34327
34350
|
async put(tag, at, marker) {
|
|
34351
|
+
const skTag = sortKeyTag(tag);
|
|
34328
34352
|
for (let attempt = 1; ; attempt++) {
|
|
34329
34353
|
const item = {
|
|
34330
34354
|
pk: { S: this.pk },
|
|
34331
|
-
sk: { S: `${logSkPrefix(at)}#${
|
|
34332
|
-
ttl: { N: String(Math.ceil((at + REVALIDATION_LOG_TTL_MS) / 1e3)) }
|
|
34355
|
+
sk: { S: `${logSkPrefix(at)}#${skTag}` },
|
|
34356
|
+
ttl: { N: String(Math.ceil((at + REVALIDATION_LOG_TTL_MS) / 1e3)) },
|
|
34357
|
+
...skTag !== tag && { longTag: { S: tag } }
|
|
34333
34358
|
};
|
|
34334
34359
|
for (const field of MARKER_FIELDS) {
|
|
34335
34360
|
const value = marker[field];
|
|
@@ -34374,7 +34399,7 @@ var RevalidationLog = class {
|
|
|
34374
34399
|
":pk": { S: this.pk },
|
|
34375
34400
|
":since": { S: logSkPrefix(Math.max(0, since)) }
|
|
34376
34401
|
},
|
|
34377
|
-
ProjectionExpression: "sk, revalidatedAt, staleAt, expiredAt",
|
|
34402
|
+
ProjectionExpression: "sk, longTag, revalidatedAt, staleAt, expiredAt",
|
|
34378
34403
|
ExclusiveStartKey: startKey
|
|
34379
34404
|
})
|
|
34380
34405
|
);
|
|
@@ -34386,7 +34411,7 @@ var RevalidationLog = class {
|
|
|
34386
34411
|
rows.push({
|
|
34387
34412
|
sk,
|
|
34388
34413
|
at: Number(sk.slice(0, LOG_SK_DIGITS)),
|
|
34389
|
-
tag: sk.slice(LOG_SK_DIGITS + 1),
|
|
34414
|
+
tag: item.longTag?.S ?? sk.slice(LOG_SK_DIGITS + 1),
|
|
34390
34415
|
marker: markerOf(item)
|
|
34391
34416
|
});
|
|
34392
34417
|
}
|
|
@@ -34430,10 +34455,64 @@ var TrackedTagMarkers = class {
|
|
|
34430
34455
|
* result rather than dropped.
|
|
34431
34456
|
*/
|
|
34432
34457
|
pendingRows = /* @__PURE__ */ new Map();
|
|
34458
|
+
/**
|
|
34459
|
+
* Log rows for tags not tracked and not being read, merged by tag, oldest
|
|
34460
|
+
* first: what the log has said about them since {@link completeSince}. Taken
|
|
34461
|
+
* into a tag's marker when it is read or written.
|
|
34462
|
+
*/
|
|
34463
|
+
logOnly = /* @__PURE__ */ new Map();
|
|
34464
|
+
/** See {@link completeSince}. */
|
|
34465
|
+
floor;
|
|
34466
|
+
/**
|
|
34467
|
+
* The future `expiredAt`s of markers dropped since, as `[from, to]`, in
|
|
34468
|
+
* order: once `from` is past, {@link completeSince} moves to `to`, and not
|
|
34469
|
+
* before, since only then does it expire anything. `from` and `to` are the
|
|
34470
|
+
* same until spans are merged for space ({@link laterFloor}).
|
|
34471
|
+
*/
|
|
34472
|
+
laterFloors = [];
|
|
34473
|
+
/** The in-memory entry stores judged by {@link completeSince}. */
|
|
34474
|
+
entryStores = [];
|
|
34475
|
+
/** How many times {@link forget} ran: a read started before one is made again. */
|
|
34476
|
+
forgotten = 0;
|
|
34433
34477
|
/** Tracked tags whose read failed, for the next refresh to read again. */
|
|
34434
34478
|
unread = /* @__PURE__ */ new Set();
|
|
34479
|
+
/**
|
|
34480
|
+
* Set inside a page render {@link catchUp} let go, for that render alone.
|
|
34481
|
+
* See {@link behindInRender}.
|
|
34482
|
+
*/
|
|
34483
|
+
caughtUpRender = new AsyncLocalStorage();
|
|
34484
|
+
/** When `sync` last scanned the entry stores ({@link oldestEntry}). */
|
|
34485
|
+
lastEntryScan = -Infinity;
|
|
34486
|
+
/**
|
|
34487
|
+
* No `logOnly` row is known by it or earlier, so a {@link pruneLogged}
|
|
34488
|
+
* against it or earlier drops nothing: a marker is only ever known by later,
|
|
34489
|
+
* as its `expiredAt` passes.
|
|
34490
|
+
*/
|
|
34491
|
+
prunedAgainst = -Infinity;
|
|
34492
|
+
/** When the last refresh started, or was skipped. */
|
|
34435
34493
|
lastRefresh = -Infinity;
|
|
34494
|
+
/**
|
|
34495
|
+
* The refresh in flight. One left open across a freeze comes back after the
|
|
34496
|
+
* thaw with what it read before, so {@link refresh} waits for it and then
|
|
34497
|
+
* for a fresh one, rather than the runtime waiting for it before the freeze:
|
|
34498
|
+
* that would bill every Lambda invocation for the query's tail.
|
|
34499
|
+
*/
|
|
34436
34500
|
refreshing;
|
|
34501
|
+
/**
|
|
34502
|
+
* When {@link refreshing} last showed the process running: a timer ticks
|
|
34503
|
+
* while it is in flight, and a tick that comes late, or none at all, means
|
|
34504
|
+
* the process was paused - frozen - since it started.
|
|
34505
|
+
*/
|
|
34506
|
+
lastTick = -Infinity;
|
|
34507
|
+
/** Whether a tick came late since {@link refreshing} started. */
|
|
34508
|
+
pausedWhileRefreshing = false;
|
|
34509
|
+
ticker;
|
|
34510
|
+
canFreeze;
|
|
34511
|
+
/**
|
|
34512
|
+
* When the last refresh settled, either way, unless it spanned a pause: what
|
|
34513
|
+
* it knows is from then. See {@link behind}.
|
|
34514
|
+
*/
|
|
34515
|
+
settledAt;
|
|
34437
34516
|
/**
|
|
34438
34517
|
* Where the next log query starts (`clock()`): the last successful one's
|
|
34439
34518
|
* start, less {@link REVALIDATION_LOG_LOOKBACK_MS}.
|
|
@@ -34455,20 +34534,68 @@ var TrackedTagMarkers = class {
|
|
|
34455
34534
|
this.random = options.random ?? Math.random;
|
|
34456
34535
|
this.debug = options.debug ?? (() => {
|
|
34457
34536
|
});
|
|
34537
|
+
this.canFreeze = options.canFreeze ?? false;
|
|
34458
34538
|
const at = this.clock();
|
|
34459
34539
|
this.cursor = at - REVALIDATION_LOG_LOOKBACK_MS;
|
|
34460
34540
|
this.lastLogRead = at;
|
|
34541
|
+
this.settledAt = at;
|
|
34542
|
+
this.floor = markerClock();
|
|
34461
34543
|
}
|
|
34462
34544
|
/** `tag`'s marker as this instance knows it, if it tracks `tag`. */
|
|
34463
34545
|
get(tag) {
|
|
34464
34546
|
return this.tags.get(tag);
|
|
34465
34547
|
}
|
|
34548
|
+
/**
|
|
34549
|
+
* Since when (`markerClock()`) this instance knows of every revalidation of
|
|
34550
|
+
* every tag, tracked or not: tracked ones from their markers and the log,
|
|
34551
|
+
* the others from the log alone (`logOnly`). So an entry created at or after
|
|
34552
|
+
* it can be judged by {@link state} and {@link expiration} without
|
|
34553
|
+
* {@link ensure} reading anything - which matters inside a `'use cache'`
|
|
34554
|
+
* lookup, where Next.js waits for the answer and a read pushes the entry out
|
|
34555
|
+
* of the static stage of a staged render.
|
|
34556
|
+
*
|
|
34557
|
+
* Starts at construction: the log is followed from before it. Moves to now
|
|
34558
|
+
* when the tracked markers are forgotten, and past the revalidations of
|
|
34559
|
+
* whatever else it lets go of - a marker evicted for space, a log row
|
|
34560
|
+
* pruned - which are no longer known: to its `expiredAt` only once that is
|
|
34561
|
+
* past, since a profile's `expire` can be a year out, and moving there at
|
|
34562
|
+
* once would turn the read-free path off for the instance's life.
|
|
34563
|
+
*
|
|
34564
|
+
* A row is pruned only once it is no later than every entry held, so that
|
|
34565
|
+
* costs those entries nothing. An entry stored afterwards with an older
|
|
34566
|
+
* `timestamp` - Next.js stamps the start of its generation, which can
|
|
34567
|
+
* outlast a lookback - is older than this too, and is read first.
|
|
34568
|
+
*
|
|
34569
|
+
* It does not cover a revalidation older than the log's first lookback
|
|
34570
|
+
* whose `expiredAt` is still to come, which would expire an entry created
|
|
34571
|
+
* after it. So the `'use cache'` handler reads an entry's tags in the
|
|
34572
|
+
* background as it stores it.
|
|
34573
|
+
*/
|
|
34574
|
+
get completeSince() {
|
|
34575
|
+
const at = markerClock();
|
|
34576
|
+
while (this.laterFloors.length > 0 && this.laterFloors[0][0] <= at) {
|
|
34577
|
+
this.floor = Math.max(this.floor, this.laterFloors.shift()[1]);
|
|
34578
|
+
}
|
|
34579
|
+
return this.floor;
|
|
34580
|
+
}
|
|
34581
|
+
/**
|
|
34582
|
+
* Register an in-memory store whose entries are judged by
|
|
34583
|
+
* {@link completeSince}, by the `timestamp` of the oldest entry it holds:
|
|
34584
|
+
* `logOnly` keeps the rows that could still apply to one of them, and no
|
|
34585
|
+
* others. With none registered it keeps nothing. Asked with `scan` false,
|
|
34586
|
+
* `oldestTimestamp` may answer early, but never late, without a scan.
|
|
34587
|
+
*/
|
|
34588
|
+
judgeEntriesOf(oldestTimestamp, holdsAny = () => oldestTimestamp(false) < Infinity) {
|
|
34589
|
+
this.entryStores.push({ oldestTimestamp, holdsAny });
|
|
34590
|
+
}
|
|
34466
34591
|
/**
|
|
34467
34592
|
* Track `tag` with `marker`: what this instance just wrote, which it knows
|
|
34468
34593
|
* without reading it back.
|
|
34469
34594
|
*/
|
|
34470
34595
|
set(tag, marker) {
|
|
34471
|
-
this.
|
|
34596
|
+
const logged = this.logOnly.get(tag);
|
|
34597
|
+
this.logOnly.delete(tag);
|
|
34598
|
+
this.remember(tag, mergeMarkers(marker, logged));
|
|
34472
34599
|
this.known(tag, this.clock());
|
|
34473
34600
|
}
|
|
34474
34601
|
/**
|
|
@@ -34521,27 +34648,103 @@ var TrackedTagMarkers = class {
|
|
|
34521
34648
|
const unread = [];
|
|
34522
34649
|
for (const tag of new Set(tags)) {
|
|
34523
34650
|
const inFlight = this.reading.get(tag);
|
|
34524
|
-
if (
|
|
34525
|
-
waits.push(inFlight);
|
|
34526
|
-
} else if (this.tags.has(tag)) {
|
|
34651
|
+
if (this.tags.has(tag)) {
|
|
34527
34652
|
this.touch(tag);
|
|
34653
|
+
} else if (inFlight) {
|
|
34654
|
+
waits.push(inFlight);
|
|
34528
34655
|
} else {
|
|
34529
34656
|
unread.push(tag);
|
|
34530
34657
|
}
|
|
34531
34658
|
}
|
|
34532
34659
|
if (unread.length > 0) {
|
|
34533
|
-
|
|
34534
|
-
for (const tag of unread) {
|
|
34535
|
-
this.reading.delete(tag);
|
|
34536
|
-
}
|
|
34537
|
-
});
|
|
34538
|
-
for (const tag of unread) {
|
|
34539
|
-
this.reading.set(tag, read);
|
|
34540
|
-
}
|
|
34541
|
-
waits.push(read);
|
|
34660
|
+
waits.push(this.startRead(unread));
|
|
34542
34661
|
}
|
|
34543
34662
|
await Promise.all(waits);
|
|
34544
34663
|
}
|
|
34664
|
+
/**
|
|
34665
|
+
* {@link readInto} `tags`, as the read of each that whoever needs one of
|
|
34666
|
+
* them next waits on ({@link reading}). Never rejects: a failed read leaves
|
|
34667
|
+
* the tags tracked.
|
|
34668
|
+
*/
|
|
34669
|
+
startRead(tags) {
|
|
34670
|
+
const read = this.readInto(tags).then(() => {
|
|
34671
|
+
for (const tag of tags) {
|
|
34672
|
+
this.reading.delete(tag);
|
|
34673
|
+
}
|
|
34674
|
+
});
|
|
34675
|
+
for (const tag of tags) {
|
|
34676
|
+
this.reading.set(tag, read);
|
|
34677
|
+
}
|
|
34678
|
+
return read;
|
|
34679
|
+
}
|
|
34680
|
+
/**
|
|
34681
|
+
* Whether the last refresh settled more than `refreshIntervalMs` plus
|
|
34682
|
+
* {@link TAG_REFRESH_GRACE_MS} ago - one that spanned a freeze counts from
|
|
34683
|
+
* when it started: after the instance sat idle or frozen - or always, with
|
|
34684
|
+
* a `refreshIntervalMs` of `0`. Answering from what it knows then could
|
|
34685
|
+
* serve an entry revalidated elsewhere since, however long ago that was, so
|
|
34686
|
+
* `refreshTags` waits for the refresh, and the runtime waits for it before
|
|
34687
|
+
* a page render starts ({@link catchUp}).
|
|
34688
|
+
*
|
|
34689
|
+
* From when it settled, not when it started, so a slow query the runtime
|
|
34690
|
+
* waited for before the render does not leave the instance behind inside
|
|
34691
|
+
* it. A failed one counts too, so one request does not wait for two: the
|
|
34692
|
+
* same "assume the entry is valid" a failed read answers with, until the
|
|
34693
|
+
* next refresh, at most the interval and the grace later.
|
|
34694
|
+
*/
|
|
34695
|
+
get behind() {
|
|
34696
|
+
return this.log !== void 0 && // `0` asks before every check, so every check waits for the answer.
|
|
34697
|
+
(this.refreshIntervalMs === 0 || this.overdue(this.settledAt));
|
|
34698
|
+
}
|
|
34699
|
+
/**
|
|
34700
|
+
* {@link behind}, as `refreshTags` asks it: never inside a page's render
|
|
34701
|
+
* {@link catchUp} caught up, since the runtime settled it for that request
|
|
34702
|
+
* before the render started, and a wait inside a `'use cache'` lookup is
|
|
34703
|
+
* what it settled it to avoid - a render crossing the line partway through
|
|
34704
|
+
* would otherwise wait after all. So a revalidation elsewhere can go unseen
|
|
34705
|
+
* for a render's duration on top of the usual window.
|
|
34706
|
+
*
|
|
34707
|
+
* A route handler, which no catch-up precedes, waits at the plain line.
|
|
34708
|
+
*/
|
|
34709
|
+
get behindInRender() {
|
|
34710
|
+
return this.refreshIntervalMs !== 0 && this.caughtUpRender.getStore() ? false : this.behind;
|
|
34711
|
+
}
|
|
34712
|
+
/**
|
|
34713
|
+
* Whether a registered store ({@link judgeEntriesOf}) holds any entry: one
|
|
34714
|
+
* a refresh could expire. With none there is nothing to wait for even when
|
|
34715
|
+
* the instance is {@link behind}, since whatever is stored from here on is
|
|
34716
|
+
* newer than what the log would say - which covers the first `'use cache'`
|
|
34717
|
+
* call of a process, which creates the handler inside the render.
|
|
34718
|
+
*/
|
|
34719
|
+
get holdsEntries() {
|
|
34720
|
+
return this.entryStores.some((store) => store.holdsAny());
|
|
34721
|
+
}
|
|
34722
|
+
/**
|
|
34723
|
+
* {@link refresh}, waited for when the instance is {@link behind}. The
|
|
34724
|
+
* runtime calls it before handing a page request to Next.js, outside the
|
|
34725
|
+
* staged render whose static stage a wait inside a `'use cache'` lookup
|
|
34726
|
+
* would cut short. Only for the `'use cache'` handler's entries
|
|
34727
|
+
* ({@link judgeEntriesOf}): the other caches wait for the refresh where they
|
|
34728
|
+
* read anyway. Not with a `refreshIntervalMs` of `0` either, where
|
|
34729
|
+
* `refreshTags` asks again regardless. Then `render`, marked as caught up
|
|
34730
|
+
* for ({@link behindInRender}). Rejects only as `render` does.
|
|
34731
|
+
*/
|
|
34732
|
+
async catchUp(render) {
|
|
34733
|
+
const log = (error2) => console.error("Error refreshing cache tags:", error2);
|
|
34734
|
+
if (this.refreshIntervalMs > 0 && this.behind) {
|
|
34735
|
+
if (!this.holdsEntries) {
|
|
34736
|
+
return render();
|
|
34737
|
+
}
|
|
34738
|
+
await this.refresh().catch(log);
|
|
34739
|
+
} else if (this.refreshIntervalMs > 0) {
|
|
34740
|
+
void this.refresh().catch(log);
|
|
34741
|
+
}
|
|
34742
|
+
return this.caughtUpRender.run(true, render);
|
|
34743
|
+
}
|
|
34744
|
+
/** Whether `at` is more than the interval and the grace ago. */
|
|
34745
|
+
overdue(at) {
|
|
34746
|
+
return this.clock() - at > this.refreshIntervalMs + TAG_REFRESH_GRACE_MS;
|
|
34747
|
+
}
|
|
34545
34748
|
/**
|
|
34546
34749
|
* Catch up on revalidations other instances ran, at most once per
|
|
34547
34750
|
* `refreshIntervalMs`. See {@link TrackedTagMarkers} for the cost.
|
|
@@ -34552,6 +34755,9 @@ var TrackedTagMarkers = class {
|
|
|
34552
34755
|
return;
|
|
34553
34756
|
}
|
|
34554
34757
|
if (this.refreshing) {
|
|
34758
|
+
if (this.behind && this.pausedSinceRefreshStarted()) {
|
|
34759
|
+
return this.refreshing.then(() => this.refresh());
|
|
34760
|
+
}
|
|
34555
34761
|
return this.refreshing;
|
|
34556
34762
|
}
|
|
34557
34763
|
const at = this.clock();
|
|
@@ -34559,14 +34765,47 @@ var TrackedTagMarkers = class {
|
|
|
34559
34765
|
return;
|
|
34560
34766
|
}
|
|
34561
34767
|
this.lastRefresh = at;
|
|
34562
|
-
if (this.tags.size === 0) {
|
|
34768
|
+
if (this.tags.size === 0 && this.entryStores.length === 0) {
|
|
34563
34769
|
return;
|
|
34564
34770
|
}
|
|
34771
|
+
this.startTicking();
|
|
34565
34772
|
this.refreshing = this.sync(at, log).finally(() => {
|
|
34773
|
+
this.settledAt = Math.max(
|
|
34774
|
+
this.settledAt,
|
|
34775
|
+
this.pausedSinceRefreshStarted() ? this.lastRefresh : this.clock()
|
|
34776
|
+
);
|
|
34777
|
+
this.stopTicking();
|
|
34566
34778
|
this.refreshing = void 0;
|
|
34567
34779
|
});
|
|
34568
34780
|
return this.refreshing;
|
|
34569
34781
|
}
|
|
34782
|
+
startTicking() {
|
|
34783
|
+
if (!this.canFreeze) {
|
|
34784
|
+
return;
|
|
34785
|
+
}
|
|
34786
|
+
this.lastTick = this.clock();
|
|
34787
|
+
this.pausedWhileRefreshing = false;
|
|
34788
|
+
this.ticker = setInterval(() => {
|
|
34789
|
+
const at = this.clock();
|
|
34790
|
+
if (at - this.lastTick > REFRESH_PAUSE_MS) {
|
|
34791
|
+
this.pausedWhileRefreshing = true;
|
|
34792
|
+
}
|
|
34793
|
+
this.lastTick = at;
|
|
34794
|
+
}, REFRESH_TICK_MS);
|
|
34795
|
+
this.ticker.unref?.();
|
|
34796
|
+
}
|
|
34797
|
+
stopTicking() {
|
|
34798
|
+
clearInterval(this.ticker);
|
|
34799
|
+
this.ticker = void 0;
|
|
34800
|
+
}
|
|
34801
|
+
/**
|
|
34802
|
+
* Whether the process was paused since {@link refreshing} started: a tick
|
|
34803
|
+
* came late, or the next one is overdue (after a thaw, a request can run
|
|
34804
|
+
* before the timer does).
|
|
34805
|
+
*/
|
|
34806
|
+
pausedSinceRefreshStarted() {
|
|
34807
|
+
return this.canFreeze && (this.pausedWhileRefreshing || this.clock() - this.lastTick > REFRESH_PAUSE_MS);
|
|
34808
|
+
}
|
|
34570
34809
|
/**
|
|
34571
34810
|
* Record a revalidation of `tags`: here at once, and in the revalidation
|
|
34572
34811
|
* table for every other instance - the marker row plus a log row per tag.
|
|
@@ -34599,7 +34838,7 @@ var TrackedTagMarkers = class {
|
|
|
34599
34838
|
const at = markerClock();
|
|
34600
34839
|
let state2 = "fresh";
|
|
34601
34840
|
for (const tag of tags) {
|
|
34602
|
-
const marker = this.
|
|
34841
|
+
const marker = this.knownMarker(tag);
|
|
34603
34842
|
if (!marker) {
|
|
34604
34843
|
continue;
|
|
34605
34844
|
}
|
|
@@ -34623,7 +34862,7 @@ var TrackedTagMarkers = class {
|
|
|
34623
34862
|
const at = markerClock();
|
|
34624
34863
|
let latest = 0;
|
|
34625
34864
|
for (const tag of tags) {
|
|
34626
|
-
const marker = this.
|
|
34865
|
+
const marker = this.knownMarker(tag);
|
|
34627
34866
|
if (!marker) {
|
|
34628
34867
|
continue;
|
|
34629
34868
|
}
|
|
@@ -34710,13 +34949,14 @@ var TrackedTagMarkers = class {
|
|
|
34710
34949
|
);
|
|
34711
34950
|
return;
|
|
34712
34951
|
}
|
|
34713
|
-
const
|
|
34714
|
-
|
|
34715
|
-
|
|
34716
|
-
|
|
34717
|
-
|
|
34718
|
-
|
|
34719
|
-
|
|
34952
|
+
const due = this.due(at);
|
|
34953
|
+
if (due.length > 0) {
|
|
34954
|
+
void this.startRead(due);
|
|
34955
|
+
}
|
|
34956
|
+
const result = await log.query(this.cursor).catch((error2) => {
|
|
34957
|
+
console.error("Error reading tag revalidation log:", error2);
|
|
34958
|
+
return void 0;
|
|
34959
|
+
});
|
|
34720
34960
|
if (!result) {
|
|
34721
34961
|
return;
|
|
34722
34962
|
}
|
|
@@ -34727,6 +34967,11 @@ var TrackedTagMarkers = class {
|
|
|
34727
34967
|
);
|
|
34728
34968
|
return;
|
|
34729
34969
|
}
|
|
34970
|
+
const scan = at - this.lastEntryScan > ENTRY_SCAN_INTERVAL_MS;
|
|
34971
|
+
if (scan) {
|
|
34972
|
+
this.lastEntryScan = at;
|
|
34973
|
+
}
|
|
34974
|
+
const oldest = this.oldestEntry(scan);
|
|
34730
34975
|
let applied = 0;
|
|
34731
34976
|
for (const row of result.rows) {
|
|
34732
34977
|
if (this.applied.has(row.sk)) {
|
|
@@ -34741,11 +34986,20 @@ var TrackedTagMarkers = class {
|
|
|
34741
34986
|
mergeMarkers(this.pendingRows.get(row.tag), row.marker)
|
|
34742
34987
|
);
|
|
34743
34988
|
} else {
|
|
34989
|
+
this.rememberLogged(
|
|
34990
|
+
row.tag,
|
|
34991
|
+
mergeMarkers(this.logOnly.get(row.tag), row.marker),
|
|
34992
|
+
oldest
|
|
34993
|
+
);
|
|
34744
34994
|
continue;
|
|
34745
34995
|
}
|
|
34746
34996
|
this.applied.set(row.sk, row.at);
|
|
34747
34997
|
applied++;
|
|
34748
34998
|
}
|
|
34999
|
+
if (oldest > this.prunedAgainst) {
|
|
35000
|
+
this.pruneLogged(oldest);
|
|
35001
|
+
}
|
|
35002
|
+
this.prunedAgainst = oldest;
|
|
34749
35003
|
this.lastLogRead = at;
|
|
34750
35004
|
this.advance(at - REVALIDATION_LOG_LOOKBACK_MS);
|
|
34751
35005
|
this.debug(
|
|
@@ -34761,14 +35015,16 @@ var TrackedTagMarkers = class {
|
|
|
34761
35015
|
const due = [];
|
|
34762
35016
|
for (const tag of this.unread) {
|
|
34763
35017
|
if (due.length >= BATCH_GET_MAX_KEYS) return due;
|
|
34764
|
-
|
|
35018
|
+
if (!this.reading.has(tag)) {
|
|
35019
|
+
due.push(tag);
|
|
35020
|
+
}
|
|
34765
35021
|
}
|
|
34766
35022
|
const jitter = this.resyncIntervalMs * TAG_RESYNC_JITTER;
|
|
34767
35023
|
for (const [tag, dueAt] of this.dueAt) {
|
|
34768
35024
|
if (due.length >= BATCH_GET_MAX_KEYS || dueAt - jitter > at) {
|
|
34769
35025
|
break;
|
|
34770
35026
|
}
|
|
34771
|
-
if (dueAt <= at && !this.unread.has(tag)) {
|
|
35027
|
+
if (dueAt <= at && !this.unread.has(tag) && !this.reading.has(tag)) {
|
|
34772
35028
|
due.push(tag);
|
|
34773
35029
|
}
|
|
34774
35030
|
}
|
|
@@ -34781,10 +35037,19 @@ var TrackedTagMarkers = class {
|
|
|
34781
35037
|
*/
|
|
34782
35038
|
forget(at, why) {
|
|
34783
35039
|
this.debug(`forgetting ${this.tags.size} tracked tags: ${why}`);
|
|
35040
|
+
this.forgotten++;
|
|
35041
|
+
for (const marker of this.tags.values()) {
|
|
35042
|
+
this.raiseFloor(marker);
|
|
35043
|
+
}
|
|
35044
|
+
for (const marker of this.logOnly.values()) {
|
|
35045
|
+
this.raiseFloor(marker);
|
|
35046
|
+
}
|
|
34784
35047
|
this.tags.clear();
|
|
34785
35048
|
this.dueAt.clear();
|
|
34786
35049
|
this.unread.clear();
|
|
34787
35050
|
this.applied.clear();
|
|
35051
|
+
this.logOnly.clear();
|
|
35052
|
+
this.floor = Math.max(this.floor, markerClock());
|
|
34788
35053
|
this.cursor = at - REVALIDATION_LOG_LOOKBACK_MS;
|
|
34789
35054
|
this.lastLogRead = at;
|
|
34790
35055
|
}
|
|
@@ -34810,13 +35075,17 @@ var TrackedTagMarkers = class {
|
|
|
34810
35075
|
* always answered with, rather than a read on every request while DynamoDB
|
|
34811
35076
|
* is unavailable.
|
|
34812
35077
|
*/
|
|
34813
|
-
async readInto(tags) {
|
|
35078
|
+
async readInto(tags, again = false) {
|
|
34814
35079
|
if (tags.length === 0) {
|
|
34815
35080
|
return true;
|
|
34816
35081
|
}
|
|
34817
35082
|
const at = this.clock();
|
|
35083
|
+
const forgotten = this.forgotten;
|
|
34818
35084
|
try {
|
|
34819
35085
|
const read = await this.markers.read(tags);
|
|
35086
|
+
if (this.forgotten !== forgotten && !again) {
|
|
35087
|
+
return await this.readInto(tags, true);
|
|
35088
|
+
}
|
|
34820
35089
|
for (const tag of tags) {
|
|
34821
35090
|
this.unread.delete(tag);
|
|
34822
35091
|
this.remember(
|
|
@@ -34832,6 +35101,9 @@ var TrackedTagMarkers = class {
|
|
|
34832
35101
|
return true;
|
|
34833
35102
|
} catch (error2) {
|
|
34834
35103
|
console.error("Error reading tag markers:", error2);
|
|
35104
|
+
if (this.forgotten !== forgotten && !again) {
|
|
35105
|
+
return this.readInto(tags, true);
|
|
35106
|
+
}
|
|
34835
35107
|
this.trackUnread(tags);
|
|
34836
35108
|
for (const tag of tags) {
|
|
34837
35109
|
this.unread.add(tag);
|
|
@@ -34845,8 +35117,119 @@ var TrackedTagMarkers = class {
|
|
|
34845
35117
|
}
|
|
34846
35118
|
takePendingRows(tag) {
|
|
34847
35119
|
const pending = this.pendingRows.get(tag);
|
|
35120
|
+
const logged = this.logOnly.get(tag);
|
|
34848
35121
|
this.pendingRows.delete(tag);
|
|
34849
|
-
|
|
35122
|
+
this.logOnly.delete(tag);
|
|
35123
|
+
return logged ? mergeMarkers(logged, pending) : pending;
|
|
35124
|
+
}
|
|
35125
|
+
/**
|
|
35126
|
+
* What this instance knows of `tag`: tracked, or from the log alone -
|
|
35127
|
+
* including rows that arrived while its first read is in flight.
|
|
35128
|
+
*/
|
|
35129
|
+
knownMarker(tag) {
|
|
35130
|
+
const tracked = this.tags.get(tag);
|
|
35131
|
+
if (tracked) {
|
|
35132
|
+
return tracked;
|
|
35133
|
+
}
|
|
35134
|
+
const logged = this.logOnly.get(tag);
|
|
35135
|
+
const pending = this.pendingRows.get(tag);
|
|
35136
|
+
return logged || pending ? mergeMarkers(logged, pending) : void 0;
|
|
35137
|
+
}
|
|
35138
|
+
/**
|
|
35139
|
+
* Keep `tag`'s log rows in `logOnly`, unless they cannot apply to an entry
|
|
35140
|
+
* held, `oldest` or newer (see {@link pruneLogged}), as bounded as the
|
|
35141
|
+
* tracked tags. Either way a row let go of takes what it knew with it, so
|
|
35142
|
+
* `completeSince` moves past it.
|
|
35143
|
+
*/
|
|
35144
|
+
rememberLogged(tag, marker, oldest) {
|
|
35145
|
+
this.logOnly.delete(tag);
|
|
35146
|
+
if (knownBy(marker, markerClock()) <= oldest) {
|
|
35147
|
+
this.raiseFloor(marker);
|
|
35148
|
+
return;
|
|
35149
|
+
}
|
|
35150
|
+
this.logOnly.set(tag, marker);
|
|
35151
|
+
for (const [first, dropped] of this.logOnly) {
|
|
35152
|
+
if (this.logOnly.size <= this.maxTrackedTags) {
|
|
35153
|
+
break;
|
|
35154
|
+
}
|
|
35155
|
+
this.logOnly.delete(first);
|
|
35156
|
+
this.raiseFloor(dropped);
|
|
35157
|
+
}
|
|
35158
|
+
}
|
|
35159
|
+
/**
|
|
35160
|
+
* Drop the `logOnly` rows that cannot apply to an entry held, `oldest` or
|
|
35161
|
+
* newer: every revalidation they name so far is no later than it. That
|
|
35162
|
+
* moves `completeSince` no further than `oldest`, which costs the entries
|
|
35163
|
+
* held nothing, and a profile's `expiredAt` still to come waits in
|
|
35164
|
+
* `laterFloors`. Without this, a deployment revalidating many distinct tags
|
|
35165
|
+
* filled `logOnly` with tags no entry here carries, and the evictions past
|
|
35166
|
+
* its bound kept moving `completeSince` up to the present.
|
|
35167
|
+
*/
|
|
35168
|
+
pruneLogged(oldest) {
|
|
35169
|
+
const at = markerClock();
|
|
35170
|
+
for (const [tag, marker] of this.logOnly) {
|
|
35171
|
+
if (knownBy(marker, at) <= oldest) {
|
|
35172
|
+
this.logOnly.delete(tag);
|
|
35173
|
+
this.raiseFloor(marker);
|
|
35174
|
+
}
|
|
35175
|
+
}
|
|
35176
|
+
}
|
|
35177
|
+
/**
|
|
35178
|
+
* The oldest entry timestamp of any registered store, `Infinity` with none:
|
|
35179
|
+
* early, never late, unless `scan`.
|
|
35180
|
+
*/
|
|
35181
|
+
oldestEntry(scan) {
|
|
35182
|
+
let oldest = Infinity;
|
|
35183
|
+
for (const { oldestTimestamp } of this.entryStores) {
|
|
35184
|
+
oldest = Math.min(oldest, oldestTimestamp(scan));
|
|
35185
|
+
}
|
|
35186
|
+
return oldest;
|
|
35187
|
+
}
|
|
35188
|
+
/**
|
|
35189
|
+
* Move `completeSince` past `marker`'s revalidations: what is no longer
|
|
35190
|
+
* known about a tag once its marker is dropped. A future `expiredAt` waits
|
|
35191
|
+
* in `laterFloors` until it is past.
|
|
35192
|
+
*/
|
|
35193
|
+
raiseFloor(marker) {
|
|
35194
|
+
const at = markerClock();
|
|
35195
|
+
this.floor = Math.max(this.floor, knownBy(marker, at));
|
|
35196
|
+
const { expiredAt } = marker;
|
|
35197
|
+
if (expiredAt !== void 0 && expiredAt > Math.max(at, this.floor)) {
|
|
35198
|
+
this.laterFloor(expiredAt);
|
|
35199
|
+
}
|
|
35200
|
+
}
|
|
35201
|
+
/**
|
|
35202
|
+
* Add `time` to `laterFloors`, in order. Past the tracked-tag bound the two
|
|
35203
|
+
* spans closest together are merged: from the first's start,
|
|
35204
|
+
* `completeSince` is the second's end, early - which only costs reads, and
|
|
35205
|
+
* for as short a time as any merge allows. Never later than its time, which
|
|
35206
|
+
* would let an entry expire unseen.
|
|
35207
|
+
*/
|
|
35208
|
+
laterFloor(time2) {
|
|
35209
|
+
const floors = this.laterFloors;
|
|
35210
|
+
let low = 0;
|
|
35211
|
+
let high = floors.length;
|
|
35212
|
+
while (low < high) {
|
|
35213
|
+
const mid = Math.floor((low + high) / 2);
|
|
35214
|
+
if (floors[mid][0] < time2) {
|
|
35215
|
+
low = mid + 1;
|
|
35216
|
+
} else {
|
|
35217
|
+
high = mid;
|
|
35218
|
+
}
|
|
35219
|
+
}
|
|
35220
|
+
if (low > 0 && floors[low - 1][1] >= time2 || floors[low]?.[0] === time2) {
|
|
35221
|
+
return;
|
|
35222
|
+
}
|
|
35223
|
+
floors.splice(low, 0, [time2, time2]);
|
|
35224
|
+
if (floors.length > this.maxTrackedTags) {
|
|
35225
|
+
let merge2 = 0;
|
|
35226
|
+
for (let i5 = 1; i5 < floors.length - 1; i5++) {
|
|
35227
|
+
if (floors[i5 + 1][1] - floors[i5][0] < floors[merge2 + 1][1] - floors[merge2][0]) {
|
|
35228
|
+
merge2 = i5;
|
|
35229
|
+
}
|
|
35230
|
+
}
|
|
35231
|
+
floors.splice(merge2, 2, [floors[merge2][0], floors[merge2 + 1][1]]);
|
|
35232
|
+
}
|
|
34850
35233
|
}
|
|
34851
35234
|
/** Record that `tag`'s marker was known from the table at `at`. */
|
|
34852
35235
|
known(tag, at) {
|
|
@@ -34863,6 +35246,7 @@ var TrackedTagMarkers = class {
|
|
|
34863
35246
|
if (this.tags.size <= this.maxTrackedTags) {
|
|
34864
35247
|
break;
|
|
34865
35248
|
}
|
|
35249
|
+
this.raiseFloor(this.tags.get(oldest));
|
|
34866
35250
|
this.tags.delete(oldest);
|
|
34867
35251
|
this.dueAt.delete(oldest);
|
|
34868
35252
|
this.unread.delete(oldest);
|
|
@@ -34875,7 +35259,18 @@ var TrackedTagMarkers = class {
|
|
|
34875
35259
|
}
|
|
34876
35260
|
}
|
|
34877
35261
|
};
|
|
35262
|
+
function knownBy(marker, at) {
|
|
35263
|
+
const { revalidatedAt, staleAt, expiredAt } = marker;
|
|
35264
|
+
return Math.max(
|
|
35265
|
+
revalidatedAt ?? -Infinity,
|
|
35266
|
+
staleAt ?? -Infinity,
|
|
35267
|
+
expiredAt !== void 0 && expiredAt <= at ? expiredAt : -Infinity
|
|
35268
|
+
);
|
|
35269
|
+
}
|
|
34878
35270
|
var MAX_LOG_PUT_MS = REVALIDATION_LOG_LOOKBACK_MS / 2;
|
|
35271
|
+
var ENTRY_SCAN_INTERVAL_MS = 6e4;
|
|
35272
|
+
var REFRESH_TICK_MS = 250;
|
|
35273
|
+
var REFRESH_PAUSE_MS = 1e3;
|
|
34879
35274
|
function mergeMarkers(local, remote) {
|
|
34880
35275
|
if (!local) {
|
|
34881
35276
|
return remote ?? {};
|
|
@@ -34895,6 +35290,11 @@ function maxDefined(a5, b5) {
|
|
|
34895
35290
|
return Math.max(a5, b5);
|
|
34896
35291
|
}
|
|
34897
35292
|
|
|
35293
|
+
// src/runtime/tag-manifest.ts
|
|
35294
|
+
var TAG_MANIFEST_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
35295
|
+
"cdk-nextjs.use-cache.tag-manifest"
|
|
35296
|
+
);
|
|
35297
|
+
|
|
34898
35298
|
// src/adapter/use-cache-common.ts
|
|
34899
35299
|
function isBuildPhase() {
|
|
34900
35300
|
return process.env.NEXT_PHASE === "phase-production-build";
|
|
@@ -34976,6 +35376,12 @@ var EntryLru = class {
|
|
|
34976
35376
|
maxBytes;
|
|
34977
35377
|
entries = /* @__PURE__ */ new Map();
|
|
34978
35378
|
bytes = 0;
|
|
35379
|
+
/**
|
|
35380
|
+
* No later than the oldest entry's `timestamp`: the last scan's answer, less
|
|
35381
|
+
* whatever was set since. Entries dropped since only make it early. See
|
|
35382
|
+
* {@link oldestBound}.
|
|
35383
|
+
*/
|
|
35384
|
+
lowest = Infinity;
|
|
34979
35385
|
get(key) {
|
|
34980
35386
|
const entry = this.entries.get(key);
|
|
34981
35387
|
if (entry) {
|
|
@@ -34992,6 +35398,7 @@ var EntryLru = class {
|
|
|
34992
35398
|
}
|
|
34993
35399
|
this.entries.set(key, entry);
|
|
34994
35400
|
this.bytes += size;
|
|
35401
|
+
this.lowest = Math.min(this.lowest, entry.timestamp);
|
|
34995
35402
|
for (const [oldestKey, oldest] of this.entries) {
|
|
34996
35403
|
if (this.bytes <= this.maxBytes) {
|
|
34997
35404
|
break;
|
|
@@ -35005,7 +35412,58 @@ var EntryLru = class {
|
|
|
35005
35412
|
if (existing) {
|
|
35006
35413
|
this.entries.delete(key);
|
|
35007
35414
|
this.bytes -= sizeOf2(key, existing);
|
|
35415
|
+
if (this.entries.size === 0) {
|
|
35416
|
+
this.lowest = Infinity;
|
|
35417
|
+
}
|
|
35418
|
+
}
|
|
35419
|
+
}
|
|
35420
|
+
/**
|
|
35421
|
+
* The `timestamp` of the oldest entry held, `Infinity` with none, dropping
|
|
35422
|
+
* on the way the entries past `revalidate` at `at` (`now()`): a handler
|
|
35423
|
+
* that drops them when next read would otherwise hold them until then, and
|
|
35424
|
+
* they would count here meanwhile. A scan, so asked once in a while: in
|
|
35425
|
+
* between, {@link oldestBound} answers.
|
|
35426
|
+
*/
|
|
35427
|
+
oldestTimestamp(at = -Infinity) {
|
|
35428
|
+
let oldest = Infinity;
|
|
35429
|
+
for (const [key, entry] of this.entries) {
|
|
35430
|
+
if (isPastRevalidate(entry, at)) {
|
|
35431
|
+
this.delete(key);
|
|
35432
|
+
} else {
|
|
35433
|
+
oldest = Math.min(oldest, entry.timestamp);
|
|
35434
|
+
}
|
|
35435
|
+
}
|
|
35436
|
+
this.lowest = oldest;
|
|
35437
|
+
return oldest;
|
|
35438
|
+
}
|
|
35439
|
+
/**
|
|
35440
|
+
* No later than {@link oldestTimestamp}, without its scan: as of the last
|
|
35441
|
+
* one, less the entries set since. `Infinity` with none held. Drops the
|
|
35442
|
+
* entries past `revalidate` at `at` from the least recently used end, up to
|
|
35443
|
+
* the first that isn't, so {@link holdsLive} doesn't walk past them on every
|
|
35444
|
+
* request until the next scan.
|
|
35445
|
+
*/
|
|
35446
|
+
oldestBound(at = -Infinity) {
|
|
35447
|
+
for (const [key, entry] of this.entries) {
|
|
35448
|
+
if (!isPastRevalidate(entry, at)) {
|
|
35449
|
+
break;
|
|
35450
|
+
}
|
|
35451
|
+
this.delete(key);
|
|
35452
|
+
}
|
|
35453
|
+
return this.entries.size === 0 ? Infinity : this.lowest;
|
|
35454
|
+
}
|
|
35455
|
+
/**
|
|
35456
|
+
* Whether an entry not past `revalidate` at `at` is held. Usually the first
|
|
35457
|
+
* entry answers; the expired ones it passes are dropped by the scan
|
|
35458
|
+
* {@link oldestTimestamp} makes, not here, so asking changes nothing.
|
|
35459
|
+
*/
|
|
35460
|
+
holdsLive(at) {
|
|
35461
|
+
for (const entry of this.entries.values()) {
|
|
35462
|
+
if (!isPastRevalidate(entry, at)) {
|
|
35463
|
+
return true;
|
|
35464
|
+
}
|
|
35008
35465
|
}
|
|
35466
|
+
return false;
|
|
35009
35467
|
}
|
|
35010
35468
|
get size() {
|
|
35011
35469
|
return this.entries.size;
|
|
@@ -35014,14 +35472,39 @@ var EntryLru = class {
|
|
|
35014
35472
|
function sizeOf2(key, entry) {
|
|
35015
35473
|
return entry.value.byteLength + key.length;
|
|
35016
35474
|
}
|
|
35475
|
+
function isPastRevalidate(stored, at) {
|
|
35476
|
+
return at > stored.timestamp + stored.revalidate * 1e3;
|
|
35477
|
+
}
|
|
35017
35478
|
var DEFAULT_MEMORY_BYTES = 50 * 1024 * 1024;
|
|
35018
|
-
|
|
35479
|
+
var TAG_TABLE_REQUEST_TIMEOUT_MS = 3e3;
|
|
35480
|
+
async function awaitIf(wait, work, failure) {
|
|
35481
|
+
const logged = work.catch((error2) => {
|
|
35482
|
+
console.error(failure, error2);
|
|
35483
|
+
});
|
|
35484
|
+
if (wait) {
|
|
35485
|
+
await logged;
|
|
35486
|
+
}
|
|
35487
|
+
}
|
|
35488
|
+
function tagMethods(tags, options = {}) {
|
|
35019
35489
|
return {
|
|
35020
|
-
|
|
35021
|
-
|
|
35022
|
-
|
|
35023
|
-
|
|
35024
|
-
|
|
35490
|
+
// Started, not awaited, while the instance keeps up, unless `blocking`.
|
|
35491
|
+
// Next.js awaits `refreshTags` inside the first `'use cache'` lookup of a
|
|
35492
|
+
// request (`use-cache-wrapper`), and a staged render ends its static stage
|
|
35493
|
+
// on a timer: a log `Query` there pushed the entry out of the static
|
|
35494
|
+
// stage, so a cached navigation stored the page segment without it. What
|
|
35495
|
+
// the query finds applies from the next request. Awaited once the instance
|
|
35496
|
+
// is `behind`, which the runtime settles before a page render starts
|
|
35497
|
+
// (`catchUp`): the first request after an idle or frozen spell would
|
|
35498
|
+
// otherwise serve whatever was revalidated elsewhere meanwhile. A page's
|
|
35499
|
+
// render just after `catchUp` gets a grace more (`behindInRender`), so a
|
|
35500
|
+
// request that came in just short of the line doesn't cross it inside
|
|
35501
|
+
// the render. Not even then while the instance holds no entry the query
|
|
35502
|
+
// could expire (`holdsEntries`), with a `refreshIntervalMs` of `0` too.
|
|
35503
|
+
refreshTags: () => awaitIf(
|
|
35504
|
+
options.blocking === true || tags.behindInRender && tags.holdsEntries,
|
|
35505
|
+
tags.refresh(),
|
|
35506
|
+
"Error refreshing cache tags:"
|
|
35507
|
+
),
|
|
35025
35508
|
async updateTags(revalidatedTags, durations) {
|
|
35026
35509
|
await tags.update(revalidatedTags, durations);
|
|
35027
35510
|
}
|
|
@@ -35038,7 +35521,6 @@ function lazyHandler(create) {
|
|
|
35038
35521
|
updateTags: (tags, durations) => instance().updateTags(tags, durations)
|
|
35039
35522
|
};
|
|
35040
35523
|
}
|
|
35041
|
-
var TAG_MANIFEST_SYMBOL = /* @__PURE__ */ Symbol.for("cdk-nextjs.use-cache.tag-manifest");
|
|
35042
35524
|
function sharedTagManifest() {
|
|
35043
35525
|
const global = globalThis;
|
|
35044
35526
|
if (!global[TAG_MANIFEST_SYMBOL]) {
|
|
@@ -35048,12 +35530,24 @@ function sharedTagManifest() {
|
|
|
35048
35530
|
DEFAULT_TAG_REFRESH_MS
|
|
35049
35531
|
);
|
|
35050
35532
|
if (config.tableName && !isBuildPhase()) {
|
|
35051
|
-
const client = new import_client_dynamodb2.DynamoDBClient({
|
|
35533
|
+
const client = new import_client_dynamodb2.DynamoDBClient({
|
|
35534
|
+
region: config.region,
|
|
35535
|
+
// Without `throwOnRequestTimeout` the timeout only logs a warning. It
|
|
35536
|
+
// ends once the response headers arrive: `socketTimeout`, an
|
|
35537
|
+
// inactivity timer kept until the request closes, covers the body.
|
|
35538
|
+
requestHandler: {
|
|
35539
|
+
requestTimeout: TAG_TABLE_REQUEST_TIMEOUT_MS,
|
|
35540
|
+
throwOnRequestTimeout: true,
|
|
35541
|
+
socketTimeout: TAG_TABLE_REQUEST_TIMEOUT_MS
|
|
35542
|
+
}
|
|
35543
|
+
});
|
|
35052
35544
|
global[TAG_MANIFEST_SYMBOL] = new TrackedTagMarkers({
|
|
35053
35545
|
markers: new TagMarkerTable(client, config.tableName, config.buildId),
|
|
35054
35546
|
log: new RevalidationLog(client, config.tableName, config.buildId),
|
|
35055
35547
|
refreshIntervalMs,
|
|
35056
|
-
debug: (0, import_debug5.default)("cdk-nextjs:cache-handler:tags")
|
|
35548
|
+
debug: (0, import_debug5.default)("cdk-nextjs:cache-handler:tags"),
|
|
35549
|
+
// Set in every Lambda runtime; a container is never frozen.
|
|
35550
|
+
canFreeze: process.env.AWS_LAMBDA_FUNCTION_NAME !== void 0
|
|
35057
35551
|
});
|
|
35058
35552
|
} else {
|
|
35059
35553
|
if (!isBuildPhase()) {
|
|
@@ -35077,21 +35571,29 @@ function createDefaultUseCacheHandler(options = {}) {
|
|
|
35077
35571
|
);
|
|
35078
35572
|
const pending = new PendingSets();
|
|
35079
35573
|
const debug = (0, import_debug6.default)("cdk-nextjs:cache-handler:use-cache:default");
|
|
35574
|
+
tags.judgeEntriesOf(
|
|
35575
|
+
(scan) => scan ? memory.oldestTimestamp(markerClock()) : memory.oldestBound(markerClock()),
|
|
35576
|
+
() => memory.holdsLive(markerClock())
|
|
35577
|
+
);
|
|
35578
|
+
const ensureTags = (needed, tagList) => awaitIf(needed, tags.ensure(tagList), "Error reading cache tags:");
|
|
35080
35579
|
return {
|
|
35081
|
-
async get(cacheKey) {
|
|
35580
|
+
async get(cacheKey, softTags = []) {
|
|
35082
35581
|
await pending.wait(cacheKey);
|
|
35083
35582
|
const stored = memory.get(cacheKey);
|
|
35084
35583
|
if (!stored) {
|
|
35085
35584
|
debug(`MISS ${cacheKey}`);
|
|
35086
35585
|
return void 0;
|
|
35087
35586
|
}
|
|
35088
|
-
if (
|
|
35587
|
+
if (isPastRevalidate(stored, markerClock())) {
|
|
35089
35588
|
debug(`EXPIRED ${cacheKey}`);
|
|
35090
35589
|
memory.delete(cacheKey);
|
|
35091
35590
|
return void 0;
|
|
35092
35591
|
}
|
|
35093
|
-
await
|
|
35094
|
-
|
|
35592
|
+
await ensureTags(stored.timestamp < tags.completeSince, [
|
|
35593
|
+
...stored.tags,
|
|
35594
|
+
...softTags
|
|
35595
|
+
]);
|
|
35596
|
+
const state2 = tags.expiration(softTags) >= stored.timestamp ? "expired" : tags.state(stored.tags, stored.timestamp);
|
|
35095
35597
|
if (state2 === "expired") {
|
|
35096
35598
|
debug(`EXPIRED BY TAG ${cacheKey}`);
|
|
35097
35599
|
memory.delete(cacheKey);
|
|
@@ -35109,7 +35611,7 @@ function createDefaultUseCacheHandler(options = {}) {
|
|
|
35109
35611
|
return;
|
|
35110
35612
|
}
|
|
35111
35613
|
memory.set(cacheKey, stored);
|
|
35112
|
-
|
|
35614
|
+
void ensureTags(false, stored.tags);
|
|
35113
35615
|
debug(`SET ${cacheKey}`);
|
|
35114
35616
|
} catch (error2) {
|
|
35115
35617
|
debug(`SET FAILED ${cacheKey}`, error2);
|
|
@@ -35117,7 +35619,11 @@ function createDefaultUseCacheHandler(options = {}) {
|
|
|
35117
35619
|
done();
|
|
35118
35620
|
}
|
|
35119
35621
|
},
|
|
35120
|
-
...tagMethods(tags)
|
|
35622
|
+
...tagMethods(tags),
|
|
35623
|
+
// `Infinity` has Next.js pass the implicit tags to `get` instead, so they
|
|
35624
|
+
// are judged against the entry read: whether answering needs a marker
|
|
35625
|
+
// read depends on that entry's `timestamp`, which this does not know.
|
|
35626
|
+
getExpiration: async () => Infinity
|
|
35121
35627
|
};
|
|
35122
35628
|
}
|
|
35123
35629
|
var use_cache_default_handler_default = lazyHandler(() => createDefaultUseCacheHandler());
|