cdk-nextjs 0.6.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsii +2404 -2723
- package/.jsii.tabl.json +1 -1
- package/API.md +1681 -1930
- package/CLAUDE.md +2 -1
- package/README.md +429 -130
- package/assets/lambdas/post-deploy/post-deploy.lambda/index.js +148 -169
- package/assets/lambdas/redeploy-stage/redeploy-stage.lambda/index.js +875 -0
- package/docs/adapter-runtime.md +185 -0
- package/docs/breaking-changes.md +667 -4
- package/docs/caching-guide.md +156 -112
- package/docs/cdk-nextjs-cfn-exec-policy.json +2 -1
- package/docs/development-guide.md +1 -1
- package/docs/harness-coverage.md +598 -0
- package/docs/init-cache-deployment.md +32 -1
- package/docs/next-build-output-guide.md +30 -20
- package/examples/README.md +5 -1
- package/lib/adapter/__fixtures__/app-playground-base-path.json +4926 -0
- package/lib/adapter/__fixtures__/app-playground.json +4917 -0
- package/lib/adapter/__fixtures__/pages-i18n.json +1728 -0
- package/lib/adapter/adapter.mjs +1551 -185
- package/lib/adapter/adapter.test.js +106 -0
- package/lib/adapter/aws-cache-store.d.ts +489 -0
- package/lib/adapter/aws-cache-store.js +1004 -0
- package/lib/adapter/aws-cache-store.test.d.ts +1 -0
- package/lib/adapter/aws-cache-store.test.js +361 -0
- package/lib/adapter/build-outputs.d.ts +80 -0
- package/lib/adapter/build-outputs.js +857 -0
- package/lib/adapter/build-outputs.test.d.ts +1 -0
- package/lib/adapter/build-outputs.test.js +863 -0
- package/lib/adapter/cache-handler.d.ts +9 -2
- package/lib/adapter/cache-handler.js +72 -96
- package/lib/adapter/cache-handler.mjs +18956 -17774
- package/lib/adapter/cache-handler.test.js +263 -31
- package/lib/adapter/cache-kinds.d.ts +32 -0
- package/lib/adapter/cache-kinds.js +35 -0
- package/lib/adapter/cache-kinds.test.d.ts +1 -0
- package/lib/adapter/cache-kinds.test.js +69 -0
- package/lib/adapter/cache-utils.d.ts +90 -0
- package/lib/adapter/cache-utils.js +199 -4
- package/lib/adapter/cache-utils.test.d.ts +1 -0
- package/lib/adapter/cache-utils.test.js +179 -0
- package/lib/adapter/function-groups.d.ts +215 -0
- package/lib/adapter/function-groups.js +1099 -0
- package/lib/adapter/function-groups.test.d.ts +1 -0
- package/lib/adapter/function-groups.test.js +743 -0
- package/lib/adapter/init-cache.d.ts +6 -0
- package/lib/adapter/init-cache.js +245 -0
- package/lib/adapter/init-cache.test.d.ts +1 -0
- package/lib/adapter/init-cache.test.js +137 -0
- package/lib/adapter/local-file-cache-handler.d.ts +23 -0
- package/lib/adapter/local-file-cache-handler.js +52 -1
- package/lib/adapter/memory-cache-handler.d.ts +26 -17
- package/lib/adapter/memory-cache-handler.js +32 -50
- package/lib/adapter/memory-cache-handler.test.js +105 -85
- package/lib/adapter/s3-cache-handler.d.ts +179 -40
- package/lib/adapter/s3-cache-handler.js +807 -277
- package/lib/adapter/s3-cache-handler.test.js +1186 -31
- package/lib/adapter/use-cache-common.d.ts +90 -0
- package/lib/adapter/use-cache-common.js +250 -0
- package/lib/adapter/use-cache-common.test.d.ts +1 -0
- package/lib/adapter/use-cache-common.test.js +646 -0
- package/lib/adapter/use-cache-default-handler.d.ts +24 -0
- package/lib/adapter/use-cache-default-handler.js +90 -0
- package/lib/adapter/use-cache-default-handler.mjs +38645 -0
- package/lib/adapter/use-cache-handlers.test.d.ts +1 -0
- package/lib/adapter/use-cache-handlers.test.js +349 -0
- package/lib/adapter/use-cache-remote-handler.d.ts +26 -0
- package/lib/adapter/use-cache-remote-handler.js +186 -0
- package/lib/{image-optimization/handler.mjs → adapter/use-cache-remote-handler.mjs} +33572 -38798
- package/lib/generated-structs/OptionalNextjsApiProps.d.ts +59 -0
- package/lib/generated-structs/OptionalNextjsApiProps.js +3 -0
- package/lib/generated-structs/OptionalNextjsCacheProps.d.ts +15 -6
- package/lib/generated-structs/OptionalNextjsCacheProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsContainersProps.d.ts +25 -11
- package/lib/generated-structs/OptionalNextjsContainersProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsDistributionProps.d.ts +31 -9
- package/lib/generated-structs/OptionalNextjsDistributionProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsPostDeployProps.d.ts +8 -0
- package/lib/generated-structs/OptionalNextjsPostDeployProps.js +1 -1
- package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.d.ts +6 -4
- package/lib/generated-structs/OptionalPostDeployCustomResourceProperties.js +1 -1
- package/lib/index.d.ts +6 -7
- package/lib/index.js +2 -4
- package/lib/lambdas/post-deploy/prune-cache-bucket.d.ts +7 -3
- package/lib/lambdas/post-deploy/prune-cache-bucket.js +57 -71
- package/lib/lambdas/post-deploy/prune-cache-bucket.test.d.ts +1 -0
- package/lib/lambdas/post-deploy/prune-cache-bucket.test.js +114 -0
- package/lib/lambdas/post-deploy/prune-revalidation-table.d.ts +1 -0
- package/lib/lambdas/post-deploy/prune-revalidation-table.js +23 -31
- package/lib/lambdas/post-deploy/prune-revalidation-table.test.d.ts +1 -0
- package/lib/lambdas/post-deploy/prune-revalidation-table.test.js +71 -0
- package/lib/lambdas/post-deploy/prune-s3.d.ts +14 -5
- package/lib/lambdas/post-deploy/prune-s3.js +41 -12
- package/lib/lambdas/post-deploy/prune-s3.test.js +97 -18
- package/lib/lambdas/redeploy-stage/redeploy-stage-function.d.ts +13 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage-function.js +55 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.d.ts +24 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.js +68 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.lambda.d.ts +20 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.lambda.js +24 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.test.d.ts +1 -0
- package/lib/lambdas/redeploy-stage/redeploy-stage.test.js +93 -0
- package/lib/nextjs-api.d.ts +102 -18
- package/lib/nextjs-api.js +338 -38
- package/lib/nextjs-api.test.js +537 -1
- package/lib/nextjs-build/global-containers.Dockerfile +15 -10
- package/lib/nextjs-build/nextjs-build.d.ts +339 -63
- package/lib/nextjs-build/nextjs-build.js +684 -327
- package/lib/nextjs-build/nextjs-build.test.d.ts +1 -0
- package/lib/nextjs-build/nextjs-build.test.js +412 -0
- package/lib/nextjs-build/patch-fetch.js +1 -1
- package/lib/nextjs-build/patch-fetch.test.js +166 -11
- package/lib/nextjs-build/regional-containers.Dockerfile +17 -10
- package/lib/nextjs-cache.d.ts +82 -4
- package/lib/nextjs-cache.js +186 -10
- package/lib/nextjs-cache.test.js +176 -1
- package/lib/nextjs-compute/nextjs-compute-base-props.d.ts +21 -8
- package/lib/nextjs-compute/nextjs-compute-base-props.js +41 -1
- package/lib/nextjs-compute/nextjs-compute-base-props.test.d.ts +1 -0
- package/lib/nextjs-compute/nextjs-compute-base-props.test.js +44 -0
- package/lib/nextjs-compute/nextjs-containers.d.ts +19 -0
- package/lib/nextjs-compute/nextjs-containers.js +57 -20
- package/lib/nextjs-compute/nextjs-functions.d.ts +122 -7
- package/lib/nextjs-compute/nextjs-functions.js +113 -67
- package/lib/nextjs-compute/nextjs-functions.test.d.ts +1 -0
- package/lib/nextjs-compute/nextjs-functions.test.js +99 -0
- package/lib/nextjs-distribution.d.ts +194 -23
- package/lib/nextjs-distribution.js +535 -62
- package/lib/nextjs-distribution.test.d.ts +1 -0
- package/lib/nextjs-distribution.test.js +697 -0
- package/lib/nextjs-post-deploy.d.ts +13 -5
- package/lib/nextjs-post-deploy.js +13 -10
- package/lib/nextjs-post-deploy.test.d.ts +1 -0
- package/lib/nextjs-post-deploy.test.js +34 -0
- package/lib/nextjs-static-assets.d.ts +17 -4
- package/lib/nextjs-static-assets.js +22 -5
- package/lib/root-constructs/nextjs-base-construct.d.ts +102 -58
- package/lib/root-constructs/nextjs-base-construct.js +139 -47
- package/lib/root-constructs/nextjs-global-containers.d.ts +18 -20
- package/lib/root-constructs/nextjs-global-containers.js +20 -71
- package/lib/root-constructs/nextjs-global-functions.d.ts +26 -28
- package/lib/root-constructs/nextjs-global-functions.js +52 -70
- package/lib/root-constructs/nextjs-regional-containers.d.ts +18 -5
- package/lib/root-constructs/nextjs-regional-containers.js +8 -15
- package/lib/root-constructs/nextjs-regional-functions.d.ts +22 -18
- package/lib/root-constructs/nextjs-regional-functions.js +8 -26
- package/lib/runtime/api-gateway-path.d.ts +34 -0
- package/lib/runtime/api-gateway-path.js +71 -0
- package/lib/runtime/api-gateway-path.test.d.ts +1 -0
- package/lib/runtime/api-gateway-path.test.js +65 -0
- package/lib/runtime/case-redirect.d.ts +22 -0
- package/lib/runtime/case-redirect.js +63 -0
- package/lib/runtime/case-redirect.test.d.ts +1 -0
- package/lib/runtime/case-redirect.test.js +32 -0
- package/lib/runtime/core.d.ts +234 -0
- package/lib/runtime/core.js +1216 -0
- package/lib/runtime/core.test.d.ts +1 -0
- package/lib/runtime/core.test.js +1686 -0
- package/lib/runtime/deployment-root.d.ts +13 -0
- package/lib/runtime/deployment-root.js +29 -0
- package/lib/runtime/dispatch.d.ts +331 -0
- package/lib/runtime/dispatch.js +751 -0
- package/lib/runtime/dispatch.test.d.ts +1 -0
- package/lib/runtime/dispatch.test.js +853 -0
- package/lib/runtime/entrypoints.d.ts +57 -0
- package/lib/runtime/entrypoints.js +115 -0
- package/lib/runtime/http/abort-signal.d.ts +6 -0
- package/lib/runtime/http/abort-signal.js +19 -0
- package/lib/runtime/http/abort-signal.test.d.ts +1 -0
- package/lib/runtime/http/abort-signal.test.js +49 -0
- package/lib/runtime/http/node-server.d.ts +38 -0
- package/lib/runtime/http/node-server.js +99 -0
- package/lib/runtime/http/node-server.test.d.ts +1 -0
- package/lib/runtime/http/node-server.test.js +96 -0
- package/lib/runtime/http/node-sink.d.ts +16 -0
- package/lib/runtime/http/node-sink.js +48 -0
- package/lib/runtime/http/node-sink.test.d.ts +1 -0
- package/lib/runtime/http/node-sink.test.js +66 -0
- package/lib/runtime/http/request.d.ts +62 -0
- package/lib/runtime/http/request.js +134 -0
- package/lib/runtime/http/request.test.d.ts +1 -0
- package/lib/runtime/http/request.test.js +103 -0
- package/lib/runtime/http/response.d.ts +77 -0
- package/lib/runtime/http/response.js +229 -0
- package/lib/runtime/http/response.test.d.ts +1 -0
- package/lib/runtime/http/response.test.js +178 -0
- package/lib/runtime/http/sink.d.ts +73 -0
- package/lib/runtime/http/sink.js +188 -0
- package/lib/runtime/http/sink.test.d.ts +1 -0
- package/lib/runtime/http/sink.test.js +257 -0
- package/lib/runtime/image-utils.d.ts +78 -0
- package/lib/runtime/image-utils.js +129 -0
- package/lib/runtime/image-utils.test.d.ts +1 -0
- package/lib/runtime/image-utils.test.js +237 -0
- package/lib/runtime/image.d.ts +76 -0
- package/lib/runtime/image.js +296 -0
- package/lib/runtime/image.test.d.ts +1 -0
- package/lib/runtime/image.test.js +405 -0
- package/lib/runtime/lambda-event.d.ts +4 -0
- package/lib/runtime/lambda-event.js +113 -0
- package/lib/runtime/lambda-event.test.d.ts +1 -0
- package/lib/runtime/lambda-event.test.js +119 -0
- package/lib/runtime/lambda.mjs +41600 -0
- package/lib/runtime/load-module.d.ts +8 -0
- package/lib/runtime/load-module.js +35 -0
- package/lib/runtime/manifest.d.ts +215 -0
- package/lib/runtime/manifest.js +118 -0
- package/lib/runtime/middleware.d.ts +43 -0
- package/lib/runtime/middleware.js +113 -0
- package/lib/runtime/middleware.test.d.ts +1 -0
- package/lib/runtime/middleware.test.js +259 -0
- package/lib/runtime/next-modules.d.ts +71 -0
- package/lib/runtime/next-modules.js +144 -0
- package/lib/runtime/next-modules.test.d.ts +1 -0
- package/lib/runtime/next-modules.test.js +34 -0
- package/lib/runtime/public-files.d.ts +32 -0
- package/lib/runtime/public-files.js +117 -0
- package/lib/runtime/public-files.test.d.ts +1 -0
- package/lib/runtime/public-files.test.js +97 -0
- package/lib/runtime/server.mjs +41593 -0
- package/lib/runtime/static-files.d.ts +50 -0
- package/lib/runtime/static-files.js +266 -0
- package/lib/runtime/static-files.test.d.ts +1 -0
- package/lib/runtime/static-files.test.js +118 -0
- package/lib/runtime/util.d.ts +28 -0
- package/lib/runtime/util.js +79 -0
- package/lib/utils/base-path.d.ts +106 -13
- package/lib/utils/base-path.js +254 -41
- package/lib/utils/base-path.test.js +219 -51
- package/lib/utils/get-architecture.d.ts +7 -8
- package/lib/utils/get-architecture.js +12 -13
- package/lib/utils/zip-directory.d.ts +14 -0
- package/lib/utils/zip-directory.js +121 -0
- package/lib/utils/zip-directory.test.d.ts +1 -0
- package/lib/utils/zip-directory.test.js +99 -0
- package/package.json +22 -3
- package/scripts/capture-adapter-fixture.mjs +325 -0
- package/scripts/e2e-deploy.sh +398 -0
- package/scripts/e2e-harness/README.md +707 -0
- package/scripts/e2e-harness/app.js +370 -0
- package/scripts/e2e-harness/common.sh +249 -0
- package/scripts/e2e-harness/screen.mjs +302 -0
- package/scripts/e2e-harness/stage-proxy.mjs +145 -0
- package/scripts/e2e-harness/type-filters.mjs +47 -0
- package/scripts/e2e-logs.sh +75 -0
- package/scripts/e2e-offline.sh +91 -0
- package/scripts/e2e-sweep.sh +370 -0
- package/scripts/e2e-warm.sh +114 -0
- package/scripts/zero-config-build.mjs +418 -0
- package/docs/1k-concurrent-users-executions.png +0 -0
- package/docs/plans/adapter-runtime-release.md +0 -362
- package/docs/plans/phase-0-image-function-opt-in.md +0 -295
- package/lib/generated-structs/OptionalDockerImageFunctionProps.d.ts +0 -425
- package/lib/generated-structs/OptionalDockerImageFunctionProps.js +0 -3
- package/lib/image-optimization/handler-utils.d.ts +0 -34
- package/lib/image-optimization/handler-utils.js +0 -83
- package/lib/image-optimization/handler-utils.test.js +0 -178
- package/lib/nextjs-build/functions.Dockerfile +0 -31
- package/lib/nextjs-compute/nextjs-image-function.d.ts +0 -53
- package/lib/nextjs-compute/nextjs-image-function.js +0 -59
- package/lib/utils/experimental-flags.d.ts +0 -24
- package/lib/utils/experimental-flags.js +0 -30
- /package/lib/{image-optimization/handler-utils.test.d.ts → adapter/adapter.test.d.ts} +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const esbuild_1 = require("esbuild");
|
|
9
|
+
// `adapter.mts` is ESM that calls `import.meta.resolve`, which ts-jest's
|
|
10
|
+
// CommonJS cannot run, so it is bundled the way `pnpm bundle` does and driven
|
|
11
|
+
// from a real `node`. Its `cdk-nextjs/…` specifiers resolve against a stand-in
|
|
12
|
+
// package carrying the real `exports`, so the test needs no prior bundle.
|
|
13
|
+
const repoRoot = (0, node_path_1.join)(__dirname, "..", "..");
|
|
14
|
+
let dir;
|
|
15
|
+
let pkgDir;
|
|
16
|
+
beforeAll(() => {
|
|
17
|
+
// Real, since `import.meta.resolve` returns real paths (macOS's tmpdir is a link).
|
|
18
|
+
dir = (0, node_fs_1.realpathSync)((0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "cdk-nextjs-adapter-")));
|
|
19
|
+
pkgDir = (0, node_path_1.join)(dir, "node_modules", "cdk-nextjs");
|
|
20
|
+
const { exports } = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(repoRoot, "package.json"), "utf-8"));
|
|
21
|
+
(0, node_fs_1.mkdirSync)(pkgDir, { recursive: true });
|
|
22
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(pkgDir, "package.json"), JSON.stringify({ name: "cdk-nextjs", exports }));
|
|
23
|
+
for (const target of Object.values(exports)) {
|
|
24
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)((0, node_path_1.join)(pkgDir, target.import)), { recursive: true });
|
|
25
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(pkgDir, target.import), "");
|
|
26
|
+
}
|
|
27
|
+
(0, node_fs_1.symlinkSync)((0, node_path_1.join)(repoRoot, "node_modules", "next"), (0, node_path_1.join)(dir, "node_modules", "next"));
|
|
28
|
+
(0, esbuild_1.buildSync)({
|
|
29
|
+
entryPoints: [(0, node_path_1.join)(repoRoot, "src/adapter/adapter.mts")],
|
|
30
|
+
bundle: true,
|
|
31
|
+
platform: "node",
|
|
32
|
+
target: "node24",
|
|
33
|
+
format: "esm",
|
|
34
|
+
external: ["next"],
|
|
35
|
+
outfile: (0, node_path_1.join)(dir, "adapter.mjs"),
|
|
36
|
+
banner: {
|
|
37
|
+
js: "import { createRequire as __cdkNextjsCreateRequire } from 'node:module'; const require = __cdkNextjsCreateRequire(import.meta.url);",
|
|
38
|
+
},
|
|
39
|
+
logLevel: "silent",
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
afterAll(() => {
|
|
43
|
+
(0, node_fs_1.rmSync)(dir, { recursive: true, force: true });
|
|
44
|
+
});
|
|
45
|
+
/** `adapter.modifyConfig(config, { phase, nextVersion })`, run by `node`. */
|
|
46
|
+
function modifyConfig(config, phase, nextVersion = "16.3.0") {
|
|
47
|
+
const script = `import adapter from ${JSON.stringify((0, node_path_1.join)(dir, "adapter.mjs"))};` +
|
|
48
|
+
`const out = await adapter.modifyConfig(${JSON.stringify(config)}, ` +
|
|
49
|
+
`${JSON.stringify({ phase, nextVersion })});` +
|
|
50
|
+
`process.stdout.write(JSON.stringify(out));`;
|
|
51
|
+
return JSON.parse((0, node_child_process_1.execFileSync)(process.execPath, ["--input-type=module", "-e", script], {
|
|
52
|
+
cwd: dir,
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
const handler = (path) => (0, node_path_1.join)(pkgDir, "lib/adapter", path);
|
|
58
|
+
const ours = () => ({
|
|
59
|
+
default: handler("use-cache-default-handler.mjs"),
|
|
60
|
+
remote: handler("use-cache-remote-handler.mjs"),
|
|
61
|
+
});
|
|
62
|
+
describe("modifyConfig", () => {
|
|
63
|
+
it("wires cdk-nextjs's cache handlers into the build", () => {
|
|
64
|
+
const out = modifyConfig({ images: {} }, "phase-production-build");
|
|
65
|
+
expect(out.cacheHandler).toBe(handler("cache-handler.mjs"));
|
|
66
|
+
expect(out.cacheHandlers).toEqual(ours());
|
|
67
|
+
expect(out.images.customCacheHandler).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
it("keeps the handlers the app configured, and adds the missing ones", () => {
|
|
70
|
+
const out = modifyConfig({
|
|
71
|
+
cacheHandler: "/app/my-handler.js",
|
|
72
|
+
cacheHandlers: { remote: "/app/remote.js", custom: "/app/custom.js" },
|
|
73
|
+
images: { customCacheHandler: "/app/images.js" },
|
|
74
|
+
}, "phase-production-build");
|
|
75
|
+
expect(out.cacheHandler).toBe("/app/my-handler.js");
|
|
76
|
+
expect(out.cacheHandlers).toEqual({
|
|
77
|
+
default: ours().default,
|
|
78
|
+
remote: "/app/remote.js",
|
|
79
|
+
custom: "/app/custom.js",
|
|
80
|
+
});
|
|
81
|
+
expect(out.images.customCacheHandler).toBe("/app/images.js");
|
|
82
|
+
});
|
|
83
|
+
// `next start` registers `use cache` handlers from this config, once.
|
|
84
|
+
it("registers the same `use cache` handlers for `next start`", () => {
|
|
85
|
+
const out = modifyConfig({ cacheHandlers: { default: "/app/default.js" } }, "phase-production-server");
|
|
86
|
+
expect(out.cacheHandlers).toEqual({
|
|
87
|
+
default: "/app/default.js",
|
|
88
|
+
remote: ours().remote,
|
|
89
|
+
});
|
|
90
|
+
expect(out.cacheHandler).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
it("leaves other phases alone", () => {
|
|
93
|
+
expect(modifyConfig({ images: {} }, "phase-development-server")).toEqual({
|
|
94
|
+
images: {},
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("assertSupportedNextVersion", () => {
|
|
99
|
+
it.each(["16.2.9", "15.5.0"])("rejects Next.js %s at build time", (version) => {
|
|
100
|
+
expect(() => modifyConfig({ images: {} }, "phase-production-build", version)).toThrow(`Next.js ${version} is not supported`);
|
|
101
|
+
});
|
|
102
|
+
it.each(["16.3.0", "17.0.0-canary.1"])("accepts Next.js %s", (version) => {
|
|
103
|
+
expect(() => modifyConfig({ images: {} }, "phase-production-build", version)).not.toThrow();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci50ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FkYXB0ZXIvYWRhcHRlci50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsc0RBQXNEO0FBQ3RELDJEQUFrRDtBQUNsRCxxQ0FRaUI7QUFDakIscUNBQWlDO0FBQ2pDLHlDQUEwQztBQUMxQyxxQ0FBb0M7QUFFcEMseUVBQXlFO0FBQ3pFLDhFQUE4RTtBQUM5RSwrRUFBK0U7QUFDL0UsMEVBQTBFO0FBQzFFLE1BQU0sUUFBUSxHQUFHLElBQUEsZ0JBQUksRUFBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQzdDLElBQUksR0FBVyxDQUFDO0FBQ2hCLElBQUksTUFBYyxDQUFDO0FBRW5CLFNBQVMsQ0FBQyxHQUFHLEVBQUU7SUFDYixtRkFBbUY7SUFDbkYsR0FBRyxHQUFHLElBQUEsc0JBQVksRUFBQyxJQUFBLHFCQUFXLEVBQUMsSUFBQSxnQkFBSSxFQUFDLElBQUEsZ0JBQU0sR0FBRSxFQUFFLHFCQUFxQixDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3ZFLE1BQU0sR0FBRyxJQUFBLGdCQUFJLEVBQUMsR0FBRyxFQUFFLGNBQWMsRUFBRSxZQUFZLENBQUMsQ0FBQztJQUNqRCxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FDNUIsSUFBQSxzQkFBWSxFQUFDLElBQUEsZ0JBQUksRUFBQyxRQUFRLEVBQUUsY0FBYyxDQUFDLEVBQUUsT0FBTyxDQUFDLENBQ3RELENBQUM7SUFDRixJQUFBLG1CQUFTLEVBQUMsTUFBTSxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFDdkMsSUFBQSx1QkFBYSxFQUNYLElBQUEsZ0JBQUksRUFBQyxNQUFNLEVBQUUsY0FBYyxDQUFDLEVBQzVCLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLE9BQU8sRUFB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QUNWLFlBQVksQ0FBQyxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsRUFBRSx3QkFBd0IsRUFBRSxPQUFPLENBQUMsQ0FDaEUsQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDbEIsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llcyAqL1xuaW1wb3J0IHsgZXhlY0ZpbGVTeW5jIH0gZnJvbSBcIm5vZGU6Y2hpbGRfcHJvY2Vzc1wiO1xuaW1wb3J0IHtcbiAgbWtkaXJTeW5jLFxuICBta2R0ZW1wU3luYyxcbiAgcmVhZEZpbGVTeW5jLFxuICByZWFscGF0aFN5bmMsXG4gIHJtU3luYyxcbiAgc3ltbGlua1N5bmMsXG4gIHdyaXRlRmlsZVN5bmMsXG59IGZyb20gXCJub2RlOmZzXCI7XG5pbXBvcnQgeyB0bXBkaXIgfSBmcm9tIFwibm9kZTpvc1wiO1xuaW1wb3J0IHsgZGlybmFtZSwgam9pbiB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB7IGJ1aWxkU3luYyB9IGZyb20gXCJlc2J1aWxkXCI7XG5cbi8vIGBhZGFwdGVyLm10c2AgaXMgRVNNIHRoYXQgY2FsbHMgYGltcG9ydC5tZXRhLnJlc29sdmVgLCB3aGljaCB0cy1qZXN0J3Ncbi8vIENvbW1vbkpTIGNhbm5vdCBydW4sIHNvIGl0IGlzIGJ1bmRsZWQgdGhlIHdheSBgcG5wbSBidW5kbGVgIGRvZXMgYW5kIGRyaXZlblxuLy8gZnJvbSBhIHJlYWwgYG5vZGVgLiBJdHMgYGNkay1uZXh0anMv4oCmYCBzcGVjaWZpZXJzIHJlc29sdmUgYWdhaW5zdCBhIHN0YW5kLWluXG4vLyBwYWNrYWdlIGNhcnJ5aW5nIHRoZSByZWFsIGBleHBvcnRzYCwgc28gdGhlIHRlc3QgbmVlZHMgbm8gcHJpb3IgYnVuZGxlLlxuY29uc3QgcmVwb1Jvb3QgPSBqb2luKF9fZGlybmFtZSwgXCIuLlwiLCBcIi4uXCIpO1xubGV0IGRpcjogc3RyaW5nO1xubGV0IHBrZ0Rpcjogc3RyaW5nO1xuXG5iZWZvcmVBbGwoKCkgPT4ge1xuICAvLyBSZWFsLCBzaW5jZSBgaW1wb3J0Lm1ldGEucmVzb2x2ZWAgcmV0dXJucyByZWFsIHBhdGhzIChtYWNPUydzIHRtcGRpciBpcyBhIGxpbmspLlxuICBkaXIgPSByZWFscGF0aFN5bmMobWtkdGVtcFN5bmMoam9pbih0bXBkaXIoKSwgXCJjZGstbmV4dGpzLWFkYXB0ZXItXCIpKSk7XG4gIHBrZ0RpciA9IGpvaW4oZGlyLCBcIm5vZGVfbW9kdWxlc1wiLCBcImNkay1uZXh0anNcIik7XG4gIGNvbnN0IHsgZXhwb3J0cyB9ID0gSlNPTi5wYXJzZShcbiAgICByZWFkRmlsZVN5bmMoam9pbihyZXBvUm9vdCwgXCJwYWNrYWdlLmpzb25cIiksIFwidXRmLThcIiksXG4gICk7XG4gIG1rZGlyU3luYyhwa2dEaXIsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICB3cml0ZUZpbGVTeW5jKFxuICAgIGpvaW4ocGtnRGlyLCBcInBhY2thZ2UuanNvblwiKSxcbiAgICBKU09OLnN0cmluZ2lmeSh7IG5hbWU6IFwiY2RrLW5leHRqc1wiLCBleHBvcnRzIH0pLFxuICApO1xuICBmb3IgKGNvbnN0IHRhcmdldCBvZiBPYmplY3QudmFsdWVzKGV4cG9ydHMpIGFzIHsgaW1wb3J0OiBzdHJpbmcgfVtdKSB7XG4gICAgbWtkaXJTeW5jKGRpcm5hbWUoam9pbihwa2dEaXIsIHRhcmdldC5pbXBvcnQpKSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgd3JpdGVGaWxlU3luYyhqb2luKHBrZ0RpciwgdGFyZ2V0LmltcG9ydCksIFwiXCIpO1xuICB9XG4gIHN5bWxpbmtTeW5jKFxuICAgIGpvaW4ocmVwb1Jvb3QsIFwibm9kZV9tb2R1bGVzXCIsIFwibmV4dFwiKSxcbiAgICBqb2luKGRpciwgXCJub2RlX21vZHVsZXNcIiwgXCJuZXh0XCIpLFxuICApO1xuICBidWlsZFN5bmMoe1xuICAgIGVudHJ5UG9pbnRzOiBbam9pbihyZXBvUm9vdCwgXCJzcmMvYWRhcHRlci9hZGFwdGVyLm10c1wiKV0sXG4gICAgYnVuZGxlOiB0cnVlLFxuICAgIHBsYXRmb3JtOiBcIm5vZGVcIixcbiAgICB0YXJnZXQ6IFwibm9kZTI0XCIsXG4gICAgZm9ybWF0OiBcImVzbVwiLFxuICAgIGV4dGVybmFsOiBbXCJuZXh0XCJdLFxuICAgIG91dGZpbGU6IGpvaW4oZGlyLCBcImFkYXB0ZXIubWpzXCIpLFxuICAgIGJhbm5lcjoge1xuICAgICAganM6IFwiaW1wb3J0IHsgY3JlYXRlUmVxdWlyZSBhcyBfX2Nka05leHRqc0NyZWF0ZVJlcXVpcmUgfSBmcm9tICdub2RlOm1vZHVsZSc7IGNvbnN0IHJlcXVpcmUgPSBfX2Nka05leHRqc0NyZWF0ZVJlcXVpcmUoaW1wb3J0Lm1ldGEudXJsKTtcIixcbiAgICB9LFxuICAgIGxvZ0xldmVsOiBcInNpbGVudFwiLFxuICB9KTtcbn0pO1xuXG5hZnRlckFsbCgoKSA9PiB7XG4gIHJtU3luYyhkaXIsIHsgcmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZSB9KTtcbn0pO1xuXG4vKiogYGFkYXB0ZXIubW9kaWZ5Q29uZmlnKGNvbmZpZywgeyBwaGFzZSwgbmV4dFZlcnNpb24gfSlgLCBydW4gYnkgYG5vZGVgLiAqL1xuZnVuY3Rpb24gbW9kaWZ5Q29uZmlnKFxuICBjb25maWc6IFJlY29yZDxzdHJpbmcsIHVua25vd24+LFxuICBwaGFzZTogc3RyaW5nLFxuICBuZXh0VmVyc2lvbiA9IFwiMTYuMy4wXCIsXG4pOiBSZWNvcmQ8c3RyaW5nLCBhbnk+IHtcbiAgY29uc3Qgc2NyaXB0ID1cbiAgICBgaW1wb3J0IGFkYXB0ZXIgZnJvbSAke0pTT04uc3RyaW5naWZ5KGpvaW4oZGlyLCBcImFkYXB0ZXIubWpzXCIpKX07YCArXG4gICAgYGNvbnN0IG91dCA9IGF3YWl0IGFkYXB0ZXIubW9kaWZ5Q29uZmlnKCR7SlNPTi5zdHJpbmdpZnkoY29uZmlnKX0sIGAgK1xuICAgIGAke0pTT04uc3RyaW5naWZ5KHsgcGhhc2UsIG5leHRWZXJzaW9uIH0pfSk7YCArXG4gICAgYHByb2Nlc3Muc3Rkb3V0LndyaXRlKEpTT04uc3RyaW5naWZ5KG91dCkpO2A7XG4gIHJldHVybiBKU09OLnBhcnNlKFxuICAgIGV4ZWNGaWxlU3luYyhwcm9jZXNzLmV4ZWNQYXRoLCBbXCItLWlucHV0LXR5cGU9bW9kdWxlXCIsIFwiLWVcIiwgc2NyaXB0XSwge1xuICAgICAgY3dkOiBkaXIsXG4gICAgICBlbmNvZGluZzogXCJ1dGYtOFwiLFxuICAgICAgc3RkaW86IFtcImlnbm9yZVwiLCBcInBpcGVcIiwgXCJwaXBlXCJdLFxuICAgIH0pLFxuICApO1xufVxuXG5jb25zdCBoYW5kbGVyID0gKHBhdGg6IHN0cmluZykgPT4gam9pbihwa2dEaXIsIFwibGliL2FkYXB0ZXJcIiwgcGF0aCk7XG5jb25zdCBvdXJzID0gKCkgPT4gKHtcbiAgZGVmYXVsdDogaGFuZGxlcihcInVzZS1jYWNoZS1kZWZhdWx0LWhhbmRsZXIubWpzXCIpLFxuICByZW1vdGU6IGhhbmRsZXIoXCJ1c2UtY2FjaGUtcmVtb3RlLWhhbmRsZXIubWpzXCIpLFxufSk7XG5cbmRlc2NyaWJlKFwibW9kaWZ5Q29uZmlnXCIsICgpID0+IHtcbiAgaXQoXCJ3aXJlcyBjZGstbmV4dGpzJ3MgY2FjaGUgaGFuZGxlcnMgaW50byB0aGUgYnVpbGRcIiwgKCkgPT4ge1xuICAgIGNvbnN0IG91dCA9IG1vZGlmeUNvbmZpZyh7IGltYWdlczoge30gfSwgXCJwaGFzZS1wcm9kdWN0aW9uLWJ1aWxkXCIpO1xuICAgIGV4cGVjdChvdXQuY2FjaGVIYW5kbGVyKS50b0JlKGhhbmRsZXIoXCJjYWNoZS1oYW5kbGVyLm1qc1wiKSk7XG4gICAgZXhwZWN0KG91dC5jYWNoZUhhbmRsZXJzKS50b0VxdWFsKG91cnMoKSk7XG4gICAgZXhwZWN0KG91dC5pbWFnZXMuY3VzdG9tQ2FjaGVIYW5kbGVyKS50b0JlKHRydWUpO1xuICB9KTtcblxuICBpdChcImtlZXBzIHRoZSBoYW5kbGVycyB0aGUgYXBwIGNvbmZpZ3VyZWQsIGFuZCBhZGRzIHRoZSBtaXNzaW5nIG9uZXNcIiwgKCkgPT4ge1xuICAgIGNvbnN0IG91dCA9IG1vZGlmeUNvbmZpZyhcbiAgICAgIHtcbiAgICAgICAgY2FjaGVIYW5kbGVyOiBcIi9hcHAvbXktaGFuZGxlci5qc1wiLFxuICAgICAgICBjYWNoZUhhbmRsZXJzOiB7IHJlbW90ZTogXCIvYXBwL3JlbW90ZS5qc1wiLCBjdXN0b206IFwiL2FwcC9jdXN0b20uanNcIiB9LFxuICAgICAgICBpbWFnZXM6IHsgY3VzdG9tQ2FjaGVIYW5kbGVyOiBcIi9hcHAvaW1hZ2VzLmpzXCIgfSxcbiAgICAgIH0sXG4gICAgICBcInBoYXNlLXByb2R1Y3Rpb24tYnVpbGRcIixcbiAgICApO1xuICAgIGV4cGVjdChvdXQuY2FjaGVIYW5kbGVyKS50b0JlKFwiL2FwcC9teS1oYW5kbGVyLmpzXCIpO1xuICAgIGV4cGVjdChvdXQuY2FjaGVIYW5kbGVycykudG9FcXVhbCh7XG4gICAgICBkZWZhdWx0OiBvdXJzKCkuZGVmYXVsdCxcbiAgICAgIHJlbW90ZTogXCIvYXBwL3JlbW90ZS5qc1wiLFxuICAgICAgY3VzdG9tOiBcIi9hcHAvY3VzdG9tLmpzXCIsXG4gICAgfSk7XG4gICAgZXhwZWN0KG91dC5pbWFnZXMuY3VzdG9tQ2FjaGVIYW5kbGVyKS50b0JlKFwiL2FwcC9pbWFnZXMuanNcIik7XG4gIH0pO1xuXG4gIC8vIGBuZXh0IHN0YXJ0YCByZWdpc3RlcnMgYHVzZSBjYWNoZWAgaGFuZGxlcnMgZnJvbSB0aGlzIGNvbmZpZywgb25jZS5cbiAgaXQoXCJyZWdpc3RlcnMgdGhlIHNhbWUgYHVzZSBjYWNoZWAgaGFuZGxlcnMgZm9yIGBuZXh0IHN0YXJ0YFwiLCAoKSA9PiB7XG4gICAgY29uc3Qgb3V0ID0gbW9kaWZ5Q29uZmlnKFxuICAgICAgeyBjYWNoZUhhbmRsZXJzOiB7IGRlZmF1bHQ6IFwiL2FwcC9kZWZhdWx0LmpzXCIgfSB9LFxuICAgICAgXCJwaGFzZS1wcm9kdWN0aW9uLXNlcnZlclwiLFxuICAgICk7XG4gICAgZXhwZWN0KG91dC5jYWNoZUhhbmRsZXJzKS50b0VxdWFsKHtcbiAgICAgIGRlZmF1bHQ6IFwiL2FwcC9kZWZhdWx0LmpzXCIsXG4gICAgICByZW1vdGU6IG91cnMoKS5yZW1vdGUsXG4gICAgfSk7XG4gICAgZXhwZWN0KG91dC5jYWNoZUhhbmRsZXIpLnRvQmVVbmRlZmluZWQoKTtcbiAgfSk7XG5cbiAgaXQoXCJsZWF2ZXMgb3RoZXIgcGhhc2VzIGFsb25lXCIsICgpID0+IHtcbiAgICBleHBlY3QobW9kaWZ5Q29uZmlnKHsgaW1hZ2VzOiB7fSB9LCBcInBoYXNlLWRldmVsb3BtZW50LXNlcnZlclwiKSkudG9FcXVhbCh7XG4gICAgICBpbWFnZXM6IHt9LFxuICAgIH0pO1xuICB9KTtcbn0pO1xuXG5kZXNjcmliZShcImFzc2VydFN1cHBvcnRlZE5leHRWZXJzaW9uXCIsICgpID0+IHtcbiAgaXQuZWFjaChbXCIxNi4yLjlcIiwgXCIxNS41LjBcIl0pKFxuICAgIFwicmVqZWN0cyBOZXh0LmpzICVzIGF0IGJ1aWxkIHRpbWVcIixcbiAgICAodmVyc2lvbikgPT4ge1xuICAgICAgZXhwZWN0KCgpID0+XG4gICAgICAgIG1vZGlmeUNvbmZpZyh7IGltYWdlczoge30gfSwgXCJwaGFzZS1wcm9kdWN0aW9uLWJ1aWxkXCIsIHZlcnNpb24pLFxuICAgICAgKS50b1Rocm93KGBOZXh0LmpzICR7dmVyc2lvbn0gaXMgbm90IHN1cHBvcnRlZGApO1xuICAgIH0sXG4gICk7XG5cbiAgaXQuZWFjaChbXCIxNi4zLjBcIiwgXCIxNy4wLjAtY2FuYXJ5LjFcIl0pKFwiYWNjZXB0cyBOZXh0LmpzICVzXCIsICh2ZXJzaW9uKSA9PiB7XG4gICAgZXhwZWN0KCgpID0+XG4gICAgICBtb2RpZnlDb25maWcoeyBpbWFnZXM6IHt9IH0sIFwicGhhc2UtcHJvZHVjdGlvbi1idWlsZFwiLCB2ZXJzaW9uKSxcbiAgICApLm5vdC50b1Rocm93KCk7XG4gIH0pO1xufSk7XG4iXX0=
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
|
|
2
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
3
|
+
/**
|
|
4
|
+
* Where the cache lives, as the constructs pass it to the compute through the
|
|
5
|
+
* environment (`NextjsFunctions`, `NextjsContainers`). Empty strings for what is
|
|
6
|
+
* not configured, which each handler reads as "that layer is disabled".
|
|
7
|
+
*/
|
|
8
|
+
export interface AwsCacheConfig {
|
|
9
|
+
/** `CDK_NEXTJS_CACHE_BUCKET_NAME`. */
|
|
10
|
+
bucketName: string;
|
|
11
|
+
/** `CDK_NEXTJS_REVALIDATION_TABLE_NAME`. */
|
|
12
|
+
tableName: string;
|
|
13
|
+
/** `AWS_REGION`, `us-east-1` when unset. */
|
|
14
|
+
region: string;
|
|
15
|
+
/** `CDK_NEXTJS_BUILD_ID`: every S3 key and every marker row is scoped to it. */
|
|
16
|
+
buildId: string;
|
|
17
|
+
}
|
|
18
|
+
/** The configuration from the environment. */
|
|
19
|
+
export declare function resolveAwsCacheConfig(): AwsCacheConfig;
|
|
20
|
+
/**
|
|
21
|
+
* `{buildId}/{cacheKey}.json`, the incremental cache's key convention.
|
|
22
|
+
*
|
|
23
|
+
* The root path becomes `index`, and a leading slash is dropped so there is no
|
|
24
|
+
* empty folder under the build prefix. Everything this returns ends in `.json`,
|
|
25
|
+
* which is what keeps it disjoint from {@link useCacheS3Key}.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildS3Key(buildId: string, cacheKey: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* The folder under the build prefix that `'use cache'` entries are stored in.
|
|
30
|
+
* @see useCacheS3Key
|
|
31
|
+
*/
|
|
32
|
+
export declare const USE_CACHE_KEY_PREFIX = "_use-cache";
|
|
33
|
+
/**
|
|
34
|
+
* `{buildId}/_use-cache/{hash}.entry`, where a `cacheHandlers` entry is stored.
|
|
35
|
+
*
|
|
36
|
+
* Hashed because a `'use cache'` key is the serialized arguments of the call and
|
|
37
|
+
* has no length limit, where an S3 key stops at 1024 bytes. The `.entry` suffix
|
|
38
|
+
* 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
|
+
* Still under the build prefix, so post-deploy pruning drops them with the build.
|
|
42
|
+
*/
|
|
43
|
+
export declare function useCacheS3Key(buildId: string, keyHash: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* The `durations` Next.js 16 passes a tag revalidation for a profile:
|
|
46
|
+
* `revalidateTag("posts", "max")` arrives as `{ expire: <the profile's expire> }`.
|
|
47
|
+
* Absent for `updateTag` and a bare `revalidateTag`, which expire immediately.
|
|
48
|
+
*/
|
|
49
|
+
export interface RevalidateDurations {
|
|
50
|
+
expire?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A tag's marker row, as timestamps in milliseconds. Written by
|
|
54
|
+
* {@link markerUpdate}; each field is absent until a revalidation of that shape
|
|
55
|
+
* has happened.
|
|
56
|
+
*/
|
|
57
|
+
export interface TagMarker {
|
|
58
|
+
/** Expired outright: `updateTag`, or `revalidateTag` with no profile. */
|
|
59
|
+
revalidatedAt?: number;
|
|
60
|
+
/** Stale from here: `revalidateTag(tag, profile)`. */
|
|
61
|
+
staleAt?: number;
|
|
62
|
+
/** When the profile's `expire` runs out, which may be in the future. */
|
|
63
|
+
expiredAt?: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What the tag markers say about an entry: untouched, served stale while a
|
|
67
|
+
* background render replaces it, or expired and re-rendered before answering.
|
|
68
|
+
*/
|
|
69
|
+
export type RevalidationState = "fresh" | "stale" | "expired";
|
|
70
|
+
/**
|
|
71
|
+
* What `marker` means for an entry created at `createdAt`, at `now`.
|
|
72
|
+
*
|
|
73
|
+
* `expiredAt` in the future is a profile's `expire` that has not come yet,
|
|
74
|
+
* compared the way Next.js's `areTagsExpired` does.
|
|
75
|
+
*/
|
|
76
|
+
export declare function markerState(marker: TagMarker, createdAt: number, now: number): RevalidationState;
|
|
77
|
+
/**
|
|
78
|
+
* The clock every tag marker is stamped with, and every entry timestamp it is
|
|
79
|
+
* compared against: the one Next.js stamps `CacheEntry.timestamp` with
|
|
80
|
+
* (`performance.timeOrigin + performance.now()`, in `use-cache-wrapper.js` and
|
|
81
|
+
* the default handler), rather than `Date.now()`. Both handlers write the same
|
|
82
|
+
* marker rows, so both have to use it: two clocks on one row let the last writer
|
|
83
|
+
* decide which clock the others compare against, and they drift apart for as
|
|
84
|
+
* long as a Lambda sandbox or Fargate task lives.
|
|
85
|
+
*/
|
|
86
|
+
export declare function markerClock(): number;
|
|
87
|
+
/**
|
|
88
|
+
* The marker fields a tag revalidated at `now` sets.
|
|
89
|
+
*
|
|
90
|
+
* Without `durations` - `updateTag`, or `revalidateTag` with no profile - the
|
|
91
|
+
* tag's entries expire immediately (`revalidatedAt`). With them the entries go
|
|
92
|
+
* stale now and expire after the profile's `expire`, the same pair of
|
|
93
|
+
* timestamps Next.js's `FileSystemCache.revalidateTag` records. A profile with
|
|
94
|
+
* no `expire` sets only `staleAt`, and whatever `expiredAt` the tag had stays.
|
|
95
|
+
*/
|
|
96
|
+
export declare function markerFor(now: number, durations: RevalidateDurations | undefined): TagMarker;
|
|
97
|
+
/**
|
|
98
|
+
* `existing` with the fields `update` sets, as a {@link markerUpdate} leaves
|
|
99
|
+
* the row: a field `update` does not set keeps its value.
|
|
100
|
+
*/
|
|
101
|
+
export declare function applyMarker(existing: TagMarker | undefined, update: TagMarker): TagMarker;
|
|
102
|
+
/** The marker-row update that sets {@link markerFor}'s fields. */
|
|
103
|
+
export declare function markerUpdate(now: number, durations: RevalidateDurations | undefined): Pick<ConstructorParameters<typeof UpdateItemCommand>[0], "UpdateExpression" | "ExpressionAttributeValues">;
|
|
104
|
+
/** DynamoDB's cap on keys in one `BatchGetItem`. */
|
|
105
|
+
export declare const BATCH_GET_MAX_KEYS = 100;
|
|
106
|
+
/**
|
|
107
|
+
* How often, at most, an instance catches up on revalidations other instances
|
|
108
|
+
* ran, unless `CDK_NEXTJS_TAG_REFRESH_MS` says otherwise: the staleness window
|
|
109
|
+
* for a `revalidateTag` run elsewhere. Every cache hit checks its tags'
|
|
110
|
+
* markers, and every marker of a deployment shares one partition key, so
|
|
111
|
+
* reading them per request capped a route's cache hits at the partition's read
|
|
112
|
+
* throughput (~1,900 req/s on NextjsRegionalFunctions). Once a second, from the
|
|
113
|
+
* revalidation log, the reads scale with instances instead of requests or tags.
|
|
114
|
+
*/
|
|
115
|
+
export declare const DEFAULT_TAG_REFRESH_MS = 1000;
|
|
116
|
+
/**
|
|
117
|
+
* The bare-tag marker rows of the revalidation table (`pk = buildId`,
|
|
118
|
+
* `sk = tag`), which record when a tag was last revalidated and how.
|
|
119
|
+
*
|
|
120
|
+
* The table also holds the incremental cache's `tag#s3Key` mapping rows, which
|
|
121
|
+
* are its own business and stay in `S3CacheHandler`: only the markers mean the
|
|
122
|
+
* same thing to every handler. What an instance keeps of them between reads is
|
|
123
|
+
* {@link TrackedTagMarkers}.
|
|
124
|
+
*/
|
|
125
|
+
export declare class TagMarkerTable {
|
|
126
|
+
private readonly client;
|
|
127
|
+
private readonly tableName;
|
|
128
|
+
private readonly buildId;
|
|
129
|
+
constructor(client: DynamoDBClient, tableName: string, buildId: string);
|
|
130
|
+
/**
|
|
131
|
+
* Record `tag`'s revalidation at `now`. See {@link markerUpdate}.
|
|
132
|
+
*
|
|
133
|
+
* Returns the row as this write left it, so the instance that wrote it can
|
|
134
|
+
* track it rather than read it back: reads are eventually consistent, and a
|
|
135
|
+
* read-back racing replication could return the marker from before the write.
|
|
136
|
+
*/
|
|
137
|
+
write(tag: string, now: number, durations: RevalidateDurations | undefined): Promise<TagMarker | undefined>;
|
|
138
|
+
/**
|
|
139
|
+
* The marker rows for `tags` that exist, by tag, in one `BatchGetItem` per
|
|
140
|
+
* {@link BATCH_GET_MAX_KEYS} tags rather than a `GetItem` each: a page
|
|
141
|
+
* carries its whole implicit `_N_T_/…` chain plus the app's own tags, a
|
|
142
|
+
* page with a few fetches checks each of them too, and every one is on the
|
|
143
|
+
* request path. A tag with no row is absent from the result.
|
|
144
|
+
*/
|
|
145
|
+
read(tags: string[]): Promise<Map<string, TagMarker>>;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* How long a revalidation log row is kept (`ttl`, epoch seconds). DynamoDB never
|
|
149
|
+
* deletes an item before its TTL, only some time after, so a reader that last
|
|
150
|
+
* queried less than this long ago (less a margin for writers' clocks) is sure
|
|
151
|
+
* to find every row it has not seen yet.
|
|
152
|
+
*/
|
|
153
|
+
export declare const REVALIDATION_LOG_TTL_MS: number;
|
|
154
|
+
/** The most `Query` pages one {@link RevalidationLog.query} reads. */
|
|
155
|
+
export declare const REVALIDATION_LOG_MAX_PAGES = 5;
|
|
156
|
+
/** A revalidation read back from the log. */
|
|
157
|
+
export interface RevalidationLogRow {
|
|
158
|
+
/** The row's sort key: unique per revalidation of a tag. */
|
|
159
|
+
sk: string;
|
|
160
|
+
/** When it was written, on the writer's `Date.now()`. */
|
|
161
|
+
at: number;
|
|
162
|
+
tag: string;
|
|
163
|
+
/** The marker fields the revalidation set, as {@link markerFor} returns them. */
|
|
164
|
+
marker: TagMarker;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The revalidation log: one row per tag revalidation (`pk = <buildId>#log`,
|
|
168
|
+
* `sk = <Date.now(), zero-padded>#<tag>`), which expires after
|
|
169
|
+
* {@link REVALIDATION_LOG_TTL_MS}.
|
|
170
|
+
*
|
|
171
|
+
* The marker rows stay the source of truth. The log exists so an instance
|
|
172
|
+
* can ask "what was revalidated since I last looked?" in one `Query`, instead
|
|
173
|
+
* of re-reading every tag it tracks to find out that almost none changed. Every
|
|
174
|
+
* marker row of a deployment shares one partition, and those re-reads from a
|
|
175
|
+
* handful of busy instances were enough to throttle it.
|
|
176
|
+
*
|
|
177
|
+
* The sort key's timestamp is the writer's wall clock (`Date.now()`), not the
|
|
178
|
+
* performance clock ({@link markerClock}) the marker values are stamped with by both
|
|
179
|
+
* handlers: it only orders rows for readers' cursors, which are on their own
|
|
180
|
+
* wall clocks, and is never compared with an entry's timestamp.
|
|
181
|
+
*/
|
|
182
|
+
export declare class RevalidationLog {
|
|
183
|
+
private readonly client;
|
|
184
|
+
private readonly tableName;
|
|
185
|
+
private readonly pk;
|
|
186
|
+
constructor(client: DynamoDBClient, tableName: string, buildId: string);
|
|
187
|
+
/**
|
|
188
|
+
* Record that `tag` was revalidated at `at` (`Date.now()`), setting `marker`.
|
|
189
|
+
*
|
|
190
|
+
* Two revalidations of `tag` in the same millisecond would share a sort key,
|
|
191
|
+
* and the second would overwrite the first. So the row is only written where
|
|
192
|
+
* its key is free, and on a collision it moves to the next millisecond, up
|
|
193
|
+
* to {@link LOG_PUT_ATTEMPTS} times.
|
|
194
|
+
*/
|
|
195
|
+
put(tag: string, at: number, marker: TagMarker): Promise<void>;
|
|
196
|
+
/**
|
|
197
|
+
* The rows written at or after `since` (`Date.now()`), oldest first.
|
|
198
|
+
* `truncated` when there were more than {@link REVALIDATION_LOG_MAX_PAGES}
|
|
199
|
+
* pages of them, and `rows` is only the start.
|
|
200
|
+
*
|
|
201
|
+
* Eventually consistent: a row written a moment ago may not show yet, which
|
|
202
|
+
* is what the reader's lookback is for.
|
|
203
|
+
*/
|
|
204
|
+
query(since: number): Promise<{
|
|
205
|
+
rows: RevalidationLogRow[];
|
|
206
|
+
truncated: boolean;
|
|
207
|
+
}>;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* How far behind the last query's start the next one starts reading the log:
|
|
211
|
+
* the room for the `Query` being eventually consistent and for writers' clocks
|
|
212
|
+
* running behind the reader's. Rows inside it are read again, and dropped as
|
|
213
|
+
* already applied.
|
|
214
|
+
*/
|
|
215
|
+
export declare const REVALIDATION_LOG_LOOKBACK_MS = 5000;
|
|
216
|
+
/**
|
|
217
|
+
* The longest {@link TrackedTagMarkers} trusts the log alone for a tag before
|
|
218
|
+
* reading its marker row again. The log is the fast path, but the marker rows
|
|
219
|
+
* are the source of truth: a log row whose write failed would otherwise be
|
|
220
|
+
* missed for as long as the tag stays tracked.
|
|
221
|
+
*
|
|
222
|
+
* Rolling, not all at once: each refresh re-reads at most
|
|
223
|
+
* {@link BATCH_GET_MAX_KEYS} of the tags due, oldest first. Re-reading them all
|
|
224
|
+
* together cost ~500 RCU at 1000 tags - a fleet started by one load test did it
|
|
225
|
+
* in the same minute, 12,800 RCU, and throttled - and would be ~5,000 RCU at
|
|
226
|
+
* {@link DEFAULT_MAX_TRACKED_TAGS}, past what the one partition serves in a
|
|
227
|
+
* second. Spread out, 10,000 tracked tags cost ~8 RCU a second per instance.
|
|
228
|
+
*
|
|
229
|
+
* Each tag is due at a random 75-100% of this after it was last read, so tags
|
|
230
|
+
* first read together - an instance warming up - do not all come due together
|
|
231
|
+
* again every interval: the load test that measured the rolling re-read saw
|
|
232
|
+
* its warm-up's reads return as a 30-36 RCU/s bump ten minutes later.
|
|
233
|
+
*/
|
|
234
|
+
export declare const DEFAULT_TAG_RESYNC_MS: number;
|
|
235
|
+
/**
|
|
236
|
+
* How long since the last successful log query the log can still be trusted to
|
|
237
|
+
* hold every row not read yet: its TTL less a margin for writers' clocks. Past
|
|
238
|
+
* it - a Lambda frozen between invocations, a run of failed queries - the
|
|
239
|
+
* tracked markers are forgotten, to be read again as they are needed, and the
|
|
240
|
+
* log is followed from there.
|
|
241
|
+
*/
|
|
242
|
+
export declare const MAX_REVALIDATION_LOG_GAP_MS: number;
|
|
243
|
+
/**
|
|
244
|
+
* The most tags one instance tracks. Past it the least recently used is
|
|
245
|
+
* forgotten, which costs a read the next time it is needed: an untracked tag
|
|
246
|
+
* is read before it is trusted.
|
|
247
|
+
*
|
|
248
|
+
* A tracked tag costs memory (~2 MB at this bound) and its share of the rolling
|
|
249
|
+
* re-read, not a read every refresh, so the bound is set by how many tags an
|
|
250
|
+
* instance really uses. Every path is a tag of its own (`_N_T_/<path>`), and at
|
|
251
|
+
* 1000 an app of 1000 tagged pages kept evicting what it was about to need:
|
|
252
|
+
* half its requests re-read a marker.
|
|
253
|
+
*/
|
|
254
|
+
export declare const DEFAULT_MAX_TRACKED_TAGS = 10000;
|
|
255
|
+
/**
|
|
256
|
+
* The revalidation table's marker rows and its log, which are only ever used
|
|
257
|
+
* together, or neither for tags local to this process.
|
|
258
|
+
*/
|
|
259
|
+
export type TagTable = {
|
|
260
|
+
markers: TagMarkerTable;
|
|
261
|
+
log: RevalidationLog;
|
|
262
|
+
} | {
|
|
263
|
+
markers?: undefined;
|
|
264
|
+
log?: undefined;
|
|
265
|
+
};
|
|
266
|
+
/** How {@link TrackedTagMarkers} is built. */
|
|
267
|
+
export type TrackedTagMarkersOptions = TagTable & {
|
|
268
|
+
/**
|
|
269
|
+
* How often, at most, {@link TrackedTagMarkers.refresh} reads anything.
|
|
270
|
+
* @default DEFAULT_TAG_REFRESH_MS
|
|
271
|
+
*/
|
|
272
|
+
refreshIntervalMs?: number;
|
|
273
|
+
maxTrackedTags?: number;
|
|
274
|
+
/** @see DEFAULT_TAG_RESYNC_MS */
|
|
275
|
+
resyncIntervalMs?: number;
|
|
276
|
+
/** The wall clock the log's cursor and the intervals are kept on. */
|
|
277
|
+
clock?: () => number;
|
|
278
|
+
/** `Math.random`, for when each tag's re-read comes due. */
|
|
279
|
+
random?: () => number;
|
|
280
|
+
debug?: (message: string) => void;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* One instance's copy of the marker rows of the tags it has needed, kept
|
|
284
|
+
* current through the {@link RevalidationLog}: the process's one tag manifest
|
|
285
|
+
* (`sharedTagManifest`), for the `'use cache'` handlers and the incremental
|
|
286
|
+
* cache alike.
|
|
287
|
+
*
|
|
288
|
+
* Next.js's built-in `'use cache'` handler keeps tags in a module-level map
|
|
289
|
+
* (`tags-manifest.external.js`) that only its own `updateTags` writes, and its
|
|
290
|
+
* `refreshTags` is a no-op, so a `revalidateTag` on one instance never reaches
|
|
291
|
+
* the others: they keep serving the revalidated entry, including inside pages
|
|
292
|
+
* that are being re-rendered *because* of that revalidation. The
|
|
293
|
+
* `cacheHandlers` interface's answer is `refreshTags`, "called before starting
|
|
294
|
+
* a new request … to refresh the local tags manifest", and this is that
|
|
295
|
+
* manifest, backed by the same marker rows the incremental cache's
|
|
296
|
+
* `revalidateTag` writes (`pk = buildId`, `sk = tag`).
|
|
297
|
+
*
|
|
298
|
+
* The cost is bounded per instance, not per request:
|
|
299
|
+
* - {@link ensure} reads a tag's marker the first time it is needed and not
|
|
300
|
+
* again until it is evicted, so a tag costs one read per instance, not one
|
|
301
|
+
* per request.
|
|
302
|
+
* - {@link refresh}, at most once per `refreshIntervalMs`, sends one `Query`
|
|
303
|
+
* for the log rows written since the last one, and applies those of tags
|
|
304
|
+
* this instance tracks. Concurrent callers share it. Alongside, it re-reads
|
|
305
|
+
* the markers of up to {@link BATCH_GET_MAX_KEYS} tags not read for
|
|
306
|
+
* {@link DEFAULT_TAG_RESYNC_MS}, in one `BatchGetItem`.
|
|
307
|
+
* 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}.
|
|
310
|
+
*/
|
|
311
|
+
export declare class TrackedTagMarkers {
|
|
312
|
+
private readonly markers;
|
|
313
|
+
private readonly log;
|
|
314
|
+
private readonly refreshIntervalMs;
|
|
315
|
+
private readonly maxTrackedTags;
|
|
316
|
+
private readonly resyncIntervalMs;
|
|
317
|
+
private readonly clock;
|
|
318
|
+
private readonly random;
|
|
319
|
+
private readonly debug;
|
|
320
|
+
/** By tag, least recently used first. */
|
|
321
|
+
private readonly tags;
|
|
322
|
+
/**
|
|
323
|
+
* When each tracked tag's marker is due to be read again: a random 75-100%
|
|
324
|
+
* of `resyncIntervalMs` after it was last known from the table - read,
|
|
325
|
+
* written here, or new. In the order they became known, least recent first.
|
|
326
|
+
*/
|
|
327
|
+
private readonly dueAt;
|
|
328
|
+
private readonly reading;
|
|
329
|
+
/**
|
|
330
|
+
* Log rows for tags whose first read is in flight, merged by tag: the read
|
|
331
|
+
* may have been answered before the revalidation, so they are applied to its
|
|
332
|
+
* result rather than dropped.
|
|
333
|
+
*/
|
|
334
|
+
private readonly pendingRows;
|
|
335
|
+
/** Tracked tags whose read failed, for the next refresh to read again. */
|
|
336
|
+
private readonly unread;
|
|
337
|
+
private lastRefresh;
|
|
338
|
+
private refreshing;
|
|
339
|
+
/**
|
|
340
|
+
* Where the next log query starts (`clock()`): the last successful one's
|
|
341
|
+
* start, less {@link REVALIDATION_LOG_LOOKBACK_MS}.
|
|
342
|
+
*/
|
|
343
|
+
private cursor;
|
|
344
|
+
/** When the last successful log query started, or the log was first read. */
|
|
345
|
+
private lastLogRead;
|
|
346
|
+
/** Log rows at or after the cursor already applied, by sort key. */
|
|
347
|
+
private readonly applied;
|
|
348
|
+
/** {@link update}s of the current turn, by their arguments. */
|
|
349
|
+
private readonly updating;
|
|
350
|
+
constructor(options: TrackedTagMarkersOptions);
|
|
351
|
+
/** `tag`'s marker as this instance knows it, if it tracks `tag`. */
|
|
352
|
+
get(tag: string): TagMarker | undefined;
|
|
353
|
+
/**
|
|
354
|
+
* Track `tag` with `marker`: what this instance just wrote, which it knows
|
|
355
|
+
* without reading it back.
|
|
356
|
+
*/
|
|
357
|
+
set(tag: string, marker: TagMarker): void;
|
|
358
|
+
/**
|
|
359
|
+
* Note a use of `tags`: those of an entry this instance just stored.
|
|
360
|
+
*
|
|
361
|
+
* With the table, a tag not tracked yet is left untracked, to be read by
|
|
362
|
+
* {@link ensure} the first time an entry needs it. Nothing revalidated before
|
|
363
|
+
* the new entry was created applies to *it*, but tracking is per tag, not per
|
|
364
|
+
* entry: tracking the tag with an empty marker would also vouch for every
|
|
365
|
+
* older entry that carries it - one another instance stored in S3, or one
|
|
366
|
+
* this instance kept in memory across a {@link forget} - and a revalidation
|
|
367
|
+
* from before the log's cursor would go unseen until the rolling re-read.
|
|
368
|
+
*
|
|
369
|
+
* Without the table every revalidation is this process's own, applied with
|
|
370
|
+
* {@link set}, so a tag is tracked at once.
|
|
371
|
+
*/
|
|
372
|
+
track(tags: readonly string[]): void;
|
|
373
|
+
/**
|
|
374
|
+
* Track `tags` as they are known now, without reading them, with an empty
|
|
375
|
+
* marker for any not tracked yet.
|
|
376
|
+
*/
|
|
377
|
+
private trackUnread;
|
|
378
|
+
/**
|
|
379
|
+
* Read the markers of whichever of `tags` this instance does not track yet.
|
|
380
|
+
* Needed before judging an entry this instance did not create, since its
|
|
381
|
+
* tags may have been revalidated at any time before.
|
|
382
|
+
*/
|
|
383
|
+
ensure(tags: readonly string[]): Promise<void>;
|
|
384
|
+
/**
|
|
385
|
+
* Catch up on revalidations other instances ran, at most once per
|
|
386
|
+
* `refreshIntervalMs`. See {@link TrackedTagMarkers} for the cost.
|
|
387
|
+
*/
|
|
388
|
+
refresh(): Promise<void>;
|
|
389
|
+
/**
|
|
390
|
+
* Record a revalidation of `tags`: here at once, and in the revalidation
|
|
391
|
+
* table for every other instance - the marker row plus a log row per tag.
|
|
392
|
+
*
|
|
393
|
+
* Next.js calls `revalidateTag` on the incremental cache and `updateTags` on
|
|
394
|
+
* every distinct `cacheHandlers` entry, all in one synchronous loop
|
|
395
|
+
* (`revalidation-utils`), and all of them share this manifest. So an
|
|
396
|
+
* identical call in the same turn joins the first instead of writing the
|
|
397
|
+
* rows again. One in a later turn is a revalidation of its own, even while
|
|
398
|
+
* the first one's writes are in flight, and gets its own, later, marker.
|
|
399
|
+
* Never rejects: resolves `false` when a row failed to write (the error is
|
|
400
|
+
* logged), so other instances may not see it.
|
|
401
|
+
*/
|
|
402
|
+
update(tags: readonly string[], durations: RevalidateDurations | undefined): Promise<boolean>;
|
|
403
|
+
/**
|
|
404
|
+
* What the tracked markers say about an entry carrying `tags`, created at
|
|
405
|
+
* `createdAt`: the most severe answer of any of them.
|
|
406
|
+
*/
|
|
407
|
+
state(tags: readonly string[], createdAt: number): RevalidationState;
|
|
408
|
+
/**
|
|
409
|
+
* `getExpiration`'s answer: the latest time any of `tags` expired, `0` if
|
|
410
|
+
* none has. Only expirations already past count - a profile's future
|
|
411
|
+
* `expire` is not an expiration yet - and a stale mark does not, matching
|
|
412
|
+
* Next.js's default handler, which reads only `expired` for implicit tags.
|
|
413
|
+
*/
|
|
414
|
+
expiration(tags: readonly string[]): number;
|
|
415
|
+
private write;
|
|
416
|
+
/**
|
|
417
|
+
* Put `tag`'s log row, stamped on the wall clock (the marker values are on
|
|
418
|
+
* Next.js's: see {@link RevalidationLog}).
|
|
419
|
+
*
|
|
420
|
+
* Readers only look {@link REVALIDATION_LOG_LOOKBACK_MS} behind their last
|
|
421
|
+
* query, so a row that shows up much later than its timestamp - a slow
|
|
422
|
+
* connection, retries under throttling - is behind every cursor and never
|
|
423
|
+
* read. A slow put is therefore made again under a fresh timestamp, which
|
|
424
|
+
* readers will see; one slow twice is reported as a failure.
|
|
425
|
+
*/
|
|
426
|
+
private putLogRow;
|
|
427
|
+
/**
|
|
428
|
+
* One refresh started at `at`: the log rows since the cursor, and the next
|
|
429
|
+
* slice of the rolling re-read.
|
|
430
|
+
*
|
|
431
|
+
* A failed query changes nothing but the log: the tags stay tracked as they
|
|
432
|
+
* were, and the next refresh asks again from the same cursor.
|
|
433
|
+
*/
|
|
434
|
+
private sync;
|
|
435
|
+
/**
|
|
436
|
+
* The tags to read on this refresh, at most one `BatchGetItem`'s worth:
|
|
437
|
+
* those whose read failed, then those whose re-read has come due, in the
|
|
438
|
+
* order they were last read.
|
|
439
|
+
*/
|
|
440
|
+
private due;
|
|
441
|
+
/**
|
|
442
|
+
* Forget every tracked marker, and follow the log from `at`: what is needed
|
|
443
|
+
* next is read from its marker as it is needed, spread over the requests
|
|
444
|
+
* that need it, rather than every tracked tag re-read at once.
|
|
445
|
+
*/
|
|
446
|
+
private forget;
|
|
447
|
+
/**
|
|
448
|
+
* Move the cursor forward to `cursor`, forgetting applied rows before it:
|
|
449
|
+
* no query returns them again.
|
|
450
|
+
*/
|
|
451
|
+
private advance;
|
|
452
|
+
/**
|
|
453
|
+
* Read `tags` from the table, and report whether that worked. A read that
|
|
454
|
+
* fails leaves them tracked as they were, and the next refresh reads them
|
|
455
|
+
* again: the same "assume the entry is valid" the incremental cache has
|
|
456
|
+
* always answered with, rather than a read on every request while DynamoDB
|
|
457
|
+
* is unavailable.
|
|
458
|
+
*/
|
|
459
|
+
private readInto;
|
|
460
|
+
private takePendingRows;
|
|
461
|
+
/** Record that `tag`'s marker was known from the table at `at`. */
|
|
462
|
+
private known;
|
|
463
|
+
private remember;
|
|
464
|
+
private touch;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* A marker as read from the table or the log, keeping whatever this instance
|
|
468
|
+
* already knows that the read does not show yet: reads are eventually
|
|
469
|
+
* consistent, and a read straight after this instance's own revalidation could
|
|
470
|
+
* otherwise take it back.
|
|
471
|
+
*/
|
|
472
|
+
export declare function mergeMarkers(local: TagMarker | undefined, remote: TagMarker | undefined): TagMarker;
|
|
473
|
+
/**
|
|
474
|
+
* The cache bucket, as the handlers use it: whole objects read and written as
|
|
475
|
+
* UTF-8 text under the keys above.
|
|
476
|
+
*/
|
|
477
|
+
export declare class CacheBucket {
|
|
478
|
+
private readonly client;
|
|
479
|
+
private readonly bucketName;
|
|
480
|
+
constructor(client: S3Client, bucketName: string);
|
|
481
|
+
/**
|
|
482
|
+
* The body of the object at `key`, or `undefined` when there is none (`NoSuchKey`, or an
|
|
483
|
+
* empty response body). Any other error is thrown: the handlers disagree on
|
|
484
|
+
* whether one is worth logging.
|
|
485
|
+
*/
|
|
486
|
+
get(key: string): Promise<string | undefined>;
|
|
487
|
+
/** Write `body` as a JSON object at `key`. */
|
|
488
|
+
putJson(key: string, body: string): Promise<void>;
|
|
489
|
+
}
|