cdk-nextjs 0.7.6 → 0.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.jsii +3 -3
  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 +154 -73
  8. package/lib/adapter/adapter.mjs +12 -3
  9. package/lib/adapter/aws-cache-store.d.ts +221 -3
  10. package/lib/adapter/aws-cache-store.js +481 -35
  11. package/lib/adapter/aws-cache-store.test.js +706 -3
  12. package/lib/adapter/cache-handler.d.ts +5 -3
  13. package/lib/adapter/cache-handler.js +16 -10
  14. package/lib/adapter/cache-handler.mjs +517 -49
  15. package/lib/adapter/cache-handler.test.js +44 -1
  16. package/lib/adapter/cache-utils.d.ts +22 -0
  17. package/lib/adapter/cache-utils.js +44 -1
  18. package/lib/adapter/function-groups.js +17 -4
  19. package/lib/adapter/function-groups.test.js +33 -1
  20. package/lib/adapter/memory-cache-handler.d.ts +5 -1
  21. package/lib/adapter/memory-cache-handler.js +6 -2
  22. package/lib/adapter/s3-cache-handler.d.ts +12 -1
  23. package/lib/adapter/s3-cache-handler.js +30 -8
  24. package/lib/adapter/s3-cache-handler.test.js +84 -2
  25. package/lib/adapter/use-cache-common.d.ts +66 -1
  26. package/lib/adapter/use-cache-common.js +142 -19
  27. package/lib/adapter/use-cache-common.test.js +174 -1
  28. package/lib/adapter/use-cache-default-handler.js +36 -6
  29. package/lib/adapter/use-cache-default-handler.mjs +553 -47
  30. package/lib/adapter/use-cache-handlers.test.js +247 -7
  31. package/lib/adapter/use-cache-remote-handler.js +9 -7
  32. package/lib/adapter/use-cache-remote-handler.mjs +542 -45
  33. package/lib/lambdas/post-deploy/prune-revalidation-table.d.ts +2 -1
  34. package/lib/lambdas/post-deploy/prune-revalidation-table.js +35 -13
  35. package/lib/lambdas/post-deploy/prune-revalidation-table.test.js +19 -1
  36. package/lib/nextjs-api.js +1 -1
  37. package/lib/nextjs-build/nextjs-build.js +1 -1
  38. package/lib/nextjs-cache.js +1 -1
  39. package/lib/nextjs-compute/nextjs-containers.js +1 -1
  40. package/lib/nextjs-compute/nextjs-functions.js +1 -1
  41. package/lib/nextjs-distribution.js +1 -1
  42. package/lib/nextjs-post-deploy.js +1 -1
  43. package/lib/nextjs-static-assets.js +1 -1
  44. package/lib/root-constructs/nextjs-base-construct.js +1 -1
  45. package/lib/root-constructs/nextjs-global-containers.js +1 -1
  46. package/lib/root-constructs/nextjs-global-functions.js +1 -1
  47. package/lib/root-constructs/nextjs-regional-containers.js +1 -1
  48. package/lib/root-constructs/nextjs-regional-functions.js +1 -1
  49. package/lib/runtime/core.js +20 -4
  50. package/lib/runtime/core.test.js +17 -1
  51. package/lib/runtime/image-utils.d.ts +8 -9
  52. package/lib/runtime/image-utils.js +16 -1
  53. package/lib/runtime/image-utils.test.js +13 -1
  54. package/lib/runtime/image.js +9 -3
  55. package/lib/runtime/lambda.mjs +66 -18
  56. package/lib/runtime/server.mjs +66 -18
  57. package/lib/runtime/tag-manifest.d.ts +40 -0
  58. package/lib/runtime/tag-manifest.js +59 -0
  59. package/lib/runtime/tag-manifest.test.d.ts +1 -0
  60. package/lib/runtime/tag-manifest.test.js +47 -0
  61. package/package.json +3 -3
  62. package/scripts/e2e-deploy.sh +7 -2
  63. package/scripts/e2e-harness/README.md +72 -35
  64. package/scripts/e2e-harness/screen.mjs +148 -7
  65. package/scripts/playwright-install-deps.sh +40 -0
@@ -116,6 +116,13 @@ export declare const BATCH_GET_MAX_KEYS = 100;
116
116
  * revalidation log, the reads scale with instances instead of requests or tags.
117
117
  */
118
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;
119
126
  /** DynamoDB's limit on a sort key, in UTF-8 bytes. */
120
127
  export declare const MAX_SORT_KEY_BYTES = 1024;
121
128
  /**
@@ -302,6 +309,14 @@ export type TrackedTagMarkersOptions = TagTable & {
302
309
  /** `Math.random`, for when each tag's re-read comes due. */
303
310
  random?: () => number;
304
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;
305
320
  };
