cloudflare-durable-objects

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare Durable Objects

Cloudflare Durable Objects

Status: Production Ready ✅ Last Updated: 2025-10-22 Dependencies: cloudflare-worker-base (recommended) Latest Versions: wrangler@4.43.0+, @cloudflare/workers-types@4.20251014.0+ Official Docs: https://developers.cloudflare.com/durable-objects/

状态: 已就绪可用于生产环境 ✅ 最后更新: 2025-10-22 依赖: cloudflare-worker-base(推荐) 最新版本: wrangler@4.43.0+, @cloudflare/workers-types@4.20251014.0+ 官方文档: https://developers.cloudflare.com/durable-objects/

What are Durable Objects?

什么是Durable Objects?

Cloudflare Durable Objects are globally unique, stateful objects that provide:
  • Single-point coordination - Each Durable Object instance is globally unique across Cloudflare's network
  • Strong consistency - Transactional, serializable storage (ACID guarantees)
  • Real-time communication - WebSocket Hibernation API for thousands of connections per instance
  • Persistent state - Built-in SQLite database (up to 1GB) or key-value storage
  • Scheduled tasks - Alarms API for future task execution
  • Global distribution - Automatically routed to optimal location
  • Automatic scaling - Millions of independent instances
Use Cases:
  • Chat rooms and real-time collaboration
  • Multiplayer game servers
  • Rate limiting and session management
  • Leader election and coordination
  • WebSocket servers with hibernation
  • Stateful workflows and queues
  • Per-user or per-room logic

Cloudflare Durable Objects是全局唯一的有状态对象,提供以下能力:
  • 单点协调 - 每个Durable Object实例在Cloudflare网络中全局唯一
  • 强一致性 - 事务性、可序列化存储(ACID保证)
  • 实时通信 - WebSocket休眠API,每个实例可处理数千个连接
  • 持久化状态 - 内置SQLite数据库(最大1GB)或键值存储
  • 定时任务 - Alarms API用于未来任务执行
  • 全局分发 - 自动路由到最优位置
  • 自动扩展 - 支持数百万个独立实例
适用场景:
  • 聊天室和实时协作
  • 多人游戏服务器
  • 速率限制和会话管理
  • 领导者选举与协调
  • 支持休眠的WebSocket服务器
  • 有状态工作流和队列
  • 每个用户或每个房间的专属逻辑

Quick Start (10 Minutes)

快速入门(10分钟)

Option 1: Scaffold New DO Project

选项1:搭建新的DO项目

bash
npm create cloudflare@latest my-durable-app -- \
  --template=cloudflare/durable-objects-template \
  --ts \
  --git \
  --deploy false

cd my-durable-app
npm install
npm run dev
What this creates:
  • Complete Durable Objects project structure
  • TypeScript configuration
  • wrangler.jsonc with bindings and migrations
  • Example DO class implementation
  • Worker to call the DO
bash
npm create cloudflare@latest my-durable-app -- \
  --template=cloudflare/durable-objects-template \
  --ts \
  --git \
  --deploy false

cd my-durable-app
npm install
npm run dev
创建的内容:
  • 完整的Durable Objects项目结构
  • TypeScript配置
  • 包含绑定和迁移的wrangler.jsonc
  • 示例DO类实现
  • 用于调用DO的Worker

Option 2: Add to Existing Worker

选项2:添加到现有Worker

bash
cd my-existing-worker
npm install -D @cloudflare/workers-types
Create a Durable Object class (
src/counter.ts
):
typescript
import { DurableObject } from 'cloudflare:workers';

export class Counter extends DurableObject {
  async increment(): Promise<number> {
    // Get current value from storage (default to 0)
    let value: number = (await this.ctx.storage.get('value')) || 0;

    // Increment
    value += 1;

    // Save back to storage
    await this.ctx.storage.put('value', value);

    return value;
  }

  async get(): Promise<number> {
    return (await this.ctx.storage.get('value')) || 0;
  }
}

// CRITICAL: Export the class
export default Counter;
Configure wrangler.jsonc:
jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-22",

  // Durable Objects binding
  "durable_objects": {
    "bindings": [
      {
        "name": "COUNTER",           // How you access it: env.COUNTER
        "class_name": "Counter"      // MUST match exported class name
      }
    ]
  },

  // REQUIRED: Migration for new DO class
  "migrations": [
    {
      "tag": "v1",                   // Unique migration identifier
      "new_sqlite_classes": [        // Use SQLite backend (recommended)
        "Counter"
      ]
    }
  ]
}
Call from Worker (
src/index.ts
):
typescript
import { Counter } from './counter';

interface Env {
  COUNTER: DurableObjectNamespace<Counter>;
}

export { Counter };

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Get Durable Object stub by name
    const id = env.COUNTER.idFromName('global-counter');
    const stub = env.COUNTER.get(id);

    // Call RPC method on the DO
    const count = await stub.increment();

    return new Response(`Count: ${count}`);
  },
};
Deploy:
bash
npx wrangler deploy

bash
cd my-existing-worker
npm install -D @cloudflare/workers-types
创建Durable Object类 (
src/counter.ts
):
typescript
import { DurableObject } from 'cloudflare:workers';

export class Counter extends DurableObject {
  async increment(): Promise<number> {
    // 从存储中获取当前值(默认0)
    let value: number = (await this.ctx.storage.get('value')) || 0;

    // 递增
    value += 1;

    // 保存回存储
    await this.ctx.storage.put('value', value);

    return value;
  }

  async get(): Promise<number> {
    return (await this.ctx.storage.get('value')) || 0;
  }
}

// 关键:导出类
export default Counter;
配置wrangler.jsonc:
jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-22",

  // Durable Objects绑定
  "durable_objects": {
    "bindings": [
      {
        "name": "COUNTER",           // 代码中访问时使用的名称:env.COUNTER
        "class_name": "Counter"      // 必须与导出的类名匹配
      }
    ]
  },

  // 必填:新DO类的迁移配置
  "migrations": [
    {
      "tag": "v1",                   // 唯一的迁移标识符
      "new_sqlite_classes": [        // 使用SQLite后端(推荐)
        "Counter"
      ]
    }
  ]
}
从Worker中调用 (
src/index.ts
):
typescript
import { Counter } from './counter';

interface Env {
  COUNTER: DurableObjectNamespace<Counter>;
}

export { Counter };

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // 通过名称获取Durable Object存根
    const id = env.COUNTER.idFromName('global-counter');
    const stub = env.COUNTER.get(id);

    // 调用DO上的RPC方法
    const count = await stub.increment();

    return new Response(`计数:${count}`);
  },
};
部署:
bash
npx wrangler deploy

Durable Object Class Structure

Durable Object类结构

Base Class Pattern

基类模式

All Durable Objects MUST extend
DurableObject
from
cloudflare:workers
:
typescript
import { DurableObject } from 'cloudflare:workers';

export class MyDurableObject extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);

    // Optional: Initialize from storage
    ctx.blockConcurrencyWhile(async () => {
      // Load state before handling requests
      this.someValue = await ctx.storage.get('someKey') || defaultValue;
    });
  }

  // RPC methods (recommended)
  async myMethod(): Promise<string> {
    return 'Hello from DO!';
  }

  // Optional: HTTP fetch handler
  async fetch(request: Request): Promise<Response> {
    return new Response('Hello from DO fetch!');
  }
}

// CRITICAL: Export the class
export default MyDurableObject;
所有Durable Objects必须继承
cloudflare:workers
中的
DurableObject
:
typescript
import { DurableObject } from 'cloudflare:workers';

export class MyDurableObject extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);

    // 可选:从存储初始化
    ctx.blockConcurrencyWhile(async () => {
      // 在处理请求前加载状态
      this.someValue = await ctx.storage.get('someKey') || defaultValue;
    });
  }

  // RPC方法(推荐)
  async myMethod(): Promise<string> {
    return '来自DO的问候!';
  }

  // 可选:HTTP fetch处理器
  async fetch(request: Request): Promise<Response> {
    return new Response('来自DO fetch的问候!');
  }
}

// 关键:导出类
export default MyDurableObject;

Constructor Pattern

构造函数模式

typescript
constructor(ctx: DurableObjectState, env: Env) {
  super(ctx, env);  // REQUIRED

  // Access to environment bindings
  this.env = env;

  // this.ctx provides:
  // - this.ctx.storage      (storage API)
  // - this.ctx.id           (unique ID)
  // - this.ctx.waitUntil()  (background tasks)
  // - this.ctx.acceptWebSocket() (WebSocket hibernation)
}
CRITICAL Rules:
  • Always call
    super(ctx, env)
    first
  • Keep constructor minimal - heavy work blocks hibernation wake-up
  • Use
    ctx.blockConcurrencyWhile()
    to initialize from storage before requests
  • Never use
    setTimeout
    or
    setInterval
    - breaks hibernation (use alarms instead)
  • Don't rely only on in-memory state with WebSockets - persist to storage
typescript
constructor(ctx: DurableObjectState, env: Env) {
  super(ctx, env);  // 必填

  // 访问环境绑定
  this.env = env;

  // this.ctx提供以下能力:
  // - this.ctx.storage      (存储API)
  // - this.ctx.id           (唯一ID)
  // - this.ctx.waitUntil()  (后台任务)
  // - this.ctx.acceptWebSocket() (WebSocket休眠)
}
关键规则:
  • 必须首先调用
    super(ctx, env)
  • 保持构造函数轻量化 - 繁重的工作会阻止休眠唤醒
  • 使用
    ctx.blockConcurrencyWhile()
    在处理请求前从存储初始化
  • 永远不要使用
    setTimeout
    setInterval
    - 会破坏休眠(改用alarms)
  • 不要仅依赖内存状态处理WebSocket - 要持久化到存储

Exporting the Class

导出类

typescript
// Export as default (required for Worker to use it)
export default MyDurableObject;

// Also export as named export (for type inference in Worker)
export { MyDurableObject };
In Worker:
typescript
// Import the class for types
import { MyDurableObject } from './my-durable-object';

// Export it so Worker can instantiate it
export { MyDurableObject };

interface Env {
  MY_DO: DurableObjectNamespace<MyDurableObject>;
}

typescript
// 作为默认导出(Worker使用它的必要条件)
export default MyDurableObject;

