chat-state-cloudflare-do
v0.2.0
Published
Cloudflare Durable Objects state adapter for Chat SDK
Maintainers
Readme
chat-state-cloudflare-do
Cloudflare Durable Objects state adapter for Chat SDK. Uses a SQLite-backed Durable Object for persistent subscriptions, distributed locking, and caching — with zero external dependencies beyond the Workers runtime.
Installation
npm install chat chat-state-cloudflare-doUsage
import { Chat } from "chat";
import { createSlackAdapter } from "@chat-adapter/slack";
import { createCloudflareState, ChatStateDO } from "chat-state-cloudflare-do";
// Re-export the Durable Object class so Cloudflare can find it
export { ChatStateDO };
export default {
async fetch(request: Request, env: Env) {
const bot = new Chat({
userName: "my-bot",
adapters: { slack: createSlackAdapter() },
state: createCloudflareState({ namespace: env.CHAT_STATE }),
});
return bot.webhooks.slack(request);
},
};Wrangler configuration
Add the Durable Object binding and migration to your wrangler.jsonc (recommended) or wrangler.toml:
wrangler.jsonc (recommended)
{
"durable_objects": {
"bindings": [
{ "name": "CHAT_STATE", "class_name": "ChatStateDO" }
]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["ChatStateDO"] }
]
}wrangler.toml
[durable_objects]
bindings = [
{ name = "CHAT_STATE", class_name = "ChatStateDO" }
]
[[migrations]]
tag = "v1"
new_sqlite_classes = ["ChatStateDO"]Environment type
import type { ChatStateDO } from "chat-state-cloudflare-do";
interface Env {
CHAT_STATE: DurableObjectNamespace<ChatStateDO>;
}Configuration
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| namespace | DurableObjectNamespace<ChatStateDO> | Yes | — | Durable Object namespace binding from wrangler config |
| name | string | No | "default" | Name for the DO instance |
| shardKey | (threadId: string) => string | No | — | Function to derive a shard name from a thread ID |
| locationHint | DurableObjectLocationHint | No | — | Location hint for DO placement |
Sharding
A single Durable Object handles approximately 500-1,000 requests per second. For high-traffic bots, use shardKey to distribute load across multiple DO instances:
const state = createCloudflareState({
namespace: env.CHAT_STATE,
shardKey: (threadId) => threadId.split(":")[0], // One DO per platform
});Locks and subscriptions are per-thread, so sharding by any prefix of the thread ID is safe. Cache operations (get/set/delete) always route to the default shard since their keys are not thread-scoped.
| Strategy | shardKey | DOs created |
|----------|-----------|-------------|
| No sharding (default) | — | 1 |
| Per platform | (id) => id.split(":")[0] | 1 per platform |
| Per channel | (id) => id.split(":").slice(0, 2).join(":") | 1 per channel |
Architecture
The adapter uses a single Durable Object class (ChatStateDO) with three SQLite tables:
subscriptions— thread IDs the bot is subscribed tolocks— distributed locks with token-based ownership and TTLcache— key-value pairs with optional TTL
All operations are single-threaded within a DO instance, providing distributed locking via DO atomicity rather than Lua scripts. Expired entries are cleaned up automatically via the Alarms API.
Each method call creates a fresh DO stub. Stubs are cheap (just a JS object) and the Cloudflare docs recommend creating new stubs rather than reusing them after errors.
Features
- Persistent subscriptions across deployments
- Distributed locking via single-threaded DO atomicity
- Key-value caching with TTL
- Automatic TTL cleanup via Alarms
- Optional sharding for high-traffic bots
- Location hints for latency optimization
- Zero external dependencies (no Redis, no database)
Production recommendations
- Use Smart Placement to co-locate your Worker with the DO
- Monitor DO metrics in the Cloudflare dashboard
- Enable sharding if you expect >500 req/s to a single DO instance
- Use
locationHintto place the DO near your primary user base
Documentation
License
MIT
