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