본문으로 건너뛰기
cccskills
무료GitHub에 공개

cloudflare-workers

Cloudflare Workers edge computing — Durable Objects, KV, R2, D1, Workers AI, AI Gateway

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(10)

  • SKILL.md14.2 KB
  • commands/cloudflare-workers.md118 B
  • hooks/post-execute.cjs189 B
  • hooks/pre-execute.cjs295 B
  • references/research-requirements.md928 B
  • rules/cloudflare-workers.md697 B
  • schemas/input.schema.json549 B
  • schemas/output.schema.json363 B
  • scripts/main.cjs588 B
  • templates/implementation-template.md366 B

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Cloudflare Workers Skill

Purpose

Expert patterns for building Cloudflare Workers applications including Durable Objects for stateful coordination, KV/R2/D1 storage tiers, Workers AI inference, and AI Gateway for LLM routing.

When to Invoke

Skill({ skill: 'cloudflare-workers' })

Invoke when:

  • Building or debugging Cloudflare Workers scripts
  • Designing Durable Object state coordination
  • Choosing between KV, R2, and D1 storage
  • Integrating Workers AI or AI Gateway
  • Configuring wrangler.toml bindings

Core Primitives

Workers Script Structure

export interface Env {
  MY_KV: KVNamespace;
  MY_R2: R2Bucket;
  MY_D1: D1Database;
  MY_DO: DurableObjectNamespace;
  AI: Ai;
  AI_GATEWAY_ID: string;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    // Route to Durable Object for stateful operations
    if (url.pathname.startsWith('/room/')) {
      const id = env.MY_DO.idFromName(url.pathname);
      const stub = env.MY_DO.get(id);
      return stub.fetch(request);
    }

    return new Response('OK');
  },
};

Durable Objects

Use Durable Objects for stateful coordination, real-time collaboration, and rate limiting:

export class RoomDO implements DurableObject {
  private sessions: Map<WebSocket, { id: string }> = new Map();
  private state: DurableObjectState;

  constructor(state: DurableObjectState, env: Env) {
    this.state = state;
    // Restore hibernated WebSockets on wake
    this.state.getWebSockets().forEach(ws => {
      this.sessions.set(ws, ws.deserializeAttachment());
    });
  }

  async fetch(request: Request): Promise<Response> {
    if (request.headers.get('Upgrade') === 'websocket') {
      const [client, server] = Object.values(new WebSocketPair());
      // Use hibernation API — do NOT call accept() for hibernatable sockets
      this.state.acceptWebSocket(server);
      server.serializeAttachment({ id: crypto.randomUUID() });
      return new Response(null, { status: 101, webSocket: client });
    }
    return new Response('Expected WebSocket', { status: 426 });
  }

  webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): void {
    // Broadcast to all connected sessions
    this.sessions.forEach((_, session) => {
      if (session !== ws && session.readyState === WebSocket.READY_STATE_OPEN) {
        session.send(message);
      }
    });
  }

  webSocketClose(ws: WebSocket): void {
    this.sessions.delete(ws);
  }

  // Persistent storage via transactional SQL
  async persistState(key: string, value: unknown): Promise<void> {
    await this.state.storage.put(key, value);
  }
}

Durable Object Rules:

  • Use state.acceptWebSocket() (hibernation API) instead of ws.accept() — saves CPU costs during idle periods
  • Use state.storage.transaction() for atomic multi-key writes
  • One DO instance per logical entity (room, user session, rate limit bucket)
  • DO IDs from idFromName() are deterministic — same name always routes to same instance
  • Never store large blobs in DO storage — use R2 instead, store only the key reference

Storage Tiers

StorageUse CaseConsistencyLatency
KVConfig, feature flags, cached dataEventual (60s)~1ms read
R2Files, images, large objectsStrong~10ms
D1Relational data, queriesStrong~5ms
DO StoragePer-entity state, coordinationStrong (per-DO)~1ms
// KV — best for reads, not writes
const value = await env.MY_KV.get('config:feature-flags', 'json');
await env.MY_KV.put('session:abc', JSON.stringify(data), { expirationTtl: 3600 });

// R2 — S3-compatible object storage
const object = await env.MY_R2.get('uploads/image.png');
if (object) {
  return new Response(object.body, {
    headers: { 'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream' },
  });
}
await env.MY_R2.put('uploads/image.png', request.body, {
  httpMetadata: { contentType: 'image/png' },
});

// D1 — SQLite at the edge
const { results } = await env.MY_D1.prepare('SELECT * FROM users WHERE id = ?').bind(userId).all();

// Batch D1 writes for efficiency
await env.MY_D1.batch([
  env.MY_D1.prepare('INSERT INTO events (type, ts) VALUES (?, ?)').bind('click', Date.now()),
  env.MY_D1.prepare('UPDATE counters SET n = n + 1 WHERE key = ?').bind('clicks'),
]);

