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: 2026-01-21 Dependencies: cloudflare-worker-base (recommended) Latest Versions: wrangler@4.58.0, @cloudflare/workers-types@4.20260109.0 Official Docs: https://developers.cloudflare.com/durable-objects/
Recent Updates (2025):
  • Oct 2025: WebSocket message size 1 MiB → 32 MiB, Data Studio UI for SQLite DOs (view/edit storage in dashboard)
  • Aug 2025:
    getByName()
    API shortcut for named DOs
  • June 2025: @cloudflare/actors library (beta) - recommended SDK with migrations, alarms, Actor class pattern. Note: Beta stability - see active issues before production use (RPC serialization, vitest integration, memory management)
  • May 2025: Python Workers support for Durable Objects
  • April 2025: SQLite GA with 10GB storage (beta → GA, 1GB → 10GB), Free tier access
  • Feb 2025: PRAGMA optimize support, improved error diagnostics with reference IDs

状态:已就绪可用于生产 ✅ 最后更新:2026-01-21 依赖:cloudflare-worker-base(推荐) 最新版本:wrangler@4.58.0, @cloudflare/workers-types@4.20260109.0 官方文档https://developers.cloudflare.com/durable-objects/
2025年近期更新:
  • 2025年10月:WebSocket消息大小从1 MiB提升至32 MiB,新增SQLite DO的数据工作室UI(可在控制台中查看/编辑存储)
  • 2025年8月:为命名DO新增
    getByName()
    API快捷方式
  • 2025年6月:@cloudflare/actors库(测试版)- 推荐的SDK,支持迁移、告警、Actor类模式。注意:测试版稳定性提示 - 生产使用前请查看活跃问题(RPC序列化、vitest集成、内存管理)
  • 2025年5月:Durable Objects支持Python Workers
  • 2025年4月:SQLite正式可用(从测试版转为正式版),存储容量从1GB提升至10GB,免费层可访问
  • 2025年2月:支持PRAGMA optimize,优化了带参考ID的错误诊断

Quick Start

快速开始

Scaffold new DO project:
bash
npm create cloudflare@latest my-durable-app -- --template=cloudflare/durable-objects-template --ts
Or add to existing Worker:
typescript
// src/counter.ts - Durable Object class
import { DurableObject } from 'cloudflare:workers';

export class Counter extends DurableObject {
  async increment(): Promise<number> {
    let value = (await this.ctx.storage.get<number>('value')) || 0;
    await this.ctx.storage.put('value', ++value);
    return value;
  }
}
export default Counter;  // CRITICAL: Export required
jsonc
// wrangler.jsonc - Configuration
{
  "durable_objects": {
    "bindings": [{ "name": "COUNTER", "class_name": "Counter" }]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["Counter"] }  // SQLite backend (10GB limit)
  ]
}
typescript
// src/index.ts - Worker
import { Counter } from './counter';
export { Counter };

export default {
  async fetch(request: Request, env: { COUNTER: DurableObjectNamespace<Counter> }) {
    const stub = env.COUNTER.getByName('global-counter');  // Aug 2025: getByName() shortcut
    return new Response(`Count: ${await stub.increment()}`);
  }
};

搭建新的DO项目:
bash
npm create cloudflare@latest my-durable-app -- --template=cloudflare/durable-objects-template --ts
或添加到现有Worker中:
typescript
// src/counter.ts - Durable Object类
import { DurableObject } from 'cloudflare:workers';

export class Counter extends DurableObject {
  async increment(): Promise<number> {
    let value = (await this.ctx.storage.get<number>('value')) || 0;
    await this.ctx.storage.put('value', ++value);
    return value;
  }
}
export default Counter;  // CRITICAL: Export required
jsonc
// wrangler.jsonc - 配置
{
  "durable_objects": {
    "bindings": [{ "name": "COUNTER", "class_name": "Counter" }]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["Counter"] }  // SQLite后端(10GB限制)
  ]
}
typescript
// src/index.ts - Worker
import { Counter } from './counter';
export { Counter };

export default {
  async fetch(request: Request, env: { COUNTER: DurableObjectNamespace<Counter> }) {
    const stub = env.COUNTER.getByName('global-counter');  // 2025年8月新增:getByName()快捷方式
    return new Response(`Count: ${await stub.increment()}`);
  }
};

DO Class Essentials

DO类核心要点

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

export class MyDO extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);  // REQUIRED first line

    // Load state before requests (optional)
    ctx.blockConcurrencyWhile(async () => {
      this.value = await ctx.storage.get('key') || defaultValue;
    });
  }

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

  // HTTP fetch handler (optional)
  async fetch(request: Request): Promise<Response> { return new Response('OK'); }
}

export default MyDO;  // CRITICAL: Export required

