关于 cloudflare-durable-objects
面向 Cloudflare Workers 开发者的 Durable Objects 实践指南,涵盖持久化状态、并发、WebSocket 休眠、定时任务和迁移。提供 API 用法、架构模式及常见错误预防建议。
主要功能
- 01SQLite 与键值存储
- 02WebSocket 休眠与状态恢复
- 03Alarm 定时任务
- 04RPC 与 HTTP Fetch 模式
- 05对象 ID 与迁移管理
适用场景
- 01为聊天室保存连接元数据
- 02构建会话管理和限流逻辑
- 03使用 Alarm 批处理或清理数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill cloudflare-durable-objects安装后在 Claude Code 中输入/cloudflare-durable-objects确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsonreferences/alarms-api.mdbest-practices.mdmigrations-guide.mdrpc-patterns.mdstate-api-reference.mdtop-errors.mdwebsocket-hibernation.mdwrangler-commands.mdrules/cloudflare-durable-objects.mdscripts/check-versions.shtemplates/alarms-api-do.tsbasic-do.tslocation-hints.tsmulti-do-coordination.tspackage.jsonrpc-vs-fetch.tsstate-api-patterns.tswebsocket-hibernation-do.ts详细介绍
Cloudflare Durable Objects
状态:已可用于生产 ✅ 最后更新:2026-01-21 依赖项:cloudflare-worker-base(推荐) 最新版本:[email protected]、@cloudflare/[email protected] 官方文档:https://developers.cloudflare.com/durable-objects/
近期更新(2025):
- 2025 年 10 月:WebSocket 消息大小从 1 MiB 提升至 32 MiB;为 SQLite DO 提供 Data Studio UI(可在仪表板中查看/编辑存储)
- 2025 年 8 月:为命名 DO 提供
getByName()API 快捷方式 - 2025 年 6 月:推出 @cloudflare/actors 库(测试版)——推荐的 SDK,支持迁移、闹钟和 Actor 类模式。注意:稳定性仍处于测试阶段——投入生产前请查看活跃问题(RPC 序列化、vitest 集成、内存管理)
- 2025 年 5 月:Durable Objects 支持 Python Workers
- 2025 年 4 月:SQLite 正式发布,存储容量为 10GB(从测试版升级为正式版,容量从 1GB 提升至 10GB),免费套餐可用
- 2025 年 2 月:支持 PRAGMA optimize;通过参考 ID 改进错误诊断
快速入门
搭建新的 DO 项目:
npm create cloudflare@latest my-durable-app -- --template=cloudflare/durable-objects-template --ts
或添加到现有 Worker:
// 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
// wrangler.jsonc - Configuration
{
"durable_objects": {
"bindings": [{ "name": "COUNTER", "class_name": "Counter" }]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["Counter"] } // SQLite backend (10GB limit)
]
}
// 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 类基础
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 };
构造函数规则:
- ✅ 首先调用
super(ctx, env) - ✅ 保持精简——繁重工作会阻塞休眠唤醒
- ✅ 使用
ctx.blockConcurrencyWhile()初始化存储 - ❌ 不要使用
setTimeout/setInterval(请改用闹钟) - ❌ 使用 WebSocket 时不要依赖内存状态(应持久化到存储中)
存储 API
提供两种后端:
- SQLite(推荐):10GB 存储、SQL 查询、原子操作、PITR
- KV:128MB 存储,仅支持键值存储
在迁移中启用 SQLite:
{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }] }
SQL API(SQLite 后端)
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 方法:
sql.exec(query, ...params)→ 游标cursor.one<T>()→ 单行(若无结果则抛出异常)cursor.one<T>({ allowNone: true })→ 单行或 nullcursor.toArray<T>()→ 所有行ctx.storage.transactionSync(() => { ... })→ 多语句原子操作
最佳实践:
- ✅ 使用
?占位符执行参数化查询 - ✅ 为频繁查询的列创建索引
- ✅ 更改架构后使用
PRAGMA optimize - ✅ 在表定义中添加
STRICT关键字,以强制执行类型亲和性并及早发现类型不匹配 - ✅ 将布尔值转换为整数(0/1)——在 SQLite 后端中,布尔值会以字符串 "true"/"false" 的形式绑定
键值 API(两种后端均支持)
// 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);
});
**存储限制:**SQLite 为 10GB(2025 年 4 月正式发布)| KV 为 128MB
WebSocket 休眠 API
功能:
- 每个实例支持数千个 WebSocket 连接
- 空闲时(约 10 秒无活动)进入休眠以节省成本
- 收到消息时自动唤醒
- 消息大小限制:32 MiB(2025 年 10 月从 1 MiB 提升)
工作原理:
- 活跃 → 处理消息
- 空闲 → 约 10 秒无活动
- 休眠 → 清除内存状态,WebSocket 保持连接
- 唤醒 → 收到消息 → 执行构造函数 → 调用处理程序
重要:休眠时会丢失内存状态。使用 serializeAttachment() 持久化每个 WebSocket 的元数据。
休眠安全模式
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);
}
}
休眠规则:
- ✅
ctx.acceptWebSocket(ws)——启用休眠 - ✅
ws.serializeAttachment(data)——持久化元数据 - ✅
ctx.getWebSockets().forEach()——在构造函数中恢复 - ✅ 使用闹钟代替
setTimeout/setInterval - ❌
ws.accept()——标准 API,不支持休眠 - ❌
setTimeout/setInterval——会阻止休眠 - ❌ 正在进行的
fetch()——会阻止休眠
Alarms API
安排 DO 在未来某个时间唤醒。**适用于:**批处理、清理、提醒、定期任务。
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 方法:
await ctx.storage.setAlarm(Date.now() + 60000)- 设置闹钟(覆盖现有闹钟)await ctx.storage.getAlarm()- 获取时间戳或 nullawait ctx.storage.deleteAlarm()- 取消闹钟async alarm(info)- 闹钟触发时调用的处理程序
行为:
- ✅ 至少执行一次,自动重试(最多 6 次,采用指数退避)
- ✅ 休眠或被驱逐后仍会保留
- ✅ 成功后自动删除
- ⚠️ 每个 DO 只能有一个闹钟(新闹钟会覆盖现有闹钟)
RPC 与 HTTP Fetch
**RPC(推荐):**直接调用方法、类型安全、简单
// 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:**请求/响应模式,WebSocket 升级时必须使用
// 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();
**使用场景:**新项目使用 RPC(更简单);WebSocket 升级或复杂路由使用 HTTP Fetch
获取 DO 存根
获取 ID 的三种方式:
idFromName(name)- 一致路由(名称相同 = DO 相同)
const stub = env.CHAT_ROOM.getByName('room-123'); // Aug 2025: Shortcut for idFromName + get
// Use for: chat rooms, user sessions, per-tenant logic, singletons
newUniqueId()- 随机唯一 ID(必须存储以供重用)
const id = env.MY_DO.newUniqueId({ jurisdiction: 'eu' }); // Optional: EU compliance
const idString = id.toString(); // Save to KV/D1 for later
idFromString(idString)- 从已保存的 ID 重新创建
const id = env.MY_DO.idFromString(await env.KV.get('session:123'));
const stub = env.MY_DO.get(id);
位置提示(尽力而为):
const stub = env.MY_DO.get(id, { locationHint: 'enam' }); // wnam, enam, sam, weur, eeur, apac, oc, afr, me
管辖区域(严格执行):
const id = env.MY_DO.newUniqueId({ jurisdiction: 'eu' }); // Options: 'eu', 'fedramp'
// Cannot combine with location hints, higher latency outside jurisdiction
迁移
**适用于:**创建、重命名、删除、转移 DO 类
1. 创建:
{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Counter"] }] } // SQLite 10GB
// Or: "new_classes": ["Counter"] // KV 128MB (legacy)
2. 重命名:
{ "migrations": [
{ "tag": "v1", "new_sqlite_classes": ["OldName"] },
{ "tag": "v2", "renamed_classes": [{ "from": "OldName", "to": "NewName" }] }
]}
3. 删除:
{ "migrations": [
{ "tag": "v1", "new_sqlite_classes": ["Counter"] },
{ "tag": "v2", "deleted_classes": ["Counter"] } // Immediate deletion, cannot undo
]}
4. 转移:
{ "migrations": [{ "tag": "v1", "transferred_classes": [
{ "from": "OldClass", "from_script": "old-worker", "to": "NewClass" }
]}]}
迁移规则:
- ❌ 原子执行(所有实例同时迁移,不支持逐步发布)
- ❌ 标签必须唯一,且只能追加
- ❌ 不能为现有的 KV 后端 DO 启用 SQLite
- ✅ 代码更改无需迁移(只有架构更改需要)
- ✅ 类名在每个账户内全局唯一
常见模式
速率限制:
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 的会话管理:
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
}
领导者选举:
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
}
多 DO 协调:
// 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() });
关键规则
始终执行
✅ 从 Worker 导出 DO 类
export class MyDO extends DurableObject { }
export default MyDO; // Required
✅ 在构造函数中调用 super(ctx, env)
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env); // Required first line
}
✅ 新 DO 使用 new_sqlite_classes
{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }
✅ 使用 ctx.acceptWebSocket() 实现休眠
this.ctx.acceptWebSocket(server); // Enables hibernation
✅ 将关键状态持久化到存储中(不要只保存在内存中)
await this.ctx.storage.put('important', value);
✅ 使用闹钟代替 setTimeout/setInterval
await this.ctx.storage.setAlarm(Date.now() + 60000);
✅ 使用参数化 SQL 查询
this.sql.exec('SELECT * FROM table WHERE id = ?', id);
✅ 尽量减少构造函数中的工作
constructor(ctx, env) {
super(ctx, env);
// Minimal initialization only
ctx.blockConcurrencyWhile(async () => {
// Load from storage
});
}
绝不执行
❌ 不添加迁移就创建 DO
// Missing migrations array = error
❌ 忘记导出 DO 类
class MyDO extends DurableObject { }
// Missing: export default MyDO;
❌ 使用 setTimeout 或 setInterval
setTimeout(() => {}, 1000); // Prevents hibernation
❌ 使用 WebSocket 时只依赖内存状态
// ❌ WRONG: this.sessions will be lost on hibernation
// ✅ CORRECT: Use serializeAttachment()
❌ 逐步部署迁移
# Migrations are atomic - cannot use gradual rollout
❌ 为现有的 KV 后端 DO 启用 SQLite
// Not supported - must create new DO class instead
❌ 期望标准 WebSocket API 支持休眠
ws.accept(); // ❌ No hibernation
this.ctx.acceptWebSocket(ws); // ✅ Hibernation enabled
❌ 认为位置提示有保证
// Location hints are best-effort only
防止已知问题
此技能可防止 20 个已记录的问题:
问题 #1:未导出类
错误:"binding not found" 或 "Class X not found"
来源:https://developers.cloudflare.com/durable-objects/get-started/
发生原因:未从 Worker 导出 DO 类
预防措施:
export class MyDO extends DurableObject { }
export default MyDO; // ← Required
问题 #2:缺少迁移
错误:"migrations required" 或 "no migration found for class"
来源:https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/
发生原因:创建 DO 类时未添加迁移条目
预防措施:创建新的 DO 类时始终添加迁移
{
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }
]
}
问题 #3:迁移类型错误(KV 与 SQLite)
错误:架构错误、存储 API 不匹配
来源:https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/
发生原因:使用了 new_classes 而不是 new_sqlite_classes
预防措施:SQLite 后端使用 new_sqlite_classes(推荐)
问题 #4:构造函数开销阻碍休眠唤醒
错误:休眠唤醒时间过长
来源:https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/
原因:构造函数中执行了繁重操作
预防:尽量精简构造函数,使用 blockConcurrencyWhile()
constructor(ctx, env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
// Load from storage
});
}
问题 #5:setTimeout 会破坏休眠
错误:DO 始终无法休眠,持续产生高额时长费用
来源:https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/
原因:setTimeout/setInterval 会阻止休眠
预防:改用 alarms API
// ❌ WRONG
setTimeout(() => {}, 1000);
// ✅ CORRECT
await this.ctx.storage.setAlarm(Date.now() + 1000);
问题 #6:休眠时内存状态丢失
错误:WebSocket 元数据丢失,状态意外重置
来源:https://developers.cloudflare.com/durable-objects/best-practices/websockets/
原因:依赖了休眠时会被清除的内存状态
预防:使用 serializeAttachment() 保存 WebSocket 元数据
ws.serializeAttachment({ userId, username });
// Restore in constructor
ctx.getWebSockets().forEach(ws => {
const metadata = ws.deserializeAttachment();
this.sessions.set(ws, metadata);
});
问题 #7:传出的 WebSocket 无法休眠
错误:即使使用休眠 API,费用仍然很高
来源:Cloudflare 文档 | GitHub 问题 #4864
原因:Durable Objects 使用 new WebSocket('url') 与外部 WebSocket 服务保持持久连接时,无法休眠,会无限期地驻留在内存中
受影响的使用场景:
- 实时数据库订阅(Supabase、Firebase)
- 消息代理(Redis Streams、Apache Kafka)
- 与外部实时服务建立 WebSocket 连接
- 服务间通信
预防:仅对服务端(传入)的 WebSocket 使用
ctx.acceptWebSocket()进行休眠。使用new WebSocket(url)创建的传出 WebSocket 连接会阻止休眠。如果需要休眠,请重新设计架构,避免 Durable Objects 建立传出 WebSocket 连接。
问题 #8:全局唯一性认知混淆
错误:DO 类名冲突出乎意料 来源:https://developers.cloudflare.com/durable-objects/platform/known-issues/#global-uniqueness 原因:每个账户中的 DO 类名全局唯一 预防:了解同一账户中的所有 Worker 共享 DO 类名
问题 #9:deleteAll 问题
错误:存储未完全删除,账单仍在产生;或闹钟处理程序中发生内部错误 来源:KV 存储 API | GitHub 问题 #2993 原因:
- KV 后端的
deleteAll()可能部分失败(非原子操作) - SQLite:在闹钟处理程序中调用
deleteAll()会导致内部错误和重试循环(已在运行时中修复) 预防: - 使用 SQLite 后端以实现原子性 deleteAll
- 在闹钟处理程序中,先调用
deleteAlarm(),再调用deleteAll():
async alarm(info: { retryCount: number }): Promise<void> {
await this.ctx.storage.deleteAlarm(); // ← Call first
await this.ctx.storage.deleteAll(); // Then delete all
}
问题 #10:绑定名称不匹配
错误:访问 DO 绑定时发生运行时错误 来源:https://developers.cloudflare.com/durable-objects/get-started/ 原因:wrangler.jsonc 中的绑定名称与代码不匹配 预防:确保名称一致
{ "bindings": [{ "name": "MY_DO", "class_name": "MyDO" }] }
env.MY_DO.getByName('instance'); // Must match binding name
问题 #11:超出状态大小限制
错误:出现 "state limit exceeded" 或存储错误
来源:https://developers.cloudflare.com/durable-objects/platform/pricing/
原因:超出 1GB(SQLite)或 128MB(KV)的限制
预防:监控存储大小,并使用闹钟实现清理
问题 #12:迁移不是原子操作
错误:渐进式部署受阻 来源:https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/ 原因:尝试在迁移期间使用渐进式发布 预防:迁移会在所有实例中原子式部署
问题 #13:忽略位置提示
错误:DO 创建在错误的区域 来源:https://developers.cloudflare.com/durable-objects/reference/data-location/ 发生原因:位置提示仅尽力而为,并非保证 预防措施:有严格要求时使用 jurisdiction
问题 #14:闹钟重试失败
错误:闹钟失败后任务丢失 来源:https://developers.cloudflare.com/durable-objects/api/alarms/ 发生原因:闹钟处理程序反复抛出错误 预防措施:实现幂等的闹钟处理程序
async alarm(info: { retryCount: number }): Promise<void> {
if (info.retryCount > 3) {
console.error('Giving up after 3 retries');
return;
}
// Idempotent operation
}
问题 #15:Fetch 阻止休眠
错误:即使使用休眠 API,DO 也从不休眠
来源:https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/
发生原因:正在进行的 fetch() 请求会阻止休眠
预防措施:确保所有异步 I/O 都在空闲期开始前完成
问题 #16:SQLite 中的布尔值绑定为字符串
错误:布尔列包含字符串 "true"/"false",而不是整数 0/1;使用布尔值进行比较的 SQL 查询会失败
来源:GitHub Issue #9964
发生原因:JavaScript 布尔值在 Durable Objects SQLite 中会被序列化为字符串(与 D1 的行为不一致)
预防措施:手动将布尔值转换为整数,并使用 STRICT 表
// 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;
`);
问题 #17:RPC ReadableStream 取消操作会记录虚假的网络错误
错误:取消 RPC 返回的 ReadableStream 时,Wrangler dev 日志会显示“Network connection lost”,尽管取消操作正确完成 来源:GitHub Issue #11071 发生原因:通过 RPC 取消 Durable Object 返回的 ReadableStream,会在 Wrangler dev 中触发误导性错误日志(这是显示问题,并非运行时错误) 预防措施:目前没有可用的变通方法。取消操作会正确完成,可忽略 Wrangler dev 中的虚假错误日志。此问题不会出现在生产环境或仅使用 workerd 的配置中。
问题 #18:blockConcurrencyWhile 在本地开发中不会阻塞(已修复)
错误:构造函数中的 blockConcurrencyWhile 在本地开发时不会阻塞请求,导致开发期间被隐藏的竞态条件
来源:GitHub Issue #8686
发生原因:较旧版本的 @cloudflare/vite-plugin 和 wrangler 中存在 Bug
预防措施:升级到 @cloudflare/vite-plugin v1.3.1+ 和 wrangler v4.18.0+,这些版本已修复此问题
问题 #19:不支持在多个 wrangler dev 会话之间使用 RPC
错误:"Cannot access MyDurableObject#myMethod as Durable Object RPC is not yet supported between multiple wrangler dev sessions"
来源:GitHub Issue #11944
发生原因:在本地开发中,尚不支持从多个 wrangler dev 实例(例如,monorepo 中的独立 Workers)通过 RPC 访问 Durable Object
预防措施:使用 wrangler dev -c config1 -c config2 在单个会话中运行多个 Worker,或者在本地开发期间使用 HTTP fetch 而非 RPC 进行跨 Worker 的 DO 通信
问题 #20:构造函数中的 state.id.name 未定义(vitest 回归问题)
错误:使用 @cloudflare/vitest-pool-workers 0.8.71 时,构造函数中的 DurableObjectState.id.name 未定义
来源:GitHub Issue #11580
发生原因:vitest-pool-workers 0.8.71 中的回归问题(在 0.8.38 中正常工作)
预防措施:降级到 @cloudflare/[email protected],或升级到已修复此问题的更高版本
配置与类型
wrangler.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:
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;
}
}
官方文档
- Durable Objects:https://developers.cloudflare.com/durable-objects/
- 状态 API(SQL):https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/
- WebSocket 休眠:https://developers.cloudflare.com/durable-objects/best-practices/websockets/
- Alarms API:https://developers.cloudflare.com/durable-objects/api/alarms/
- 迁移:https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/
- 最佳实践:https://developers.cloudflare.com/durable-objects/best-practices/
- 定价:https://developers.cloudflare.com/durable-objects/platform/pricing/
有问题?遇到故障?
- 查看
references/top-errors.md,了解常见问题 - 查看
templates/中的可用示例 - 参阅官方文档:https://developers.cloudflare.com/durable-objects/
- 仔细核对迁移配置
最后验证时间:2026-01-21 | 技能版本:3.1.0 | 变更:新增 5 个问题(布尔绑定、RPC 流取消、blockConcurrencyWhile 本地开发、RPC 多会话、vitest 回归),扩展问题 #7(传出 WebSocket 的用例)和问题 #9(deleteAll 与闹钟的交互),新增 STRICT 表最佳实践,更新 @cloudflare/actors beta 警告