306
321
  /**
307
322
  * One instance's copy of the marker rows of the tags it has needed, kept
@@ -322,15 +337,19 @@ export type TrackedTagMarkersOptions = TagTable & {
322
337
  * The cost is bounded per instance, not per request:
323
338
  * - {@link ensure} reads a tag's marker the first time it is needed and not
324
339
  * again until it is evicted, so a tag costs one read per instance, not one
325
- * per request.
340
+ * per request. An entry created since {@link completeSince} needs no read
341
+ * at all.
326
342
  * - {@link refresh}, at most once per `refreshIntervalMs`, sends one `Query`
327
343
  * for the log rows written since the last one, and applies those of tags
328
344
  * this instance tracks. Concurrent callers share it. Alongside, it re-reads
329
345
  * the markers of up to {@link BATCH_GET_MAX_KEYS} tags not read for
330
346
  * {@link DEFAULT_TAG_RESYNC_MS}, in one `BatchGetItem`.
331
347
  * A revalidation on another instance is therefore seen within
332
- * `refreshIntervalMs` (plus the query itself). The instance that ran it applies
333
- * 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}.
334
353
  */
335
354
  export declare class TrackedTagMarkers {
336
355
  private readonly markers;
@@ -356,10 +375,64 @@ export declare class TrackedTagMarkers {
356
375
  * result rather than dropped.
357
376
  */
358
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;
359
397
  /** Tracked tags whose read failed, for the next refresh to read again. */
360
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. */
361
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
+ */
362
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;
363
436
  /**
364
437
  * Where the next log query starts (`clock()`): the last successful one's
365
438
  * start, less {@link REVALIDATION_LOG_LOOKBACK_MS}.
@@ -374,6 +447,41 @@ export declare class TrackedTagMarkers {
374
447
  constructor(options: TrackedTagMarkersOptions);
375
448
  /** `tag`'s marker as this instance knows it, if it tracks `tag`. */
376
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;
377
485
  /**
378
486
  * Track `tag` with `marker`: what this instance just wrote, which it knows
379
487
  * without reading it back.
@@ -405,11 +513,80 @@ export declare class TrackedTagMarkers {
405
513
  * tags may have been revalidated at any time before.
406
514
  */
407
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
+ /**
569
+ * Run `fn` outside the request's context (`outsideRequest`), keeping only
570
+ * whether the render was {@link catchUp caught up}: a cache handler method
571
+ * called from inside a staged render, whose clock reads and AWS SDK calls
572
+ * would otherwise end its static stage.
573
+ */
574
+ outsideRender<T>(fn: () => T): T;
575
+ /** Whether `at` is more than the interval and the grace ago. */
576
+ private overdue;
408
577
  /**
409
578
  * Catch up on revalidations other instances ran, at most once per
410
579
  * `refreshIntervalMs`. See {@link TrackedTagMarkers} for the cost.
411
580
  */
412
581
  refresh(): Promise<void>;
582
+ private startTicking;
583
+ private stopTicking;
584
+ /**
585
+ * Whether the process was paused since {@link refreshing} started: a tick
586
+ * came late, or the next one is overdue (after a thaw, a request can run
587
+ * before the timer does).
588
+ */
589
+ private pausedSinceRefreshStarted;
413
590
  /**
414
591
  * Record a revalidation of `tags`: here at once, and in the revalidation
415
592
  * table for every other instance - the marker row plus a log row per tag.
@@ -482,6 +659,47 @@ export declare class TrackedTagMarkers {
482
659
  */
483
660
  private readInto;
484
661
  private takePendingRows;
662
+ /**
663
+ * What this instance knows of `tag`: tracked, or from the log alone -
664
+ * including rows that arrived while its first read is in flight.
665
+ */
666
+ private knownMarker;
667
+ /**
668
+ * Keep `tag`'s log rows in `logOnly`, unless they cannot apply to an entry
669
+ * held, `oldest` or newer (see {@link pruneLogged}), as bounded as the
670
+ * tracked tags. Either way a row let go of takes what it knew with it, so
671
+ * `completeSince` moves past it.
672
+ */
673
+ private rememberLogged;
674
+ /**
675
+ * Drop the `logOnly` rows that cannot apply to an entry held, `oldest` or
676
+ * newer: every revalidation they name so far is no later than it. That
677
+ * moves `completeSince` no further than `oldest`, which costs the entries
678
+ * held nothing, and a profile's `expiredAt` still to come waits in
679
+ * `laterFloors`. Without this, a deployment revalidating many distinct tags
680
+ * filled `logOnly` with tags no entry here carries, and the evictions past
681
+ * its bound kept moving `completeSince` up to the present.
682
+ */
683
+ private pruneLogged;
684
+ /**
685
+ * The oldest entry timestamp of any registered store, `Infinity` with none:
686
+ * early, never late, unless `scan`.
687
+ */
688
+ private oldestEntry;
689
+ /**
690
+ * Move `completeSince` past `marker`'s revalidations: what is no longer
691
+ * known about a tag once its marker is dropped. A future `expiredAt` waits
692
+ * in `laterFloors` until it is past.
693
+ */
694
+ private raiseFloor;
695
+ /**
696
+ * Add `time` to `laterFloors`, in order. Past the tracked-tag bound the two
697
+ * spans closest together are merged: from the first's start,
698
+ * `completeSince` is the second's end, early - which only costs reads, and
699
+ * for as short a time as any merge allows. Never later than its time, which
700
+ * would let an entry expire unseen.
701
+ */
702
+ private laterFloor;
485
703
  /** Record that `tag`'s marker was known from the table at `at`. */
486
704
  private known;
487
705
  private remember;