Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/do-storage/patterns.md
1# DO Storage Patterns & Best Practices23## Schema Migration45**Note:** `PRAGMA user_version` is **not supported** in Durable Objects SQLite storage. Use a `_sql_schema_migrations` table instead:67```typescript8export class MyDurableObject extends DurableObject {9constructor(ctx: DurableObjectState, env: Env) {10super(ctx, env);11this.sql = ctx.storage.sql;1213this.sql.exec(`14CREATE TABLE IF NOT EXISTS _sql_schema_migrations (15id INTEGER PRIMARY KEY,16applied_at TEXT NOT NULL DEFAULT (datetime('now'))17)18`);1920const ver = this.sql21.exec<{ version: number }>("SELECT COALESCE(MAX(id), 0) as version FROM _sql_schema_migrations")22.one().version;2324if (ver < 1) {25this.sql.exec(`CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT)`);26this.sql.exec("INSERT INTO _sql_schema_migrations (id) VALUES (1)");27}28if (ver < 2) {29this.sql.exec(`ALTER TABLE users ADD COLUMN email TEXT`);30this.sql.exec("INSERT INTO _sql_schema_migrations (id) VALUES (2)");31}32}33}34```3536For production apps, consider [`durable-utils`](https://github.com/lambrospetrou/durable-utils#sqlite-schema-migrations) — provides a `SQLSchemaMigrations` class that tracks executed migrations both in memory and in storage. Also see [`@cloudflare/actors` storage utilities](https://github.com/cloudflare/actors/blob/main/packages/storage/src/sql-schema-migrations.ts) — a reference implementation of the same pattern used by the Cloudflare Actors framework.3738## In-Memory Caching3940```typescript41export class UserCache extends DurableObject {42cache = new Map<string, User>();43async getUser(id: string): Promise<User | undefined> {44if (this.cache.has(id)) {45const cached = this.cache.get(id);46if (cached) return cached;47}48const user = await this.ctx.storage.get<User>(`user:${id}`);49if (user) this.cache.set(id, user);50return user;51}52async updateUser(id: string, data: Partial<User>) {53const updated = { ...await this.getUser(id), ...data };54this.cache.set(id, updated);55await this.ctx.storage.put(`user:${id}`, updated);56return updated;57}58}59```6061## Rate Limiting6263```typescript64export class RateLimiter extends DurableObject {65async checkLimit(key: string, limit: number, window: number): Promise<boolean> {66const now = Date.now();67this.sql.exec('DELETE FROM requests WHERE key = ? AND timestamp < ?', key, now - window);68const count = this.sql.exec('SELECT COUNT(*) as count FROM requests WHERE key = ?', key).one().count;69if (count >= limit) return false;70this.sql.exec('INSERT INTO requests (key, timestamp) VALUES (?, ?)', key, now);71return true;72}73}74```7576## Batch Processing with Alarms7778```typescript79export class BatchProcessor extends DurableObject {80pending: string[] = [];81async addItem(item: string) {82this.pending.push(item);83if (!await this.ctx.storage.getAlarm()) await this.ctx.storage.setAlarm(Date.now() + 5000);84}85async alarm() {86const items = [...this.pending];87this.pending = [];88this.sql.exec(`INSERT INTO processed_items (item, timestamp) VALUES ${items.map(() => "(?, ?)").join(", ")}`, ...items.flatMap(item => [item, Date.now()]));89}90}91```9293## Initialization Pattern9495```typescript96export class Counter extends DurableObject {97value: number;98constructor(ctx: DurableObjectState, env: Env) {99super(ctx, env);100ctx.blockConcurrencyWhile(async () => { this.value = (await ctx.storage.get("value")) || 0; });101}102async increment() {103this.value++;104this.ctx.storage.put("value", this.value); // Don't await (output gate protects)105return this.value;106}107}108```109110## Safe Counter / Optimized Write111112```typescript113// Input gate blocks other requests114async getUniqueNumber(): Promise<number> {115let val = await this.ctx.storage.get("counter");116await this.ctx.storage.put("counter", val + 1);117return val;118}119120// No await on write - output gate delays response until write confirms121async increment(): Promise<Response> {122let val = await this.ctx.storage.get("counter");123this.ctx.storage.put("counter", val + 1);124return new Response(String(val));125}126```127128## Parent-Child Coordination129130Hierarchical DO pattern where parent manages child DOs:131132```typescript133// Parent DO coordinates children134export class Workspace extends DurableObject {135async createDocument(name: string): Promise<string> {136const docId = crypto.randomUUID();137const childId = this.env.DOCUMENT.idFromName(`${this.ctx.id.toString()}:${docId}`);138const childStub = this.env.DOCUMENT.get(childId);139await childStub.initialize(name);140141// Track child in parent storage142this.sql.exec('INSERT INTO documents (id, name, created) VALUES (?, ?, ?)',143docId, name, Date.now());144return docId;145}146147async listDocuments(): Promise<string[]> {148return this.sql.exec('SELECT id FROM documents').toArray().map(r => r.id);149}150}151152// Child DO153export class Document extends DurableObject {154async initialize(name: string) {155this.sql.exec('CREATE TABLE IF NOT EXISTS content(key TEXT PRIMARY KEY, value TEXT)');156this.sql.exec('INSERT INTO content VALUES (?, ?)', 'name', name);157}158}159```160161## Write Coalescing Pattern162163Multiple writes to same key coalesce atomically (last write wins):164165```typescript166async updateMetrics(userId: string, actions: Action[]) {167// All writes coalesce - no await needed168for (const action of actions) {169this.ctx.storage.put(`user:${userId}:lastAction`, action.type);170this.ctx.storage.put(`user:${userId}:count`,171await this.ctx.storage.get(`user:${userId}:count`) + 1);172}173// Output gate ensures all writes confirm before response174return new Response("OK");175}176177// Atomic batch with SQL178async batchUpdate(items: Item[]) {179this.sql.exec('BEGIN');180for (const item of items) {181this.sql.exec('INSERT OR REPLACE INTO items VALUES (?, ?)', item.id, item.value);182}183this.sql.exec('COMMIT');184}185```186187## Cleanup188189```typescript190async cleanup() {191await this.ctx.storage.deleteAlarm(); // Separate from deleteAll192await this.ctx.storage.deleteAll();193}194```195