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 <dhravya@supermemory.com>
This commit is contained in:
Cursor Agent 2026-06-30 04:01:28 +00:00
parent e706a13877
commit fad0553b67
No known key found for this signature in database
5 changed files with 100 additions and 0 deletions

View file

@ -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"
}
}

View file

@ -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<Response> {
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);
}
},
};

View file

@ -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"]
}

View file

@ -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
}
}

View file

@ -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=="],