// Worker must export DO class too
import { MyDO } from './my-do';
export { MyDO };
Constructor Rules:
  • ✅ Call
    super(ctx, env)
    first
  • ✅ Keep minimal - heavy work blocks hibernation wake
  • ✅ Use
    ctx.blockConcurrencyWhile()
    for storage initialization
  • ❌ Never
    setTimeout
    /
    setInterval
    (use alarms)
  • ❌ Don't rely on in-memory state with WebSockets (persist to storage)

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

export class MyDO extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);  // 必须是第一行

    // 在处理请求前加载状态(可选)
    ctx.blockConcurrencyWhile(async () => {
      this.value = await ctx.storage.get('key') || defaultValue;
    });
  }

  // RPC方法(推荐)
  async myMethod(): Promise<string> { return 'Hello'; }

  // HTTP fetch处理器(可选)
  async fetch(request: Request): Promise<Response> { return new Response('OK'); }
}

export default MyDO;  // CRITICAL: 必须导出

// Worker也必须导出DO类
import { MyDO } from './my-do';
export { MyDO };
构造函数规则:
  • ✅ 首先调用
    super(ctx, env)
  • ✅ 保持轻量化 - 繁重工作会阻塞休眠唤醒
  • ✅ 使用
    ctx.blockConcurrencyWhile()
    进行存储初始化
  • ❌ 绝不要使用
    setTimeout
    /
    setInterval
    (使用告警替代)
  • ❌ 不要依赖WebSocket的内存状态(要持久化到存储中)

Storage API

存储API

Two backends available:
  • SQLite (recommended): 10GB storage, SQL queries, atomic operations, PITR
  • KV: 128MB storage, key-value only
Enable SQLite in migrations:
jsonc
{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }] }
提供两种后端:
  • SQLite(推荐):10GB存储,支持SQL查询、原子操作、时间点恢复(PITR)
  • KV:128MB存储,仅支持键值对
在迁移中启用SQLite:
jsonc
{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }] }

SQL API (SQLite backend)

SQL API(SQLite后端)

typescript
export class MyDO 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, text TEXT, created_at INTEGER);
      CREATE INDEX IF NOT EXISTS idx_created ON messages(created_at);
      PRAGMA optimize;  // Feb 2025: Query performance optimization
    `);
  }

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

  async getMessages(limit = 50): Promise<any[]> {
    return this.sql.exec('SELECT * FROM messages ORDER BY created_at DESC LIMIT ?', limit).toArray();
  }
}
SQL Methods:
  • sql.exec(query, ...params)
    → cursor
  • cursor.one<T>()
    → single row (throws if none)
  • cursor.one<T>({ allowNone: true })
    → row or null
  • cursor.toArray<T>()
    → all rows
  • ctx.storage.transactionSync(() => { ... })
    → atomic multi-statement
Best Practices:
  • ✅ Use
    ?
    placeholders for parameterized queries
  • ✅ Create indexes on frequently queried columns
  • ✅ Use
    PRAGMA optimize
    after schema changes
  • ✅ Add
    STRICT
    keyword to table definitions to enforce type affinity and catch type mismatches early
  • ✅ Convert booleans to integers (0/1) - booleans bind as strings "true"/"false" in SQLite backend
typescript
export class MyDO 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, text TEXT, created_at INTEGER);
      CREATE INDEX IF NOT EXISTS idx_created ON messages(created_at);
      PRAGMA optimize;  // 2025年2月新增:查询性能优化
    `);
  }

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

  async getMessages(limit = 50): Promise<any[]> {
    return this.sql.exec('SELECT * FROM messages ORDER BY created_at DESC LIMIT ?', limit).toArray();
  }
}
SQL方法:
  • sql.exec(query, ...params)
    → 游标
  • cursor.one<T>()
    → 单行数据(无数据时抛出错误)
  • cursor.one<T>({ allowNone: true })
    → 单行数据或null
  • cursor.toArray<T>()
    → 所有行数据
  • ctx.storage.transactionSync(() => { ... })
    → 原子化多语句操作
最佳实践:
  • ✅ 使用
    ?
    占位符进行参数化查询
  • ✅ 为频繁查询的列创建索引
  • ✅ 在模式变更后使用
    PRAGMA optimize
  • ✅ 在表定义中添加
    STRICT
    关键字以强制类型关联,提前捕获类型不匹配问题
  • ✅ 将布尔值转换为整数(0/1)- 在SQLite后端中布尔值会被绑定为字符串"true"/"false"

Key-Value API (both backends)

键值对API(两种后端均支持)

typescript
// Single operations
await this.ctx.storage.put('key', value);
const value = await this.ctx.storage.get<T>('key');
await this.ctx.storage.delete('key');

// Batch operations
await this.ctx.storage.put({ key1: val1, key2: val2 });
const map = await this.ctx.storage.get(['key1', 'key2']);
await this.ctx.storage.delete(['key1', 'key2']);

