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
@@ -25,6 +25,8 @@ export declare function resolveAwsCacheConfig(): AwsCacheConfig;
25
25
  * which is what keeps it disjoint from {@link useCacheS3Key}.
26
26
  */
27
27
  export declare function buildS3Key(buildId: string, cacheKey: string): string;
28
+ /** `{buildId}/` + {@link cacheObjectName}: the S3 object an entry is stored in. */
29
+ export declare function s3ObjectKey(buildId: string, cacheKey: string): string;
28
30
  /**
29
31
  * The folder under the build prefix that `'use cache'` entries are stored in.
30
32
  * @see useCacheS3Key
@@ -36,8 +38,9 @@ export declare const USE_CACHE_KEY_PREFIX = "_use-cache";
36
38
  * Hashed because a `'use cache'` key is the serialized arguments of the call and
37
39
  * has no length limit, where an S3 key stops at 1024 bytes. The `.entry` suffix
38
40
  * rather than `.json` is what makes a collision with the incremental cache
39
- * impossible rather than unlikely: every key {@link buildS3Key} writes ends in
40
- * `.json`, including one for a route that happens to be named `/_use-cache/…`.
41
+ * impossible rather than unlikely: every object {@link s3ObjectKey} names ends
42
+ * in `.json` or `.long`, including one for a route that happens to be named
43
+ * `/_use-cache/…`.
41
44
  * Still under the build prefix, so post-deploy pruning drops them with the build.
42
45
  */
43
46
  export declare function useCacheS3Key(buildId: string, keyHash: string): string;
@@ -113,9 +116,36 @@ export declare const BATCH_GET_MAX_KEYS = 100;
113
116
  * revalidation log, the reads scale with instances instead of requests or tags.
114
117
  */
115
118
  export declare const DEFAULT_TAG_REFRESH_MS = 1000;
119
+ /**
120
+ * How far past `refreshIntervalMs` the last refresh may have settled before a
121
+ * `'use cache'` request waits for the next one (see
122
+ * {@link TrackedTagMarkers.behind}). An instance serving steady traffic stays
123
+ * inside it; one that sat idle, or frozen between Lambda invocations, does not.
124
+ */
125
+ export declare const TAG_REFRESH_GRACE_MS = 1000;
126
+ /** DynamoDB's limit on a sort key, in UTF-8 bytes. */
127
+ export declare const MAX_SORT_KEY_BYTES = 1024;
128
+ /**
129
+ * `#<sha256(tag)>`, standing in for a tag in a sort key it would push past
130
+ * {@link MAX_SORT_KEY_BYTES}. Next.js caps the tags an app names, but not an
131
+ * implicit `_N_T_/…` tag, which is as long as its path, and a rejected write
132
+ * left `revalidatePath` on a long path recorded nowhere.
133
+ */
134
+ export declare function hashedTag(tag: string): string;
135
+ /**
136
+ * The longest tag a sort key spells out; past it, {@link sortKeyTag} hashes
137
+ * it. One cap for every row the tag is in, so a row's writer and reader agree
138
+ * without each working out what else shares its key. It leaves room for the
139
+ * most any row puts beside the tag: a mapping row's `#<buildId>/#<sha256>`,
140
+ * with the build ID capped at `MAX_BUILD_ID_BYTES`.
141
+ */
142
+ export declare const MAX_TAG_BYTES = 768;
143
+ /** `tag` as a sort key spells it: itself, or {@link hashedTag} past {@link MAX_TAG_BYTES}. */
144
+ export declare function sortKeyTag(tag: string): string;
116
145
  /**
117
146
  * The bare-tag marker rows of the revalidation table (`pk = buildId`,
118
- * `sk = tag`), which record when a tag was last revalidated and how.
147
+ * `sk = tag`, or {@link hashedTag} past {@link MAX_TAG_BYTES}), which record when a tag
148
+ * was last revalidated and how.
119
149
  *
120
150
  * The table also holds the incremental cache's `tag#s3Key` mapping rows, which
121
151
  * are its own business and stay in `S3CacheHandler`: only the markers mean the
@@ -166,7 +196,8 @@ export interface RevalidationLogRow {
166
196
  /**
167
197
  * The revalidation log: one row per tag revalidation (`pk = <buildId>#log`,
168
198
  * `sk = <Date.now(), zero-padded>#<tag>`), which expires after
169
- * {@link REVALIDATION_LOG_TTL_MS}.
199
+ * {@link REVALIDATION_LOG_TTL_MS}. A tag past {@link MAX_TAG_BYTES} is
200
+ * {@link hashedTag} there, and spelled out in the row's `longTag` instead.
170
201
  *
171
202
  * The marker rows stay the source of truth. The log exists so an instance
172
203
  * can ask "what was revalidated since I last looked?" in one `Query`, instead
@@ -278,6 +309,14 @@ export type TrackedTagMarkersOptions = TagTable & {
278
309
  /** `Math.random`, for when each tag's re-read comes due. */