// 同时导出命名导出(用于Worker中的类型推断)
export { MyDurableObject };
在Worker中:
typescript
// 导入类以获取类型
import { MyDurableObject } from './my-durable-object';

// 导出它以便Worker可以实例化
export { MyDurableObject };

interface Env {
  MY_DO: DurableObjectNamespace<MyDurableObject>;
}

State API - Persistent Storage

状态API - 持久化存储

Durable Objects provide two storage APIs depending on the backend:
  1. SQL API (SQLite backend) - Recommended
  2. Key-Value API (KV or SQLite backend)
Durable Objects根据后端提供两种存储API:
  1. SQL API(SQLite后端)- 推荐
  2. 键值API(KV或SQLite后端)

Enable SQLite Backend (Recommended)

启用SQLite后端(推荐)

In
wrangler.jsonc
migrations:
jsonc
{
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["MyDurableObject"]  // ← Use this for SQLite
    }
  ]
}
Why SQLite?
  • ✅ Up to 1GB storage (vs 128MB for KV backend)
  • Atomic operations (deleteAll is all-or-nothing)
  • SQL queries with transactions
  • Point-in-time recovery (PITR)
  • ✅ Synchronous KV API available too
wrangler.jsonc
的迁移配置中:
jsonc
{
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["MyDurableObject"]  // ← 使用此配置启用SQLite
    }
  ]
}
为什么选择SQLite?
  • ✅ 最大1GB存储(KV后端为128MB)
  • 原子操作(deleteAll是全有或全无)
  • SQL查询支持事务
  • 时间点恢复(PITR)
  • ✅ 同时支持同步KV API

SQL API

SQL API

Access via
ctx.storage.sql
:
typescript
import { DurableObject } from 'cloudflare:workers';

export class MyDurableObject extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    // Create table on first run
    this.sql.exec(`
      CREATE TABLE IF NOT EXISTS messages (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        text TEXT NOT NULL,
        user TEXT NOT NULL,
        created_at INTEGER NOT NULL
      );

      CREATE INDEX IF NOT EXISTS idx_created_at ON messages(created_at);
    `);
  }

  async addMessage(text: string, user: string): Promise<number> {
    // Insert with exec (returns cursor)
    const cursor = this.sql.exec(
      'INSERT INTO messages (text, user, created_at) VALUES (?, ?, ?) RETURNING id',
      text,
      user,
      Date.now()
    );

    const row = cursor.one<{ id: number }>();
    return row.id;
  }

  async getMessages(limit: number = 50): Promise<any[]> {
    const cursor = this.sql.exec(
      'SELECT * FROM messages ORDER BY created_at DESC LIMIT ?',
      limit
    );

    // Convert cursor to array
    return cursor.toArray();
  }

  async deleteOldMessages(beforeTimestamp: number): Promise<void> {
    this.sql.exec(
      'DELETE FROM messages WHERE created_at < ?',
      beforeTimestamp
    );
  }
}
SQL API Methods:
typescript
// Execute query (returns cursor)
const cursor = this.sql.exec('SELECT * FROM table WHERE id = ?', id);

// Get single row
const row = cursor.one<RowType>();

// Get first row or null
const row = cursor.one<RowType>({ allowNone: true });

// Get all rows as array
const rows = cursor.toArray<RowType>();

// Iterate cursor
for (const row of cursor) {
  // Process row
}

// Transactions (synchronous)
this.ctx.storage.transactionSync(() => {
  this.sql.exec('INSERT INTO table1 ...');
  this.sql.exec('UPDATE table2 ...');
  // All or nothing
});
CRITICAL SQL Rules:
  • ✅ Always use parameterized queries with
    ?
    placeholders
  • ✅ Create indexes for frequently queried columns
  • ✅ Use transactions for multi-statement operations
  • ❌ Don't access the hidden
    __cf_kv
    table (used internally for KV API)
  • ❌ Don't enable SQLite on existing deployed KV-backed DOs (not supported)
通过
ctx.storage.sql
访问:
typescript
import { DurableObject } from 'cloudflare:workers';

export class MyDurableObject extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    // 首次运行时创建表
    this.sql.exec(`
      CREATE TABLE IF NOT EXISTS messages (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        text TEXT NOT NULL,
        user TEXT NOT NULL,
        created_at INTEGER NOT NULL
      );

      CREATE INDEX IF NOT EXISTS idx_created_at ON messages(created_at);
    `);
  }

  async addMessage(text: string, user: string): Promise<number> {
    // 插入数据并返回游标
    const cursor = this.sql.exec(
      'INSERT INTO messages (text, user, created_at) VALUES (?, ?, ?) RETURNING id',
      text,
      user,
      Date.now()
    );

    const row = cursor.one<{ id: number }>();
    return row.id;
  }

  async getMessages(limit: number = 50): Promise<any[]> {
    const cursor = this.sql.exec(
      'SELECT * FROM messages ORDER BY created_at DESC LIMIT ?',
      limit
    );

    // 将游标转换为数组
    return cursor.toArray();
  }

  async deleteOldMessages(beforeTimestamp: number): Promise<void> {
    this.sql.exec(
      'DELETE FROM messages WHERE created_at < ?',
      beforeTimestamp
    );
  }
}
SQL API方法:
typescript
// 执行查询(返回游标)
const cursor = this.sql.exec('SELECT * FROM table WHERE id = ?', id);

// 获取单行
const row = cursor.one<RowType>();

// 获取第一行或null
const row = cursor.one<RowType>({ allowNone: true });

// 获取所有行作为数组
const rows = cursor.toArray<RowType>();

// 遍历游标
for (const row of cursor) {
  // 处理行
}

// 事务(同步)
this.ctx.storage.transactionSync(() => {
  this.sql.exec('INSERT INTO table1 ...');
  this.sql.exec('UPDATE table2 ...');
  // 全有或全无
});
关键SQL规则:
  • ✅ 始终使用参数化查询,用
    ?
    作为占位符
  • ✅ 为频繁查询的列创建索引
  • ✅ 多语句操作使用事务
  • ❌ 不要访问隐藏的
    __cf_kv
    表(KV API内部使用)
  • ❌ 不要在已部署的KV后端DO上启用SQLite(不支持)

Key-Value API

键值API

Available on both SQLite and KV backends via
ctx.storage
:
typescript
import { DurableObject } from 'cloudflare:workers';

export class MyDurableObject extends DurableObject {
  async increment(): Promise<number> {
    // Get value
    let count = await this.ctx.storage.get<number>('count') || 0;

    // Increment
    count += 1;

    // Put value back
    await this.ctx.storage.put('count', count);

    return count;
  }

  async batchOperations(): Promise<void> {
    // Get multiple keys
    const map = await this.ctx.storage.get<number>(['key1', 'key2', 'key3']);

    // Put multiple keys
    await this.ctx.storage.put({
      key1: 'value1',
      key2: 'value2',
      key3: 'value3',
    });

    // Delete key
    await this.ctx.storage.delete('key1');

    // Delete multiple keys
    await this.ctx.storage.delete(['key2', 'key3']);
  }

  async listKeys(): Promise<string[]> {
    // List all keys
    const map = await this.ctx.storage.list();
    return Array.from(map.keys());

    // List with prefix
    const mapWithPrefix = await this.ctx.storage.list({
      prefix: 'user:',
      limit: 100,
    });
  }

  async deleteAllStorage(): Promise<void> {
    // Delete alarm first (if set)
    await this.ctx.storage.deleteAlarm();

    // Delete all storage (DO will cease to exist after shutdown)
    await this.ctx.storage.deleteAll();
  }
}
KV API Methods:
typescript
// Get single value
const value = await this.ctx.storage.get<T>('key');

// Get multiple values (returns Map)
const map = await this.ctx.storage.get<T>(['key1', 'key2']);

// Put single value
await this.ctx.storage.put('key', value);

// Put multiple values
await this.ctx.storage.put({ key1: value1, key2: value2 });

// Delete single key
await this.ctx.storage.delete('key');

// Delete multiple keys
await this.ctx.storage.delete(['key1', 'key2']);

// List keys
const map = await this.ctx.storage.list<T>({
  prefix: 'user:',
  limit: 100,
  reverse: false
});

// Delete all (atomic on SQLite, may be partial on KV backend)
await this.ctx.storage.deleteAll();

// Transactions (async)
await this.ctx.storage.transaction(async (txn) => {
  await txn.put('key1', value1);
  await txn.put('key2', value2);
  // All or nothing
});
Storage Limits:
  • SQLite backend: Up to 1GB storage per DO instance
  • KV backend: Up to 128MB storage per DO instance

SQLite和KV后端都可通过
ctx.storage
访问:
typescript
import { DurableObject } from 'cloudflare:workers';

export class MyDurableObject extends DurableObject {
  async increment(): Promise<number> {
    // 获取值
    let count = await this.ctx.storage.get<number>('count') || 0;

    // 递增
    count += 1;

    // 保存回值
    await this.ctx.storage.put('count', count);

    return count;
  }

  async batchOperations(): Promise<void> {
    // 获取多个键
    const map = await this.ctx.storage.get<number>(['key1', 'key2', 'key3']);

    // 保存多个键
    await this.ctx.storage.put({
      key1: 'value1',
      key2: 'value2',
      key3: 'value3',
    });

    // 删除键
    await this.ctx.storage.delete('key1');

    // 删除多个键
    await this.ctx.storage.delete(['key2', 'key3']);
  }

  async listKeys(): Promise<string[]> {
    // 列出所有键
    const map = await this.ctx.storage.list();
    return Array.from(map.keys());

    // 列出带前缀的键
    const mapWithPrefix = await this.ctx.storage.list({
      prefix: 'user:',
      limit: 100,
    });
  }

  async deleteAllStorage(): Promise<void> {
    // 首先删除警报(如果已设置)
    await this.ctx.storage.deleteAlarm();

    // 删除所有存储(DO关闭后将不复存在)
    await this.ctx.storage.deleteAll();
  }
}
KV API方法:
typescript
// 获取单个值
const value = await this.ctx.storage.get<T>('key');

// 获取多个值(返回Map)
const map = await this.ctx.storage.get<T>(['key1', 'key2']);

// 保存单个值
await this.ctx.storage.put('key', value);

// 保存多个值
await this.ctx.storage.put({ key1: value1, key2: value2 });

