🎯

cloudflare-durable-objects

🎯Skill

from itechmeat/llm-code

VibeIndex|
What it does

Manages globally unique, stateful serverless compute with consistent storage, WebSocket hibernation, and cross-client coordination using Cloudflare Durable Objects.

πŸ“¦

Part of

itechmeat/llm-code(31 items)

cloudflare-durable-objects

Installation

npxRun with npx
npx wrangler deploy
npxRun with npx
npx wrangler deploy # Deploy with migrations
πŸ“– Extracted from docs: itechmeat/llm-code
3Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

"Cloudflare Durable Objects stateful serverless playbook: DurableObjectState, Storage API (SQLite/KV), WebSocket hibernation, alarms, RPC, bindings, migrations, limits, pricing. Keywords: Durable Objects, DurableObjectState, DurableObjectStorage, SQLite, ctx.storage, WebSocket hibernation, acceptWebSocket, alarms, setAlarm, RPC, blockConcurrencyWhile."

Overview

# Cloudflare Durable Objects

Durable Objects combine compute with strongly consistent, transactional storage. Each object has a globally-unique name, enabling coordination across clients worldwide.

---

Quick Start

Durable Object Class

```typescript

// src/counter.ts

import { DurableObject } from "cloudflare:workers";

export class Counter extends DurableObject {

async increment(): Promise {

let count = (await this.ctx.storage.get("count")) ?? 0;

count++;

await this.ctx.storage.put("count", count);

return count;

}

async getCount(): Promise {

return (await this.ctx.storage.get("count")) ?? 0;

}

}

```

Worker Entry Point

```typescript

// src/index.ts

export { Counter } from "./counter";

export default {

async fetch(request: Request, env: Env): Promise {

const id = env.COUNTER.idFromName("global");

const stub = env.COUNTER.get(id);

const count = await stub.increment();

return new Response(Count: ${count});

},

};

```

wrangler.jsonc

```jsonc

{

"name": "counter-worker",

"main": "src/index.ts",

"durable_objects": {

"bindings": [

{

"name": "COUNTER",

"class_name": "Counter"

}

]

},

"migrations": [

{

"tag": "v1",

"new_sqlite_classes": ["Counter"]

}

]

}

```

Deploy

```bash

npx wrangler deploy

```

---

Core Concepts

DurableObjectState

Available as this.ctx in Durable Object class:

```typescript

interface DurableObjectState {

readonly id: DurableObjectId;

readonly storage: DurableObjectStorage;

blockConcurrencyWhile(callback: () => Promise): Promise;

waitUntil(promise: Promise): void; // No effect in DO

// WebSocket Hibernation

acceptWebSocket(ws: WebSocket, tags?: string[]): void;

getWebSockets(tag?: string): WebSocket[];

getTags(ws: WebSocket): string[];

setWebSocketAutoResponse(pair?: WebSocketRequestResponsePair): void;

getWebSocketAutoResponse(): WebSocketRequestResponsePair | null;

abort(message?: string): void; // Force reset DO

}

```

DurableObjectId

```typescript

const id = env.MY_DO.idFromName("user-123"); // Deterministic ID

const id = env.MY_DO.newUniqueId(); // Random unique ID

const stub = env.MY_DO.get(id); // Get stub for DO instance

```

See [api.md](references/api.md) for full type definitions.

---

Storage API (SQLite-backed)

SQLite is the recommended storage backend for new Durable Objects.

SQL API

```typescript

const cursor = this.ctx.storage.sql.exec("SELECT * FROM users WHERE id = ?", userId);

// Get single row (throws if not exactly one)

const user = cursor.one();

// Get all rows

const users = cursor.toArray();

// Iterate

for (const row of cursor) {

console.log(row);

}

```

SQL Cursor Properties

```typescript

cursor.columnNames; // string[]

cursor.rowsRead; // number

cursor.rowsWritten; // number

```

Create Tables

```typescript

this.ctx.storage.sql.exec(`

CREATE TABLE IF NOT EXISTS users (

id TEXT PRIMARY KEY,

name TEXT NOT NULL,

created_at INTEGER DEFAULT (unixepoch())

)

