Skip to content
pre-alpha — the TypeScript API may still shift. The SQL won't.

Planning to try sqlfu with Durable Objects? Start with the normal Getting Started workflow, then change two things:

  1. Keep a separate sqlfu.config.ts, definitions.sql, migrations/, and sql/ directory for each Durable Object class that owns its own storage.
  2. Generate a migration bundle and run it from the Durable Object constructor with createDurableObjectClient(ctx.storage).

That is the whole shape. You still author SQL first, draft migration files, and generate typed wrappers from .sql query files.

One Durable Object usually wants one sqlfu project:

src/durable-objects/counter/
|-- definitions.sql
|-- migrations/
| `-- 20260506000000_create_counter.sql
|-- sql/
| |-- queries.sql
| `-- .generated/
|-- sqlfu.config.ts
`-- counter.ts

If you have several Durable Object classes with different schemas, give each one its own folder and pass --config explicitly:

Terminal window
npx sqlfu --config src/durable-objects/counter/sqlfu.config.ts draft
npx sqlfu --config src/durable-objects/counter/sqlfu.config.ts generate

Durable Object storage is not available to the Node CLI as a simple local file, so the common Durable Object config omits db. sqlfu uses .sqlfu/app.db as a local authoring database for commands that need one, and draft plus generate still read from definitions.sql and migration files.

import {defineConfig} from 'sqlfu';
export default defineConfig({
definitions: './definitions.sql',
migrations: './migrations',
queries: './sql',
generate: {
sync: true,
},
});

generate.sync: true matters because Durable Object SQLite is synchronous. Generated query wrappers accept a SyncClient and return rows directly instead of promises.

Author the schema in definitions.sql:

create table counters (
name text primary key not null,
value integer not null default 0
);

Author the runtime query in sql/queries.sql:

/** @name incrementCounter */
insert into counters (name, value)
values (:name, 1)
on conflict (name) do update set value = value + 1
returning name, value;

Then draft and generate:

Terminal window
npx sqlfu --config src/durable-objects/counter/sqlfu.config.ts draft
npx sqlfu --config src/durable-objects/counter/sqlfu.config.ts generate

Commit the reviewed migrations/*.sql files. Also commit the generated query wrappers if your app checks generated files in. The generated migration bundle under migrations/.generated/migrations.ts is what the Durable Object imports at runtime.

Pass the full ctx.storage object to the adapter:

import {DurableObject} from 'cloudflare:workers';
import {createDurableObjectClient} from 'sqlfu';
import {migrate} from './migrations/.generated/migrations.ts';
import {incrementCounter} from './sql/.generated/queries.sql.ts';
type Env = {};
export class CounterObject extends DurableObject {
client: ReturnType<typeof createDurableObjectClient>;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.client = createDurableObjectClient(ctx.storage);
migrate(this.client);
}
fetch(request: Request) {
const url = new URL(request.url);
const name = url.searchParams.get('name') || 'default';
const row = incrementCounter(this.client, {name});
return Response.json(row);
}
}

Use ctx.storage, not ctx.storage.sql. The full storage object lets sqlfu use Durable Objects’ transactionSync() API so each migration is applied inside a real storage transaction. If you only need ad-hoc query access and deliberately do not want migrations, pass {sql: ctx.storage.sql}.

Every Durable Object instance has its own private SQLite database. A deploy puts new code on the Worker, but it does not automatically upgrade storage for every existing object. Let each object call migrate(this.client) when it starts.

migrate() is idempotent. It skips migrations already recorded in that object’s sqlfu_migrations table and applies only the missing files from the generated bundle. If the object has recorded a migration that is no longer in the bundle, sqlfu treats that as migration-history drift and fails instead of guessing.

  • Getting Started for the base SQL -> draft -> generate workflow.
  • Adapters for the small Durable Object adapter snippet.
  • SQL migrations for the migration model and failure modes specific to Durable Objects.