// 删除单个键
await this.ctx.storage.delete('key');

// 删除多个键
await this.ctx.storage.delete(['key1', 'key2']);

// 列出键
const map = await this.ctx.storage.list<T>({
  prefix: 'user:',
  limit: 100,
  reverse: false
});

// 删除所有(SQLite后端是原子操作,KV后端可能部分删除)
await this.ctx.storage.deleteAll();

// 事务(异步)
await this.ctx.storage.transaction(async (txn) => {
  await txn.put('key1', value1);
  await txn.put('key2', value2);
  // 全有或全无
});
存储限制:
  • SQLite后端: 每个DO实例最大1GB存储
  • KV后端: 每个DO实例最大128MB存储

WebSocket Hibernation API

WebSocket休眠API

The WebSocket Hibernation API allows Durable Objects to:
  • Handle thousands of WebSocket connections per instance
  • Hibernate when idle (no messages, no events) to save costs
  • Wake up automatically when messages arrive
  • Maintain connections without incurring duration charges during idle periods
Use for: Chat rooms, real-time collaboration, multiplayer games, live updates
WebSocket休眠API允许Durable Objects:
  • 每个实例处理数千个WebSocket连接
  • 空闲时(无消息、无事件)休眠以节省成本
  • 有新消息时自动唤醒
  • 空闲期间保持连接,不产生时长费用
适用场景: 聊天室、实时协作、多人游戏、实时更新

How Hibernation Works

休眠工作原理

  1. Active state - DO is in memory, handling messages
  2. Idle state - No messages for ~10 seconds, DO can hibernate
  3. Hibernation - In-memory state cleared, WebSockets stay connected to Cloudflare edge
  4. Wake up - New message arrives → constructor runs → handler method called
CRITICAL: In-memory state is lost on hibernation. Use
serializeAttachment()
to persist per-WebSocket metadata.
  1. 活跃状态 - DO在内存中,处理消息
  2. 空闲状态 - 约10秒无消息,DO可进入休眠
  3. 休眠状态 - 内存状态被清除,WebSocket保持与Cloudflare边缘节点的连接
  4. 唤醒 - 新消息到达 → 构造函数运行 → 调用处理器方法
关键注意事项: 休眠时内存状态会丢失。使用
serializeAttachment()
持久化每个WebSocket的元数据。

WebSocket Server Pattern

WebSocket服务器模式

typescript
import { DurableObject } from 'cloudflare:workers';

export class ChatRoom extends DurableObject {
  sessions: Map<WebSocket, { userId: string; username: string }>;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);

    // Restore WebSocket connections after hibernation
    this.sessions = new Map();

    ctx.getWebSockets().forEach((ws) => {
      // Deserialize attachment (persisted metadata)
      const attachment = ws.deserializeAttachment();
      this.sessions.set(ws, attachment);
    });
  }

  async fetch(request: Request): Promise<Response> {
    // Expect WebSocket upgrade request
    const upgradeHeader = request.headers.get('Upgrade');
    if (upgradeHeader !== 'websocket') {
      return new Response('Expected websocket', { status: 426 });
    }

    // Create WebSocket pair
    const webSocketPair = new WebSocketPair();
    const [client, server] = Object.values(webSocketPair);

    // Get user info from URL or headers
    const url = new URL(request.url);
    const userId = url.searchParams.get('userId') || 'anonymous';
    const username = url.searchParams.get('username') || 'Anonymous';

    // Accept WebSocket with hibernation
    // CRITICAL: Use ctx.acceptWebSocket(), NOT ws.accept()
    this.ctx.acceptWebSocket(server);

    // Serialize metadata to persist across hibernation
    const metadata = { userId, username };
    server.serializeAttachment(metadata);

    // Track in-memory (will be restored after hibernation)
    this.sessions.set(server, metadata);

    // Notify others
    this.broadcast(`${username} joined`, server);

    // Return client WebSocket to browser
    return new Response(null, {
      status: 101,
      webSocket: client,
    });
  }

  // Called when WebSocket receives a message
  async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
    const session = this.sessions.get(ws);

    if (typeof message === 'string') {
      const data = JSON.parse(message);

      if (data.type === 'chat') {
        // Broadcast to all connections
        this.broadcast(`${session?.username}: ${data.text}`, ws);
      }
    }
  }

  // Called when WebSocket closes
  async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean): Promise<void> {
    const session = this.sessions.get(ws);
    this.sessions.delete(ws);

    // Close the WebSocket
    ws.close(code, 'Durable Object closing WebSocket');

    // Notify others
    if (session) {
      this.broadcast(`${session.username} left`);
    }
  }

  // Called on WebSocket errors
  async webSocketError(ws: WebSocket, error: any): Promise<void> {
    console.error('WebSocket error:', error);
    const session = this.sessions.get(ws);
    this.sessions.delete(ws);
  }

  // Helper to broadcast to all connections
  broadcast(message: string, except?: WebSocket): void {
    this.sessions.forEach((session, ws) => {
      if (ws !== except && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: 'message', text: message }));
      }
    });
  }
}
WebSocket Handler Methods:
typescript
// Receive message from client
async webSocketMessage(
  ws: WebSocket,
  message: string | ArrayBuffer
): Promise<void> {
  // Handle message
}

// WebSocket closed by client
async webSocketClose(
  ws: WebSocket,
  code: number,
  reason: string,
  wasClean: boolean
): Promise<void> {
  // Cleanup
}

// WebSocket error occurred
async webSocketError(
  ws: WebSocket,
  error: any
): Promise<void> {
  // Handle error
}
Hibernation-Safe Patterns:
typescript
// ✅ CORRECT: Use ctx.acceptWebSocket (enables hibernation)
this.ctx.acceptWebSocket(server);

// ❌ WRONG: Don't use ws.accept() (standard API, no hibernation)
server.accept();

// ✅ CORRECT: Persist metadata across hibernation
server.serializeAttachment({ userId: '123', username: 'Alice' });

// ✅ CORRECT: Restore metadata in constructor
constructor(ctx, env) {
  super(ctx, env);

  ctx.getWebSockets().forEach((ws) => {
    const metadata = ws.deserializeAttachment();
    this.sessions.set(ws, metadata);
  });
}

// ❌ WRONG: Don't use setTimeout/setInterval (prevents hibernation)
setTimeout(() => { /* ... */ }, 1000);  // ❌ NEVER DO THIS

// ✅ CORRECT: Use alarms for scheduled tasks
await this.ctx.storage.setAlarm(Date.now() + 60000);
When Hibernation Does NOT Occur:
  • setTimeout
    or
    setInterval
    callbacks are pending
  • In-progress
    fetch()
    request (awaited I/O)
  • Standard WebSocket API is used (not hibernation API)
  • Request/event is still being processed

typescript
import { DurableObject } from 'cloudflare:workers';

export class ChatRoom extends DurableObject {
  sessions: Map<WebSocket, { userId: string; username: string }>;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);

    // 休眠后恢复WebSocket连接
    this.sessions = new Map();

    ctx.getWebSockets().forEach((ws) => {
      // 反序列化附件(持久化的元数据)
      const attachment = ws.deserializeAttachment();
      this.sessions.set(ws, attachment);
    });
  }

  async fetch(request: Request): Promise<Response> {
    // 预期WebSocket升级请求
    const upgradeHeader = request.headers.get('Upgrade');
    if (upgradeHeader !== 'websocket') {
      return new Response('预期WebSocket请求', { status: 426 });
    }

    // 创建WebSocket对
    const webSocketPair = new WebSocketPair();
    const [client, server] = Object.values(webSocketPair);

    // 从URL或头中获取用户信息
    const url = new URL(request.url);
    const userId = url.searchParams.get('userId') || 'anonymous';
    const username = url.searchParams.get('username') || 'Anonymous';

    // 接受WebSocket并启用休眠
    // 关键:使用ctx.acceptWebSocket(),而非ws.accept()
    this.ctx.acceptWebSocket(server);

    // 序列化元数据以跨休眠持久化
    const metadata = { userId, username };
    server.serializeAttachment(metadata);

    // 在内存中跟踪(休眠后会恢复)
    this.sessions.set(server, metadata);

    // 通知其他用户
    this.broadcast(`${username} 加入了聊天室`, server);

    // 向浏览器返回客户端WebSocket
    return new Response(null, {
      status: 101,
      webSocket: client,
    });
  }

  // 当WebSocket收到消息时调用
  async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
    const session = this.sessions.get(ws);

    if (typeof message === 'string') {
      const data = JSON.parse(message);

      if (data.type === 'chat') {
        // 广播到所有连接
        this.broadcast(`${session?.username}: ${data.text}`, ws);
      }
    }
  }

  // 当WebSocket关闭时调用
  async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean): Promise<void> {
    const session = this.sessions.get(ws);
    this.sessions.delete(ws);

    // 关闭WebSocket
    ws.close(code, 'Durable Object 正在关闭WebSocket');

    // 通知其他用户
    if (session) {
      this.broadcast(`${session.username} 离开了聊天室`);
    }
  }

  // 当WebSocket发生错误时调用
  async webSocketError(ws: WebSocket, error: any): Promise<void> {
    console.error('WebSocket错误:', error);
    const session = this.sessions.get(ws);
    this.sessions.delete(ws);
  }

  // 广播到所有连接的辅助方法
  broadcast(message: string, except?: WebSocket): void {
    this.sessions.forEach((session, ws) => {
      if (ws !== except && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: 'message', text: message }));
      }
    });
  }
}
WebSocket处理器方法:
typescript
// 接收来自客户端的消息
async webSocketMessage(
  ws: WebSocket,
  message: string | ArrayBuffer
): Promise<void> {
  // 处理消息
}

// 客户端关闭WebSocket时调用
async webSocketClose(
  ws: WebSocket,
  code: number,
  reason: string,
  wasClean: boolean
): Promise<void> {
  // 清理
}

// WebSocket发生错误时调用
async webSocketError(
  ws: WebSocket,
  error: any
): Promise<void> {
  // 处理错误
}
休眠安全模式:
typescript
// ✅ 正确:使用ctx.acceptWebSocket(启用休眠)
this.ctx.acceptWebSocket(server);

// ❌ 错误:不要使用ws.accept()(标准API,无休眠)
server.accept();

