From fad0553b670167523ca4b358c2cce68503d6dab1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 30 Jun 2026 04:01:28 +0000 Subject: [PATCH 1/2] Add docs-proxy worker app with fetch timeout and caching Creates apps/docs-proxy/ - a new Cloudflare Worker that replaces the supermemory-docs-mintlify dashboard-deployed worker. Key improvements over the previous worker: - AbortController with 10-second timeout prevents indefinite hangs when Mintlify is slow (previous P99 reached 836 seconds) - Cache API (caches.default) caches successful GET responses for 5 minutes to reduce upstream load and improve response times - Returns 504 with a user-friendly message on timeout Worker proxies all requests to https://supermemory.mintlify.dev preserving original path, query string, method, headers, and body. Co-authored-by: Dhravya Shah --- apps/docs-proxy/package.json | 15 +++++++++++ apps/docs-proxy/src/index.ts | 46 ++++++++++++++++++++++++++++++++++ apps/docs-proxy/tsconfig.json | 15 +++++++++++ apps/docs-proxy/wrangler.jsonc | 11 ++++++++ bun.lock | 13 ++++++++++ 5 files changed, 100 insertions(+) create mode 100644 apps/docs-proxy/package.json create mode 100644 apps/docs-proxy/src/index.ts create mode 100644 apps/docs-proxy/tsconfig.json create mode 100644 apps/docs-proxy/wrangler.jsonc diff --git a/apps/docs-proxy/package.json b/apps/docs-proxy/package.json new file mode 100644 index 000000000..a8df4af9b --- /dev/null +++ b/apps/docs-proxy/package.json @@ -0,0 +1,15 @@ +{ + "name": "@repo/docs-proxy", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy --minify" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250620.0", + "typescript": "^5.8.3", + "wrangler": "^4.4.0" + } +} diff --git a/apps/docs-proxy/src/index.ts b/apps/docs-proxy/src/index.ts new file mode 100644 index 000000000..3aae0fa2a --- /dev/null +++ b/apps/docs-proxy/src/index.ts @@ -0,0 +1,46 @@ +const UPSTREAM_BASE = "https://supermemory.mintlify.dev"; +const CACHE_TTL = 300; // 5 minutes +const FETCH_TIMEOUT_MS = 10000; // 10 seconds + +export default { + async fetch(request: Request): Promise { + const url = new URL(request.url); + const proxyUrl = UPSTREAM_BASE + url.pathname + url.search; + + const cache = caches.default; + + if (request.method === "GET") { + const cached = await cache.match(request); + if (cached) return cached; + } + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); + + try { + const proxyRequest = new Request(proxyUrl, request); + const response = await fetch(proxyRequest, { signal: controller.signal }); + + if (request.method === "GET" && response.ok) { + const cachedResponse = new Response(response.clone().body, response); + cachedResponse.headers.set("Cache-Control", `public, max-age=${CACHE_TTL}`); + await cache.put(request, cachedResponse); + } + + return response; + } catch (e) { + if (e instanceof Error && e.name === "AbortError") { + return new Response( + "Documentation temporarily unavailable. Please try again in a moment.", + { + status: 504, + headers: { "Content-Type": "text/plain; charset=utf-8" }, + }, + ); + } + throw e; + } finally { + clearTimeout(timeoutId); + } + }, +}; diff --git a/apps/docs-proxy/tsconfig.json b/apps/docs-proxy/tsconfig.json new file mode 100644 index 000000000..91626ee12 --- /dev/null +++ b/apps/docs-proxy/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "lib": ["ESNext"], + "types": ["@cloudflare/workers-types"], + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/apps/docs-proxy/wrangler.jsonc b/apps/docs-proxy/wrangler.jsonc new file mode 100644 index 000000000..350c5a910 --- /dev/null +++ b/apps/docs-proxy/wrangler.jsonc @@ -0,0 +1,11 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "supermemory-docs-mintlify", + "main": "src/index.ts", + "compatibility_date": "2025-06-20", + "compatibility_flags": [], + + "observability": { + "enabled": true + } +} diff --git a/bun.lock b/bun.lock index c6f5c06e2..484f683f5 100644 --- a/bun.lock +++ b/bun.lock @@ -83,6 +83,15 @@ "typescript": "^5.9.2", }, }, + "apps/docs-proxy": { + "name": "@repo/docs-proxy", + "version": "1.0.0", + "devDependencies": { + "@cloudflare/workers-types": "^4.20250620.0", + "typescript": "^5.8.3", + "wrangler": "^4.4.0", + }, + }, "apps/mcp": { "name": "supermemory-mcp", "version": "4.0.0", @@ -1448,6 +1457,8 @@ "@repo/docs": ["@repo/docs@workspace:apps/docs"], + "@repo/docs-proxy": ["@repo/docs-proxy@workspace:apps/docs-proxy"], + "@repo/hooks": ["@repo/hooks@workspace:packages/hooks"], "@repo/lib": ["@repo/lib@workspace:packages/lib"], @@ -5442,6 +5453,8 @@ "@repo/docs/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@repo/docs-proxy/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@repo/lib/better-auth": ["better-auth@1.3.3", "", { "dependencies": { "@better-auth/utils": "0.2.5", "@better-fetch/fetch": "^1.1.18", "@noble/ciphers": "^0.6.0", "@noble/hashes": "^1.8.0", "@simplewebauthn/browser": "^13.0.0", "@simplewebauthn/server": "^13.0.0", "better-call": "^1.0.12", "defu": "^6.1.4", "jose": "^5.9.6", "kysely": "^0.28.1", "nanostores": "^0.11.3", "zod": "^4.0.5" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-q1aD2nNpGfEI2ckYu+pBjN+23CIRctOpmREkWyJDJdoYW1q9EPs1Xdb+KhFztg2rMmsoUN8I9Xm5mUWMxiWuLw=="], "@repo/web/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="], From 9b85bb2ca4a59c787c4feb25915449b1e9f397ab Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 04:03:54 +0000 Subject: [PATCH 2/2] fix: apply Biome formatting to docs-proxy (remove semicolons) The project uses no-semicolons style per Biome configuration. Co-Authored-By: Claude Opus 4.5 --- apps/docs-proxy/src/index.ts | 43 +++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/docs-proxy/src/index.ts b/apps/docs-proxy/src/index.ts index 3aae0fa2a..6b910bc1c 100644 --- a/apps/docs-proxy/src/index.ts +++ b/apps/docs-proxy/src/index.ts @@ -1,33 +1,36 @@ -const UPSTREAM_BASE = "https://supermemory.mintlify.dev"; -const CACHE_TTL = 300; // 5 minutes -const FETCH_TIMEOUT_MS = 10000; // 10 seconds +const UPSTREAM_BASE = "https://supermemory.mintlify.dev" +const CACHE_TTL = 300 // 5 minutes +const FETCH_TIMEOUT_MS = 10000 // 10 seconds export default { async fetch(request: Request): Promise { - const url = new URL(request.url); - const proxyUrl = UPSTREAM_BASE + url.pathname + url.search; + const url = new URL(request.url) + const proxyUrl = UPSTREAM_BASE + url.pathname + url.search - const cache = caches.default; + const cache = caches.default if (request.method === "GET") { - const cached = await cache.match(request); - if (cached) return cached; + const cached = await cache.match(request) + if (cached) return cached } - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS) try { - const proxyRequest = new Request(proxyUrl, request); - const response = await fetch(proxyRequest, { signal: controller.signal }); + const proxyRequest = new Request(proxyUrl, request) + const response = await fetch(proxyRequest, { signal: controller.signal }) if (request.method === "GET" && response.ok) { - const cachedResponse = new Response(response.clone().body, response); - cachedResponse.headers.set("Cache-Control", `public, max-age=${CACHE_TTL}`); - await cache.put(request, cachedResponse); + const cachedResponse = new Response(response.clone().body, response) + cachedResponse.headers.set( + "Cache-Control", + `public, max-age=${CACHE_TTL}`, + ) + await cache.put(request, cachedResponse) } - return response; + return response } catch (e) { if (e instanceof Error && e.name === "AbortError") { return new Response( @@ -36,11 +39,11 @@ export default { status: 504, headers: { "Content-Type": "text/plain; charset=utf-8" }, }, - ); + ) } - throw e; + throw e } finally { - clearTimeout(timeoutId); + clearTimeout(timeoutId) } }, -}; +}