cdk-nextjs 0.7.6 → 0.7.8
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 +3 -3
- 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 +154 -73
- package/lib/adapter/adapter.mjs +12 -3
- package/lib/adapter/aws-cache-store.d.ts +221 -3
- package/lib/adapter/aws-cache-store.js +481 -35
- package/lib/adapter/aws-cache-store.test.js +706 -3
- package/lib/adapter/cache-handler.d.ts +5 -3
- package/lib/adapter/cache-handler.js +16 -10
- package/lib/adapter/cache-handler.mjs +517 -49
- package/lib/adapter/cache-handler.test.js +44 -1
- package/lib/adapter/cache-utils.d.ts +22 -0
- package/lib/adapter/cache-utils.js +44 -1
- package/lib/adapter/function-groups.js +17 -4
- package/lib/adapter/function-groups.test.js +33 -1
- package/lib/adapter/memory-cache-handler.d.ts +5 -1
- package/lib/adapter/memory-cache-handler.js +6 -2
- package/lib/adapter/s3-cache-handler.d.ts +12 -1
- package/lib/adapter/s3-cache-handler.js +30 -8
- package/lib/adapter/s3-cache-handler.test.js +84 -2
- package/lib/adapter/use-cache-common.d.ts +66 -1
- package/lib/adapter/use-cache-common.js +142 -19
- package/lib/adapter/use-cache-common.test.js +174 -1
- package/lib/adapter/use-cache-default-handler.js +36 -6
- package/lib/adapter/use-cache-default-handler.mjs +553 -47
- package/lib/adapter/use-cache-handlers.test.js +247 -7
- package/lib/adapter/use-cache-remote-handler.js +9 -7
- package/lib/adapter/use-cache-remote-handler.mjs +542 -45
- 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.js +1 -1
- 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 +20 -4
- package/lib/runtime/core.test.js +17 -1
- package/lib/runtime/image-utils.d.ts +8 -9
- package/lib/runtime/image-utils.js +16 -1
- package/lib/runtime/image-utils.test.js +13 -1
- package/lib/runtime/image.js +9 -3
- package/lib/runtime/lambda.mjs +66 -18
- package/lib/runtime/server.mjs +66 -18
- package/lib/runtime/tag-manifest.d.ts +40 -0
- package/lib/runtime/tag-manifest.js +59 -0
- package/lib/runtime/tag-manifest.test.d.ts +1 -0
- package/lib/runtime/tag-manifest.test.js +47 -0
- package/package.json +3 -3
- package/scripts/e2e-deploy.sh +7 -2
- package/scripts/e2e-harness/README.md +72 -35
- package/scripts/e2e-harness/screen.mjs +148 -7
- package/scripts/playwright-install-deps.sh +40 -0
|
@@ -74119,6 +74119,22 @@ var INIT_CACHE_TAG_MANIFEST = "_cdk-nextjs-tag-manifest.json";
|
|
|
74119
74119
|
function getTags(ctx) {
|
|
74120
74120
|
return "tags" in ctx ? ctx.tags : void 0;
|
|
74121
74121
|
}
|
|
74122
|
+
function getCacheControl(ctx) {
|
|
74123
|
+
return "cacheControl" in ctx ? storedCacheControl(ctx.cacheControl) : void 0;
|
|
74124
|
+
}
|
|
74125
|
+
function storedCacheControl(value) {
|
|
74126
|
+
if (typeof value !== "object" || value === null) {
|
|
74127
|
+
return void 0;
|
|
74128
|
+
}
|
|
74129
|
+
const { revalidate, expire } = value;
|
|
74130
|
+
if (revalidate !== false && !(typeof revalidate === "number" && Number.isFinite(revalidate) && revalidate > 0)) {
|
|
74131
|
+
return void 0;
|
|
74132
|
+
}
|
|
74133
|
+
if (expire !== void 0 && !Number.isFinite(expire)) {
|
|
74134
|
+
return void 0;
|
|
74135
|
+
}
|
|
74136
|
+
return { revalidate, expire };
|
|
74137
|
+
}
|
|
74122
74138
|
|
|
74123
74139
|
// src/adapter/local-file-cache-handler.ts
|
|
74124
74140
|
var import_debug = __toESM(require_src());
|
|
@@ -74226,7 +74242,23 @@ var import_debug7 = __toESM(require_src());
|
|
|
74226
74242
|
// src/adapter/aws-cache-store.ts
|
|
74227
74243
|
var import_client_dynamodb = __toESM(require_dist_cjs19());
|
|
74228
74244
|
var import_client_s3 = __toESM(require_dist_cjs20());
|
|
74245
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
74229
74246
|
import { join as join6 } from "node:path";
|
|
74247
|
+
|
|
74248
|
+
// src/runtime/tag-manifest.ts
|
|
74249
|
+
var OUTSIDE_REQUEST_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
74250
|
+
"cdk-nextjs.outside-request-context"
|
|
74251
|
+
);
|
|
74252
|
+
function outsideRequest(fn) {
|
|
74253
|
+
const global = globalThis;
|
|
74254
|
+
const run = global[OUTSIDE_REQUEST_SYMBOL];
|
|
74255
|
+
return run ? run(fn) : fn();
|
|
74256
|
+
}
|
|
74257
|
+
var TAG_MANIFEST_SYMBOL = /* @__PURE__ */ Symbol.for(
|
|
74258
|
+
"cdk-nextjs.use-cache.tag-manifest"
|
|
74259
|
+
);
|
|
74260
|
+
|
|
74261
|
+
// src/adapter/aws-cache-store.ts
|
|
74230
74262
|
function resolveAwsCacheConfig() {
|
|
74231
74263
|
return {
|
|
74232
74264
|
bucketName: process.env.CDK_NEXTJS_CACHE_BUCKET_NAME || "",
|
|
@@ -74295,6 +74327,7 @@ function markerOf(item) {
|
|
|
74295
74327
|
}
|
|
74296
74328
|
var BATCH_GET_MAX_KEYS = 100;
|
|
74297
74329
|
var DEFAULT_TAG_REFRESH_MS = 1e3;
|
|
74330
|
+
var TAG_REFRESH_GRACE_MS = 1e3;
|
|
74298
74331
|
var MAX_SORT_KEY_BYTES = 1024;
|
|
74299
74332
|
function hashedTag(tag) {
|
|
74300
74333
|
return `#${sha256Hex(tag)}`;
|
|
@@ -74509,10 +74542,64 @@ var TrackedTagMarkers = class {
|
|
|
74509
74542
|
* result rather than dropped.
|
|
74510
74543
|
*/
|
|
74511
74544
|
pendingRows = /* @__PURE__ */ new Map();
|
|
74545
|
+
/**
|
|
74546
|
+
* Log rows for tags not tracked and not being read, merged by tag, oldest
|
|
74547
|
+
* first: what the log has said about them since {@link completeSince}. Taken
|
|
74548
|
+
* into a tag's marker when it is read or written.
|
|
74549
|
+
*/
|
|
74550
|
+
logOnly = /* @__PURE__ */ new Map();
|
|
74551
|
+
/** See {@link completeSince}. */
|
|
74552
|
+
floor;
|
|
74553
|
+
/**
|
|
74554
|
+
* The future `expiredAt`s of markers dropped since, as `[from, to]`, in
|
|
74555
|
+
* order: once `from` is past, {@link completeSince} moves to `to`, and not
|
|
74556
|
+
* before, since only then does it expire anything. `from` and `to` are the
|
|
74557
|
+
* same until spans are merged for space ({@link laterFloor}).
|
|
74558
|
+
*/
|
|
74559
|
+
laterFloors = [];
|
|
74560
|
+
/** The in-memory entry stores judged by {@link completeSince}. */
|
|
74561
|
+
entryStores = [];
|
|
74562
|
+
/** How many times {@link forget} ran: a read started before one is made again. */
|
|
74563
|
+
forgotten = 0;
|
|
74512
74564
|
/** Tracked tags whose read failed, for the next refresh to read again. */
|
|
74513
74565
|
unread = /* @__PURE__ */ new Set();
|
|
74566
|
+
/**
|
|
74567
|
+
* Set inside a page render {@link catchUp} let go, for that render alone.
|
|
74568
|
+
* See {@link behindInRender}.
|
|
74569
|
+
*/
|
|
74570
|
+
caughtUpRender = new AsyncLocalStorage();
|
|
74571
|
+
/** When `sync` last scanned the entry stores ({@link oldestEntry}). */
|
|
74572
|
+
lastEntryScan = -Infinity;
|
|
74573
|
+
/**
|
|
74574
|
+
* No `logOnly` row is known by it or earlier, so a {@link pruneLogged}
|
|
74575
|
+
* against it or earlier drops nothing: a marker is only ever known by later,
|
|
74576
|
+
* as its `expiredAt` passes.
|
|
74577
|
+
*/
|
|
74578
|
+
prunedAgainst = -Infinity;
|
|
74579
|
+
/** When the last refresh started, or was skipped. */
|
|
74514
74580
|
lastRefresh = -Infinity;
|
|
74581
|
+
/**
|
|
74582
|
+
* The refresh in flight. One left open across a freeze comes back after the
|
|
74583
|
+
* thaw with what it read before, so {@link refresh} waits for it and then
|
|
74584
|
+
* for a fresh one, rather than the runtime waiting for it before the freeze:
|
|
74585
|
+
* that would bill every Lambda invocation for the query's tail.
|
|
74586
|
+
*/
|
|
74515
74587
|
refreshing;
|
|
74588
|
+
/**
|
|
74589
|
+
* When {@link refreshing} last showed the process running: a timer ticks
|
|
74590
|
+
* while it is in flight, and a tick that comes late, or none at all, means
|
|
74591
|
+
* the process was paused - frozen - since it started.
|
|
74592
|
+
*/
|
|
74593
|
+
lastTick = -Infinity;
|
|
74594
|
+
/** Whether a tick came late since {@link refreshing} started. */
|
|
74595
|
+
pausedWhileRefreshing = false;
|
|
74596
|
+
ticker;
|
|
74597
|
+
canFreeze;
|
|
74598
|
+
/**
|
|
74599
|
+
* When the last refresh settled, either way, unless it spanned a pause: what
|
|
74600
|
+
* it knows is from then. See {@link behind}.
|
|
74601
|
+
*/
|
|
74602
|
+
settledAt;
|
|
74516
74603
|
/**
|
|
74517
74604
|
* Where the next log query starts (`clock()`): the last successful one's
|
|
74518
74605
|
* start, less {@link REVALIDATION_LOG_LOOKBACK_MS}.
|
|
@@ -74534,20 +74621,68 @@ var TrackedTagMarkers = class {
|
|
|
74534
74621
|
this.random = options.random ?? Math.random;
|
|
74535
74622
|
this.debug = options.debug ?? (() => {
|
|
74536
74623
|
});
|
|
74624
|
+
this.canFreeze = options.canFreeze ?? false;
|
|
74537
74625
|
const at = this.clock();
|
|
74538
74626
|
this.cursor = at - REVALIDATION_LOG_LOOKBACK_MS;
|
|
74539
74627
|
this.lastLogRead = at;
|
|
74628
|
+
this.settledAt = at;
|
|
74629
|
+
this.floor = markerClock();
|
|
74540
74630
|
}
|
|
74541
74631
|
/** `tag`'s marker as this instance knows it, if it tracks `tag`. */
|
|
74542
74632
|
get(tag) {
|
|
74543
74633
|
return this.tags.get(tag);
|
|
74544
74634
|
}
|
|
74635
|
+
/**
|
|
74636
|
+
* Since when (`markerClock()`) this instance knows of every revalidation of
|
|
74637
|
+
* every tag, tracked or not: tracked ones from their markers and the log,
|
|
74638
|
+
* the others from the log alone (`logOnly`). So an entry created at or after
|
|
74639
|
+
* it can be judged by {@link state} and {@link expiration} without
|
|
74640
|
+
* {@link ensure} reading anything - which matters inside a `'use cache'`
|
|
74641
|
+
* lookup, where Next.js waits for the answer and a read pushes the entry out
|
|
74642
|
+
* of the static stage of a staged render.
|
|
74643
|
+
*
|
|
74644
|
+
* Starts at construction: the log is followed from before it. Moves to now
|
|
74645
|
+
* when the tracked markers are forgotten, and past the revalidations of
|
|
74646
|
+
* whatever else it lets go of - a marker evicted for space, a log row
|
|
74647
|
+
* pruned - which are no longer known: to its `expiredAt` only once that is
|
|
74648
|
+
* past, since a profile's `expire` can be a year out, and moving there at
|
|
74649
|
+
* once would turn the read-free path off for the instance's life.
|
|
74650
|
+
*
|
|
74651
|
+
* A row is pruned only once it is no later than every entry held, so that
|
|
74652
|
+
* costs those entries nothing. An entry stored afterwards with an older
|
|
74653
|
+
* `timestamp` - Next.js stamps the start of its generation, which can
|
|
74654
|
+
* outlast a lookback - is older than this too, and is read first.
|
|
74655
|
+
*
|
|
74656
|
+
* It does not cover a revalidation older than the log's first lookback
|
|
74657
|
+
* whose `expiredAt` is still to come, which would expire an entry created
|
|
74658
|
+
* after it. So the `'use cache'` handler reads an entry's tags in the
|
|
74659
|
+
* background as it stores it.
|
|
74660
|
+
*/
|
|
74661
|
+
get completeSince() {
|
|
74662
|
+
const at = markerClock();
|
|
74663
|
+
while (this.laterFloors.length > 0 && this.laterFloors[0][0] <= at) {
|
|
74664
|
+
this.floor = Math.max(this.floor, this.laterFloors.shift()[1]);
|
|
74665
|
+
}
|
|
74666
|
+
return this.floor;
|
|
74667
|
+
}
|
|
74668
|
+
/**
|
|
74669
|
+
* Register an in-memory store whose entries are judged by
|
|
74670
|
+
* {@link completeSince}, by the `timestamp` of the oldest entry it holds:
|
|
74671
|
+
* `logOnly` keeps the rows that could still apply to one of them, and no
|
|
74672
|
+
* others. With none registered it keeps nothing. Asked with `scan` false,
|
|
74673
|
+
* `oldestTimestamp` may answer early, but never late, without a scan.
|
|
74674
|
+
*/
|
|
74675
|
+
judgeEntriesOf(oldestTimestamp, holdsAny = () => oldestTimestamp(false) < Infinity) {
|
|
74676
|
+
this.entryStores.push({ oldestTimestamp, holdsAny });
|
|
74677
|
+
}
|
|
74545
74678
|
/**
|
|
74546
74679
|
* Track `tag` with `marker`: what this instance just wrote, which it knows
|
|
74547
74680
|
* without reading it back.
|
|
74548
74681
|
*/
|
|
74549
74682
|
set(tag, marker) {
|
|
74550
|
-
this.
|
|
74683
|
+
const logged = this.logOnly.get(tag);
|
|
74684
|
+
this.logOnly.delete(tag);
|
|
74685
|
+
this.remember(tag, mergeMarkers(marker, logged));
|
|
74551
74686
|
this.known(tag, this.clock());
|
|
74552
74687
|
}
|
|
74553
74688
|
/**
|
|
@@ -74600,27 +74735,115 @@ var TrackedTagMarkers = class {
|
|
|
74600
74735
|
const unread = [];
|
|
74601
74736
|
for (const tag of new Set(tags)) {
|
|
74602
74737
|
const inFlight = this.reading.get(tag);
|
|
74603
|
-
if (
|
|
74604
|
-
waits.push(inFlight);
|
|
74605
|
-
} else if (this.tags.has(tag)) {
|
|
74738
|
+
if (this.tags.has(tag)) {
|
|
74606
74739
|
this.touch(tag);
|
|
74740
|
+
} else if (inFlight) {
|
|
74741
|
+
waits.push(inFlight);
|
|
74607
74742
|
} else {
|
|
74608
74743
|
unread.push(tag);
|
|
74609
74744
|
}
|
|
74610
74745
|
}
|
|
74611
74746
|
if (unread.length > 0) {
|
|
74612
|
-
|
|
74613
|
-
for (const tag of unread) {
|
|
74614
|
-
this.reading.delete(tag);
|
|
74615
|
-
}
|
|
74616
|
-
});
|
|
74617
|
-
for (const tag of unread) {
|
|
74618
|
-
this.reading.set(tag, read);
|
|
74619
|
-
}
|
|
74620
|
-
waits.push(read);
|
|
74747
|
+
waits.push(this.startRead(unread));
|
|
74621
74748
|
}
|
|
74622
74749
|
await Promise.all(waits);
|
|
74623
74750
|
}
|
|
74751
|
+
/**
|
|
74752
|
+
* {@link readInto} `tags`, as the read of each that whoever needs one of
|
|
74753
|
+
* them next waits on ({@link reading}). Never rejects: a failed read leaves
|
|
74754
|
+
* the tags tracked.
|
|
74755
|
+
*/
|
|
74756
|
+
startRead(tags) {
|
|
74757
|
+
const read = this.readInto(tags).then(() => {
|
|
74758
|
+
for (const tag of tags) {
|
|
74759
|
+
this.reading.delete(tag);
|
|
74760
|
+
}
|
|
74761
|
+
});
|
|
74762
|
+
for (const tag of tags) {
|
|
74763
|
+
this.reading.set(tag, read);
|
|
74764
|
+
}
|
|
74765
|
+
return read;
|
|
74766
|
+
}
|
|
74767
|
+
/**
|
|
74768
|
+
* Whether the last refresh settled more than `refreshIntervalMs` plus
|
|
74769
|
+
* {@link TAG_REFRESH_GRACE_MS} ago - one that spanned a freeze counts from
|
|
74770
|
+
* when it started: after the instance sat idle or frozen - or always, with
|
|
74771
|
+
* a `refreshIntervalMs` of `0`. Answering from what it knows then could
|
|
74772
|
+
* serve an entry revalidated elsewhere since, however long ago that was, so
|
|
74773
|
+
* `refreshTags` waits for the refresh, and the runtime waits for it before
|
|
74774
|
+
* a page render starts ({@link catchUp}).
|
|
74775
|
+
*
|
|
74776
|
+
* From when it settled, not when it started, so a slow query the runtime
|
|
74777
|
+
* waited for before the render does not leave the instance behind inside
|
|
74778
|
+
* it. A failed one counts too, so one request does not wait for two: the
|
|
74779
|
+
* same "assume the entry is valid" a failed read answers with, until the
|
|
74780
|
+
* next refresh, at most the interval and the grace later.
|
|
74781
|
+
*/
|
|
74782
|
+
get behind() {
|
|
74783
|
+
return this.log !== void 0 && // `0` asks before every check, so every check waits for the answer.
|
|
74784
|
+
(this.refreshIntervalMs === 0 || this.overdue(this.settledAt));
|
|
74785
|
+
}
|
|
74786
|
+
/**
|
|
74787
|
+
* {@link behind}, as `refreshTags` asks it: never inside a page's render
|
|
74788
|
+
* {@link catchUp} caught up, since the runtime settled it for that request
|
|
74789
|
+
* before the render started, and a wait inside a `'use cache'` lookup is
|
|
74790
|
+
* what it settled it to avoid - a render crossing the line partway through
|
|
74791
|
+
* would otherwise wait after all. So a revalidation elsewhere can go unseen
|
|
74792
|
+
* for a render's duration on top of the usual window.
|
|
74793
|
+
*
|
|
74794
|
+
* A route handler, which no catch-up precedes, waits at the plain line.
|
|
74795
|
+
*/
|
|
74796
|
+
get behindInRender() {
|
|
74797
|
+
return this.refreshIntervalMs !== 0 && this.caughtUpRender.getStore() ? false : this.behind;
|
|
74798
|
+
}
|
|
74799
|
+
/**
|
|
74800
|
+
* Whether a registered store ({@link judgeEntriesOf}) holds any entry: one
|
|
74801
|
+
* a refresh could expire. With none there is nothing to wait for even when
|
|
74802
|
+
* the instance is {@link behind}, since whatever is stored from here on is
|
|
74803
|
+
* newer than what the log would say - which covers the first `'use cache'`
|
|
74804
|
+
* call of a process, which creates the handler inside the render.
|
|
74805
|
+
*/
|
|
74806
|
+
get holdsEntries() {
|
|
74807
|
+
return this.entryStores.some((store) => store.holdsAny());
|
|
74808
|
+
}
|
|
74809
|
+
/**
|
|
74810
|
+
* {@link refresh}, waited for when the instance is {@link behind}. The
|
|
74811
|
+
* runtime calls it before handing a page request to Next.js, outside the
|
|
74812
|
+
* staged render whose static stage a wait inside a `'use cache'` lookup
|
|
74813
|
+
* would cut short. Only for the `'use cache'` handler's entries
|
|
74814
|
+
* ({@link judgeEntriesOf}): the other caches wait for the refresh where they
|
|
74815
|
+
* read anyway. Not with a `refreshIntervalMs` of `0` either, where
|
|
74816
|
+
* `refreshTags` asks again regardless. Then `render`, marked as caught up
|
|
74817
|
+
* for ({@link behindInRender}). Rejects only as `render` does.
|
|
74818
|
+
*/
|
|
74819
|
+
async catchUp(render) {
|
|
74820
|
+
const log = (error2) => console.error("Error refreshing cache tags:", error2);
|
|
74821
|
+
if (this.refreshIntervalMs > 0 && this.behind) {
|
|
74822
|
+
if (!this.holdsEntries) {
|
|
74823
|
+
return render();
|
|
74824
|
+
}
|
|
74825
|
+
await this.refresh().catch(log);
|
|
74826
|
+
} else if (this.refreshIntervalMs > 0) {
|
|
74827
|
+
void this.refresh().catch(log);
|
|
74828
|
+
}
|
|
74829
|
+
return this.caughtUpRender.run(true, render);
|
|
74830
|
+
}
|
|
74831
|
+
/**
|
|
74832
|
+
* Run `fn` outside the request's context (`outsideRequest`), keeping only
|
|
74833
|
+
* whether the render was {@link catchUp caught up}: a cache handler method
|
|
74834
|
+
* called from inside a staged render, whose clock reads and AWS SDK calls
|
|
74835
|
+
* would otherwise end its static stage.
|
|
74836
|
+
*/
|
|
74837
|
+
outsideRender(fn) {
|
|
74838
|
+
const caughtUp = this.caughtUpRender.getStore();
|
|
74839
|
+
return outsideRequest(
|
|
74840
|
+
() => caughtUp ? this.caughtUpRender.run(caughtUp, fn) : fn()
|
|
74841
|
+
);
|
|
74842
|
+
}
|
|
74843
|
+
/** Whether `at` is more than the interval and the grace ago. */
|
|
74844
|
+
overdue(at) {
|
|
74845
|
+
return this.clock() - at > this.refreshIntervalMs + TAG_REFRESH_GRACE_MS;
|
|
74846
|
+
}
|
|
74624
74847
|
/**
|
|
74625
74848
|
* Catch up on revalidations other instances ran, at most once per
|
|
74626
74849
|
* `refreshIntervalMs`. See {@link TrackedTagMarkers} for the cost.
|
|
@@ -74631,6 +74854,9 @@ var TrackedTagMarkers = class {
|
|
|
74631
74854
|
return;
|
|
74632
74855
|
}
|
|
74633
74856
|
if (this.refreshing) {
|
|
74857
|
+
if (this.behind && this.pausedSinceRefreshStarted()) {
|
|
74858
|
+
return this.refreshing.then(() => this.refresh());
|
|
74859
|
+
}
|
|
74634
74860
|
return this.refreshing;
|
|
74635
74861
|
}
|
|
74636
74862
|
const at = this.clock();
|
|
@@ -74638,14 +74864,47 @@ var TrackedTagMarkers = class {
|
|
|
74638
74864
|
return;
|
|
74639
74865
|
}
|
|
74640
74866
|
this.lastRefresh = at;
|
|
74641
|
-
if (this.tags.size === 0) {
|
|
74867
|
+
if (this.tags.size === 0 && this.entryStores.length === 0) {
|
|
74642
74868
|
return;
|
|
74643
74869
|
}
|
|
74870
|
+
this.startTicking();
|
|
74644
74871
|
this.refreshing = this.sync(at, log).finally(() => {
|
|
74872
|
+
this.settledAt = Math.max(
|
|
74873
|
+
this.settledAt,
|
|
74874
|
+
this.pausedSinceRefreshStarted() ? this.lastRefresh : this.clock()
|
|
74875
|
+
);
|
|
74876
|
+
this.stopTicking();
|
|
74645
74877
|
this.refreshing = void 0;
|
|
74646
74878
|
});
|
|
74647
74879
|
return this.refreshing;
|
|
74648
74880
|
}
|
|
74881
|
+
startTicking() {
|
|
74882
|
+
if (!this.canFreeze) {
|
|
74883
|
+
return;
|
|
74884
|
+
}
|
|
74885
|
+
this.lastTick = this.clock();
|
|
74886
|
+
this.pausedWhileRefreshing = false;
|
|
74887
|
+
this.ticker = setInterval(() => {
|
|
74888
|
+
const at = this.clock();
|
|
74889
|
+
if (at - this.lastTick > REFRESH_PAUSE_MS) {
|
|
74890
|
+
this.pausedWhileRefreshing = true;
|
|
74891
|
+
}
|
|
74892
|
+
this.lastTick = at;
|
|
74893
|
+
}, REFRESH_TICK_MS);
|
|
74894
|
+
this.ticker.unref?.();
|
|
74895
|
+
}
|
|
74896
|
+
stopTicking() {
|
|
74897
|
+
clearInterval(this.ticker);
|
|
74898
|
+
this.ticker = void 0;
|
|
74899
|
+
}
|
|
74900
|
+
/**
|
|
74901
|
+
* Whether the process was paused since {@link refreshing} started: a tick
|
|
74902
|
+
* came late, or the next one is overdue (after a thaw, a request can run
|
|
74903
|
+
* before the timer does).
|
|
74904
|
+
*/
|
|
74905
|
+
pausedSinceRefreshStarted() {
|
|
74906
|
+
return this.canFreeze && (this.pausedWhileRefreshing || this.clock() - this.lastTick > REFRESH_PAUSE_MS);
|
|
74907
|
+
}
|
|
74649
74908
|
/**
|
|
74650
74909
|
* Record a revalidation of `tags`: here at once, and in the revalidation
|
|
74651
74910
|
* table for every other instance - the marker row plus a log row per tag.
|
|
@@ -74678,7 +74937,7 @@ var TrackedTagMarkers = class {
|
|
|
74678
74937
|
const at = markerClock();
|
|
74679
74938
|
let state2 = "fresh";
|
|
74680
74939
|
for (const tag of tags) {
|
|
74681
|
-
const marker = this.
|
|
74940
|
+
const marker = this.knownMarker(tag);
|
|
74682
74941
|
if (!marker) {
|
|
74683
74942
|
continue;
|
|
74684
74943
|
}
|
|
@@ -74702,7 +74961,7 @@ var TrackedTagMarkers = class {
|
|
|
74702
74961
|
const at = markerClock();
|
|
74703
74962
|
let latest = 0;
|
|
74704
74963
|
for (const tag of tags) {
|
|
74705
|
-
const marker = this.
|
|
74964
|
+
const marker = this.knownMarker(tag);
|
|
74706
74965
|
if (!marker) {
|
|
74707
74966
|
continue;
|
|
74708
74967
|
}
|
|
@@ -74789,13 +75048,14 @@ var TrackedTagMarkers = class {
|
|
|
74789
75048
|
);
|
|
74790
75049
|
return;
|
|
74791
75050
|
}
|
|
74792
|
-
const
|
|
74793
|
-
|
|
74794
|
-
|
|
74795
|
-
|
|
74796
|
-
|
|
74797
|
-
|
|
74798
|
-
|
|
75051
|
+
const due = this.due(at);
|
|
75052
|
+
if (due.length > 0) {
|
|
75053
|
+
void this.startRead(due);
|
|
75054
|
+
}
|
|
75055
|
+
const result = await log.query(this.cursor).catch((error2) => {
|
|
75056
|
+
console.error("Error reading tag revalidation log:", error2);
|
|
75057
|
+
return void 0;
|
|
75058
|
+
});
|
|
74799
75059
|
if (!result) {
|
|
74800
75060
|
return;
|
|
74801
75061
|
}
|
|
@@ -74806,6 +75066,11 @@ var TrackedTagMarkers = class {
|
|
|
74806
75066
|
);
|
|
74807
75067
|
return;
|
|
74808
75068
|
}
|
|
75069
|
+
const scan = at - this.lastEntryScan > ENTRY_SCAN_INTERVAL_MS;
|
|
75070
|
+
if (scan) {
|
|
75071
|
+
this.lastEntryScan = at;
|
|
75072
|
+
}
|
|
75073
|
+
const oldest = this.oldestEntry(scan);
|
|
74809
75074
|
let applied = 0;
|
|
74810
75075
|
for (const row of result.rows) {
|
|
74811
75076
|
if (this.applied.has(row.sk)) {
|
|
@@ -74820,11 +75085,20 @@ var TrackedTagMarkers = class {
|
|
|
74820
75085
|
mergeMarkers(this.pendingRows.get(row.tag), row.marker)
|
|
74821
75086
|
);
|
|
74822
75087
|
} else {
|
|
75088
|
+
this.rememberLogged(
|
|
75089
|
+
row.tag,
|
|
75090
|
+
mergeMarkers(this.logOnly.get(row.tag), row.marker),
|
|
75091
|
+
oldest
|
|
75092
|
+
);
|
|
74823
75093
|
continue;
|
|
74824
75094
|
}
|
|
74825
75095
|
this.applied.set(row.sk, row.at);
|
|
74826
75096
|
applied++;
|
|
74827
75097
|
}
|
|
75098
|
+
if (oldest > this.prunedAgainst) {
|
|
75099
|
+
this.pruneLogged(oldest);
|
|
75100
|
+
}
|
|
75101
|
+
this.prunedAgainst = oldest;
|
|
74828
75102
|
this.lastLogRead = at;
|
|
74829
75103
|
this.advance(at - REVALIDATION_LOG_LOOKBACK_MS);
|
|
74830
75104
|
this.debug(
|
|
@@ -74840,14 +75114,16 @@ var TrackedTagMarkers = class {
|
|
|
74840
75114
|
const due = [];
|
|
74841
75115
|
for (const tag of this.unread) {
|
|
74842
75116
|
if (due.length >= BATCH_GET_MAX_KEYS) return due;
|
|
74843
|
-
|
|
75117
|
+
if (!this.reading.has(tag)) {
|
|
75118
|
+
due.push(tag);
|
|
75119
|
+
}
|
|
74844
75120
|
}
|
|
74845
75121
|
const jitter = this.resyncIntervalMs * TAG_RESYNC_JITTER;
|
|
74846
75122
|
for (const [tag, dueAt] of this.dueAt) {
|
|
74847
75123
|
if (due.length >= BATCH_GET_MAX_KEYS || dueAt - jitter > at) {
|
|
74848
75124
|
break;
|
|
74849
75125
|
}
|
|
74850
|
-
if (dueAt <= at && !this.unread.has(tag)) {
|
|
75126
|
+
if (dueAt <= at && !this.unread.has(tag) && !this.reading.has(tag)) {
|
|
74851
75127
|
due.push(tag);
|
|
74852
75128
|
}
|
|
74853
75129
|
}
|
|
@@ -74860,10 +75136,19 @@ var TrackedTagMarkers = class {
|
|
|
74860
75136
|
*/
|
|
74861
75137
|
forget(at, why) {
|
|
74862
75138
|
this.debug(`forgetting ${this.tags.size} tracked tags: ${why}`);
|
|
75139
|
+
this.forgotten++;
|
|
75140
|
+
for (const marker of this.tags.values()) {
|
|
75141
|
+
this.raiseFloor(marker);
|
|
75142
|
+
}
|
|
75143
|
+
for (const marker of this.logOnly.values()) {
|
|
75144
|
+
this.raiseFloor(marker);
|
|
75145
|
+
}
|
|
74863
75146
|
this.tags.clear();
|
|
74864
75147
|
this.dueAt.clear();
|
|
74865
75148
|
this.unread.clear();
|
|
74866
75149
|
this.applied.clear();
|
|
75150
|
+
this.logOnly.clear();
|
|
75151
|
+
this.floor = Math.max(this.floor, markerClock());
|
|
74867
75152
|
this.cursor = at - REVALIDATION_LOG_LOOKBACK_MS;
|
|
74868
75153
|
this.lastLogRead = at;
|
|
74869
75154
|
}
|
|
@@ -74889,13 +75174,17 @@ var TrackedTagMarkers = class {
|
|
|
74889
75174
|
* always answered with, rather than a read on every request while DynamoDB
|
|
74890
75175
|
* is unavailable.
|
|
74891
75176
|
*/
|
|
74892
|
-
async readInto(tags) {
|
|
75177
|
+
async readInto(tags, again = false) {
|
|
74893
75178
|
if (tags.length === 0) {
|
|
74894
75179
|
return true;
|
|
74895
75180
|
}
|
|
74896
75181
|
const at = this.clock();
|
|
75182
|
+
const forgotten = this.forgotten;
|
|
74897
75183
|
try {
|
|
74898
75184
|
const read = await this.markers.read(tags);
|
|
75185
|
+
if (this.forgotten !== forgotten && !again) {
|
|
75186
|
+
return await this.readInto(tags, true);
|
|
75187
|
+
}
|
|
74899
75188
|
for (const tag of tags) {
|
|
74900
75189
|
this.unread.delete(tag);
|
|
74901
75190
|
this.remember(
|
|
@@ -74911,6 +75200,9 @@ var TrackedTagMarkers = class {
|
|
|
74911
75200
|
return true;
|
|
74912
75201
|
} catch (error2) {
|
|
74913
75202
|
console.error("Error reading tag markers:", error2);
|
|
75203
|
+
if (this.forgotten !== forgotten && !again) {
|
|
75204
|
+
return this.readInto(tags, true);
|
|
75205
|
+
}
|
|
74914
75206
|
this.trackUnread(tags);
|
|
74915
75207
|
for (const tag of tags) {
|
|
74916
75208
|
this.unread.add(tag);
|
|
@@ -74924,8 +75216,119 @@ var TrackedTagMarkers = class {
|
|
|
74924
75216
|
}
|
|
74925
75217
|
takePendingRows(tag) {
|
|
74926
75218
|
const pending = this.pendingRows.get(tag);
|
|
75219
|
+
const logged = this.logOnly.get(tag);
|
|
74927
75220
|
this.pendingRows.delete(tag);
|
|
74928
|
-
|
|
75221
|
+
this.logOnly.delete(tag);
|
|
75222
|
+
return logged ? mergeMarkers(logged, pending) : pending;
|
|
75223
|
+
}
|
|
75224
|
+
/**
|
|
75225
|
+
* What this instance knows of `tag`: tracked, or from the log alone -
|
|
75226
|
+
* including rows that arrived while its first read is in flight.
|
|
75227
|
+
*/
|
|
75228
|
+
knownMarker(tag) {
|
|
75229
|
+
const tracked = this.tags.get(tag);
|
|
75230
|
+
if (tracked) {
|
|
75231
|
+
return tracked;
|
|
75232
|
+
}
|
|
75233
|
+
const logged = this.logOnly.get(tag);
|
|
75234
|
+
const pending = this.pendingRows.get(tag);
|
|
75235
|
+
return logged || pending ? mergeMarkers(logged, pending) : void 0;
|
|
75236
|
+
}
|
|
75237
|
+
/**
|
|
75238
|
+
* Keep `tag`'s log rows in `logOnly`, unless they cannot apply to an entry
|
|
75239
|
+
* held, `oldest` or newer (see {@link pruneLogged}), as bounded as the
|
|
75240
|
+
* tracked tags. Either way a row let go of takes what it knew with it, so
|
|
75241
|
+
* `completeSince` moves past it.
|
|
75242
|
+
*/
|
|
75243
|
+
rememberLogged(tag, marker, oldest) {
|
|
75244
|
+
this.logOnly.delete(tag);
|
|
75245
|
+
if (knownBy(marker, markerClock()) <= oldest) {
|
|
75246
|
+
this.raiseFloor(marker);
|
|
75247
|
+
return;
|
|
75248
|
+
}
|
|
75249
|
+
this.logOnly.set(tag, marker);
|
|
75250
|
+
for (const [first, dropped] of this.logOnly) {
|
|
75251
|
+
if (this.logOnly.size <= this.maxTrackedTags) {
|
|
75252
|
+
break;
|
|
75253
|
+
}
|
|
75254
|
+
this.logOnly.delete(first);
|
|
75255
|
+
this.raiseFloor(dropped);
|
|
75256
|
+
}
|
|
75257
|
+
}
|
|
75258
|
+
/**
|
|
75259
|
+
* Drop the `logOnly` rows that cannot apply to an entry held, `oldest` or
|
|
75260
|
+
* newer: every revalidation they name so far is no later than it. That
|
|
75261
|
+
* moves `completeSince` no further than `oldest`, which costs the entries
|
|
75262
|
+
* held nothing, and a profile's `expiredAt` still to come waits in
|
|
75263
|
+
* `laterFloors`. Without this, a deployment revalidating many distinct tags
|
|
75264
|
+
* filled `logOnly` with tags no entry here carries, and the evictions past
|
|
75265
|
+
* its bound kept moving `completeSince` up to the present.
|
|
75266
|
+
*/
|
|
75267
|
+
pruneLogged(oldest) {
|
|
75268
|
+
const at = markerClock();
|
|
75269
|
+
for (const [tag, marker] of this.logOnly) {
|
|
75270
|
+
if (knownBy(marker, at) <= oldest) {
|
|
75271
|
+
this.logOnly.delete(tag);
|
|
75272
|
+
this.raiseFloor(marker);
|
|
75273
|
+
}
|
|
75274
|
+
}
|
|
75275
|
+
}
|
|
75276
|
+
/**
|
|
75277
|
+
* The oldest entry timestamp of any registered store, `Infinity` with none:
|
|
75278
|
+
* early, never late, unless `scan`.
|
|
75279
|
+
*/
|
|
75280
|
+
oldestEntry(scan) {
|
|
75281
|
+
let oldest = Infinity;
|
|
75282
|
+
for (const { oldestTimestamp } of this.entryStores) {
|
|
75283
|
+
oldest = Math.min(oldest, oldestTimestamp(scan));
|
|
75284
|
+
}
|
|
75285
|
+
return oldest;
|
|
75286
|
+
}
|
|
75287
|
+
/**
|
|
75288
|
+
* Move `completeSince` past `marker`'s revalidations: what is no longer
|
|
75289
|
+
* known about a tag once its marker is dropped. A future `expiredAt` waits
|
|
75290
|
+
* in `laterFloors` until it is past.
|
|
75291
|
+
*/
|
|
75292
|
+
raiseFloor(marker) {
|
|
75293
|
+
const at = markerClock();
|
|
75294
|
+
this.floor = Math.max(this.floor, knownBy(marker, at));
|
|
75295
|
+
const { expiredAt } = marker;
|
|
75296
|
+
if (expiredAt !== void 0 && expiredAt > Math.max(at, this.floor)) {
|
|
75297
|
+
this.laterFloor(expiredAt);
|
|
75298
|
+
}
|
|
75299
|
+
}
|
|
75300
|
+
/**
|
|
75301
|
+
* Add `time` to `laterFloors`, in order. Past the tracked-tag bound the two
|
|
75302
|
+
* spans closest together are merged: from the first's start,
|
|
75303
|
+
* `completeSince` is the second's end, early - which only costs reads, and
|
|
75304
|
+
* for as short a time as any merge allows. Never later than its time, which
|
|
75305
|
+
* would let an entry expire unseen.
|
|
75306
|
+
*/
|
|
75307
|
+
laterFloor(time2) {
|
|
75308
|
+
const floors = this.laterFloors;
|
|
75309
|
+
let low = 0;
|
|
75310
|
+
let high = floors.length;
|
|
75311
|
+
while (low < high) {
|
|
75312
|
+
const mid = Math.floor((low + high) / 2);
|
|
75313
|
+
if (floors[mid][0] < time2) {
|
|
75314
|
+
low = mid + 1;
|
|
75315
|
+
} else {
|
|
75316
|
+
high = mid;
|
|
75317
|
+
}
|
|
75318
|
+
}
|
|
75319
|
+
if (low > 0 && floors[low - 1][1] >= time2 || floors[low]?.[0] === time2) {
|
|
75320
|
+
return;
|
|
75321
|
+
}
|
|
75322
|
+
floors.splice(low, 0, [time2, time2]);
|
|
75323
|
+
if (floors.length > this.maxTrackedTags) {
|
|
75324
|
+
let merge2 = 0;
|
|
75325
|
+
for (let i5 = 1; i5 < floors.length - 1; i5++) {
|
|
75326
|
+
if (floors[i5 + 1][1] - floors[i5][0] < floors[merge2 + 1][1] - floors[merge2][0]) {
|
|
75327
|
+
merge2 = i5;
|
|
75328
|
+
}
|
|
75329
|
+
}
|
|
75330
|
+
floors.splice(merge2, 2, [floors[merge2][0], floors[merge2 + 1][1]]);
|
|
75331
|
+
}
|
|
74929
75332
|
}
|
|
74930
75333
|
/** Record that `tag`'s marker was known from the table at `at`. */
|
|
74931
75334
|
known(tag, at) {
|
|
@@ -74942,6 +75345,7 @@ var TrackedTagMarkers = class {
|
|
|
74942
75345
|
if (this.tags.size <= this.maxTrackedTags) {
|
|
74943
75346
|
break;
|
|
74944
75347
|
}
|
|
75348
|
+
this.raiseFloor(this.tags.get(oldest));
|
|
74945
75349
|
this.tags.delete(oldest);
|
|
74946
75350
|
this.dueAt.delete(oldest);
|
|
74947
75351
|
this.unread.delete(oldest);
|
|
@@ -74954,7 +75358,18 @@ var TrackedTagMarkers = class {
|
|
|
74954
75358
|
}
|
|
74955
75359
|
}
|
|
74956
75360
|
};
|
|
75361
|
+
function knownBy(marker, at) {
|
|
75362
|
+
const { revalidatedAt, staleAt, expiredAt } = marker;
|
|
75363
|
+
return Math.max(
|
|
75364
|
+
revalidatedAt ?? -Infinity,
|
|
75365
|
+
staleAt ?? -Infinity,
|
|
75366
|
+
expiredAt !== void 0 && expiredAt <= at ? expiredAt : -Infinity
|
|
75367
|
+
);
|
|
75368
|
+
}
|
|
74957
75369
|
var MAX_LOG_PUT_MS = REVALIDATION_LOG_LOOKBACK_MS / 2;
|
|
75370
|
+
var ENTRY_SCAN_INTERVAL_MS = 6e4;
|
|
75371
|
+
var REFRESH_TICK_MS = 250;
|
|
75372
|
+
var REFRESH_PAUSE_MS = 1e3;
|
|
74958
75373
|
function mergeMarkers(local, remote) {
|
|
74959
75374
|
if (!local) {
|
|
74960
75375
|
return remote ?? {};
|
|
@@ -75026,7 +75441,10 @@ function numberFromEnv(name, fallback2) {
|
|
|
75026
75441
|
return Number.isFinite(value) && value >= 0 ? value : fallback2;
|
|
75027
75442
|
}
|
|
75028
75443
|
var DEFAULT_MEMORY_BYTES = 50 * 1024 * 1024;
|
|
75029
|
-
var
|
|
75444
|
+
var TAG_TABLE_REQUEST_TIMEOUT_MS = 3e3;
|
|
75445
|
+
function outsideRender(fn) {
|
|
75446
|
+
return outsideRequest(sharedTagManifest).outsideRender(fn);
|
|
75447
|
+
}
|
|
75030
75448
|
function sharedTagManifest() {
|
|
75031
75449
|
const global = globalThis;
|
|
75032
75450
|
if (!global[TAG_MANIFEST_SYMBOL]) {
|
|
@@ -75036,12 +75454,24 @@ function sharedTagManifest() {
|
|
|
75036
75454
|
DEFAULT_TAG_REFRESH_MS
|
|
75037
75455
|
);
|
|
75038
75456
|
if (config.tableName && !isBuildPhase()) {
|
|
75039
|
-
const client = new import_client_dynamodb2.DynamoDBClient({
|
|
75457
|
+
const client = new import_client_dynamodb2.DynamoDBClient({
|
|
75458
|
+
region: config.region,
|
|
75459
|
+
// Without `throwOnRequestTimeout` the timeout only logs a warning. It
|
|
75460
|
+
// ends once the response headers arrive: `socketTimeout`, an
|
|
75461
|
+
// inactivity timer kept until the request closes, covers the body.
|
|
75462
|
+
requestHandler: {
|
|
75463
|
+
requestTimeout: TAG_TABLE_REQUEST_TIMEOUT_MS,
|
|
75464
|
+
throwOnRequestTimeout: true,
|
|
75465
|
+
socketTimeout: TAG_TABLE_REQUEST_TIMEOUT_MS
|
|
75466
|
+
}
|
|
75467
|
+
});
|
|
75040
75468
|
global[TAG_MANIFEST_SYMBOL] = new TrackedTagMarkers({
|
|
75041
75469
|
markers: new TagMarkerTable(client, config.tableName, config.buildId),
|
|
75042
75470
|
log: new RevalidationLog(client, config.tableName, config.buildId),
|
|
75043
75471
|
refreshIntervalMs,
|
|
75044
|
-
debug: (0, import_debug6.default)("cdk-nextjs:cache-handler:tags")
|
|
75472
|
+
debug: (0, import_debug6.default)("cdk-nextjs:cache-handler:tags"),
|
|
75473
|
+
// Set in every Lambda runtime; a container is never frozen.
|
|
75474
|
+
canFreeze: process.env.AWS_LAMBDA_FUNCTION_NAME !== void 0
|
|
75045
75475
|
});
|
|
75046
75476
|
} else {
|
|
75047
75477
|
if (!isBuildPhase()) {
|
|
@@ -75152,8 +75582,11 @@ var MemoryCacheHandler = class {
|
|
|
75152
75582
|
* `Date.now()` drifts from it for as long as the process lives: an entry
|
|
75153
75583
|
* stamped ahead of it looked newer than a `revalidateTag` run within the
|
|
75154
75584
|
* drift, and was served for the whole memory TTL.
|
|
75585
|
+
*
|
|
75586
|
+
* `cacheControl` is the lifetime the entry was rendered with, handed back
|
|
75587
|
+
* from {@link get} as the S3 layer does; see `getCacheControl`.
|
|
75155
75588
|
*/
|
|
75156
|
-
async set(cacheKey, data, lastModified) {
|
|
75589
|
+
async set(cacheKey, data, lastModified, cacheControl) {
|
|
75157
75590
|
if (!data) {
|
|
75158
75591
|
this.debug(`MEMORY CACHE DELETE: ${cacheKey}`);
|
|
75159
75592
|
this.inMemoryCache.delete(cacheKey);
|
|
@@ -75162,7 +75595,8 @@ var MemoryCacheHandler = class {
|
|
|
75162
75595
|
this.debug(`MEMORY CACHE SET: ${cacheKey} (${data.kind})`);
|
|
75163
75596
|
const cacheHandlerValue = {
|
|
75164
75597
|
lastModified: lastModified ?? markerClock(),
|
|
75165
|
-
value: data
|
|
75598
|
+
value: data,
|
|
75599
|
+
cacheControl
|
|
75166
75600
|
};
|
|
75167
75601
|
const entry = {
|
|
75168
75602
|
value: cacheHandlerValue,
|
|
@@ -75338,15 +75772,26 @@ var S3CacheHandler = class {
|
|
|
75338
75772
|
* request is answered from the stale entry - stale-while-revalidate - and that
|
|
75339
75773
|
* response carries the entry's original `s-maxage`, so the edge caches the
|
|
75340
75774
|
* stale body again. The background render that replaces it runs in this same
|
|
75341
|
-
* process, so `set` checks
|
|
75775
|
+
* process, so `set` checks for the key here and invalidates the key's paths a second
|
|
75342
75776
|
* time once the fresh entry is in S3. Without that, the edge kept serving the
|
|
75343
75777
|
* pre-revalidation page for the whole `s-maxage`.
|
|
75344
75778
|
*
|
|
75345
75779
|
* Expiring soft-revalidated entries outright instead would also keep the edge
|
|
75346
75780
|
* right, but it turns every `revalidateTag(tag, "max")` into a blocking render
|
|
75347
75781
|
* at the origin, which is the thing a profile asks not to happen.
|
|
75782
|
+
*
|
|
75783
|
+
* When the re-render fails, next 16.4's `retainPreviousCacheEntry` writes the
|
|
75784
|
+
* stale value back with a short lifetime: that is no regeneration, so the key
|
|
75785
|
+
* keeps waiting rather than invalidating the edge for a page that has not
|
|
75786
|
+
* changed. {@link servedStale} is how `set` recognises it.
|
|
75348
75787
|
*/
|
|
75349
75788
|
softRevalidatedKeys = /* @__PURE__ */ new Set();
|
|
75789
|
+
/**
|
|
75790
|
+
* Every value handed out stale, by identity: the object `get` returned is the
|
|
75791
|
+
* one `retainPreviousCacheEntry` writes back. Weak, so a page body is held no
|
|
75792
|
+
* longer than the memory layer or the request holds it.
|
|
75793
|
+
*/
|
|
75794
|
+
servedStale = /* @__PURE__ */ new WeakSet();
|
|
75350
75795
|
/** @see buildTagManifest */
|
|
75351
75796
|
buildTags;
|
|
75352
75797
|
constructor(options) {
|
|
@@ -75406,12 +75851,17 @@ var S3CacheHandler = class {
|
|
|
75406
75851
|
const parsedValue = parseCacheValue(response);
|
|
75407
75852
|
if (await this.isRevalidated(parsedValue, ctx, cacheKey)) {
|
|
75408
75853
|
this.debug(`S3 CACHE INVALIDATED BY TAG: ${cacheKey}`);
|
|
75409
|
-
return isFetchCacheGet(ctx) ? null : {
|
|
75854
|
+
return isFetchCacheGet(ctx) ? null : {
|
|
75855
|
+
lastModified: EXPIRED_LAST_MODIFIED,
|
|
75856
|
+
value: parsedValue.value,
|
|
75857
|
+
cacheControl: storedCacheControl(parsedValue.cacheControl)
|
|
75858
|
+
};
|
|
75410
75859
|
}
|
|
75411
75860
|
this.debug(`S3 CACHE HIT: ${cacheKey} (${objectKey})`);
|
|
75412
75861
|
return {
|
|
75413
75862
|
lastModified: parsedValue.lastModified,
|
|
75414
|
-
value: parsedValue.value
|
|
75863
|
+
value: parsedValue.value,
|
|
75864
|
+
cacheControl: storedCacheControl(parsedValue.cacheControl)
|
|
75415
75865
|
};
|
|
75416
75866
|
} catch (error2) {
|
|
75417
75867
|
console.error(`Error retrieving cache from S3:`, error2);
|
|
@@ -75457,7 +75907,7 @@ var S3CacheHandler = class {
|
|
|
75457
75907
|
checkTags
|
|
75458
75908
|
);
|
|
75459
75909
|
if (state2 === "stale" && !isFetchCacheGet(ctx)) {
|
|
75460
|
-
this.noteSoftRevalidated(cacheKey);
|
|
75910
|
+
this.noteSoftRevalidated(cacheKey, entry.value);
|
|
75461
75911
|
}
|
|
75462
75912
|
return state2 === "expired";
|
|
75463
75913
|
}
|
|
@@ -75466,13 +75916,16 @@ var S3CacheHandler = class {
|
|
|
75466
75916
|
* whose copy of it will need invalidating again. See
|
|
75467
75917
|
* {@link softRevalidatedKeys}.
|
|
75468
75918
|
*/
|
|
75469
|
-
noteSoftRevalidated(cacheKey) {
|
|
75919
|
+
noteSoftRevalidated(cacheKey, value) {
|
|
75470
75920
|
if (!this.invalidatesCdn) {
|
|
75471
75921
|
return;
|
|
75472
75922
|
}
|
|
75923
|
+
if (typeof value === "object" && value !== null) {
|
|
75924
|
+
this.servedStale.add(value);
|
|
75925
|
+
}
|
|
75473
75926
|
this.softRevalidatedKeys.delete(cacheKey);
|
|
75474
75927
|
if (this.softRevalidatedKeys.size >= MAX_SOFT_REVALIDATED_KEYS) {
|
|
75475
|
-
const oldest = this.softRevalidatedKeys.
|
|
75928
|
+
const oldest = this.softRevalidatedKeys.keys().next().value;
|
|
75476
75929
|
if (oldest !== void 0) {
|
|
75477
75930
|
this.softRevalidatedKeys.delete(oldest);
|
|
75478
75931
|
}
|
|
@@ -75510,7 +75963,8 @@ var S3CacheHandler = class {
|
|
|
75510
75963
|
const cacheHandlerValue = {
|
|
75511
75964
|
// On the marker clock, since that is what it is compared with.
|
|
75512
75965
|
lastModified: markerClock(),
|
|
75513
|
-
value: data
|
|
75966
|
+
value: data,
|
|
75967
|
+
cacheControl: getCacheControl(ctx)
|
|
75514
75968
|
};
|
|
75515
75969
|
const cacheEntryWithTags = {
|
|
75516
75970
|
...cacheHandlerValue,
|
|
@@ -75519,7 +75973,7 @@ var S3CacheHandler = class {
|
|
|
75519
75973
|
const body = serializeCacheValue(cacheEntryWithTags);
|
|
75520
75974
|
await this.bucket.putJson(objectKey, body);
|
|
75521
75975
|
this.debug(`S3 CACHE STORED: ${cacheKey} (${data.kind})`);
|
|
75522
|
-
if (this.softRevalidatedKeys.delete(cacheKey)) {
|
|
75976
|
+
if (!this.servedStale.has(data) && this.softRevalidatedKeys.delete(cacheKey)) {
|
|
75523
75977
|
const route = this.s3KeyToInvalidationPath(s3Key);
|
|
75524
75978
|
if (route !== void 0) {
|
|
75525
75979
|
this.debug(`SOFT REVALIDATION REGENERATED: ${cacheKey}`);
|
|
@@ -75987,19 +76441,22 @@ var CdkNextjsCacheHandler = class _CdkNextjsCacheHandler {
|
|
|
75987
76441
|
`Build-time cache directory: ${this.localFileHandler.getCacheDir()}`
|
|
75988
76442
|
);
|
|
75989
76443
|
} else {
|
|
75990
|
-
this.s3DynamoHandler = _CdkNextjsCacheHandler.sharedS3DynamoHandler ??= new S3CacheHandler({ context: options });
|
|
76444
|
+
this.s3DynamoHandler = _CdkNextjsCacheHandler.sharedS3DynamoHandler ??= outsideRender(() => new S3CacheHandler({ context: options }));
|
|
75991
76445
|
this.memoryHandler = _CdkNextjsCacheHandler.sharedMemoryHandler ??= new MemoryCacheHandler();
|
|
75992
76446
|
}
|
|
75993
76447
|
}
|
|
75994
76448
|
/**
|
|
75995
76449
|
* Get cache entry
|
|
75996
76450
|
* - Build time: Not implemented (build doesn't read cache)
|
|
75997
|
-
* - Runtime: Try memory first, then S3/DynamoDB
|
|
76451
|
+
* - Runtime: Try memory first, then S3/DynamoDB, {@link outsideRender}
|
|
75998
76452
|
*/
|
|
75999
76453
|
async get(cacheKey, ctx) {
|
|
76000
76454
|
if (this.isBuildTime) {
|
|
76001
76455
|
return this.localFileHandler.get(cacheKey);
|
|
76002
76456
|
}
|
|
76457
|
+
return outsideRender(() => this.getAtRuntime(cacheKey, ctx));
|
|
76458
|
+
}
|
|
76459
|
+
async getAtRuntime(cacheKey, ctx) {
|
|
76003
76460
|
const memoryResult = await this.memoryHandler.get(cacheKey);
|
|
76004
76461
|
if (memoryResult && await this.s3DynamoHandler.isRevalidated(
|
|
76005
76462
|
{
|
|
@@ -76022,7 +76479,8 @@ var CdkNextjsCacheHandler = class _CdkNextjsCacheHandler {
|
|
|
76022
76479
|
await this.memoryHandler.set(
|
|
76023
76480
|
cacheKey,
|
|
76024
76481
|
s3Result.value,
|
|
76025
|
-
s3Result.lastModified
|
|
76482
|
+
s3Result.lastModified,
|
|
76483
|
+
s3Result.cacheControl
|
|
76026
76484
|
);
|
|
76027
76485
|
}
|
|
76028
76486
|
return s3Result;
|
|
@@ -76033,7 +76491,8 @@ var CdkNextjsCacheHandler = class _CdkNextjsCacheHandler {
|
|
|
76033
76491
|
/**
|
|
76034
76492
|
* Set cache entry
|
|
76035
76493
|
* - Build time: Write to local file cache only (or delete if data is null)
|
|
76036
|
-
* - Runtime: Write to both memory and S3/DynamoDB (or delete if data is null)
|
|
76494
|
+
* - Runtime: Write to both memory and S3/DynamoDB (or delete if data is null),
|
|
76495
|
+
* {@link outsideRender}
|
|
76037
76496
|
*/
|
|
76038
76497
|
async set(cacheKey, data, ctx) {
|
|
76039
76498
|
if (this.isBuildTime) {
|
|
@@ -76045,20 +76504,29 @@ var CdkNextjsCacheHandler = class _CdkNextjsCacheHandler {
|
|
|
76045
76504
|
}
|
|
76046
76505
|
return;
|
|
76047
76506
|
}
|
|
76048
|
-
|
|
76049
|
-
|
|
76050
|
-
|
|
76507
|
+
await outsideRender(async () => {
|
|
76508
|
+
this.debug(`Cache ${data ? "write" : "delete"}: ${cacheKey}`);
|
|
76509
|
+
await this.memoryHandler.set(
|
|
76510
|
+
cacheKey,
|
|
76511
|
+
data,
|
|
76512
|
+
void 0,
|
|
76513
|
+
getCacheControl(ctx)
|
|
76514
|
+
);
|
|
76515
|
+
await this.s3DynamoHandler.set(cacheKey, data, ctx);
|
|
76516
|
+
});
|
|
76051
76517
|
}
|
|
76052
76518
|
/**
|
|
76053
76519
|
* Revalidate tags
|
|
76054
76520
|
* - Build time: Not implemented
|
|
76055
|
-
* - Runtime: Delegate to S3/DynamoDB handler
|
|
76521
|
+
* - Runtime: Delegate to S3/DynamoDB handler, {@link outsideRender}
|
|
76056
76522
|
*/
|
|
76057
76523
|
async revalidateTag(tag, durations) {
|
|
76058
76524
|
if (this.isBuildTime) {
|
|
76059
76525
|
return;
|
|
76060
76526
|
}
|
|
76061
|
-
await
|
|
76527
|
+
await outsideRender(
|
|
76528
|
+
() => this.s3DynamoHandler.revalidateTag(tag, durations)
|
|
76529
|
+
);
|
|
76062
76530
|
}
|
|
76063
76531
|
/**
|
|
76064
76532
|
* Deliberately a no-op. Next.js calls `resetRequestCache` at the start of
|