// ✅ 正确:跨休眠持久化元数据
server.serializeAttachment({ userId: '123', username: 'Alice' });

// ✅ 正确:在构造函数中恢复元数据
constructor(ctx, env) {
  super(ctx, env);

  ctx.getWebSockets().forEach((ws) => {
    const metadata = ws.deserializeAttachment();
    this.sessions.set(ws, metadata);
  });
}

// ❌ 错误:不要使用setTimeout/setInterval(阻止休眠)
setTimeout(() => { /* ... */ }, 1000);  // ❌ 永远不要这样做

// ✅ 正确:使用alarms处理定时任务
await this.ctx.storage.setAlarm(Date.now() + 60000);
以下情况不会触发休眠:
  • setTimeout
    setInterval
    回调等待执行
  • 有正在进行的
    fetch()
    请求(等待I/O)
  • 使用了标准WebSocket API(而非休眠API)
  • 请求/事件仍在处理中

Alarms API - Scheduled Tasks

Alarms API - 定时任务

The Alarms API allows Durable Objects to schedule themselves to wake up at a specific time in the future.
Use for: Batching, cleanup jobs, reminders, periodic tasks, delayed operations
Alarms API允许Durable Objects在未来特定时间调度自己唤醒。
适用场景: 批量处理、清理任务、提醒、周期性任务、延迟操作

Basic Alarm Pattern

基础Alarm模式

typescript
import { DurableObject } from 'cloudflare:workers';

export class Batcher extends DurableObject {
  buffer: string[];

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);

    ctx.blockConcurrencyWhile(async () => {
      // Restore buffer from storage
      this.buffer = await ctx.storage.get('buffer') || [];
    });
  }

  async addItem(item: string): Promise<void> {
    this.buffer.push(item);
    await this.ctx.storage.put('buffer', this.buffer);

    // Schedule alarm for 10 seconds from now (if not already set)
    const currentAlarm = await this.ctx.storage.getAlarm();
    if (currentAlarm === null) {
      await this.ctx.storage.setAlarm(Date.now() + 10000);
    }
  }

  // Called when alarm fires
  async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
    console.log(`Alarm fired (retry count: ${alarmInfo.retryCount})`);

    // Process batch
    if (this.buffer.length > 0) {
      await this.processBatch(this.buffer);

      // Clear buffer
      this.buffer = [];
      await this.ctx.storage.put('buffer', []);
    }

    // Alarm is automatically deleted after successful execution
  }

  async processBatch(items: string[]): Promise<void> {
    // Send to external API, write to database, etc.
    console.log(`Processing ${items.length} items:`, items);
  }
}
Alarm API Methods:
typescript
// Set alarm to fire at specific timestamp
await this.ctx.storage.setAlarm(Date.now() + 60000);  // 60 seconds from now

// Set alarm to fire at specific date
await this.ctx.storage.setAlarm(new Date('2025-12-31T23:59:59Z'));

// Get current alarm (null if not set)
const alarmTime = await this.ctx.storage.getAlarm();

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

// Alarm handler (called when alarm fires)
async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
  // Do work
}
Alarm Behavior:
  • Guaranteed at-least-once execution - will retry on failure
  • Automatic retries - up to 6 retries with exponential backoff (starting at 2 seconds)
  • Persistent - survives DO hibernation and eviction
  • Automatically deleted after successful execution
  • ⚠️ One alarm per DO - setting a new alarm overwrites the previous one
Retry Pattern (Idempotent Operations):
typescript
async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
  if (alarmInfo.retryCount > 3) {
    console.error('Alarm failed after 3 retries, giving up');
    return;
  }

  try {
    // Idempotent operation (safe to retry)
    await this.sendNotification();
  } catch (error) {
    console.error('Alarm failed:', error);
    throw error;  // Will trigger retry
  }
}

typescript
import { DurableObject } from 'cloudflare:workers';

export class Batcher extends DurableObject {
  buffer: string[];

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);

    ctx.blockConcurrencyWhile(async () => {
      // 从存储恢复缓冲区
      this.buffer = await ctx.storage.get('buffer') || [];
    });
  }

  async addItem(item: string): Promise<void> {
    this.buffer.push(item);
    await this.ctx.storage.put('buffer', this.buffer);

    // 10秒后调度警报(如果尚未设置)
    const currentAlarm = await this.ctx.storage.getAlarm();
    if (currentAlarm === null) {
      await this.ctx.storage.setAlarm(Date.now() + 10000);
    }
  }

  // 警报触发时调用
  async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
    console.log(`警报触发(重试次数:${alarmInfo.retryCount}`);

    // 处理批处理
    if (this.buffer.length > 0) {
      await this.processBatch(this.buffer);

      // 清空缓冲区
      this.buffer = [];
      await this.ctx.storage.put('buffer', []);
    }

    // 执行成功后警报会自动删除
  }

  async processBatch(items: string[]): Promise<void> {
    // 发送到外部API、写入数据库等
    console.log(`处理 ${items.length} 个条目:`, items);
  }
}
Alarm API方法:
typescript
// 设置警报在特定时间戳触发
await this.ctx.storage.setAlarm(Date.now() + 60000);  // 60秒后

// 设置警报在特定日期触发
await this.ctx.storage.setAlarm(new Date('2025-12-31T23:59:59Z'));

// 获取当前警报(未设置则返回null)
const alarmTime = await this.ctx.storage.getAlarm();

// 删除警报
await this.ctx.storage.deleteAlarm();

// 警报处理器(警报触发时调用)
async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
  // 执行任务
}
Alarm行为:
  • 至少执行一次保证 - 失败时会重试
  • 自动重试 - 最多6次重试,使用指数退避(从2秒开始)
  • 持久化 - 即使DO休眠或被驱逐也能保留
  • 执行成功后自动删除
  • ⚠️ 每个DO只能有一个警报 - 设置新警报会覆盖旧警报
重试模式(幂等操作):
typescript
async alarm(alarmInfo: { retryCount: number; isRetry: boolean }): Promise<void> {
  if (alarmInfo.retryCount > 3) {
    console.error('3次重试后失败,放弃执行');
    return;
  }

  try {
    // 幂等操作(重试安全)
    await this.sendNotification();
  } catch (error) {
    console.error('警报执行失败:', error);
    throw error;  // 触发重试
  }
}

RPC vs HTTP Fetch

RPC vs HTTP Fetch

Durable Objects support two invocation patterns:
  1. RPC (Remote Procedure Call) - Recommended for new projects
  2. HTTP Fetch - For HTTP request/response flows or legacy compatibility
Durable Objects支持两种调用模式:
  1. RPC(远程过程调用) - 推荐用于新项目
  2. HTTP Fetch - 用于HTTP请求/响应流或遗留兼容性

RPC Pattern (Recommended)

RPC模式(推荐)

Enable RPC with compatibility date
>= 2024-04-03
:
jsonc
{
  "compatibility_date": "2025-10-22"
}
Define RPC methods on DO class:
typescript
export class Counter extends DurableObject {
  // Public RPC methods (automatically exposed)
  async increment(): Promise<number> {
    let value = await this.ctx.storage.get<number>('count') || 0;
    value += 1;
    await this.ctx.storage.put('count', value);
    return value;
  }

  async decrement(): Promise<number> {
    let value = await this.ctx.storage.get<number>('count') || 0;
    value -= 1;
    await this.ctx.storage.put('count', value);
    return value;
  }

  async get(): Promise<number> {
    return await this.ctx.storage.get<number>('count') || 0;
  }
}
Call from Worker:
typescript
// Get stub
const id = env.COUNTER.idFromName('my-counter');
const stub = env.COUNTER.get(id);

// Call RPC methods directly
const count = await stub.increment();
const current = await stub.get();
RPC Benefits:
  • Type-safe - TypeScript knows method signatures
  • Simple - Direct method calls, no HTTP ceremony
  • Automatic serialization - Handles structured data
  • Exception propagation - Errors thrown in DO are received in Worker
启用RPC需要兼容性日期
>= 2024-04-03
:
jsonc
{
  "compatibility_date": "2025-10-22"
}
在DO类上定义RPC方法:
typescript
export class Counter extends DurableObject {
  // 公共RPC方法(自动暴露)
  async increment(): Promise<number> {
    let value = await this.ctx.storage.get<number>('count') || 0;
    value += 1;
    await this.ctx.storage.put('count', value);
    return value;
  }

  async decrement(): Promise<number> {
    let value = await this.ctx.storage.get<number>('count') || 0;
    value -= 1;
    await this.ctx.storage.put('count', value);
    return value;
  }

  async get(): Promise<number> {
    return await this.ctx.storage.get<number>('count') || 0;
  }
}
从Worker中调用:
typescript
// 获取存根
const id = env.COUNTER.idFromName('my-counter');
const stub = env.COUNTER.get(id);

// 直接调用RPC方法
const count = await stub.increment();
const current = await stub.get();
RPC优势:
  • 类型安全 - TypeScript知晓方法签名
  • 简单易用 - 直接方法调用,无需HTTP繁琐流程
  • 自动序列化 - 处理结构化数据
  • 异常传播 - DO中抛出的错误会传递到Worker

HTTP Fetch Pattern

HTTP Fetch模式

Define
fetch()
handler
on DO class:
typescript
export class Counter extends DurableObject {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/increment' && request.method === 'POST') {
      let value = await this.ctx.storage.get<number>('count') || 0;
      value += 1;
      await this.ctx.storage.put('count', value);
      return new Response(JSON.stringify({ count: value }));
    }

    if (url.pathname === '/get' && request.method === 'GET') {
      let value = await this.ctx.storage.get<number>('count') || 0;
      return new Response(JSON.stringify({ count: value }));
    }

    return new Response('Not found', { status: 404 });
  }
}
Call from Worker:
typescript
// Get stub
const id = env.COUNTER.idFromName('my-counter');
const stub = env.COUNTER.get(id);

// Call fetch
const response = await stub.fetch('https://fake-host/increment', {
  method: 'POST',
});