`);

```

Insert/Update

```typescript

this.ctx.storage.sql.exec("INSERT INTO users (id, name) VALUES (?, ?)", id, name);

this.ctx.storage.sql.exec("UPDATE users SET name = ? WHERE id = ?", newName, id);

```

Transactions

```typescript

// Synchronous transaction (SQLite only)

this.ctx.storage.transactionSync(() => {

this.ctx.storage.sql.exec("INSERT INTO logs (msg) VALUES (?)", "start");

this.ctx.storage.sql.exec("UPDATE counters SET value = value + 1");

});

```

Database Size

```typescript

const sizeBytes = this.ctx.storage.sql.databaseSize;

```

See [storage.md](references/storage.md) for KV API and advanced usage.

---

Storage API (KV)

Synchronous KV (SQLite-backed)

```typescript

this.ctx.storage.kv.put("key", value);

const val = this.ctx.storage.kv.get("key");

const deleted = this.ctx.storage.kv.delete("key");

for (const [key, value] of this.ctx.storage.kv.list()) {

console.log(key, value);

}

```

Async KV (Both backends)

```typescript

await this.ctx.storage.put("key", value);

const val = await this.ctx.storage.get("key");

// Batch operations (up to 128 keys)

const values = await this.ctx.storage.get(["key1", "key2", "key3"]);

await this.ctx.storage.put({ key1: val1, key2: val2 });

await this.ctx.storage.delete(["key1", "key2"]);

// List with options

const map = await this.ctx.storage.list({ prefix: "user:" });

// Delete all

await this.ctx.storage.deleteAll();

```

Write Coalescing

Multiple writes without await are coalesced atomically:

```typescript

// These are batched into single transaction

this.ctx.storage.put("a", 1);

this.ctx.storage.put("b", 2);

this.ctx.storage.put("c", 3);

// All committed together

```

---

Alarms

Schedule single alarm per Durable Object for background processing.

Set Alarm

```typescript

// Schedule 1 hour from now

await this.ctx.storage.setAlarm(Date.now() + 60 60 1000);

// Schedule at specific time

await this.ctx.storage.setAlarm(new Date("2024-12-31T00:00:00Z"));

```

Handle Alarm

```typescript

export class MyDO extends DurableObject {

async alarm(info?: AlarmInfo): Promise {

console.log(Alarm fired! Retry: ${info?.isRetry}, count: ${info?.retryCount});

// Process scheduled work

await this.processScheduledTasks();

// Schedule next alarm if needed

const nextRun = await this.getNextScheduledTime();

if (nextRun) {

await this.ctx.storage.setAlarm(nextRun);

}

}

}

```

Alarm Methods

```typescript

await this.ctx.storage.setAlarm(timestamp); // Set/overwrite alarm

const time = await this.ctx.storage.getAlarm(); // Get scheduled time (ms) or null

await this.ctx.storage.deleteAlarm(); // Cancel alarm

```

Retry behavior: Alarms retry with exponential backoff (2s initial, up to 6 retries) on exceptions.

See [alarms.md](references/alarms.md) for patterns.

---

WebSocket Hibernation

Keep WebSocket connections alive while Durable Object hibernates.

Accept WebSocket

```typescript

export class ChatRoom extends DurableObject {

async fetch(request: Request): Promise {

const upgradeHeader = request.headers.get("Upgrade");

if (upgradeHeader === "websocket") {

const [client, server] = Object.values(new WebSocketPair());

// Accept with hibernation support

this.ctx.acceptWebSocket(server, ["user:123"]); // Optional tags

return new Response(null, { status: 101, webSocket: client });

}

return new Response("Expected WebSocket", { status: 400 });

}

async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise {

// Handle incoming message (DO wakes from hibernation)

this.broadcast(message);

}

async webSocketClose(ws: WebSocket, code: number, reason: string): Promise {

// Handle disconnect

}

}

```

Broadcast to All

```typescript

broadcast(message: string) {

for (const ws of this.ctx.getWebSockets()) {

ws.send(message);

}

}

```

