Curated Skills
by lstudlo

cloudflare

references/containers/patterns.md

.md 203 lines
Content
## Routing Patterns

### Session Affinity (Stateful)

```typescript
export class SessionBackend extends Container {
  defaultPort = 3000;
  sleepAfter = "30m";
}

export default {
  async fetch(request: Request, env: Env) {
    const sessionId = request.headers.get("X-Session-ID") || crypto.randomUUID();
    const container = env.SESSION_BACKEND.getByName(sessionId);
    await container.startAndWaitForPorts();
    return container.fetch(request);
  }
};
```

**Use:** User sessions, WebSocket, stateful games, per-user caching.

### Load Balancing (Stateless)

```typescript
export default {
  async fetch(request: Request, env: Env) {
    const container = env.STATELESS_API.getRandom();
    await container.startAndWaitForPorts();
    return container.fetch(request);
  }
};
```

**Use:** Stateless HTTP APIs, CPU-intensive work, read-only queries.

### Singleton Pattern

```typescript
export default {
  async fetch(request: Request, env: Env) {
    const container = env.GLOBAL_SERVICE.getByName("singleton");
    await container.startAndWaitForPorts();
    return container.fetch(request);
  }
};
```

**Use:** Global cache, centralized coordinator, single source of truth.

## WebSocket Forwarding

```typescript
export default {
  async fetch(request: Request, env: Env) {
    if (request.headers.get("Upgrade") === "websocket") {
      const sessionId = request.headers.get("X-Session-ID") || crypto.randomUUID();
      const container = env.WS_BACKEND.getByName(sessionId);
      await container.startAndWaitForPorts();
      
      // ⚠️ MUST use fetch(), not containerFetch()
      return container.fetch(request);
    }
    return new Response("Not a WebSocket request", { status: 400 });
  }
};
```

**⚠️ Critical:** Always use `fetch()` for WebSocket.

## Graceful Shutdown

```typescript
export class GracefulContainer extends Container {
  private connections = new Set<WebSocket>();

  onStop() {
    // SIGTERM received, 15 minutes until SIGKILL
    for (const ws of this.connections) {
      ws.close(1001, "Server shutting down");
    }
    this.ctx.storage.put("shutdown-time", Date.now());
  }

  onActivityExpired(): boolean {
    return this.connections.size > 0;  // Keep alive if connections
  }
}
```

## Concurrent Request Handling

```typescript
export class SafeContainer extends Container {
  private initialized = false;

  async fetch(request: Request) {
    await this.ctx.blockConcurrencyWhile(async () => {
      if (!this.initialized) {
        await this.startAndWaitForPorts();
        this.initialized = true;
      }
    });
    return super.fetch(request);
  }
}
```

**Use:** One-time initialization, preventing concurrent startup.

## Activity Timeout Renewal

```typescript
export class LongRunningContainer extends Container {
  sleepAfter = "5m";

  async processLongJob(data: unknown) {
    const interval = setInterval(() => {
      this.ctx.storage.put("keepalive", Date.now());
    }, 60000);

    try {
      await this.doLongWork(data);
    } finally {
      clearInterval(interval);
    }
  }
}
```

**Use:** Long operations exceeding `sleepAfter`.

## Multiple Port Routing

```typescript
export class MultiPortContainer extends Container {
  requiredPorts = [8080, 8081, 9090];

  async fetch(request: Request) {
    const path = new URL(request.url).pathname;
    if (path.startsWith("/grpc")) this.switchPort(8081);
    else if (path.startsWith("/metrics")) this.switchPort(9090);
    return super.fetch(request);
  }
}
```

**Use:** Multi-protocol services (HTTP + gRPC), separate metrics endpoints.

## Workflow Integration

```typescript
import { WorkflowEntrypoint } from "cloudflare:workers";

export class ProcessingWorkflow extends WorkflowEntrypoint {
  async run(event, step) {
    const container = this.env.PROCESSOR.getByName(event.payload.jobId);
    
    await step.do("start", async () => {
      await container.startAndWaitForPorts();
    });
    
    const result = await step.do("process", async () => {
      return container.fetch("/process", {
        method: "POST",
        body: JSON.stringify(event.payload.data)
      }).then(r => r.json());
    });
    
    return result;
  }
}
```

**Use:** Orchestrating multi-step container operations, durable execution.

## Queue Consumer Integration

```typescript
export default {
  async queue(batch, env) {
    for (const msg of batch.messages) {
      try {
        const container = env.PROCESSOR.getByName(msg.body.jobId);
        await container.startAndWaitForPorts();
        
        const response = await container.fetch("/process", {
          method: "POST",
          body: JSON.stringify(msg.body)
        });
        
        response.ok ? msg.ack() : msg.retry();
      } catch (err) {
        console.error("Queue processing error:", err);
        msg.retry();
      }
    }
  }
};
```

**Use:** Asynchronous job processing, batch operations, event-driven execution.