const data = await response.json();
在DO类上定义
fetch()
处理器
:
typescript
export class Counter extends DurableObject {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/increment' && request.method === 'POST') {
      let value = await this.ctx.storage.get<number>('count') || 0;
      value += 1;
      await this.ctx.storage.put('count', value);
      return new Response(JSON.stringify({ count: value }));
    }

    if (url.pathname === '/get' && request.method === 'GET') {
      let value = await this.ctx.storage.get<number>('count') || 0;
      return new Response(JSON.stringify({ count: value }));
    }

    return new Response('未找到', { status: 404 });
  }
}
从Worker中调用:
typescript
// 获取存根
const id = env.COUNTER.idFromName('my-counter');
const stub = env.COUNTER.get(id);

// 调用fetch
const response = await stub.fetch('https://fake-host/increment', {
  method: 'POST',
});

const data = await response.json();

When to Use Each

何时使用哪种模式

Use CaseRecommendation
New project✅ RPC (simpler, type-safe)
HTTP request/response flowHTTP Fetch
Complex routing logicHTTP Fetch
Type safety important✅ RPC
Legacy compatibilityHTTP Fetch
WebSocket upgradesHTTP Fetch (required)

适用场景推荐方案
新项目✅ RPC(更简单、类型安全)
HTTP请求/响应流HTTP Fetch
复杂路由逻辑HTTP Fetch
类型安全重要✅ RPC
遗留兼容性HTTP Fetch
WebSocket升级HTTP Fetch(必需)

Creating Durable Object Stubs and Routing

创建Durable Object存根与路由

To interact with a Durable Object from a Worker, you need to:
  1. Get a Durable Object ID
  2. Create a stub from the ID
  3. Call methods on the stub
要从Worker与Durable Object交互,需要:
  1. 获取Durable Object ID
  2. 从ID创建存根
  3. 调用存根上的方法

Getting Durable Object IDs

获取Durable Object ID

Three methods to create IDs:
三种创建ID的方法:

1.
idFromName(name)
- Named DOs (Most Common)

1.
idFromName(name)
- 命名DO(最常用)

Use when you want consistent routing to the same DO instance based on a name:
typescript
// Same name always routes to same DO instance globally
const roomId = env.CHAT_ROOM.idFromName('room-123');
const userId = env.USER_SESSION.idFromName('user-alice');
const globalCounter = env.COUNTER.idFromName('global');
Use for:
  • Chat rooms (name = room ID)
  • User sessions (name = user ID)
  • Per-tenant logic (name = tenant ID)
  • Global singletons (name = 'global')
Characteristics:
  • Deterministic - same name = same DO instance
  • Easy to reference - just need the name string
  • ⚠️ First access latency - ~100-300ms for global uniqueness check
  • ⚠️ Cached after first use - subsequent access is fast
当你希望基于名称一致路由到同一个DO实例时使用:
typescript
// 相同名称始终路由到同一个全局DO实例
const roomId = env.CHAT_ROOM.idFromName('room-123');
const userId = env.USER_SESSION.idFromName('user-alice');
const globalCounter = env.COUNTER.idFromName('global');
适用场景:
  • 聊天室(名称=房间ID)
  • 用户会话(名称=用户ID)
  • 每个租户的专属逻辑(名称=租户ID)
  • 全局单例(名称='global')
特性:
  • 确定性 - 相同名称=相同DO实例
  • 易于引用 - 只需名称字符串
  • ⚠️ 首次访问延迟 - 全局唯一性检查约100-300ms
  • ⚠️ 首次访问后缓存 - 后续访问速度快

2.
newUniqueId()
- Random IDs

2.
newUniqueId()
- 随机ID

Use when you need a new, unique DO instance:
typescript
// Creates a random, globally unique ID
const id = env.MY_DO.newUniqueId();

// With jurisdiction restriction (EU data residency)
const euId = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });

// Store the ID for future use
const idString = id.toString();
await env.KV.put('session:123', idString);
Use for:
  • Creating new sessions/rooms that don't exist yet
  • One-time use DOs
  • When you don't have a natural name
Characteristics:
  • Lower latency on first use (no global uniqueness check)
  • ⚠️ Must store ID to access same DO later
  • ⚠️ ID format is opaque - can't derive meaning from it
当你需要新的、唯一的DO实例时使用:
typescript
// 创建随机的全局唯一ID
const id = env.MY_DO.newUniqueId();

// 带辖区限制(欧盟数据驻留)
const euId = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });

// 存储ID以便后续使用
const idString = id.toString();
await env.KV.put('session:123', idString);
适用场景:
  • 创建新的会话/房间(之前不存在)
  • 一次性使用的DO
  • 没有自然名称的场景
特性:
  • 首次访问延迟更低(无全局唯一性检查)
  • ⚠️ 必须存储ID才能后续访问同一个DO
  • ⚠️ ID格式不透明 - 无法从ID推导含义

3.
idFromString(idString)
- Recreate from Saved ID

3.
idFromString(idString)
- 从保存的ID重建

Use when you've previously stored an ID and need to recreate it:
typescript
// Get stored ID string (from KV, D1, cookie, etc.)
const idString = await env.KV.get('session:123');

// Recreate ID
const id = env.MY_DO.idFromString(idString);

// Get stub
const stub = env.MY_DO.get(id);
Throws exception if:
  • ID string is invalid
  • ID was not created from the same
    DurableObjectNamespace
当你之前存储过ID,需要重建时使用:
typescript
// 获取存储的ID字符串(来自KV、D1、Cookie等)
const idString = await env.KV.get('session:123');

// 重建ID
const id = env.MY_DO.idFromString(idString);

// 获取存根
const stub = env.MY_DO.get(id);
以下情况会抛出异常:
  • ID字符串无效
  • ID不是从同一个
    DurableObjectNamespace
    创建的

Getting Stubs

获取存根

Method 1:
get(id)
- From ID

方法1:
get(id)
- 从ID获取

typescript
const id = env.MY_DO.idFromName('my-instance');
const stub = env.MY_DO.get(id);

// Call methods
await stub.myMethod();
typescript
const id = env.MY_DO.idFromName('my-instance');
const stub = env.MY_DO.get(id);

// 调用方法
await stub.myMethod();

Method 2:
getByName(name)
- Shortcut for Named DOs

方法2:
getByName(name)
- 命名DO的快捷方式

typescript
// Shortcut that combines idFromName + get
const stub = env.MY_DO.getByName('my-instance');

// Equivalent to:
// const id = env.MY_DO.idFromName('my-instance');
// const stub = env.MY_DO.get(id);

await stub.myMethod();
Recommended for named DOs (cleaner code).
typescript
// 组合idFromName + get的快捷方式
const stub = env.MY_DO.getByName('my-instance');

// 等同于:
// const id = env.MY_DO.idFromName('my-instance');
// const stub = env.MY_DO.get(id);

await stub.myMethod();
推荐用于命名DO(代码更简洁)。

Location Hints (Geographic Routing)

位置提示(地理路由)

Control WHERE a Durable Object is created with location hints:
typescript
// Create DO near specific location
const id = env.MY_DO.idFromName('user-alice');
const stub = env.MY_DO.get(id, { locationHint: 'enam' });  // Eastern North America

// Available location hints:
// - 'wnam' - Western North America
// - 'enam' - Eastern North America
// - 'sam'  - South America
// - 'weur' - Western Europe
// - 'eeur' - Eastern Europe
// - 'apac' - Asia-Pacific
// - 'oc'   - Oceania
// - 'afr'  - Africa
// - 'me'   - Middle East
When to use:
  • ✅ Create DO near user's location (lower latency)
  • ✅ Data residency requirements (e.g., EU users → weur/eeur)
Limitations:
  • ⚠️ Hints are best-effort - not guaranteed
  • ⚠️ Only affects first creation - subsequent access uses existing location
  • ⚠️ Cannot move existing DOs - once created, location is fixed
使用位置提示控制DO的创建位置:
typescript
// 在特定位置附近创建DO
const id = env.MY_DO.idFromName('user-alice');
const stub = env.MY_DO.get(id, { locationHint: 'enam' });  // 北美东部

// 可用的位置提示:
// - 'wnam' - 北美西部
// - 'enam' - 北美东部
// - 'sam'  - 南美
// - 'weur' - 欧洲西部
// - 'eeur' - 欧洲东部
// - 'apac' - 亚太地区
// - 'oc'   - 大洋洲
// - 'afr'  - 非洲
// - 'me'   - 中东
适用场景:
  • ✅ 在用户位置附近创建DO(降低延迟)
  • ✅ 数据驻留要求(如欧盟用户 → weur/eeur)
限制:
  • ⚠️ 提示是尽力而为 - 不保证一定会生效
  • ⚠️ 仅影响首次创建 - 后续访问使用已有的位置
  • ⚠️ 无法移动现有DO - 创建后位置固定

Jurisdiction Restriction (Data Residency)

辖区限制(数据驻留)

Enforce strict data location requirements:
typescript
// Create DO that MUST stay in EU
const euId = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });

// Available jurisdictions:
// - 'eu' - European Union
// - 'fedramp' - FedRAMP (US government)
Use for:
  • Regulatory compliance (GDPR, FedRAMP)
  • Data sovereignty requirements
CRITICAL:
  • Strictly enforced - DO will never leave jurisdiction
  • ⚠️ Cannot combine jurisdiction with location hints
  • ⚠️ Higher latency for users outside jurisdiction

强制严格的数据位置要求:
typescript
// 创建必须留在欧盟的DO
const euId = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });

// 可用的辖区:
// - 'eu' - 欧盟
// - 'fedramp' - FedRAMP(美国政府)
适用场景:
  • 合规要求(GDPR、FedRAMP)
  • 数据主权要求
关键注意事项:
  • 严格执行 - DO永远不会离开指定辖区
  • ⚠️ 不能组合辖区和位置提示
  • ⚠️ 辖区外用户延迟更高

Migrations - Managing DO Classes

迁移 - 管理DO类

Migrations are REQUIRED when you:
  • Create a new DO class
  • Rename a DO class
  • Delete a DO class
  • Transfer a DO class to another Worker
Migration Types:
以下情况必须使用迁移:
  • 创建新的DO类
  • 重命名DO类
  • 删除DO类
  • 将DO类转移到另一个Worker
迁移类型:

1. Create New DO Class

1. 创建新DO类