Per-Connection State

```typescript

// Save state that survives hibernation (max 2048 bytes)

ws.serializeAttachment({ userId: "123", role: "admin" });

// Restore in message handler

const state = ws.deserializeAttachment();

```

Auto-Response (No Wake)

```typescript

// Respond to pings without waking DO

this.ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair("ping", "pong"));

```

See [websockets.md](references/websockets.md) for details.

---

RPC Methods

Call Durable Object methods directly (compatibility date >= 2024-04-03):

```typescript

const stub = env.USER_SERVICE.get(id);

const user = await stub.getUser("123"); // Direct RPC call

await stub.updateUser("123", { name: "New Name" });

```

Note: Each RPC method call = one RPC session for billing.

---

Initialization

Use blockConcurrencyWhile in constructor for migrations:

```typescript

constructor(ctx: DurableObjectState, env: Env) {

super(ctx, env);

ctx.blockConcurrencyWhile(async () => {

this.ctx.storage.sql.exec(CREATE TABLE IF NOT EXISTS data (key TEXT PRIMARY KEY, value TEXT));

});

}

```

Timeout: 30 seconds.

---

Hibernation

Conditions for Hibernation

All must be true:

  • No pending setTimeout/setInterval
  • No in-progress await fetch()
  • Using Hibernation WebSocket API (not standard WebSocket)
  • No active request processing

Lifecycle

  1. Active: Processing requests
  2. Idle hibernateable: ~10 seconds β†’ may hibernate
  3. Hibernated: Removed from memory, WebSockets stay connected
  4. Wake: On message/event, constructor runs, handler invoked

Important: In-memory state is lost on hibernation. Restore from storage or attachments.

---

Bindings & Migrations

```jsonc

{

"durable_objects": {

"bindings": [{ "name": "MY_DO", "class_name": "MyDO" }]

},

"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }]

}

```

Note: Cannot enable SQLite on existing deployed classes.

---

Wrangler Commands

```bash

npx wrangler deploy # Deploy with migrations

wrangler tail # Tail logs

```

---

Limits

| Feature | Free | Paid |

| --------------------- | -------- | ------------------ |

| DO classes | 100 | 500 |

| Storage per DO | 10 GB | 10 GB |

| Storage per account | 5 GB | Unlimited |

| CPU per request | 30 sec | 30 sec (max 5 min) |

| WebSocket connections | 32,768 | 32,768 |

| SQL row/value size | 2 MB | 2 MB |

| KV value size | 128 KiB | 128 KiB |

| Batch size | 128 keys | 128 keys |

---

Pricing

| Metric | Free | Paid |

| ------------------- | ------------ | ------------------------------ |

| Requests | 100K/day | 1M/mo included, +$0.15/M |

| Duration | 13K GB-s/day | 400K GB-s/mo, +$12.50/M GB-s |

| SQLite rows read | 5M/day | 25B/mo included, +$0.001/M |

| SQLite rows written | 100K/day | 50M/mo included, +$1.00/M |

| Storage | 5 GB | 5 GB/mo included, +$0.20/GB-mo |

WebSocket: 20:1 billing ratio (1M messages = 50K requests).

See [pricing.md](references/pricing.md) for details.

---

Prohibitions

  • ❌ Do not store state outside storage (lost on hibernation)
  • ❌ Do not use standard WebSocket API for hibernation
  • ❌ Do not exceed 2 MB per row/value in SQLite
  • ❌ Do not call sql.exec() with transaction control statements
  • ❌ Do not expect waitUntil to work (no effect in DO)

---

References

  • [api.md](references/api.md) β€” Full API reference
  • [storage.md](references/storage.md) β€” Storage API details
  • [websockets.md](references/websockets.md) β€” WebSocket hibernation
  • [alarms.md](references/alarms.md) β€” Alarm patterns
  • [pricing.md](references/pricing.md) β€” Billing details

Related Skills

  • cloudflare-workers β€” Worker development
  • cloudflare-d1 β€” D1 database
  • cloudflare-kv β€” Global KV
  • cloudflare-workflows β€” Durable execution