// List and delete all
const map = await this.ctx.storage.list({ prefix: 'user:', limit: 100 });
await this.ctx.storage.deleteAll();  // Atomic on SQLite only

// Transactions
await this.ctx.storage.transaction(async (txn) => {
  await txn.put('key1', val1);
  await txn.put('key2', val2);
});
Storage Limits: SQLite 10GB (April 2025 GA) | KV 128MB

typescript
// 单个操作
await this.ctx.storage.put('key', value);
const value = await this.ctx.storage.get<T>('key');
await this.ctx.storage.delete('key');

// 批量操作
await this.ctx.storage.put({ key1: val1, key2: val2 });
const map = await this.ctx.storage.get(['key1', 'key2']);
await this.ctx.storage.delete(['key1', 'key2']);

// 列出并删除所有数据
const map = await this.ctx.storage.list({ prefix: 'user:', limit: 100 });
await this.ctx.storage.deleteAll();  // 仅在SQLite上是原子操作

// 事务
await this.ctx.storage.transaction(async (txn) => {
  await txn.put('key1', val1);
  await txn.put('key2', val2);
});
存储限制: SQLite 10GB(2025年4月正式可用)| KV 128MB

WebSocket Hibernation API

WebSocket休眠API

Capabilities:
  • Thousands of WebSocket connections per instance
  • Hibernate when idle (~10s no activity) to save costs
  • Auto wake-up when messages arrive
  • Message size limit: 32 MiB (Oct 2025, up from 1 MiB)
How it works:
  1. Active → handles messages
  2. Idle → ~10s no activity
  3. Hibernation → in-memory state cleared, WebSockets stay connected
  4. Wake → message arrives → constructor runs → handler called
CRITICAL: In-memory state is lost on hibernation. Use
serializeAttachment()
to persist per-WebSocket metadata.
功能:
  • 每个实例支持数千个WebSocket连接
  • 空闲时(约10秒无活动)进入休眠以节省成本
  • 消息到达时自动唤醒
  • 消息大小限制: 32 MiB(2025年10月,从1 MiB提升)
工作原理:
  1. 活跃状态 → 处理消息
  2. 空闲状态 → 约10秒无活动
  3. 休眠状态 → 内存状态被清除,WebSocket保持连接
  4. 唤醒 → 消息到达 → 构造函数运行 → 处理器被调用
关键注意事项: 休眠时内存状态会丢失。使用
serializeAttachment()
来持久化每个WebSocket的元数据。

Hibernation-Safe Pattern

休眠安全模式

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

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sessions = new Map();

    // CRITICAL: Restore WebSocket metadata after hibernation
    ctx.getWebSockets().forEach((ws) => {
      this.sessions.set(ws, ws.deserializeAttachment());
    });
  }

  async fetch(request: Request): Promise<Response> {
    const pair = new WebSocketPair();
    const [client, server] = Object.values(pair);

    const url = new URL(request.url);
    const metadata = { userId: url.searchParams.get('userId'), username: url.searchParams.get('username') };

    // CRITICAL: Use ctx.acceptWebSocket(), NOT ws.accept()
    this.ctx.acceptWebSocket(server);
    server.serializeAttachment(metadata);  // Persist across hibernation
    this.sessions.set(server, metadata);

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

  async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
    const session = this.sessions.get(ws);
    // Handle message (max 32 MiB since Oct 2025)
  }

  async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean): Promise<void> {
    this.sessions.delete(ws);
    ws.close(code, 'Closing');
  }

  async webSocketError(ws: WebSocket, error: any): Promise<void> {
    this.sessions.delete(ws);
  }
}
Hibernation Rules:
  • ctx.acceptWebSocket(ws)
    - enables hibernation
  • ws.serializeAttachment(data)
    - persist metadata
  • ctx.getWebSockets().forEach()
    - restore in constructor
  • ✅ Use alarms instead of
    setTimeout
    /
    setInterval
  • ws.accept()
    - standard API, no hibernation
  • setTimeout
    /
    setInterval
    - prevents hibernation
  • ❌ In-progress
    fetch()
    - blocks hibernation

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

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sessions = new Map();

    // 关键:休眠后恢复WebSocket元数据
    ctx.getWebSockets().forEach((ws) => {
      this.sessions.set(ws, ws.deserializeAttachment());
    });
  }

  async fetch(request: Request): Promise<Response> {
    const pair = new WebSocketPair();
    const [client, server] = Object.values(pair);

    const url = new URL(request.url);
    const metadata = { userId: url.searchParams.get('userId'), username: url.searchParams.get('username') };

    // 关键:使用ctx.acceptWebSocket(),而非ws.accept()
    this.ctx.acceptWebSocket(server);
    server.serializeAttachment(metadata);  // 跨休眠持久化
    this.sessions.set(server, metadata);

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

  async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void> {
    const session = this.sessions.get(ws);
    // 处理消息(2025年10月起最大32 MiB)
  }

  async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean): Promise<void> {
    this.sessions.delete(ws);
    ws.close(code, 'Closing');
  }

  async webSocketError(ws: WebSocket, error: any): Promise<void> {
    this.sessions.delete(ws);
  }
}
休眠规则:
  • ctx.acceptWebSocket(ws)
    - 启用休眠
  • ws.serializeAttachment(data)
    - 持久化元数据
  • ctx.getWebSockets().forEach()
    - 在构造函数中恢复
  • ✅ 使用告警替代
    setTimeout
    /
    setInterval
  • ws.accept()
    - 标准API,不支持休眠
  • setTimeout
    /
    setInterval
    - 会阻止休眠
  • ❌ 进行中的
    fetch()
    - 会阻止休眠