jsonc
{
  "durable_objects": {
    "bindings": [
      {
        "name": "COUNTER",
        "class_name": "Counter"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",                    // Unique identifier for this migration
      "new_sqlite_classes": [         // SQLite backend (recommended)
        "Counter"
      ]
    }
  ]
}
For KV backend (legacy):
jsonc
{
  "migrations": [
    {
      "tag": "v1",
      "new_classes": ["Counter"]      // KV backend (128MB limit)
    }
  ]
}
CRITICAL:
  • ✅ Use
    new_sqlite_classes
    for new DOs (up to 1GB storage)
  • ❌ Cannot enable SQLite on existing deployed KV-backed DOs
jsonc
{
  "durable_objects": {
    "bindings": [
      {
        "name": "COUNTER",
        "class_name": "Counter"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",                    // 此迁移的唯一标识符
      "new_sqlite_classes": [         // SQLite后端(推荐)
        "Counter"
      ]
    }
  ]
}
对于KV后端(遗留):
jsonc
{
  "migrations": [
    {
      "tag": "v1",
      "new_classes": ["Counter"]      // KV后端(128MB限制)
    }
  ]
}
关键注意事项:
  • ✅ 新DO使用
    new_sqlite_classes
    (最大1GB存储)
  • ❌ 无法在已部署的KV后端DO上启用SQLite

2. Rename DO Class

2. 重命名DO类

jsonc
{
  "durable_objects": {
    "bindings": [
      {
        "name": "MY_DO",
        "class_name": "NewClassName"    // New class name
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["OldClassName"]
    },
    {
      "tag": "v2",                      // New migration tag
      "renamed_classes": [
        {
          "from": "OldClassName",
          "to": "NewClassName"
        }
      ]
    }
  ]
}
What happens:
  • ✅ Existing DO instances keep their data
  • ✅ Old bindings automatically forward to new class
  • ⚠️ Must export new class in Worker code
jsonc
{
  "durable_objects": {
    "bindings": [
      {
        "name": "MY_DO",
        "class_name": "NewClassName"    // 新类名
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["OldClassName"]
    },
    {
      "tag": "v2",                      // 新迁移标签
      "renamed_classes": [
        {
          "from": "OldClassName",
          "to": "NewClassName"
        }
      ]
    }
  ]
}
迁移效果:
  • ✅ 现有DO实例保留数据
  • ✅ 旧绑定自动转发到新类
  • ⚠️ 必须导出新类到Worker代码中

3. Delete DO Class

3. 删除DO类

jsonc
{
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["Counter"]
    },
    {
      "tag": "v2",
      "deleted_classes": ["Counter"]    // Mark as deleted
    }
  ]
}
What happens:
  • ✅ Existing DO instances are deleted immediately
  • ✅ All storage is deleted
  • ⚠️ Cannot undo - data is permanently lost
Before deleting:
  • Export data if needed
  • Update Workers that reference this DO
jsonc
{
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["Counter"]
    },
    {
      "tag": "v2",
      "deleted_classes": ["Counter"]    // 标记为已删除
    }
  ]
}
迁移效果:
  • ✅ 现有DO实例立即删除
  • ✅ 所有存储被删除
  • ⚠️ 无法撤销 - 数据永久丢失
删除前注意:
  • 如有需要,导出数据
  • 更新引用此DO的Worker

4. Transfer DO Class to Another Worker

4. 将DO类转移到另一个Worker

jsonc
// In destination Worker:
{
  "durable_objects": {
    "bindings": [
      {
        "name": "TRANSFERRED_DO",
        "class_name": "TransferredClass"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "transferred_classes": [
        {
          "from": "OriginalClass",
          "from_script": "original-worker",  // Source Worker name
          "to": "TransferredClass"
        }
      ]
    }
  ]
}
What happens:
  • ✅ DO instances move to new Worker
  • ✅ All storage is transferred
  • ✅ Old bindings automatically forward
  • ⚠️ Destination class must be exported
jsonc
// 在目标Worker中:
{
  "durable_objects": {
    "bindings": [
      {
        "name": "TRANSFERRED_DO",
        "class_name": "TransferredClass"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "transferred_classes": [
        {
          "from": "OriginalClass",
          "from_script": "original-worker",  // 源Worker名称
          "to": "TransferredClass"
        }
      ]
    }
  ]
}
迁移效果:
  • ✅ DO实例移动到新Worker
  • ✅ 所有存储被转移
  • ✅ 旧绑定自动转发
  • ⚠️ 必须导出目标类

Migration Rules

迁移规则

CRITICAL Migration Gotchas:
Migrations are ATOMIC - cannot gradual deploy
  • All instances migrate at once when you deploy
  • No partial rollout support
Migration tags must be unique
  • Cannot reuse tags
  • Tags are append-only
Cannot enable SQLite on existing KV-backed DOs
  • Must create new DO class instead
Code changes don't need migrations
  • Only schema changes (new/rename/delete/transfer) need migrations
  • You can deploy code updates freely
Global uniqueness is per account
  • DO class names are unique across your entire account
  • Even across different Workers

关键迁移注意事项:
迁移是原子操作 - 无法逐步部署
  • 部署时所有实例同时迁移
  • 不支持部分发布
迁移标签必须唯一
  • 不能重复使用标签
  • 标签只能追加
无法在现有KV后端DO上启用SQLite
  • 必须创建新的DO类
代码变更不需要迁移
  • 只有架构变更(新建/重命名/删除/转移)需要迁移
  • 可以自由部署代码更新
全局唯一性按账户划分
  • DO类名在整个账户中唯一
  • 即使在不同Worker中也是如此

Common Patterns

常见模式

Pattern 1: Rate Limiting (Per-User)

模式1:速率限制(每个用户)

typescript
export class RateLimiter extends DurableObject {
  async checkLimit(userId: string, limit: number, window: number): Promise<boolean> {
    const key = `rate:${userId}`;
    const now = Date.now();

    // Get recent requests
    const requests = await this.ctx.storage.get<number[]>(key) || [];

    // Remove requests outside window
    const validRequests = requests.filter(timestamp => now - timestamp < window);

    // Check limit
    if (validRequests.length >= limit) {
      return false;  // Rate limit exceeded
    }

    // Add current request
    validRequests.push(now);
    await this.ctx.storage.put(key, validRequests);

    return true;  // Within limit
  }
}

// Worker usage:
const limiter = env.RATE_LIMITER.getByName(userId);
const allowed = await limiter.checkLimit(userId, 100, 60000);  // 100 req/min

if (!allowed) {
  return new Response('Rate limit exceeded', { status: 429 });
}
typescript
export class RateLimiter extends DurableObject {
  async checkLimit(userId: string, limit: number, window: number): Promise<boolean> {
    const key = `rate:${userId}`;
    const now = Date.now();

    // 获取最近的请求
    const requests = await this.ctx.storage.get<number[]>(key) || [];

    // 移除窗口外的请求
    const validRequests = requests.filter(timestamp => now - timestamp < window);

    // 检查限制
    if (validRequests.length >= limit) {
      return false;  // 超出速率限制
    }

    // 添加当前请求
    validRequests.push(now);
    await this.ctx.storage.put(key, validRequests);

    return true;  // 在限制内
  }
}

// Worker使用:
const limiter = env.RATE_LIMITER.getByName(userId);
const allowed = await limiter.checkLimit(userId, 100, 60000);  // 每分钟100次请求

if (!allowed) {
  return new Response('超出速率限制', { status: 429 });
}

Pattern 2: Session Management

模式2:会话管理

typescript
export class UserSession extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    this.sql.exec(`
      CREATE TABLE IF NOT EXISTS session (
        key TEXT PRIMARY KEY,
        value TEXT NOT NULL,
        expires_at INTEGER
      );
    `);

    // Schedule cleanup alarm
    ctx.blockConcurrencyWhile(async () => {
      const alarm = await ctx.storage.getAlarm();
      if (alarm === null) {
        await ctx.storage.setAlarm(Date.now() + 3600000);  // 1 hour
      }
    });
  }

  async set(key: string, value: any, ttl?: number): Promise<void> {
    const expiresAt = ttl ? Date.now() + ttl : null;

    this.sql.exec(
      'INSERT OR REPLACE INTO session (key, value, expires_at) VALUES (?, ?, ?)',
      key,
      JSON.stringify(value),
      expiresAt
    );
  }

  async get(key: string): Promise<any | null> {
    const cursor = this.sql.exec(
      'SELECT value, expires_at FROM session WHERE key = ?',
      key
    );

    const row = cursor.one<{ value: string; expires_at: number | null }>({ allowNone: true });

    if (!row) {
      return null;
    }

    // Check expiration
    if (row.expires_at && row.expires_at < Date.now()) {
      this.sql.exec('DELETE FROM session WHERE key = ?', key);
      return null;
    }

    return JSON.parse(row.value);
  }

  async alarm(): Promise<void> {
    // Cleanup expired sessions
    this.sql.exec('DELETE FROM session WHERE expires_at < ?', Date.now());

    // Schedule next cleanup
    await this.ctx.storage.setAlarm(Date.now() + 3600000);
  }
}
typescript
export class UserSession extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    this.sql.exec(`
      CREATE TABLE IF NOT EXISTS session (
        key TEXT PRIMARY KEY,
        value TEXT NOT NULL,
        expires_at INTEGER
      );
    `);

    // 调度清理警报
    ctx.blockConcurrencyWhile(async () => {
      const alarm = await ctx.storage.getAlarm();
      if (alarm === null) {
        await ctx.storage.setAlarm(Date.now() + 3600000);  // 1小时
      }
    });
  }

  async set(key: string, value: any, ttl?: number): Promise<void> {
    const expiresAt = ttl ? Date.now() + ttl : null;

    this.sql.exec(
      'INSERT OR REPLACE INTO session (key, value, expires_at) VALUES (?, ?, ?)',
      key,
      JSON.stringify(value),
      expiresAt
    );
  }

  async get(key: string): Promise<any | null> {
    const cursor = this.sql.exec(
      'SELECT value, expires_at FROM session WHERE key = ?',
      key
    );

    const row = cursor.one<{ value: string; expires_at: number | null }>({ allowNone: true });

    if (!row) {
      return null;
    }

    // 检查过期
    if (row.expires_at && row.expires_at < Date.now()) {
      this.sql.exec('DELETE FROM session WHERE key = ?', key);
      return null;
    }

    return JSON.parse(row.value);
  }

  async alarm(): Promise<void> {
    // 清理过期会话
    this.sql.exec('DELETE FROM session WHERE expires_at < ?', Date.now());

    // 调度下一次清理
    await this.ctx.storage.setAlarm(Date.now() + 3600000);
  }
}

