@opennextjs/cloudflare 0.5.11 → 0.6.0

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 (110) hide show
  1. package/README.md +8 -8
  2. package/dist/api/cloudflare-context.d.ts +16 -5
  3. package/dist/api/config.d.ts +16 -43
  4. package/dist/api/config.js +21 -19
  5. package/dist/api/durable-objects/queue.d.ts +32 -0
  6. package/dist/api/durable-objects/queue.js +234 -0
  7. package/dist/api/durable-objects/queue.spec.js +290 -0
  8. package/dist/api/durable-objects/sharded-tag-cache.d.ts +7 -0
  9. package/dist/api/durable-objects/sharded-tag-cache.js +22 -0
  10. package/dist/api/durable-objects/sharded-tag-cache.spec.js +37 -0
  11. package/dist/api/overrides/incremental-cache/internal.d.ts +5 -0
  12. package/dist/api/{kv-cache.d.ts → overrides/incremental-cache/kv-incremental-cache.d.ts} +1 -1
  13. package/dist/api/{kv-cache.js → overrides/incremental-cache/kv-incremental-cache.js} +5 -5
  14. package/dist/api/overrides/incremental-cache/r2-incremental-cache.d.ts +17 -0
  15. package/dist/api/overrides/incremental-cache/r2-incremental-cache.js +61 -0
  16. package/dist/api/overrides/incremental-cache/regional-cache.d.ts +51 -0
  17. package/dist/api/overrides/incremental-cache/regional-cache.js +111 -0
  18. package/dist/api/overrides/queue/do-queue.d.ts +6 -0
  19. package/dist/api/overrides/queue/do-queue.js +15 -0
  20. package/dist/api/{memory-queue.d.ts → overrides/queue/memory-queue.d.ts} +3 -3
  21. package/dist/api/{memory-queue.js → overrides/queue/memory-queue.js} +18 -14
  22. package/dist/api/overrides/queue/memory-queue.spec.d.ts +1 -0
  23. package/dist/api/{memory-queue.spec.js → overrides/queue/memory-queue.spec.js} +20 -14
  24. package/dist/api/overrides/tag-cache/d1-next-tag-cache.d.ts +13 -0
  25. package/dist/api/overrides/tag-cache/d1-next-tag-cache.js +61 -0
  26. package/dist/api/{d1-tag-cache.d.ts → overrides/tag-cache/d1-tag-cache.d.ts} +3 -5
  27. package/dist/api/{d1-tag-cache.js → overrides/tag-cache/d1-tag-cache.js} +22 -29
  28. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.d.ts +122 -0
  29. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.js +247 -0
  30. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.spec.d.ts +1 -0
  31. package/dist/api/overrides/tag-cache/do-sharded-tag-cache.spec.js +322 -0
  32. package/dist/cli/args.d.ts +13 -2
  33. package/dist/cli/args.js +44 -29
  34. package/dist/cli/build/build.d.ts +5 -1
  35. package/dist/cli/build/build.js +9 -19
  36. package/dist/cli/build/bundle-server.js +5 -13
  37. package/dist/cli/build/open-next/compile-cache-assets-manifest.d.ts +1 -1
  38. package/dist/cli/build/open-next/compile-cache-assets-manifest.js +4 -6
  39. package/dist/cli/build/open-next/compileDurableObjects.d.ts +2 -0
  40. package/dist/cli/build/open-next/compileDurableObjects.js +30 -0
  41. package/dist/cli/build/open-next/copyCacheAssets.js +1 -1
  42. package/dist/cli/build/open-next/createServerBundle.d.ts +9 -1
  43. package/dist/cli/build/open-next/createServerBundle.js +28 -9
  44. package/dist/cli/build/patches/ast/patch-vercel-og-library.js +1 -1
  45. package/dist/cli/build/patches/ast/vercel-og.d.ts +5 -5
  46. package/dist/cli/build/patches/ast/vercel-og.js +1 -1
  47. package/dist/cli/build/patches/ast/vercel-og.spec.js +1 -1
  48. package/dist/cli/build/patches/ast/webpack-runtime.js +1 -1
  49. package/dist/cli/build/patches/ast/webpack-runtime.spec.js +1 -1
  50. package/dist/cli/build/patches/plugins/build-id.d.ts +2 -2
  51. package/dist/cli/build/patches/plugins/build-id.js +12 -5
  52. package/dist/cli/build/patches/plugins/build-id.spec.js +1 -1
  53. package/dist/cli/build/patches/plugins/dynamic-requires.d.ts +1 -2
  54. package/dist/cli/build/patches/plugins/dynamic-requires.js +21 -11
  55. package/dist/cli/build/patches/plugins/eval-manifest.d.ts +2 -2
  56. package/dist/cli/build/patches/plugins/eval-manifest.js +12 -5
  57. package/dist/cli/build/patches/plugins/find-dir.d.ts +2 -2
  58. package/dist/cli/build/patches/plugins/find-dir.js +10 -5
  59. package/dist/cli/build/patches/plugins/instrumentation.d.ts +2 -5
  60. package/dist/cli/build/patches/plugins/instrumentation.js +19 -3
  61. package/dist/cli/build/patches/plugins/instrumentation.spec.js +1 -1
  62. package/dist/cli/build/patches/plugins/load-manifest.d.ts +2 -2
  63. package/dist/cli/build/patches/plugins/load-manifest.js +12 -5
  64. package/dist/cli/build/patches/plugins/next-minimal.d.ts +4 -7
  65. package/dist/cli/build/patches/plugins/next-minimal.js +31 -15
  66. package/dist/cli/build/patches/plugins/next-minimal.spec.js +1 -1
  67. package/dist/cli/build/patches/plugins/patch-depd-deprecations.d.ts +2 -2
  68. package/dist/cli/build/patches/plugins/patch-depd-deprecations.js +10 -2
  69. package/dist/cli/build/patches/plugins/patch-depd-deprecations.spec.js +1 -1
  70. package/dist/cli/build/patches/plugins/require.d.ts +2 -2
  71. package/dist/cli/build/patches/plugins/require.js +43 -35
  72. package/dist/cli/build/patches/plugins/res-revalidate.d.ts +3 -0
  73. package/dist/cli/build/patches/plugins/res-revalidate.js +77 -0
  74. package/dist/cli/build/patches/plugins/res-revalidate.spec.d.ts +1 -0
  75. package/dist/cli/build/patches/plugins/res-revalidate.spec.js +141 -0
  76. package/dist/cli/build/utils/create-config-files.d.ts +2 -2
  77. package/dist/cli/build/utils/create-config-files.js +3 -3
  78. package/dist/cli/build/utils/ensure-cf-config.js +3 -13
  79. package/dist/cli/commands/deploy.d.ts +5 -0
  80. package/dist/cli/commands/deploy.js +9 -0
  81. package/dist/cli/commands/populate-cache.d.ts +7 -0
  82. package/dist/cli/commands/populate-cache.js +78 -0
  83. package/dist/cli/commands/preview.d.ts +5 -0
  84. package/dist/cli/commands/preview.js +9 -0
  85. package/dist/cli/index.js +36 -9
  86. package/dist/cli/project-options.d.ts +5 -1
  87. package/dist/cli/templates/worker.d.ts +3 -4
  88. package/dist/cli/templates/worker.js +30 -18
  89. package/dist/cli/utils/run-wrangler.d.ts +18 -0
  90. package/dist/cli/utils/run-wrangler.js +41 -0
  91. package/package.json +8 -10
  92. package/templates/open-next.config.ts +1 -1
  93. package/templates/wrangler.jsonc +2 -2
  94. package/dist/api/kvCache.d.ts +0 -5
  95. package/dist/api/kvCache.js +0 -5
  96. package/dist/cli/build/patches/ast/util.d.ts +0 -50
  97. package/dist/cli/build/patches/ast/util.js +0 -65
  98. package/dist/cli/build/patches/ast/util.spec.js +0 -43
  99. package/dist/cli/build/patches/plugins/content-updater.d.ts +0 -44
  100. package/dist/cli/build/patches/plugins/content-updater.js +0 -55
  101. package/dist/cli/build/patches/plugins/fetch-cache-wait-until.d.ts +0 -14
  102. package/dist/cli/build/patches/plugins/fetch-cache-wait-until.js +0 -40
  103. package/dist/cli/build/patches/plugins/fetch-cache-wait-until.spec.js +0 -453
  104. package/dist/cli/templates/shims/node-fs.d.ts +0 -17
  105. package/dist/cli/templates/shims/node-fs.js +0 -51
  106. package/dist/cli/templates/shims/throw.d.ts +0 -0
  107. package/dist/cli/templates/shims/throw.js +0 -2
  108. /package/dist/api/{memory-queue.spec.d.ts → durable-objects/queue.spec.d.ts} +0 -0
  109. /package/dist/{cli/build/patches/ast/util.spec.d.ts → api/durable-objects/sharded-tag-cache.spec.d.ts} +0 -0
  110. /package/dist/{cli/build/patches/plugins/fetch-cache-wait-until.spec.d.ts → api/overrides/incremental-cache/internal.js} +0 -0