Alarms API

告警API

Schedule DO to wake at future time. Use for: batching, cleanup, reminders, periodic tasks.
typescript
export class Batcher extends DurableObject {
  async addItem(item: string): Promise<void> {
    // Add to buffer
    const buffer = await this.ctx.storage.get<string[]>('buffer') || [];
    buffer.push(item);
    await this.ctx.storage.put('buffer', buffer);

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

  async alarm(info: { retryCount: number; isRetry: boolean }): Promise<void> {
    if (info.retryCount > 3) return;  // Give up after 3 retries

    const buffer = await this.ctx.storage.get<string[]>('buffer') || [];
    await this.processBatch(buffer);
    await this.ctx.storage.put('buffer', []);
    // Alarm auto-deleted after success
  }
}
API Methods:
  • await ctx.storage.setAlarm(Date.now() + 60000)
    - set alarm (overwrites existing)
  • await ctx.storage.getAlarm()
    - get timestamp or null
  • await ctx.storage.deleteAlarm()
    - cancel alarm
  • async alarm(info)
    - handler called when alarm fires
Behavior:
  • ✅ At-least-once execution, auto-retries (up to 6x, exponential backoff)
  • ✅ Survives hibernation/eviction
  • ✅ Auto-deleted after success
  • ⚠️ One alarm per DO (new alarm overwrites)

调度DO在未来某个时间唤醒。适用场景: 批量处理、清理、提醒、周期性任务。
typescript
export class Batcher extends DurableObject {
  async addItem(item: string): Promise<void> {
    // 添加到缓冲区
    const buffer = await this.ctx.storage.get<string[]>('buffer') || [];
    buffer.push(item);
    await this.ctx.storage.put('buffer', buffer);

    // 若未设置告警则进行调度
    if ((await this.ctx.storage.getAlarm()) === null) {
      await this.ctx.storage.setAlarm(Date.now() + 10000);  // 10秒后
    }
  }

  async alarm(info: { retryCount: number; isRetry: boolean }): Promise<void> {
    if (info.retryCount > 3) return;  // 重试3次后放弃

    const buffer = await this.ctx.storage.get<string[]>('buffer') || [];
    await this.processBatch(buffer);
    await this.ctx.storage.put('buffer', []);
    // 成功后告警会自动删除
  }
}
API方法:
  • await ctx.storage.setAlarm(Date.now() + 60000)
    - 设置告警(会覆盖现有告警)
  • await ctx.storage.getAlarm()
    - 获取时间戳或null
  • await ctx.storage.deleteAlarm()
    - 取消告警
  • async alarm(info)
    - 告警触发时调用的处理器
行为:
  • ✅ 至少执行一次,自动重试(最多6次,指数退避)
  • ✅ 可在休眠/驱逐后存活
  • ✅ 成功后自动删除
  • ⚠️ 每个DO只能有一个告警(新告警会覆盖旧的)

RPC vs HTTP Fetch

RPC vs HTTP Fetch

RPC (Recommended): Direct method calls, type-safe, simple
typescript
// DO class
export class Counter extends DurableObject {
  async increment(): Promise<number> {
    let value = (await this.ctx.storage.get<number>('count')) || 0;
    await this.ctx.storage.put('count', ++value);
    return value;
  }
}

// Worker calls
const stub = env.COUNTER.getByName('my-counter');
const count = await stub.increment();  // Type-safe!
HTTP Fetch: Request/response pattern, required for WebSocket upgrades
typescript
// DO class
export class Counter extends DurableObject {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === '/increment') {
      let value = (await this.ctx.storage.get<number>('count')) || 0;
      await this.ctx.storage.put('count', ++value);
      return new Response(JSON.stringify({ count: value }));
    }
    return new Response('Not found', { status: 404 });
  }
}

