# 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)