@@ -0,0 +1,15 @@
1
+ import { IgnorableError } from "@opennextjs/aws/utils/error.js";
2
+ import { getCloudflareContext } from "../../cloudflare-context";
3
+ export default {
4
+ name: "do-queue",
5
+ send: async (msg) => {
6
+ const durableObject = getCloudflareContext().env.NEXT_CACHE_DO_QUEUE;
7
+ if (!durableObject)
8
+ throw new IgnorableError("No durable object binding for cache revalidation");
9
+ const id = durableObject.idFromName(msg.MessageGroupId);
10
+ const stub = durableObject.get(id);
11
+ await stub.revalidate({
12
+ ...msg,
13
+ });
14
+ },
15
+ };
@@ -5,16 +5,16 @@ export declare const DEFAULT_REVALIDATION_TIMEOUT_MS = 10000;
5
5
  *
6
6
  * It offers basic support for in-memory de-duping per isolate.
7
7
  *
8
- * A service binding called `NEXT_CACHE_REVALIDATION_WORKER` that points to your worker is required.
8
+ * A service binding called `WORKER_SELF_REFERENCE` that points to your worker is required.
9
9
  */
10
10
  export declare class MemoryQueue implements Queue {
11
11
  private opts;
12
12
  readonly name = "memory-queue";
13
- revalidatedPaths: Map<string, NodeJS.Timeout>;
13
+ revalidatedPaths: Set<string>;
14
14
  constructor(opts?: {
15
15
  revalidationTimeoutMs: number;
16
16
  });
17
- send({ MessageBody: { host, url }, MessageGroupId }: QueueMessage): Promise<void>;
17
+ send({ MessageBody: { host, url }, MessageDeduplicationId }: QueueMessage): Promise<void>;
18
18
  }
