update skills

This commit is contained in:
2026-03-17 16:53:22 -07:00
parent 0b0783ef8e
commit f9a530667e
389 changed files with 54512 additions and 1 deletions

View File

@@ -0,0 +1,122 @@
# Cloudflare Bindings Skill Reference
Expert guidance on Cloudflare Workers Bindings - the runtime APIs that connect Workers to Cloudflare platform resources.
## What Are Bindings?
Bindings are how Workers access Cloudflare resources (storage, compute, services) via the `env` object. They're configured in `wrangler.jsonc`, type-safe via TypeScript, and zero-overhead at runtime.
## Reading Order
1. **This file** - Binding catalog and selection guide
2. **[api.md](api.md)** - TypeScript types and env access patterns
3. **[configuration.md](configuration.md)** - Complete wrangler.jsonc examples
4. **[patterns.md](patterns.md)** - Best practices and common patterns
5. **[gotchas.md](gotchas.md)** - Critical pitfalls and troubleshooting
## Binding Catalog
### Storage Bindings
| Binding | Use Case | Access Pattern |
|---------|----------|----------------|
| **KV** | Key-value cache, CDN-backed reads | `env.MY_KV.get(key)` |
| **R2** | Object storage (S3-compatible) | `env.MY_BUCKET.get(key)` |
| **D1** | SQL database (SQLite) | `env.DB.prepare(sql).all()` |
| **Durable Objects** | Coordination, real-time state | `env.MY_DO.get(id)` |
| **Vectorize** | Vector embeddings search | `env.VECTORIZE.query(vector)` |
| **Queues** | Async message processing | `env.MY_QUEUE.send(msg)` |
### Compute Bindings
| Binding | Use Case | Access Pattern |
|---------|----------|----------------|
| **Service** | Worker-to-Worker RPC | `env.MY_SERVICE.fetch(req)` |
| **Workers AI** | LLM inference | `env.AI.run(model, input)` |
| **Browser Rendering** | Headless Chrome | `env.BROWSER.fetch(url)` |
### Platform Bindings
| Binding | Use Case | Access Pattern |
|---------|----------|----------------|
| **Analytics Engine** | Custom metrics | `env.ANALYTICS.writeDataPoint(data)` |
| **mTLS** | Client certificates | `env.MY_CERT` (string) |
| **Hyperdrive** | Database pooling | `env.HYPERDRIVE.connectionString` |
| **Rate Limiting** | Request throttling | `env.RATE_LIMITER.limit(id)` |
| **Workflows** | Long-running workflows | `env.MY_WORKFLOW.create()` |
### Configuration Bindings
| Binding | Use Case | Access Pattern |
|---------|----------|----------------|
| **Environment Variables** | Non-sensitive config | `env.API_URL` (string) |
| **Secrets** | Sensitive values | `env.API_KEY` (string) |
| **Text/Data Blobs** | Static files | `env.MY_BLOB` (string) |
| **WASM** | WebAssembly modules | `env.MY_WASM` (WebAssembly.Module) |
## Quick Selection Guide
**Need persistent storage?**
- Key-value < 25MB → **KV**
- Files/objects → **R2**
- Relational data → **D1**
- Real-time coordination → **Durable Objects**
**Need AI/compute?**
- LLM inference → **Workers AI**
- Scraping/PDFs → **Browser Rendering**
- Call another Worker → **Service binding**
**Need async processing?**
- Background jobs → **Queues**
**Need config?**
- Public values → **Environment Variables**
- Secrets → **Secrets** (never commit)
## Quick Start
1. **Add binding to wrangler.jsonc:**
```jsonc
{
"kv_namespaces": [
{ "binding": "MY_KV", "id": "your-kv-id" }
]
}
```
2. **Generate types:**
```bash
npx wrangler types
```
3. **Access in Worker:**
```typescript
export default {
async fetch(request, env, ctx) {
await env.MY_KV.put('key', 'value');
return new Response('OK');
}
}
```
## Type Safety
Bindings are fully typed via `wrangler types`. See [api.md](api.md) for details.
## Limits
- 64 bindings max per Worker (all types combined)
- See [gotchas.md](gotchas.md) for per-binding limits
## Key Concepts
**Zero-overhead access:** Bindings compiled into Worker, no network calls to access
**Type-safe:** Full TypeScript support via `wrangler types`
**Per-environment:** Different IDs for dev/staging/production
**Secrets vs Vars:** Secrets encrypted at rest, never in config files
## See Also
- [Cloudflare Docs: Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/)
- [Wrangler Configuration](https://developers.cloudflare.com/workers/wrangler/configuration/)

View File

@@ -0,0 +1,203 @@
# Bindings API Reference
## TypeScript Types
Cloudflare generates binding types via `npx wrangler types`. This creates `.wrangler/types/runtime.d.ts` with your Env interface.
### Generated Env Interface
After running `wrangler types`, TypeScript knows your bindings:
```typescript
interface Env {
// From wrangler.jsonc bindings
MY_KV: KVNamespace;
MY_BUCKET: R2Bucket;
DB: D1Database;
MY_SERVICE: Fetcher;
AI: Ai;
// From vars
API_URL: string;
// From secrets (set via wrangler secret put)
API_KEY: string;
}
```
### Binding Types
| Config | TypeScript Type | Package |
|--------|-----------------|---------|
| `kv_namespaces` | `KVNamespace` | `@cloudflare/workers-types` |
| `r2_buckets` | `R2Bucket` | `@cloudflare/workers-types` |
| `d1_databases` | `D1Database` | `@cloudflare/workers-types` |
| `durable_objects.bindings` | `DurableObjectNamespace` | `@cloudflare/workers-types` |
| `vectorize` | `VectorizeIndex` | `@cloudflare/workers-types` |
| `queues.producers` | `Queue` | `@cloudflare/workers-types` |
| `services` | `Fetcher` | `@cloudflare/workers-types` |
| `ai` | `Ai` | `@cloudflare/workers-types` |
| `browser` | `Fetcher` | `@cloudflare/workers-types` |
| `analytics_engine_datasets` | `AnalyticsEngineDataset` | `@cloudflare/workers-types` |
| `hyperdrive` | `Hyperdrive` | `@cloudflare/workers-types` |
| `rate_limiting` | `RateLimit` | `@cloudflare/workers-types` |
| `workflows` | `Workflow` | `@cloudflare/workers-types` |
| `mtls_certificates` / `vars` / `text_blobs` / `data_blobs` | `string` | Built-in |
| `wasm_modules` | `WebAssembly.Module` | Built-in |
## Accessing Bindings
### Method 1: fetch() Handler (Recommended)
```typescript
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const value = await env.MY_KV.get('key');
return new Response(value);
}
}
```
**Why:** Type-safe, aligns with Workers API, supports ctx for waitUntil/passThroughOnException.
### Method 2: Hono Framework
```typescript
import { Hono } from 'hono';
const app = new Hono<{ Bindings: Env }>();
app.get('/', async (c) => {
const value = await c.env.MY_KV.get('key');
return c.json({ value });
});
export default app;
```
**Why:** c.env auto-typed, ergonomic for routing-heavy apps.
### Method 3: Module Workers (Legacy)
```typescript
export async function handleRequest(request: Request, env: Env): Promise<Response> {
const value = await env.MY_KV.get('key');
return new Response(value);
}
addEventListener('fetch', (event) => {
// env not directly available - requires workarounds
});
```
**Avoid:** Use fetch() handler instead (Method 1).
## Type Generation Workflow
### Initial Setup
```bash
# Install wrangler
npm install -D wrangler
# Generate types from wrangler.jsonc
npx wrangler types
```
### After Changing Bindings
```bash
# Added/modified binding in wrangler.jsonc
npx wrangler types
# TypeScript now sees updated Env interface
```
**Note:** `wrangler types` outputs to `.wrangler/types/runtime.d.ts`. TypeScript picks this up automatically if `@cloudflare/workers-types` is in `tsconfig.json` `"types"` array.
## Key Binding Methods
**KV:**
```typescript
await env.MY_KV.get(key, { type: 'json' }); // text|json|arrayBuffer|stream
await env.MY_KV.put(key, value, { expirationTtl: 3600 });
await env.MY_KV.delete(key);
await env.MY_KV.list({ prefix: 'user:' });
```
**R2:**
```typescript
await env.BUCKET.get(key);
await env.BUCKET.put(key, value);
await env.BUCKET.delete(key);
await env.BUCKET.list({ prefix: 'images/' });
```
**D1:**
```typescript
await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first();
await env.DB.batch([stmt1, stmt2]);
```
**Service:**
```typescript
await env.MY_SERVICE.fetch(new Request('https://fake/path'));
```
**Workers AI:**
```typescript
await env.AI.run('@cf/meta/llama-3.1-8b-instruct', { prompt: 'Hello' });
```
**Queues:**
```typescript
await env.MY_QUEUE.send({ userId: 123, action: 'process' });
```
**Durable Objects:**
```typescript
const id = env.MY_DO.idFromName('user-123');
const stub = env.MY_DO.get(id);
await stub.fetch(new Request('https://fake/increment'));
```
## Runtime vs Build-Time Types
| Type Source | When Generated | Use Case |
|-------------|----------------|----------|
| `@cloudflare/workers-types` | npm install | Base Workers APIs (Request, Response, etc.) |
| `wrangler types` | After config change | Your specific bindings (Env interface) |
**Install both:**
```bash
npm install -D @cloudflare/workers-types
npx wrangler types
```
## Type Safety Best Practices
1. **Never use `any` for env:**
```typescript
// ❌ BAD
async fetch(request: Request, env: any) { }
// ✅ GOOD
async fetch(request: Request, env: Env) { }
```
2. **Run wrangler types after config changes:**
```bash
# After editing wrangler.jsonc
npx wrangler types
```
3. **Check generated types match config:**
```bash
# View generated Env interface
cat .wrangler/types/runtime.d.ts
```
## See Also
- [Workers Types Package](https://www.npmjs.com/package/@cloudflare/workers-types)
- [Wrangler Types Command](https://developers.cloudflare.com/workers/wrangler/commands/#types)

View File

@@ -0,0 +1,188 @@
# Binding Configuration Reference
## Storage Bindings
```jsonc
{
"kv_namespaces": [{ "binding": "MY_KV", "id": "..." }],
"r2_buckets": [{ "binding": "MY_BUCKET", "bucket_name": "my-bucket" }],
"d1_databases": [{ "binding": "DB", "database_name": "my-db", "database_id": "..." }],
"durable_objects": { "bindings": [{ "name": "MY_DO", "class_name": "MyDO" }] },
"vectorize": [{ "binding": "VECTORIZE", "index_name": "my-index" }],
"queues": { "producers": [{ "binding": "MY_QUEUE", "queue": "my-queue" }] }
}
```
**Create commands:**
```bash
npx wrangler kv namespace create MY_KV
npx wrangler r2 bucket create my-bucket
npx wrangler d1 create my-db
npx wrangler vectorize create my-index --dimensions=768 --metric=cosine
npx wrangler queues create my-queue
# List existing resources
npx wrangler kv namespace list
npx wrangler r2 bucket list
npx wrangler d1 list
npx wrangler vectorize list
npx wrangler queues list
```
## Compute Bindings
```jsonc
{
"services": [{
"binding": "MY_SERVICE",
"service": "other-worker",
"environment": "production" // Optional: target specific env
}],
"ai": { "binding": "AI" },
"browser": { "binding": "BROWSER" },
"workflows": [{ "binding": "MY_WORKFLOW", "name": "my-workflow" }]
}
```
**Create workflows:**
```bash
npx wrangler workflows create my-workflow
```
## Platform Bindings
```jsonc
{
"analytics_engine_datasets": [{ "binding": "ANALYTICS" }],
"mtls_certificates": [{ "binding": "MY_CERT", "certificate_id": "..." }],
"hyperdrive": [{ "binding": "HYPERDRIVE", "id": "..." }],
"unsafe": {
"bindings": [{ "name": "RATE_LIMITER", "type": "ratelimit", "namespace_id": "..." }]
}
}
```
## Configuration Bindings
```jsonc
{
"vars": {
"API_URL": "https://api.example.com",
"MAX_RETRIES": "3"
},
"text_blobs": { "MY_TEXT": "./data/template.html" },
"data_blobs": { "MY_DATA": "./data/config.bin" },
"wasm_modules": { "MY_WASM": "./build/module.wasm" }
}
```
**Secrets (never in config):**
```bash
npx wrangler secret put API_KEY
```
## Environment-Specific Configuration
```jsonc
{
"name": "my-worker",
"vars": { "ENV": "production" },
"kv_namespaces": [{ "binding": "CACHE", "id": "prod-kv-id" }],
"env": {
"staging": {
"vars": { "ENV": "staging" },
"kv_namespaces": [{ "binding": "CACHE", "id": "staging-kv-id" }]
}
}
}
```
**Deploy:**
```bash
npx wrangler deploy # Production
npx wrangler deploy --env staging
```
## Local Development
```jsonc
{
"kv_namespaces": [{
"binding": "MY_KV",
"id": "prod-id",
"preview_id": "dev-id" // Used in wrangler dev
}]
}
```
**Or use remote:**
```bash
npx wrangler dev --remote # Uses production bindings
```
## Complete Example
```jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-app",
"main": "src/index.ts",
"compatibility_date": "2025-01-01",
"vars": { "API_URL": "https://api.example.com" },
"kv_namespaces": [{ "binding": "CACHE", "id": "abc123" }],
"r2_buckets": [{ "binding": "ASSETS", "bucket_name": "my-assets" }],
"d1_databases": [{ "binding": "DB", "database_name": "my-db", "database_id": "xyz789" }],
"services": [{ "binding": "AUTH", "service": "auth-worker" }],
"ai": { "binding": "AI" }
}
```
## Binding-Specific Configuration
### Durable Objects with Class Export
```jsonc
{
"durable_objects": {
"bindings": [
{ "name": "COUNTER", "class_name": "Counter", "script_name": "my-worker" }
]
}
}
```
```typescript
// In same Worker or script_name Worker
export class Counter {
constructor(private state: DurableObjectState, private env: Env) {}
async fetch(request: Request) { /* ... */ }
}
```
### Queue Consumers
```jsonc
{
"queues": {
"producers": [{ "binding": "MY_QUEUE", "queue": "my-queue" }],
"consumers": [{ "queue": "my-queue", "max_batch_size": 10 }]
}
}
```
Queue consumer handler: `export default { async queue(batch, env) { /* process batch.messages */ } }`
## Key Points
- **64 binding limit** (all types combined)
- **Secrets**: Always use `wrangler secret put`, never commit
- **Types**: Run `npx wrangler types` after config changes
- **Environments**: Use `env` field for staging/production variants
- **Development**: Use `preview_id` or `--remote` flag
- **IDs vs Names**: Some bindings use `id` (KV, D1), others use `name` (R2, Queues)
## See Also
- [Wrangler Configuration](https://developers.cloudflare.com/workers/wrangler/configuration/)

View File

@@ -0,0 +1,208 @@
# Binding Gotchas and Troubleshooting
## Critical: Global Scope Mutation
### ❌ THE #1 GOTCHA: Caching env in Global Scope
```typescript
// ❌ DANGEROUS - env cached at deploy time
const apiKey = env.API_KEY; // ERROR: env not available in global scope
export default {
async fetch(request: Request, env: Env) {
// Uses undefined or stale value!
}
}
```
**Why it breaks:**
- `env` not available in global scope
- If using workarounds, secrets may not update without redeployment
- Leads to "Cannot read property 'X' of undefined" errors
**✅ Always access env per-request:**
```typescript
export default {
async fetch(request: Request, env: Env) {
const apiKey = env.API_KEY; // Fresh every request
}
}
```
## Common Errors
### "env.MY_KV is undefined"
**Cause:** Name mismatch or not configured
**Solution:** Check wrangler.jsonc (case-sensitive), run `npx wrangler types`, verify `npx wrangler kv namespace list`
### "Property 'MY_KV' does not exist on type 'Env'"
**Cause:** Types not generated
**Solution:** `npx wrangler types`
### "preview_id is required for --remote"
**Cause:** Missing preview binding
**Solution:** Add `"preview_id": "dev-id"` or use `npx wrangler dev` (local mode)
### "Secret updated but Worker still uses old value"
**Cause:** Cached in global scope or not redeployed
**Solution:** Avoid global caching, redeploy after secret change
### "KV get() returns null for existing key"
**Cause:** Eventual consistency (60s), wrong namespace, wrong environment
**Solution:**
```bash
# Check key exists
npx wrangler kv key get --binding=MY_KV "your-key"
# Verify namespace ID
npx wrangler kv namespace list
# Check environment
npx wrangler deployments list
```
### "D1 database not found"
**Solution:** `npx wrangler d1 list`, verify ID in wrangler.jsonc
### "Service binding returns 'No such service'"
**Cause:** Target Worker not deployed, name mismatch, environment mismatch
**Solution:**
```bash
# List deployed Workers
npx wrangler deployments list --name=target-worker
# Check service binding config
cat wrangler.jsonc | grep -A2 services
# Deploy target first
cd ../target-worker && npx wrangler deploy
```
### "Rate limit exceeded" on KV writes
**Cause:** >1 write/second per key
**Solution:** Use different keys, Durable Objects, or Queues
## Type Safety Gotchas
### Missing @cloudflare/workers-types
**Error:** `Cannot find name 'Request'`
**Solution:** `npm install -D @cloudflare/workers-types`, add to tsconfig.json `"types"`
### Binding Type Mismatches
```typescript
// ❌ Wrong - KV returns string | null
const value: string = await env.MY_KV.get('key');
// ✅ Handle null
const value = await env.MY_KV.get('key');
if (!value) return new Response('Not found', { status: 404 });
```
## Environment Gotchas
### Wrong Environment Deployed
**Solution:** Check `npx wrangler deployments list`, use `--env` flag
### Secrets Not Per-Environment
**Solution:** Set per environment: `npx wrangler secret put API_KEY --env staging`
## Development Gotchas
**wrangler dev vs deploy:**
- dev: Uses `preview_id` or local bindings, secrets not available
- deploy: Uses production `id`, secrets available
**Access secrets in dev:** `npx wrangler dev --remote`
**Persist local data:** `npx wrangler dev --persist`
## Performance Gotchas
### Sequential Binding Calls
```typescript
// ❌ Slow
const user = await env.DB.prepare('...').first();
const config = await env.MY_KV.get('config');
// ✅ Parallel
const [user, config] = await Promise.all([
env.DB.prepare('...').first(),
env.MY_KV.get('config')
]);
```
## Security Gotchas
**❌ Secrets in logs:** `console.log('Key:', env.API_KEY)` - visible in dashboard
**✅** `console.log('Key:', env.API_KEY ? '***' : 'missing')`
**❌ Exposing env:** `return Response.json(env)` - exposes all bindings
**✅** Never return env object in responses
## Limits Reference
| Resource | Limit | Impact | Plan |
|----------|-------|--------|------|
| **Bindings per Worker** | 64 total | All binding types combined | All |
| **Environment variables** | 64 max, 5KB each | Per Worker | All |
| **Secret size** | 1KB | Per secret | All |
| **KV key size** | 512 bytes | UTF-8 encoded | All |
| **KV value size** | 25 MB | Per value | All |
| **KV writes per key** | 1/second | Per key; exceeding = 429 error | All |
| **KV list() results** | 1000 keys | Per call; use cursor for more | All |
| **KV operations** | 1000 reads/day | Free tier only | Free |
| **R2 object size** | 5 TB | Per object | All |
| **R2 operations** | 1M Class A/month free | Writes | All |
| **D1 database size** | 10 GB | Per database | All |
| **D1 rows per query** | 100,000 | Result set limit | All |
| **D1 databases** | 10 | Free tier | Free |
| **Queue batch size** | 100 messages | Per consumer batch | All |
| **Queue message size** | 128 KB | Per message | All |
| **Service binding calls** | Unlimited | Counts toward CPU time | All |
| **Durable Objects** | 1M requests/month free | First 1M | Free |
## Debugging Tips
```bash
# Check configuration
npx wrangler deploy --dry-run # Validate config without deploying
npx wrangler kv namespace list # List KV namespaces
npx wrangler secret list # List secrets (not values)
npx wrangler deployments list # Recent deployments
# Inspect bindings
npx wrangler kv key list --binding=MY_KV
npx wrangler kv key get --binding=MY_KV "key-name"
npx wrangler r2 object get my-bucket/file.txt
npx wrangler d1 execute my-db --command="SELECT * FROM sqlite_master"
# Test locally
npx wrangler dev # Local mode
npx wrangler dev --remote # Production bindings
npx wrangler dev --persist # Persist data across restarts
# Verify types
npx wrangler types
cat .wrangler/types/runtime.d.ts | grep "interface Env"
# Debug specific binding issues
npx wrangler tail # Stream logs in real-time
npx wrangler tail --format=pretty # Formatted logs
```
## See Also
- [Workers Limits](https://developers.cloudflare.com/workers/platform/limits/)
- [Wrangler Commands](https://developers.cloudflare.com/workers/wrangler/commands/)

View File

@@ -0,0 +1,200 @@
# Binding Patterns and Best Practices
## Service Binding Patterns
### RPC via Service Bindings
```typescript
// auth-worker
export default {
async fetch(request: Request, env: Env) {
const token = request.headers.get('Authorization');
return new Response(JSON.stringify({ valid: await validateToken(token) }));
}
}
// api-worker
const response = await env.AUTH_SERVICE.fetch(
new Request('https://fake-host/validate', {
headers: { 'Authorization': token }
})
);
```
**Why RPC?** Zero latency (same datacenter), no DNS, free, type-safe.
**HTTP vs Service:**
```typescript
// ❌ HTTP (slow, paid, cross-region latency)
await fetch('https://auth-worker.example.com/validate');
// ✅ Service binding (fast, free, same isolate)
await env.AUTH_SERVICE.fetch(new Request('https://fake-host/validate'));
```
**URL doesn't matter:** Service bindings ignore hostname/protocol, routing happens via binding name.
### Typed Service RPC
```typescript
// shared-types.ts
export interface AuthRequest { token: string; }
export interface AuthResponse { valid: boolean; userId?: string; }
// auth-worker
export default {
async fetch(request: Request): Promise<Response> {
const body: AuthRequest = await request.json();
const response: AuthResponse = { valid: true, userId: '123' };
return Response.json(response);
}
}
// api-worker
const response = await env.AUTH_SERVICE.fetch(
new Request('https://fake/validate', {
method: 'POST',
body: JSON.stringify({ token } satisfies AuthRequest)
})
);
const data: AuthResponse = await response.json();
```
## Secrets Management
```bash
# Set secret
npx wrangler secret put API_KEY
cat api-key.txt | npx wrangler secret put API_KEY
npx wrangler secret put API_KEY --env staging
```
```typescript
// Use secret
const response = await fetch('https://api.example.com', {
headers: { 'Authorization': `Bearer ${env.API_KEY}` }
});
```
**Never commit secrets:**
```jsonc
// ❌ NEVER
{ "vars": { "API_KEY": "sk_live_abc123" } }
```
## Testing with Mock Bindings
### Vitest Mock
```typescript
import { vi } from 'vitest';
const mockKV: KVNamespace = {
get: vi.fn(async (key) => key === 'test' ? 'value' : null),
put: vi.fn(async () => {}),
delete: vi.fn(async () => {}),
list: vi.fn(async () => ({ keys: [], list_complete: true, cursor: '' })),
getWithMetadata: vi.fn(),
} as unknown as KVNamespace;
const mockEnv: Env = { MY_KV: mockKV };
const mockCtx: ExecutionContext = {
waitUntil: vi.fn(),
passThroughOnException: vi.fn(),
};
const response = await worker.fetch(
new Request('http://localhost/test'),
mockEnv,
mockCtx
);
```
## Binding Access Patterns
### Lazy Access
```typescript
// ✅ Access only when needed
if (url.pathname === '/cached') {
const cached = await env.MY_KV.get('data');
if (cached) return new Response(cached);
}
```
### Parallel Access
```typescript
// ✅ Parallelize independent calls
const [user, config, cache] = await Promise.all([
env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first(),
env.MY_KV.get('config'),
env.CACHE.get('data')
]);
```
## Storage Selection
### KV: CDN-Backed Reads
```typescript
const config = await env.MY_KV.get('app-config', { type: 'json' });
```
**Use when:** Read-heavy, <25MB, global distribution, eventual consistency OK
**Latency:** <10ms reads (cached), writes eventually consistent (60s)
### D1: Relational Queries
```typescript
const results = await env.DB.prepare(`
SELECT u.name, COUNT(o.id) FROM users u
LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id
`).all();
```
**Use when:** Relational data, JOINs, ACID transactions
**Limits:** 10GB database size, 100k rows per query
### R2: Large Objects
```typescript
const object = await env.MY_BUCKET.get('large-file.zip');
return new Response(object.body);
```
**Use when:** Files >25MB, S3-compatible API needed
**Limits:** 5TB per object, unlimited storage
### Durable Objects: Coordination
```typescript
const id = env.COUNTER.idFromName('global');
const stub = env.COUNTER.get(id);
await stub.fetch(new Request('https://fake/increment'));
```
**Use when:** Strong consistency, real-time coordination, WebSocket state
**Guarantees:** Single-threaded execution, transactional storage
## Anti-Patterns
**❌ Hardcoding credentials:** `const apiKey = 'sk_live_abc123'`
**✅** `npx wrangler secret put API_KEY`
**❌ Using REST API:** `fetch('https://api.cloudflare.com/.../kv/...')`
**✅** `env.MY_KV.get('key')`
**❌ Polling storage:** `setInterval(() => env.KV.get('config'), 1000)`
**✅** Use Durable Objects for real-time state
**❌ Large data in vars:** `{ "vars": { "HUGE_CONFIG": "..." } }` (5KB max)
**✅** `env.MY_KV.put('config', data)`
**❌ Caching env globally:** `const apiKey = env.API_KEY` outside fetch()
**✅** Access `env.API_KEY` per-request inside fetch()
## See Also
- [Service Bindings Docs](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/)
- [Miniflare Testing](https://miniflare.dev/)