279
310
  random?: () => number;
280
311
  debug?: (message: string) => void;
312
+ /**
313
+ * Whether the process can be frozen with a refresh in flight, as a Lambda
314
+ * sandbox is between invocations: then a timer checks it kept running, and
315
+ * one that spanned a freeze does not count. A container is never frozen,
316
+ * so it skips the timer and waits for the refresh in flight, however slow.
317
+ * @default false
318
+ */
319
+ canFreeze?: boolean;
281
320
  };
282
321
  /**
283
322
  * One instance's copy of the marker rows of the tags it has needed, kept
@@ -298,15 +337,19 @@ export type TrackedTagMarkersOptions = TagTable & {
298
337
  * The cost is bounded per instance, not per request:
299
338
  * - {@link ensure} reads a tag's marker the first time it is needed and not
300
339
  * again until it is evicted, so a tag costs one read per instance, not one
301
- * per request.
340
+ * per request. An entry created since {@link completeSince} needs no read
341
+ * at all.
302
342
  * - {@link refresh}, at most once per `refreshIntervalMs`, sends one `Query`
303
343
  * for the log rows written since the last one, and applies those of tags
304
344
  * this instance tracks. Concurrent callers share it. Alongside, it re-reads
305
345
  * the markers of up to {@link BATCH_GET_MAX_KEYS} tags not read for
306
346
  * {@link DEFAULT_TAG_RESYNC_MS}, in one `BatchGetItem`.
307
347
  * A revalidation on another instance is therefore seen within
308
- * `refreshIntervalMs` (plus the query itself). The instance that ran it applies
309
- * it itself, at once, with {@link set}.
348
+ * `refreshIntervalMs` (plus the query itself) - by `'use cache'`, from the
349
+ * first request after the query returns, since its `refreshTags` does not wait
350
+ * for it (see `tagMethods`) unless the instance is {@link behind}, which the
351
+ * runtime settles before a page render starts ({@link catchUp}). The instance
352
+ * that ran it applies it itself, at once, with {@link set}.
310
353
  */