Workers AI

// Text generation
const response = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
  messages: [
    { role: 'system', content: 'You are a helpful assistant.' },
    { role: 'user', content: prompt },
  ],
  max_tokens: 512,
  stream: true,
});

// Return streaming response directly
return new Response(response as ReadableStream, {
  headers: { 'Content-Type': 'text/event-stream' },
});

// Embeddings
const { data } = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
  text: [document1, document2],
});
const embeddings = data; // Float32Array[]

// Image classification
const result = await env.AI.run('@cf/microsoft/resnet-50', {
  image: [...new Uint8Array(await request.arrayBuffer())],
});

AI Gateway

Route LLM requests through AI Gateway for caching, rate limiting, and observability:

// Use AI Gateway endpoint instead of direct provider URL
const gatewayUrl = `https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.AI_GATEWAY_ID}/openai`;

const response = await fetch(`${gatewayUrl}/chat/completions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${env.OPENAI_API_KEY}`,
    'Content-Type': 'application/json',
    // Optional: cache control
    'cf-aig-cache-ttl': '3600',
    'cf-aig-skip-cache': 'false',
  },
  body: JSON.stringify({
    model: 'gpt-4o-mini',
    messages: [{ role: 'user', content: prompt }],
  }),
});

// AI Gateway supports multiple providers in one config
// openai, anthropic, google-ai-studio, azure-openai, workers-ai

Deployment

wrangler.toml

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat"]

# KV namespace binding
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123..."
preview_id = "def456..."

# R2 bucket binding
[[r2_buckets]]
binding = "MY_R2"
bucket_name = "my-bucket"
preview_bucket_name = "my-bucket-dev"

# D1 database binding
[[d1_databases]]
binding = "MY_D1"
database_name = "my-db"
database_id = "ghi789..."

# Durable Object binding + migration
[[durable_objects.bindings]]
name = "MY_DO"
class_name = "RoomDO"

[[migrations]]
tag = "v1"
new_classes = ["RoomDO"]

# Workers AI binding
[ai]
binding = "AI"

# Environment variables (non-secret)
[vars]
AI_GATEWAY_ID = "my-gateway"

# Secrets set via: wrangler secret put SECRET_NAME

CLI Commands

# Development
wrangler dev                          # Local dev server
wrangler dev --remote                 # Dev against production bindings

# Deployment
wrangler deploy                       # Deploy to production
wrangler deploy --env staging         # Deploy to staging environment

# Type generation (ALWAYS run after editing wrangler.toml/wrangler.jsonc)
wrangler types                        # Generates worker-configuration.d.ts — never hand-write Env interface

# Storage management
wrangler kv:key put --binding MY_KV "key" "value"
wrangler kv:key get --binding MY_KV "key"
wrangler r2 object put my-bucket/path/file.txt --file ./local-file.txt
wrangler d1 execute my-db --file ./migrations/001.sql
wrangler d1 execute my-db --command "SELECT * FROM users LIMIT 5"

# Secrets
wrangler secret put OPENAI_API_KEY    # Prompts for value
wrangler secret list

# Durable Objects
wrangler durable-objects migrate apply  # Apply pending migrations

# Logs and observability
wrangler tail                         # Stream live logs from production
wrangler tail --format pretty
wrangler tail --json                  # Structured JSON log stream for analysis

Performance Patterns

// Use ctx.waitUntil() for non-blocking background work
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const response = await handleRequest(request, env);

    // Fire-and-forget: analytics, cache warm, audit logging
    ctx.waitUntil(logAnalytics(request, response, env));

    return response;
  },
};

// Stream large bodies — never buffer fully into memory
async function streamBody(request: Request, env: Env): Promise<Response> {
  const { readable, writable } = new TransformStream();
  // Pipe without buffering — stays within 128MB Worker limit
  request.body?.pipeTo(writable);
  return new Response(readable);
}

Durable Objects with SQLite (2025 default):

New Durable Objects should use the SQL API for storage — SQLite-backed DOs provide relational queries, indexes, and transactions:

export class RoomDO implements DurableObject {
  private sql: SqlStorage;

  constructor(state: DurableObjectState, env: Env) {
    this.state = state;
    this.sql = state.storage.sql;
    // Create tables on first initialization
    this.sql.exec(
      `CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY, ts INTEGER, body TEXT)`
    );
  }

  async addMessage(body: string): Promise<void> {
    this.sql.exec('INSERT INTO messages (ts, body) VALUES (?, ?)', Date.now(), body);
  }

  async getMessages(): Promise<{ id: number; ts: number; body: string }[]> {
    return [...this.sql.exec('SELECT * FROM messages ORDER BY ts DESC LIMIT 50')];
  }
}

Observability

Enable Workers Logs and Traces before any production deployment:

# wrangler.toml
[observability]
enabled = true
head_sampling_rate = 1  # 0.0–1.0; reduce for high-volume workers
// Structured logging — searchable in Workers dashboard
console.log(JSON.stringify({ level: 'info', requestId: crypto.randomUUID(), path: url.pathname }));
console.error(JSON.stringify({ level: 'error', message: err.message, stack: err.stack }));

Never use passThroughOnException() — it hides bugs. Use explicit try/catch with structured error responses.


Anti-Patterns

  • Never use setTimeout/setInterval in Workers — use Cron Triggers or Durable Object alarms instead
  • Never store secrets in wrangler.toml vars — use wrangler secret put for sensitive values
  • Never use ws.accept() for Durable Object WebSockets — use state.acceptWebSocket() for hibernation
  • Never read KV in hot loops — KV has a 1000 reads/second soft limit per worker; cache in memory for the request lifetime
  • Never write to D1 on every request — batch writes or use a queue (Workers Queue) for high-throughput write paths
  • Never exceed 128MB script size — keep Workers bundles lean; offload large assets to R2
  • Never rely on in-memory state between requests — Workers are stateless; use KV/D1/DO for persistence
  • Never store request-scoped data in module-level variables — isolates reuse across requests causing data leaks; pass state via function arguments
  • Never use passThroughOnException() — it hides bugs silently; use explicit try/catch blocks
  • Never hand-write the Env interface — run wrangler types to generate it from your actual config
  • Never use Math.random() for security tokens — use crypto.randomUUID() or crypto.getRandomValues()

Testing

// Use Vitest + @cloudflare/vitest-pool-workers for unit tests
import { env, SELF } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';

describe('Worker', () => {
  it('responds with 200', async () => {
    const response = await SELF.fetch('https://example.com/');
    expect(response.status).toBe(200);
  });

  it('reads from KV', async () => {
    await env.MY_KV.put('test-key', 'test-value');
    const response = await SELF.fetch('https://example.com/kv/test-key');
    expect(await response.text()).toBe('test-value');
  });
});
# Run tests
pnpm vitest run

# Run with Cloudflare runtime (recommended)
pnpm vitest run --pool @cloudflare/vitest-pool-workers

Related Skills

  • devops — CI/CD pipeline configuration for Cloudflare deployments
  • terraform-infra — Cloudflare Terraform provider for infrastructure-as-code
  • database-expert — D1 schema design and query optimization
  • container-expert — Cloudflare Containers (complementary to Workers)

Search Protocol

Before starting any Cloudflare Workers task, search for existing wrangler configs and worker scripts:

pnpm search:code "wrangler OR DurableObject OR KVNamespace OR R2Bucket"
pnpm search:code "cloudflare workers"

Use Skill({ skill: 'ripgrep' }) for fast search across .toml and .ts files. Use Skill({ skill: 'code-semantic-search' }) to find similar edge function patterns.


Memory Protocol (MANDATORY)

Before starting any task, you must query semantic memory and read recent static memory:

node .claude/lib/memory/memory-search.cjs "cloudflare workers durable objects KV R2 D1"

Read .claude/context/memory/learnings.md Read .claude/context/memory/decisions.md

Check for Cloudflare-specific gotchas (wrangler version issues, D1 migration quirks, DO hibernation bugs, SQLite API changes).

After completing work, record findings:

  • Cloudflare Workers gotchas or undocumented behavior -> Append to .claude/context/memory/issues.md
  • Storage tier decisions (KV vs D1 vs DO) -> Update .claude/context/memory/decisions.md
  • Wrangler version-specific issues -> Append to .claude/context/memory/learnings.md

During long tasks: Use .claude/context/memory/active_context.md as scratchpad.

ASSUME INTERRUPTION: If it's not in memory, it didn't happen.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Ensure accessibility in UI components including semantic HTML, ARIA attributes, keyboard navigation, and WCAG 2.2 AA compliance.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

Use when you want to improve response quality through meta-cognitive reasoning. Applies 15+ reasoning methods to reconsider and refine initial outputs.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

N-round opposing-stance debates for trade-off analysis. Assigns pro/con roles to agents, runs structured debate rounds with quality scoring, and produces a moderator synthesis with confidence-rated recommendation. Generalizable to architecture, technology, security, and design decisions.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

Force adversarial code review stance that eliminates confirmation bias — reviewer must find issues or re-analyze

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

Creates specialized AI agents on-demand when no existing agent matches a request. Use when the Router cannot find a suitable agent for a task. Enables self-evolution by generating persistent agents.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

LLM-as-judge evaluation framework with 5-dimension rubric (accuracy, groundedness, coherence, completeness, helpfulness) for scoring AI-generated content quality with weighted composite scores and evidence citations

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

oimiragieo/agent-studio422026년 7월 14일 업데이트

oimiragieo의 스킬 모두 보기

이 스킬의 문제 신고