// Worker calls
const stub = env.COUNTER.getByName('my-counter');
const response = await stub.fetch('https://fake-host/increment', { method: 'POST' });
const data = await response.json();
When to use: RPC for new projects (simpler), HTTP Fetch for WebSocket upgrades or complex routing

RPC(推荐): 直接方法调用,类型安全,简单
typescript
// DO类
export class Counter extends DurableObject {
  async increment(): Promise<number> {
    let value = (await this.ctx.storage.get<number>('count')) || 0;
    await this.ctx.storage.put('count', ++value);
    return value;
  }
}

// Worker调用
const stub = env.COUNTER.getByName('my-counter');
const count = await stub.increment();  // 类型安全!
HTTP Fetch: 请求/响应模式,WebSocket升级时必须使用
typescript
// DO类
export class Counter extends DurableObject {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === '/increment') {
      let value = (await this.ctx.storage.get<number>('count')) || 0;
      await this.ctx.storage.put('count', ++value);
      return new Response(JSON.stringify({ count: value }));
    }
    return new Response('Not found', { status: 404 });
  }
}

// Worker调用
const stub = env.COUNTER.getByName('my-counter');
const response = await stub.fetch('https://fake-host/increment', { method: 'POST' });
const data = await response.json();
使用场景: 新项目推荐用RPC(更简单),WebSocket升级或复杂路由时用HTTP Fetch

Getting DO Stubs

获取DO存根

Three ways to get IDs:
  1. idFromName(name)
    - Consistent routing (same name = same DO)
typescript
const stub = env.CHAT_ROOM.getByName('room-123');  // Aug 2025: Shortcut for idFromName + get
// Use for: chat rooms, user sessions, per-tenant logic, singletons
  1. newUniqueId()
    - Random unique ID (must store for reuse)
typescript
const id = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });  // Optional: EU compliance
const idString = id.toString();  // Save to KV/D1 for later
  1. idFromString(idString)
    - Recreate from saved ID
typescript
const id = env.MY_DO.idFromString(await env.KV.get('session:123'));
const stub = env.MY_DO.get(id);
Location hints (best-effort):
typescript
const stub = env.MY_DO.get(id, { locationHint: 'enam' });  // wnam, enam, sam, weur, eeur, apac, oc, afr, me
Jurisdiction (strict enforcement):
typescript
const id = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });  // Options: 'eu', 'fedramp'
// Cannot combine with location hints, higher latency outside jurisdiction

三种获取ID的方式:
  1. idFromName(name)
    - 一致路由(相同名称对应同一个DO)
typescript
const stub = env.CHAT_ROOM.getByName('room-123');  // 2025年8月新增:idFromName + get的快捷方式
// 适用场景: 聊天室、用户会话、租户专属逻辑、单例
  1. newUniqueId()
    - 随机唯一ID(必须存储以便复用)
typescript
const id = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });  // 可选:符合欧盟合规
const idString = id.toString();  // 保存到KV/D1以便后续使用
  1. idFromString(idString)
    - 从保存的ID重新创建
typescript
const id = env.MY_DO.idFromString(await env.KV.get('session:123'));
const stub = env.MY_DO.get(id);
位置提示(尽力而为):
typescript
const stub = env.MY_DO.get(id, { locationHint: 'enam' });  // 可选值:wnam, enam, sam, weur, eeur, apac, oc, afr, me
管辖区域(严格强制):
typescript
const id = env.MY_DO.newUniqueId({ jurisdiction: 'eu' });  // 可选值: 'eu', 'fedramp'
// 不能与位置提示结合使用,管辖区域外的延迟会更高

Migrations

迁移

