mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 18:11:27 -07:00
update skills
This commit is contained in:
122
.agents/skills/cloudflare-deploy/references/bindings/README.md
Normal file
122
.agents/skills/cloudflare-deploy/references/bindings/README.md
Normal 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/)
|
||||
203
.agents/skills/cloudflare-deploy/references/bindings/api.md
Normal file
203
.agents/skills/cloudflare-deploy/references/bindings/api.md
Normal 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)
|
||||
@@ -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/)
|
||||
208
.agents/skills/cloudflare-deploy/references/bindings/gotchas.md
Normal file
208
.agents/skills/cloudflare-deploy/references/bindings/gotchas.md
Normal 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/)
|
||||
200
.agents/skills/cloudflare-deploy/references/bindings/patterns.md
Normal file
200
.agents/skills/cloudflare-deploy/references/bindings/patterns.md
Normal 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/)
|
||||
Reference in New Issue
Block a user