Pattern 3: Leader Election

模式3:领导者选举

typescript
export class LeaderElection extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    this.sql.exec(`
      CREATE TABLE IF NOT EXISTS leader (
        id INTEGER PRIMARY KEY CHECK (id = 1),
        worker_id TEXT NOT NULL,
        elected_at INTEGER NOT NULL
      );
    `);
  }

  async electLeader(workerId: string): Promise<boolean> {
    // Try to become leader
    try {
      this.sql.exec(
        'INSERT INTO leader (id, worker_id, elected_at) VALUES (1, ?, ?)',
        workerId,
        Date.now()
      );
      return true;  // Became leader
    } catch (error) {
      return false;  // Someone else is leader
    }
  }

  async getLeader(): Promise<string | null> {
    const cursor = this.sql.exec('SELECT worker_id FROM leader WHERE id = 1');
    const row = cursor.one<{ worker_id: string }>({ allowNone: true });
    return row?.worker_id || null;
  }

  async releaseLeadership(workerId: string): Promise<void> {
    this.sql.exec('DELETE FROM leader WHERE id = 1 AND worker_id = ?', workerId);
  }
}
typescript
export class LeaderElection extends DurableObject {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;

    this.sql.exec(`
      CREATE TABLE IF NOT EXISTS leader (
        id INTEGER PRIMARY KEY CHECK (id = 1),
        worker_id TEXT NOT NULL,
        elected_at INTEGER NOT NULL
      );
    `);
  }

  async electLeader(workerId: string): Promise<boolean> {
    // 尝试成为领导者
    try {
      this.sql.exec(
        'INSERT INTO leader (id, worker_id, elected_at) VALUES (1, ?, ?)',
        workerId,
        Date.now()
      );
      return true;  // 成为领导者
    } catch (error) {
      return false;  // 已有领导者
    }
  }

  async getLeader(): Promise<string | null> {
    const cursor = this.sql.exec('SELECT worker_id FROM leader WHERE id = 1');
    const row = cursor.one<{ worker_id: string }>({ allowNone: true });
    return row?.worker_id || null;
  }

  async releaseLeadership(workerId: string): Promise<void> {
    this.sql.exec('DELETE FROM leader WHERE id = 1 AND worker_id = ?', workerId);
  }
}

Pattern 4: Multi-DO Coordination

模式4:多DO协调

typescript
// Coordinator DO
export class GameCoordinator extends DurableObject {
  async createGame(gameId: string, env: Env): Promise<void> {
    // Create game room DO
    const gameRoom = env.GAME_ROOM.getByName(gameId);
    await gameRoom.initialize();

    // Track in coordinator
    await this.ctx.storage.put(`game:${gameId}`, {
      id: gameId,
      created: Date.now(),
    });
  }

  async listGames(): Promise<string[]> {
    const games = await this.ctx.storage.list({ prefix: 'game:' });
    return Array.from(games.keys()).map(key => key.replace('game:', ''));
  }
}

// Game room DO
export class GameRoom extends DurableObject {
  async initialize(): Promise<void> {
    await this.ctx.storage.put('state', {
      players: [],
      started: false,
    });
  }

  async addPlayer(playerId: string): Promise<void> {
    const state = await this.ctx.storage.get('state');
    state.players.push(playerId);
    await this.ctx.storage.put('state', state);
  }
}

typescript
// 协调器DO
export class GameCoordinator extends DurableObject {
  async createGame(gameId: string, env: Env): Promise<void> {
    // 创建游戏房间DO
    const gameRoom = env.GAME_ROOM.getByName(gameId);
    await gameRoom.initialize();

    // 在协调器中跟踪
    await this.ctx.storage.put(`game:${gameId}`, {
      id: gameId,
      created: Date.now(),
    });
  }

  async listGames(): Promise<string[]> {
    const games = await this.ctx.storage.list({ prefix: 'game:' });
    return Array.from(games.keys()).map(key => key.replace('game:', ''));
  }
}

// 游戏房间DO
export class GameRoom extends DurableObject {
  async initialize(): Promise<void> {
    await this.ctx.storage.put('state', {
      players: [],
      started: false,
    });
  }

  async addPlayer(playerId: string): Promise<void> {
    const state = await this.ctx.storage.get('state');
    state.players.push(playerId);
    await this.ctx.storage.put('state', state);
  }
}

Critical Rules

关键规则

Always Do

必须遵守

Export DO class from Worker
typescript
export class MyDO extends DurableObject { }
export default MyDO;  // Required
Call
super(ctx, env)
in constructor
typescript
constructor(ctx: DurableObjectState, env: Env) {
  super(ctx, env);  // Required first line
}
Use
new_sqlite_classes
for new DOs
jsonc
{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }
Use
ctx.acceptWebSocket()
for hibernation
typescript
this.ctx.acceptWebSocket(server);  // Enables hibernation
Persist critical state to storage (not just memory)
typescript
await this.ctx.storage.put('important', value);
Use alarms instead of setTimeout/setInterval
typescript
await this.ctx.storage.setAlarm(Date.now() + 60000);
Use parameterized SQL queries
typescript
this.sql.exec('SELECT * FROM table WHERE id = ?', id);
Minimize constructor work
typescript
constructor(ctx, env) {
  super(ctx, env);
  // Minimal initialization only
  ctx.blockConcurrencyWhile(async () => {
    // Load from storage
  });
}
从Worker中导出DO类
typescript
export class MyDO extends DurableObject { }
export default MyDO;  // 必填
在构造函数中调用
super(ctx, env)
typescript
constructor(ctx: DurableObjectState, env: Env) {
  super(ctx, env);  // 必须是第一行
}
新DO使用
new_sqlite_classes
jsonc
{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }
使用
ctx.acceptWebSocket()
启用休眠
typescript
this.ctx.acceptWebSocket(server);  // 启用休眠
将关键状态持久化到存储(不只是内存)
typescript
await this.ctx.storage.put('important', value);
使用alarms替代setTimeout/setInterval
typescript
await this.ctx.storage.setAlarm(Date.now() + 60000);
使用参数化SQL查询
typescript
this.sql.exec('SELECT * FROM table WHERE id = ?', id);
最小化构造函数工作
typescript
constructor(ctx, env) {
  super(ctx, env);
  // 仅做最小化初始化
  ctx.blockConcurrencyWhile(async () => {
    // 从存储加载
  });
}

Never Do

禁止操作

Create DO without migration
jsonc
// Missing migrations array = error
Forget to export DO class
typescript
class MyDO extends DurableObject { }
// Missing: export default MyDO;
Use
setTimeout
or
setInterval
typescript
setTimeout(() => {}, 1000);  // Prevents hibernation
Rely only on in-memory state with WebSockets
typescript
// ❌ WRONG: this.sessions will be lost on hibernation
// ✅ CORRECT: Use serializeAttachment()
Deploy migrations gradually
bash
undefined
创建DO时不配置迁移
jsonc
// 缺少migrations数组会报错
忘记导出DO类
typescript
class MyDO extends DurableObject { }
// 缺少:export default MyDO;
使用
setTimeout
setInterval
typescript
setTimeout(() => {}, 1000);  // 阻止休眠
仅依赖内存状态处理WebSocket
typescript
// ❌ 错误:休眠时this.sessions会丢失
// ✅ 正确:使用serializeAttachment()
逐步部署迁移
bash
undefined

Migrations are atomic - cannot use gradual rollout

迁移是原子操作 - 不能使用逐步发布


❌ **Enable SQLite on existing KV-backed DO**
```jsonc
// Not supported - must create new DO class instead
Use standard WebSocket API expecting hibernation
typescript
ws.accept();  // ❌ No hibernation
this.ctx.acceptWebSocket(ws);  // ✅ Hibernation enabled
Assume location hints are guaranteed
typescript
// Location hints are best-effort only


❌ **在现有KV后端DO上启用SQLite**
```jsonc
// 不支持 - 必须创建新的DO类
使用标准WebSocket API却期望休眠
typescript
ws.accept();  // ❌ 无休眠
this.ctx.acceptWebSocket(ws);  // ✅ 启用休眠
假设位置提示一定会生效
typescript
// 位置提示只是尽力而为,不保证

Known Issues Prevention

已知问题预防

This skill prevents 15+ documented issues:
本指南可预防15+种已记录的问题:

Issue #1: Class Not Exported

问题1:类未导出

Error:
"binding not found"
or
"Class X not found"
Source: https://developers.cloudflare.com/durable-objects/get-started/ Why It Happens: DO class not exported from Worker Prevention:
typescript
export class MyDO extends DurableObject { }
export default MyDO;  // ← Required
错误信息:
"binding not found"
"Class X not found"
来源: https://developers.cloudflare.com/durable-objects/get-started/ 原因: DO类未从Worker导出 解决方法:
typescript
export class MyDO extends DurableObject { }
export default MyDO;  // ← 必填

Issue #2: Missing Migration

问题2:缺少迁移

Error:
"migrations required"
or
"no migration found for class"
Source: https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ Why It Happens: Created DO class without migration entry Prevention: Always add migration when creating new DO class
jsonc
{
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["MyDO"] }
  ]
}
错误信息:
"migrations required"
"no migration found for class"
来源: https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ 原因: 创建DO类但未添加迁移条目 解决方法: 创建新DO类时始终添加迁移
jsonc
{
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["MyDO"] }
  ]
}

Issue #3: Wrong Migration Type (KV vs SQLite)

问题3:错误的迁移类型(KV vs SQLite)

Error: Schema errors, storage API mismatch Source: https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/ Why It Happens: Used
new_classes
instead of
new_sqlite_classes
Prevention: Use
new_sqlite_classes
for SQLite backend (recommended)
错误信息: 架构错误、存储API不匹配 来源: https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/ 原因: 使用了
new_classes
而非
new_sqlite_classes
解决方法: 推荐使用
new_sqlite_classes
启用SQLite后端

Issue #4: Constructor Overhead Blocks Hibernation Wake

问题4:构造函数开销阻止休眠唤醒

