From 4ca6955caa5fe0538041109022d244b358c109c0 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 22 May 2025 10:46:05 +0200 Subject: [PATCH 01/19] default initial implementation of an external middleware --- .../composite/deploy-cloudflare/action.yaml | 21 ++- .../openNext/customWorkers/default.js | 12 ++ .../customWorkers/defaultWrangler.jsonc | 100 +++++++++++ .../openNext/customWorkers/middleware.js | 31 ++++ .../customWorkers/middlewareWrangler.jsonc | 168 ++++++++++++++++++ packages/gitbook-v2/package.json | 2 + 6 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 packages/gitbook-v2/openNext/customWorkers/default.js create mode 100644 packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc create mode 100644 packages/gitbook-v2/openNext/customWorkers/middleware.js create mode 100644 packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index fbc98fc82f..290beb5622 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -63,8 +63,8 @@ runs: env: GITBOOK_RUNTIME: cloudflare shell: bash - - id: deploy - name: Deploy to Cloudflare + - id: upload_middleware + name: Upload middleware to Cloudflare uses: cloudflare/wrangler-action@v3.14.0 with: apiToken: ${{ inputs.apiToken }} @@ -72,10 +72,21 @@ runs: workingDirectory: ./ wranglerVersion: '4.10.0' environment: ${{ inputs.environment }} - command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/wrangler.jsonc + command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc + - id: upload_server + name: Upload server to Cloudflare + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc + - name: Outputs shell: bash env: - DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }} + DEPLOYMENT_URL: ${{ steps.upload_middleware.outputs.deployment-url }} run: | - echo "URL: ${{ steps.deploy.outputs.deployment-url }}" \ No newline at end of file + echo "URL: ${{ steps.upload_middleware.outputs.deployment-url }}" \ No newline at end of file diff --git a/packages/gitbook-v2/openNext/customWorkers/default.js b/packages/gitbook-v2/openNext/customWorkers/default.js new file mode 100644 index 0000000000..941cadedbb --- /dev/null +++ b/packages/gitbook-v2/openNext/customWorkers/default.js @@ -0,0 +1,12 @@ +import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; + +import { handler } from '../../.open-next/server-functions/default/handler.mjs'; + +export default { + async fetch(request, env, ctx) { + return runWithCloudflareRequestContext(request, env, ctx, async () => { + // - `Request`s are handled by the Next server + return handler(request, env, ctx); + }); + }, +}; diff --git a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc new file mode 100644 index 0000000000..eeca40b3df --- /dev/null +++ b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc @@ -0,0 +1,100 @@ +{ + "main": "default.js", + "name": "gitbook-open-v2-server", + "compatibility_date": "2025-04-14", + "compatibility_flags": [ + "nodejs_compat", + "allow_importable_env", + "global_fetch_strictly_public" + ], + "observability": { + "enabled": true + }, + "vars": { + "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true", + "IS_PREVIEW": "false" + }, + "env": { + "preview": { + "vars": { + "IS_PREVIEW": "true" + }, + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "gitbook-open-v2-cache-preview" + } + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "gitbook-open-v2-server-preview" + }, + { + "binding": "GITBOOK_API", + "service": "gitbook-x-prod-api-cache" + }, + { + "binding": "MIDDLEWARE_REFERENCE", + "service": "gitbook-open-v2-preview" + } + ] + // No durable objects on preview, as they block the generation of preview URLs + // and we don't need tags invalidation on preview + }, + "staging": { + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "gitbook-open-v2-cache-staging" + } + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "gitbook-open-v2-server-staging" + }, + { + "binding": "GITBOOK_API", + "service": "gitbook-x-staging-api-cache" + }, + { + "binding": "MIDDLEWARE_REFERENCE", + "service": "gitbook-open-v2-staging" + } + ], + "tail_consumers": [ + { + "service": "gitbook-x-staging-tail" + } + ] + }, + "production": { + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "gitbook-open-v2-cache-production" + } + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "gitbook-open-v2-server-production" + }, + { + "binding": "GITBOOK_API", + "service": "gitbook-x-prod-api-cache" + }, + { + "binding": "MIDDLEWARE_REFERENCE", + "service": "gitbook-open-v2-production" + } + ], + "tail_consumers": [ + { + "service": "gitbook-x-prod-tail" + } + ] + } + } +} diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js new file mode 100644 index 0000000000..67ae8a38b7 --- /dev/null +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -0,0 +1,31 @@ +import { WorkerEntrypoint } from 'cloudflare:workers'; +import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; + +import { handler as middlewareHandler } from '../../.open-next/middleware/handler.mjs'; + +export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js'; + +export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js'; + +export default class extends WorkerEntrypoint { + async fetch(request) { + return runWithCloudflareRequestContext(request, this.env, this.ctx, async () => { + // - `Request`s are handled by the Next server + const reqOrResp = await middlewareHandler(request, this.env, this.ctx); + if (reqOrResp instanceof Response) { + return reqOrResp; + } + + // If it is a `Request`, we need to send it to the Next server + const nextRequest = reqOrResp; + + return this.env.DEFAULT_WORKER?.fetch(nextRequest, { + cf: { + cacheEverything: false, + }, + }); + }); + } + + //TODO: Add methods for the DO queue and sharded tag cache so that they can be used in the main function through service bindings +} diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc new file mode 100644 index 0000000000..2a67154ff0 --- /dev/null +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -0,0 +1,168 @@ +{ + "main": "middleware.js", + "name": "gitbook-open-v2", + "compatibility_date": "2025-04-14", + "compatibility_flags": [ + "nodejs_compat", + "allow_importable_env", + "global_fetch_strictly_public" + ], + "assets": { + "directory": "../../.open-next/assets", + "binding": "ASSETS" + }, + "observability": { + "enabled": true + }, + "vars": { + "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true", + "IS_PREVIEW": "false" + }, + "env": { + "preview": { + "vars": { + "IS_PREVIEW": "true" + }, + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "gitbook-open-v2-cache-preview" + } + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "gitbook-open-v2-preview" + }, + { + "binding": "GITBOOK_API", + "service": "gitbook-x-prod-api-cache" + }, + { + "binding": "DEFAULT_WORKER", + "service": "gitbook-open-v2-server-preview" + } + ] + // No durable objects on preview, as they block the generation of preview URLs + // and we don't need tags invalidation on preview + }, + "staging": { + "routes": [ + { + "pattern": "open-2c.gitbook-staging.com/*", + "zone_name": "gitbook-staging.com" + }, + { + "pattern": "static-2c.gitbook-staging.com/*", + "zone_name": "gitbook-staging.com" + } + ], + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "gitbook-open-v2-cache-staging" + } + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "gitbook-open-v2-staging" + }, + { + "binding": "GITBOOK_API", + "service": "gitbook-x-staging-api-cache" + }, + { + "binding": "DEFAULT_WORKER", + "service": "gitbook-open-v2-server-staging" + } + ], + "tail_consumers": [ + { + "service": "gitbook-x-staging-tail" + } + ], + "durable_objects": { + "bindings": [ + { + "name": "NEXT_CACHE_DO_QUEUE", + "class_name": "DOQueueHandler" + }, + { + "name": "NEXT_TAG_CACHE_DO_SHARDED", + "class_name": "DOShardedTagCache" + } + ] + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"] + } + ] + }, + "production": { + "vars": { + // This is a bit misleading, but it means that we can have 500 concurrent revalidations + // This means that we'll have up to 100 durable objects instance running at the same time + "MAX_REVALIDATE_CONCURRENCY": "100", + // Temporary variable to find the issue once deployed + // TODO: remove this once the issue is fixed + "DEBUG_CLOUDFLARE": "true" + }, + "routes": [ + { + "pattern": "open-2c.gitbook.com/*", + "zone_name": "gitbook.com" + }, + { + "pattern": "static-2c.gitbook.com/*", + "zone_name": "gitbook.com" + } + ], + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "gitbook-open-v2-cache-production" + } + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "gitbook-open-v2-production" + }, + { + "binding": "GITBOOK_API", + "service": "gitbook-x-prod-api-cache" + }, + { + "binding": "DEFAULT_WORKER", + "service": "gitbook-open-v2-server-production" + } + ], + "tail_consumers": [ + { + "service": "gitbook-x-prod-tail" + } + ], + "durable_objects": { + "bindings": [ + { + "name": "NEXT_CACHE_DO_QUEUE", + "class_name": "DOQueueHandler" + }, + { + "name": "NEXT_TAG_CACHE_DO_SHARDED", + "class_name": "DOShardedTagCache" + } + ] + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"] + } + ] + } + } +} diff --git a/packages/gitbook-v2/package.json b/packages/gitbook-v2/package.json index 5561b7f8d3..582739608f 100644 --- a/packages/gitbook-v2/package.json +++ b/packages/gitbook-v2/package.json @@ -30,6 +30,8 @@ "start": "next start", "build:v2:cloudflare": "opennextjs-cloudflare build", "dev:v2:cloudflare": "wrangler dev --port 8771 --env preview", + "dev:v2:cf:middleware": "wrangler dev --port 8771 --env preview --config ./openNext/customWorkers/middlewareWrangler.jsonc", + "dev:v2:cf:server": "wrangler dev --port 8772 --env preview --config ./openNext/customWorkers/defaultWrangler.jsonc", "unit": "bun test", "typecheck": "tsc --noEmit" } From 93a5ffb61b27a272e952f05ccbc9202032de3584 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 22 May 2025 13:40:38 +0200 Subject: [PATCH 02/19] force deploy the server first --- .github/composite/deploy-cloudflare/action.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index 290beb5622..bc639aa0f2 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -63,8 +63,8 @@ runs: env: GITBOOK_RUNTIME: cloudflare shell: bash - - id: upload_middleware - name: Upload middleware to Cloudflare + - id: upload_server + name: Upload server to Cloudflare uses: cloudflare/wrangler-action@v3.14.0 with: apiToken: ${{ inputs.apiToken }} @@ -72,9 +72,10 @@ runs: workingDirectory: ./ wranglerVersion: '4.10.0' environment: ${{ inputs.environment }} - command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc - - id: upload_server - name: Upload server to Cloudflare + command: deploy --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc + + - id: upload_middleware + name: Upload middleware to Cloudflare uses: cloudflare/wrangler-action@v3.14.0 with: apiToken: ${{ inputs.apiToken }} @@ -82,8 +83,7 @@ runs: workingDirectory: ./ wranglerVersion: '4.10.0' environment: ${{ inputs.environment }} - command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc - + command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc - name: Outputs shell: bash env: From adaf25dec5e95a2d24fa0dfb5e3e6f55292d0c87 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 22 May 2025 17:25:38 +0200 Subject: [PATCH 03/19] add dev config --- .../customWorkers/defaultWrangler.jsonc | 30 +++++++++++++++++-- .../customWorkers/middlewareWrangler.jsonc | 30 +++++++++++++++++-- packages/gitbook-v2/openNext/queue.ts | 6 ++-- packages/gitbook-v2/package.json | 4 +-- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc index eeca40b3df..08dcdbf1b2 100644 --- a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc @@ -11,13 +11,37 @@ "enabled": true }, "vars": { - "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true", - "IS_PREVIEW": "false" + "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true" }, "env": { + "dev": { + "vars": { + "STAGE": "dev" + }, + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "gitbook-open-v2-cache-preview" + } + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "gitbook-open-v2-server-dev" + }, + { + "binding": "GITBOOK_API", + "service": "gitbook-x-prod-api-cache" + }, + { + "binding": "MIDDLEWARE_REFERENCE", + "service": "gitbook-open-v2-dev" + } + ] + }, "preview": { "vars": { - "IS_PREVIEW": "true" + "STAGE": "preview" }, "r2_buckets": [ { diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc index 2a67154ff0..53422b1ee3 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -15,13 +15,37 @@ "enabled": true }, "vars": { - "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true", - "IS_PREVIEW": "false" + "NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true" }, "env": { + "dev": { + "vars": { + "STAGE": "dev" + }, + "r2_buckets": [ + { + "binding": "NEXT_INC_CACHE_R2_BUCKET", + "bucket_name": "gitbook-open-v2-cache-preview" + } + ], + "services": [ + { + "binding": "WORKER_SELF_REFERENCE", + "service": "gitbook-open-v2-dev" + }, + { + "binding": "GITBOOK_API", + "service": "gitbook-x-prod-api-cache" + }, + { + "binding": "DEFAULT_WORKER", + "service": "gitbook-open-v2-server-dev" + } + ] + }, "preview": { "vars": { - "IS_PREVIEW": "true" + "STAGE": "preview" }, "r2_buckets": [ { diff --git a/packages/gitbook-v2/openNext/queue.ts b/packages/gitbook-v2/openNext/queue.ts index ab33c479d9..cc4cb73287 100644 --- a/packages/gitbook-v2/openNext/queue.ts +++ b/packages/gitbook-v2/openNext/queue.ts @@ -4,14 +4,14 @@ import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue'; import memoryQueue from '@opennextjs/cloudflare/overrides/queue/memory-queue'; interface Env { - IS_PREVIEW?: string; + STAGE?: string; } export default { name: 'GitbookISRQueue', send: async (msg) => { const { ctx, env } = getCloudflareContext(); - const isPreview = (env as Env).IS_PREVIEW === 'true'; - ctx.waitUntil(isPreview ? memoryQueue.send(msg) : doQueue.send(msg)); + const hasDurableObject = (env as Env).STAGE !== 'dev' && (env as Env).STAGE !== 'preview'; + ctx.waitUntil(hasDurableObject ? memoryQueue.send(msg) : doQueue.send(msg)); }, } satisfies Queue; diff --git a/packages/gitbook-v2/package.json b/packages/gitbook-v2/package.json index 582739608f..e8f2e76533 100644 --- a/packages/gitbook-v2/package.json +++ b/packages/gitbook-v2/package.json @@ -30,8 +30,8 @@ "start": "next start", "build:v2:cloudflare": "opennextjs-cloudflare build", "dev:v2:cloudflare": "wrangler dev --port 8771 --env preview", - "dev:v2:cf:middleware": "wrangler dev --port 8771 --env preview --config ./openNext/customWorkers/middlewareWrangler.jsonc", - "dev:v2:cf:server": "wrangler dev --port 8772 --env preview --config ./openNext/customWorkers/defaultWrangler.jsonc", + "dev:v2:cf:middleware": "wrangler dev --port 8771 --env dev --config ./openNext/customWorkers/middlewareWrangler.jsonc", + "dev:v2:cf:server": "wrangler dev --port 8772 --env dev --config ./openNext/customWorkers/defaultWrangler.jsonc", "unit": "bun test", "typecheck": "tsc --noEmit" } From 0aeda5061b535464333f1af102bbabaaed29656b Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 22 May 2025 18:31:20 +0200 Subject: [PATCH 04/19] Only upload and fix for preview --- .../composite/deploy-cloudflare/action.yaml | 23 +++++++++++++++---- .../openNext/customWorkers/middleware.js | 16 +++++++++++-- .../customWorkers/middlewareWrangler.jsonc | 3 ++- .../customWorkers/script/updateWrangler.js | 18 +++++++++++++++ 4 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index bc639aa0f2..c742631e91 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -63,6 +63,7 @@ runs: env: GITBOOK_RUNTIME: cloudflare shell: bash + - id: upload_server name: Upload server to Cloudflare uses: cloudflare/wrangler-action@v3.14.0 @@ -72,8 +73,20 @@ runs: workingDirectory: ./ wranglerVersion: '4.10.0' environment: ${{ inputs.environment }} - command: deploy --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc - + command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc + + - name: Extract version worker ID + shell: bash + id: extract_version_id + run: | + version_id=$(echo '${{ steps.upload_server.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}') + echo "version_id=$version_id" >> $GITHUB_OUTPUT + + - name: Run updateWrangler scripts + shell: bash + run: | + bun run ./packages/gitbook-v2/openNext/script/updateWrangler.ts ${{ steps.extract_version_id.outputs.version_id }} + - id: upload_middleware name: Upload middleware to Cloudflare uses: cloudflare/wrangler-action@v3.14.0 @@ -83,10 +96,12 @@ runs: workingDirectory: ./ wranglerVersion: '4.10.0' environment: ${{ inputs.environment }} - command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc + command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc + - name: Outputs shell: bash env: DEPLOYMENT_URL: ${{ steps.upload_middleware.outputs.deployment-url }} run: | - echo "URL: ${{ steps.upload_middleware.outputs.deployment-url }}" \ No newline at end of file + echo "URL: ${{ steps.upload_middleware.outputs.deployment-url }}" + echo "Output server: ${{ steps.upload_server.outputs.command-output }}" \ No newline at end of file diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index 67ae8a38b7..10b32c902b 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -17,9 +17,21 @@ export default class extends WorkerEntrypoint { } // If it is a `Request`, we need to send it to the Next server - const nextRequest = reqOrResp; + // But we need to send it using the preview URL if we are in preview mode + const nextRequest = + this.env.STAGE === 'preview' + ? new Request(new URL(reqOrResp.url, this.env.PREVIEW_URL), reqOrResp) + : reqOrResp; - return this.env.DEFAULT_WORKER?.fetch(nextRequest, { + if (this.env.STAGE !== 'preview') { + return this.env.DEFAULT_WORKER?.fetch(nextRequest, { + cf: { + cacheEverything: false, + }, + }); + } + // If we are in preview mode, we need to send the request to the preview URL + return fetch(nextRequest, { cf: { cacheEverything: false, }, diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc index 53422b1ee3..469d60a142 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -45,7 +45,8 @@ }, "preview": { "vars": { - "STAGE": "preview" + "STAGE": "preview", + "PREVIEW_URL": "TO_REPLACE" }, "r2_buckets": [ { diff --git a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js new file mode 100644 index 0000000000..c53d989044 --- /dev/null +++ b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js @@ -0,0 +1,18 @@ +// In this script, we use the args from the cli to update the PREVIEW_URL vars in the wrangler config file for the middleware +const fs = require('node:fs'); +const path = require('node:path'); + +const wranglerConfigPath = path.join(__dirname, '../middlewareWrangler.jsonc'); + +const file = fs.readFileSync(wranglerConfigPath, 'utf-8'); + +const args = process.argv.slice(2); +// The versionId is in the format xxx-xxx-xxx-xxx, we need the first part to reconstruct the preview URL +const versionId = args[0]; + +// The preview URL is in the format https://-gitbook-open-v2-preview.gitbook.workers.dev +const previewUrl = `https://${versionId.split('-')[0]}-gitbook-open-v2-preview.gitbook.workers.dev`; + +const updatedFile = file.replace(/"PREVIEW_URL": "TO_REPLACE"/, `"PREVIEW_URL": "${previewUrl}"`); + +fs.writeFileSync(wranglerConfigPath, updatedFile); From 39182c476bdc179e212cd14e904b471166900ad6 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 22 May 2025 18:47:24 +0200 Subject: [PATCH 05/19] fix gh action --- .github/composite/deploy-cloudflare/action.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index c742631e91..a16d51fbb2 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -28,7 +28,7 @@ inputs: outputs: deployment-url: description: "Deployment URL" - value: ${{ steps.deploy.outputs.deployment-url }} + value: ${{ steps.upload_middleware.outputs.deployment-url }} runs: using: 'composite' steps: @@ -75,9 +75,9 @@ runs: environment: ${{ inputs.environment }} command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc - - name: Extract version worker ID + - name: Extract server version worker ID shell: bash - id: extract_version_id + id: extract_server_version_id run: | version_id=$(echo '${{ steps.upload_server.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}') echo "version_id=$version_id" >> $GITHUB_OUTPUT @@ -85,7 +85,7 @@ runs: - name: Run updateWrangler scripts shell: bash run: | - bun run ./packages/gitbook-v2/openNext/script/updateWrangler.ts ${{ steps.extract_version_id.outputs.version_id }} + bun run ./packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js ${{ steps.extract_server_version_id.outputs.version_id }} - id: upload_middleware name: Upload middleware to Cloudflare @@ -98,6 +98,13 @@ runs: environment: ${{ inputs.environment }} command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc + - name: Extract middleware version worker ID + shell: bash + id: extract_middleware_version_id + run: | + version_id=$(echo '${{ steps.upload_middleware.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}') + echo "version_id=$version_id" >> $GITHUB_OUTPUT + - name: Outputs shell: bash env: From 4c3cc1f24c3989d20420324080ac9bf72b9adc48 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 22 May 2025 19:03:38 +0200 Subject: [PATCH 06/19] fix script --- .../openNext/customWorkers/script/updateWrangler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js index c53d989044..2feddaace5 100644 --- a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js +++ b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js @@ -10,8 +10,8 @@ const args = process.argv.slice(2); // The versionId is in the format xxx-xxx-xxx-xxx, we need the first part to reconstruct the preview URL const versionId = args[0]; -// The preview URL is in the format https://-gitbook-open-v2-preview.gitbook.workers.dev -const previewUrl = `https://${versionId.split('-')[0]}-gitbook-open-v2-preview.gitbook.workers.dev`; +// The preview URL is in the format https://-gitbook-open-v2-server-preview.gitbook.workers.dev +const previewUrl = `https://${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`; const updatedFile = file.replace(/"PREVIEW_URL": "TO_REPLACE"/, `"PREVIEW_URL": "${previewUrl}"`); From 4c0c7bcdd823acf0a4f99f42d1f8ab53d4b90ad9 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 22 May 2025 19:35:59 +0200 Subject: [PATCH 07/19] use worker version overrides --- .../composite/deploy-cloudflare/action.yaml | 10 ++++++++++ .../openNext/customWorkers/middleware.js | 20 ++++++------------- .../customWorkers/middlewareWrangler.jsonc | 2 +- .../customWorkers/script/updateWrangler.js | 5 +---- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index a16d51fbb2..7148b2c739 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -98,6 +98,16 @@ runs: environment: ${{ inputs.environment }} command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc + - name: Deploy server at 0% traffic + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ steps.extract_server_version_id.outputs.version_id }}@0% --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc + - name: Extract middleware version worker ID shell: bash id: extract_middleware_version_id diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index 10b32c902b..8507d7f831 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -18,20 +18,12 @@ export default class extends WorkerEntrypoint { // If it is a `Request`, we need to send it to the Next server // But we need to send it using the preview URL if we are in preview mode - const nextRequest = - this.env.STAGE === 'preview' - ? new Request(new URL(reqOrResp.url, this.env.PREVIEW_URL), reqOrResp) - : reqOrResp; - - if (this.env.STAGE !== 'preview') { - return this.env.DEFAULT_WORKER?.fetch(nextRequest, { - cf: { - cacheEverything: false, - }, - }); - } - // If we are in preview mode, we need to send the request to the preview URL - return fetch(nextRequest, { + const nextRequest = reqOrResp; + + // We always pass the version ID so that it always fetch from the correct version + nextRequest.headers.set('Cloudflare-Workers-Version-Overrides', this.env.VERSION_ID); + + return this.env.DEFAULT_WORKER?.fetch(nextRequest, { cf: { cacheEverything: false, }, diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc index 469d60a142..8e999f0f63 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -46,7 +46,7 @@ "preview": { "vars": { "STAGE": "preview", - "PREVIEW_URL": "TO_REPLACE" + "VERSION_ID": "TO_REPLACE" }, "r2_buckets": [ { diff --git a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js index 2feddaace5..474bf29f83 100644 --- a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js +++ b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js @@ -10,9 +10,6 @@ const args = process.argv.slice(2); // The versionId is in the format xxx-xxx-xxx-xxx, we need the first part to reconstruct the preview URL const versionId = args[0]; -// The preview URL is in the format https://-gitbook-open-v2-server-preview.gitbook.workers.dev -const previewUrl = `https://${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`; - -const updatedFile = file.replace(/"PREVIEW_URL": "TO_REPLACE"/, `"PREVIEW_URL": "${previewUrl}"`); +const updatedFile = file.replace(/"VERSION_ID": "TO_REPLACE"/, `"VERSION_ID": "${versionId}"`); fs.writeFileSync(wranglerConfigPath, updatedFile); From a8fe220d4d79c2611160b0d6dc3e15f116c4ba08 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Fri, 23 May 2025 09:26:21 +0200 Subject: [PATCH 08/19] Revert "use worker version overrides" This reverts commit 4c0c7bcdd823acf0a4f99f42d1f8ab53d4b90ad9. --- .../composite/deploy-cloudflare/action.yaml | 10 ---------- .../openNext/customWorkers/middleware.js | 20 +++++++++++++------ .../customWorkers/middlewareWrangler.jsonc | 2 +- .../customWorkers/script/updateWrangler.js | 5 ++++- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index 7148b2c739..a16d51fbb2 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -98,16 +98,6 @@ runs: environment: ${{ inputs.environment }} command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc - - name: Deploy server at 0% traffic - uses: cloudflare/wrangler-action@v3.14.0 - with: - apiToken: ${{ inputs.apiToken }} - accountId: ${{ inputs.accountId }} - workingDirectory: ./ - wranglerVersion: '4.10.0' - environment: ${{ inputs.environment }} - command: versions deploy ${{ steps.extract_server_version_id.outputs.version_id }}@0% --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc - - name: Extract middleware version worker ID shell: bash id: extract_middleware_version_id diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index 8507d7f831..10b32c902b 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -18,12 +18,20 @@ export default class extends WorkerEntrypoint { // If it is a `Request`, we need to send it to the Next server // But we need to send it using the preview URL if we are in preview mode - const nextRequest = reqOrResp; - - // We always pass the version ID so that it always fetch from the correct version - nextRequest.headers.set('Cloudflare-Workers-Version-Overrides', this.env.VERSION_ID); - - return this.env.DEFAULT_WORKER?.fetch(nextRequest, { + const nextRequest = + this.env.STAGE === 'preview' + ? new Request(new URL(reqOrResp.url, this.env.PREVIEW_URL), reqOrResp) + : reqOrResp; + + if (this.env.STAGE !== 'preview') { + return this.env.DEFAULT_WORKER?.fetch(nextRequest, { + cf: { + cacheEverything: false, + }, + }); + } + // If we are in preview mode, we need to send the request to the preview URL + return fetch(nextRequest, { cf: { cacheEverything: false, }, diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc index 8e999f0f63..469d60a142 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -46,7 +46,7 @@ "preview": { "vars": { "STAGE": "preview", - "VERSION_ID": "TO_REPLACE" + "PREVIEW_URL": "TO_REPLACE" }, "r2_buckets": [ { diff --git a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js index 474bf29f83..2feddaace5 100644 --- a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js +++ b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js @@ -10,6 +10,9 @@ const args = process.argv.slice(2); // The versionId is in the format xxx-xxx-xxx-xxx, we need the first part to reconstruct the preview URL const versionId = args[0]; -const updatedFile = file.replace(/"VERSION_ID": "TO_REPLACE"/, `"VERSION_ID": "${versionId}"`); +// The preview URL is in the format https://-gitbook-open-v2-server-preview.gitbook.workers.dev +const previewUrl = `https://${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`; + +const updatedFile = file.replace(/"PREVIEW_URL": "TO_REPLACE"/, `"PREVIEW_URL": "${previewUrl}"`); fs.writeFileSync(wranglerConfigPath, updatedFile); From 971bad2e9362e3d15822dbaf59d6d06f886c70dd Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Fri, 23 May 2025 10:07:59 +0200 Subject: [PATCH 09/19] fix url --- .github/composite/deploy-cloudflare/action.yaml | 2 +- .../gitbook-v2/openNext/customWorkers/middleware.js | 12 ++++-------- .../openNext/customWorkers/middlewareWrangler.jsonc | 2 +- .../script/{updateWrangler.js => updateWrangler.ts} | 11 +++++++---- 4 files changed, 13 insertions(+), 14 deletions(-) rename packages/gitbook-v2/openNext/customWorkers/script/{updateWrangler.js => updateWrangler.ts} (67%) diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index a16d51fbb2..47fe8491d7 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -85,7 +85,7 @@ runs: - name: Run updateWrangler scripts shell: bash run: | - bun run ./packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js ${{ steps.extract_server_version_id.outputs.version_id }} + bun run ./packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts ${{ steps.extract_server_version_id.outputs.version_id }} - id: upload_middleware name: Upload middleware to Cloudflare diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index 10b32c902b..da9669f19a 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -16,21 +16,17 @@ export default class extends WorkerEntrypoint { return reqOrResp; } - // If it is a `Request`, we need to send it to the Next server - // But we need to send it using the preview URL if we are in preview mode - const nextRequest = - this.env.STAGE === 'preview' - ? new Request(new URL(reqOrResp.url, this.env.PREVIEW_URL), reqOrResp) - : reqOrResp; - if (this.env.STAGE !== 'preview') { - return this.env.DEFAULT_WORKER?.fetch(nextRequest, { + return this.env.DEFAULT_WORKER?.fetch(reqOrResp, { cf: { cacheEverything: false, }, }); } // If we are in preview mode, we need to send the request to the preview URL + const modifiedUrl = new URL(reqOrResp.url); + modifiedUrl.hostname = this.env.PREVIEW_HOSTNAME; + const nextRequest = new Request(modifiedUrl, reqOrResp); return fetch(nextRequest, { cf: { cacheEverything: false, diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc index 469d60a142..1d43340f5b 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -46,7 +46,7 @@ "preview": { "vars": { "STAGE": "preview", - "PREVIEW_URL": "TO_REPLACE" + "PREVIEW_HOSTNAME": "TO_REPLACE" }, "r2_buckets": [ { diff --git a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts similarity index 67% rename from packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js rename to packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts index 2feddaace5..024a6dfe3f 100644 --- a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.js +++ b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts @@ -1,6 +1,6 @@ // In this script, we use the args from the cli to update the PREVIEW_URL vars in the wrangler config file for the middleware -const fs = require('node:fs'); -const path = require('node:path'); +import fs from 'node:fs'; +import path from 'node:path'; const wranglerConfigPath = path.join(__dirname, '../middlewareWrangler.jsonc'); @@ -11,8 +11,11 @@ const args = process.argv.slice(2); const versionId = args[0]; // The preview URL is in the format https://-gitbook-open-v2-server-preview.gitbook.workers.dev -const previewUrl = `https://${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`; +const previewHostname = `${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`; -const updatedFile = file.replace(/"PREVIEW_URL": "TO_REPLACE"/, `"PREVIEW_URL": "${previewUrl}"`); +const updatedFile = file.replace( + /"PREVIEW_HOSTNAME": "TO_REPLACE"/, + `"PREVIEW_HOSTNAME": "${previewHostname}"` +); fs.writeFileSync(wranglerConfigPath, updatedFile); From a9769314309b8cf5bf6587c29389828d1bee2426 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Fri, 23 May 2025 15:31:43 +0200 Subject: [PATCH 10/19] make overrides compatible with the external middleware --- packages/gitbook-v2/open-next.config.ts | 53 ++++++++------- .../openNext/customWorkers/default.js | 4 ++ .../openNext/customWorkers/middleware.js | 66 ++++++++++++++++++- .../{queue.ts => queue/middleware.ts} | 0 packages/gitbook-v2/openNext/queue/server.ts | 14 ++++ .../openNext/tagCache/middleware.ts | 18 +++++ .../gitbook-v2/openNext/tagCache/server.ts | 26 ++++++++ 7 files changed, 155 insertions(+), 26 deletions(-) rename packages/gitbook-v2/openNext/{queue.ts => queue/middleware.ts} (100%) create mode 100644 packages/gitbook-v2/openNext/queue/server.ts create mode 100644 packages/gitbook-v2/openNext/tagCache/middleware.ts create mode 100644 packages/gitbook-v2/openNext/tagCache/server.ts diff --git a/packages/gitbook-v2/open-next.config.ts b/packages/gitbook-v2/open-next.config.ts index d35c9ef03e..e85d583c45 100644 --- a/packages/gitbook-v2/open-next.config.ts +++ b/packages/gitbook-v2/open-next.config.ts @@ -1,26 +1,29 @@ -import { defineCloudflareConfig } from '@opennextjs/cloudflare'; -import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache'; -import { - softTagFilter, - withFilter, -} from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter'; +import type { OpenNextConfig } from '@opennextjs/cloudflare'; -export default defineCloudflareConfig({ - incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default), - tagCache: withFilter({ - tagCache: doShardedTagCache({ - baseShardSize: 12, - regionalCache: true, - shardReplication: { - numberOfSoftReplicas: 2, - numberOfHardReplicas: 1, - }, - }), - // We don't use `revalidatePath`, so we filter out soft tags - filterFn: softTagFilter, - }), - queue: () => import('./openNext/queue').then((m) => m.default), - - // Performance improvements as we don't use PPR - enableCacheInterception: true, -}); +export default { + default: { + override: { + wrapper: 'cloudflare-node', + converter: 'edge', + proxyExternalRequest: 'fetch', + queue: () => import('./openNext/queue/server').then((m) => m.default), + incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default), + tagCache: () => import('./openNext/tagCache/server').then((m) => m.default), + }, + }, + middleware: { + external: true, + override: { + wrapper: 'cloudflare-edge', + converter: 'edge', + proxyExternalRequest: 'fetch', + queue: () => import('./openNext/queue/middleware').then((m) => m.default), + incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default), + tagCache: () => import('./openNext/tagCache/middleware').then((m) => m.default), + }, + }, + dangerous: { + enableCacheInterception: true, + }, + edgeExternals: ['node:crypto'], +} satisfies OpenNextConfig; diff --git a/packages/gitbook-v2/openNext/customWorkers/default.js b/packages/gitbook-v2/openNext/customWorkers/default.js index 941cadedbb..5d0961a4f8 100644 --- a/packages/gitbook-v2/openNext/customWorkers/default.js +++ b/packages/gitbook-v2/openNext/customWorkers/default.js @@ -6,6 +6,10 @@ export default { async fetch(request, env, ctx) { return runWithCloudflareRequestContext(request, env, ctx, async () => { // - `Request`s are handled by the Next server + console.log('Request URL:', request); + if (request.url.includes('404')) { + await env.MIDDLEWARE_REFERENCE.send('whatever'); + } return handler(request, env, ctx); }); }, diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index da9669f19a..36a001a6ed 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -1,6 +1,8 @@ import { WorkerEntrypoint } from 'cloudflare:workers'; import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; +import onConfig from '../../.open-next/middleware/open-next.config.mjs'; + import { handler as middlewareHandler } from '../../.open-next/middleware/handler.mjs'; export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js'; @@ -35,5 +37,67 @@ export default class extends WorkerEntrypoint { }); } - //TODO: Add methods for the DO queue and sharded tag cache so that they can be used in the main function through service bindings + /** + * Forwards the message from the server to the DO queue. + */ + async send(message) { + return runWithCloudflareRequestContext( + new Request('http://local'), + this.env, + this.ctx, + async () => { + const queue = await onConfig.middleware.override.queue(); + if (queue) { + return queue.send(message); + } + } + ); + } + + /** + * All the functions below are used to interact with the tag cache. + * They are needed for the server who wouldn't be able to access the tag cache directly. + */ + + async getLastRevalidated() { + return runWithCloudflareRequestContext( + new Request('http://local'), + this.env, + this.ctx, + async () => { + const tagCache = await onConfig.middleware.override.tagCache(); + if (tagCache) { + return tagCache.getLastRevalidated(); + } + } + ); + } + + async hasBeenRevalidated() { + return runWithCloudflareRequestContext( + new Request('http://local'), + this.env, + this.ctx, + async () => { + const tagCache = await onConfig.middleware.override.tagCache(); + if (tagCache) { + return tagCache.hasBeenRevalidated(); + } + } + ); + } + + async writeTags(tags) { + return runWithCloudflareRequestContext( + new Request('http://local'), + this.env, + this.ctx, + async () => { + const tagCache = await onConfig.middleware.override.tagCache(); + if (tagCache) { + return tagCache.writeTags(tags); + } + } + ); + } } diff --git a/packages/gitbook-v2/openNext/queue.ts b/packages/gitbook-v2/openNext/queue/middleware.ts similarity index 100% rename from packages/gitbook-v2/openNext/queue.ts rename to packages/gitbook-v2/openNext/queue/middleware.ts diff --git a/packages/gitbook-v2/openNext/queue/server.ts b/packages/gitbook-v2/openNext/queue/server.ts new file mode 100644 index 0000000000..a9e0de7082 --- /dev/null +++ b/packages/gitbook-v2/openNext/queue/server.ts @@ -0,0 +1,14 @@ +import type { Queue } from '@opennextjs/aws/types/overrides.js'; +import { getCloudflareContext } from '@opennextjs/cloudflare'; + +interface Env { + MIDDLEWARE_REFERENCE?: Pick; +} + +export default { + name: 'GitbookISRQueue', + send: async (msg) => { + const { env, ctx } = getCloudflareContext(); + ctx.waitUntil((env as Env).MIDDLEWARE_REFERENCE?.send(msg) ?? Promise.resolve()); + }, +} satisfies Queue; diff --git a/packages/gitbook-v2/openNext/tagCache/middleware.ts b/packages/gitbook-v2/openNext/tagCache/middleware.ts new file mode 100644 index 0000000000..8534878e55 --- /dev/null +++ b/packages/gitbook-v2/openNext/tagCache/middleware.ts @@ -0,0 +1,18 @@ +import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache'; +import { + softTagFilter, + withFilter, +} from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter'; + +export default withFilter({ + tagCache: doShardedTagCache({ + baseShardSize: 12, + regionalCache: true, + shardReplication: { + numberOfSoftReplicas: 2, + numberOfHardReplicas: 1, + }, + }), + // We don't use `revalidatePath`, so we filter out soft tags + filterFn: softTagFilter, +}); diff --git a/packages/gitbook-v2/openNext/tagCache/server.ts b/packages/gitbook-v2/openNext/tagCache/server.ts new file mode 100644 index 0000000000..d248ba53d2 --- /dev/null +++ b/packages/gitbook-v2/openNext/tagCache/server.ts @@ -0,0 +1,26 @@ +import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js'; +import { getCloudflareContext } from '@opennextjs/cloudflare'; + +interface Env { + MIDDLEWARE_REFERENCE?: Pick< + NextModeTagCache, + 'getLastRevalidated' | 'hasBeenRevalidated' | 'writeTags' | 'getPathsByTags' + >; +} + +export default { + name: 'GitbookTagCache', + mode: 'nextMode', + getLastRevalidated: async (tags: string[]) => { + const { env } = getCloudflareContext(); + return (env as Env).MIDDLEWARE_REFERENCE?.getLastRevalidated(tags) ?? 0; + }, + hasBeenRevalidated: async (tags: string[]) => { + const { env } = getCloudflareContext(); + return (env as Env).MIDDLEWARE_REFERENCE?.hasBeenRevalidated(tags) ?? false; + }, + writeTags: async (tags: string[]) => { + const { env } = getCloudflareContext(); + (env as Env).MIDDLEWARE_REFERENCE?.writeTags(tags); + }, +} satisfies NextModeTagCache; From d28ff61c127f091630c98e5874a288aa87f36875 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Fri, 23 May 2025 18:46:40 +0200 Subject: [PATCH 11/19] gh action to deploy middleware and server in sync --- .../gradual-deploy-cloudflare/action.yaml | 83 +++++++++++++++++++ .../composite/deploy-cloudflare/action.yaml | 14 ++++ .../openNext/customWorkers/default.js | 4 - .../openNext/customWorkers/middleware.js | 3 + .../customWorkers/middlewareWrangler.jsonc | 11 ++- .../customWorkers/script/updateWrangler.ts | 7 +- 6 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 .github/actions/gradual-deploy-cloudflare/action.yaml diff --git a/.github/actions/gradual-deploy-cloudflare/action.yaml b/.github/actions/gradual-deploy-cloudflare/action.yaml new file mode 100644 index 0000000000..eff064a754 --- /dev/null +++ b/.github/actions/gradual-deploy-cloudflare/action.yaml @@ -0,0 +1,83 @@ +name: Gradual Deploy to Cloudflare +description: Use gradual deployment to deploy to Cloudflare. This action will upload the middleware and server versions to Cloudflare and kept them bound together +inputs: + apiToken: + description: 'Cloudflare API token' + required: true + accountId: + description: 'Cloudflare account ID' + required: true + environment: + description: 'Cloudflare environment to deploy to (staging, production, preview)' + required: true + middlewareVersionId: + description: 'Middleware version ID to deploy' + required: true + serverVersionId: + description: 'Server version ID to deploy' + required: true +outputs: + deployment-url: + description: "Deployment URL" + value: ${{ steps.deploy_middleware.outputs.deployment-url }} +runs: + using: 'composite' + steps: + - id: wrangler_status + name: Check wrangler deployment status + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: deployments status --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc + + # This step is used to get the version ID that is currently deployed to Cloudflare. + - id: extract_current_version + name: Extract current version + shell: bash + run: | + version_id=$(echo "${{ steps.wrangler_status.outputs.command-output }}" | grep -A 3 "(100%)" | grep -oP '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + echo "version_id=$version_id" >> $GITHUB_OUTPUT + + - id: deploy_server + name: Deploy server to Cloudflare at 0% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ steps.extract_current_version.outputs.version_id }}@100% ${{ inputs.serverVersionId }}@0% -y --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc + + # Since we use version overrides headers, we can directly deploy the middleware to 100%. + - id: deploy_middleware + name: Deploy middleware to Cloudflare at 100% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ inputs.middlewareVersionId }}@100% -y --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc + + - name: Deploy server to Cloudflare at 100% + uses: cloudflare/wrangler-action@v3.14.0 + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + workingDirectory: ./ + wranglerVersion: '4.10.0' + environment: ${{ inputs.environment }} + command: versions deploy ${{ inputs.serverVersionId }}@100% -y --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc + + - name: Outputs + shell: bash + env: + DEPLOYMENT_URL: ${{ steps.deploy_middleware.outputs.deployment-url }} + run: | + echo "URL: ${{ steps.deploy_middleware.outputs.deployment-url }}" \ No newline at end of file diff --git a/.github/composite/deploy-cloudflare/action.yaml b/.github/composite/deploy-cloudflare/action.yaml index 47fe8491d7..d1d0d66339 100644 --- a/.github/composite/deploy-cloudflare/action.yaml +++ b/.github/composite/deploy-cloudflare/action.yaml @@ -105,6 +105,20 @@ runs: version_id=$(echo '${{ steps.upload_middleware.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}') echo "version_id=$version_id" >> $GITHUB_OUTPUT + - name: Deploy server and middleware to Cloudflare + if: ${{ inputs.deploy == 'true' }} + uses: ./.github/actions/gradual-deploy-cloudflare + with: + apiToken: ${{ inputs.apiToken }} + accountId: ${{ inputs.accountId }} + opServiceAccount: ${{ inputs.opServiceAccount }} + opItem: ${{ inputs.opItem }} + environment: ${{ inputs.environment }} + serverVersionId: ${{ steps.extract_server_version_id.outputs.version_id }} + middlewareVersionId: ${{ steps.extract_middleware_version_id.outputs.version_id }} + deploy: ${{ inputs.deploy }} + + - name: Outputs shell: bash env: diff --git a/packages/gitbook-v2/openNext/customWorkers/default.js b/packages/gitbook-v2/openNext/customWorkers/default.js index 5d0961a4f8..941cadedbb 100644 --- a/packages/gitbook-v2/openNext/customWorkers/default.js +++ b/packages/gitbook-v2/openNext/customWorkers/default.js @@ -6,10 +6,6 @@ export default { async fetch(request, env, ctx) { return runWithCloudflareRequestContext(request, env, ctx, async () => { // - `Request`s are handled by the Next server - console.log('Request URL:', request); - if (request.url.includes('404')) { - await env.MIDDLEWARE_REFERENCE.send('whatever'); - } return handler(request, env, ctx); }); }, diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index 36a001a6ed..bf43d4e3b2 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -20,6 +20,9 @@ export default class extends WorkerEntrypoint { if (this.env.STAGE !== 'preview') { return this.env.DEFAULT_WORKER?.fetch(reqOrResp, { + headers: { + 'Cloudflare-Workers-Version-Overrides': `gitbook-open-v2-${this.env.STAGE}="${this.env.WORKER_VERSION_ID}"`, + }, cf: { cacheEverything: false, }, diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc index 1d43340f5b..5d0b50daa8 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -46,7 +46,8 @@ "preview": { "vars": { "STAGE": "preview", - "PREVIEW_HOSTNAME": "TO_REPLACE" + "PREVIEW_HOSTNAME": "TO_REPLACE", + "WORKER_VERSION_ID": "TO_REPLACE" }, "r2_buckets": [ { @@ -72,6 +73,10 @@ // and we don't need tags invalidation on preview }, "staging": { + "vars": { + "STAGE": "staging", + "WORKER_VERSION_ID": "TO_REPLACE" + }, "routes": [ { "pattern": "open-2c.gitbook-staging.com/*", @@ -133,7 +138,9 @@ "MAX_REVALIDATE_CONCURRENCY": "100", // Temporary variable to find the issue once deployed // TODO: remove this once the issue is fixed - "DEBUG_CLOUDFLARE": "true" + "DEBUG_CLOUDFLARE": "true", + "WORKER_VERSION_ID": "TO_REPLACE", + "STAGE": "production" }, "routes": [ { diff --git a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts index 024a6dfe3f..0fdbf6cc70 100644 --- a/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts +++ b/packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts @@ -13,9 +13,14 @@ const versionId = args[0]; // The preview URL is in the format https://-gitbook-open-v2-server-preview.gitbook.workers.dev const previewHostname = `${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`; -const updatedFile = file.replace( +let updatedFile = file.replace( /"PREVIEW_HOSTNAME": "TO_REPLACE"/, `"PREVIEW_HOSTNAME": "${previewHostname}"` ); +updatedFile = updatedFile.replaceAll( + /"WORKER_VERSION_ID": "TO_REPLACE"/g, + `"WORKER_VERSION_ID": "${versionId}"` +); + fs.writeFileSync(wranglerConfigPath, updatedFile); From 7304e348844ef08d342175f1b2a6135487b3bf74 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Mon, 26 May 2025 17:04:54 +0200 Subject: [PATCH 12/19] add different inspector port --- packages/gitbook-v2/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gitbook-v2/package.json b/packages/gitbook-v2/package.json index e8f2e76533..224d55e301 100644 --- a/packages/gitbook-v2/package.json +++ b/packages/gitbook-v2/package.json @@ -30,7 +30,7 @@ "start": "next start", "build:v2:cloudflare": "opennextjs-cloudflare build", "dev:v2:cloudflare": "wrangler dev --port 8771 --env preview", - "dev:v2:cf:middleware": "wrangler dev --port 8771 --env dev --config ./openNext/customWorkers/middlewareWrangler.jsonc", + "dev:v2:cf:middleware": "wrangler dev --port 8771 --inspector-port 9230 --env dev --config ./openNext/customWorkers/middlewareWrangler.jsonc", "dev:v2:cf:server": "wrangler dev --port 8772 --env dev --config ./openNext/customWorkers/defaultWrangler.jsonc", "unit": "bun test", "typecheck": "tsc --noEmit" From 2a9d1db393040e7122bbcd38a2451a5b65e42ee8 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 27 May 2025 12:46:58 +0200 Subject: [PATCH 13/19] add try catch around tag cache --- .../openNext/tagCache/middleware.ts | 31 ++++++++++++++++++- .../gitbook-v2/openNext/tagCache/server.ts | 26 ++++++++++++---- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/packages/gitbook-v2/openNext/tagCache/middleware.ts b/packages/gitbook-v2/openNext/tagCache/middleware.ts index 8534878e55..54f4b37da4 100644 --- a/packages/gitbook-v2/openNext/tagCache/middleware.ts +++ b/packages/gitbook-v2/openNext/tagCache/middleware.ts @@ -1,10 +1,11 @@ +import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js'; import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache'; import { softTagFilter, withFilter, } from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter'; -export default withFilter({ +const filteredTagCache = withFilter({ tagCache: doShardedTagCache({ baseShardSize: 12, regionalCache: true, @@ -16,3 +17,31 @@ export default withFilter({ // We don't use `revalidatePath`, so we filter out soft tags filterFn: softTagFilter, }); + +export default { + name: 'GitbookTagCache', + mode: 'nextMode', + getLastRevalidated: async (tags: string[]) => { + try { + return await filteredTagCache.getLastRevalidated(tags); + } catch (error) { + console.error('Error getting last revalidated tags:', error); + return 0; // Fallback to 0 if there's an error + } + }, + hasBeenRevalidated: async (tags: string[]) => { + try { + return await filteredTagCache.hasBeenRevalidated(tags); + } catch (error) { + console.error('Error checking if tags have been revalidated:', error); + return false; // Fallback to false if there's an error + } + }, + writeTags: async (tags: string[]) => { + try { + await filteredTagCache.writeTags(tags); + } catch (error) { + console.error('Error writing tags:', error); + } + }, +} satisfies NextModeTagCache; diff --git a/packages/gitbook-v2/openNext/tagCache/server.ts b/packages/gitbook-v2/openNext/tagCache/server.ts index d248ba53d2..796d9243c2 100644 --- a/packages/gitbook-v2/openNext/tagCache/server.ts +++ b/packages/gitbook-v2/openNext/tagCache/server.ts @@ -12,15 +12,29 @@ export default { name: 'GitbookTagCache', mode: 'nextMode', getLastRevalidated: async (tags: string[]) => { - const { env } = getCloudflareContext(); - return (env as Env).MIDDLEWARE_REFERENCE?.getLastRevalidated(tags) ?? 0; + try { + const { env } = getCloudflareContext(); + return (env as Env).MIDDLEWARE_REFERENCE?.getLastRevalidated(tags) ?? 0; + } catch (error) { + console.error('Server - Error getting last revalidated tags:', error); + return 0; // Fallback to 0 if there's an error + } }, hasBeenRevalidated: async (tags: string[]) => { - const { env } = getCloudflareContext(); - return (env as Env).MIDDLEWARE_REFERENCE?.hasBeenRevalidated(tags) ?? false; + try { + const { env } = getCloudflareContext(); + return (env as Env).MIDDLEWARE_REFERENCE?.hasBeenRevalidated(tags) ?? false; + } catch (error) { + console.error('Server - Error checking if tags have been revalidated:', error); + return false; // Fallback to false if there's an error + } }, writeTags: async (tags: string[]) => { - const { env } = getCloudflareContext(); - (env as Env).MIDDLEWARE_REFERENCE?.writeTags(tags); + try { + const { env } = getCloudflareContext(); + (env as Env).MIDDLEWARE_REFERENCE?.writeTags(tags); + } catch (error) { + console.error('Server - Error writing tags:', error); + } }, } satisfies NextModeTagCache; From 042011ac1a8a91cdbaf8701610b0228fb20f0c21 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 27 May 2025 13:41:31 +0200 Subject: [PATCH 14/19] temp disable tag and queue in the server --- packages/gitbook-v2/openNext/queue/server.ts | 9 ++- .../gitbook-v2/openNext/tagCache/server.ts | 63 ++++++++++--------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/packages/gitbook-v2/openNext/queue/server.ts b/packages/gitbook-v2/openNext/queue/server.ts index a9e0de7082..ad231fc413 100644 --- a/packages/gitbook-v2/openNext/queue/server.ts +++ b/packages/gitbook-v2/openNext/queue/server.ts @@ -1,14 +1,17 @@ import type { Queue } from '@opennextjs/aws/types/overrides.js'; -import { getCloudflareContext } from '@opennextjs/cloudflare'; +// import { getCloudflareContext } from '@opennextjs/cloudflare'; +// biome-ignore lint/correctness/noUnusedVariables: interface Env { MIDDLEWARE_REFERENCE?: Pick; } export default { name: 'GitbookISRQueue', + // biome-ignore lint/correctness/noUnusedVariables: send: async (msg) => { - const { env, ctx } = getCloudflareContext(); - ctx.waitUntil((env as Env).MIDDLEWARE_REFERENCE?.send(msg) ?? Promise.resolve()); + //TODO: Re add this + // const { env, ctx } = getCloudflareContext(); + // ctx.waitUntil((env as Env).MIDDLEWARE_REFERENCE?.send(msg) ?? Promise.resolve()); }, } satisfies Queue; diff --git a/packages/gitbook-v2/openNext/tagCache/server.ts b/packages/gitbook-v2/openNext/tagCache/server.ts index 796d9243c2..c894a85c8a 100644 --- a/packages/gitbook-v2/openNext/tagCache/server.ts +++ b/packages/gitbook-v2/openNext/tagCache/server.ts @@ -1,40 +1,43 @@ import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js'; -import { getCloudflareContext } from '@opennextjs/cloudflare'; -interface Env { - MIDDLEWARE_REFERENCE?: Pick< - NextModeTagCache, - 'getLastRevalidated' | 'hasBeenRevalidated' | 'writeTags' | 'getPathsByTags' - >; -} +// interface Env { +// MIDDLEWARE_REFERENCE?: Pick< +// NextModeTagCache, +// 'getLastRevalidated' | 'hasBeenRevalidated' | 'writeTags' | 'getPathsByTags' +// >; +// } +//TODO: Reenable all of this. export default { name: 'GitbookTagCache', mode: 'nextMode', - getLastRevalidated: async (tags: string[]) => { - try { - const { env } = getCloudflareContext(); - return (env as Env).MIDDLEWARE_REFERENCE?.getLastRevalidated(tags) ?? 0; - } catch (error) { - console.error('Server - Error getting last revalidated tags:', error); - return 0; // Fallback to 0 if there's an error - } + getLastRevalidated: async (_tags: string[]) => { + return 0; + // try { + // const { env } = getCloudflareContext(); + // return (env as Env).MIDDLEWARE_REFERENCE?.getLastRevalidated(tags) ?? 0; + // } catch (error) { + // console.error('Server - Error getting last revalidated tags:', error); + // return 0; // Fallback to 0 if there's an error + // } }, - hasBeenRevalidated: async (tags: string[]) => { - try { - const { env } = getCloudflareContext(); - return (env as Env).MIDDLEWARE_REFERENCE?.hasBeenRevalidated(tags) ?? false; - } catch (error) { - console.error('Server - Error checking if tags have been revalidated:', error); - return false; // Fallback to false if there's an error - } + hasBeenRevalidated: async (_tags: string[]) => { + // try { + // const { env } = getCloudflareContext(); + // return (env as Env).MIDDLEWARE_REFERENCE?.hasBeenRevalidated(tags) ?? false; + // } catch (error) { + // console.error('Server - Error checking if tags have been revalidated:', error); + // return false; // Fallback to false if there's an error + // } + return false; }, - writeTags: async (tags: string[]) => { - try { - const { env } = getCloudflareContext(); - (env as Env).MIDDLEWARE_REFERENCE?.writeTags(tags); - } catch (error) { - console.error('Server - Error writing tags:', error); - } + writeTags: async (_tags: string[]) => { + // try { + // const { env } = getCloudflareContext(); + // (env as Env).MIDDLEWARE_REFERENCE?.writeTags(tags); + // } catch (error) { + // console.error('Server - Error writing tags:', error); + // } + return; }, } satisfies NextModeTagCache; From c8a499426869374d4483eb9a5fe86016f869579f Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 27 May 2025 14:55:34 +0200 Subject: [PATCH 15/19] test without the binding --- .../openNext/customWorkers/middleware.js | 31 ---------- packages/gitbook-v2/openNext/queue/server.ts | 12 +--- .../gitbook-v2/openNext/tagCache/server.ts | 60 +++++++++---------- packages/gitbook-v2/src/lib/data/api.ts | 35 +++++------ 4 files changed, 48 insertions(+), 90 deletions(-) diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index bf43d4e3b2..334e071e8f 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -40,23 +40,6 @@ export default class extends WorkerEntrypoint { }); } - /** - * Forwards the message from the server to the DO queue. - */ - async send(message) { - return runWithCloudflareRequestContext( - new Request('http://local'), - this.env, - this.ctx, - async () => { - const queue = await onConfig.middleware.override.queue(); - if (queue) { - return queue.send(message); - } - } - ); - } - /** * All the functions below are used to interact with the tag cache. * They are needed for the server who wouldn't be able to access the tag cache directly. @@ -76,20 +59,6 @@ export default class extends WorkerEntrypoint { ); } - async hasBeenRevalidated() { - return runWithCloudflareRequestContext( - new Request('http://local'), - this.env, - this.ctx, - async () => { - const tagCache = await onConfig.middleware.override.tagCache(); - if (tagCache) { - return tagCache.hasBeenRevalidated(); - } - } - ); - } - async writeTags(tags) { return runWithCloudflareRequestContext( new Request('http://local'), diff --git a/packages/gitbook-v2/openNext/queue/server.ts b/packages/gitbook-v2/openNext/queue/server.ts index ad231fc413..9a5b3b689b 100644 --- a/packages/gitbook-v2/openNext/queue/server.ts +++ b/packages/gitbook-v2/openNext/queue/server.ts @@ -1,17 +1,9 @@ import type { Queue } from '@opennextjs/aws/types/overrides.js'; -// import { getCloudflareContext } from '@opennextjs/cloudflare'; - -// biome-ignore lint/correctness/noUnusedVariables: -interface Env { - MIDDLEWARE_REFERENCE?: Pick; -} export default { name: 'GitbookISRQueue', - // biome-ignore lint/correctness/noUnusedVariables: send: async (msg) => { - //TODO: Re add this - // const { env, ctx } = getCloudflareContext(); - // ctx.waitUntil((env as Env).MIDDLEWARE_REFERENCE?.send(msg) ?? Promise.resolve()); + // We should never reach this point in the server. If that's the case, we should log it. + console.warn('GitbookISRQueue: send called on server side, this should not happen.', msg); }, } satisfies Queue; diff --git a/packages/gitbook-v2/openNext/tagCache/server.ts b/packages/gitbook-v2/openNext/tagCache/server.ts index c894a85c8a..473d3d7412 100644 --- a/packages/gitbook-v2/openNext/tagCache/server.ts +++ b/packages/gitbook-v2/openNext/tagCache/server.ts @@ -1,43 +1,39 @@ import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js'; +import { getCloudflareContext } from '@opennextjs/cloudflare'; -// interface Env { -// MIDDLEWARE_REFERENCE?: Pick< -// NextModeTagCache, -// 'getLastRevalidated' | 'hasBeenRevalidated' | 'writeTags' | 'getPathsByTags' -// >; -// } +interface Env { + MIDDLEWARE_REFERENCE?: Pick; +} -//TODO: Reenable all of this. export default { name: 'GitbookTagCache', mode: 'nextMode', - getLastRevalidated: async (_tags: string[]) => { - return 0; - // try { - // const { env } = getCloudflareContext(); - // return (env as Env).MIDDLEWARE_REFERENCE?.getLastRevalidated(tags) ?? 0; - // } catch (error) { - // console.error('Server - Error getting last revalidated tags:', error); - // return 0; // Fallback to 0 if there's an error - // } + getLastRevalidated: async (tags: string[]) => { + try { + const { env } = getCloudflareContext(); + const lastRevalidated = await (env as Env).MIDDLEWARE_REFERENCE?.getLastRevalidated( + tags + ); + return lastRevalidated ?? 0; // Fallback to 0 if there's no last revalidated time + } catch (error) { + console.error('GitbookTagCache: Error getting last revalidated:', error); + return 0; // Fallback to 0 if there's an error + } }, - hasBeenRevalidated: async (_tags: string[]) => { - // try { - // const { env } = getCloudflareContext(); - // return (env as Env).MIDDLEWARE_REFERENCE?.hasBeenRevalidated(tags) ?? false; - // } catch (error) { - // console.error('Server - Error checking if tags have been revalidated:', error); - // return false; // Fallback to false if there's an error - // } + hasBeenRevalidated: async (tags: string[]) => { + // We should never reach this point in the server. If that's the case, we should log it. + console.warn( + 'GitbookTagCache: hasBeenRevalidated called on server side, this should not happen.', + tags + ); return false; }, - writeTags: async (_tags: string[]) => { - // try { - // const { env } = getCloudflareContext(); - // (env as Env).MIDDLEWARE_REFERENCE?.writeTags(tags); - // } catch (error) { - // console.error('Server - Error writing tags:', error); - // } - return; + writeTags: async (tags: string[]) => { + try { + const { env } = getCloudflareContext(); + (env as Env).MIDDLEWARE_REFERENCE?.writeTags(tags); + } catch (error) { + console.error('Server - Error writing tags:', error); + } }, } satisfies NextModeTagCache; diff --git a/packages/gitbook-v2/src/lib/data/api.ts b/packages/gitbook-v2/src/lib/data/api.ts index 7a2d03d16d..0296f39a2e 100644 --- a/packages/gitbook-v2/src/lib/data/api.ts +++ b/packages/gitbook-v2/src/lib/data/api.ts @@ -9,7 +9,7 @@ import { import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-tags'; import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env'; import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'; -import { getCloudflareContext, getCloudflareRequestGlobal } from './cloudflare'; +import { getCloudflareRequestGlobal } from './cloudflare'; import { DataFetcherError, wrapDataFetcherError } from './errors'; import { withCacheKey, withoutConcurrentExecution } from './memoize'; import type { GitBookDataFetcher } from './types'; @@ -828,7 +828,7 @@ async function* streamAIResponse( } } -let loggedServiceBinding = false; +// const loggedServiceBinding = false; /** * Create a new API client. @@ -837,21 +837,22 @@ export function apiClient(input: DataFetcherInput = { apiToken: null }) { const { apiToken } = input; let serviceBinding: GitBookAPIServiceBinding | undefined; - const cloudflareContext = getCloudflareContext(); - if (cloudflareContext) { - // @ts-expect-error - serviceBinding = cloudflareContext.env.GITBOOK_API as GitBookAPIServiceBinding | undefined; - if (!loggedServiceBinding) { - loggedServiceBinding = true; - if (serviceBinding) { - // biome-ignore lint/suspicious/noConsole: we want to log here - console.log(`using service binding for the API (${GITBOOK_API_URL})`); - } else { - // biome-ignore lint/suspicious/noConsole: we want to log here - console.warn(`no service binding for the API (${GITBOOK_API_URL})`); - } - } - } + // TODO: Don't forget to uncomment if it wasn't the issue. + // const cloudflareContext = getCloudflareContext(); + // if (cloudflareContext) { + // // @ts-expect-error + // serviceBinding = cloudflareContext.env.GITBOOK_API as GitBookAPIServiceBinding | undefined; + // if (!loggedServiceBinding) { + // loggedServiceBinding = true; + // if (serviceBinding) { + // // biome-ignore lint/suspicious/noConsole: we want to log here + // console.log(`using service binding for the API (${GITBOOK_API_URL})`); + // } else { + // // biome-ignore lint/suspicious/noConsole: we want to log here + // console.warn(`no service binding for the API (${GITBOOK_API_URL})`); + // } + // } + // } const api = new GitBookAPI({ authToken: apiToken || GITBOOK_API_TOKEN || undefined, From 2c9431ebe38a297fc51b537a015f31683aa732cd Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 27 May 2025 17:29:58 +0200 Subject: [PATCH 16/19] remove GITBOOK_API binding --- .../customWorkers/defaultWrangler.jsonc | 16 ------------- .../customWorkers/middlewareWrangler.jsonc | 16 ------------- .../gitbook-v2/openNext/tagCache/server.ts | 24 +++++++++++++------ packages/gitbook-v2/src/lib/data/api.ts | 24 ++----------------- 4 files changed, 19 insertions(+), 61 deletions(-) diff --git a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc index 08dcdbf1b2..037a2dcb3b 100644 --- a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc @@ -29,10 +29,6 @@ "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-server-dev" }, - { - "binding": "GITBOOK_API", - "service": "gitbook-x-prod-api-cache" - }, { "binding": "MIDDLEWARE_REFERENCE", "service": "gitbook-open-v2-dev" @@ -54,10 +50,6 @@ "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-server-preview" }, - { - "binding": "GITBOOK_API", - "service": "gitbook-x-prod-api-cache" - }, { "binding": "MIDDLEWARE_REFERENCE", "service": "gitbook-open-v2-preview" @@ -78,10 +70,6 @@ "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-server-staging" }, - { - "binding": "GITBOOK_API", - "service": "gitbook-x-staging-api-cache" - }, { "binding": "MIDDLEWARE_REFERENCE", "service": "gitbook-open-v2-staging" @@ -105,10 +93,6 @@ "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-server-production" }, - { - "binding": "GITBOOK_API", - "service": "gitbook-x-prod-api-cache" - }, { "binding": "MIDDLEWARE_REFERENCE", "service": "gitbook-open-v2-production" diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc index 5d0b50daa8..8ecc036269 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -33,10 +33,6 @@ "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-dev" }, - { - "binding": "GITBOOK_API", - "service": "gitbook-x-prod-api-cache" - }, { "binding": "DEFAULT_WORKER", "service": "gitbook-open-v2-server-dev" @@ -60,10 +56,6 @@ "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-preview" }, - { - "binding": "GITBOOK_API", - "service": "gitbook-x-prod-api-cache" - }, { "binding": "DEFAULT_WORKER", "service": "gitbook-open-v2-server-preview" @@ -98,10 +90,6 @@ "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-staging" }, - { - "binding": "GITBOOK_API", - "service": "gitbook-x-staging-api-cache" - }, { "binding": "DEFAULT_WORKER", "service": "gitbook-open-v2-server-staging" @@ -163,10 +151,6 @@ "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-production" }, - { - "binding": "GITBOOK_API", - "service": "gitbook-x-prod-api-cache" - }, { "binding": "DEFAULT_WORKER", "service": "gitbook-open-v2-server-production" diff --git a/packages/gitbook-v2/openNext/tagCache/server.ts b/packages/gitbook-v2/openNext/tagCache/server.ts index 473d3d7412..d01bc3c3b9 100644 --- a/packages/gitbook-v2/openNext/tagCache/server.ts +++ b/packages/gitbook-v2/openNext/tagCache/server.ts @@ -2,12 +2,16 @@ import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js'; import { getCloudflareContext } from '@opennextjs/cloudflare'; interface Env { - MIDDLEWARE_REFERENCE?: Pick; + MIDDLEWARE_REFERENCE?: Pick< + NextModeTagCache, + 'writeTags' | 'getLastRevalidated' | 'hasBeenRevalidated' + >; } export default { name: 'GitbookTagCache', mode: 'nextMode', + // This one is used for the composable cache. getLastRevalidated: async (tags: string[]) => { try { const { env } = getCloudflareContext(); @@ -20,13 +24,19 @@ export default { return 0; // Fallback to 0 if there's an error } }, + // This one get called for ISR/SSG cache, if we reach here, it very likely means that we need to revalidate the page. + // TODO: see if we could just return true. hasBeenRevalidated: async (tags: string[]) => { - // We should never reach this point in the server. If that's the case, we should log it. - console.warn( - 'GitbookTagCache: hasBeenRevalidated called on server side, this should not happen.', - tags - ); - return false; + try { + const { env } = getCloudflareContext(); + const hasBeenRevalidated = await (env as Env).MIDDLEWARE_REFERENCE?.hasBeenRevalidated( + tags + ); + return hasBeenRevalidated ?? false; // Fallback to false if there's no revalidation status + } catch (error) { + console.error('GitbookTagCache: Error checking revalidation:', error); + return false; // Fallback to false if there's an error + } }, writeTags: async (tags: string[]) => { try { diff --git a/packages/gitbook-v2/src/lib/data/api.ts b/packages/gitbook-v2/src/lib/data/api.ts index 0296f39a2e..46ba7c3a87 100644 --- a/packages/gitbook-v2/src/lib/data/api.ts +++ b/packages/gitbook-v2/src/lib/data/api.ts @@ -2,7 +2,6 @@ import { trace } from '@/lib/tracing'; import { type ComputedContentSource, GitBookAPI, - type GitBookAPIServiceBinding, type HttpResponse, type RenderIntegrationUI, } from '@gitbook/api'; @@ -828,37 +827,18 @@ async function* streamAIResponse( } } -// const loggedServiceBinding = false; - /** * Create a new API client. + * We don't use the binding because it can cause Error: Response closed due to connection limit + * Connection limit are shared between all the bindings. */ export function apiClient(input: DataFetcherInput = { apiToken: null }) { const { apiToken } = input; - let serviceBinding: GitBookAPIServiceBinding | undefined; - - // TODO: Don't forget to uncomment if it wasn't the issue. - // const cloudflareContext = getCloudflareContext(); - // if (cloudflareContext) { - // // @ts-expect-error - // serviceBinding = cloudflareContext.env.GITBOOK_API as GitBookAPIServiceBinding | undefined; - // if (!loggedServiceBinding) { - // loggedServiceBinding = true; - // if (serviceBinding) { - // // biome-ignore lint/suspicious/noConsole: we want to log here - // console.log(`using service binding for the API (${GITBOOK_API_URL})`); - // } else { - // // biome-ignore lint/suspicious/noConsole: we want to log here - // console.warn(`no service binding for the API (${GITBOOK_API_URL})`); - // } - // } - // } const api = new GitBookAPI({ authToken: apiToken || GITBOOK_API_TOKEN || undefined, endpoint: GITBOOK_API_URL, userAgent: GITBOOK_USER_AGENT, - serviceBinding, }); return api; From 0585bf0fe71a40145377e3c17528ccb3a2a5fe8f Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 28 May 2025 12:54:44 +0200 Subject: [PATCH 17/19] use DO tag cache in the server Fix NextRequest body being incorrect --- packages/gitbook-v2/open-next.config.ts | 2 +- .../openNext/customWorkers/default.js | 7 +- .../customWorkers/defaultWrangler.jsonc | 46 ++++++---- .../openNext/customWorkers/middleware.js | 42 +-------- .../customWorkers/middlewareWrangler.jsonc | 3 +- .../gitbook-v2/openNext/queue/middleware.ts | 10 ++- .../openNext/tagCache/middleware.ts | 89 ++++++++++++------- .../gitbook-v2/openNext/tagCache/server.ts | 49 ---------- 8 files changed, 109 insertions(+), 139 deletions(-) delete mode 100644 packages/gitbook-v2/openNext/tagCache/server.ts diff --git a/packages/gitbook-v2/open-next.config.ts b/packages/gitbook-v2/open-next.config.ts index e85d583c45..959834739a 100644 --- a/packages/gitbook-v2/open-next.config.ts +++ b/packages/gitbook-v2/open-next.config.ts @@ -8,7 +8,7 @@ export default { proxyExternalRequest: 'fetch', queue: () => import('./openNext/queue/server').then((m) => m.default), incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default), - tagCache: () => import('./openNext/tagCache/server').then((m) => m.default), + tagCache: () => import('./openNext/tagCache/middleware').then((m) => m.default), }, }, middleware: { diff --git a/packages/gitbook-v2/openNext/customWorkers/default.js b/packages/gitbook-v2/openNext/customWorkers/default.js index 941cadedbb..bb673e1722 100644 --- a/packages/gitbook-v2/openNext/customWorkers/default.js +++ b/packages/gitbook-v2/openNext/customWorkers/default.js @@ -1,10 +1,13 @@ import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; -import { handler } from '../../.open-next/server-functions/default/handler.mjs'; - export default { async fetch(request, env, ctx) { return runWithCloudflareRequestContext(request, env, ctx, async () => { + // We can't move the handler import to the top level, otherwise the runtime will not be properly initialized + const { handler } = await import( + '../../.open-next/server-functions/default/handler.mjs' + ); + // - `Request`s are handled by the Next server return handler(request, env, ctx); }); diff --git a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc index 037a2dcb3b..ce213f43f5 100644 --- a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc @@ -28,16 +28,14 @@ { "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-server-dev" - }, - { - "binding": "MIDDLEWARE_REFERENCE", - "service": "gitbook-open-v2-dev" } ] }, "preview": { "vars": { - "STAGE": "preview" + "STAGE": "preview", + // Just as a test for the preview environment to check that everything works + "NEXT_PRIVATE_DEBUG_CACHE": "true" }, "r2_buckets": [ { @@ -49,10 +47,6 @@ { "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-server-preview" - }, - { - "binding": "MIDDLEWARE_REFERENCE", - "service": "gitbook-open-v2-preview" } ] // No durable objects on preview, as they block the generation of preview URLs @@ -69,10 +63,21 @@ { "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-server-staging" - }, + } + ], + "durable_objects": { + "bindings": [ + { + "name": "NEXT_TAG_CACHE_DO_SHARDED", + "class_name": "DOShardedTagCache", + "script_name": "gitbook-open-v2-staging" + } + ] + }, + "migrations": [ { - "binding": "MIDDLEWARE_REFERENCE", - "service": "gitbook-open-v2-staging" + "tag": "v1", + "new_sqlite_classes": ["DOShardedTagCache"] } ], "tail_consumers": [ @@ -92,10 +97,21 @@ { "binding": "WORKER_SELF_REFERENCE", "service": "gitbook-open-v2-server-production" - }, + } + ], + "durable_objects": { + "bindings": [ + { + "name": "NEXT_TAG_CACHE_DO_SHARDED", + "class_name": "DOShardedTagCache", + "script_name": "gitbook-open-v2-production" + } + ] + }, + "migrations": [ { - "binding": "MIDDLEWARE_REFERENCE", - "service": "gitbook-open-v2-production" + "tag": "v1", + "new_sqlite_classes": ["DOShardedTagCache"] } ], "tail_consumers": [ diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index 334e071e8f..95ef0e6ddb 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -1,8 +1,6 @@ import { WorkerEntrypoint } from 'cloudflare:workers'; import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js'; -import onConfig from '../../.open-next/middleware/open-next.config.mjs'; - import { handler as middlewareHandler } from '../../.open-next/middleware/handler.mjs'; export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js'; @@ -19,10 +17,11 @@ export default class extends WorkerEntrypoint { } if (this.env.STAGE !== 'preview') { + reqOrResp.headers.set( + 'Cloudflare-Workers-Version-Overrides', + `gitbook-open-v2-${this.env.STAGE}="${this.env.WORKER_VERSION_ID}"` + ); return this.env.DEFAULT_WORKER?.fetch(reqOrResp, { - headers: { - 'Cloudflare-Workers-Version-Overrides': `gitbook-open-v2-${this.env.STAGE}="${this.env.WORKER_VERSION_ID}"`, - }, cf: { cacheEverything: false, }, @@ -39,37 +38,4 @@ export default class extends WorkerEntrypoint { }); }); } - - /** - * All the functions below are used to interact with the tag cache. - * They are needed for the server who wouldn't be able to access the tag cache directly. - */ - - async getLastRevalidated() { - return runWithCloudflareRequestContext( - new Request('http://local'), - this.env, - this.ctx, - async () => { - const tagCache = await onConfig.middleware.override.tagCache(); - if (tagCache) { - return tagCache.getLastRevalidated(); - } - } - ); - } - - async writeTags(tags) { - return runWithCloudflareRequestContext( - new Request('http://local'), - this.env, - this.ctx, - async () => { - const tagCache = await onConfig.middleware.override.tagCache(); - if (tagCache) { - return tagCache.writeTags(tags); - } - } - ); - } } diff --git a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc index 8ecc036269..4870c89d54 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc @@ -20,7 +20,8 @@ "env": { "dev": { "vars": { - "STAGE": "dev" + "STAGE": "dev", + "NEXT_PRIVATE_DEBUG_CACHE": "true" }, "r2_buckets": [ { diff --git a/packages/gitbook-v2/openNext/queue/middleware.ts b/packages/gitbook-v2/openNext/queue/middleware.ts index cc4cb73287..2a14dc1d8b 100644 --- a/packages/gitbook-v2/openNext/queue/middleware.ts +++ b/packages/gitbook-v2/openNext/queue/middleware.ts @@ -1,3 +1,4 @@ +import { trace } from '@/lib/tracing'; import type { Queue } from '@opennextjs/aws/types/overrides.js'; import { getCloudflareContext } from '@opennextjs/cloudflare'; import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue'; @@ -10,8 +11,11 @@ interface Env { export default { name: 'GitbookISRQueue', send: async (msg) => { - const { ctx, env } = getCloudflareContext(); - const hasDurableObject = (env as Env).STAGE !== 'dev' && (env as Env).STAGE !== 'preview'; - ctx.waitUntil(hasDurableObject ? memoryQueue.send(msg) : doQueue.send(msg)); + return trace({ operation: 'gitbookISRQueueSend', name: msg.MessageBody.url }, async () => { + const { ctx, env } = getCloudflareContext(); + const hasDurableObject = + (env as Env).STAGE !== 'dev' && (env as Env).STAGE !== 'preview'; + ctx.waitUntil(hasDurableObject ? memoryQueue.send(msg) : doQueue.send(msg)); + }); }, } satisfies Queue; diff --git a/packages/gitbook-v2/openNext/tagCache/middleware.ts b/packages/gitbook-v2/openNext/tagCache/middleware.ts index 54f4b37da4..173b5dd283 100644 --- a/packages/gitbook-v2/openNext/tagCache/middleware.ts +++ b/packages/gitbook-v2/openNext/tagCache/middleware.ts @@ -1,47 +1,76 @@ +import { trace } from '@/lib/tracing'; import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js'; import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache'; -import { - softTagFilter, - withFilter, -} from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter'; +import { softTagFilter } from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter'; -const filteredTagCache = withFilter({ - tagCache: doShardedTagCache({ - baseShardSize: 12, - regionalCache: true, - shardReplication: { - numberOfSoftReplicas: 2, - numberOfHardReplicas: 1, - }, - }), - // We don't use `revalidatePath`, so we filter out soft tags - filterFn: softTagFilter, +const originalTagCache = doShardedTagCache({ + baseShardSize: 12, + regionalCache: true, + shardReplication: { + numberOfSoftReplicas: 2, + numberOfHardReplicas: 1, + }, }); export default { name: 'GitbookTagCache', mode: 'nextMode', getLastRevalidated: async (tags: string[]) => { - try { - return await filteredTagCache.getLastRevalidated(tags); - } catch (error) { - console.error('Error getting last revalidated tags:', error); - return 0; // Fallback to 0 if there's an error + const tagsToCheck = tags.filter(softTagFilter); + if (tagsToCheck.length === 0) { + // If we reach here, it probably means that there is an issue that we'll need to address. + console.warn( + 'getLastRevalidated - No valid tags to check for last revalidation, original tags:', + tags + ); + return 0; // If no tags to check, return 0 } + return trace( + { + operation: 'gitbookTagCacheGetLastRevalidated', + name: tagsToCheck.join(', '), + }, + async () => { + return await originalTagCache.getLastRevalidated(tagsToCheck); + } + ); }, hasBeenRevalidated: async (tags: string[]) => { - try { - return await filteredTagCache.hasBeenRevalidated(tags); - } catch (error) { - console.error('Error checking if tags have been revalidated:', error); - return false; // Fallback to false if there's an error + const tagsToCheck = tags.filter(softTagFilter); + if (tagsToCheck.length === 0) { + // If we reach here, it probably means that there is an issue that we'll need to address. + console.warn( + 'hasBeenRevalidated - No valid tags to check for revalidation, original tags:', + tags + ); + return false; // If no tags to check, return false } + return trace( + { + operation: 'gitbookTagCacheHasBeenRevalidated', + name: tagsToCheck.join(', '), + }, + async () => { + const result = await originalTagCache.hasBeenRevalidated(tagsToCheck); + return result; + } + ); }, writeTags: async (tags: string[]) => { - try { - await filteredTagCache.writeTags(tags); - } catch (error) { - console.error('Error writing tags:', error); - } + return trace( + { + operation: 'gitbookTagCacheWriteTags', + name: tags.join(', '), + }, + async () => { + const tagsToWrite = tags.filter(softTagFilter); + if (tagsToWrite.length === 0) { + console.warn('writeTags - No valid tags to write'); + return; // If no tags to write, exit early + } + // Write only the filtered tags + await originalTagCache.writeTags(tagsToWrite); + } + ); }, } satisfies NextModeTagCache; diff --git a/packages/gitbook-v2/openNext/tagCache/server.ts b/packages/gitbook-v2/openNext/tagCache/server.ts deleted file mode 100644 index d01bc3c3b9..0000000000 --- a/packages/gitbook-v2/openNext/tagCache/server.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js'; -import { getCloudflareContext } from '@opennextjs/cloudflare'; - -interface Env { - MIDDLEWARE_REFERENCE?: Pick< - NextModeTagCache, - 'writeTags' | 'getLastRevalidated' | 'hasBeenRevalidated' - >; -} - -export default { - name: 'GitbookTagCache', - mode: 'nextMode', - // This one is used for the composable cache. - getLastRevalidated: async (tags: string[]) => { - try { - const { env } = getCloudflareContext(); - const lastRevalidated = await (env as Env).MIDDLEWARE_REFERENCE?.getLastRevalidated( - tags - ); - return lastRevalidated ?? 0; // Fallback to 0 if there's no last revalidated time - } catch (error) { - console.error('GitbookTagCache: Error getting last revalidated:', error); - return 0; // Fallback to 0 if there's an error - } - }, - // This one get called for ISR/SSG cache, if we reach here, it very likely means that we need to revalidate the page. - // TODO: see if we could just return true. - hasBeenRevalidated: async (tags: string[]) => { - try { - const { env } = getCloudflareContext(); - const hasBeenRevalidated = await (env as Env).MIDDLEWARE_REFERENCE?.hasBeenRevalidated( - tags - ); - return hasBeenRevalidated ?? false; // Fallback to false if there's no revalidation status - } catch (error) { - console.error('GitbookTagCache: Error checking revalidation:', error); - return false; // Fallback to false if there's an error - } - }, - writeTags: async (tags: string[]) => { - try { - const { env } = getCloudflareContext(); - (env as Env).MIDDLEWARE_REFERENCE?.writeTags(tags); - } catch (error) { - console.error('Server - Error writing tags:', error); - } - }, -} satisfies NextModeTagCache; From 81e223b199c604190fafd638eb04ed6dde6a7dc6 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Thu, 29 May 2025 15:10:44 +0200 Subject: [PATCH 18/19] remove useless DO migration --- .../openNext/customWorkers/defaultWrangler.jsonc | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc index ce213f43f5..3fff07d7c0 100644 --- a/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc +++ b/packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc @@ -67,6 +67,8 @@ ], "durable_objects": { "bindings": [ + // We do not need to define migrations for external DOs, + // In fact, defining migrations for external DOs will crash { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", @@ -74,12 +76,6 @@ } ] }, - "migrations": [ - { - "tag": "v1", - "new_sqlite_classes": ["DOShardedTagCache"] - } - ], "tail_consumers": [ { "service": "gitbook-x-staging-tail" @@ -102,18 +98,14 @@ "durable_objects": { "bindings": [ { + // We do not need to define migrations for external DOs, + // In fact, defining migrations for external DOs will crash "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", "script_name": "gitbook-open-v2-production" } ] }, - "migrations": [ - { - "tag": "v1", - "new_sqlite_classes": ["DOShardedTagCache"] - } - ], "tail_consumers": [ { "service": "gitbook-x-prod-tail" From 875c39f76acf4b9e575f731a62021f646c7cd47e Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Fri, 30 May 2025 14:42:54 +0200 Subject: [PATCH 19/19] review --- packages/gitbook-v2/openNext/customWorkers/middleware.js | 1 + packages/gitbook-v2/src/lib/data/api.ts | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/gitbook-v2/openNext/customWorkers/middleware.js b/packages/gitbook-v2/openNext/customWorkers/middleware.js index 95ef0e6ddb..78a84a9760 100644 --- a/packages/gitbook-v2/openNext/customWorkers/middleware.js +++ b/packages/gitbook-v2/openNext/customWorkers/middleware.js @@ -17,6 +17,7 @@ export default class extends WorkerEntrypoint { } if (this.env.STAGE !== 'preview') { + // https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#version-affinity reqOrResp.headers.set( 'Cloudflare-Workers-Version-Overrides', `gitbook-open-v2-${this.env.STAGE}="${this.env.WORKER_VERSION_ID}"` diff --git a/packages/gitbook-v2/src/lib/data/api.ts b/packages/gitbook-v2/src/lib/data/api.ts index 46ba7c3a87..f8ae6d2711 100644 --- a/packages/gitbook-v2/src/lib/data/api.ts +++ b/packages/gitbook-v2/src/lib/data/api.ts @@ -829,8 +829,6 @@ async function* streamAIResponse( /** * Create a new API client. - * We don't use the binding because it can cause Error: Response closed due to connection limit - * Connection limit are shared between all the bindings. */ export function apiClient(input: DataFetcherInput = { apiToken: null }) { const { apiToken } = input;