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
|
@@ -46261,12 +46261,70 @@ var require_dist_cjs20 = __commonJS({
|
|
|
46261
46261
|
// src/adapter/use-cache-remote-handler.ts
|
|
46262
46262
|
var import_client_s32 = __toESM(require_dist_cjs16());
|
|
46263
46263
|
var import_debug6 = __toESM(require_src());
|
|
46264
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
46265
46264
|
|
|
46266
46265
|
// src/adapter/aws-cache-store.ts
|
|
46267
46266
|
var import_client_dynamodb = __toESM(require_dist_cjs20());
|
|
46268
46267
|
var import_client_s3 = __toESM(require_dist_cjs16());
|
|
46268
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
46269
46269
|
import { join as join5 } from "node:path";
|
|
46270
|
+
|
|
46271
|
+
// src/adapter/cache-utils.ts
|
|
46272
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
46273
|
+
function preprocessValue(value) {
|
|
46274
|
+
if (value == null) {
|
|
46275
|
+
return value;
|
|
46276
|
+
}
|
|
46277
|
+
if (Buffer.isBuffer(value)) {
|
|
46278
|
+
return {
|
|
46279
|
+
__type: "Buffer",
|
|
46280
|
+
base64: value.toString("base64")
|
|
46281
|
+
};
|
|
46282
|
+
}
|
|
46283
|
+
if (value instanceof Map) {
|
|
46284
|
+
const entries = {};
|
|
46285
|
+
for (const [key, val] of value.entries()) {
|
|
46286
|
+
entries[key] = preprocessValue(val);
|
|
46287
|
+
}
|
|
46288
|
+
return {
|
|
46289
|
+
__type: "Map",
|
|
46290
|
+
data: entries
|
|
46291
|
+
};
|
|
46292
|
+
}
|
|
46293
|
+
if (Array.isArray(value)) {
|
|
46294
|
+
return value.map((item) => preprocessValue(item));
|
|
46295
|
+
}
|
|
46296
|
+
if (typeof value === "object") {
|
|
46297
|
+
const processed = {};
|
|
46298
|
+
for (const [key, val] of Object.entries(value)) {
|
|
46299
|
+
processed[key] = preprocessValue(val);
|
|
46300
|
+
}
|
|
46301
|
+
return processed;
|
|
46302
|
+
}
|
|
46303
|
+
return value;
|
|
46304
|
+
}
|
|
46305
|
+
function serializeCacheValue(value) {
|
|
46306
|
+
const processed = preprocessValue(value);
|
|
46307
|
+
return JSON.stringify(processed);
|
|
46308
|
+
}
|
|
46309
|
+
function parseCacheValue(jsonString) {
|
|
46310
|
+
return JSON.parse(jsonString, (_key, val) => {
|
|
46311
|
+
if (val && typeof val === "object" && val.__type === "Map") {
|
|
46312
|
+
return new Map(Object.entries(val.data));
|
|
46313
|
+
}
|
|
46314
|
+
if (val && typeof val === "object" && val.__type === "Buffer") {
|
|
46315
|
+
return typeof val.base64 === "string" ? Buffer.from(val.base64, "base64") : Buffer.from(val.data);
|
|
46316
|
+
}
|
|
46317
|
+
if (val && typeof val === "object" && val.type === "Buffer" && Array.isArray(val.data)) {
|
|
46318
|
+
return Buffer.from(val.data);
|
|
46319
|
+
}
|
|
46320
|
+
return val;
|
|
46321
|
+
});
|
|
46322
|
+
}
|
|
46323
|
+
function sha256Hex(value) {
|
|
46324
|
+
return createHash6("sha256").update(value).digest("hex");
|
|
46325
|
+
}
|
|
46326
|
+
|
|
46327
|
+
// src/adapter/aws-cache-store.ts
|
|
46270
46328
|
function resolveAwsCacheConfig() {
|
|
46271
46329
|
return {
|
|
46272
46330
|
bucketName: process.env.CDK_NEXTJS_CACHE_BUCKET_NAME || "",
|
|
@@ -46333,6 +46391,14 @@ function markerOf(item) {
|
|
|
46333
46391
|
}
|
|
46334
46392
|
var BATCH_GET_MAX_KEYS = 100;
|
|
46335
46393
|
var DEFAULT_TAG_REFRESH_MS = 1e3;
|
|
46394
|
+
var TAG_REFRESH_GRACE_MS = 1e3;
|
|
46395
|
+
function hashedTag(tag) {
|
|
46396
|
+
return `#${sha256Hex(tag)}`;
|
|
46397
|
+
}
|
|
46398
|
+
var MAX_TAG_BYTES = 768;
|
|
46399
|
+
function sortKeyTag(tag) {
|
|
46400
|
+
return Buffer.byteLength(tag) <= MAX_TAG_BYTES ? tag : hashedTag(tag);
|
|
46401
|
+
}
|
|
46336
46402
|
var TagMarkerTable = class {
|
|
46337
46403
|
constructor(client, tableName, buildId) {
|
|
46338
46404
|
this.client = client;
|
|
@@ -46355,7 +46421,7 @@ var TagMarkerTable = class {
|
|
|
46355
46421
|
TableName: this.tableName,
|
|
46356
46422
|
Key: {
|
|
46357
46423
|
pk: { S: this.buildId },
|
|
46358
|
-
sk: { S: tag }
|
|
46424
|
+
sk: { S: sortKeyTag(tag) }
|
|
46359
46425
|
},
|
|
46360
46426
|
...markerUpdate(now, durations),
|
|
46361
46427
|
ReturnValues: "ALL_NEW"
|
|
@@ -46375,7 +46441,13 @@ var TagMarkerTable = class {
|
|
|
46375
46441
|
const unique = Array.from(new Set(tags));
|
|
46376
46442
|
const markers = /* @__PURE__ */ new Map();
|
|
46377
46443
|
for (let i5 = 0; i5 < unique.length; i5 += BATCH_GET_MAX_KEYS) {
|
|
46378
|
-
|
|
46444
|
+
const tagBySk = new Map(
|
|
46445
|
+
unique.slice(i5, i5 + BATCH_GET_MAX_KEYS).map((tag) => [sortKeyTag(tag), tag])
|
|
46446
|
+
);
|
|
46447
|
+
let keys = Array.from(
|
|
46448
|
+
tagBySk.keys(),
|
|
46449
|
+
(sk) => ({ pk: { S: buildId }, sk: { S: sk } })
|
|
46450
|
+
);
|
|
46379
46451
|
for (let attempt = 0; keys?.length && attempt < 3; attempt++) {
|
|
46380
46452
|
const response = await this.client.send(
|
|
46381
46453
|
new import_client_dynamodb.BatchGetItemCommand({
|
|
@@ -46388,7 +46460,7 @@ var TagMarkerTable = class {
|
|
|
46388
46460
|
})
|
|
46389
46461
|
);
|
|
46390
46462
|
for (const item of response.Responses?.[tableName] ?? []) {
|
|
46391
|
-
const tag = item.sk?.S;
|
|
46463
|
+
const tag = tagBySk.get(item.sk?.S ?? "");
|
|
46392
46464
|
if (tag !== void 0) {
|
|
46393
46465
|
markers.set(tag, markerOf(item));
|
|
46394
46466
|
}
|
|
@@ -46426,11 +46498,13 @@ var RevalidationLog = class {
|
|
|
46426
46498
|
* to {@link LOG_PUT_ATTEMPTS} times.
|
|
46427
46499
|
*/
|
|
46428
46500
|
async put(tag, at, marker) {
|
|
46501
|
+
const skTag = sortKeyTag(tag);
|
|
46429
46502
|
for (let attempt = 1; ; attempt++) {
|
|
46430
46503
|
const item = {
|
|
46431
46504
|
pk: { S: this.pk },
|
|
46432
|
-
sk: { S: `${logSkPrefix(at)}#${
|
|
46433
|
-
ttl: { N: String(Math.ceil((at + REVALIDATION_LOG_TTL_MS) / 1e3)) }
|
|
46505
|
+
sk: { S: `${logSkPrefix(at)}#${skTag}` },
|
|
46506
|
+
ttl: { N: String(Math.ceil((at + REVALIDATION_LOG_TTL_MS) / 1e3)) },
|
|
46507
|
+
...skTag !== tag && { longTag: { S: tag } }
|
|
46434
46508
|
};
|
|
46435
46509
|
for (const field of MARKER_FIELDS) {
|
|
46436
46510
|
const value = marker[field];
|
|
@@ -46475,7 +46549,7 @@ var RevalidationLog = class {
|
|
|
46475
46549
|
":pk": { S: this.pk },
|
|
46476
46550
|
":since": { S: logSkPrefix(Math.max(0, since)) }
|
|
46477
46551
|
},
|
|
46478
|
-
ProjectionExpression: "sk, revalidatedAt, staleAt, expiredAt",
|
|
46552
|
+
ProjectionExpression: "sk, longTag, revalidatedAt, staleAt, expiredAt",
|
|
46479
46553
|
ExclusiveStartKey: startKey
|
|
46480
46554
|
})
|
|
46481
46555
|
);
|
|
@@ -46487,7 +46561,7 @@ var RevalidationLog = class {
|
|
|
46487
46561
|
rows.push({
|
|
46488
46562
|
sk,
|
|
46489
46563
|
at: Number(sk.slice(0, LOG_SK_DIGITS)),
|
|
46490
|
-
tag: sk.slice(LOG_SK_DIGITS + 1),
|
|
46564
|
+
tag: item.longTag?.S ?? sk.slice(LOG_SK_DIGITS + 1),
|
|
46491
46565
|
marker: markerOf(item)
|
|
46492
46566
|
});
|
|
46493
46567
|
}
|
|
@@ -46531,10 +46605,64 @@ var TrackedTagMarkers = class {
|
|
|
46531
46605
|
* result rather than dropped.
|
|
46532
46606
|
*/
|
|
46533
46607
|
pendingRows = /* @__PURE__ */ new Map();
|
|
46608
|
+
/**
|
|
46609
|
+
* Log rows for tags not tracked and not being read, merged by tag, oldest
|
|
46610
|
+
* first: what the log has said about them since {@link completeSince}. Taken
|
|
46611
|
+
* into a tag's marker when it is read or written.
|
|
46612
|
+
*/
|
|
46613
|
+
logOnly = /* @__PURE__ */ new Map();
|
|
46614
|
+
/** See {@link completeSince}. */
|
|
46615
|
+
floor;
|
|
46616
|
+
/**
|
|
46617
|
+
* The future `expiredAt`s of markers dropped since, as `[from, to]`, in
|
|
46618
|
+
* order: once `from` is past, {@link completeSince} moves to `to`, and not
|
|
46619
|
+
* before, since only then does it expire anything. `from` and `to` are the
|
|
46620
|
+
* same until spans are merged for space ({@link laterFloor}).
|
|
46621
|
+
*/
|
|
46622
|
+
laterFloors = [];
|
|
46623
|
+
/** The in-memory entry stores judged by {@link completeSince}. */
|
|
46624
|
+
entryStores = [];
|
|
46625
|
+
/** How many times {@link forget} ran: a read started before one is made again. */
|
|
46626
|
+
forgotten = 0;
|
|
46534
46627
|
/** Tracked tags whose read failed, for the next refresh to read again. */
|
|
46535
46628
|
unread = /* @__PURE__ */ new Set();
|
|
46629
|
+
/**
|
|
46630
|
+
* Set inside a page render {@link catchUp} let go, for that render alone.
|
|
46631
|
+
* See {@link behindInRender}.
|
|
46632
|
+
*/
|
|
46633
|
+
caughtUpRender = new AsyncLocalStorage();
|
|
46634
|
+
/** When `sync` last scanned the entry stores ({@link oldestEntry}). */
|
|
46635
|
+
lastEntryScan = -Infinity;
|
|
46636
|
+
/**
|
|
46637
|
+
* No `logOnly` row is known by it or earlier, so a {@link pruneLogged}
|
|
46638
|
+
* against it or earlier drops nothing: a marker is only ever known by later,
|
|
46639
|
+
* as its `expiredAt` passes.
|
|
46640
|
+
*/
|
|
46641
|
+
prunedAgainst = -Infinity;
|
|
46642
|
+
/** When the last refresh started, or was skipped. */
|
|
46536
46643
|
lastRefresh = -Infinity;
|
|
46644
|
+
/**
|
|
46645
|
+
* The refresh in flight. One left open across a freeze comes back after the
|
|
46646
|
+
* thaw with what it read before, so {@link refresh} waits for it and then
|
|
46647
|
+
* for a fresh one, rather than the runtime waiting for it before the freeze:
|
|
46648
|
+
* that would bill every Lambda invocation for the query's tail.
|
|
46649
|
+
*/
|
|
46537
46650
|
refreshing;
|
|
46651
|
+
/**
|
|
46652
|
+
* When {@link refreshing} last showed the process running: a timer ticks
|
|
46653
|
+
* while it is in flight, and a tick that comes late, or none at all, means
|
|
46654
|
+
* the process was paused - frozen - since it started.
|
|
46655
|
+
*/
|
|
46656
|
+
lastTick = -Infinity;
|
|
46657
|
+
/** Whether a tick came late since {@link refreshing} started. */
|
|
46658
|
+
pausedWhileRefreshing = false;
|
|
46659
|
+
ticker;
|
|
46660
|
+
canFreeze;
|
|
46661
|
+
/**
|
|
46662
|
+
* When the last refresh settled, either way, unless it spanned a pause: what
|
|
46663
|
+
* it knows is from then. See {@link behind}.
|
|
46664
|
+
*/
|
|
46665
|
+
settledAt;
|
|
46538
46666
|
/**
|
|
46539
46667
|
* Where the next log query starts (`clock()`): the last successful one's
|
|
46540
46668
|
* start, less {@link REVALIDATION_LOG_LOOKBACK_MS}.
|
|
@@ -46556,20 +46684,68 @@ var TrackedTagMarkers = class {
|
|
|
46556
46684
|
this.random = options.random ?? Math.random;
|
|
46557
46685
|
this.debug = options.debug ?? (() => {
|
|
46558
46686
|
});
|
|
46687
|
+
this.canFreeze = options.canFreeze ?? false;
|
|
46559
46688
|
const at = this.clock();
|
|
46560
46689
|
this.cursor = at - REVALIDATION_LOG_LOOKBACK_MS;
|
|
46561
46690
|
this.lastLogRead = at;
|
|
46691
|
+
this.settledAt = at;
|
|
46692
|
+
this.floor = markerClock();
|
|
46562
46693
|
}
|
|
46563
46694
|
/** `tag`'s marker as this instance knows it, if it tracks `tag`. */
|
|
46564
46695
|
get(tag) {
|
|
46565
46696
|
return this.tags.get(tag);
|
|
46566
46697
|
}
|
|
46698
|
+
/**
|
|
46699
|
+
* Since when (`markerClock()`) this instance knows of every revalidation of
|
|
46700
|
+
* every tag, tracked or not: tracked ones from their markers and the log,
|
|
46701
|
+
* the others from the log alone (`logOnly`). So an entry created at or after
|
|
46702
|
+
* it can be judged by {@link state} and {@link expiration} without
|
|
46703
|
+
* {@link ensure} reading anything - which matters inside a `'use cache'`
|
|
46704
|
+
* lookup, where Next.js waits for the answer and a read pushes the entry out
|
|
46705
|
+
* of the static stage of a staged render.
|
|
46706
|
+
*
|
|
46707
|
+
* Starts at construction: the log is followed from before it. Moves to now
|
|
46708
|
+
* when the tracked markers are forgotten, and past the revalidations of
|
|
46709
|
+
* whatever else it lets go of - a marker evicted for space, a log row
|
|
46710
|
+
* pruned - which are no longer known: to its `expiredAt` only once that is
|
|
46711
|
+
* past, since a profile's `expire` can be a year out, and moving there at
|
|
46712
|
+
* once would turn the read-free path off for the instance's life.
|
|
46713
|
+
*
|
|
46714
|
+
* A row is pruned only once it is no later than every entry held, so that
|
|
46715
|
+
* costs those entries nothing. An entry stored afterwards with an older
|
|
46716
|
+
* `timestamp` - Next.js stamps the start of its generation, which can
|
|
46717
|
+
* outlast a lookback - is older than this too, and is read first.
|
|
46718
|
+
*
|
|
46719
|
+
* It does not cover a revalidation older than the log's first lookback
|
|
46720
|
+
* whose `expiredAt` is still to come, which would expire an entry created
|
|
46721
|
+
* after it. So the `'use cache'` handler reads an entry's tags in the
|
|
46722
|
+
* background as it stores it.
|
|
46723
|
+
*/
|
|
46724
|
+
get completeSince() {
|
|
46725
|
+
const at = markerClock();
|
|
46726
|
+
while (this.laterFloors.length > 0 && this.laterFloors[0][0] <= at) {
|
|
46727
|
+
this.floor = Math.max(this.floor, this.laterFloors.shift()[1]);
|
|
46728
|
+
}
|
|
46729
|
+
return this.floor;
|
|
46730
|
+
}
|
|
46731
|
+
/**
|
|
46732
|
+
* Register an in-memory store whose entries are judged by
|
|
46733
|
+
* {@link completeSince}, by the `timestamp` of the oldest entry it holds:
|
|
46734
|
+
* `logOnly` keeps the rows that could still apply to one of them, and no
|
|
46735
|
+
* others. With none registered it keeps nothing. Asked with `scan` false,
|
|
46736
|
+
* `oldestTimestamp` may answer early, but never late, without a scan.
|
|
46737
|
+
*/
|
|
46738
|
+
judgeEntriesOf(oldestTimestamp, holdsAny = () => oldestTimestamp(false) < Infinity) {
|
|
46739
|
+
this.entryStores.push({ oldestTimestamp, holdsAny });
|
|
46740
|
+
}
|
|
46567
46741
|
/**
|
|
46568
46742
|
* Track `tag` with `marker`: what this instance just wrote, which it knows
|
|
46569
46743
|
* without reading it back.
|
|
46570
46744
|
*/
|
|
46571
46745
|
set(tag, marker) {
|
|
46572
|
-
this.
|
|
46746
|
+
const logged = this.logOnly.get(tag);
|
|
46747
|
+
this.logOnly.delete(tag);
|
|
46748
|
+
this.remember(tag, mergeMarkers(marker, logged));
|
|
46573
46749
|
this.known(tag, this.clock());
|
|
46574
46750
|
}
|
|
46575
46751
|
/**
|
|
@@ -46622,27 +46798,103 @@ var TrackedTagMarkers = class {
|
|
|
46622
46798
|
const unread = [];
|
|
46623
46799
|
for (const tag of new Set(tags)) {
|
|
46624
46800
|
const inFlight = this.reading.get(tag);
|
|
46625
|
-
if (
|
|
46626
|
-
waits.push(inFlight);
|
|
46627
|
-
} else if (this.tags.has(tag)) {
|
|
46801
|
+
if (this.tags.has(tag)) {
|
|
46628
46802
|
this.touch(tag);
|
|
46803
|
+
} else if (inFlight) {
|
|
46804
|
+
waits.push(inFlight);
|
|
46629
46805
|
} else {
|
|
46630
46806
|
unread.push(tag);
|
|
46631
46807
|
}
|
|
46632
46808
|
}
|
|
46633
46809
|
if (unread.length > 0) {
|
|
46634
|
-
|
|
46635
|
-
for (const tag of unread) {
|
|
46636
|
-
this.reading.delete(tag);
|
|
46637
|
-
}
|
|
46638
|
-
});
|
|
46639
|
-
for (const tag of unread) {
|
|
46640
|
-
this.reading.set(tag, read);
|
|
46641
|
-
}
|
|
46642
|
-
waits.push(read);
|
|
46810
|
+
waits.push(this.startRead(unread));
|
|
46643
46811
|
}
|
|
46644
46812
|
await Promise.all(waits);
|
|
46645
46813
|
}
|
|
46814
|
+
/**
|
|
46815
|
+
* {@link readInto} `tags`, as the read of each that whoever needs one of
|
|
46816
|
+
* them next waits on ({@link reading}). Never rejects: a failed read leaves
|
|
46817
|
+
* the tags tracked.
|
|
46818
|
+
*/
|
|
46819
|
+
startRead(tags) {
|
|
46820
|
+
const read = this.readInto(tags).then(() => {
|
|
46821
|
+
for (const tag of tags) {
|
|
46822
|
+
this.reading.delete(tag);
|
|
46823
|
+
}
|
|
46824
|
+
});
|
|
46825
|
+
for (const tag of tags) {
|
|
46826
|
+
this.reading.set(tag, read);
|
|
46827
|
+
}
|
|
46828
|
+
return read;
|
|
46829
|
+
}
|
|
46830
|
+
/**
|
|
46831
|
+
* Whether the last refresh settled more than `refreshIntervalMs` plus
|
|
46832
|
+
* {@link TAG_REFRESH_GRACE_MS} ago - one that spanned a freeze counts from
|
|
46833
|
+
* when it started: after the instance sat idle or frozen - or always, with
|
|
46834
|
+
* a `refreshIntervalMs` of `0`. Answering from what it knows then could
|
|
46835
|
+
* serve an entry revalidated elsewhere since, however long ago that was, so
|
|
46836
|
+
* `refreshTags` waits for the refresh, and the runtime waits for it before
|
|
46837
|
+
* a page render starts ({@link catchUp}).
|
|
46838
|
+
*
|
|
46839
|
+
* From when it settled, not when it started, so a slow query the runtime
|
|
46840
|
+
* waited for before the render does not leave the instance behind inside
|
|
46841
|
+
* it. A failed one counts too, so one request does not wait for two: the
|
|
46842
|
+
* same "assume the entry is valid" a failed read answers with, until the
|
|
46843
|
+
* next refresh, at most the interval and the grace later.
|
|
46844
|
+
*/
|
|
46845
|
+
get behind() {
|
|
46846
|
+
return this.log !== void 0 && // `0` asks before every check, so every check waits for the answer.
|
|
46847
|
+
(this.refreshIntervalMs === 0 || this.overdue(this.settledAt));
|
|
46848
|
+
}
|
|
46849
|
+
/**
|
|
46850
|
+
* {@link behind}, as `refreshTags` asks it: never inside a page's render
|
|
46851
|
+
* {@link catchUp} caught up, since the runtime settled it for that request
|
|
46852
|
+
* before the render started, and a wait inside a `'use cache'` lookup is
|
|
46853
|
+
* what it settled it to avoid - a render crossing the line partway through
|
|
46854
|
+
* would otherwise wait after all. So a revalidation elsewhere can go unseen
|
|
46855
|
+
* for a render's duration on top of the usual window.
|
|
46856
|
+
*
|
|
46857
|
+
* A route handler, which no catch-up precedes, waits at the plain line.
|
|
46858
|
+
*/
|
|
46859
|
+
get behindInRender() {
|
|
46860
|
+
return this.refreshIntervalMs !== 0 && this.caughtUpRender.getStore() ? false : this.behind;
|
|
46861
|
+
}
|
|
46862
|
+
/**
|
|
46863
|
+
* Whether a registered store ({@link judgeEntriesOf}) holds any entry: one
|
|
46864
|
+
* a refresh could expire. With none there is nothing to wait for even when
|
|
46865
|
+
* the instance is {@link behind}, since whatever is stored from here on is
|
|
46866
|
+
* newer than what the log would say - which covers the first `'use cache'`
|
|
46867
|
+
* call of a process, which creates the handler inside the render.
|
|
46868
|
+
*/
|
|
46869
|
+
get holdsEntries() {
|
|
46870
|
+
return this.entryStores.some((store) => store.holdsAny());
|
|
46871
|
+
}
|
|
46872
|
+
/**
|
|
46873
|
+
* {@link refresh}, waited for when the instance is {@link behind}. The
|
|
46874
|
+
* runtime calls it before handing a page request to Next.js, outside the
|
|
46875
|
+
* staged render whose static stage a wait inside a `'use cache'` lookup
|
|
46876
|
+
* would cut short. Only for the `'use cache'` handler's entries
|
|
46877
|
+
* ({@link judgeEntriesOf}): the other caches wait for the refresh where they
|
|
46878
|
+
* read anyway. Not with a `refreshIntervalMs` of `0` either, where
|
|
46879
|
+
* `refreshTags` asks again regardless. Then `render`, marked as caught up
|
|
46880
|
+
* for ({@link behindInRender}). Rejects only as `render` does.
|
|
46881
|
+
*/
|
|
46882
|
+
async catchUp(render) {
|
|
46883
|
+
const log = (error2) => console.error("Error refreshing cache tags:", error2);
|
|
46884
|
+
if (this.refreshIntervalMs > 0 && this.behind) {
|
|
46885
|
+
if (!this.holdsEntries) {
|
|
46886
|
+
return render();
|
|
46887
|
+
}
|
|
46888
|
+
await this.refresh().catch(log);
|
|
46889
|
+
} else if (this.refreshIntervalMs > 0) {
|
|
46890
|
+
void this.refresh().catch(log);
|
|
46891
|
+
}
|
|
46892
|
+
return this.caughtUpRender.run(true, render);
|
|
46893
|
+
}
|
|
46894
|
+
/** Whether `at` is more than the interval and the grace ago. */
|
|
46895
|
+
overdue(at) {
|
|
46896
|
+
return this.clock() - at > this.refreshIntervalMs + TAG_REFRESH_GRACE_MS;
|
|
46897
|
+
}
|
|
46646
46898
|
/**
|
|
46647
46899
|
* Catch up on revalidations other instances ran, at most once per
|
|
46648
46900
|
* `refreshIntervalMs`. See {@link TrackedTagMarkers} for the cost.
|
|
@@ -46653,6 +46905,9 @@ var TrackedTagMarkers = class {
|
|
|
46653
46905
|
return;
|
|
46654
46906
|
}
|
|
46655
46907
|
if (this.refreshing) {
|
|
46908
|
+
if (this.behind && this.pausedSinceRefreshStarted()) {
|
|
46909
|
+
return this.refreshing.then(() => this.refresh());
|
|
46910
|
+
}
|
|
46656
46911
|
return this.refreshing;
|
|
46657
46912
|
}
|
|
46658
46913
|
const at = this.clock();
|
|
@@ -46660,14 +46915,47 @@ var TrackedTagMarkers = class {
|
|
|
46660
46915
|
return;
|
|
46661
46916
|
}
|
|
46662
46917
|
this.lastRefresh = at;
|
|
46663
|
-
if (this.tags.size === 0) {
|
|
46918
|
+
if (this.tags.size === 0 && this.entryStores.length === 0) {
|
|
46664
46919
|
return;
|
|
46665
46920
|
}
|
|
46921
|
+
this.startTicking();
|
|
46666
46922
|
this.refreshing = this.sync(at, log).finally(() => {
|
|
46923
|
+
this.settledAt = Math.max(
|
|
46924
|
+
this.settledAt,
|
|
46925
|
+
this.pausedSinceRefreshStarted() ? this.lastRefresh : this.clock()
|
|
46926
|
+
);
|
|
46927
|
+
this.stopTicking();
|
|
46667
46928
|
this.refreshing = void 0;
|
|
46668
46929
|
});
|
|
46669
46930
|
return this.refreshing;
|
|
46670
46931
|
}
|
|
46932
|
+
startTicking() {
|
|
46933
|
+
if (!this.canFreeze) {
|
|
46934
|
+
return;
|
|
46935
|
+
}
|
|
46936
|
+
this.lastTick = this.clock();
|
|
46937
|
+
this.pausedWhileRefreshing = false;
|
|
46938
|
+
this.ticker = setInterval(() => {
|
|
46939
|
+
const at = this.clock();
|
|
46940
|
+
if (at - this.lastTick > REFRESH_PAUSE_MS) {
|
|
46941
|
+
this.pausedWhileRefreshing = true;
|
|
46942
|
+
}
|
|
46943
|
+
this.lastTick = at;
|
|
46944
|
+
}, REFRESH_TICK_MS);
|
|
46945
|
+
this.ticker.unref?.();
|
|
46946
|
+
}
|
|
46947
|
+
stopTicking() {
|
|
46948
|
+
clearInterval(this.ticker);
|
|
46949
|
+
this.ticker = void 0;
|
|
46950
|
+
}
|
|
46951
|
+
/**
|
|
46952
|
+
* Whether the process was paused since {@link refreshing} started: a tick
|
|
46953
|
+
* came late, or the next one is overdue (after a thaw, a request can run
|
|
46954
|
+
* before the timer does).
|
|
46955
|
+
*/
|
|
46956
|
+
pausedSinceRefreshStarted() {
|
|
46957
|
+
return this.canFreeze && (this.pausedWhileRefreshing || this.clock() - this.lastTick > REFRESH_PAUSE_MS);
|
|
46958
|
+
}
|
|
46671
46959
|
/**
|
|
46672
46960
|
* Record a revalidation of `tags`: here at once, and in the revalidation
|
|
46673
46961
|
* table for every other instance - the marker row plus a log row per tag.
|
|
@@ -46700,7 +46988,7 @@ var TrackedTagMarkers = class {
|
|
|
46700
46988
|
const at = markerClock();
|
|
46701
46989
|
let state2 = "fresh";
|
|
46702
46990
|
for (const tag of tags) {
|
|
46703
|
-
const marker = this.
|
|
46991
|
+
const marker = this.knownMarker(tag);
|
|
46704
46992
|
if (!marker) {
|
|
46705
46993
|
continue;
|
|
46706
46994
|
}
|
|
@@ -46724,7 +47012,7 @@ var TrackedTagMarkers = class {
|
|
|
46724
47012
|
const at = markerClock();
|
|
46725
47013
|
let latest = 0;
|
|
46726
47014
|
for (const tag of tags) {
|
|
46727
|
-
const marker = this.
|
|
47015
|
+
const marker = this.knownMarker(tag);
|
|
46728
47016
|
if (!marker) {
|
|
46729
47017
|
continue;
|
|
46730
47018
|
}
|
|
@@ -46811,13 +47099,14 @@ var TrackedTagMarkers = class {
|
|
|
46811
47099
|
);
|
|
46812
47100
|
return;
|
|
46813
47101
|
}
|
|
46814
|
-
const
|
|
46815
|
-
|
|
46816
|
-
|
|
46817
|
-
|
|
46818
|
-
|
|
46819
|
-
|
|
46820
|
-
|
|
47102
|
+
const due = this.due(at);
|
|
47103
|
+
if (due.length > 0) {
|
|
47104
|
+
void this.startRead(due);
|
|
47105
|
+
}
|
|
47106
|
+
const result = await log.query(this.cursor).catch((error2) => {
|
|
47107
|
+
console.error("Error reading tag revalidation log:", error2);
|
|
47108
|
+
return void 0;
|
|
47109
|
+
});
|
|
46821
47110
|
if (!result) {
|
|
46822
47111
|
return;
|
|
46823
47112
|
}
|
|
@@ -46828,6 +47117,11 @@ var TrackedTagMarkers = class {
|
|
|
46828
47117
|
);
|
|
46829
47118
|
return;
|
|
46830
47119
|
}
|
|
47120
|
+
const scan = at - this.lastEntryScan > ENTRY_SCAN_INTERVAL_MS;
|
|
47121
|
+
if (scan) {
|
|
47122
|
+
this.lastEntryScan = at;
|
|
47123
|
+
}
|
|
47124
|
+
const oldest = this.oldestEntry(scan);
|
|
46831
47125
|
let applied = 0;
|
|
46832
47126
|
for (const row of result.rows) {
|
|
46833
47127
|
if (this.applied.has(row.sk)) {
|
|
@@ -46842,11 +47136,20 @@ var TrackedTagMarkers = class {
|
|
|
46842
47136
|
mergeMarkers(this.pendingRows.get(row.tag), row.marker)
|
|
46843
47137
|
);
|
|
46844
47138
|
} else {
|
|
47139
|
+
this.rememberLogged(
|
|
47140
|
+
row.tag,
|
|
47141
|
+
mergeMarkers(this.logOnly.get(row.tag), row.marker),
|
|
47142
|
+
oldest
|
|
47143
|
+
);
|
|
46845
47144
|
continue;
|
|
46846
47145
|
}
|
|
46847
47146
|
this.applied.set(row.sk, row.at);
|
|
46848
47147
|
applied++;
|
|
46849
47148
|
}
|
|
47149
|
+
if (oldest > this.prunedAgainst) {
|
|
47150
|
+
this.pruneLogged(oldest);
|
|
47151
|
+
}
|
|
47152
|
+
this.prunedAgainst = oldest;
|
|
46850
47153
|
this.lastLogRead = at;
|
|
46851
47154
|
this.advance(at - REVALIDATION_LOG_LOOKBACK_MS);
|
|
46852
47155
|
this.debug(
|
|
@@ -46862,14 +47165,16 @@ var TrackedTagMarkers = class {
|
|
|
46862
47165
|
const due = [];
|
|
46863
47166
|
for (const tag of this.unread) {
|
|
46864
47167
|
if (due.length >= BATCH_GET_MAX_KEYS) return due;
|
|
46865
|
-
|
|
47168
|
+
if (!this.reading.has(tag)) {
|
|
47169
|
+
due.push(tag);
|
|
47170
|
+
}
|
|
46866
47171
|
}
|
|
46867
47172
|
const jitter = this.resyncIntervalMs * TAG_RESYNC_JITTER;
|
|
46868
47173
|
for (const [tag, dueAt] of this.dueAt) {
|
|
46869
47174
|
if (due.length >= BATCH_GET_MAX_KEYS || dueAt - jitter > at) {
|
|
46870
47175
|
break;
|
|
46871
47176
|
}
|
|
46872
|
-
if (dueAt <= at && !this.unread.has(tag)) {
|
|
47177
|
+
if (dueAt <= at && !this.unread.has(tag) && !this.reading.has(tag)) {
|
|
46873
47178
|
due.push(tag);
|
|
46874
47179
|
}
|
|
46875
47180
|
}
|
|
@@ -46882,10 +47187,19 @@ var TrackedTagMarkers = class {
|
|
|
46882
47187
|
*/
|
|
46883
47188
|
forget(at, why) {
|
|
46884
47189
|
this.debug(`forgetting ${this.tags.size} tracked tags: ${why}`);
|
|
47190
|
+
this.forgotten++;
|
|
47191
|
+
for (const marker of this.tags.values()) {
|
|
47192
|
+
this.raiseFloor(marker);
|
|
47193
|
+
}
|
|
47194
|
+
for (const marker of this.logOnly.values()) {
|
|
47195
|
+
this.raiseFloor(marker);
|
|
47196
|
+
}
|
|
46885
47197
|
this.tags.clear();
|
|
46886
47198
|
this.dueAt.clear();
|
|
46887
47199
|
this.unread.clear();
|
|
46888
47200
|
this.applied.clear();
|
|
47201
|
+
this.logOnly.clear();
|
|
47202
|
+
this.floor = Math.max(this.floor, markerClock());
|
|
46889
47203
|
this.cursor = at - REVALIDATION_LOG_LOOKBACK_MS;
|
|
46890
47204
|
this.lastLogRead = at;
|
|
46891
47205
|
}
|
|
@@ -46911,13 +47225,17 @@ var TrackedTagMarkers = class {
|
|
|
46911
47225
|
* always answered with, rather than a read on every request while DynamoDB
|
|
46912
47226
|
* is unavailable.
|
|
46913
47227
|
*/
|
|
46914
|
-
async readInto(tags) {
|
|
47228
|
+
async readInto(tags, again = false) {
|
|
46915
47229
|
if (tags.length === 0) {
|
|
46916
47230
|
return true;
|
|
46917
47231
|
}
|
|
46918
47232
|
const at = this.clock();
|
|
47233
|
+
const forgotten = this.forgotten;
|
|
46919
47234
|
try {
|
|
46920
47235
|
const read = await this.markers.read(tags);
|
|
47236
|
+
if (this.forgotten !== forgotten && !again) {
|
|
47237
|
+
return await this.readInto(tags, true);
|
|
47238
|
+
}
|
|
46921
47239
|
for (const tag of tags) {
|
|
46922
47240
|
this.unread.delete(tag);
|
|
46923
47241
|
this.remember(
|
|
@@ -46933,6 +47251,9 @@ var TrackedTagMarkers = class {
|
|
|
46933
47251
|
return true;
|
|
46934
47252
|
} catch (error2) {
|
|
46935
47253
|
console.error("Error reading tag markers:", error2);
|
|
47254
|
+
if (this.forgotten !== forgotten && !again) {
|
|
47255
|
+
return this.readInto(tags, true);
|
|
47256
|
+
}
|
|
46936
47257
|
this.trackUnread(tags);
|
|
46937
47258
|
for (const tag of tags) {
|
|
46938
47259
|
this.unread.add(tag);
|
|
@@ -46946,8 +47267,119 @@ var TrackedTagMarkers = class {
|
|
|
46946
47267
|
}
|
|
46947
47268
|
takePendingRows(tag) {
|
|
46948
47269
|
const pending = this.pendingRows.get(tag);
|
|
47270
|
+
const logged = this.logOnly.get(tag);
|
|
46949
47271
|
this.pendingRows.delete(tag);
|
|
46950
|
-
|
|
47272
|
+
this.logOnly.delete(tag);
|
|
47273
|
+
return logged ? mergeMarkers(logged, pending) : pending;
|
|
47274
|
+
}
|
|
47275
|
+
/**
|
|
47276
|
+
* What this instance knows of `tag`: tracked, or from the log alone -
|
|
47277
|
+
* including rows that arrived while its first read is in flight.
|
|
47278
|
+
*/
|
|
47279
|
+
knownMarker(tag) {
|
|
47280
|
+
const tracked = this.tags.get(tag);
|
|
47281
|
+
if (tracked) {
|
|
47282
|
+
return tracked;
|
|
47283
|
+
}
|
|
47284
|
+
const logged = this.logOnly.get(tag);
|
|
47285
|
+
const pending = this.pendingRows.get(tag);
|
|
47286
|
+
return logged || pending ? mergeMarkers(logged, pending) : void 0;
|
|
47287
|
+
}
|
|
47288
|
+
/**
|
|
47289
|
+
* Keep `tag`'s log rows in `logOnly`, unless they cannot apply to an entry
|
|
47290
|
+
* held, `oldest` or newer (see {@link pruneLogged}), as bounded as the
|
|
47291
|
+
* tracked tags. Either way a row let go of takes what it knew with it, so
|
|
47292
|
+
* `completeSince` moves past it.
|
|
47293
|
+
*/
|
|
47294
|
+
rememberLogged(tag, marker, oldest) {
|
|
47295
|
+
this.logOnly.delete(tag);
|
|
47296
|
+
if (knownBy(marker, markerClock()) <= oldest) {
|
|
47297
|
+
this.raiseFloor(marker);
|
|
47298
|
+
return;
|
|
47299
|
+
}
|
|
47300
|
+
this.logOnly.set(tag, marker);
|
|
47301
|
+
for (const [first, dropped] of this.logOnly) {
|
|
47302
|
+
if (this.logOnly.size <= this.maxTrackedTags) {
|
|
47303
|
+
break;
|
|
47304
|
+
}
|
|
47305
|
+
this.logOnly.delete(first);
|
|
47306
|
+
this.raiseFloor(dropped);
|
|
47307
|
+
}
|
|
47308
|
+
}
|
|
47309
|
+
/**
|
|
47310
|
+
* Drop the `logOnly` rows that cannot apply to an entry held, `oldest` or
|
|
47311
|
+
* newer: every revalidation they name so far is no later than it. That
|
|
47312
|
+
* moves `completeSince` no further than `oldest`, which costs the entries
|
|
47313
|
+
* held nothing, and a profile's `expiredAt` still to come waits in
|
|
47314
|
+
* `laterFloors`. Without this, a deployment revalidating many distinct tags
|
|
47315
|
+
* filled `logOnly` with tags no entry here carries, and the evictions past
|
|
47316
|
+
* its bound kept moving `completeSince` up to the present.
|
|
47317
|
+
*/
|
|
47318
|
+
pruneLogged(oldest) {
|
|
47319
|
+
const at = markerClock();
|
|
47320
|
+
for (const [tag, marker] of this.logOnly) {
|
|
47321
|
+
if (knownBy(marker, at) <= oldest) {
|
|
47322
|
+
this.logOnly.delete(tag);
|
|
47323
|
+
this.raiseFloor(marker);
|
|
47324
|
+
}
|
|
47325
|
+
}
|
|
47326
|
+
}
|
|
47327
|
+
/**
|
|
47328
|
+
* The oldest entry timestamp of any registered store, `Infinity` with none:
|
|
47329
|
+
* early, never late, unless `scan`.
|
|
47330
|
+
*/
|
|
47331
|
+
oldestEntry(scan) {
|
|
47332
|
+
let oldest = Infinity;
|
|
47333
|
+
for (const { oldestTimestamp } of this.entryStores) {
|
|
47334
|
+
oldest = Math.min(oldest, oldestTimestamp(scan));
|
|
47335
|
+
}
|
|
47336
|
+
return oldest;
|
|
47337
|
+
}
|
|
47338
|
+
/**
|
|
47339
|
+
* Move `completeSince` past `marker`'s revalidations: what is no longer
|
|
47340
|
+
* known about a tag once its marker is dropped. A future `expiredAt` waits
|
|
47341
|
+
* in `laterFloors` until it is past.
|
|
47342
|
+
*/
|
|
47343
|
+
raiseFloor(marker) {
|
|
47344
|
+
const at = markerClock();
|
|
47345
|
+
this.floor = Math.max(this.floor, knownBy(marker, at));
|
|
47346
|
+
const { expiredAt } = marker;
|
|
47347
|
+
if (expiredAt !== void 0 && expiredAt > Math.max(at, this.floor)) {
|
|
47348
|
+
this.laterFloor(expiredAt);
|
|
47349
|
+
}
|
|
47350
|
+
}
|
|
47351
|
+
/**
|
|
47352
|
+
* Add `time` to `laterFloors`, in order. Past the tracked-tag bound the two
|
|
47353
|
+
* spans closest together are merged: from the first's start,
|
|
47354
|
+
* `completeSince` is the second's end, early - which only costs reads, and
|
|
47355
|
+
* for as short a time as any merge allows. Never later than its time, which
|
|
47356
|
+
* would let an entry expire unseen.
|
|
47357
|
+
*/
|
|
47358
|
+
laterFloor(time2) {
|
|
47359
|
+
const floors = this.laterFloors;
|
|
47360
|
+
let low = 0;
|
|
47361
|
+
let high = floors.length;
|
|
47362
|
+
while (low < high) {
|
|
47363
|
+
const mid = Math.floor((low + high) / 2);
|
|
47364
|
+
if (floors[mid][0] < time2) {
|
|
47365
|
+
low = mid + 1;
|
|
47366
|
+
} else {
|
|
47367
|
+
high = mid;
|
|
47368
|
+
}
|
|
47369
|
+
}
|
|
47370
|
+
if (low > 0 && floors[low - 1][1] >= time2 || floors[low]?.[0] === time2) {
|
|
47371
|
+
return;
|
|
47372
|
+
}
|
|
47373
|
+
floors.splice(low, 0, [time2, time2]);
|
|
47374
|
+
if (floors.length > this.maxTrackedTags) {
|
|
47375
|
+
let merge2 = 0;
|
|
47376
|
+
for (let i5 = 1; i5 < floors.length - 1; i5++) {
|
|
47377
|
+
if (floors[i5 + 1][1] - floors[i5][0] < floors[merge2 + 1][1] - floors[merge2][0]) {
|
|
47378
|
+
merge2 = i5;
|
|
47379
|
+
}
|
|
47380
|
+
}
|
|
47381
|
+
floors.splice(merge2, 2, [floors[merge2][0], floors[merge2 + 1][1]]);
|
|
47382
|
+
}
|
|
46951
47383
|
}
|
|
46952
47384
|
/** Record that `tag`'s marker was known from the table at `at`. */
|
|
46953
47385
|
known(tag, at) {
|
|
@@ -46964,6 +47396,7 @@ var TrackedTagMarkers = class {
|
|
|
46964
47396
|
if (this.tags.size <= this.maxTrackedTags) {
|
|
46965
47397
|
break;
|
|
46966
47398
|
}
|
|
47399
|
+
this.raiseFloor(this.tags.get(oldest));
|
|
46967
47400
|
this.tags.delete(oldest);
|
|
46968
47401
|
this.dueAt.delete(oldest);
|
|
46969
47402
|
this.unread.delete(oldest);
|
|
@@ -46976,7 +47409,18 @@ var TrackedTagMarkers = class {
|
|
|
46976
47409
|
}
|
|
46977
47410
|
}
|
|
46978
47411
|
};
|
|
47412
|
+
function knownBy(marker, at) {
|
|
47413
|
+
const { revalidatedAt, staleAt, expiredAt } = marker;
|
|
47414
|
+
return Math.max(
|
|
47415
|
+
revalidatedAt ?? -Infinity,
|
|
47416
|
+
staleAt ?? -Infinity,
|
|
47417
|
+
expiredAt !== void 0 && expiredAt <= at ? expiredAt : -Infinity
|
|
47418
|
+
);
|
|
47419
|
+
}
|
|
46979
47420
|
var MAX_LOG_PUT_MS = REVALIDATION_LOG_LOOKBACK_MS / 2;
|
|
47421
|
+
var ENTRY_SCAN_INTERVAL_MS = 6e4;
|
|
47422
|
+
var REFRESH_TICK_MS = 250;
|
|
47423
|
+
var REFRESH_PAUSE_MS = 1e3;
|
|
46980
47424
|
function mergeMarkers(local, remote) {
|
|
46981
47425
|
if (!local) {
|
|
46982
47426
|
return remote ?? {};
|
|
@@ -47036,61 +47480,16 @@ var CacheBucket = class {
|
|
|
47036
47480
|
}
|
|
47037
47481
|
};
|
|
47038
47482
|
|
|
47039
|
-
// src/adapter/cache-utils.ts
|
|
47040
|
-
function preprocessValue(value) {
|
|
47041
|
-
if (value == null) {
|
|
47042
|
-
return value;
|
|
47043
|
-
}
|
|
47044
|
-
if (Buffer.isBuffer(value)) {
|
|
47045
|
-
return {
|
|
47046
|
-
__type: "Buffer",
|
|
47047
|
-
base64: value.toString("base64")
|
|
47048
|
-
};
|
|
47049
|
-
}
|
|
47050
|
-
if (value instanceof Map) {
|
|
47051
|
-
const entries = {};
|
|
47052
|
-
for (const [key, val] of value.entries()) {
|
|
47053
|
-
entries[key] = preprocessValue(val);
|
|
47054
|
-
}
|
|
47055
|
-
return {
|
|
47056
|
-
__type: "Map",
|
|
47057
|
-
data: entries
|
|
47058
|
-
};
|
|
47059
|
-
}
|
|
47060
|
-
if (Array.isArray(value)) {
|
|
47061
|
-
return value.map((item) => preprocessValue(item));
|
|
47062
|
-
}
|
|
47063
|
-
if (typeof value === "object") {
|
|
47064
|
-
const processed = {};
|
|
47065
|
-
for (const [key, val] of Object.entries(value)) {
|
|
47066
|
-
processed[key] = preprocessValue(val);
|
|
47067
|
-
}
|
|
47068
|
-
return processed;
|
|
47069
|
-
}
|
|
47070
|
-
return value;
|
|
47071
|
-
}
|
|
47072
|
-
function serializeCacheValue(value) {
|
|
47073
|
-
const processed = preprocessValue(value);
|
|
47074
|
-
return JSON.stringify(processed);
|
|
47075
|
-
}
|
|
47076
|
-
function parseCacheValue(jsonString) {
|
|
47077
|
-
return JSON.parse(jsonString, (_key, val) => {
|
|
47078
|
-
if (val && typeof val === "object" && val.__type === "Map") {
|
|
47079
|
-
return new Map(Object.entries(val.data));
|
|
47080
|
-
}
|
|
47081
|
-
if (val && typeof val === "object" && val.__type === "Buffer") {
|
|
47082
|
-
return typeof val.base64 === "string" ? Buffer.from(val.base64, "base64") : Buffer.from(val.data);
|
|
47083
|
-
}
|
|
47084
|
-
if (val && typeof val === "object" && val.type === "Buffer" && Array.isArray(val.data)) {
|
|
47085
|
-
return Buffer.from(val.data);
|
|
47086
|
-
}
|
|
47087
|
-
return val;
|
|
47088
|
-
});
|
|
47089
|
-
}
|
|
47090
|
-
|
|
47091
47483
|
// src/adapter/use-cache-common.ts
|
|
47092
47484
|
var import_client_dynamodb2 = __toESM(require_dist_cjs20());
|
|
47093
47485
|
var import_debug5 = __toESM(require_src());
|
|
47486
|
+
|
|
47487
|
+
// src/runtime/tag-manifest.ts
|
|
47488
|
+
var TAG_MANIFEST_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
47489
|
+
"cdk-nextjs.use-cache.tag-manifest"
|
|
47490
|
+
);
|
|
47491
|
+
|
|
47492
|
+
// src/adapter/use-cache-common.ts
|
|
47094
47493
|
function isBuildPhase() {
|
|
47095
47494
|
return process.env.NEXT_PHASE === "phase-production-build";
|
|
47096
47495
|
}
|
|
@@ -47171,6 +47570,12 @@ var EntryLru = class {
|
|
|
47171
47570
|
maxBytes;
|
|
47172
47571
|
entries = /* @__PURE__ */ new Map();
|
|
47173
47572
|
bytes = 0;
|
|
47573
|
+
/**
|
|
47574
|
+
* No later than the oldest entry's `timestamp`: the last scan's answer, less
|
|
47575
|
+
* whatever was set since. Entries dropped since only make it early. See
|
|
47576
|
+
* {@link oldestBound}.
|
|
47577
|
+
*/
|
|
47578
|
+
lowest = Infinity;
|
|
47174
47579
|
get(key) {
|
|
47175
47580
|
const entry = this.entries.get(key);
|
|
47176
47581
|
if (entry) {
|
|
@@ -47187,6 +47592,7 @@ var EntryLru = class {
|
|
|
47187
47592
|
}
|
|
47188
47593
|
this.entries.set(key, entry);
|
|
47189
47594
|
this.bytes += size;
|
|
47595
|
+
this.lowest = Math.min(this.lowest, entry.timestamp);
|
|
47190
47596
|
for (const [oldestKey, oldest] of this.entries) {
|
|
47191
47597
|
if (this.bytes <= this.maxBytes) {
|
|
47192
47598
|
break;
|
|
@@ -47200,7 +47606,58 @@ var EntryLru = class {
|
|
|
47200
47606
|
if (existing) {
|
|
47201
47607
|
this.entries.delete(key);
|
|
47202
47608
|
this.bytes -= sizeOf2(key, existing);
|
|
47609
|
+
if (this.entries.size === 0) {
|
|
47610
|
+
this.lowest = Infinity;
|
|
47611
|
+
}
|
|
47612
|
+
}
|
|
47613
|
+
}
|
|
47614
|
+
/**
|
|
47615
|
+
* The `timestamp` of the oldest entry held, `Infinity` with none, dropping
|
|
47616
|
+
* on the way the entries past `revalidate` at `at` (`now()`): a handler
|
|
47617
|
+
* that drops them when next read would otherwise hold them until then, and
|
|
47618
|
+
* they would count here meanwhile. A scan, so asked once in a while: in
|
|
47619
|
+
* between, {@link oldestBound} answers.
|
|
47620
|
+
*/
|
|
47621
|
+
oldestTimestamp(at = -Infinity) {
|
|
47622
|
+
let oldest = Infinity;
|
|
47623
|
+
for (const [key, entry] of this.entries) {
|
|
47624
|
+
if (isPastRevalidate(entry, at)) {
|
|
47625
|
+
this.delete(key);
|
|
47626
|
+
} else {
|
|
47627
|
+
oldest = Math.min(oldest, entry.timestamp);
|
|
47628
|
+
}
|
|
47629
|
+
}
|
|
47630
|
+
this.lowest = oldest;
|
|
47631
|
+
return oldest;
|
|
47632
|
+
}
|
|
47633
|
+
/**
|
|
47634
|
+
* No later than {@link oldestTimestamp}, without its scan: as of the last
|
|
47635
|
+
* one, less the entries set since. `Infinity` with none held. Drops the
|
|
47636
|
+
* entries past `revalidate` at `at` from the least recently used end, up to
|
|
47637
|
+
* the first that isn't, so {@link holdsLive} doesn't walk past them on every
|
|
47638
|
+
* request until the next scan.
|
|
47639
|
+
*/
|
|
47640
|
+
oldestBound(at = -Infinity) {
|
|
47641
|
+
for (const [key, entry] of this.entries) {
|
|
47642
|
+
if (!isPastRevalidate(entry, at)) {
|
|
47643
|
+
break;
|
|
47644
|
+
}
|
|
47645
|
+
this.delete(key);
|
|
47203
47646
|
}
|
|
47647
|
+
return this.entries.size === 0 ? Infinity : this.lowest;
|
|
47648
|
+
}
|
|
47649
|
+
/**
|
|
47650
|
+
* Whether an entry not past `revalidate` at `at` is held. Usually the first
|
|
47651
|
+
* entry answers; the expired ones it passes are dropped by the scan
|
|
47652
|
+
* {@link oldestTimestamp} makes, not here, so asking changes nothing.
|
|
47653
|
+
*/
|
|
47654
|
+
holdsLive(at) {
|
|
47655
|
+
for (const entry of this.entries.values()) {
|
|
47656
|
+
if (!isPastRevalidate(entry, at)) {
|
|
47657
|
+
return true;
|
|
47658
|
+
}
|
|
47659
|
+
}
|
|
47660
|
+
return false;
|
|
47204
47661
|
}
|
|
47205
47662
|
get size() {
|
|
47206
47663
|
return this.entries.size;
|
|
@@ -47209,14 +47666,39 @@ var EntryLru = class {
|
|
|
47209
47666
|
function sizeOf2(key, entry) {
|
|
47210
47667
|
return entry.value.byteLength + key.length;
|
|
47211
47668
|
}
|
|
47669
|
+
function isPastRevalidate(stored, at) {
|
|
47670
|
+
return at > stored.timestamp + stored.revalidate * 1e3;
|
|
47671
|
+
}
|
|
47212
47672
|
var DEFAULT_MEMORY_BYTES = 50 * 1024 * 1024;
|
|
47213
|
-
|
|
47673
|
+
var TAG_TABLE_REQUEST_TIMEOUT_MS = 3e3;
|
|
47674
|
+
async function awaitIf(wait, work, failure) {
|
|
47675
|
+
const logged = work.catch((error2) => {
|
|
47676
|
+
console.error(failure, error2);
|
|
47677
|
+
});
|
|
47678
|
+
if (wait) {
|
|
47679
|
+
await logged;
|
|
47680
|
+
}
|
|
47681
|
+
}
|
|
47682
|
+
function tagMethods(tags, options = {}) {
|
|
47214
47683
|
return {
|
|
47215
|
-
|
|
47216
|
-
|
|
47217
|
-
|
|
47218
|
-
|
|
47219
|
-
|
|
47684
|
+
// Started, not awaited, while the instance keeps up, unless `blocking`.
|
|
47685
|
+
// Next.js awaits `refreshTags` inside the first `'use cache'` lookup of a
|
|
47686
|
+
// request (`use-cache-wrapper`), and a staged render ends its static stage
|
|
47687
|
+
// on a timer: a log `Query` there pushed the entry out of the static
|
|
47688
|
+
// stage, so a cached navigation stored the page segment without it. What
|
|
47689
|
+
// the query finds applies from the next request. Awaited once the instance
|
|
47690
|
+
// is `behind`, which the runtime settles before a page render starts
|
|
47691
|
+
// (`catchUp`): the first request after an idle or frozen spell would
|
|
47692
|
+
// otherwise serve whatever was revalidated elsewhere meanwhile. A page's
|
|
47693
|
+
// render just after `catchUp` gets a grace more (`behindInRender`), so a
|
|
47694
|
+
// request that came in just short of the line doesn't cross it inside
|
|
47695
|
+
// the render. Not even then while the instance holds no entry the query
|
|
47696
|
+
// could expire (`holdsEntries`), with a `refreshIntervalMs` of `0` too.
|
|
47697
|
+
refreshTags: () => awaitIf(
|
|
47698
|
+
options.blocking === true || tags.behindInRender && tags.holdsEntries,
|
|
47699
|
+
tags.refresh(),
|
|
47700
|
+
"Error refreshing cache tags:"
|
|
47701
|
+
),
|
|
47220
47702
|
async updateTags(revalidatedTags, durations) {
|
|
47221
47703
|
await tags.update(revalidatedTags, durations);
|
|
47222
47704
|
}
|
|
@@ -47233,7 +47715,6 @@ function lazyHandler(create) {
|
|
|
47233
47715
|
updateTags: (tags, durations) => instance().updateTags(tags, durations)
|
|
47234
47716
|
};
|
|
47235
47717
|
}
|
|
47236
|
-
var TAG_MANIFEST_SYMBOL = /* @__PURE__ */ Symbol.for("cdk-nextjs.use-cache.tag-manifest");
|
|
47237
47718
|
function sharedTagManifest() {
|
|
47238
47719
|
const global = globalThis;
|
|
47239
47720
|
if (!global[TAG_MANIFEST_SYMBOL]) {
|
|
@@ -47243,12 +47724,24 @@ function sharedTagManifest() {
|
|
|
47243
47724
|
DEFAULT_TAG_REFRESH_MS
|
|
47244
47725
|
);
|
|
47245
47726
|
if (config.tableName && !isBuildPhase()) {
|
|
47246
|
-
const client = new import_client_dynamodb2.DynamoDBClient({
|
|
47727
|
+
const client = new import_client_dynamodb2.DynamoDBClient({
|
|
47728
|
+
region: config.region,
|
|
47729
|
+
// Without `throwOnRequestTimeout` the timeout only logs a warning. It
|
|
47730
|
+
// ends once the response headers arrive: `socketTimeout`, an
|
|
47731
|
+
// inactivity timer kept until the request closes, covers the body.
|
|
47732
|
+
requestHandler: {
|
|
47733
|
+
requestTimeout: TAG_TABLE_REQUEST_TIMEOUT_MS,
|
|
47734
|
+
throwOnRequestTimeout: true,
|
|
47735
|
+
socketTimeout: TAG_TABLE_REQUEST_TIMEOUT_MS
|
|
47736
|
+
}
|
|
47737
|
+
});
|
|
47247
47738
|
global[TAG_MANIFEST_SYMBOL] = new TrackedTagMarkers({
|
|
47248
47739
|
markers: new TagMarkerTable(client, config.tableName, config.buildId),
|
|
47249
47740
|
log: new RevalidationLog(client, config.tableName, config.buildId),
|
|
47250
47741
|
refreshIntervalMs,
|
|
47251
|
-
debug: (0, import_debug5.default)("cdk-nextjs:cache-handler:tags")
|
|
47742
|
+
debug: (0, import_debug5.default)("cdk-nextjs:cache-handler:tags"),
|
|
47743
|
+
// Set in every Lambda runtime; a container is never frozen.
|
|
47744
|
+
canFreeze: process.env.AWS_LAMBDA_FUNCTION_NAME !== void 0
|
|
47252
47745
|
});
|
|
47253
47746
|
} else {
|
|
47254
47747
|
if (!isBuildPhase()) {
|
|
@@ -47278,9 +47771,6 @@ function bucketFromEnv() {
|
|
|
47278
47771
|
}
|
|
47279
47772
|
return new CacheBucket(new import_client_s32.S3Client({ region }), bucketName);
|
|
47280
47773
|
}
|
|
47281
|
-
function isPastRevalidate(stored, at) {
|
|
47282
|
-
return at > stored.timestamp + stored.revalidate * 1e3;
|
|
47283
|
-
}
|
|
47284
47774
|
function isPastExpire(stored, at) {
|
|
47285
47775
|
return at > stored.timestamp + stored.expire * 1e3;
|
|
47286
47776
|
}
|
|
@@ -47293,7 +47783,7 @@ function createRemoteUseCacheHandler(options = {}) {
|
|
|
47293
47783
|
);
|
|
47294
47784
|
const pending = new PendingSets();
|
|
47295
47785
|
const debug = (0, import_debug6.default)("cdk-nextjs:cache-handler:use-cache:remote");
|
|
47296
|
-
const s3Key = (cacheKey) => useCacheS3Key(buildId,
|
|
47786
|
+
const s3Key = (cacheKey) => useCacheS3Key(buildId, sha256Hex(cacheKey));
|
|
47297
47787
|
async function readFromS3(cacheKey) {
|
|
47298
47788
|
if (!bucket) {
|
|
47299
47789
|
return void 0;
|
|
@@ -47400,7 +47890,13 @@ function createRemoteUseCacheHandler(options = {}) {
|
|
|
47400
47890
|
done();
|
|
47401
47891
|
await writeToS3(cacheKey, stored);
|
|
47402
47892
|
},
|
|
47403
|
-
|
|
47893
|
+
// Waits for the log query on every request: an entry read from S3 may
|
|
47894
|
+
// predate this instance, so what the query says applies to it at once.
|
|
47895
|
+
...tagMethods(tags, { blocking: true }),
|
|
47896
|
+
async getExpiration(implicitTags) {
|
|
47897
|
+
await tags.ensure(implicitTags);
|
|
47898
|
+
return tags.expiration(implicitTags);
|
|
47899
|
+
}
|
|
47404
47900
|
};
|
|
47405
47901
|
}
|
|
47406
47902
|
var use_cache_remote_handler_default = lazyHandler(() => createRemoteUseCacheHandler());
|