Required for: create, rename, delete, transfer DO classes
1. Create:
jsonc
{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Counter"] }] }  // SQLite 10GB
// Or: "new_classes": ["Counter"]  // KV 128MB (legacy)
2. Rename:
jsonc
{ "migrations": [
  { "tag": "v1", "new_sqlite_classes": ["OldName"] },
  { "tag": "v2", "renamed_classes": [{ "from": "OldName", "to": "NewName" }] }
]}
3. Delete:
jsonc
{ "migrations": [
  { "tag": "v1", "new_sqlite_classes": ["Counter"] },
  { "tag": "v2", "deleted_classes": ["Counter"] }  // Immediate deletion, cannot undo
]}
4. Transfer:
jsonc
{ "migrations": [{ "tag": "v1", "transferred_classes": [
  { "from": "OldClass", "from_script": "old-worker", "to": "NewClass" }
]}]}
Migration Rules:
  • ❌ Atomic (all instances migrate at once, no gradual rollout)
  • ❌ Tags are unique and append-only
  • ❌ Cannot enable SQLite on existing KV-backed DOs
  • ✅ Code changes don't need migrations (only schema changes)
  • ✅ Class names globally unique per account

适用场景: 创建、重命名、删除、转移DO类
1. 创建:
jsonc
{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Counter"] }] }  // SQLite 10GB
// 或者: "new_classes": ["Counter"]  // KV 128MB(旧版)
2. 重命名:
jsonc
{ "migrations": [
  { "tag": "v1", "new_sqlite_classes": ["OldName"] },
  { "tag": "v2", "renamed_classes": [{ "from": "OldName", "to": "NewName" }] }
]}
3. 删除:
jsonc
{ "migrations": [
  { "tag": "v1", "new_sqlite_classes": ["Counter"] },
  { "tag": "v2", "deleted_classes": ["Counter"] }  // 立即删除,无法撤销
]}
4. 转移:
jsonc
{ "migrations": [{ "tag": "v1", "transferred_classes": [
  { "from": "OldClass", "from_script": "old-worker", "to": "NewClass" }
]}]}
迁移规则:
  • ❌ 原子操作(所有实例同时迁移,不支持逐步发布)
  • ❌ 标签唯一且只能追加
  • ❌ 无法为现有KV后端的DO启用SQLite
  • ✅ 代码变更不需要迁移(仅模式变更需要)
  • ✅ 类名称在账户内全局唯一

Common Patterns

常见模式

Rate Limiting:
typescript
async checkLimit(userId: string, limit: number, window: number): Promise<boolean> {
  const requests = (await this.ctx.storage.get<number[]>(`rate:${userId}`)) || [];
  const valid = requests.filter(t => Date.now() - t < window);
  if (valid.length >= limit) return false;
  valid.push(Date.now());
  await this.ctx.storage.put(`rate:${userId}`, valid);
  return true;
}
Session Management with TTL:
typescript
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 alarm(): Promise<void> {
  this.sql.exec('DELETE FROM session WHERE expires_at < ?', Date.now());
  await this.ctx.storage.setAlarm(Date.now() + 3600000);  // Hourly cleanup
}
Leader Election:
typescript
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 { return false; }  // Already has leader
}
Multi-DO Coordination:
typescript
// Coordinator delegates to child DOs
const gameRoom = env.GAME_ROOM.getByName(gameId);
await gameRoom.initialize();
await this.ctx.storage.put(`game:${gameId}`, { created: Date.now() });

速率限制:
typescript
async checkLimit(userId: string, limit: number, window: number): Promise<boolean> {
  const requests = (await this.ctx.storage.get<number[]>(`rate:${userId}`)) || [];
  const valid = requests.filter(t => Date.now() - t < window);
  if (valid.length >= limit) return false;
  valid.push(Date.now());
  await this.ctx.storage.put(`rate:${userId}`, valid);
  return true;
}
带TTL的会话管理:
typescript
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 alarm(): Promise<void> {
  this.sql.exec('DELETE FROM session WHERE expires_at < ?', Date.now());
  await this.ctx.storage.setAlarm(Date.now() + 3600000);  // 每小时清理一次
}
领导者选举:
typescript
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 { return false; }  // 已存在领导者
}
多DO协调:
typescript
// 协调者委托给子DO
const gameRoom = env.GAME_ROOM.getByName(gameId);
await gameRoom.initialize();
await this.ctx.storage.put(`game:${gameId}`, { created: Date.now() });

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);
使用告警替代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 20 documented issues:
本技能可预防20种已记录的问题:

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
会阻止休眠 预防: 使用告警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: Cloudflare Docs | GitHub Issue #4864 Why It Happens: Durable Objects maintaining persistent connections to external WebSocket services using
new WebSocket('url')
cannot hibernate and remain pinned in memory indefinitely Use Cases Affected:
  • Real-time database subscriptions (Supabase, Firebase)
  • Message brokers (Redis Streams, Apache Kafka)
  • WebSocket connections to external real-time services
  • Inter-service communication Prevention: Only use hibernation for server-side (incoming) WebSockets via
    ctx.acceptWebSocket()
    . Outgoing WebSocket connections created with
    new WebSocket(url)
    prevent hibernation. Redesign architecture to avoid outgoing WebSocket connections from Durable Objects if hibernation is required.
错误: 尽管使用了休眠API,但费用仍然很高 来源: Cloudflare文档 | GitHub Issue #4864 原因: Durable Objects使用
new WebSocket('url')
维护到外部WebSocket服务的持久连接时,无法进入休眠,会一直驻留在内存中 受影响的场景:
  • 实时数据库订阅(Supabase、Firebase)
  • 消息代理(Redis Streams、Apache Kafka)
  • 与外部实时服务的WebSocket连接
  • 服务间通信 预防: 仅对通过
    ctx.acceptWebSocket()
    处理的服务器端(入站)WebSocket使用休眠。使用
    new WebSocket(url)
    创建的出站WebSocket连接会阻止休眠。如果需要休眠,请重新设计架构以避免从Durable Objects发起出站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: deleteAll Issues

