Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/pulumi/patterns.md
1# Architecture Patterns23## Component Resources45```typescript6class WorkerApp extends pulumi.ComponentResource {7constructor(name: string, args: WorkerAppArgs, opts?) {8super("custom:cloudflare:WorkerApp", name, {}, opts);9const defaultOpts = {parent: this};1011this.kv = new cloudflare.WorkersKvNamespace(`${name}-kv`, {accountId: args.accountId, title: `${name}-kv`}, defaultOpts);12this.worker = new cloudflare.WorkerScript(`${name}-worker`, {13accountId: args.accountId, name: `${name}-worker`, content: args.workerCode,14module: true, kvNamespaceBindings: [{name: "KV", namespaceId: this.kv.id}],15}, defaultOpts);16this.domain = new cloudflare.WorkersDomain(`${name}-domain`, {17accountId: args.accountId, hostname: args.domain, service: this.worker.name,18}, defaultOpts);19}20}21```2223## Full-Stack Worker App2425```typescript26const kv = new cloudflare.WorkersKvNamespace("cache", {accountId, title: "api-cache"});27const db = new cloudflare.D1Database("db", {accountId, name: "app-database"});28const bucket = new cloudflare.R2Bucket("assets", {accountId, name: "app-assets"});2930const apiWorker = new cloudflare.WorkerScript("api", {31accountId, name: "api-worker", content: fs.readFileSync("./dist/api.js", "utf8"),32module: true, kvNamespaceBindings: [{name: "CACHE", namespaceId: kv.id}],33d1DatabaseBindings: [{name: "DB", databaseId: db.id}],34r2BucketBindings: [{name: "ASSETS", bucketName: bucket.name}],35});36```3738## Multi-Environment Setup3940```typescript41const stack = pulumi.getStack();42const worker = new cloudflare.WorkerScript(`worker-${stack}`, {43accountId, name: `my-worker-${stack}`, content: code,44plainTextBindings: [{name: "ENVIRONMENT", text: stack}],45});46```4748## Queue-Based Processing4950```typescript51const queue = new cloudflare.Queue("processing-queue", {accountId, name: "image-processing"});5253// Producer: API receives requests54const apiWorker = new cloudflare.WorkerScript("api", {55accountId, name: "api-worker", content: apiCode,56queueBindings: [{name: "PROCESSING_QUEUE", queue: queue.id}],57});5859// Consumer: Process async60const processorWorker = new cloudflare.WorkerScript("processor", {61accountId, name: "processor-worker", content: processorCode,62queueConsumers: [{queue: queue.name, maxBatchSize: 10, maxRetries: 3, maxWaitTimeMs: 5000}],63r2BucketBindings: [{name: "OUTPUT_BUCKET", bucketName: outputBucket.name}],64});65```6667## Microservices with Service Bindings6869```typescript70const authWorker = new cloudflare.WorkerScript("auth", {accountId, name: "auth-service", content: authCode});71const apiWorker = new cloudflare.WorkerScript("api", {72accountId, name: "api-service", content: apiCode,73serviceBindings: [{name: "AUTH", service: authWorker.name}],74});75```7677## Event-Driven Architecture7879```typescript80const eventQueue = new cloudflare.Queue("events", {accountId, name: "event-bus"});81const producer = new cloudflare.WorkerScript("producer", {82accountId, name: "api-producer", content: producerCode,83queueBindings: [{name: "EVENTS", queue: eventQueue.id}],84});85const consumer = new cloudflare.WorkerScript("consumer", {86accountId, name: "email-consumer", content: consumerCode,87queueConsumers: [{queue: eventQueue.name, maxBatchSize: 10}],88});89```9091## v6.x Versioned Deployments (Blue-Green/Canary)9293```typescript94const worker = new cloudflare.Worker("api", {accountId, name: "api-worker"});95const v1 = new cloudflare.WorkerVersion("v1", {accountId, workerId: worker.id, content: fs.readFileSync("./dist/v1.js", "utf8"), compatibilityDate: "2025-01-01"});96const v2 = new cloudflare.WorkerVersion("v2", {accountId, workerId: worker.id, content: fs.readFileSync("./dist/v2.js", "utf8"), compatibilityDate: "2025-01-01"});9798// Gradual rollout: 10% v2, 90% v199const deployment = new cloudflare.WorkersDeployment("canary", {100accountId, workerId: worker.id,101versions: [{versionId: v2.id, percentage: 10}, {versionId: v1.id, percentage: 90}],102kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}],103});104```105106**Use:** Canary releases, A/B testing, blue-green. Most apps use `WorkerScript` (auto-versioning).107108## Wrangler.toml Generation (Bridge IaC with Local Dev)109110Generate wrangler.toml from Pulumi config to keep local dev in sync:111112```typescript113import * as command from "@pulumi/command";114115const workerConfig = {116name: "my-worker",117compatibilityDate: "2025-01-01",118compatibilityFlags: ["nodejs_compat"],119};120121// Create resources122const kv = new cloudflare.WorkersKvNamespace("kv", {accountId, title: "my-kv"});123const db = new cloudflare.D1Database("db", {accountId, name: "my-db"});124const bucket = new cloudflare.R2Bucket("bucket", {accountId, name: "my-bucket"});125126// Generate wrangler.toml after resources created127const wranglerGen = new command.local.Command("gen-wrangler", {128create: pulumi.interpolate`cat > wrangler.toml <<EOF129name = "${workerConfig.name}"130main = "src/index.ts"131compatibility_date = "${workerConfig.compatibilityDate}"132compatibility_flags = ${JSON.stringify(workerConfig.compatibilityFlags)}133134[[kv_namespaces]]135binding = "MY_KV"136id = "${kv.id}"137138[[d1_databases]]139binding = "DB"140database_id = "${db.id}"141database_name = "${db.name}"142143[[r2_buckets]]144binding = "MY_BUCKET"145bucket_name = "${bucket.name}"146EOF`,147}, {dependsOn: [kv, db, bucket]});148149// Deploy worker after wrangler.toml generated150const worker = new cloudflare.WorkerScript("worker", {151accountId, name: workerConfig.name, content: code,152compatibilityDate: workerConfig.compatibilityDate,153compatibilityFlags: workerConfig.compatibilityFlags,154kvNamespaceBindings: [{name: "MY_KV", namespaceId: kv.id}],155d1DatabaseBindings: [{name: "DB", databaseId: db.id}],156r2BucketBindings: [{name: "MY_BUCKET", bucketName: bucket.name}],157}, {dependsOn: [wranglerGen]});158```159160**Benefits:**161- `wrangler dev` uses same bindings as production162- No config drift between Pulumi and local dev163- Single source of truth (Pulumi config)164165**Alternative:** Read wrangler.toml in Pulumi (reverse direction) if wrangler is source of truth166167## Build + Deploy Pattern168169```typescript170import * as command from "@pulumi/command";171const build = new command.local.Command("build", {create: "npm run build", dir: "./worker"});172const worker = new cloudflare.WorkerScript("worker", {173accountId, name: "my-worker",174content: build.stdout.apply(() => fs.readFileSync("./worker/dist/index.js", "utf8")),175}, {dependsOn: [build]});176```177178## Content SHA Pattern (Force Updates)179180Prevent false "no changes" detections:181182```typescript183const version = Date.now().toString();184const worker = new cloudflare.WorkerScript("worker", {185accountId, name: "my-worker", content: code,186plainTextBindings: [{name: "VERSION", text: version}], // Forces deployment187});188```189190---191See: [README.md](./README.md), [configuration.md](./configuration.md), [api.md](./api.md), [gotchas.md](./gotchas.md)192