{"_id":"@builtwithai/serverless-redis-cloudflare","name":"@builtwithai/serverless-redis-cloudflare","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@builtwithai/serverless-redis-cloudflare","version":"1.0.0","description":"Cloudflare Workers integration for Serverless Redis Client","main":"dist/index.js","module":"dist/index.esm.js","types":"dist/index.d.ts","scripts":{"build":"rollup -c","dev":"rollup -c -w","test":"jest","lint":"eslint src/**/*.ts","type-check":"tsc --noEmit","clean":"rm -rf dist"},"keywords":["cloudflare","workers","redis","serverless","edge","client"],"repository":{"type":"git","url":"git+https://github.com/built-with-ai/serverless-redis.git","directory":"client-sdks/packages/cloudflare"},"license":"MIT","author":{"name":"Built with AI Team"},"dependencies":{"@builtwithai/serverless-redis-client":"^1.0.0"},"peerDependencies":{"@cloudflare/workers-types":">=3.0.0"},"devDependencies":{"@cloudflare/workers-types":"^4.20231025.0","@types/node":"^20.0.0","rollup":"^3.29.4","rollup-plugin-typescript2":"^0.36.0","typescript":"^5.2.2"},"publishConfig":{"access":"public"},"_id":"@builtwithai/serverless-redis-cloudflare@1.0.0","gitHead":"d945edfe0ae36cd3a2136df2b2001db43916891e","bugs":{"url":"https://github.com/built-with-ai/serverless-redis/issues"},"homepage":"https://github.com/built-with-ai/serverless-redis#readme","_nodeVersion":"20.11.0","_npmVersion":"10.2.4","dist":{"integrity":"sha512-7Tdb+LIfMfQETix4yVYMj1HgeGKp9ttAzO1mGghOYMXIEeZGOshTDx459F4wCdNL01mogTwr6anUZg5DZR0+Ew==","shasum":"d088bfe9c4365ac69e5b2e9dec402f4ac8aa05a2","tarball":"https://registry.npmjs.org/@builtwithai/serverless-redis-cloudflare/-/serverless-redis-cloudflare-1.0.0.tgz","fileCount":2,"unpackedSize":15475,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIArEkaQVFOCiUi6xOzGpAMFkRohCQvJ5V3CeexzawQXBAiEAl8ug9EM0/j1D2+mkodgiRHd7IxMvpTiil4F5aHHDSzk="}]},"_npmUser":{"name":"scalerllc","email":"chris.naegelin@scaler.io"},"directories":{},"maintainers":[{"name":"scalerllc","email":"chris.naegelin@scaler.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/serverless-redis-cloudflare_1.0.0_1754148987492_0.7410576067523285"},"_hasShrinkwrap":false}},"time":{"created":"2025-08-02T15:36:27.360Z","1.0.0":"2025-08-02T15:36:27.681Z","modified":"2025-08-02T15:36:27.997Z"},"maintainers":[{"name":"scalerllc","email":"chris.naegelin@scaler.io"}],"description":"Cloudflare Workers integration for Serverless Redis Client","homepage":"https://github.com/built-with-ai/serverless-redis#readme","keywords":["cloudflare","workers","redis","serverless","edge","client"],"repository":{"type":"git","url":"git+https://github.com/built-with-ai/serverless-redis.git","directory":"client-sdks/packages/cloudflare"},"author":{"name":"Built with AI Team"},"bugs":{"url":"https://github.com/built-with-ai/serverless-redis/issues"},"license":"MIT","readme":"# @scaler/serverless-redis-cloudflare\n\nCloudflare Workers integration for the Serverless Redis Client, optimized for Cloudflare's edge runtime with advanced caching and storage integrations.\n\n## Installation\n\n```bash\nnpm install @scaler/serverless-redis-cloudflare\n```\n\n## Quick Start\n\n### Basic Worker\n\n```typescript\n// src/worker.ts\nimport { withRedis } from '@scaler/serverless-redis-cloudflare';\n\nexport interface Env {\n  REDIS_PROXY_URL: string;\n  REDIS_TOKEN: string;\n}\n\nexport default withRedis(async (redis, request, env, ctx) => {\n  const url = new URL(request.url);\n  const key = url.searchParams.get('key') || 'default';\n  \n  if (request.method === 'GET') {\n    const value = await redis.get(key);\n    return new Response(JSON.stringify({ key, value }), {\n      headers: { 'Content-Type': 'application/json' }\n    });\n  }\n  \n  if (request.method === 'POST') {\n    const { value } = await request.json();\n    await redis.set(key, value);\n    return new Response(JSON.stringify({ success: true, key, value }), {\n      headers: { 'Content-Type': 'application/json' }\n    });\n  }\n  \n  return new Response('Method not allowed', { status: 405 });\n});\n```\n\n### With KV Caching\n\n```typescript\nimport { CloudflareRedis } from '@scaler/serverless-redis-cloudflare';\n\nexport interface Env {\n  REDIS_PROXY_URL: string;\n  REDIS_TOKEN: string;\n  REDIS_CACHE: KVNamespace; // KV binding for caching\n}\n\nexport default {\n  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {\n    const redis = new CloudflareRedis({\n      url: env.REDIS_PROXY_URL,\n      token: env.REDIS_TOKEN,\n    }, env, ctx);\n    \n    const key = new URL(request.url).searchParams.get('key') || 'default';\n    \n    // Get with KV cache fallback (5 minute TTL)\n    const value = await redis.getWithCache(key, 300);\n    \n    return new Response(JSON.stringify({ key, value }), {\n      headers: { 'Content-Type': 'application/json' }\n    });\n  }\n};\n```\n\n## Features\n\n- 🚀 **Cloudflare Workers Optimized** - Ultra-low latency edge computing\n- ⚡ **KV Integration** - Automatic caching with Cloudflare KV\n- 💾 **R2 Storage** - Large object storage with Redis metadata\n- 🌍 **Global Edge Network** - Deploy to 200+ locations worldwide\n- 🔧 **TypeScript First** - Full type safety for Workers environment\n- 📊 **Built-in Rate Limiting** - Distributed rate limiting across edge\n- 🛡️ **Security Headers** - Automatic client info extraction\n- 🎯 **Zero Cold Start** - Instant execution at the edge\n\n## Configuration\n\n### Environment Variables\n\nSet these in your `wrangler.toml` or Cloudflare Dashboard:\n\n```toml\n# wrangler.toml\n[vars]\nREDIS_PROXY_URL = \"https://your-redis-proxy.example.com\"\nREDIS_TOKEN = \"your-api-key-or-jwt\"\nREDIS_TIMEOUT = \"2000\"  # Very short timeout for Workers\nREDIS_RETRIES = \"1\"     # Single retry for Workers\n\n# KV namespace binding (optional)\n[[kv_namespaces]]\nbinding = \"REDIS_CACHE\"\nid = \"your-kv-namespace-id\"\n\n# R2 bucket binding (optional)\n[[r2_buckets]]\nbinding = \"R2\"\nbucket_name = \"your-r2-bucket\"\n\n# D1 database binding (optional)\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"your-d1-database\"\ndatabase_id = \"your-d1-database-id\"\n```\n\n### Programmatic Configuration\n\n```typescript\nimport { createServerlessRedis, CloudflareRedisUtils } from '@scaler/serverless-redis-cloudflare';\n\n// Basic configuration\nconst redis = createServerlessRedis({\n  url: 'https://your-proxy.example.com',\n  token: 'your-api-key',\n  timeout: 2000,  // Short timeout for Workers\n  retries: 1,     // Single retry\n  compression: true,\n}, env);\n\n// Optimized configuration\nconst optimizedRedis = createServerlessRedis(\n  CloudflareRedisUtils.getOptimizedConfig({\n    url: env.REDIS_PROXY_URL,\n    token: env.REDIS_TOKEN,\n  }),\n  env\n);\n```\n\n## API Reference\n\n### Functions\n\n#### `createServerlessRedis(config?, env?)`\n\nCreates a Redis client optimized for Cloudflare Workers.\n\n```typescript\nconst redis = createServerlessRedis({\n  url: 'https://your-proxy.example.com',\n  token: 'your-api-key',\n  timeout: 2000,    // Very short timeout for Workers\n  retries: 1,       // Single retry\n  compression: true // Enable compression\n}, env);\n```\n\n#### `withRedis(handler)`\n\nWraps a Worker fetch handler with Redis client injection.\n\n```typescript\nexport default withRedis(async (redis, request, env, ctx) => {\n  const data = await redis.get('key');\n  return new Response(JSON.stringify({ data }));\n});\n```\n\n### Enhanced Client\n\n#### `CloudflareRedis`\n\nExtended client with Cloudflare-specific optimizations:\n\n```typescript\nconst redis = new CloudflareRedis(config, env, ctx);\n\n// KV-cached operations\nconst value = await redis.getWithCache('key', 300); // 5 min TTL\nawait redis.setWithCache('key', 'value', 300);\nawait redis.delWithCache('key');\n\n// R2 large object storage\nawait redis.setLargeObject('large-key', largeData, { type: 'image' });\nconst object = await redis.getLargeObject('large-key');\n```\n\n### Utilities\n\n#### `CloudflareRedisUtils`\n\nCloudflare-specific utility functions:\n\n```typescript\nimport { CloudflareRedisUtils } from '@scaler/serverless-redis-cloudflare';\n\n// Environment detection\nCloudflareRedisUtils.isCloudflareWorker(); // true if running in Workers\n\n// Client information extraction\nconst ip = CloudflareRedisUtils.getClientIP(request);\nconst country = CloudflareRedisUtils.getClientCountry(request);\nconst datacenter = CloudflareRedisUtils.getDataCenter(request);\n\n// Rate limiting key generation\nconst rateLimitKey = CloudflareRedisUtils.createRateLimitKey(request, 'api');\n\n// Optimized configuration\nconst config = CloudflareRedisUtils.getOptimizedConfig({\n  url: env.REDIS_PROXY_URL,\n  token: env.REDIS_TOKEN,\n});\n\n// CORS helpers\nconst corsResponse = CloudflareRedisUtils.corsResponse({ data: 'value' });\nconst corsPreflightResponse = CloudflareRedisUtils.handleCors(request);\n```\n\n#### `CloudflareRateLimit`\n\nDistributed rate limiting across Cloudflare's edge:\n\n```typescript\nimport { CloudflareRateLimit } from '@scaler/serverless-redis-cloudflare';\n\nconst rateLimit = new CloudflareRateLimit(redis, 100, 60); // 100 requests per minute\n\nconst key = CloudflareRedisUtils.createRateLimitKey(request);\nconst { allowed, remaining, resetIn } = await rateLimit.check(key);\n\nif (!allowed) {\n  return new Response('Rate limit exceeded', {\n    status: 429,\n    headers: {\n      'X-RateLimit-Remaining': remaining.toString(),\n      'X-RateLimit-Reset': resetIn.toString(),\n    }\n  });\n}\n```\n\n## Examples\n\n### API with Rate Limiting\n\n```typescript\nimport { withRedis, CloudflareRedisUtils, CloudflareRateLimit } from '@scaler/serverless-redis-cloudflare';\n\nexport interface Env {\n  REDIS_PROXY_URL: string;\n  REDIS_TOKEN: string;\n}\n\nexport default withRedis(async (redis, request, env, ctx) => {\n  // Handle CORS preflight\n  const corsResponse = CloudflareRedisUtils.handleCors(request);\n  if (corsResponse) return corsResponse;\n  \n  // Rate limiting\n  const rateLimit = new CloudflareRateLimit(redis, 60, 60); // 60 requests per minute\n  const rateLimitKey = CloudflareRedisUtils.createRateLimitKey(request);\n  \n  const { allowed, remaining, resetIn } = await rateLimit.check(rateLimitKey);\n  \n  if (!allowed) {\n    return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), {\n      status: 429,\n      headers: {\n        'Content-Type': 'application/json',\n        'X-RateLimit-Remaining': remaining.toString(),\n        'X-RateLimit-Reset': resetIn.toString(),\n      }\n    });\n  }\n  \n  // Your API logic here\n  const data = await redis.get('api-data');\n  \n  return CloudflareRedisUtils.corsResponse({\n    data,\n    rateLimit: { remaining, resetIn }\n  });\n});\n```\n\n### Geo-distributed Caching\n\n```typescript\nimport { CloudflareRedis, CloudflareRedisUtils } from '@scaler/serverless-redis-cloudflare';\n\nexport interface Env {\n  REDIS_PROXY_URL: string;\n  REDIS_TOKEN: string;\n  REDIS_CACHE: KVNamespace;\n}\n\nexport default {\n  async fetch(request: Request, env: Env, ctx: ExecutionContext) {\n    const redis = new CloudflareRedis({\n      url: env.REDIS_PROXY_URL,\n      token: env.REDIS_TOKEN,\n    }, env, ctx);\n    \n    const url = new URL(request.url);\n    const cacheKey = `page:${url.pathname}`;\n    const country = CloudflareRedisUtils.getClientCountry(request);\n    \n    // Try country-specific cache first\n    const countryKey = `${cacheKey}:${country}`;\n    let content = await redis.getWithCache(countryKey, 300);\n    \n    if (!content) {\n      // Fallback to global cache\n      content = await redis.getWithCache(cacheKey, 600);\n      \n      if (!content) {\n        // Generate content (expensive operation)\n        content = await generateContent(url.pathname);\n        \n        // Cache globally\n        await redis.setWithCache(cacheKey, content, 600);\n      }\n      \n      // Customize for country and cache\n      const localizedContent = localizeContent(content, country);\n      await redis.setWithCache(countryKey, localizedContent, 300);\n      content = localizedContent;\n    }\n    \n    return new Response(content, {\n      headers: {\n        'Content-Type': 'text/html',\n        'Cache-Control': 'public, max-age=300',\n        'CF-Cache-Status': 'HIT',\n      }\n    });\n  }\n};\n```\n\n### Large File Storage with R2\n\n```typescript\nimport { CloudflareRedis } from '@scaler/serverless-redis-cloudflare';\n\nexport interface Env {\n  REDIS_PROXY_URL: string;\n  REDIS_TOKEN: string;\n  R2: R2Bucket;\n}\n\nexport default {\n  async fetch(request: Request, env: Env, ctx: ExecutionContext) {\n    const redis = new CloudflareRedis({\n      url: env.REDIS_PROXY_URL,\n      token: env.REDIS_TOKEN,\n    }, env, ctx);\n    \n    const url = new URL(request.url);\n    const fileId = url.pathname.split('/').pop();\n    \n    if (request.method === 'POST') {\n      // Upload large file\n      const formData = await request.formData();\n      const file = formData.get('file') as File;\n      \n      if (file && file.size > 1024 * 1024) { // > 1MB\n        // Store in R2 with Redis metadata\n        await redis.setLargeObject(fileId!, file.stream(), {\n          filename: file.name,\n          contentType: file.type,\n          size: file.size.toString(),\n        });\n        \n        return new Response(JSON.stringify({ \n          success: true, \n          fileId,\n          size: file.size \n        }));\n      } else {\n        // Store small files directly in Redis\n        const buffer = await file.arrayBuffer();\n        const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));\n        await redis.set(fileId!, base64);\n        \n        return new Response(JSON.stringify({ \n          success: true, \n          fileId,\n          stored: 'redis' \n        }));\n      }\n    }\n    \n    if (request.method === 'GET') {\n      // Retrieve file\n      const largeObject = await redis.getLargeObject(fileId!);\n      \n      if (largeObject) {\n        // Large file from R2\n        return new Response(largeObject.body, {\n          headers: {\n            'Content-Type': largeObject.customMetadata?.contentType || 'application/octet-stream',\n            'Content-Length': largeObject.size?.toString() || '',\n          }\n        });\n      } else {\n        // Small file from Redis\n        const base64 = await redis.get(fileId!);\n        if (base64) {\n          const buffer = Uint8Array.from(atob(base64), c => c.charCodeAt(0));\n          return new Response(buffer);\n        }\n      }\n      \n      return new Response('File not found', { status: 404 });\n    }\n    \n    return new Response('Method not allowed', { status: 405 });\n  }\n};\n```\n\n### Session Management with Analytics\n\n```typescript\nimport { withRedis, CloudflareRedisUtils } from '@scaler/serverless-redis-cloudflare';\n\nexport interface Env {\n  REDIS_PROXY_URL: string;\n  REDIS_TOKEN: string;\n  REDIS_CACHE: KVNamespace;\n}\n\nexport default withRedis(async (redis, request, env, ctx) => {\n  const url = new URL(request.url);\n  const sessionId = url.searchParams.get('session');\n  \n  if (!sessionId) {\n    return new Response('Session ID required', { status: 400 });\n  }\n  \n  const clientIP = CloudflareRedisUtils.getClientIP(request);\n  const country = CloudflareRedisUtils.getClientCountry(request);\n  const datacenter = CloudflareRedisUtils.getDataCenter(request);\n  \n  if (request.method === 'GET') {\n    // Get session data\n    const sessionData = await redis.hgetall(`session:${sessionId}`);\n    \n    // Update analytics in background\n    ctx.waitUntil(\n      redis.pipeline()\n        .incr(`analytics:daily:${new Date().toISOString().split('T')[0]}`)\n        .incr(`analytics:country:${country}`)\n        .incr(`analytics:datacenter:${datacenter}`)\n        .exec()\n    );\n    \n    return CloudflareRedisUtils.corsResponse({\n      sessionData,\n      metadata: {\n        ip: clientIP,\n        country,\n        datacenter,\n      }\n    });\n  }\n  \n  if (request.method === 'POST') {\n    const data = await request.json();\n    \n    // Update session\n    await redis.pipeline()\n      .hset(`session:${sessionId}`, ...Object.entries(data).flat())\n      .expire(`session:${sessionId}`, 86400) // 24 hour TTL\n      .exec();\n    \n    return CloudflareRedisUtils.corsResponse({ success: true });\n  }\n  \n  return new Response('Method not allowed', { status: 405 });\n});\n```\n\n## Performance Optimization\n\n### Workers Runtime Limits\n\nCloudflare Workers have strict limits that this package automatically handles:\n\n- **CPU Time**: 10ms (free) / 50ms (paid)\n- **Memory**: 128MB\n- **Execution Time**: 10s for HTTP requests\n- **Subrequest Limit**: 50 per request\n\n### Optimization Strategies\n\n1. **Ultra-short timeouts** (2000ms default)\n2. **Single retry** to minimize latency\n3. **Automatic compression** for payload reduction\n4. **KV caching** for frequently accessed data\n5. **R2 integration** for large objects\n6. **Background tasks** with `ctx.waitUntil()`\n\n## Deployment\n\n1. **Install Wrangler CLI**:\n   ```bash\n   npm install -g wrangler\n   ```\n\n2. **Configure `wrangler.toml`**:\n   ```toml\n   name = \"my-redis-worker\"\n   compatibility_date = \"2023-10-25\"\n   \n   [vars]\n   REDIS_PROXY_URL = \"https://your-proxy.example.com\"\n   REDIS_TOKEN = \"your-api-key\"\n   ```\n\n3. **Deploy**:\n   ```bash\n   wrangler deploy\n   ```\n\nYour Redis-powered Worker is now running on Cloudflare's global edge network!\n\n## License\n\nMIT License - see [LICENSE](../../../LICENSE) for details.","readmeFilename":"README.md","_rev":"1-be5542077a350651b2ce5933d1561834"}