311
354
  export declare class TrackedTagMarkers {
312
355
  private readonly markers;
@@ -332,10 +375,64 @@ export declare class TrackedTagMarkers {
332
375
  * result rather than dropped.
333
376
  */
334
377
  private readonly pendingRows;
378
+ /**
379
+ * Log rows for tags not tracked and not being read, merged by tag, oldest
380
+ * first: what the log has said about them since {@link completeSince}. Taken
381
+ * into a tag's marker when it is read or written.
382
+ */
383
+ private readonly logOnly;
384
+ /** See {@link completeSince}. */
385
+ private floor;
386
+ /**
387
+ * The future `expiredAt`s of markers dropped since, as `[from, to]`, in
388
+ * order: once `from` is past, {@link completeSince} moves to `to`, and not
389
+ * before, since only then does it expire anything. `from` and `to` are the
390
+ * same until spans are merged for space ({@link laterFloor}).
391
+ */
392
+ private readonly laterFloors;
393
+ /** The in-memory entry stores judged by {@link completeSince}. */
394
+ private readonly entryStores;
395
+ /** How many times {@link forget} ran: a read started before one is made again. */
396
+ private forgotten;
335
397
  /** Tracked tags whose read failed, for the next refresh to read again. */
336
398
  private readonly unread;
399
+ /**
400
+ * Set inside a page render {@link catchUp} let go, for that render alone.
401
+ * See {@link behindInRender}.
402
+ */
403
+ private readonly caughtUpRender;
404
+ /** When `sync` last scanned the entry stores ({@link oldestEntry}). */
405
+ private lastEntryScan;
406
+ /**
407
+ * No `logOnly` row is known by it or earlier, so a {@link pruneLogged}
408
+ * against it or earlier drops nothing: a marker is only ever known by later,
409
+ * as its `expiredAt` passes.
410
+ */
411
+ private prunedAgainst;
412
+ /** When the last refresh started, or was skipped. */
337
413
  private lastRefresh;
414
+ /**
415
+ * The refresh in flight. One left open across a freeze comes back after the
416
+ * thaw with what it read before, so {@link refresh} waits for it and then
417
+ * for a fresh one, rather than the runtime waiting for it before the freeze:
418
+ * that would bill every Lambda invocation for the query's tail.
419
+ */
338
420
  private refreshing;
421
+ /**
422
+ * When {@link refreshing} last showed the process running: a timer ticks
423
+ * while it is in flight, and a tick that comes late, or none at all, means
424
+ * the process was paused - frozen - since it started.
425
+ */
426
+ private lastTick;
427
+ /** Whether a tick came late since {@link refreshing} started. */
428
+ private pausedWhileRefreshing;
429
+ private ticker;
430
+ private readonly canFreeze;
431
+ /**
432
+ * When the last refresh settled, either way, unless it spanned a pause: what
433
+ * it knows is from then. See {@link behind}.
434
+ */
435
+ private settledAt;
339
436
  /**
340
437
  * Where the next log query starts (`clock()`): the last successful one's
341
438
  * start, less {@link REVALIDATION_LOG_LOOKBACK_MS}.
@@ -350,6 +447,41 @@ export declare class TrackedTagMarkers {
350
447
  constructor(options: TrackedTagMarkersOptions);
351
448
  /** `tag`'s marker as this instance knows it, if it tracks `tag`. */
352
449
  get(tag: string): TagMarker | undefined;
450
+ /**
451
+ * Since when (`markerClock()`) this instance knows of every revalidation of
452
+ * every tag, tracked or not: tracked ones from their markers and the log,
453
+ * the others from the log alone (`logOnly`). So an entry created at or after
454
+ * it can be judged by {@link state} and {@link expiration} without
455
+ * {@link ensure} reading anything - which matters inside a `'use cache'`
456
+ * lookup, where Next.js waits for the answer and a read pushes the entry out
457
+ * of the static stage of a staged render.
458
+ *
459
+ * Starts at construction: the log is followed from before it. Moves to now
460
+ * when the tracked markers are forgotten, and past the revalidations of
461
+ * whatever else it lets go of - a marker evicted for space, a log row
462
+ * pruned - which are no longer known: to its `expiredAt` only once that is
463
+ * past, since a profile's `expire` can be a year out, and moving there at
464
+ * once would turn the read-free path off for the instance's life.
465
+ *
466
+ * A row is pruned only once it is no later than every entry held, so that
467
+ * costs those entries nothing. An entry stored afterwards with an older
468
+ * `timestamp` - Next.js stamps the start of its generation, which can
469
+ * outlast a lookback - is older than this too, and is read first.
470
+ *
471
+ * It does not cover a revalidation older than the log's first lookback
472
+ * whose `expiredAt` is still to come, which would expire an entry created
473
+ * after it. So the `'use cache'` handler reads an entry's tags in the
474
+ * background as it stores it.
475
+ */
476
+ get completeSince(): number;
477
+ /**
478
+ * Register an in-memory store whose entries are judged by
479
+ * {@link completeSince}, by the `timestamp` of the oldest entry it holds:
480
+ * `logOnly` keeps the rows that could still apply to one of them, and no
481
+ * others. With none registered it keeps nothing. Asked with `scan` false,
482
+ * `oldestTimestamp` may answer early, but never late, without a scan.
483
+ */
484
+ judgeEntriesOf(oldestTimestamp: (scan: boolean) => number, holdsAny?: () => boolean): void;
353
485
  /**
354
486
  * Track `tag` with `marker`: what this instance just wrote, which it knows
355
487
  * without reading it back.
@@ -381,11 +513,73 @@ export declare class TrackedTagMarkers {
381
513
  * tags may have been revalidated at any time before.
382
514
  */
383
515
  ensure(tags: readonly string[]): Promise<void>;
516
+ /**
517
+ * {@link readInto} `tags`, as the read of each that whoever needs one of
518
+ * them next waits on ({@link reading}). Never rejects: a failed read leaves
519
+ * the tags tracked.
520
+ */
521
+ private startRead;
522
+ /**
523
+ * Whether the last refresh settled more than `refreshIntervalMs` plus
524
+ * {@link TAG_REFRESH_GRACE_MS} ago - one that spanned a freeze counts from
525
+ * when it started: after the instance sat idle or frozen - or always, with
526
+ * a `refreshIntervalMs` of `0`. Answering from what it knows then could
527
+ * serve an entry revalidated elsewhere since, however long ago that was, so
528
+ * `refreshTags` waits for the refresh, and the runtime waits for it before
529
+ * a page render starts ({@link catchUp}).
530
+ *
531
+ * From when it settled, not when it started, so a slow query the runtime
532
+ * waited for before the render does not leave the instance behind inside
533
+ * it. A failed one counts too, so one request does not wait for two: the
534
+ * same "assume the entry is valid" a failed read answers with, until the
535
+ * next refresh, at most the interval and the grace later.
536
+ */
537
+ get behind(): boolean;
538
+ /**
539
+ * {@link behind}, as `refreshTags` asks it: never inside a page's render
540
+ * {@link catchUp} caught up, since the runtime settled it for that request
541
+ * before the render started, and a wait inside a `'use cache'` lookup is
542
+ * what it settled it to avoid - a render crossing the line partway through
543
+ * would otherwise wait after all. So a revalidation elsewhere can go unseen
544
+ * for a render's duration on top of the usual window.
545
+ *
546
+ * A route handler, which no catch-up precedes, waits at the plain line.
547
+ */
548
+ get behindInRender(): boolean;
549
+ /**
550
+ * Whether a registered store ({@link judgeEntriesOf}) holds any entry: one
551
+ * a refresh could expire. With none there is nothing to wait for even when
552
+ * the instance is {@link behind}, since whatever is stored from here on is
553
+ * newer than what the log would say - which covers the first `'use cache'`
554
+ * call of a process, which creates the handler inside the render.
555
+ */
556
+ get holdsEntries(): boolean;
557
+ /**
558
+ * {@link refresh}, waited for when the instance is {@link behind}. The
559
+ * runtime calls it before handing a page request to Next.js, outside the
560
+ * staged render whose static stage a wait inside a `'use cache'` lookup
561
+ * would cut short. Only for the `'use cache'` handler's entries
562
+ * ({@link judgeEntriesOf}): the other caches wait for the refresh where they
563
+ * read anyway. Not with a `refreshIntervalMs` of `0` either, where
564
+ * `refreshTags` asks again regardless. Then `render`, marked as caught up
565
+ * for ({@link behindInRender}). Rejects only as `render` does.
566
+ */
567
+ catchUp<T>(render: () => Promise<T>): Promise<T>;
568
+ /** Whether `at` is more than the interval and the grace ago. */
569
+ private overdue;
384
570
  /**
385
571
  * Catch up on revalidations other instances ran, at most once per
386
572
  * `refreshIntervalMs`. See {@link TrackedTagMarkers} for the cost.
387
573
  */
388
574
  refresh(): Promise<void>;
575
+ private startTicking;
576
+ private stopTicking;
577
+ /**
578
+ * Whether the process was paused since {@link refreshing} started: a tick
579
+ * came late, or the next one is overdue (after a thaw, a request can run
580
+ * before the timer does).
581
+ */
582
+ private pausedSinceRefreshStarted;
389
583
  /**
390
584
  * Record a revalidation of `tags`: here at once, and in the revalidation
391
585
  * table for every other instance - the marker row plus a log row per tag.
@@ -458,6 +652,47 @@ export declare class TrackedTagMarkers {
458
652
  */
459
653
  private readInto;
460
654
  private takePendingRows;
655
+ /**
656
+ * What this instance knows of `tag`: tracked, or from the log alone -
657
+ * including rows that arrived while its first read is in flight.
658
+ */
659
+ private knownMarker;
660
+ /**
661
+ * Keep `tag`'s log rows in `logOnly`, unless they cannot apply to an entry
662
+ * held, `oldest` or newer (see {@link pruneLogged}), as bounded as the
663
+ * tracked tags. Either way a row let go of takes what it knew with it, so
664
+ * `completeSince` moves past it.
665
+ */
666
+ private rememberLogged;
667
+ /**
668
+ * Drop the `logOnly` rows that cannot apply to an entry held, `oldest` or
669
+ * newer: every revalidation they name so far is no later than it. That
670
+ * moves `completeSince` no further than `oldest`, which costs the entries
671
+ * held nothing, and a profile's `expiredAt` still to come waits in
672
+ * `laterFloors`. Without this, a deployment revalidating many distinct tags
673
+ * filled `logOnly` with tags no entry here carries, and the evictions past
674
+ * its bound kept moving `completeSince` up to the present.
675
+ */
676
+ private pruneLogged;
677
+ /**
678
+ * The oldest entry timestamp of any registered store, `Infinity` with none:
679
+ * early, never late, unless `scan`.
680
+ */
681
+ private oldestEntry;
682
+ /**
683
+ * Move `completeSince` past `marker`'s revalidations: what is no longer
684
+ * known about a tag once its marker is dropped. A future `expiredAt` waits
685
+ * in `laterFloors` until it is past.
686
+ */
687
+ private raiseFloor;
688
+ /**
689
+ * Add `time` to `laterFloors`, in order. Past the tracked-tag bound the two
690
+ * spans closest together are merged: from the first's start,
691
+ * `completeSince` is the second's end, early - which only costs reads, and
692
+ * for as short a time as any merge allows. Never later than its time, which
693
+ * would let an entry expire unseen.
694
+ */
695
+ private laterFloor;
461
696
  /** Record that `tag`'s marker was known from the table at `at`. */
462
697
  private known;
463
698
  private remember;