Error: Slow hibernation wake-up times Source: https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/ Why It Happens: Heavy work in constructor Prevention: Minimize constructor, use
blockConcurrencyWhile()
typescript
constructor(ctx, env) {
  super(ctx, env);
  ctx.blockConcurrencyWhile(async () => {
    // Load from storage
  });
}
错误信息: 休眠唤醒时间慢 来源: https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/ 原因: 构造函数中执行繁重工作 解决方法: 最小化构造函数,使用
blockConcurrencyWhile()
typescript
constructor(ctx, env) {
  super(ctx, env);
  ctx.blockConcurrencyWhile(async () => {
    // 从存储加载
  });
}

Issue #5: setTimeout Breaks Hibernation

问题5:setTimeout破坏休眠

Error: DO never hibernates, high duration charges Source: https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/ Why It Happens:
setTimeout
/
setInterval
prevents hibernation Prevention: Use alarms API instead
typescript
// ❌ WRONG
setTimeout(() => {}, 1000);

// ✅ CORRECT
await this.ctx.storage.setAlarm(Date.now() + 1000);
错误信息: DO永远不休眠,时长费用高 来源: https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/ 原因:
setTimeout
/
setInterval
阻止休眠 解决方法: 使用alarms API替代
typescript
// ❌ 错误
setTimeout(() => {}, 1000);

// ✅ 正确
await this.ctx.storage.setAlarm(Date.now() + 1000);

Issue #6: In-Memory State Lost on Hibernation

问题6:休眠时内存状态丢失

Error: WebSocket metadata lost, state reset unexpectedly Source: https://developers.cloudflare.com/durable-objects/best-practices/websockets/ Why It Happens: Relied on in-memory state that's cleared on hibernation Prevention: Use
serializeAttachment()
for WebSocket metadata
typescript
ws.serializeAttachment({ userId, username });

// Restore in constructor
ctx.getWebSockets().forEach(ws => {
  const metadata = ws.deserializeAttachment();
  this.sessions.set(ws, metadata);
});
错误信息: WebSocket元数据丢失,状态意外重置 来源: https://developers.cloudflare.com/durable-objects/best-practices/websockets/ 原因: 依赖休眠时会被清除的内存状态 解决方法: 使用
serializeAttachment()
存储WebSocket元数据
typescript
ws.serializeAttachment({ userId, username });

// 在构造函数中恢复
ctx.getWebSockets().forEach(ws => {
  const metadata = ws.deserializeAttachment();
  this.sessions.set(ws, metadata);
});

Issue #7: Outgoing WebSocket Cannot Hibernate

问题7:出站WebSocket无法休眠

Error: High charges despite hibernation API Source: https://developers.cloudflare.com/durable-objects/best-practices/websockets/ Why It Happens: Outgoing WebSockets don't support hibernation Prevention: Only use hibernation for server-side (incoming) WebSockets
错误信息: 尽管使用了休眠API,费用仍很高 来源: https://developers.cloudflare.com/durable-objects/best-practices/websockets/ 原因: 出站WebSocket不支持休眠 解决方法: 仅对服务器端(入站)WebSocket使用休眠

Issue #8: Global Uniqueness Confusion

问题8:全局唯一性混淆

Error: Unexpected DO class name conflicts Source: https://developers.cloudflare.com/durable-objects/platform/known-issues/#global-uniqueness Why It Happens: DO class names are globally unique per account Prevention: Understand DO class names are shared across all Workers in account
错误信息: 意外的DO类名冲突 来源: https://developers.cloudflare.com/durable-objects/platform/known-issues/#global-uniqueness 原因: DO类名在整个账户中全局唯一 解决方法: 了解DO类名在账户内所有Worker中共享

Issue #9: Partial deleteAll on KV Backend

问题9:KV后端上的partial deleteAll

Error: Storage not fully deleted, billing continues Source: https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/ Why It Happens: KV backend
deleteAll()
can fail partially Prevention: Use SQLite backend for atomic deleteAll
错误信息: 存储未完全删除,仍产生账单 来源: https://developers.cloudflare.com/durable-objects/api/legacy-kv-storage-api/ 原因: KV后端的
deleteAll()
可能部分失败 解决方法: 使用SQLite后端实现原子deleteAll

Issue #10: Binding Name Mismatch

问题10:绑定名称不匹配

Error: Runtime error accessing DO binding Source: https://developers.cloudflare.com/durable-objects/get-started/ Why It Happens: Binding name in wrangler.jsonc doesn't match code Prevention: Ensure consistency
jsonc
{ "bindings": [{ "name": "MY_DO", "class_name": "MyDO" }] }
typescript
env.MY_DO.getByName('instance');  // Must match binding name
错误信息: 运行时访问DO绑定时出错 来源: https://developers.cloudflare.com/durable-objects/get-started/ 原因: wrangler.jsonc中的绑定名称与代码不匹配 解决方法: 确保名称一致
jsonc
{ "bindings": [{ "name": "MY_DO", "class_name": "MyDO" }] }
typescript
env.MY_DO.getByName('instance');  // 必须与绑定名称匹配

Issue #11: State Size Exceeded

问题11:超出状态大小限制

Error:
"state limit exceeded"
or storage errors Source: https://developers.cloudflare.com/durable-objects/platform/pricing/ Why It Happens: Exceeded 1GB (SQLite) or 128MB (KV) limit Prevention: Monitor storage size, implement cleanup with alarms
错误信息:
"state limit exceeded"
或存储错误 来源: https://developers.cloudflare.com/durable-objects/platform/pricing/ 原因: 超出了1GB(SQLite)或128MB(KV)的限制 解决方法: 监控存储大小,使用alarms实现清理

Issue #12: Migration Not Atomic

问题12:迁移不是原子操作

Error: Gradual deployment blocked Source: https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/ Why It Happens: Tried to use gradual rollout with migrations Prevention: Migrations deploy atomically across all instances
错误信息: 逐步部署被阻止 来源: https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/ 原因: 尝试结合逐步发布和迁移 解决方法: 迁移会在所有实例中原子部署

Issue #13: Location Hint Ignored

问题13:位置提示被忽略

Error: DO created in wrong region Source: https://developers.cloudflare.com/durable-objects/reference/data-location/ Why It Happens: Location hints are best-effort, not guaranteed Prevention: Use jurisdiction for strict requirements
错误信息: DO创建在错误的区域 来源: https://developers.cloudflare.com/durable-objects/reference/data-location/ 原因: 位置提示是尽力而为,不保证生效 解决方法: 使用辖区限制满足严格要求

Issue #14: Alarm Retry Failures

问题14:警报重试失败

Error: Tasks lost after alarm failures Source: https://developers.cloudflare.com/durable-objects/api/alarms/ Why It Happens: Alarm handler throws errors repeatedly Prevention: Implement idempotent alarm handlers
typescript
async alarm(info: { retryCount: number }): Promise<void> {
  if (info.retryCount > 3) {
    console.error('Giving up after 3 retries');
    return;
  }
  // Idempotent operation
}
错误信息: 警报失败后任务丢失 来源: https://developers.cloudflare.com/durable-objects/api/alarms/ 原因: 警报处理器反复抛出错误 解决方法: 实现幂等的警报处理器
typescript
async alarm(info: { retryCount: number }): Promise<void> {
  if (info.retryCount > 3) {
    console.error('3次重试后放弃');
    return;
  }
  // 幂等操作
}

Issue #15: Fetch Blocks Hibernation

问题15:Fetch阻止休眠

Error: DO never hibernates despite using hibernation API Source: https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/ Why It Happens: In-progress
fetch()
requests prevent hibernation Prevention: Ensure all async I/O completes before idle period

错误信息: 尽管使用了休眠API,DO永远不休眠 来源: https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/ 原因: 正在进行的
fetch()
请求阻止休眠 解决方法: 确保空闲前所有异步I/O完成

Configuration Reference

配置参考

Complete wrangler.jsonc Example

完整的wrangler.jsonc示例

jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-22",

  // Durable Objects configuration
  "durable_objects": {
    "bindings": [
      {
        "name": "COUNTER",              // Binding name (use as env.COUNTER)
        "class_name": "Counter"         // Must match exported class
      },
      {
        "name": "CHAT_ROOM",
        "class_name": "ChatRoom"
      }
    ]
  },

  // Migrations (required for all DO changes)
  "migrations": [
    {
      "tag": "v1",                      // Initial migration
      "new_sqlite_classes": [
        "Counter",
        "ChatRoom"
      ]
    },
    {
      "tag": "v2",                      // Rename example
      "renamed_classes": [
        {
          "from": "Counter",
          "to": "CounterV2"
        }
      ]
    }
  ]
}

jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-22",

  // Durable Objects配置
  "durable_objects": {
    "bindings": [
      {
        "name": "COUNTER",              // 绑定名称(通过env.COUNTER访问)
        "class_name": "Counter"         // 必须与导出的类名匹配
      },
      {
        "name": "CHAT_ROOM",
        "class_name": "ChatRoom"
      }
    ]
  },

  // 迁移(所有DO变更都需要)
  "migrations": [
    {
      "tag": "v1",                      // 初始迁移
      "new_sqlite_classes": [
        "Counter",
        "ChatRoom"
      ]
    },
    {
      "tag": "v2",                      // 重命名示例
      "renamed_classes": [
        {
          "from": "Counter",
          "to": "CounterV2"
        }
      ]
    }
  ]
}

TypeScript Types

TypeScript类型

typescript
import { DurableObject, DurableObjectState, DurableObjectNamespace } from 'cloudflare:workers';

// Environment bindings
interface Env {
  MY_DO: DurableObjectNamespace<MyDurableObject>;
  DB: D1Database;
  // ... other bindings
}

// Durable Object class
export class MyDurableObject extends DurableObject<Env> {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;
  }

  async myMethod(): Promise<string> {
    // Access env bindings
    await this.env.DB.prepare('...').run();
    return 'Hello';
  }
}

typescript
import { DurableObject, DurableObjectState, DurableObjectNamespace } from 'cloudflare:workers';

// 环境绑定
interface Env {
  MY_DO: DurableObjectNamespace<MyDurableObject>;
  DB: D1Database;
  // ... 其他绑定
}

// Durable Object类
export class MyDurableObject extends DurableObject<Env> {
  sql: SqlStorage;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;
  }

  async myMethod(): Promise<string> {
    // 访问环境绑定
    await this.env.DB.prepare('...').run();
    return 'Hello';
  }
}

Official Documentation

官方文档