mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-20 18:11:27 -07:00
update skills
This commit is contained in:
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