问题#9:deleteAll问题

Error: Storage not fully deleted, billing continues; or internal error in alarm handler Source: KV Storage API | GitHub Issue #2993 Why It Happens:
  • KV backend
    deleteAll()
    can fail partially (not atomic)
  • SQLite: calling
    deleteAll()
    in alarm handler causes internal error and retry loop (fixed in runtime) Prevention:
  • Use SQLite backend for atomic deleteAll
  • In alarm handlers, call
    deleteAlarm()
    BEFORE
    deleteAll()
    :
typescript
async alarm(info: { retryCount: number }): Promise<void> {
  await this.ctx.storage.deleteAlarm();  // ← Call first
  await this.ctx.storage.deleteAll();    // Then delete all
}
错误: 存储未完全删除,计费仍在继续;或告警处理器中出现内部错误 来源: KV存储API | GitHub Issue #2993 原因:
  • KV后端的
    deleteAll()
    可能部分失败(非原子操作)
  • SQLite:在告警处理器中调用
    deleteAll()
    会导致内部错误和重试循环(已在运行时修复) 预防:
  • 使用SQLite后端进行原子化deleteAll
  • 在告警处理器中,先调用
    deleteAlarm()
    再调用
    deleteAll()
    :
typescript
async alarm(info: { retryCount: number }): Promise<void> {
  await this.ctx.storage.deleteAlarm();  // ← 先调用
  await this.ctx.storage.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)的限制 预防: 监控存储大小,使用告警实现清理

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在空闲期前完成

Issue #16: Boolean Values Bind as Strings in SQLite

问题#16:SQLite中布尔值绑定为字符串

Error: Boolean columns contain strings
"true"
/
"false"
instead of integers 0/1; SQL queries with boolean comparisons fail Source: GitHub Issue #9964 Why It Happens: JavaScript boolean values are serialized as strings in Durable Objects SQLite (inconsistent with D1 behavior) Prevention: Manually convert booleans to integers and use STRICT tables
typescript
// Convert booleans to integers
this.sql.exec('INSERT INTO test (bool_col) VALUES (?)', value ? 1 : 0);

// Use STRICT tables to catch type mismatches early
this.sql.exec(`
  CREATE TABLE IF NOT EXISTS test (
    id INTEGER PRIMARY KEY,
    bool_col INTEGER NOT NULL
  ) STRICT;
`);
错误: 布尔列中包含字符串
"true"
/
"false"
而非整数0/1;使用布尔比较的SQL查询失败 来源: GitHub Issue #9964 原因: JavaScript布尔值在Durable Objects SQLite中被序列化为字符串(与D1行为不一致) 预防: 手动将布尔值转换为整数并使用STRICT表
typescript
// 将布尔值转换为整数
this.sql.exec('INSERT INTO test (bool_col) VALUES (?)', value ? 1 : 0);

// 使用STRICT表提前捕获类型不匹配
this.sql.exec(`
  CREATE TABLE IF NOT EXISTS test (
    id INTEGER PRIMARY KEY,
    bool_col INTEGER NOT NULL
  ) STRICT;
`);

Issue #17: RPC ReadableStream Cancel Logs False Network Errors

问题#17:RPC ReadableStream取消记录虚假网络错误

Error: Wrangler dev logs show "Network connection lost" when canceling ReadableStream from RPC, despite correct cancellation Source: GitHub Issue #11071 Why It Happens: Canceling ReadableStream returned from Durable Object via RPC triggers misleading error logs in Wrangler dev (presentation issue, not runtime bug) Prevention: No workaround available. The cancellation works correctly - ignore the false error logs in Wrangler dev. Issue does not appear in production or workerd-only setup.
错误: Wrangler开发日志中显示“Network connection lost”,但取消操作实际是正确的 来源: GitHub Issue #11071 原因: 取消从RPC返回的Durable Object的ReadableStream时,Wrangler开发环境会触发误导性的错误日志(显示问题,非运行时bug) 预防: 没有解决方法。取消操作是正确的 - 忽略Wrangler开发环境中的虚假错误日志。该问题不会出现在生产环境或仅使用workerd的设置中。

Issue #18: blockConcurrencyWhile Does Not Block in Local Dev (Fixed)

问题#18:blockConcurrencyWhile在本地开发中不阻塞(已修复)

Error: Constructor's
blockConcurrencyWhile
doesn't block requests in local dev, causing race conditions hidden during development Source: GitHub Issue #8686 Why It Happens: Bug in older @cloudflare/vite-plugin and wrangler versions Prevention: Upgrade to @cloudflare/vite-plugin v1.3.1+ and wrangler v4.18.0+ where this is fixed
错误: 构造函数中的
blockConcurrencyWhile
在本地开发中不阻塞请求,导致开发期间隐藏的竞态条件 来源: GitHub Issue #8686 原因: 旧版本的@cloudflare/vite-plugin和wrangler中的bug 预防: 升级到@cloudflare/vite-plugin v1.3.1+和wrangler v4.18.0+,该问题已修复

