diff --git a/.changeset/cyan-donuts-perform.md b/.changeset/cyan-donuts-perform.md new file mode 100644 index 00000000..abe4b863 --- /dev/null +++ b/.changeset/cyan-donuts-perform.md @@ -0,0 +1,31 @@ +--- +"@opennextjs/cloudflare": minor +--- + +Refactor the codebase for consistency + +BREAKING CHANGE + +Overrides: + +Overrides now live in `@opennextjs/cloudflare/overrides` and some files have been renamed. + +- Incremental cache overrides: `@opennextjs/cloudflare/overrides/incremental-cache/...` +- Tag cache overrides: `@opennextjs/cloudflare/overrides/tag-cache/...` +- Queue overrides: `@opennextjs/cloudflare/overrides/queue/...` + +For example the KV incremental cache override can be imported as `@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache`. + +Environment variables and bindings name changes: + +- `NEXT_CACHE_WORKERS_KV` -> `NEXT_INC_CACHE_KV` +- `NEXT_CACHE_R2_...` -> `NEXT_INC_CACHE_R2_...` +- `NEXT_CACHE_D1` -> `NEXT_TAG_CACHE_D1` +- `NEXT_CACHE_DO_...` -> `NEXT_TAG_CACHE_DO_...` +- `NEXT_CACHE_DO_REVALIDATION` -> `NEXT_CACHE_DO_QUEUE` +- `NEXT_CACHE_REVALIDATION_WORKER` -> `WORKER_SELF_REFERENCE` + +Other: + +`NEXT_CACHE_D1_TAGS_TABLE` and `NEXT_CACHE_D1_REVALIDATIONS_TABLE` have been dropped. +The tables have a fixed names `tags` and `revalidations`. diff --git a/examples/e2e/app-pages-router/open-next.config.ts b/examples/e2e/app-pages-router/open-next.config.ts index 992d7fc8..4156694c 100644 --- a/examples/e2e/app-pages-router/open-next.config.ts +++ b/examples/e2e/app-pages-router/open-next.config.ts @@ -1,6 +1,6 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; -import memoryQueue from "@opennextjs/cloudflare/memory-queue"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; +import memoryQueue from "@opennextjs/cloudflare/overrides/queue/memory-queue"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/examples/e2e/app-pages-router/wrangler.jsonc b/examples/e2e/app-pages-router/wrangler.jsonc index ea0c7e69..ecc0ab45 100644 --- a/examples/e2e/app-pages-router/wrangler.jsonc +++ b/examples/e2e/app-pages-router/wrangler.jsonc @@ -10,13 +10,13 @@ }, "kv_namespaces": [ { - "binding": "NEXT_CACHE_WORKERS_KV", + "binding": "NEXT_INC_CACHE_KV", "id": "" } ], "services": [ { - "binding": "NEXT_CACHE_REVALIDATION_WORKER", + "binding": "WORKER_SELF_REFERENCE", "service": "app-pages-router" } ] diff --git a/examples/e2e/app-router/open-next.config.ts b/examples/e2e/app-router/open-next.config.ts index 01e59f15..6322ba96 100644 --- a/examples/e2e/app-router/open-next.config.ts +++ b/examples/e2e/app-router/open-next.config.ts @@ -1,13 +1,13 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; -import shardedTagCache from "@opennextjs/cloudflare/do-sharded-tag-cache"; -import doQueue from "@opennextjs/cloudflare/durable-queue"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; +import shardedTagCache from "@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache"; +import doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, // With such a configuration, we could have up to 12 * (8 + 2) = 120 Durable Objects instances tagCache: shardedTagCache({ - numberOfShards: 12, + baseShardSize: 12, enableShardReplication: true, shardReplicationOptions: { numberOfSoftReplicas: 8, diff --git a/examples/e2e/app-router/wrangler.jsonc b/examples/e2e/app-router/wrangler.jsonc index 18c8e4ad..c53a41ca 100644 --- a/examples/e2e/app-router/wrangler.jsonc +++ b/examples/e2e/app-router/wrangler.jsonc @@ -11,11 +11,11 @@ "durable_objects": { "bindings": [ { - "name": "NEXT_CACHE_REVALIDATION_DURABLE_OBJECT", + "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DurableObjectQueueHandler" }, { - "name": "NEXT_CACHE_DO_SHARDED", + "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache" } ] @@ -28,13 +28,13 @@ ], "kv_namespaces": [ { - "binding": "NEXT_CACHE_WORKERS_KV", + "binding": "NEXT_INC_CACHE_KV", "id": "" } ], "services": [ { - "binding": "NEXT_CACHE_REVALIDATION_WORKER", + "binding": "WORKER_SELF_REFERENCE", "service": "app-router" } ] diff --git a/examples/e2e/pages-router/open-next.config.ts b/examples/e2e/pages-router/open-next.config.ts index 992d7fc8..4156694c 100644 --- a/examples/e2e/pages-router/open-next.config.ts +++ b/examples/e2e/pages-router/open-next.config.ts @@ -1,6 +1,6 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; -import memoryQueue from "@opennextjs/cloudflare/memory-queue"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; +import memoryQueue from "@opennextjs/cloudflare/overrides/queue/memory-queue"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/examples/e2e/pages-router/wrangler.jsonc b/examples/e2e/pages-router/wrangler.jsonc index 15316fc4..45b6369c 100644 --- a/examples/e2e/pages-router/wrangler.jsonc +++ b/examples/e2e/pages-router/wrangler.jsonc @@ -10,13 +10,13 @@ }, "kv_namespaces": [ { - "binding": "NEXT_CACHE_WORKERS_KV", + "binding": "NEXT_INC_CACHE_KV", "id": "" } ], "services": [ { - "binding": "NEXT_CACHE_REVALIDATION_WORKER", + "binding": "WORKER_SELF_REFERENCE", "service": "pages-router" } ] diff --git a/examples/overrides/d1-tag-next/open-next.config.ts b/examples/overrides/d1-tag-next/open-next.config.ts index b41b414b..cd3d53eb 100644 --- a/examples/overrides/d1-tag-next/open-next.config.ts +++ b/examples/overrides/d1-tag-next/open-next.config.ts @@ -1,6 +1,6 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; -import d1NextTagCache from "@opennextjs/cloudflare/d1-next-tag-cache"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; +import d1NextTagCache from "@opennextjs/cloudflare/overrides/tag-cache/d1-next-tag-cache"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/examples/overrides/d1-tag-next/wrangler.jsonc b/examples/overrides/d1-tag-next/wrangler.jsonc index f2122ede..2425e6e4 100644 --- a/examples/overrides/d1-tag-next/wrangler.jsonc +++ b/examples/overrides/d1-tag-next/wrangler.jsonc @@ -13,15 +13,15 @@ }, "kv_namespaces": [ { - "binding": "NEXT_CACHE_WORKERS_KV", + "binding": "NEXT_INC_CACHE_KV", "id": "" } ], "d1_databases": [ { - "binding": "NEXT_CACHE_D1", - "database_id": "NEXT_CACHE_D1", - "database_name": "NEXT_CACHE_D1" + "binding": "NEXT_TAG_CACHE_D1", + "database_id": "NEXT_TAG_CACHE_D1", + "database_name": "NEXT_TAG_CACHE_D1" } ] } diff --git a/examples/overrides/memory-queue/open-next.config.ts b/examples/overrides/memory-queue/open-next.config.ts index 992d7fc8..4156694c 100644 --- a/examples/overrides/memory-queue/open-next.config.ts +++ b/examples/overrides/memory-queue/open-next.config.ts @@ -1,6 +1,6 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; -import memoryQueue from "@opennextjs/cloudflare/memory-queue"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; +import memoryQueue from "@opennextjs/cloudflare/overrides/queue/memory-queue"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/examples/overrides/memory-queue/wrangler.jsonc b/examples/overrides/memory-queue/wrangler.jsonc index 2970e70a..84268786 100644 --- a/examples/overrides/memory-queue/wrangler.jsonc +++ b/examples/overrides/memory-queue/wrangler.jsonc @@ -10,13 +10,13 @@ }, "kv_namespaces": [ { - "binding": "NEXT_CACHE_WORKERS_KV", + "binding": "NEXT_INC_CACHE_KV", "id": "" } ], "services": [ { - "binding": "NEXT_CACHE_REVALIDATION_WORKER", + "binding": "WORKER_SELF_REFERENCE", "service": "memory-queue" } ] diff --git a/examples/overrides/r2-incremental-cache/open-next.config.ts b/examples/overrides/r2-incremental-cache/open-next.config.ts index 2d73a2dd..7e56cad4 100644 --- a/examples/overrides/r2-incremental-cache/open-next.config.ts +++ b/examples/overrides/r2-incremental-cache/open-next.config.ts @@ -1,8 +1,8 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import d1TagCache from "@opennextjs/cloudflare/d1-tag-cache"; -import memoryQueue from "@opennextjs/cloudflare/memory-queue"; -import r2IncrementalCache from "@opennextjs/cloudflare/r2-incremental-cache"; -import { withRegionalCache } from "@opennextjs/cloudflare/regional-cache"; +import d1TagCache from "@opennextjs/cloudflare/overrides/tag-cache/d1-tag-cache"; +import memoryQueue from "@opennextjs/cloudflare/overrides/queue/memory-queue"; +import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache"; +import { withRegionalCache } from "@opennextjs/cloudflare/overrides/incremental-cache/regional-cache"; export default defineCloudflareConfig({ incrementalCache: withRegionalCache(r2IncrementalCache, { diff --git a/examples/overrides/r2-incremental-cache/wrangler.jsonc b/examples/overrides/r2-incremental-cache/wrangler.jsonc index 7a9c4216..2d666fda 100644 --- a/examples/overrides/r2-incremental-cache/wrangler.jsonc +++ b/examples/overrides/r2-incremental-cache/wrangler.jsonc @@ -12,22 +12,22 @@ "e2e": { "d1_databases": [ { - "binding": "NEXT_CACHE_D1", - "database_id": "NEXT_CACHE_D1", - "database_name": "NEXT_CACHE_D1" + "binding": "NEXT_TAG_CACHE_D1", + "database_id": "NEXT_TAG_CACHE_D1", + "database_name": "NEXT_TAG_CACHE_D1" } ], "services": [ { - "binding": "NEXT_CACHE_REVALIDATION_WORKER", + "binding": "WORKER_SELF_REFERENCE", "service": "r2-incremental-cache-e2e" } ], "r2_buckets": [ { - "binding": "NEXT_CACHE_R2_BUCKET", - "bucket_name": "NEXT_CACHE_R2_BUCKET", - "preview_bucket_name": "NEXT_CACHE_R2_BUCKET" + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "NEXT_INC_CACHE_R2_BUCKET", + "preview_bucket_name": "NEXT_INC_CACHE_R2_BUCKET" } ] }, diff --git a/examples/playground14/open-next.config.ts b/examples/playground14/open-next.config.ts index 1b5ed9ce..0f15d530 100644 --- a/examples/playground14/open-next.config.ts +++ b/examples/playground14/open-next.config.ts @@ -1,5 +1,5 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/examples/playground14/wrangler.jsonc b/examples/playground14/wrangler.jsonc index 53a0fbe6..b1339759 100644 --- a/examples/playground14/wrangler.jsonc +++ b/examples/playground14/wrangler.jsonc @@ -10,7 +10,7 @@ }, "kv_namespaces": [ { - "binding": "NEXT_CACHE_WORKERS_KV", + "binding": "NEXT_INC_CACHE_KV", "id": "" } ], diff --git a/examples/playground15/open-next.config.ts b/examples/playground15/open-next.config.ts index 1b5ed9ce..0f15d530 100644 --- a/examples/playground15/open-next.config.ts +++ b/examples/playground15/open-next.config.ts @@ -1,5 +1,5 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/examples/playground15/wrangler.jsonc b/examples/playground15/wrangler.jsonc index 53a0fbe6..b1339759 100644 --- a/examples/playground15/wrangler.jsonc +++ b/examples/playground15/wrangler.jsonc @@ -10,7 +10,7 @@ }, "kv_namespaces": [ { - "binding": "NEXT_CACHE_WORKERS_KV", + "binding": "NEXT_INC_CACHE_KV", "id": "" } ], diff --git a/examples/ssg-app/open-next.config.ts b/examples/ssg-app/open-next.config.ts index 1b5ed9ce..0f15d530 100644 --- a/examples/ssg-app/open-next.config.ts +++ b/examples/ssg-app/open-next.config.ts @@ -1,5 +1,5 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/examples/vercel-blog-starter/open-next.config.ts b/examples/vercel-blog-starter/open-next.config.ts index 1b5ed9ce..0f15d530 100644 --- a/examples/vercel-blog-starter/open-next.config.ts +++ b/examples/vercel-blog-starter/open-next.config.ts @@ -1,5 +1,5 @@ import { defineCloudflareConfig } from "@opennextjs/cloudflare"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/packages/cloudflare/env.d.ts b/packages/cloudflare/env.d.ts index bd422364..d5950dd1 100644 --- a/packages/cloudflare/env.d.ts +++ b/packages/cloudflare/env.d.ts @@ -7,8 +7,6 @@ declare global { NEXT_PRIVATE_DEBUG_CACHE?: string; OPEN_NEXT_ORIGIN: string; NODE_ENV?: string; - NEXT_CACHE_D1_TAGS_TABLE?: string; - NEXT_CACHE_D1_REVALIDATIONS_TABLE?: string; } } } diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index f2ebcf4a..279b74f5 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -12,42 +12,42 @@ declare global { // Default to "production" NEXTJS_ENV?: string; - // KV used for the incremental cache - NEXT_CACHE_WORKERS_KV?: KVNamespace; - // D1 db used for the tag cache - NEXT_CACHE_D1?: D1Database; - // D1 table to use for the tag cache for the tag/path mapping - NEXT_CACHE_D1_TAGS_TABLE?: string; - // D1 table to use for the tag cache for storing the tag and their associated revalidation times - NEXT_CACHE_D1_REVALIDATIONS_TABLE?: string; // Service binding for the worker itself to be able to call itself from within the worker - NEXT_CACHE_REVALIDATION_WORKER?: Service; + WORKER_SELF_REFERENCE?: Service; + + // KV used for the incremental cache + NEXT_INC_CACHE_KV?: KVNamespace; + // R2 bucket used for the incremental cache - NEXT_CACHE_R2_BUCKET?: R2Bucket; + NEXT_INC_CACHE_R2_BUCKET?: R2Bucket; // Prefix used for the R2 incremental cache bucket - NEXT_CACHE_R2_PREFIX?: string; - // Durable Object namespace to use for the durable object queue handler - NEXT_CACHE_REVALIDATION_DURABLE_OBJECT?: DurableObjectNamespace; + NEXT_INC_CACHE_R2_PREFIX?: string; + + // D1 db used for the tag cache + NEXT_TAG_CACHE_D1?: D1Database; + // Durables object namespace to use for the sharded tag cache - NEXT_CACHE_DO_SHARDED?: DurableObjectNamespace; + NEXT_TAG_CACHE_DO_SHARDED?: DurableObjectNamespace; // Queue of failed tag write - // It could be used for monitoring or to reprocess failed writes - // Entirely optional - NEXT_CACHE_DO_SHARDED_DLQ?: Queue; + // Optional, could be used to monitor or reprocess failed writes + NEXT_TAG_CACHE_DO_SHARDED_DLQ?: Queue; + + // Durable Object namespace to use for the durable object queue + NEXT_CACHE_DO_QUEUE?: DurableObjectNamespace; - // Below are the potential environment variables that can be set by the user to configure the durable object queue handler + // Below are the optional environment variables to configure the durable object queue // The max number of revalidations that can be processed by the durable worker at the same time - MAX_REVALIDATION_BY_DURABLE_OBJECT?: string; + NEXT_CACHE_DO_QUEUE_MAX_REVALIDATION?: string; // The max time in milliseconds that a revalidation can take before being considered as failed - REVALIDATION_TIMEOUT_MS?: string; + NEXT_CACHE_DO_QUEUE_REVALIDATION_TIMEOUT_MS?: string; // The amount of time after which a revalidation will be attempted again if it failed // If it fails again it will exponentially back off until it reaches the max retry interval - REVALIDATION_RETRY_INTERVAL_MS?: string; + NEXT_CACHE_DO_QUEUE_RETRY_INTERVAL_MS?: string; // The maximum number of attempts that can be made to revalidate a path - MAX_REVALIDATION_ATTEMPTS?: string; + NEXT_CACHE_DO_QUEUE_MAX_NUM_REVALIDATIONS?: string; // Disable SQLite for the durable object queue handler // This can be safely used if you don't use an eventually consistent incremental cache (i.e. R2 without the regional cache for example) - REVALIDATION_DO_DISABLE_SQLITE?: string; + NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE?: string; } } diff --git a/packages/cloudflare/src/api/constants.ts b/packages/cloudflare/src/api/constants.ts deleted file mode 100644 index 2fbe4055..00000000 --- a/packages/cloudflare/src/api/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const DEFAULT_NEXT_CACHE_D1_REVALIDATIONS_TABLE = "revalidations"; diff --git a/packages/cloudflare/src/api/durable-objects/queue.spec.ts b/packages/cloudflare/src/api/durable-objects/queue.spec.ts index fbd9a9b7..fefa3683 100644 --- a/packages/cloudflare/src/api/durable-objects/queue.spec.ts +++ b/packages/cloudflare/src/api/durable-objects/queue.spec.ts @@ -37,7 +37,7 @@ const createDurableObjectQueue = ({ }; // eslint-disable-next-line @typescript-eslint/no-explicit-any return new DurableObjectQueueHandler(mockState as any, { - NEXT_CACHE_REVALIDATION_WORKER: { + WORKER_SELF_REFERENCE: { fetch: vi.fn().mockReturnValue( new Promise((res) => setTimeout( @@ -54,7 +54,7 @@ const createDurableObjectQueue = ({ ), connect: vi.fn(), }, - REVALIDATION_DO_DISABLE_SQLITE: disableSQLite ? "true" : undefined, + NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE: disableSQLite ? "true" : undefined, }); }; diff --git a/packages/cloudflare/src/api/durable-objects/queue.ts b/packages/cloudflare/src/api/durable-objects/queue.ts index 5b68cc72..edb76cb5 100644 --- a/packages/cloudflare/src/api/durable-objects/queue.ts +++ b/packages/cloudflare/src/api/durable-objects/queue.ts @@ -8,10 +8,10 @@ import { } from "@opennextjs/aws/utils/error.js"; import { DurableObject } from "cloudflare:workers"; -const DEFAULT_MAX_REVALIDATION_BY_DURABLE_OBJECT = 5; +const DEFAULT_MAX_REVALIDATION = 5; const DEFAULT_REVALIDATION_TIMEOUT_MS = 10_000; -const DEFAULT_REVALIDATION_RETRY_INTERVAL_MS = 2_000; -const DEFAULT_MAX_REVALIDATION_ATTEMPTS = 6; +const DEFAULT_RETRY_INTERVAL_MS = 2_000; +const DEFAULT_MAX_NUM_REVALIDATIONS = 6; interface FailedState { msg: QueueMessage; @@ -29,7 +29,7 @@ export class DurableObjectQueueHandler extends DurableObject { routeInFailedState = new Map(); - service: NonNullable; + service: NonNullable; // Configurable params readonly maxRevalidations: number; @@ -40,28 +40,28 @@ export class DurableObjectQueueHandler extends DurableObject { constructor(ctx: DurableObjectState, env: CloudflareEnv) { super(ctx, env); - this.service = env.NEXT_CACHE_REVALIDATION_WORKER!; + this.service = env.WORKER_SELF_REFERENCE!; // If there is no service binding, we throw an error because we can't revalidate without it if (!this.service) throw new IgnorableError("No service binding for cache revalidation worker"); this.sql = ctx.storage.sql; - this.maxRevalidations = env.MAX_REVALIDATION_BY_DURABLE_OBJECT - ? parseInt(env.MAX_REVALIDATION_BY_DURABLE_OBJECT) - : DEFAULT_MAX_REVALIDATION_BY_DURABLE_OBJECT; + this.maxRevalidations = env.NEXT_CACHE_DO_QUEUE_MAX_REVALIDATION + ? parseInt(env.NEXT_CACHE_DO_QUEUE_MAX_REVALIDATION) + : DEFAULT_MAX_REVALIDATION; - this.revalidationTimeout = env.REVALIDATION_TIMEOUT_MS - ? parseInt(env.REVALIDATION_TIMEOUT_MS) + this.revalidationTimeout = env.NEXT_CACHE_DO_QUEUE_REVALIDATION_TIMEOUT_MS + ? parseInt(env.NEXT_CACHE_DO_QUEUE_REVALIDATION_TIMEOUT_MS) : DEFAULT_REVALIDATION_TIMEOUT_MS; - this.revalidationRetryInterval = env.REVALIDATION_RETRY_INTERVAL_MS - ? parseInt(env.REVALIDATION_RETRY_INTERVAL_MS) - : DEFAULT_REVALIDATION_RETRY_INTERVAL_MS; + this.revalidationRetryInterval = env.NEXT_CACHE_DO_QUEUE_RETRY_INTERVAL_MS + ? parseInt(env.NEXT_CACHE_DO_QUEUE_RETRY_INTERVAL_MS) + : DEFAULT_RETRY_INTERVAL_MS; - this.maxRevalidationAttempts = env.MAX_REVALIDATION_ATTEMPTS - ? parseInt(env.MAX_REVALIDATION_ATTEMPTS) - : DEFAULT_MAX_REVALIDATION_ATTEMPTS; + this.maxRevalidationAttempts = env.NEXT_CACHE_DO_QUEUE_MAX_NUM_REVALIDATIONS + ? parseInt(env.NEXT_CACHE_DO_QUEUE_MAX_NUM_REVALIDATIONS) + : DEFAULT_MAX_NUM_REVALIDATIONS; - this.disableSQLite = env.REVALIDATION_DO_DISABLE_SQLITE === "true"; + this.disableSQLite = env.NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE === "true"; // We restore the state ctx.blockConcurrencyWhile(async () => { diff --git a/packages/cloudflare/src/api/internal/incremental-cache.ts b/packages/cloudflare/src/api/overrides/incremental-cache/internal.ts similarity index 100% rename from packages/cloudflare/src/api/internal/incremental-cache.ts rename to packages/cloudflare/src/api/overrides/incremental-cache/internal.ts diff --git a/packages/cloudflare/src/api/kv-cache.ts b/packages/cloudflare/src/api/overrides/incremental-cache/kv-incremental-cache.ts similarity index 94% rename from packages/cloudflare/src/api/kv-cache.ts rename to packages/cloudflare/src/api/overrides/incremental-cache/kv-incremental-cache.ts index 4064fc4e..da9ec7d2 100644 --- a/packages/cloudflare/src/api/kv-cache.ts +++ b/packages/cloudflare/src/api/overrides/incremental-cache/kv-incremental-cache.ts @@ -1,7 +1,7 @@ import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides"; import { IgnorableError, RecoverableError } from "@opennextjs/aws/utils/error.js"; -import { getCloudflareContext } from "./cloudflare-context.js"; +import { getCloudflareContext } from "../../cloudflare-context.js"; export const CACHE_ASSET_DIR = "cdn-cgi/_next_cache"; @@ -11,7 +11,7 @@ export const STATUS_DELETED = 1; * Open Next cache based on cloudflare KV and Assets. * * Note: The class is instantiated outside of the request context. - * The cloudflare context and process.env are not initialzed yet + * The cloudflare context and process.env are not initialized yet * when the constructor is called. */ class Cache implements IncrementalCache { @@ -22,7 +22,7 @@ class Cache implements IncrementalCache { isFetch?: IsFetch ): Promise> | null> { const cfEnv = getCloudflareContext().env; - const kv = cfEnv.NEXT_CACHE_WORKERS_KV; + const kv = cfEnv.NEXT_INC_CACHE_KV; const assets = cfEnv.ASSETS; if (!(kv || assets)) { @@ -92,7 +92,7 @@ class Cache implements IncrementalCache { value: CacheValue, isFetch?: IsFetch ): Promise { - const kv = getCloudflareContext().env.NEXT_CACHE_WORKERS_KV; + const kv = getCloudflareContext().env.NEXT_INC_CACHE_KV; if (!kv) { throw new IgnorableError(`No KVNamespace`); @@ -120,7 +120,7 @@ class Cache implements IncrementalCache { } async delete(key: string): Promise { - const kv = getCloudflareContext().env.NEXT_CACHE_WORKERS_KV; + const kv = getCloudflareContext().env.NEXT_INC_CACHE_KV; if (!kv) { throw new IgnorableError(`No KVNamespace`); diff --git a/packages/cloudflare/src/api/r2-incremental-cache.ts b/packages/cloudflare/src/api/overrides/incremental-cache/r2-incremental-cache.ts similarity index 75% rename from packages/cloudflare/src/api/r2-incremental-cache.ts rename to packages/cloudflare/src/api/overrides/incremental-cache/r2-incremental-cache.ts index de216d70..bd1b0fb4 100644 --- a/packages/cloudflare/src/api/r2-incremental-cache.ts +++ b/packages/cloudflare/src/api/overrides/incremental-cache/r2-incremental-cache.ts @@ -2,17 +2,14 @@ import { debug, error } from "@opennextjs/aws/adapters/logger.js"; import type { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js"; import { IgnorableError } from "@opennextjs/aws/utils/error.js"; -import { getCloudflareContext } from "./cloudflare-context.js"; +import { getCloudflareContext } from "../../cloudflare-context.js"; /** - * An instance of the Incremental Cache that uses an R2 bucket (`NEXT_CACHE_R2_BUCKET`) as it's + * An instance of the Incremental Cache that uses an R2 bucket (`NEXT_INC_CACHE_R2_BUCKET`) as it's * underlying data store. * - * The directory that the cache entries are stored in can be configured with the `NEXT_CACHE_R2_PREFIX` + * The directory that the cache entries are stored in can be configured with the `NEXT_INC_CACHE_R2_PREFIX` * environment variable, and defaults to `incremental-cache`. - * - * The cache uses an instance of the Cache API (`incremental-cache`) to store a local version of the - * R2 cache entry to enable fast retrieval, with the cache being updated from R2 in the background. */ class R2IncrementalCache implements IncrementalCache { readonly name = "r2-incremental-cache"; @@ -21,7 +18,7 @@ class R2IncrementalCache implements IncrementalCache { key: string, isFetch?: IsFetch ): Promise> | null> { - const r2 = getCloudflareContext().env.NEXT_CACHE_R2_BUCKET; + const r2 = getCloudflareContext().env.NEXT_INC_CACHE_R2_BUCKET; if (!r2) throw new IgnorableError("No R2 bucket"); debug(`Get ${key}`); @@ -45,7 +42,7 @@ class R2IncrementalCache implements IncrementalCache { value: CacheValue, isFetch?: IsFetch ): Promise { - const r2 = getCloudflareContext().env.NEXT_CACHE_R2_BUCKET; + const r2 = getCloudflareContext().env.NEXT_INC_CACHE_R2_BUCKET; if (!r2) throw new IgnorableError("No R2 bucket"); debug(`Set ${key}`); @@ -58,7 +55,7 @@ class R2IncrementalCache implements IncrementalCache { } async delete(key: string): Promise { - const r2 = getCloudflareContext().env.NEXT_CACHE_R2_BUCKET; + const r2 = getCloudflareContext().env.NEXT_INC_CACHE_R2_BUCKET; if (!r2) throw new IgnorableError("No R2 bucket"); debug(`Delete ${key}`); @@ -71,7 +68,7 @@ class R2IncrementalCache implements IncrementalCache { } protected getR2Key(key: string, isFetch?: boolean): string { - const directory = getCloudflareContext().env.NEXT_CACHE_R2_PREFIX ?? "incremental-cache"; + const directory = getCloudflareContext().env.NEXT_INC_CACHE_R2_PREFIX ?? "incremental-cache"; return `${directory}/${process.env.NEXT_BUILD_ID ?? "no-build-id"}/${key}.${isFetch ? "fetch" : "cache"}`; } diff --git a/packages/cloudflare/src/api/regional-cache.ts b/packages/cloudflare/src/api/overrides/incremental-cache/regional-cache.ts similarity index 97% rename from packages/cloudflare/src/api/regional-cache.ts rename to packages/cloudflare/src/api/overrides/incremental-cache/regional-cache.ts index f526aa4e..413d9537 100644 --- a/packages/cloudflare/src/api/regional-cache.ts +++ b/packages/cloudflare/src/api/overrides/incremental-cache/regional-cache.ts @@ -1,8 +1,8 @@ import { debug, error } from "@opennextjs/aws/adapters/logger.js"; import { CacheValue, IncrementalCache, WithLastModified } from "@opennextjs/aws/types/overrides.js"; -import { getCloudflareContext } from "./cloudflare-context.js"; -import { IncrementalCacheEntry } from "./internal/incremental-cache.js"; +import { getCloudflareContext } from "../../cloudflare-context.js"; +import { IncrementalCacheEntry } from "./internal.js"; const ONE_MINUTE_IN_SECONDS = 60; const THIRTY_MINUTES_IN_SECONDS = ONE_MINUTE_IN_SECONDS * 30; diff --git a/packages/cloudflare/src/api/durable-queue.ts b/packages/cloudflare/src/api/overrides/queue/do-queue.ts similarity index 82% rename from packages/cloudflare/src/api/durable-queue.ts rename to packages/cloudflare/src/api/overrides/queue/do-queue.ts index fd3fa55f..6f5adf75 100644 --- a/packages/cloudflare/src/api/durable-queue.ts +++ b/packages/cloudflare/src/api/overrides/queue/do-queue.ts @@ -1,12 +1,12 @@ import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides"; import { IgnorableError } from "@opennextjs/aws/utils/error.js"; -import { getCloudflareContext } from "./cloudflare-context"; +import { getCloudflareContext } from "../../cloudflare-context"; export default { - name: "durable-queue", + name: "do-queue", send: async (msg: QueueMessage) => { - const durableObject = getCloudflareContext().env.NEXT_CACHE_REVALIDATION_DURABLE_OBJECT; + const durableObject = getCloudflareContext().env.NEXT_CACHE_DO_QUEUE; if (!durableObject) throw new IgnorableError("No durable object binding for cache revalidation"); const id = durableObject.idFromName(msg.MessageGroupId); diff --git a/packages/cloudflare/src/api/memory-queue.spec.ts b/packages/cloudflare/src/api/overrides/queue/memory-queue.spec.ts similarity index 96% rename from packages/cloudflare/src/api/memory-queue.spec.ts rename to packages/cloudflare/src/api/overrides/queue/memory-queue.spec.ts index 4a9c673e..65f99121 100644 --- a/packages/cloudflare/src/api/memory-queue.spec.ts +++ b/packages/cloudflare/src/api/overrides/queue/memory-queue.spec.ts @@ -6,9 +6,9 @@ import cache, { DEFAULT_REVALIDATION_TIMEOUT_MS } from "./memory-queue.js"; vi.mock("./.next/prerender-manifest.json", () => Promise.resolve({ preview: { previewModeId: "id" } })); const mockServiceWorkerFetch = vi.fn(); -vi.mock("./cloudflare-context", () => ({ +vi.mock("../../cloudflare-context", () => ({ getCloudflareContext: () => ({ - env: { NEXT_CACHE_REVALIDATION_WORKER: { fetch: mockServiceWorkerFetch } }, + env: { WORKER_SELF_REFERENCE: { fetch: mockServiceWorkerFetch } }, }), })); diff --git a/packages/cloudflare/src/api/memory-queue.ts b/packages/cloudflare/src/api/overrides/queue/memory-queue.ts similarity index 89% rename from packages/cloudflare/src/api/memory-queue.ts rename to packages/cloudflare/src/api/overrides/queue/memory-queue.ts index 338b4353..60de7599 100644 --- a/packages/cloudflare/src/api/memory-queue.ts +++ b/packages/cloudflare/src/api/overrides/queue/memory-queue.ts @@ -2,7 +2,7 @@ import { debug, error } from "@opennextjs/aws/adapters/logger.js"; import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js"; import { IgnorableError } from "@opennextjs/aws/utils/error.js"; -import { getCloudflareContext } from "./cloudflare-context"; +import { getCloudflareContext } from "../../cloudflare-context"; export const DEFAULT_REVALIDATION_TIMEOUT_MS = 10_000; @@ -11,7 +11,7 @@ export const DEFAULT_REVALIDATION_TIMEOUT_MS = 10_000; * * It offers basic support for in-memory de-duping per isolate. * - * A service binding called `NEXT_CACHE_REVALIDATION_WORKER` that points to your worker is required. + * A service binding called `WORKER_SELF_REFERENCE` that points to your worker is required. */ export class MemoryQueue implements Queue { readonly name = "memory-queue"; @@ -21,7 +21,7 @@ export class MemoryQueue implements Queue { constructor(private opts = { revalidationTimeoutMs: DEFAULT_REVALIDATION_TIMEOUT_MS }) {} async send({ MessageBody: { host, url }, MessageDeduplicationId }: QueueMessage): Promise { - const service = getCloudflareContext().env.NEXT_CACHE_REVALIDATION_WORKER; + const service = getCloudflareContext().env.WORKER_SELF_REFERENCE; if (!service) throw new IgnorableError("No service binding for cache revalidation worker"); if (this.revalidatedPaths.has(MessageDeduplicationId)) return; diff --git a/packages/cloudflare/src/api/d1-next-tag-cache.ts b/packages/cloudflare/src/api/overrides/tag-cache/d1-next-tag-cache.ts similarity index 70% rename from packages/cloudflare/src/api/d1-next-tag-cache.ts rename to packages/cloudflare/src/api/overrides/tag-cache/d1-next-tag-cache.ts index 224d6f54..1f61006d 100644 --- a/packages/cloudflare/src/api/d1-next-tag-cache.ts +++ b/packages/cloudflare/src/api/overrides/tag-cache/d1-next-tag-cache.ts @@ -3,20 +3,19 @@ import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; import type { NextModeTagCache } from "@opennextjs/aws/types/overrides.js"; import { RecoverableError } from "@opennextjs/aws/utils/error.js"; -import { getCloudflareContext } from "./cloudflare-context.js"; -import { DEFAULT_NEXT_CACHE_D1_REVALIDATIONS_TABLE } from "./constants.js"; +import { getCloudflareContext } from "../../cloudflare-context.js"; export class D1NextModeTagCache implements NextModeTagCache { readonly mode = "nextMode" as const; readonly name = "d1-next-mode-tag-cache"; async hasBeenRevalidated(tags: string[], lastModified?: number): Promise { - const { isDisabled, db, tables } = this.getConfig(); + const { isDisabled, db } = this.getConfig(); if (isDisabled) return false; try { const result = await db .prepare( - `SELECT COUNT(*) as cnt FROM ${JSON.stringify(tables.revalidations)} WHERE tag IN (${tags.map(() => "?").join(", ")}) AND revalidatedAt > ? LIMIT 1` + `SELECT COUNT(*) as cnt FROM revalidations WHERE tag IN (${tags.map(() => "?").join(", ")}) AND revalidatedAt > ? LIMIT 1` ) .bind(...tags.map((tag) => this.getCacheKey(tag)), lastModified ?? Date.now()) .first<{ cnt: number }>(); @@ -32,12 +31,12 @@ export class D1NextModeTagCache implements NextModeTagCache { } async writeTags(tags: string[]): Promise { - const { isDisabled, db, tables } = this.getConfig(); + const { isDisabled, db } = this.getConfig(); if (isDisabled) return Promise.resolve(); const result = await db.batch( tags.map((tag) => db - .prepare(`INSERT INTO ${JSON.stringify(tables.revalidations)} (tag, revalidatedAt) VALUES (?, ?)`) + .prepare(`INSERT INTO revalidations (tag, revalidatedAt) VALUES (?, ?)`) .bind(this.getCacheKey(tag), Date.now()) ) ); @@ -46,24 +45,19 @@ export class D1NextModeTagCache implements NextModeTagCache { private getConfig() { const cfEnv = getCloudflareContext().env; - const db = cfEnv.NEXT_CACHE_D1; + const db = cfEnv.NEXT_TAG_CACHE_D1; if (!db) debug("No D1 database found"); const isDisabled = !!(globalThis as unknown as { openNextConfig: OpenNextConfig }).openNextConfig .dangerous?.disableTagCache; - if (!db || isDisabled) { - return { isDisabled: true as const }; - } - - return { - isDisabled: false as const, - db, - tables: { - revalidations: cfEnv.NEXT_CACHE_D1_REVALIDATIONS_TABLE ?? DEFAULT_NEXT_CACHE_D1_REVALIDATIONS_TABLE, - }, - }; + return !db || isDisabled + ? { isDisabled: true as const } + : { + isDisabled: false as const, + db, + }; } protected removeBuildId(key: string) { diff --git a/packages/cloudflare/src/api/d1-tag-cache.ts b/packages/cloudflare/src/api/overrides/tag-cache/d1-tag-cache.ts similarity index 69% rename from packages/cloudflare/src/api/d1-tag-cache.ts rename to packages/cloudflare/src/api/overrides/tag-cache/d1-tag-cache.ts index b32ea6d0..444180ab 100644 --- a/packages/cloudflare/src/api/d1-tag-cache.ts +++ b/packages/cloudflare/src/api/overrides/tag-cache/d1-tag-cache.ts @@ -3,38 +3,35 @@ import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; import type { OriginalTagCache } from "@opennextjs/aws/types/overrides.js"; import { RecoverableError } from "@opennextjs/aws/utils/error.js"; -import { getCloudflareContext } from "./cloudflare-context.js"; -import { DEFAULT_NEXT_CACHE_D1_REVALIDATIONS_TABLE } from "./constants.js"; +import { getCloudflareContext } from "../../cloudflare-context.js"; /** - * An instance of the Tag Cache that uses a D1 binding (`NEXT_CACHE_D1`) as it's underlying data store. + * An instance of the Tag Cache that uses a D1 binding (`NEXT_TAG_CACHE_D1`) as it's underlying data store. * * **Tag/path mappings table** * * Information about the relation between tags and paths is stored in a `tags` table that contains - * two columns; `tag`, and `path`. The table name can be configured with `NEXT_CACHE_D1_TAGS_TABLE` - * environment variable. + * two columns; `tag`, and `path`. * * This table should be populated using an SQL file that is generated during the build process. * * **Tag revalidations table** * * Revalidation times for tags are stored in a `revalidations` table that contains two columns; `tags`, - * and `revalidatedAt`. The table name can be configured with `NEXT_CACHE_D1_REVALIDATIONS_TABLE` - * environment variable. + * and `revalidatedAt`. */ class D1TagCache implements OriginalTagCache { public readonly name = "d1-tag-cache"; public async getByPath(rawPath: string): Promise { - const { isDisabled, db, tables } = this.getConfig(); + const { isDisabled, db } = this.getConfig(); if (isDisabled) return []; const path = this.getCacheKey(rawPath); try { const { success, results } = await db - .prepare(`SELECT tag FROM ${JSON.stringify(tables.tags)} WHERE path = ?`) + .prepare(`SELECT tag FROM tags WHERE path = ?`) .bind(path) .all<{ tag: string }>(); @@ -51,14 +48,14 @@ class D1TagCache implements OriginalTagCache { } public async getByTag(rawTag: string): Promise { - const { isDisabled, db, tables } = this.getConfig(); + const { isDisabled, db } = this.getConfig(); if (isDisabled) return []; const tag = this.getCacheKey(rawTag); try { const { success, results } = await db - .prepare(`SELECT path FROM ${JSON.stringify(tables.tags)} WHERE tag = ?`) + .prepare(`SELECT path FROM tags WHERE tag = ?`) .bind(tag) .all<{ path: string }>(); @@ -75,15 +72,15 @@ class D1TagCache implements OriginalTagCache { } public async getLastModified(path: string, lastModified?: number): Promise { - const { isDisabled, db, tables } = this.getConfig(); + const { isDisabled, db } = this.getConfig(); if (isDisabled) return lastModified ?? Date.now(); try { const { success, results } = await db .prepare( - `SELECT ${JSON.stringify(tables.revalidations)}.tag FROM ${JSON.stringify(tables.revalidations)} - INNER JOIN ${JSON.stringify(tables.tags)} ON ${JSON.stringify(tables.revalidations)}.tag = ${JSON.stringify(tables.tags)}.tag - WHERE ${JSON.stringify(tables.tags)}.path = ? AND ${JSON.stringify(tables.revalidations)}.revalidatedAt > ?;` + `SELECT revalidations.tag FROM revalidations + INNER JOIN tags ON revalidations.tag = tags.tag + WHERE tags.path = ? AND revalidations.revalidatedAt > ?;` ) .bind(this.getCacheKey(path), lastModified ?? 0) .all<{ tag: string }>(); @@ -99,7 +96,7 @@ class D1TagCache implements OriginalTagCache { } public async writeTags(tags: { tag: string; path: string; revalidatedAt?: number }[]): Promise { - const { isDisabled, db, tables } = this.getConfig(); + const { isDisabled, db } = this.getConfig(); if (isDisabled || tags.length === 0) return; try { @@ -110,7 +107,7 @@ class D1TagCache implements OriginalTagCache { if (revalidatedAt === 1) { // new tag/path mapping from set return db - .prepare(`INSERT INTO ${JSON.stringify(tables.tags)} (tag, path) VALUES (?, ?)`) + .prepare(`INSERT INTO tags (tag, path) VALUES (?, ?)`) .bind(this.getCacheKey(tag), this.getCacheKey(path)); } @@ -118,9 +115,7 @@ class D1TagCache implements OriginalTagCache { // tag was revalidated uniqueTags.add(tag); return db - .prepare( - `INSERT INTO ${JSON.stringify(tables.revalidations)} (tag, revalidatedAt) VALUES (?, ?)` - ) + .prepare(`INSERT INTO revalidations (tag, revalidatedAt) VALUES (?, ?)`) .bind(this.getCacheKey(tag), revalidatedAt ?? Date.now()); } }) @@ -139,25 +134,19 @@ class D1TagCache implements OriginalTagCache { private getConfig() { const cfEnv = getCloudflareContext().env; - const db = cfEnv.NEXT_CACHE_D1; + const db = cfEnv.NEXT_TAG_CACHE_D1; if (!db) debug("No D1 database found"); const isDisabled = !!(globalThis as unknown as { openNextConfig: OpenNextConfig }).openNextConfig .dangerous?.disableTagCache; - if (!db || isDisabled) { - return { isDisabled: true as const }; - } - - return { - isDisabled: false as const, - db, - tables: { - tags: cfEnv.NEXT_CACHE_D1_TAGS_TABLE ?? "tags", - revalidations: cfEnv.NEXT_CACHE_D1_REVALIDATIONS_TABLE ?? DEFAULT_NEXT_CACHE_D1_REVALIDATIONS_TABLE, - }, - }; + return !db || isDisabled + ? { isDisabled: true as const } + : { + isDisabled: false as const, + db, + }; } protected removeBuildId(key: string) { diff --git a/packages/cloudflare/src/api/do-sharded-tag-cache.spec.ts b/packages/cloudflare/src/api/overrides/tag-cache/do-sharded-tag-cache.spec.ts similarity index 98% rename from packages/cloudflare/src/api/do-sharded-tag-cache.spec.ts rename to packages/cloudflare/src/api/overrides/tag-cache/do-sharded-tag-cache.spec.ts index 4476e88c..02ece6bc 100644 --- a/packages/cloudflare/src/api/do-sharded-tag-cache.spec.ts +++ b/packages/cloudflare/src/api/overrides/tag-cache/do-sharded-tag-cache.spec.ts @@ -14,11 +14,11 @@ const getMock = vi .mockReturnValue({ hasBeenRevalidated: hasBeenRevalidatedMock, writeTags: writeTagsMock }); const waitUntilMock = vi.fn().mockImplementation(async (fn) => fn()); const sendDLQMock = vi.fn(); -vi.mock("./cloudflare-context", () => ({ +vi.mock("../../cloudflare-context", () => ({ getCloudflareContext: () => ({ env: { - NEXT_CACHE_DO_SHARDED: { idFromName: idFromNameMock, get: getMock }, - NEXT_CACHE_DO_SHARDED_DLQ: { + NEXT_TAG_CACHE_DO_SHARDED: { idFromName: idFromNameMock, get: getMock }, + NEXT_TAG_CACHE_DO_SHARDED_DLQ: { send: sendDLQMock, }, }, @@ -83,7 +83,6 @@ describe("DOShardedTagCache", () => { { doId: expect.objectContaining({ shardId: "tag-hard;shard-1" }), tags: ["tag1"] }, ]; const result = cache.groupTagsByDO({ tags: ["tag1", "_N_T_/tag1"], generateAllReplicas: true }); - console.log(result); expect(result).toEqual(expectedResult); }); diff --git a/packages/cloudflare/src/api/do-sharded-tag-cache.ts b/packages/cloudflare/src/api/overrides/tag-cache/do-sharded-tag-cache.ts similarity index 96% rename from packages/cloudflare/src/api/do-sharded-tag-cache.ts rename to packages/cloudflare/src/api/overrides/tag-cache/do-sharded-tag-cache.ts index 530355c9..a54347ba 100644 --- a/packages/cloudflare/src/api/do-sharded-tag-cache.ts +++ b/packages/cloudflare/src/api/overrides/tag-cache/do-sharded-tag-cache.ts @@ -4,7 +4,7 @@ import type { OpenNextConfig } from "@opennextjs/aws/types/open-next"; import type { NextModeTagCache } from "@opennextjs/aws/types/overrides.js"; import { IgnorableError } from "@opennextjs/aws/utils/error.js"; -import { getCloudflareContext } from "./cloudflare-context"; +import { getCloudflareContext } from "../../cloudflare-context"; const SOFT_TAG_PREFIX = "_N_T_/"; export const DEFAULT_SOFT_REPLICAS = 4; @@ -89,7 +89,7 @@ export class TagCacheDOId { } class ShardedDOTagCache implements NextModeTagCache { readonly mode = "nextMode" as const; - readonly name = "sharded-do-tag-cache"; + readonly name = "do-sharded-tag-cache"; readonly numSoftReplicas: number; readonly numHardReplicas: number; readonly maxWriteRetries: number; @@ -102,7 +102,7 @@ class ShardedDOTagCache implements NextModeTagCache { } private getDurableObjectStub(doId: TagCacheDOId) { - const durableObject = getCloudflareContext().env.NEXT_CACHE_DO_SHARDED; + const durableObject = getCloudflareContext().env.NEXT_TAG_CACHE_DO_SHARDED; if (!durableObject) throw new IgnorableError("No durable object binding for cache revalidation"); const id = durableObject.idFromName(doId.key); @@ -189,20 +189,18 @@ class ShardedDOTagCache implements NextModeTagCache { private async getConfig() { const cfEnv = getCloudflareContext().env; - const db = cfEnv.NEXT_CACHE_DO_SHARDED; + const db = cfEnv.NEXT_TAG_CACHE_DO_SHARDED; if (!db) debug("No Durable object found"); const isDisabled = !!(globalThis as unknown as { openNextConfig: OpenNextConfig }).openNextConfig .dangerous?.disableTagCache; - if (!db || isDisabled) { - return { isDisabled: true as const }; - } - - return { - isDisabled: false as const, - db, - }; + return !db || isDisabled + ? { isDisabled: true as const } + : { + isDisabled: false as const, + db, + }; } /** @@ -271,7 +269,7 @@ class ShardedDOTagCache implements NextModeTagCache { if (retryNumber >= this.maxWriteRetries) { error("Error while writing tags, too many retries"); // Do we want to throw an error here ? - await getCloudflareContext().env.NEXT_CACHE_DO_SHARDED_DLQ?.send({ + await getCloudflareContext().env.NEXT_TAG_CACHE_DO_SHARDED_DLQ?.send({ failingShardId: doId.key, failingTags: tags, lastModified, diff --git a/packages/cloudflare/src/cli/build/open-next/compile-cache-assets-manifest.ts b/packages/cloudflare/src/cli/build/open-next/compile-cache-assets-manifest.ts index 49ea3460..1be40d84 100644 --- a/packages/cloudflare/src/cli/build/open-next/compile-cache-assets-manifest.ts +++ b/packages/cloudflare/src/cli/build/open-next/compile-cache-assets-manifest.ts @@ -5,27 +5,21 @@ import type { BuildOptions } from "@opennextjs/aws/build/helper.js"; import type { TagCacheMetaFile } from "@opennextjs/aws/types/cache.js"; /** - * Generates SQL statements that can be used to initialise the cache assets manifest in an SQL data store. + * Generates SQL statements that can be used to initialize the cache assets manifest in an SQL data store. */ export function compileCacheAssetsManifestSqlFile(options: BuildOptions, metaFiles: TagCacheMetaFile[]) { const outputPath = path.join(options.outputDir, "cloudflare/cache-assets-manifest.sql"); - const tagsTable = process.env.NEXT_CACHE_D1_TAGS_TABLE || "tags"; - const revalidationsTable = process.env.NEXT_CACHE_D1_REVALIDATIONS_TABLE || "revalidations"; - mkdirSync(path.dirname(outputPath), { recursive: true }); writeFileSync( outputPath, - `CREATE TABLE IF NOT EXISTS ${JSON.stringify(tagsTable)} (tag TEXT NOT NULL, path TEXT NOT NULL, UNIQUE(tag, path) ON CONFLICT REPLACE); - CREATE TABLE IF NOT EXISTS ${JSON.stringify(revalidationsTable)} (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);\n` + `CREATE TABLE IF NOT EXISTS tags (tag TEXT NOT NULL, path TEXT NOT NULL, UNIQUE(tag, path) ON CONFLICT REPLACE); + CREATE TABLE IF NOT EXISTS revalidations (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);\n` ); const values = metaFiles.map(({ tag, path }) => `(${JSON.stringify(tag.S)}, ${JSON.stringify(path.S)})`); if (values.length) { - appendFileSync( - outputPath, - `INSERT INTO ${JSON.stringify(tagsTable)} (tag, path) VALUES ${values.join(", ")};` - ); + appendFileSync(outputPath, `INSERT INTO tags (tag, path) VALUES ${values.join(", ")};`); } } diff --git a/packages/cloudflare/src/cli/build/open-next/copyCacheAssets.ts b/packages/cloudflare/src/cli/build/open-next/copyCacheAssets.ts index ae67984b..9ea967a7 100644 --- a/packages/cloudflare/src/cli/build/open-next/copyCacheAssets.ts +++ b/packages/cloudflare/src/cli/build/open-next/copyCacheAssets.ts @@ -3,7 +3,7 @@ import { join } from "node:path"; import * as buildHelper from "@opennextjs/aws/build/helper.js"; -import { CACHE_ASSET_DIR } from "../../../api/kv-cache.js"; +import { CACHE_ASSET_DIR } from "../../../api/overrides/incremental-cache/kv-incremental-cache.js"; export function copyCacheAssets(options: buildHelper.BuildOptions) { const { outputDir } = options; diff --git a/packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.spec.ts index 5b9d5e0d..eaa339d4 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.spec.ts @@ -75,7 +75,7 @@ const unminifiedCode = `async function revalidate(urlPath, opts, req, context) { describe("patchResRevalidate", () => { test("patch minified code", () => { expect(patchCode(minifiedApiPageRuntimeCode, rule)).toMatchInlineSnapshot( - `"var r=/(?:^|,)\\s*?no-cache\\s*?(?:,|$)/;function t(e){var r=e&&Date.parse(e);return"number"==typeof r?r:NaN}e.exports=function(e,n){var o=e["if-modified-since"],i=e["if-none-match"];if(!o&&!i)return!1;var a=e["cache-control"];if(a&&r.test(a))return!1;if(i&&"*"!==i){var s=n.etag;if(!s)return!1;for(var d=!0,u=function(e){for(var r=0,t=[],n=0,o=0,i=e.length;o{"use strict";t.r(r),t.d(r,{decryptWithSecret:()=>s,encryptWithSecret:()=>a});let n=require("crypto");var o=/*#__PURE__*/t.n(n);let i="aes-256-gcm";function a(e,r){let t=o().randomBytes(16),n=o().randomBytes(64),a=o().pbkdf2Sync(e,n,1e5,32,"sha512"),s=o().createCipheriv(i,a,t),d=Buffer.concat([s.update(r,"utf8"),s.final()]),u=s.getAuthTag();return Buffer.concat([n,t,u,d]).toString("hex")}function s(e,r){let t=Buffer.from(r,"hex"),n=t.slice(0,64),a=t.slice(64,80),s=t.slice(80,96),d=t.slice(96),u=o().pbkdf2Sync(e,n,1e5,32,"sha512"),l=o().createDecipheriv(i,u,a);return l.setAuthTag(s),l.update(d)+l.final("utf8")}},"next/dist/compiled/jsonwebtoken":e=>{"use strict";e.exports=require("next/dist/compiled/jsonwebtoken")},"next/dist/compiled/raw-body":e=>{"use strict";e.exports=require("next/dist/compiled/raw-body")},querystring:e=>{"use strict";e.exports=require("querystring")}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n](i,i.exports,t),i.exports}t.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return t.d(r,{a:r}),r},t.d=(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};(()=>{"use strict";t.r(n),t.d(n,{PagesAPIRouteModule:()=>z,default:()=>U});class e{static get(e,r,t){let n=Reflect.get(e,r,t);return"function"==typeof n?n.bind(e):n}static set(e,r,t,n){return Reflect.set(e,r,t,n)}static has(e,r){return Reflect.has(e,r)}static deleteProperty(e,r){return Reflect.deleteProperty(e,r)}}class r extends Error{constructor(){super("Headers cannot be modified. Read more: https://nextjs.org/docs/app/api-reference/functions/headers")}static callable(){throw new r}}class o extends Headers{constructor(r){super(),this.headers=new Proxy(r,{get(t,n,o){if("symbol"==typeof n)return e.get(t,n,o);let i=n.toLowerCase(),a=Object.keys(r).find(e=>e.toLowerCase()===i);if(void 0!==a)return e.get(t,a,o)},set(t,n,o,i){if("symbol"==typeof n)return e.set(t,n,o,i);let a=n.toLowerCase(),s=Object.keys(r).find(e=>e.toLowerCase()===a);return e.set(t,s??n,o,i)},has(t,n){if("symbol"==typeof n)return e.has(t,n);let o=n.toLowerCase(),i=Object.keys(r).find(e=>e.toLowerCase()===o);return void 0!==i&&e.has(t,i)},deleteProperty(t,n){if("symbol"==typeof n)return e.deleteProperty(t,n);let o=n.toLowerCase(),i=Object.keys(r).find(e=>e.toLowerCase()===o);return void 0===i||e.deleteProperty(t,i)}})}static seal(t){return new Proxy(t,{get(t,n,o){switch(n){case"append":case"delete":case"set":return r.callable;default:return e.get(t,n,o)}}})}merge(e){return Array.isArray(e)?e.join(", "):e}static from(e){return e instanceof Headers?e:new o(e)}append(e,r){let t=this.headers[e];"string"==typeof t?this.headers[e]=[t,r]:Array.isArray(t)?t.push(r):this.headers[e]=r}delete(e){delete this.headers[e]}get(e){let r=this.headers[e];return void 0!==r?this.merge(r):null}has(e){return void 0!==this.headers[e]}set(e,r){this.headers[e]=r}forEach(e,r){for(let[t,n]of this.entries())e.call(r,n,t,this)}*entries(){for(let e of Object.keys(this.headers)){let r=e.toLowerCase(),t=this.get(r);yield[r,t]}}*keys(){for(let e of Object.keys(this.headers)){let r=e.toLowerCase();yield r}}*values(){for(let e of Object.keys(this.headers)){let r=this.get(e);yield r}}[Symbol.iterator](){return this.entries()}}let i="x-prerender-revalidate",a="x-prerender-revalidate-if-generated",s={shared:"shared",reactServerComponents:"rsc",serverSideRendering:"ssr",actionBrowser:"action-browser",apiNode:"api-node",apiEdge:"api-edge",middleware:"middleware",instrument:"instrument",edgeAsset:"edge-asset",appPagesBrowser:"app-pages-browser",pagesDirBrowser:"pages-dir-browser",pagesDirEdge:"pages-dir-edge",pagesDirNode:"pages-dir-node"};({...s,GROUP:{builtinReact:[s.reactServerComponents,s.actionBrowser],serverOnly:[s.reactServerComponents,s.actionBrowser,s.instrument,s.middleware],neutralTarget:[s.apiNode,s.apiEdge],clientOnly:[s.serverSideRendering,s.appPagesBrowser],bundled:[s.reactServerComponents,s.actionBrowser,s.serverSideRendering,s.appPagesBrowser,s.shared,s.instrument,s.middleware],appPages:[s.reactServerComponents,s.serverSideRendering,s.appPagesBrowser,s.actionBrowser]}});let d=require("next/dist/server/lib/trace/tracer");var u=/*#__PURE__*/function(e){return e.handleRequest="BaseServer.handleRequest",e.run="BaseServer.run",e.pipe="BaseServer.pipe",e.getStaticHTML="BaseServer.getStaticHTML",e.render="BaseServer.render",e.renderToResponseWithComponents="BaseServer.renderToResponseWithComponents",e.renderToResponse="BaseServer.renderToResponse",e.renderToHTML="BaseServer.renderToHTML",e.renderError="BaseServer.renderError",e.renderErrorToResponse="BaseServer.renderErrorToResponse",e.renderErrorToHTML="BaseServer.renderErrorToHTML",e.render404="BaseServer.render404",e}(u||{}),l=/*#__PURE__*/function(e){return e.loadDefaultErrorComponents="LoadComponents.loadDefaultErrorComponents",e.loadComponents="LoadComponents.loadComponents",e}(l||{}),p=/*#__PURE__*/function(e){return e.getRequestHandler="NextServer.getRequestHandler",e.getServer="NextServer.getServer",e.getServerRequestHandler="NextServer.getServerRequestHandler",e.createServer="createServer.createServer",e}(p||{}),c=/*#__PURE__*/function(e){return e.compression="NextNodeServer.compression",e.getBuildId="NextNodeServer.getBuildId",e.createComponentTree="NextNodeServer.createComponentTree",e.clientComponentLoading="NextNodeServer.clientComponentLoading",e.getLayoutOrPageModule="NextNodeServer.getLayoutOrPageModule",e.generateStaticRoutes="NextNodeServer.generateStaticRoutes",e.generateFsStaticRoutes="NextNodeServer.generateFsStaticRoutes",e.generatePublicRoutes="NextNodeServer.generatePublicRoutes",e.generateImageRoutes="NextNodeServer.generateImageRoutes.route",e.sendRenderResult="NextNodeServer.sendRenderResult",e.proxyRequest="NextNodeServer.proxyRequest",e.runApi="NextNodeServer.runApi",e.render="NextNodeServer.render",e.renderHTML="NextNodeServer.renderHTML",e.imageOptimizer="NextNodeServer.imageOptimizer",e.getPagePath="NextNodeServer.getPagePath",e.getRoutesManifest="NextNodeServer.getRoutesManifest",e.findPageComponents="NextNodeServer.findPageComponents",e.getFontManifest="NextNodeServer.getFontManifest",e.getServerComponentManifest="NextNodeServer.getServerComponentManifest",e.getRequestHandler="NextNodeServer.getRequestHandler",e.renderToHTML="NextNodeServer.renderToHTML",e.renderError="NextNodeServer.renderError",e.renderErrorToHTML="NextNodeServer.renderErrorToHTML",e.render404="NextNodeServer.render404",e.startResponse="NextNodeServer.startResponse",e.route="route",e.onProxyReq="onProxyReq",e.apiResolver="apiResolver",e.internalFetch="internalFetch",e}(c||{}),f=/*#__PURE__*/function(e){return e.startServer="startServer.startServer",e}(f||{}),g=/*#__PURE__*/function(e){return e.getServerSideProps="Render.getServerSideProps",e.getStaticProps="Render.getStaticProps",e.renderToString="Render.renderToString",e.renderDocument="Render.renderDocument",e.createBodyResult="Render.createBodyResult",e}(g||{}),v=/*#__PURE__*/function(e){return e.renderToString="AppRender.renderToString",e.renderToReadableStream="AppRender.renderToReadableStream",e.getBodyResult="AppRender.getBodyResult",e.fetch="AppRender.fetch",e}(v||{}),m=/*#__PURE__*/function(e){return e.executeRoute="Router.executeRoute",e}(m||{}),h=/*#__PURE__*/function(e){return e.runHandler="Node.runHandler",e}(h||{}),y=/*#__PURE__*/function(e){return e.runHandler="AppRouteRouteHandlers.runHandler",e}(y||{}),b=/*#__PURE__*/function(e){return e.generateMetadata="ResolveMetadata.generateMetadata",e.generateViewport="ResolveMetadata.generateViewport",e}(b||{}),x=/*#__PURE__*/function(e){return e.execute="Middleware.execute",e}(x||{});let w="__prerender_bypass",S="__next_preview_data",R=Symbol(S),_=Symbol(w);function E(e,r={}){if(_ in e)return e;let{serialize:n}=t("./dist/compiled/cookie/index.js"),o=e.getHeader("Set-Cookie");return e.setHeader("Set-Cookie",[..."string"==typeof o?[o]:Array.isArray(o)?o:[],n(w,"",{expires:new Date(0),httpOnly:!0,sameSite:"none",secure:!0,path:"/",...void 0!==r.path?{path:r.path}:void 0}),n(S,"",{expires:new Date(0),httpOnly:!0,sameSite:"none",secure:!0,path:"/",...void 0!==r.path?{path:r.path}:void 0})]),Object.defineProperty(e,_,{value:!0,enumerable:!1}),e}class O extends Error{constructor(e,r){super(r),this.statusCode=e}}function C(e,r,t){e.statusCode=r,e.statusMessage=t,e.end(t)}function N({req:e},r,t){let n={configurable:!0,enumerable:!0},o={...n,writable:!0};Object.defineProperty(e,r,{...n,get:()=>{let n=t();return Object.defineProperty(e,r,{...o,value:n}),n},set:t=>{Object.defineProperty(e,r,{...o,value:t})}})}class j{constructor({userland:e,definition:r}){this.userland=e,this.definition=r}}var T=t("./dist/compiled/bytes/index.js"),P=/*#__PURE__*/t.n(T);let A=e=>{let r=e.length,t=0,n=0,o=8997,i=0,a=33826,s=0,d=40164,u=0,l=52210;for(;t>>16,o=65535&n,s+=i>>>16,a=65535&i,l=u+(s>>>16)&65535,d=65535&s;return(15&l)*0x1000000000000+0x100000000*d+65536*a+(o^l>>4)},H=(e,r=!1)=>(r?'W/"':'"')+A(e).toString(36)+e.length.toString(36)+'"';"undefined"!=typeof performance&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);var M=t("./dist/compiled/fresh/index.js"),k=/*#__PURE__*/t.n(M);let B=require("stream");function L(e){return"object"==typeof e&&null!==e&&"name"in e&&"message"in e}var D=t("./dist/compiled/@edge-runtime/cookies/index.js"),$=t("./dist/compiled/content-type/index.js");async function q(e,r){let n,o;try{n=(0,$.parse)(e.headers["content-type"]||"text/plain")}catch{n=(0,$.parse)("text/plain")}let{type:i,parameters:a}=n,s=a.charset||"utf-8";try{let n=t("next/dist/compiled/raw-body");o=await n(e,{encoding:s,limit:r})}catch(e){if(L(e)&&"entity.too.large"===e.type)throw Object.defineProperty(new O(413,\`Body exceeded \${r} limit\`),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});throw Object.defineProperty(new O(400,"Invalid body"),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0})}let d=o.toString();return"application/json"===i||"application/ld+json"===i?function(e){if(0===e.length)return{};try{return JSON.parse(e)}catch(e){throw Object.defineProperty(new O(400,"Invalid JSON"),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0})}}(d):"application/x-www-form-urlencoded"===i?t("querystring").decode(d):d}function I(e){return"string"==typeof e&&e.length>=16}async function K(e,r,t,n){if("string"!=typeof e||!e.startsWith("/"))throw Object.defineProperty(Error(\`Invalid urlPath provided to revalidate(), must be a path e.g. /blog/post-1, received \${e}\`),"__NEXT_ERROR_CODE",{value:"E153",enumerable:!1,configurable:!0});let o={[i]:n.previewModeId,...r.unstable_onlyGenerated?{[a]:"1"}:{}},s=[...n.allowedRevalidateHeaderKeys||[]];for(let e of((n.trustHostHeader||n.dev)&&s.push("cookie"),n.trustHostHeader&&s.push("x-vercel-protection-bypass"),Object.keys(t.headers)))s.includes(e)&&(o[e]=t.headers[e]);try{if(n.trustHostHeader){let n=await (await import("@opennextjs/cloudflare")).getCloudflareContext().env.NEXT_CACHE_REVALIDATION_WORKER.fetch(\`\${t.headers.host.includes("localhost") ? "http":"https" }://\${t.headers.host}\${e}\`,{method:'HEAD', headers:o}),i=n.headers.get("x-vercel-cache")||n.headers.get("x-nextjs-cache");if((null==i?void 0:i.toUpperCase())!=="REVALIDATED"&&200!==n.status&&!(404===n.status&&r.unstable_onlyGenerated))throw Object.defineProperty(Error(\`Invalid response \${n.status}\`),"__NEXT_ERROR_CODE",{value:"E175",enumerable:!1,configurable:!0})}else if(n.revalidate)await n.revalidate({urlPath:e,revalidateHeaders:o,opts:r});else throw Object.defineProperty(Error("Invariant: required internal revalidate method not passed to api-utils"),"__NEXT_ERROR_CODE",{value:"E174",enumerable:!1,configurable:!0})}catch(r){throw Object.defineProperty(Error(\`Failed to revalidate \${e}: \${L(r)?r.message:r}\`),"__NEXT_ERROR_CODE",{value:"E240",enumerable:!1,configurable:!0})}}async function X(e,r,n,s,d,u,l,p,c){try{var f,g,v,m;if(!s){r.statusCode=404,r.end("Not Found");return}let u=s.config||{},l=(null==(f=u.api)?void 0:f.bodyParser)!==!1,p=(null==(g=u.api)?void 0:g.responseLimit)??!0;null==(v=u.api)||v.externalResolver,N({req:e},"cookies",(m=e.headers,function(){let{cookie:e}=m;if(!e)return{};let{parse:r}=t("./dist/compiled/cookie/index.js");return r(Array.isArray(e)?e.join("; "):e)})),e.query=n,N({req:e},"previewData",()=>(function(e,r,n,s){var d,u;let l;if(n&&function(e,r){let t=o.from(e.headers);return{isOnDemandRevalidate:t.get(i)===r.previewModeId,revalidateOnlyGenerated:t.has(a)}}(e,n).isOnDemandRevalidate)return!1;if(R in e)return e[R];let p=o.from(e.headers),c=new D.RequestCookies(p),f=null==(d=c.get(w))?void 0:d.value,g=null==(u=c.get(S))?void 0:u.value;if(f&&!g&&f===n.previewModeId){let r={};return Object.defineProperty(e,R,{value:r,enumerable:!1}),r}if(!f&&!g)return!1;if(!f||!g||f!==n.previewModeId)return s||E(r),!1;try{l=t("next/dist/compiled/jsonwebtoken").verify(g,n.previewModeSigningKey)}catch{return E(r),!1}let{decryptWithSecret:v}=t("./dist/esm/server/crypto-utils.js"),m=v(Buffer.from(n.previewModeEncryptionKey),l.data);try{let r=JSON.parse(m);return Object.defineProperty(e,R,{value:r,enumerable:!1}),r}catch{return!1}})(e,r,d,!!d.multiZoneDraftMode)),N({req:e},"preview",()=>!1!==e.previewData||void 0),N({req:e},"draftMode",()=>e.preview),l&&!e.body&&(e.body=await q(e,u.api&&u.api.bodyParser&&u.api.bodyParser.sizeLimit?u.api.bodyParser.sizeLimit:"1mb"));let c=0,h=p&&"boolean"!=typeof p?P().parse(p):4194304,y=r.write,b=r.end;r.write=(...e)=>(c+=Buffer.byteLength(e[0]||""),y.apply(r,e)),r.end=(...t)=>(t.length&&"function"!=typeof t[0]&&(c+=Buffer.byteLength(t[0]||"")),p&&c>=h&&console.warn(\`API response for \${e.url} exceeds \${P().format(h)}. API Routes are meant to respond quickly. https://nextjs.org/docs/messages/api-routes-response-size-limit\`),b.apply(r,t)),r.status=e=>(r.statusCode=e,r),r.send=t=>(function(e,r,t){var n;if(null==t){r.end();return}if(204===r.statusCode||304===r.statusCode){r.removeHeader("Content-Type"),r.removeHeader("Content-Length"),r.removeHeader("Transfer-Encoding"),r.end();return}let o=r.getHeader("Content-Type");if(t instanceof B.Stream){o||r.setHeader("Content-Type","application/octet-stream"),t.pipe(r);return}let i=["object","number","boolean"].includes(typeof t),a=i?JSON.stringify(t):t;if((n=H(a))&&r.setHeader("ETag",n),!k()(e.headers,{etag:n})||(r.statusCode=304,r.end(),0)){if(Buffer.isBuffer(t)){o||r.setHeader("Content-Type","application/octet-stream"),r.setHeader("Content-Length",t.length),r.end(t);return}i&&r.setHeader("Content-Type","application/json; charset=utf-8"),r.setHeader("Content-Length",Buffer.byteLength(a)),r.end(a)}})(e,r,t),r.json=e=>{r.setHeader("Content-Type","application/json; charset=utf-8"),r.send(JSON.stringify(e))},r.redirect=(e,t)=>(function(e,r,t){if("string"==typeof r&&(t=r,r=307),"number"!=typeof r||"string"!=typeof t)throw Object.defineProperty(Error("Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination')."),"__NEXT_ERROR_CODE",{value:"E389",enumerable:!1,configurable:!0});return e.writeHead(r,{Location:t}),e.write(t),e.end(),e})(r,e,t),r.setDraftMode=(e={enable:!0})=>(function(e,r){if(!I(r.previewModeId))throw Object.defineProperty(Error("invariant: invalid previewModeId"),"__NEXT_ERROR_CODE",{value:"E169",enumerable:!1,configurable:!0});let n=r.enable?void 0:new Date(0),{serialize:o}=t("./dist/compiled/cookie/index.js"),i=e.getHeader("Set-Cookie");return e.setHeader("Set-Cookie",[..."string"==typeof i?[i]:Array.isArray(i)?i:[],o(w,r.previewModeId,{httpOnly:!0,sameSite:"none",secure:!0,path:"/",expires:n})]),e})(r,Object.assign({},d,e)),r.setPreviewData=(e,n={})=>(function(e,r,n){if(!I(n.previewModeId))throw Object.defineProperty(Error("invariant: invalid previewModeId"),"__NEXT_ERROR_CODE",{value:"E169",enumerable:!1,configurable:!0});if(!I(n.previewModeEncryptionKey))throw Object.defineProperty(Error("invariant: invalid previewModeEncryptionKey"),"__NEXT_ERROR_CODE",{value:"E334",enumerable:!1,configurable:!0});if(!I(n.previewModeSigningKey))throw Object.defineProperty(Error("invariant: invalid previewModeSigningKey"),"__NEXT_ERROR_CODE",{value:"E436",enumerable:!1,configurable:!0});let o=t("next/dist/compiled/jsonwebtoken"),{encryptWithSecret:i}=t("./dist/esm/server/crypto-utils.js"),a=o.sign({data:i(Buffer.from(n.previewModeEncryptionKey),JSON.stringify(r))},n.previewModeSigningKey,{algorithm:"HS256",...void 0!==n.maxAge?{expiresIn:n.maxAge}:void 0});if(a.length>2048)throw Object.defineProperty(Error("Preview data is limited to 2KB currently, reduce how much data you are storing as preview data to continue"),"__NEXT_ERROR_CODE",{value:"E465",enumerable:!1,configurable:!0});let{serialize:s}=t("./dist/compiled/cookie/index.js"),d=e.getHeader("Set-Cookie");return e.setHeader("Set-Cookie",[..."string"==typeof d?[d]:Array.isArray(d)?d:[],s(w,n.previewModeId,{httpOnly:!0,sameSite:"none",secure:!0,path:"/",...void 0!==n.maxAge?{maxAge:n.maxAge}:void 0,...void 0!==n.path?{path:n.path}:void 0}),s(S,a,{httpOnly:!0,sameSite:"none",secure:!0,path:"/",...void 0!==n.maxAge?{maxAge:n.maxAge}:void 0,...void 0!==n.path?{path:n.path}:void 0})]),e})(r,e,Object.assign({},d,n)),r.clearPreviewData=(e={})=>E(r,e),r.revalidate=(r,t)=>K(r,t||{},e,d);let x=s.default||s;await x(e,r)}catch(t){if(null==c||c(t,e,{routerKind:"Pages Router",routePath:p||"",routeType:"route",revalidateReason:void 0}),t instanceof O)C(r,t.statusCode,t.message);else{if(l)throw L(t)&&(t.page=p),t;if(console.error(t),u)throw t;C(r,500,"Internal Server Error")}}}class z extends j{constructor(e){if(super(e),"function"!=typeof e.userland.default)throw Object.defineProperty(Error(\`Page \${e.definition.page} does not export a default function.\`),"__NEXT_ERROR_CODE",{value:"E379",enumerable:!1,configurable:!0});this.apiResolverWrapped=function(e,r){return(...t)=>((0,d.getTracer)().setRootSpanAttribute("next.route",e),(0,d.getTracer)().trace(h.runHandler,{spanName:\`executing api route (pages) \${e}\`},()=>r(...t)))}(e.definition.page,X)}async render(e,r,t){let{apiResolverWrapped:n}=this;await n(e,r,t.query,this.userland,{...t.previewProps,revalidate:t.revalidate,trustHostHeader:t.trustHostHeader,allowedRevalidateHeaderKeys:t.allowedRevalidateHeaderKeys,hostname:t.hostname,multiZoneDraftMode:t.multiZoneDraftMode,dev:t.dev},t.minimalMode,t.dev,t.page,t.onError)}}let U=z})(),module.exports=n})();"` + `"var r=/(?:^|,)\\s*?no-cache\\s*?(?:,|$)/;function t(e){var r=e&&Date.parse(e);return"number"==typeof r?r:NaN}e.exports=function(e,n){var o=e["if-modified-since"],i=e["if-none-match"];if(!o&&!i)return!1;var a=e["cache-control"];if(a&&r.test(a))return!1;if(i&&"*"!==i){var s=n.etag;if(!s)return!1;for(var d=!0,u=function(e){for(var r=0,t=[],n=0,o=0,i=e.length;o{"use strict";t.r(r),t.d(r,{decryptWithSecret:()=>s,encryptWithSecret:()=>a});let n=require("crypto");var o=/*#__PURE__*/t.n(n);let i="aes-256-gcm";function a(e,r){let t=o().randomBytes(16),n=o().randomBytes(64),a=o().pbkdf2Sync(e,n,1e5,32,"sha512"),s=o().createCipheriv(i,a,t),d=Buffer.concat([s.update(r,"utf8"),s.final()]),u=s.getAuthTag();return Buffer.concat([n,t,u,d]).toString("hex")}function s(e,r){let t=Buffer.from(r,"hex"),n=t.slice(0,64),a=t.slice(64,80),s=t.slice(80,96),d=t.slice(96),u=o().pbkdf2Sync(e,n,1e5,32,"sha512"),l=o().createDecipheriv(i,u,a);return l.setAuthTag(s),l.update(d)+l.final("utf8")}},"next/dist/compiled/jsonwebtoken":e=>{"use strict";e.exports=require("next/dist/compiled/jsonwebtoken")},"next/dist/compiled/raw-body":e=>{"use strict";e.exports=require("next/dist/compiled/raw-body")},querystring:e=>{"use strict";e.exports=require("querystring")}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n](i,i.exports,t),i.exports}t.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return t.d(r,{a:r}),r},t.d=(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};(()=>{"use strict";t.r(n),t.d(n,{PagesAPIRouteModule:()=>z,default:()=>U});class e{static get(e,r,t){let n=Reflect.get(e,r,t);return"function"==typeof n?n.bind(e):n}static set(e,r,t,n){return Reflect.set(e,r,t,n)}static has(e,r){return Reflect.has(e,r)}static deleteProperty(e,r){return Reflect.deleteProperty(e,r)}}class r extends Error{constructor(){super("Headers cannot be modified. Read more: https://nextjs.org/docs/app/api-reference/functions/headers")}static callable(){throw new r}}class o extends Headers{constructor(r){super(),this.headers=new Proxy(r,{get(t,n,o){if("symbol"==typeof n)return e.get(t,n,o);let i=n.toLowerCase(),a=Object.keys(r).find(e=>e.toLowerCase()===i);if(void 0!==a)return e.get(t,a,o)},set(t,n,o,i){if("symbol"==typeof n)return e.set(t,n,o,i);let a=n.toLowerCase(),s=Object.keys(r).find(e=>e.toLowerCase()===a);return e.set(t,s??n,o,i)},has(t,n){if("symbol"==typeof n)return e.has(t,n);let o=n.toLowerCase(),i=Object.keys(r).find(e=>e.toLowerCase()===o);return void 0!==i&&e.has(t,i)},deleteProperty(t,n){if("symbol"==typeof n)return e.deleteProperty(t,n);let o=n.toLowerCase(),i=Object.keys(r).find(e=>e.toLowerCase()===o);return void 0===i||e.deleteProperty(t,i)}})}static seal(t){return new Proxy(t,{get(t,n,o){switch(n){case"append":case"delete":case"set":return r.callable;default:return e.get(t,n,o)}}})}merge(e){return Array.isArray(e)?e.join(", "):e}static from(e){return e instanceof Headers?e:new o(e)}append(e,r){let t=this.headers[e];"string"==typeof t?this.headers[e]=[t,r]:Array.isArray(t)?t.push(r):this.headers[e]=r}delete(e){delete this.headers[e]}get(e){let r=this.headers[e];return void 0!==r?this.merge(r):null}has(e){return void 0!==this.headers[e]}set(e,r){this.headers[e]=r}forEach(e,r){for(let[t,n]of this.entries())e.call(r,n,t,this)}*entries(){for(let e of Object.keys(this.headers)){let r=e.toLowerCase(),t=this.get(r);yield[r,t]}}*keys(){for(let e of Object.keys(this.headers)){let r=e.toLowerCase();yield r}}*values(){for(let e of Object.keys(this.headers)){let r=this.get(e);yield r}}[Symbol.iterator](){return this.entries()}}let i="x-prerender-revalidate",a="x-prerender-revalidate-if-generated",s={shared:"shared",reactServerComponents:"rsc",serverSideRendering:"ssr",actionBrowser:"action-browser",apiNode:"api-node",apiEdge:"api-edge",middleware:"middleware",instrument:"instrument",edgeAsset:"edge-asset",appPagesBrowser:"app-pages-browser",pagesDirBrowser:"pages-dir-browser",pagesDirEdge:"pages-dir-edge",pagesDirNode:"pages-dir-node"};({...s,GROUP:{builtinReact:[s.reactServerComponents,s.actionBrowser],serverOnly:[s.reactServerComponents,s.actionBrowser,s.instrument,s.middleware],neutralTarget:[s.apiNode,s.apiEdge],clientOnly:[s.serverSideRendering,s.appPagesBrowser],bundled:[s.reactServerComponents,s.actionBrowser,s.serverSideRendering,s.appPagesBrowser,s.shared,s.instrument,s.middleware],appPages:[s.reactServerComponents,s.serverSideRendering,s.appPagesBrowser,s.actionBrowser]}});let d=require("next/dist/server/lib/trace/tracer");var u=/*#__PURE__*/function(e){return e.handleRequest="BaseServer.handleRequest",e.run="BaseServer.run",e.pipe="BaseServer.pipe",e.getStaticHTML="BaseServer.getStaticHTML",e.render="BaseServer.render",e.renderToResponseWithComponents="BaseServer.renderToResponseWithComponents",e.renderToResponse="BaseServer.renderToResponse",e.renderToHTML="BaseServer.renderToHTML",e.renderError="BaseServer.renderError",e.renderErrorToResponse="BaseServer.renderErrorToResponse",e.renderErrorToHTML="BaseServer.renderErrorToHTML",e.render404="BaseServer.render404",e}(u||{}),l=/*#__PURE__*/function(e){return e.loadDefaultErrorComponents="LoadComponents.loadDefaultErrorComponents",e.loadComponents="LoadComponents.loadComponents",e}(l||{}),p=/*#__PURE__*/function(e){return e.getRequestHandler="NextServer.getRequestHandler",e.getServer="NextServer.getServer",e.getServerRequestHandler="NextServer.getServerRequestHandler",e.createServer="createServer.createServer",e}(p||{}),c=/*#__PURE__*/function(e){return e.compression="NextNodeServer.compression",e.getBuildId="NextNodeServer.getBuildId",e.createComponentTree="NextNodeServer.createComponentTree",e.clientComponentLoading="NextNodeServer.clientComponentLoading",e.getLayoutOrPageModule="NextNodeServer.getLayoutOrPageModule",e.generateStaticRoutes="NextNodeServer.generateStaticRoutes",e.generateFsStaticRoutes="NextNodeServer.generateFsStaticRoutes",e.generatePublicRoutes="NextNodeServer.generatePublicRoutes",e.generateImageRoutes="NextNodeServer.generateImageRoutes.route",e.sendRenderResult="NextNodeServer.sendRenderResult",e.proxyRequest="NextNodeServer.proxyRequest",e.runApi="NextNodeServer.runApi",e.render="NextNodeServer.render",e.renderHTML="NextNodeServer.renderHTML",e.imageOptimizer="NextNodeServer.imageOptimizer",e.getPagePath="NextNodeServer.getPagePath",e.getRoutesManifest="NextNodeServer.getRoutesManifest",e.findPageComponents="NextNodeServer.findPageComponents",e.getFontManifest="NextNodeServer.getFontManifest",e.getServerComponentManifest="NextNodeServer.getServerComponentManifest",e.getRequestHandler="NextNodeServer.getRequestHandler",e.renderToHTML="NextNodeServer.renderToHTML",e.renderError="NextNodeServer.renderError",e.renderErrorToHTML="NextNodeServer.renderErrorToHTML",e.render404="NextNodeServer.render404",e.startResponse="NextNodeServer.startResponse",e.route="route",e.onProxyReq="onProxyReq",e.apiResolver="apiResolver",e.internalFetch="internalFetch",e}(c||{}),f=/*#__PURE__*/function(e){return e.startServer="startServer.startServer",e}(f||{}),g=/*#__PURE__*/function(e){return e.getServerSideProps="Render.getServerSideProps",e.getStaticProps="Render.getStaticProps",e.renderToString="Render.renderToString",e.renderDocument="Render.renderDocument",e.createBodyResult="Render.createBodyResult",e}(g||{}),v=/*#__PURE__*/function(e){return e.renderToString="AppRender.renderToString",e.renderToReadableStream="AppRender.renderToReadableStream",e.getBodyResult="AppRender.getBodyResult",e.fetch="AppRender.fetch",e}(v||{}),m=/*#__PURE__*/function(e){return e.executeRoute="Router.executeRoute",e}(m||{}),h=/*#__PURE__*/function(e){return e.runHandler="Node.runHandler",e}(h||{}),y=/*#__PURE__*/function(e){return e.runHandler="AppRouteRouteHandlers.runHandler",e}(y||{}),b=/*#__PURE__*/function(e){return e.generateMetadata="ResolveMetadata.generateMetadata",e.generateViewport="ResolveMetadata.generateViewport",e}(b||{}),x=/*#__PURE__*/function(e){return e.execute="Middleware.execute",e}(x||{});let w="__prerender_bypass",S="__next_preview_data",R=Symbol(S),_=Symbol(w);function E(e,r={}){if(_ in e)return e;let{serialize:n}=t("./dist/compiled/cookie/index.js"),o=e.getHeader("Set-Cookie");return e.setHeader("Set-Cookie",[..."string"==typeof o?[o]:Array.isArray(o)?o:[],n(w,"",{expires:new Date(0),httpOnly:!0,sameSite:"none",secure:!0,path:"/",...void 0!==r.path?{path:r.path}:void 0}),n(S,"",{expires:new Date(0),httpOnly:!0,sameSite:"none",secure:!0,path:"/",...void 0!==r.path?{path:r.path}:void 0})]),Object.defineProperty(e,_,{value:!0,enumerable:!1}),e}class O extends Error{constructor(e,r){super(r),this.statusCode=e}}function C(e,r,t){e.statusCode=r,e.statusMessage=t,e.end(t)}function N({req:e},r,t){let n={configurable:!0,enumerable:!0},o={...n,writable:!0};Object.defineProperty(e,r,{...n,get:()=>{let n=t();return Object.defineProperty(e,r,{...o,value:n}),n},set:t=>{Object.defineProperty(e,r,{...o,value:t})}})}class j{constructor({userland:e,definition:r}){this.userland=e,this.definition=r}}var T=t("./dist/compiled/bytes/index.js"),P=/*#__PURE__*/t.n(T);let A=e=>{let r=e.length,t=0,n=0,o=8997,i=0,a=33826,s=0,d=40164,u=0,l=52210;for(;t>>16,o=65535&n,s+=i>>>16,a=65535&i,l=u+(s>>>16)&65535,d=65535&s;return(15&l)*0x1000000000000+0x100000000*d+65536*a+(o^l>>4)},H=(e,r=!1)=>(r?'W/"':'"')+A(e).toString(36)+e.length.toString(36)+'"';"undefined"!=typeof performance&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);var M=t("./dist/compiled/fresh/index.js"),k=/*#__PURE__*/t.n(M);let B=require("stream");function L(e){return"object"==typeof e&&null!==e&&"name"in e&&"message"in e}var D=t("./dist/compiled/@edge-runtime/cookies/index.js"),$=t("./dist/compiled/content-type/index.js");async function q(e,r){let n,o;try{n=(0,$.parse)(e.headers["content-type"]||"text/plain")}catch{n=(0,$.parse)("text/plain")}let{type:i,parameters:a}=n,s=a.charset||"utf-8";try{let n=t("next/dist/compiled/raw-body");o=await n(e,{encoding:s,limit:r})}catch(e){if(L(e)&&"entity.too.large"===e.type)throw Object.defineProperty(new O(413,\`Body exceeded \${r} limit\`),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0});throw Object.defineProperty(new O(400,"Invalid body"),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0})}let d=o.toString();return"application/json"===i||"application/ld+json"===i?function(e){if(0===e.length)return{};try{return JSON.parse(e)}catch(e){throw Object.defineProperty(new O(400,"Invalid JSON"),"__NEXT_ERROR_CODE",{value:"E394",enumerable:!1,configurable:!0})}}(d):"application/x-www-form-urlencoded"===i?t("querystring").decode(d):d}function I(e){return"string"==typeof e&&e.length>=16}async function K(e,r,t,n){if("string"!=typeof e||!e.startsWith("/"))throw Object.defineProperty(Error(\`Invalid urlPath provided to revalidate(), must be a path e.g. /blog/post-1, received \${e}\`),"__NEXT_ERROR_CODE",{value:"E153",enumerable:!1,configurable:!0});let o={[i]:n.previewModeId,...r.unstable_onlyGenerated?{[a]:"1"}:{}},s=[...n.allowedRevalidateHeaderKeys||[]];for(let e of((n.trustHostHeader||n.dev)&&s.push("cookie"),n.trustHostHeader&&s.push("x-vercel-protection-bypass"),Object.keys(t.headers)))s.includes(e)&&(o[e]=t.headers[e]);try{if(n.trustHostHeader){let n=await (await import("@opennextjs/cloudflare")).getCloudflareContext().env.WORKER_SELF_REFERENCE.fetch(\`\${t.headers.host.includes("localhost") ? "http":"https" }://\${t.headers.host}\${e}\`,{method:'HEAD', headers:o}),i=n.headers.get("x-vercel-cache")||n.headers.get("x-nextjs-cache");if((null==i?void 0:i.toUpperCase())!=="REVALIDATED"&&200!==n.status&&!(404===n.status&&r.unstable_onlyGenerated))throw Object.defineProperty(Error(\`Invalid response \${n.status}\`),"__NEXT_ERROR_CODE",{value:"E175",enumerable:!1,configurable:!0})}else if(n.revalidate)await n.revalidate({urlPath:e,revalidateHeaders:o,opts:r});else throw Object.defineProperty(Error("Invariant: required internal revalidate method not passed to api-utils"),"__NEXT_ERROR_CODE",{value:"E174",enumerable:!1,configurable:!0})}catch(r){throw Object.defineProperty(Error(\`Failed to revalidate \${e}: \${L(r)?r.message:r}\`),"__NEXT_ERROR_CODE",{value:"E240",enumerable:!1,configurable:!0})}}async function X(e,r,n,s,d,u,l,p,c){try{var f,g,v,m;if(!s){r.statusCode=404,r.end("Not Found");return}let u=s.config||{},l=(null==(f=u.api)?void 0:f.bodyParser)!==!1,p=(null==(g=u.api)?void 0:g.responseLimit)??!0;null==(v=u.api)||v.externalResolver,N({req:e},"cookies",(m=e.headers,function(){let{cookie:e}=m;if(!e)return{};let{parse:r}=t("./dist/compiled/cookie/index.js");return r(Array.isArray(e)?e.join("; "):e)})),e.query=n,N({req:e},"previewData",()=>(function(e,r,n,s){var d,u;let l;if(n&&function(e,r){let t=o.from(e.headers);return{isOnDemandRevalidate:t.get(i)===r.previewModeId,revalidateOnlyGenerated:t.has(a)}}(e,n).isOnDemandRevalidate)return!1;if(R in e)return e[R];let p=o.from(e.headers),c=new D.RequestCookies(p),f=null==(d=c.get(w))?void 0:d.value,g=null==(u=c.get(S))?void 0:u.value;if(f&&!g&&f===n.previewModeId){let r={};return Object.defineProperty(e,R,{value:r,enumerable:!1}),r}if(!f&&!g)return!1;if(!f||!g||f!==n.previewModeId)return s||E(r),!1;try{l=t("next/dist/compiled/jsonwebtoken").verify(g,n.previewModeSigningKey)}catch{return E(r),!1}let{decryptWithSecret:v}=t("./dist/esm/server/crypto-utils.js"),m=v(Buffer.from(n.previewModeEncryptionKey),l.data);try{let r=JSON.parse(m);return Object.defineProperty(e,R,{value:r,enumerable:!1}),r}catch{return!1}})(e,r,d,!!d.multiZoneDraftMode)),N({req:e},"preview",()=>!1!==e.previewData||void 0),N({req:e},"draftMode",()=>e.preview),l&&!e.body&&(e.body=await q(e,u.api&&u.api.bodyParser&&u.api.bodyParser.sizeLimit?u.api.bodyParser.sizeLimit:"1mb"));let c=0,h=p&&"boolean"!=typeof p?P().parse(p):4194304,y=r.write,b=r.end;r.write=(...e)=>(c+=Buffer.byteLength(e[0]||""),y.apply(r,e)),r.end=(...t)=>(t.length&&"function"!=typeof t[0]&&(c+=Buffer.byteLength(t[0]||"")),p&&c>=h&&console.warn(\`API response for \${e.url} exceeds \${P().format(h)}. API Routes are meant to respond quickly. https://nextjs.org/docs/messages/api-routes-response-size-limit\`),b.apply(r,t)),r.status=e=>(r.statusCode=e,r),r.send=t=>(function(e,r,t){var n;if(null==t){r.end();return}if(204===r.statusCode||304===r.statusCode){r.removeHeader("Content-Type"),r.removeHeader("Content-Length"),r.removeHeader("Transfer-Encoding"),r.end();return}let o=r.getHeader("Content-Type");if(t instanceof B.Stream){o||r.setHeader("Content-Type","application/octet-stream"),t.pipe(r);return}let i=["object","number","boolean"].includes(typeof t),a=i?JSON.stringify(t):t;if((n=H(a))&&r.setHeader("ETag",n),!k()(e.headers,{etag:n})||(r.statusCode=304,r.end(),0)){if(Buffer.isBuffer(t)){o||r.setHeader("Content-Type","application/octet-stream"),r.setHeader("Content-Length",t.length),r.end(t);return}i&&r.setHeader("Content-Type","application/json; charset=utf-8"),r.setHeader("Content-Length",Buffer.byteLength(a)),r.end(a)}})(e,r,t),r.json=e=>{r.setHeader("Content-Type","application/json; charset=utf-8"),r.send(JSON.stringify(e))},r.redirect=(e,t)=>(function(e,r,t){if("string"==typeof r&&(t=r,r=307),"number"!=typeof r||"string"!=typeof t)throw Object.defineProperty(Error("Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination')."),"__NEXT_ERROR_CODE",{value:"E389",enumerable:!1,configurable:!0});return e.writeHead(r,{Location:t}),e.write(t),e.end(),e})(r,e,t),r.setDraftMode=(e={enable:!0})=>(function(e,r){if(!I(r.previewModeId))throw Object.defineProperty(Error("invariant: invalid previewModeId"),"__NEXT_ERROR_CODE",{value:"E169",enumerable:!1,configurable:!0});let n=r.enable?void 0:new Date(0),{serialize:o}=t("./dist/compiled/cookie/index.js"),i=e.getHeader("Set-Cookie");return e.setHeader("Set-Cookie",[..."string"==typeof i?[i]:Array.isArray(i)?i:[],o(w,r.previewModeId,{httpOnly:!0,sameSite:"none",secure:!0,path:"/",expires:n})]),e})(r,Object.assign({},d,e)),r.setPreviewData=(e,n={})=>(function(e,r,n){if(!I(n.previewModeId))throw Object.defineProperty(Error("invariant: invalid previewModeId"),"__NEXT_ERROR_CODE",{value:"E169",enumerable:!1,configurable:!0});if(!I(n.previewModeEncryptionKey))throw Object.defineProperty(Error("invariant: invalid previewModeEncryptionKey"),"__NEXT_ERROR_CODE",{value:"E334",enumerable:!1,configurable:!0});if(!I(n.previewModeSigningKey))throw Object.defineProperty(Error("invariant: invalid previewModeSigningKey"),"__NEXT_ERROR_CODE",{value:"E436",enumerable:!1,configurable:!0});let o=t("next/dist/compiled/jsonwebtoken"),{encryptWithSecret:i}=t("./dist/esm/server/crypto-utils.js"),a=o.sign({data:i(Buffer.from(n.previewModeEncryptionKey),JSON.stringify(r))},n.previewModeSigningKey,{algorithm:"HS256",...void 0!==n.maxAge?{expiresIn:n.maxAge}:void 0});if(a.length>2048)throw Object.defineProperty(Error("Preview data is limited to 2KB currently, reduce how much data you are storing as preview data to continue"),"__NEXT_ERROR_CODE",{value:"E465",enumerable:!1,configurable:!0});let{serialize:s}=t("./dist/compiled/cookie/index.js"),d=e.getHeader("Set-Cookie");return e.setHeader("Set-Cookie",[..."string"==typeof d?[d]:Array.isArray(d)?d:[],s(w,n.previewModeId,{httpOnly:!0,sameSite:"none",secure:!0,path:"/",...void 0!==n.maxAge?{maxAge:n.maxAge}:void 0,...void 0!==n.path?{path:n.path}:void 0}),s(S,a,{httpOnly:!0,sameSite:"none",secure:!0,path:"/",...void 0!==n.maxAge?{maxAge:n.maxAge}:void 0,...void 0!==n.path?{path:n.path}:void 0})]),e})(r,e,Object.assign({},d,n)),r.clearPreviewData=(e={})=>E(r,e),r.revalidate=(r,t)=>K(r,t||{},e,d);let x=s.default||s;await x(e,r)}catch(t){if(null==c||c(t,e,{routerKind:"Pages Router",routePath:p||"",routeType:"route",revalidateReason:void 0}),t instanceof O)C(r,t.statusCode,t.message);else{if(l)throw L(t)&&(t.page=p),t;if(console.error(t),u)throw t;C(r,500,"Internal Server Error")}}}class z extends j{constructor(e){if(super(e),"function"!=typeof e.userland.default)throw Object.defineProperty(Error(\`Page \${e.definition.page} does not export a default function.\`),"__NEXT_ERROR_CODE",{value:"E379",enumerable:!1,configurable:!0});this.apiResolverWrapped=function(e,r){return(...t)=>((0,d.getTracer)().setRootSpanAttribute("next.route",e),(0,d.getTracer)().trace(h.runHandler,{spanName:\`executing api route (pages) \${e}\`},()=>r(...t)))}(e.definition.page,X)}async render(e,r,t){let{apiResolverWrapped:n}=this;await n(e,r,t.query,this.userland,{...t.previewProps,revalidate:t.revalidate,trustHostHeader:t.trustHostHeader,allowedRevalidateHeaderKeys:t.allowedRevalidateHeaderKeys,hostname:t.hostname,multiZoneDraftMode:t.multiZoneDraftMode,dev:t.dev},t.minimalMode,t.dev,t.page,t.onError)}}let U=z})(),module.exports=n})();"` ); }); @@ -111,7 +111,7 @@ describe("patchResRevalidate", () => { } try { if (context.trustHostHeader) { - const res = await (await import("@opennextjs/cloudflare")).getCloudflareContext().env.NEXT_CACHE_REVALIDATION_WORKER.fetch(\`\${req.headers.host.includes("localhost") ? "http":"https" }://\${req.headers.host}\${urlPath}\`,{method:'HEAD', headers:revalidateHeaders}); + const res = await (await import("@opennextjs/cloudflare")).getCloudflareContext().env.WORKER_SELF_REFERENCE.fetch(\`\${req.headers.host.includes("localhost") ? "http":"https" }://\${req.headers.host}\${urlPath}\`,{method:'HEAD', headers:revalidateHeaders}); // we use the cache header to determine successful revalidate as // a non-200 status code can be returned from a successful revalidate // e.g. notFound: true returns 404 status code but is successful diff --git a/packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.ts b/packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.ts index 3f2f11b4..ed942255 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/res-revalidate.ts @@ -1,7 +1,7 @@ /** * This patch will replace code used for `res.revalidate` in page router * Without the patch it uses `fetch` to make a call to itself, which doesn't work once deployed in cloudflare workers - * This patch will replace this fetch by a call to `NEXT_CACHE_REVALIDATION_WORKER` service binding + * This patch will replace this fetch by a call to `WORKER_SELF_REFERENCE` service binding */ import { patchCode } from "@opennextjs/aws/build/patch/astCodePatcher.js"; import type { CodePatcher } from "@opennextjs/aws/build/patch/codePatcher.js"; @@ -16,16 +16,16 @@ rule: has: kind: parenthesized_expression has: { kind: property_identifier , stopBy: end, regex: trustHostHeader} - has: + has: kind: call_expression all: - has: {kind: identifier, pattern: fetch} - has: kind: arguments - all: + all: - has: kind: object - all: + all: - has: kind: pair all: @@ -50,7 +50,7 @@ rule: kind: property_identifier regex: headers stopBy: end - - has: + - has: kind: property_identifier regex: host stopBy: end @@ -59,8 +59,8 @@ rule: pattern: $URL_PATH has: kind: identifier - -fix: await (await import("@opennextjs/cloudflare")).getCloudflareContext().env.NEXT_CACHE_REVALIDATION_WORKER.fetch(\`\${$REQ.headers.host.includes("localhost") ? "http":"https" }://\${$REQ.headers.host}$URL_PATH\`,{method:'HEAD', headers:$HEADERS}) + +fix: await (await import("@opennextjs/cloudflare")).getCloudflareContext().env.WORKER_SELF_REFERENCE.fetch(\`\${$REQ.headers.host.includes("localhost") ? "http":"https" }://\${$REQ.headers.host}$URL_PATH\`,{method:'HEAD', headers:$HEADERS}) `; export const patchResRevalidate: CodePatcher = { diff --git a/packages/cloudflare/src/cli/deploy/deploy.ts b/packages/cloudflare/src/cli/commands/deploy.ts similarity index 89% rename from packages/cloudflare/src/cli/deploy/deploy.ts rename to packages/cloudflare/src/cli/commands/deploy.ts index 81e2a963..1343946c 100644 --- a/packages/cloudflare/src/cli/deploy/deploy.ts +++ b/packages/cloudflare/src/cli/commands/deploy.ts @@ -1,8 +1,8 @@ import { BuildOptions } from "@opennextjs/aws/build/helper.js"; import { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; -import { populateCache } from "../populate-cache/populate-cache.js"; import { getWranglerEnvironmentFlag, runWrangler } from "../utils/run-wrangler.js"; +import { populateCache } from "./populate-cache.js"; export async function deploy( options: BuildOptions, diff --git a/packages/cloudflare/src/cli/populate-cache/populate-cache.ts b/packages/cloudflare/src/cli/commands/populate-cache.ts similarity index 89% rename from packages/cloudflare/src/cli/populate-cache/populate-cache.ts rename to packages/cloudflare/src/cli/commands/populate-cache.ts index 5149887e..0dea972e 100644 --- a/packages/cloudflare/src/cli/populate-cache/populate-cache.ts +++ b/packages/cloudflare/src/cli/commands/populate-cache.ts @@ -64,8 +64,8 @@ export async function populateCache( const assets = getCacheAssetPaths(options); assets.forEach(({ fsPath, destPath }) => { const fullDestPath = path.join( - "NEXT_CACHE_R2_BUCKET", - process.env.NEXT_CACHE_R2_PREFIX ?? "incremental-cache", + "NEXT_INC_CACHE_R2_BUCKET", + process.env.NEXT_INC_CACHE_R2_PREFIX ?? "incremental-cache", destPath ); @@ -90,14 +90,13 @@ export async function populateCache( switch (name) { case "d1-next-mode-tag-cache": { logger.info("\nCreating D1 table if necessary..."); - const revalidationsTable = process.env.NEXT_CACHE_D1_REVALIDATIONS_TABLE || "revalidations"; runWrangler( options, [ "d1 execute", - "NEXT_CACHE_D1", - `--command "CREATE TABLE IF NOT EXISTS ${JSON.stringify(revalidationsTable)} (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);"`, + "NEXT_TAG_CACHE_D1", + `--command "CREATE TABLE IF NOT EXISTS revalidations (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);"`, ], { ...populateCacheOptions, logging: "error" } ); @@ -112,7 +111,7 @@ export async function populateCache( options, [ "d1 execute", - "NEXT_CACHE_D1", + "NEXT_TAG_CACHE_D1", `--file ${JSON.stringify(path.join(options.outputDir, "cloudflare/cache-assets-manifest.sql"))}`, ], { ...populateCacheOptions, logging: "error" } diff --git a/packages/cloudflare/src/cli/preview/preview.ts b/packages/cloudflare/src/cli/commands/preview.ts similarity index 89% rename from packages/cloudflare/src/cli/preview/preview.ts rename to packages/cloudflare/src/cli/commands/preview.ts index 0963f879..3a8aa246 100644 --- a/packages/cloudflare/src/cli/preview/preview.ts +++ b/packages/cloudflare/src/cli/commands/preview.ts @@ -1,8 +1,8 @@ import { BuildOptions } from "@opennextjs/aws/build/helper.js"; import { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; -import { populateCache } from "../populate-cache/populate-cache.js"; import { getWranglerEnvironmentFlag, runWrangler } from "../utils/run-wrangler.js"; +import { populateCache } from "./populate-cache.js"; export async function preview( options: BuildOptions, diff --git a/packages/cloudflare/src/cli/index.ts b/packages/cloudflare/src/cli/index.ts index 788f0969..128e4878 100644 --- a/packages/cloudflare/src/cli/index.ts +++ b/packages/cloudflare/src/cli/index.ts @@ -10,9 +10,9 @@ import logger from "@opennextjs/aws/logger.js"; import { Arguments, getArgs } from "./args.js"; import { build } from "./build/build.js"; import { createOpenNextConfigIfNotExistent, ensureCloudflareConfig } from "./build/utils/index.js"; -import { deploy } from "./deploy/deploy.js"; -import { populateCache } from "./populate-cache/populate-cache.js"; -import { preview } from "./preview/preview.js"; +import { deploy } from "./commands/deploy.js"; +import { populateCache } from "./commands/populate-cache.js"; +import { preview } from "./commands/preview.js"; const nextAppDir = process.cwd(); diff --git a/packages/cloudflare/templates/open-next.config.ts b/packages/cloudflare/templates/open-next.config.ts index a62dbe76..0714ae29 100644 --- a/packages/cloudflare/templates/open-next.config.ts +++ b/packages/cloudflare/templates/open-next.config.ts @@ -1,6 +1,6 @@ // default open-next.config.ts file created by @opennextjs/cloudflare import { defineCloudflareConfig } from "@opennextjs/cloudflare/config"; -import kvIncrementalCache from "@opennextjs/cloudflare/kv-cache"; +import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache"; export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, diff --git a/packages/cloudflare/templates/wrangler.jsonc b/packages/cloudflare/templates/wrangler.jsonc index c7036e0e..edab74a4 100644 --- a/packages/cloudflare/templates/wrangler.jsonc +++ b/packages/cloudflare/templates/wrangler.jsonc @@ -9,10 +9,10 @@ "binding": "ASSETS" }, "kv_namespaces": [ - // Create a KV binding with the binding name "NEXT_CACHE_WORKERS_KV" + // Create a KV binding with the binding name "NEXT_INC_CACHE_KV" // to enable the KV based caching: // { - // "binding": "NEXT_CACHE_WORKERS_KV", + // "binding": "NEXT_INC_CACHE_KV", // "id": "" // } ]