# Architecture Patterns

## Component Resources

```typescript
class WorkerApp extends pulumi.ComponentResource {
  constructor(name: string, args: WorkerAppArgs, opts?) {
    super("custom:cloudflare:WorkerApp", name, {}, opts);
    const defaultOpts = { parent: this };

    this.kv = new cloudflare.WorkersKvNamespace(
      `${name}-kv`,
      { accountId: args.accountId, title: `${name}-kv` },
      defaultOpts
    );
    this.worker = new cloudflare.WorkerScript(
      `${name}-worker`,
      {
        accountId: args.accountId,
        name: `${name}-worker`,
        content: args.workerCode,
        module: true,
        kvNamespaceBindings: [{ name: "KV", namespaceId: this.kv.id }],
      },
      defaultOpts
    );
    this.domain = new cloudflare.WorkersDomain(
      `${name}-domain`,
      {
        accountId: args.accountId,
        hostname: args.domain,
        service: this.worker.name,
      },
      defaultOpts
    );
  }
}
```

## Full-Stack Worker App

```typescript
const kv = new cloudflare.WorkersKvNamespace("cache", { accountId, title: "api-cache" });
const db = new cloudflare.D1Database("db", { accountId, name: "app-database" });
const bucket = new cloudflare.R2Bucket("assets", { accountId, name: "app-assets" });

const apiWorker = new cloudflare.WorkerScript("api", {
  accountId,
  name: "api-worker",
  content: fs.readFileSync("./dist/api.js", "utf8"),
  module: true,
  kvNamespaceBindings: [{ name: "CACHE", namespaceId: kv.id }],
  d1DatabaseBindings: [{ name: "DB", databaseId: db.id }],
  r2BucketBindings: [{ name: "ASSETS", bucketName: bucket.name }],
});
```

## Multi-Environment Setup

```typescript
const stack = pulumi.getStack();
const worker = new cloudflare.WorkerScript(`worker-${stack}`, {
  accountId,
  name: `my-worker-${stack}`,
  content: code,
  plainTextBindings: [{ name: "ENVIRONMENT", text: stack }],
});
```

## Queue-Based Processing

```typescript
const queue = new cloudflare.Queue("processing-queue", { accountId, name: "image-processing" });

// Producer: API receives requests
const apiWorker = new cloudflare.WorkerScript("api", {
  accountId,
  name: "api-worker",
  content: apiCode,
  queueBindings: [{ name: "PROCESSING_QUEUE", queue: queue.id }],
});

// Consumer: Process async
const processorWorker = new cloudflare.WorkerScript("processor", {
  accountId,
  name: "processor-worker",
  content: processorCode,
  queueConsumers: [{ queue: queue.name, maxBatchSize: 10, maxRetries: 3, maxWaitTimeMs: 5000 }],
  r2BucketBindings: [{ name: "OUTPUT_BUCKET", bucketName: outputBucket.name }],
});
```

## Microservices with Service Bindings

```typescript
const authWorker = new cloudflare.WorkerScript("auth", {
  accountId,
  name: "auth-service",
  content: authCode,
});
const apiWorker = new cloudflare.WorkerScript("api", {
  accountId,
  name: "api-service",
  content: apiCode,
  serviceBindings: [{ name: "AUTH", service: authWorker.name }],
});
```

## Event-Driven Architecture

```typescript
const eventQueue = new cloudflare.Queue("events", { accountId, name: "event-bus" });
const producer = new cloudflare.WorkerScript("producer", {
  accountId,
  name: "api-producer",
  content: producerCode,
  queueBindings: [{ name: "EVENTS", queue: eventQueue.id }],
});
const consumer = new cloudflare.WorkerScript("consumer", {
  accountId,
  name: "email-consumer",
  content: consumerCode,
  queueConsumers: [{ queue: eventQueue.name, maxBatchSize: 10 }],
});
```

## v6.x Versioned Deployments (Blue-Green/Canary)

