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
@@ -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
- let keys = unique.slice(i5, i5 + BATCH_GET_MAX_KEYS).map((tag) => ({ pk: { S: buildId }, sk: { S: tag } }));
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)}#${tag}` },
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.remember(tag, marker);
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 (inFlight) {
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
- const read = this.readInto(unread).then(() => {
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.get(tag);
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.get(tag);
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 [result] = await Promise.all([
46815
- log.query(this.cursor).catch((error2) => {
46816
- console.error("Error reading tag revalidation log:", error2);
46817
- return void 0;
46818
- }),
46819
- this.readInto(this.due(at))
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
- due.push(tag);
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
- return pending;
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
- function tagMethods(tags) {
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
- refreshTags: () => tags.refresh(),
47216
- async getExpiration(implicitTags) {
47217
- await tags.ensure(implicitTags);
47218
- return tags.expiration(implicitTags);
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({ region: config.region });
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, createHash6("sha256").update(cacheKey).digest("hex"));
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
- ...tagMethods(tags)
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());