mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 06:11:27 -07:00
update skills
This commit is contained in:
198
.agents/skills/cloudflare-deploy/references/workers/patterns.md
Normal file
198
.agents/skills/cloudflare-deploy/references/workers/patterns.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Workers Patterns
|
||||
|
||||
## Error Handling
|
||||
|
||||
```typescript
|
||||
class HTTPError extends Error {
|
||||
constructor(public status: number, message: string) { super(message); }
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
try {
|
||||
return await handleRequest(request, env);
|
||||
} catch (error) {
|
||||
if (error instanceof HTTPError) {
|
||||
return new Response(JSON.stringify({ error: error.message }), {
|
||||
status: error.status, headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
return new Response('Internal Server Error', { status: 500 });
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## CORS
|
||||
|
||||
```typescript
|
||||
const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' };
|
||||
if (request.method === 'OPTIONS') return new Response(null, { headers: corsHeaders });
|
||||
```
|
||||
|
||||
## Routing
|
||||
|
||||
```typescript
|
||||
const router = { 'GET /api/users': handleGetUsers, 'POST /api/users': handleCreateUser };
|
||||
|
||||
const handler = router[`${request.method} ${url.pathname}`];
|
||||
return handler ? handler(request, env) : new Response('Not Found', { status: 404 });
|
||||
```
|
||||
|
||||
**Production**: Use Hono, itty-router, or Worktop (see [frameworks.md](./frameworks.md))
|
||||
|
||||
## Request Validation (Zod)
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
|
||||
const userSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
email: z.string().email(),
|
||||
age: z.number().int().positive().optional(),
|
||||
});
|
||||
|
||||
async function handleCreateUser(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const validated = userSchema.parse(body); // Throws on invalid data
|
||||
return new Response(JSON.stringify({ id: 1, ...validated }), {
|
||||
status: 201,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof z.ZodError) {
|
||||
return new Response(JSON.stringify({ errors: err.errors }), { status: 400 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**With Hono**: Use `@hono/zod-validator` for automatic validation (see [frameworks.md](./frameworks.md))
|
||||
|
||||
## Performance
|
||||
|
||||
```typescript
|
||||
// ❌ Sequential
|
||||
const user = await fetch('/api/user/1');
|
||||
const posts = await fetch('/api/posts?user=1');
|
||||
|
||||
// ✅ Parallel
|
||||
const [user, posts] = await Promise.all([fetch('/api/user/1'), fetch('/api/posts?user=1')]);
|
||||
```
|
||||
|
||||
## Streaming
|
||||
|
||||
```typescript
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
controller.enqueue(new TextEncoder().encode(`Item ${i}\n`));
|
||||
if (i % 100 === 0) await new Promise(r => setTimeout(r, 0));
|
||||
}
|
||||
controller.close();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Transform Streams
|
||||
|
||||
```typescript
|
||||
response.body.pipeThrough(new TextDecoderStream()).pipeThrough(
|
||||
new TransformStream({ transform(chunk, c) { c.enqueue(chunk.toUpperCase()); } })
|
||||
).pipeThrough(new TextEncoderStream());
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import worker from '../src/index';
|
||||
|
||||
describe('Worker', () => {
|
||||
it('returns 200', async () => {
|
||||
const req = new Request('http://localhost/');
|
||||
const env = { MY_VAR: 'test' };
|
||||
const ctx = { waitUntil: () => {}, passThroughOnException: () => {} };
|
||||
expect((await worker.fetch(req, env, ctx)).status).toBe(200);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
npx wrangler deploy # production
|
||||
npx wrangler deploy --env staging
|
||||
npx wrangler versions upload --message "Add feature"
|
||||
npx wrangler rollback
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
```typescript
|
||||
const start = Date.now();
|
||||
const response = await handleRequest(request, env);
|
||||
ctx.waitUntil(env.ANALYTICS.writeDataPoint({
|
||||
doubles: [Date.now() - start], blobs: [request.url, String(response.status)]
|
||||
}));
|
||||
```
|
||||
|
||||
## Security & Rate Limiting
|
||||
|
||||
```typescript
|
||||
// Security headers
|
||||
const security = { 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY' };
|
||||
|
||||
// Auth
|
||||
const auth = request.headers.get('Authorization');
|
||||
if (!auth?.startsWith('Bearer ')) return new Response('Unauthorized', { status: 401 });
|
||||
|
||||
// Gradual rollouts (deterministic user bucketing)
|
||||
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(userId));
|
||||
if (new Uint8Array(hash)[0] % 100 < rolloutPercent) return newFeature(request);
|
||||
```
|
||||
|
||||
Rate limiting: See [Durable Objects](../durable-objects/README.md)
|
||||
|
||||
## R2 Multipart Upload
|
||||
|
||||
```typescript
|
||||
// For files > 100MB
|
||||
const upload = await env.MY_BUCKET.createMultipartUpload('large-file.bin');
|
||||
try {
|
||||
const parts = [];
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
parts.push(await upload.uploadPart(i + 1, chunks[i]));
|
||||
}
|
||||
await upload.complete(parts);
|
||||
} catch (err) { await upload.abort(); throw err; }
|
||||
```
|
||||
|
||||
Parallel uploads, resume on failure, handle files > 5GB
|
||||
|
||||
## Workflows (Step Orchestration)
|
||||
|
||||
```typescript
|
||||
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
|
||||
|
||||
export class MyWorkflow extends WorkflowEntrypoint {
|
||||
async run(event: WorkflowEvent<{ userId: string }>, step: WorkflowStep) {
|
||||
const user = await step.do('fetch-user', async () =>
|
||||
fetch(`/api/users/${event.payload.userId}`).then(r => r.json())
|
||||
);
|
||||
await step.sleep('wait', '1 hour');
|
||||
await step.do('notify', async () => sendEmail(user.email));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Multi-step jobs with automatic retries, state persistence, resume from failure
|
||||
|
||||
## See Also
|
||||
|
||||
- [API](./api.md) - Runtime APIs
|
||||
- [Gotchas](./gotchas.md) - Common issues
|
||||
- [Configuration](./configuration.md) - Setup
|
||||
- [Frameworks](./frameworks.md) - Hono, routing, validation
|
||||
Reference in New Issue
Block a user