19
19
  declare const _default: MemoryQueue;
20
20
  export default _default;
@@ -1,49 +1,53 @@
1
- import logger from "@opennextjs/aws/logger.js";
1
+ import { debug, error } from "@opennextjs/aws/adapters/logger.js";
2
2
  import { IgnorableError } from "@opennextjs/aws/utils/error.js";
3
- import { getCloudflareContext } from "./cloudflare-context";
3
+ import { getCloudflareContext } from "../../cloudflare-context";
4
4
  export const DEFAULT_REVALIDATION_TIMEOUT_MS = 10_000;
5
5
  /**
6
6
  * The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route.
7
7
  *
8
8
  * It offers basic support for in-memory de-duping per isolate.
9
9
  *
10
- * A service binding called `NEXT_CACHE_REVALIDATION_WORKER` that points to your worker is required.
10
+ * A service binding called `WORKER_SELF_REFERENCE` that points to your worker is required.
11
11
  */
12
12
  export class MemoryQueue {
13
13
  opts;
14
14
  name = "memory-queue";
15
- revalidatedPaths = new Map();
15
+ revalidatedPaths = new Set();
16
16
  constructor(opts = { revalidationTimeoutMs: DEFAULT_REVALIDATION_TIMEOUT_MS }) {
17
17
  this.opts = opts;
18
18
  }
19
- async send({ MessageBody: { host, url }, MessageGroupId }) {
20
- const service = getCloudflareContext().env.NEXT_CACHE_REVALIDATION_WORKER;
19
+ async send({ MessageBody: { host, url }, MessageDeduplicationId }) {
20
+ const service = getCloudflareContext().env.WORKER_SELF_REFERENCE;
21
21
  if (!service)
22
22
  throw new IgnorableError("No service binding for cache revalidation worker");
23
- if (this.revalidatedPaths.has(MessageGroupId))
23
+ if (this.revalidatedPaths.has(MessageDeduplicationId))
24
24
  return;
25
- this.revalidatedPaths.set(MessageGroupId,
26
- // force remove to allow new revalidations incase something went wrong
27
- setTimeout(() => this.revalidatedPaths.delete(MessageGroupId), this.opts.revalidationTimeoutMs));
25
+ this.revalidatedPaths.add(MessageDeduplicationId);
28
26
  try {
29
27
  const protocol = host.includes("localhost") ? "http" : "https";
30
28
  // TODO: Drop the import - https://github.com/opennextjs/opennextjs-cloudflare/issues/361
31
29
  // @ts-ignore
32
30
  const manifest = await import("./.next/prerender-manifest.json");
33
- await service.fetch(`${protocol}://${host}${url}`, {
31
+ const response = await service.fetch(`${protocol}://${host}${url}`, {
34
32
  method: "HEAD",
35
33
  headers: {
36
34
  "x-prerender-revalidate": manifest.preview.previewModeId,
37
35
  "x-isr": "1",
38
36
  },
37
+ // We want to timeout the revalidation to avoid hanging the queue
38
+ signal: AbortSignal.timeout(this.opts.revalidationTimeoutMs),
39
39
  });
40
+ // Here we want at least to log when the revalidation was not successful
41
+ if (response.status !== 200 || response.headers.get("x-nextjs-cache") !== "REVALIDATED") {
42
+ error(`Revalidation failed for ${url} with status ${response.status}`);
43
+ }
44
+ debug(`Revalidation successful for ${url}`);
40
45
  }
41
46
  catch (e) {
42
- logger.error(e);
47
+ error(e);
43
48
  }
44
49
  finally {
45
- clearTimeout(this.revalidatedPaths.get(MessageGroupId));
46
- this.revalidatedPaths.delete(MessageGroupId);
50
+ this.revalidatedPaths.delete(MessageDeduplicationId);
47
51
  }
48
52
  }
49
53
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -3,11 +3,17 @@ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
3
3
  import cache, { DEFAULT_REVALIDATION_TIMEOUT_MS } from "./memory-queue.js";
4
4
  vi.mock("./.next/prerender-manifest.json", () => Promise.resolve({ preview: { previewModeId: "id" } }));
5
5
  const mockServiceWorkerFetch = vi.fn();
6
- vi.mock("./cloudflare-context", () => ({
6
+ vi.mock("../../cloudflare-context", () => ({
7
7
  getCloudflareContext: () => ({
8
- env: { NEXT_CACHE_REVALIDATION_WORKER: { fetch: mockServiceWorkerFetch } },
8
+ env: { WORKER_SELF_REFERENCE: { fetch: mockServiceWorkerFetch } },
9
9
  }),
10
10
  }));
11
+ const generateMessageBody = ({ host, url }) => ({
12
+ host,
13
+ url,
14
+ eTag: "etag",
15
+ lastModified: Date.now(),
16
+ });
11
17
  describe("MemoryQueue", () => {
12
18
  beforeAll(() => {
13
19
  vi.useFakeTimers();
@@ -16,17 +22,17 @@ describe("MemoryQueue", () => {
16
22
  afterEach(() => vi.clearAllMocks());
17
23
  it("should process revalidations for a path", async () => {
18
24
  const firstRequest = cache.send({
19
- MessageBody: { host: "test.local", url: "/test" },
25
+ MessageBody: generateMessageBody({ host: "test.local", url: "/test" }),
20
26
  MessageGroupId: generateMessageGroupId("/test"),
21
- MessageDeduplicationId: "",
27
+ MessageDeduplicationId: "/test",
22
28
  });
23
29
  vi.advanceTimersByTime(DEFAULT_REVALIDATION_TIMEOUT_MS);
24
30
  await firstRequest;
25
31
  expect(mockServiceWorkerFetch).toHaveBeenCalledTimes(1);
26
32
  const secondRequest = cache.send({
27
- MessageBody: { host: "test.local", url: "/test" },
33
+ MessageBody: generateMessageBody({ host: "test.local", url: "/test" }),
28
34
  MessageGroupId: generateMessageGroupId("/test"),
29
- MessageDeduplicationId: "",
35
+ MessageDeduplicationId: "/test",
30
36
  });
31
37
  vi.advanceTimersByTime(1);
32
38
  await secondRequest;
@@ -34,17 +40,17 @@ describe("MemoryQueue", () => {
34
40
  });
35
41
  it("should process revalidations for multiple paths", async () => {
36
42
  const firstRequest = cache.send({
37
- MessageBody: { host: "test.local", url: "/test" },
43
+ MessageBody: generateMessageBody({ host: "test.local", url: "/test" }),
38
44
  MessageGroupId: generateMessageGroupId("/test"),
39
- MessageDeduplicationId: "",
45
+ MessageDeduplicationId: "/test",
40
46
  });
41
47
  vi.advanceTimersByTime(1);
42
48
  await firstRequest;
43
49
  expect(mockServiceWorkerFetch).toHaveBeenCalledTimes(1);
44
50
  const secondRequest = cache.send({
45
- MessageBody: { host: "test.local", url: "/test" },
51
+ MessageBody: generateMessageBody({ host: "test.local", url: "/test" }),
46
52
  MessageGroupId: generateMessageGroupId("/other"),
47
- MessageDeduplicationId: "",
53
+ MessageDeduplicationId: "/other",
48
54
  });
49
55
  vi.advanceTimersByTime(1);
50
56
  await secondRequest;
@@ -53,14 +59,14 @@ describe("MemoryQueue", () => {
53
59
  it("should de-dupe revalidations", async () => {
54
60
  const requests = [
55
61
  cache.send({
56
- MessageBody: { host: "test.local", url: "/test" },
62
+ MessageBody: generateMessageBody({ host: "test.local", url: "/test" }),
57
63
  MessageGroupId: generateMessageGroupId("/test"),
58
- MessageDeduplicationId: "",
64
+ MessageDeduplicationId: "/test",
59
65
  }),
60
66
  cache.send({
61
- MessageBody: { host: "test.local", url: "/test" },
67
+ MessageBody: generateMessageBody({ host: "test.local", url: "/test" }),
62
68
  MessageGroupId: generateMessageGroupId("/test"),
63
- MessageDeduplicationId: "",
69
+ MessageDeduplicationId: "/test",
64
70
  }),
65
71
  ];
66
72
  vi.advanceTimersByTime(1);
@@ -0,0 +1,13 @@
1
+ import type { NextModeTagCache } from "@opennextjs/aws/types/overrides.js";
2
+ export declare class D1NextModeTagCache implements NextModeTagCache {
3
+ readonly mode: "nextMode";
4
+ readonly name = "d1-next-mode-tag-cache";
5
+ hasBeenRevalidated(tags: string[], lastModified?: number): Promise<boolean>;
6
+ writeTags(tags: string[]): Promise<void>;
7
+ private getConfig;
8
+ protected removeBuildId(key: string): string;
9
+ protected getCacheKey(key: string): string;
10
+ protected getBuildId(): string;
11
+ }
12
+ declare const _default: D1NextModeTagCache;
13
+ export default _default;
@@ -0,0 +1,61 @@
1
+ import { debug, error } from "@opennextjs/aws/adapters/logger.js";
2
+ import { RecoverableError } from "@opennextjs/aws/utils/error.js";
3
+ import { getCloudflareContext } from "../../cloudflare-context.js";
4
+ export class D1NextModeTagCache {
5
+ mode = "nextMode";
6
+ name = "d1-next-mode-tag-cache";
7
+ async hasBeenRevalidated(tags, lastModified) {
8
+ const { isDisabled, db } = this.getConfig();
9
+ if (isDisabled)
10
+ return false;
11
+ try {
12
+ const result = await db
13
+ .prepare(`SELECT COUNT(*) as cnt FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")}) AND revalidatedAt > ? LIMIT 1`)
14
+ .bind(...tags.map((tag) => this.getCacheKey(tag)), lastModified ?? Date.now())
15
+ .first();
16
+ if (!result)
17
+ throw new RecoverableError(`D1 select failed for ${tags}`);
18
+ return result.cnt > 0;
19
+ }
20
+ catch (e) {
21
+ error(e);
22
+ // By default we don't want to crash here, so we return false
23
+ // We still log the error though so we can debug it
24
+ return false;
25
+ }
26
+ }
27
+ async writeTags(tags) {
28
+ const { isDisabled, db } = this.getConfig();
29
+ if (isDisabled)
30
+ return Promise.resolve();
31
+ const result = await db.batch(tags.map((tag) => db
32
+ .prepare(`INSERT INTO revalidations (tag, revalidatedAt) VALUES (?, ?)`)
33
+ .bind(this.getCacheKey(tag), Date.now())));
34
+ if (!result)
35
+ throw new RecoverableError(`D1 insert failed for ${tags}`);
36
+ }
37
+ getConfig() {
38
+ const cfEnv = getCloudflareContext().env;
39
+ const db = cfEnv.NEXT_TAG_CACHE_D1;
40
+ if (!db)
41
+ debug("No D1 database found");
42
+ const isDisabled = !!globalThis.openNextConfig
43
+ .dangerous?.disableTagCache;
44
+ return !db || isDisabled
45
+ ? { isDisabled: true }
46
+ : {
47
+ isDisabled: false,
48
+ db,
49
+ };
50
+ }
51
+ removeBuildId(key) {
52
+ return key.replace(`${this.getBuildId()}/`, "");
53
+ }
54
+ getCacheKey(key) {
55
+ return `${this.getBuildId()}/${key}`.replaceAll("//", "/");
56
+ }
57
+ getBuildId() {
58
+ return process.env.NEXT_BUILD_ID ?? "no-build-id";
59
+ }
60
+ }
61
+ export default new D1NextModeTagCache();
@@ -1,20 +1,18 @@
1
1
  import type { OriginalTagCache } from "@opennextjs/aws/types/overrides.js";
2
2
  /**
3
- * An instance of the Tag Cache that uses a D1 binding (`NEXT_CACHE_D1`) as it's underlying data store.
3
+ * An instance of the Tag Cache that uses a D1 binding (`NEXT_TAG_CACHE_D1`) as it's underlying data store.
4
4
  *
5
5
  * **Tag/path mappings table**
6
6
  *
7
7
  * Information about the relation between tags and paths is stored in a `tags` table that contains
8
- * two columns; `tag`, and `path`. The table name can be configured with `NEXT_CACHE_D1_TAGS_TABLE`
9
- * environment variable.
8
+ * two columns; `tag`, and `path`.
10
9
  *
11
10
  * This table should be populated using an SQL file that is generated during the build process.
12
11
  *
13
12
  * **Tag revalidations table**
14
13
  *
15
14
  * Revalidation times for tags are stored in a `revalidations` table that contains two columns; `tags`,
16
- * and `revalidatedAt`. The table name can be configured with `NEXT_CACHE_D1_REVALIDATIONS_TABLE`
17
- * environment variable.
15
+ * and `revalidatedAt`.
18
16
  */
19
17
  declare class D1TagCache implements OriginalTagCache {
20
18
  readonly name = "d1-tag-cache";
@@ -1,33 +1,31 @@
1
1
  import { debug, error } from "@opennextjs/aws/adapters/logger.js";
2
2
  import { RecoverableError } from "@opennextjs/aws/utils/error.js";
3
- import { getCloudflareContext } from "./cloudflare-context.js";
3
+ import { getCloudflareContext } from "../../cloudflare-context.js";
4
4
  /**
5
- * An instance of the Tag Cache that uses a D1 binding (`NEXT_CACHE_D1`) as it's underlying data store.
5
+ * An instance of the Tag Cache that uses a D1 binding (`NEXT_TAG_CACHE_D1`) as it's underlying data store.
6
6
  *
7
7
  * **Tag/path mappings table**
8
8
  *
9
9
  * Information about the relation between tags and paths is stored in a `tags` table that contains
10
- * two columns; `tag`, and `path`. The table name can be configured with `NEXT_CACHE_D1_TAGS_TABLE`
11
- * environment variable.
10
+ * two columns; `tag`, and `path`.
12
11
  *
13
12
  * This table should be populated using an SQL file that is generated during the build process.
14
13
  *
15
14
  * **Tag revalidations table**
16
15
  *
17
16
  * Revalidation times for tags are stored in a `revalidations` table that contains two columns; `tags`,
18
- * and `revalidatedAt`. The table name can be configured with `NEXT_CACHE_D1_REVALIDATIONS_TABLE`
19
- * environment variable.
17
+ * and `revalidatedAt`.
20
18
  */
21
19
  class D1TagCache {
22
20
  name = "d1-tag-cache";
23
21
  async getByPath(rawPath) {
24
- const { isDisabled, db, tables } = this.getConfig();
22
+ const { isDisabled, db } = this.getConfig();
25
23
  if (isDisabled)
26
24
  return [];
27
25
  const path = this.getCacheKey(rawPath);
28
26
  try {
29
27
  const { success, results } = await db
30
- .prepare(`SELECT tag FROM ${JSON.stringify(tables.tags)} WHERE path = ?`)
28
+ .prepare(`SELECT tag FROM tags WHERE path = ?`)
31
29
  .bind(path)
32
30
  .all();
33
31
  if (!success)
@@ -42,13 +40,13 @@ class D1TagCache {
42
40
  }
43
41
  }
44
42
  async getByTag(rawTag) {
45
- const { isDisabled, db, tables } = this.getConfig();
43
+ const { isDisabled, db } = this.getConfig();
46
44
  if (isDisabled)
47
45
  return [];
48
46
  const tag = this.getCacheKey(rawTag);
49
47
  try {
50
48
  const { success, results } = await db
51
- .prepare(`SELECT path FROM ${JSON.stringify(tables.tags)} WHERE tag = ?`)
49
+ .prepare(`SELECT path FROM tags WHERE tag = ?`)
52
50
  .bind(tag)
53
51
  .all();
54
52
  if (!success)
@@ -63,14 +61,14 @@ class D1TagCache {
63
61
  }
64
62
  }
65
63
  async getLastModified(path, lastModified) {
66
- const { isDisabled, db, tables } = this.getConfig();
64
+ const { isDisabled, db } = this.getConfig();
67
65
  if (isDisabled)
68
66
  return lastModified ?? Date.now();
69
67
  try {
70
68
  const { success, results } = await db
71
- .prepare(`SELECT ${JSON.stringify(tables.revalidations)}.tag FROM ${JSON.stringify(tables.revalidations)}
72
- INNER JOIN ${JSON.stringify(tables.tags)} ON ${JSON.stringify(tables.revalidations)}.tag = ${JSON.stringify(tables.tags)}.tag
73
- WHERE ${JSON.stringify(tables.tags)}.path = ? AND ${JSON.stringify(tables.revalidations)}.revalidatedAt > ?;`)
69
+ .prepare(`SELECT revalidations.tag FROM revalidations
70
+ INNER JOIN tags ON revalidations.tag = tags.tag
71
+ WHERE tags.path = ? AND revalidations.revalidatedAt > ?;`)
74
72
  .bind(this.getCacheKey(path), lastModified ?? 0)
75
73
  .all();
76
74
  if (!success)
@@ -84,7 +82,7 @@ class D1TagCache {
84
82
  }
85
83
  }
86
84
  async writeTags(tags) {
87
- const { isDisabled, db, tables } = this.getConfig();
85
+ const { isDisabled, db } = this.getConfig();
88
86
  if (isDisabled || tags.length === 0)
89
87
  return;
90
88
  try {
@@ -94,14 +92,14 @@ class D1TagCache {
94
92
  if (revalidatedAt === 1) {
95
93
  // new tag/path mapping from set
96
94
  return db
97
- .prepare(`INSERT INTO ${JSON.stringify(tables.tags)} (tag, path) VALUES (?, ?)`)
95
+ .prepare(`INSERT INTO tags (tag, path) VALUES (?, ?)`)
98
96
  .bind(this.getCacheKey(tag), this.getCacheKey(path));
99
97
  }
100
98
  if (!uniqueTags.has(tag) && revalidatedAt !== -1) {
101
99
  // tag was revalidated
102
100
  uniqueTags.add(tag);
103
101
  return db
104
- .prepare(`INSERT INTO ${JSON.stringify(tables.revalidations)} (tag, revalidatedAt) VALUES (?, ?)`)
102
+ .prepare(`INSERT INTO revalidations (tag, revalidatedAt) VALUES (?, ?)`)
105
103
  .bind(this.getCacheKey(tag), revalidatedAt ?? Date.now());
106
104
  }
107
105
  })
@@ -117,22 +115,17 @@ class D1TagCache {
117
115
  }
118
116
  getConfig() {
119
117
  const cfEnv = getCloudflareContext().env;
120
- const db = cfEnv.NEXT_CACHE_D1;
118
+ const db = cfEnv.NEXT_TAG_CACHE_D1;
121
119
  if (!db)
122
120
  debug("No D1 database found");
123
121
  const isDisabled = !!globalThis.openNextConfig
124
122
  .dangerous?.disableTagCache;
125
- if (!db || isDisabled) {
126
- return { isDisabled: true };
127
- }
128
- return {
129
- isDisabled: false,
130
- db,
131
- tables: {
132
- tags: cfEnv.NEXT_CACHE_D1_TAGS_TABLE ?? "tags",
133
- revalidations: cfEnv.NEXT_CACHE_D1_REVALIDATIONS_TABLE ?? "revalidations",
134
- },
135
- };
123
+ return !db || isDisabled
124
+ ? { isDisabled: true }
125
+ : {
126
+ isDisabled: false,
127
+ db,
128
+ };
136
129
  }
137
130
  removeBuildId(key) {
138
131
  return key.replace(`${this.getBuildId()}/`, "");
@@ -0,0 +1,122 @@
1
+ import type { NextModeTagCache } from "@opennextjs/aws/types/overrides.js";
2
+ export declare const DEFAULT_SOFT_REPLICAS = 4;
3
+ export declare const DEFAULT_HARD_REPLICAS = 2;
4
+ export declare const DEFAULT_WRITE_RETRIES = 3;
5
+ export declare const DEFAULT_NUM_SHARDS = 4;
6
+ interface ShardedDOTagCacheOptions {
7
+ /**
8
+ * The number of shards that will be used.
9
+ * 1 shards means 1 durable object instance.
10
+ * Soft (internal next tags used for `revalidatePath`) and hard tags (the one you define in your app) will be split in different shards.
11
+ * The number of requests made to Durable Objects will scale linearly with the number of shards.
12
+ * For example, a request involving 5 tags may access between 1 and 5 shards, with the upper limit being the lesser of the number of tags or the number of shards
13
+ * @default 4
14
+ */
15
+ baseShardSize: number;
16
+ /**
17
+ * Whether to enable a regional cache on a per-shard basis
18
+ * Because of the way tags are implemented in Next.js, some shards will have more requests than others. For these cases, it is recommended to enable the regional cache.
19
+ * @default false
20
+ */
21
+ regionalCache?: boolean;
22
+ /**
23
+ * The TTL for the regional cache in seconds
24
+ * Increasing this value will reduce the number of requests to the Durable Object, but it could make `revalidateTags`/`revalidatePath` call being longer to take effect
25
+ * @default 5
26
+ */
27
+ regionalCacheTtlSec?: number;
28
+ /**
29
+ * Whether to enable shard replication
30
+ * Shard replication will duplicate each shards into N replicas to spread the load even more
31
+ * All replicas of the a shard contain the same value - write are sent to all of the replicas.
32
+ * This allows most frequent read operations to be sent to only one replica to spread the load.
33
+ * For example with N being 2, tag `tag1` could be read from 2 different durable object instance
34
+ * On read you only need to read from one of the shards, but on write you need to write to all shards
35
+ * @default false
36
+ */
37
+ enableShardReplication?: boolean;
38
+ /**
39
+ * The number of replicas that will be used for shard replication
40
+ * Soft shard replicas are more often accessed than hard shard replicas, so it is recommended to have more soft replicas than hard replicas
41
+ * Soft replicas are for internal next tags used for `revalidatePath` (i.e. `_N_T_/layout`, `_N_T_/page1`), hard replicas are the tags defined in your app
42
+ * @default { numberOfSoftReplicas: 4, numberOfHardReplicas: 2 }
43
+ */
44
+ shardReplicationOptions?: {
45
+ numberOfSoftReplicas: number;
46
+ numberOfHardReplicas: number;
47
+ };
48
+ /**
49
+ * The number of retries to perform when writing tags
50
+ * @default 3
51
+ */
52
+ maxWriteRetries?: number;
53
+ }
54
+ interface TagCacheDOIdOptions {
55
+ baseShardId: string;
56
+ numberOfReplicas: number;
57
+ shardType: "soft" | "hard";
58
+ replicaId?: number;
59
+ }
60
+ export declare class TagCacheDOId {
61
+ options: TagCacheDOIdOptions;
62
+ shardId: string;
63
+ replicaId: number;
64
+ constructor(options: TagCacheDOIdOptions);
65
+ private generateRandomNumberBetween;
66
+ get key(): string;
67
+ }
68
+ declare class ShardedDOTagCache implements NextModeTagCache {
69
+ private opts;
70
+ readonly mode: "nextMode";
71
+ readonly name = "do-sharded-tag-cache";
72
+ readonly numSoftReplicas: number;
73
+ readonly numHardReplicas: number;
74
+ readonly maxWriteRetries: number;
75
+ localCache?: Cache;
76
+ constructor(opts?: ShardedDOTagCacheOptions);
77
+ private getDurableObjectStub;
78
+ /**
79
+ * Generates a list of DO ids for the shards and replicas
80
+ * @param tags The tags to generate shards for
81
+ * @param shardType Whether to generate shards for soft or hard tags
82
+ * @param generateAllShards Whether to generate all shards or only one
83
+ * @returns An array of TagCacheDOId and tag
84
+ */
85
+ private generateDOIdArray;
86
+ /**
87
+ * Same tags are guaranteed to be in the same shard
88
+ * @param tags
89
+ * @returns An array of DO ids and tags
90
+ */
91
+ groupTagsByDO({ tags, generateAllReplicas }: {
92
+ tags: string[];
93
+ generateAllReplicas?: boolean;
94
+ }): {
95
+ doId: TagCacheDOId;
96
+ tags: string[];
97
+ }[];
98
+ private getConfig;
99
+ /**
100
+ * This function checks if the tags have been revalidated
101
+ * It is never supposed to throw and in case of error, it will return false
102
+ * @param tags
103
+ * @param lastModified default to `Date.now()`
104
+ * @returns
105
+ */
106
+ hasBeenRevalidated(tags: string[], lastModified?: number): Promise<boolean>;
107
+ /**
108
+ * This function writes the tags to the cache
109
+ * Due to the way shards and regional cache are implemented, the regional cache may not be properly invalidated
110
+ * @param tags
111
+ * @returns
112
+ */
113
+ writeTags(tags: string[]): Promise<void>;
114
+ performWriteTagsWithRetry(doId: TagCacheDOId, tags: string[], lastModified: number, retryNumber?: number): Promise<void>;
115
+ getCacheInstance(): Promise<Cache | undefined>;
116
+ getCacheKey(doId: TagCacheDOId, tags: string[]): Promise<Request<unknown, CfProperties<unknown>>>;
117
+ getFromRegionalCache(doId: TagCacheDOId, tags: string[]): Promise<Response | undefined>;
118
+ putToRegionalCache(doId: TagCacheDOId, tags: string[], hasBeenRevalidated: boolean): Promise<void>;
119
+ deleteRegionalCache(doId: TagCacheDOId, tags: string[]): Promise<void>;
120
+ }
121
+ declare const _default: (opts?: ShardedDOTagCacheOptions) => ShardedDOTagCache;
122
+ export default _default;