```typescript
const worker = new cloudflare.Worker("api", { accountId, name: "api-worker" });
const v1 = new cloudflare.WorkerVersion("v1", {
  accountId,
  workerId: worker.id,
  content: fs.readFileSync("./dist/v1.js", "utf8"),
  compatibilityDate: "2025-01-01",
});
const v2 = new cloudflare.WorkerVersion("v2", {
  accountId,
  workerId: worker.id,
  content: fs.readFileSync("./dist/v2.js", "utf8"),
  compatibilityDate: "2025-01-01",
});

// Gradual rollout: 10% v2, 90% v1
const deployment = new cloudflare.WorkersDeployment("canary", {
  accountId,
  workerId: worker.id,
  versions: [
    { versionId: v2.id, percentage: 10 },
    { versionId: v1.id, percentage: 90 },
  ],
  kvNamespaceBindings: [{ name: "MY_KV", namespaceId: kv.id }],
});
```

**Use:** Canary releases, A/B testing, blue-green. Most apps use `WorkerScript` (auto-versioning).

## Wrangler.toml Generation (Bridge IaC with Local Dev)

Generate wrangler.toml from Pulumi config to keep local dev in sync:

```typescript
import * as command from "@pulumi/command";

const workerConfig = {
  name: "my-worker",
  compatibilityDate: "2025-01-01",
  compatibilityFlags: ["nodejs_compat"],
};

// Create resources
const kv = new cloudflare.WorkersKvNamespace("kv", { accountId, title: "my-kv" });
const db = new cloudflare.D1Database("db", { accountId, name: "my-db" });
const bucket = new cloudflare.R2Bucket("bucket", { accountId, name: "my-bucket" });

// Generate wrangler.toml after resources created
const wranglerGen = new command.local.Command(
  "gen-wrangler",
  {
    create: pulumi.interpolate`cat > wrangler.toml <<EOF
name = "${workerConfig.name}"
main = "src/index.ts"
compatibility_date = "${workerConfig.compatibilityDate}"
compatibility_flags = ${JSON.stringify(workerConfig.compatibilityFlags)}

[[kv_namespaces]]
binding = "MY_KV"
id = "${kv.id}"

[[d1_databases]]
binding = "DB"
database_id = "${db.id}"
database_name = "${db.name}"

[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "${bucket.name}"
EOF`,
  },
  { dependsOn: [kv, db, bucket] }
);

// Deploy worker after wrangler.toml generated
const worker = new cloudflare.WorkerScript(
  "worker",
  {
    accountId,
    name: workerConfig.name,
    content: code,
    compatibilityDate: workerConfig.compatibilityDate,
    compatibilityFlags: workerConfig.compatibilityFlags,
    kvNamespaceBindings: [{ name: "MY_KV", namespaceId: kv.id }],
    d1DatabaseBindings: [{ name: "DB", databaseId: db.id }],
    r2BucketBindings: [{ name: "MY_BUCKET", bucketName: bucket.name }],
  },
  { dependsOn: [wranglerGen] }
);
```

**Benefits:**

- `wrangler dev` uses same bindings as production
- No config drift between Pulumi and local dev
- Single source of truth (Pulumi config)

**Alternative:** Read wrangler.toml in Pulumi (reverse direction) if wrangler is source of truth

## Build + Deploy Pattern

```typescript
import * as command from "@pulumi/command";
const build = new command.local.Command("build", { create: "npm run build", dir: "./worker" });
const worker = new cloudflare.WorkerScript(
  "worker",
  {
    accountId,
    name: "my-worker",
    content: build.stdout.apply(() => fs.readFileSync("./worker/dist/index.js", "utf8")),
  },
  { dependsOn: [build] }
);
```

## Content SHA Pattern (Force Updates)

Prevent false "no changes" detections:

```typescript
const version = Date.now().toString();
const worker = new cloudflare.WorkerScript("worker", {
  accountId,
  name: "my-worker",
  content: code,
  plainTextBindings: [{ name: "VERSION", text: version }], // Forces deployment
});
```

---

See: [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md), [gotchas.md](./gotchas.md)