Issue #19: RPC Between Multiple wrangler dev Sessions Not Supported

问题#19:多个wrangler dev会话间不支持RPC

Error:
"Cannot access MyDurableObject#myMethod as Durable Object RPC is not yet supported between multiple wrangler dev sessions"
Source: GitHub Issue #11944 Why It Happens: Accessing a Durable Object over RPC from multiple
wrangler dev
instances (e.g., separate Workers in monorepo) is not yet supported in local dev Prevention: Use
wrangler dev -c config1 -c config2
to run multiple workers in single session, or use HTTP fetch instead of RPC for cross-worker DO communication during local development
错误:
"Cannot access MyDurableObject#myMethod as Durable Object RPC is not yet supported between multiple wrangler dev sessions"
来源: GitHub Issue #11944 原因: 在本地开发中,从多个
wrangler dev
实例(例如,单体仓库中的独立Worker)通过RPC访问Durable Object目前不被支持 预防: 使用
wrangler dev -c config1 -c config2
在单个会话中运行多个Worker,或在本地开发期间使用HTTP fetch替代RPC进行跨Worker DO通信

Issue #20: state.id.name Undefined in Constructor (vitest Regression)

问题#20:构造函数中state.id.name未定义(vitest回归)

Error:
DurableObjectState.id.name
is undefined in constructor when using @cloudflare/vitest-pool-workers 0.8.71 Source: GitHub Issue #11580 Why It Happens: Regression in vitest-pool-workers 0.8.71 (worked in 0.8.38) Prevention: Downgrade to @cloudflare/vitest-pool-workers@0.8.38 or upgrade to later version where this is fixed

错误: 使用@cloudflare/vitest-pool-workers 0.8.71时,
DurableObjectState.id.name
在构造函数中未定义 来源: GitHub Issue #11580 原因: vitest-pool-workers 0.8.71中的回归问题(在0.8.38版本中正常) 预防: 降级到@cloudflare/vitest-pool-workers@0.8.38或升级到修复该问题的更高版本

Configuration & Types

配置与类型

wrangler.jsonc:
jsonc
{
  "compatibility_date": "2025-11-23",
  "durable_objects": {
    "bindings": [{ "name": "COUNTER", "class_name": "Counter" }]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["Counter"] },
    { "tag": "v2", "renamed_classes": [{ "from": "Counter", "to": "CounterV2" }] }
  ]
}
TypeScript:
typescript
import { DurableObject, DurableObjectState, DurableObjectNamespace } from 'cloudflare:workers';

interface Env { MY_DO: DurableObjectNamespace<MyDurableObject>; }

export class MyDurableObject extends DurableObject<Env> {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;
  }
}

wrangler.jsonc:
jsonc
{
  "compatibility_date": "2025-11-23",
  "durable_objects": {
    "bindings": [{ "name": "COUNTER", "class_name": "Counter" }]
  },
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["Counter"] },
    { "tag": "v2", "renamed_classes": [{ "from": "Counter", "to": "CounterV2" }] }
  ]
}
TypeScript:
typescript
import { DurableObject, DurableObjectState, DurableObjectNamespace } from 'cloudflare:workers';

interface Env { MY_DO: DurableObjectNamespace<MyDurableObject>; }

export class MyDurableObject extends DurableObject<Env> {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.sql = ctx.storage.sql;
  }
}

Official Documentation

官方文档


Questions? Issues?
  1. Check
    references/top-errors.md
    for common problems
  2. Review
    templates/
    for working examples
  3. Consult official docs: https://developers.cloudflare.com/durable-objects/
  4. Verify migrations configuration carefully

Last verified: 2026-01-21 | Skill version: 3.1.0 | Changes: Added 5 new issues (boolean binding, RPC stream cancel, blockConcurrencyWhile local dev, RPC multi-session, vitest regression), expanded Issue #7 (outgoing WebSocket use cases) and Issue #9 (deleteAll alarm interaction), added STRICT tables best practice, updated @cloudflare/actors beta warning

有疑问?遇到问题?
  1. 查看
    references/top-errors.md
    了解常见问题
  2. 查看
    templates/
    获取可用示例
  3. 查阅官方文档: https://developers.cloudflare.com/durable-objects/
  4. 仔细验证迁移配置

最后验证: 2026-01-21 | 技能版本: 3.1.0 | 变更: 新增5个问题(布尔值绑定、RPC流取消、本地开发blockConcurrencyWhile、多会话RPC、vitest回归),扩展了问题#7(出站WebSocket场景)和问题#9(deleteAll与告警交互),新增STRICT表最佳实践,更新了@cloudflare/actors测试版警告