sqlfu doesn’t ship its own database driver. It wraps whichever SQLite-compatible client you already use and gives you the same typedDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/iterate/sqlfu/llms.txt
Use this file to discover all available pages before exploring further.
Client surface on top. The adapter is the only runtime-specific line in your codebase — everything else works against the shared interface.
The adapter pattern
Each adapter factory takes the underlying driver’s database object as its sole argument and returns a sqlfuClient. None of the drivers are peer dependencies of sqlfu: install only the one you use.
Sync vs. async
Most query libraries force every database call to beasync, even when the underlying driver is synchronous. sqlfu preserves the sync or async nature of the driver you brought:
- Pass
better-sqlite3and you get aSyncClient.client.all(...)returns rows, notPromise<rows>. - Pass
@libsql/clientand you get anAsyncClient. Same surface, but promise-returning. - Generated wrappers and
applyMigrations()follow the same split.
Compatibility matrix
| Driver package | Runtime | Sync/Async | Factory function |
|---|---|---|---|
node:sqlite | Node ≥ 22 | Sync | createNodeSqliteClient |
better-sqlite3 | Node | Sync | createBetterSqlite3Client |
bun:sqlite | Bun | Sync | createBunClient |
libsql | Node | Sync | createLibsqlSyncClient |
@libsql/client | Node / Deno / edge | Async | createLibsqlClient |
@tursodatabase/database | Node | Async | createTursoDatabaseClient |
@tursodatabase/sync | Node | Async | createTursoDatabaseClient |
@tursodatabase/serverless | Any fetch() runtime | Async | createTursoServerlessClient |
D1Database (Cloudflare) | Cloudflare Workers | Async | createD1Client |
SqlStorage (Durable Objects) | Cloudflare Durable Objects | Sync | createDurableObjectClient |
expo-sqlite | Expo / React Native | Async | createExpoSqliteClient |
@sqlite.org/sqlite-wasm | Browsers | Async | createSqliteWasmClient |
Local and embedded adapters
node:sqlite
The built-in SQLite module available in Node ≥ 22. No extra packages needed.
better-sqlite3
The most widely used synchronous SQLite binding for Node. Well-tested and battle-hardened.
bun:sqlite
The built-in SQLite module for Bun. No install needed when running on Bun.
libsql (native embedded)
The native libsql binding for Node. Runs locally as a file database, optionally with embedded replica support.
@tursodatabase/database
Turso’s next-generation engine with native Node bindings. Uses the same factory as @tursodatabase/sync.
Remote and cloud adapters
@libsql/client — Turso Cloud or local file:
Connects to Turso Cloud over the libsql:// protocol, or to a local file when url is file:app.db. The same adapter works for both — no code changes needed between environments.
@tursodatabase/serverless — HTTP, no native dependencies
Connects to Turso Cloud over HTTP using only fetch(). Runs on Vercel Edge, Cloudflare Workers, Deno Deploy, and AWS Lambda without native bindings.
@tursodatabase/sync — local file synced to Turso Cloud
Same createTursoDatabaseClient factory as @tursodatabase/database. The difference is at the driver level: the driver keeps a local SQLite file and knows how to push()/pull() to a remote Turso database.
Cloudflare D1
D1 bindings are injected via the Worker’senv object. Wrap them with createD1Client inside your fetch handler.
Cloudflare Durable Objects
Passctx.storage, not ctx.storage.sql. The full storage object gives sqlfu access to Cloudflare’s transactionSync() API so each migration runs inside a real Durable Object storage transaction.
migrate() is idempotent. Once a Durable Object’s private SQLite database has a row in sqlfu_migrations for a given migration, that migration is skipped on later starts. Each Durable Object instance manages its own independent database.Mobile and browser adapters
Expo SQLite
Works withexpo-sqlite’s async API on iOS and Android.
@sqlite.org/sqlite-wasm (browsers)
Runs SQLite compiled to WebAssembly. Supports OPFS for persistent storage or in-memory databases.
Prepared statements
Generated wrappers from.sql files are the main application path. client.prepare(sql) is the lower-level API for SQL that needs to stay dynamic without reaching through to client.driver. See the client reference for full usage details.
sync
async
Choosing an adapter
I want the same code in local dev and production
I want the same code in local dev and production
Use
@libsql/client for both. Set url: 'file:app.db' locally and url: process.env.TURSO_DATABASE_URL in production. No code changes needed at the sqlfu layer.I need zero native dependencies
I need zero native dependencies
Use
@tursodatabase/serverless (Turso Cloud over HTTP) or Cloudflare D1. Both work on any runtime with fetch().I want an embedded database that syncs to the cloud
I want an embedded database that syncs to the cloud
Use
@tursodatabase/sync. The local file works offline; you call push()/pull() at your own cadence.I want the fastest pure-local experience on Node
I want the fastest pure-local experience on Node
Use
better-sqlite3 or @tursodatabase/database. Both are synchronous with native bindings.I'm on Bun
I'm on Bun
Use
bun:sqlite with createBunClient. No extra packages needed.I'm on Node ≥ 22 and want no extra packages
I'm on Node ≥ 22 and want no extra packages
Use
node:sqlite with createNodeSqliteClient. It ships with Node.I'm building a mobile or browser app
I'm building a mobile or browser app
Use
expo-sqlite for React Native / Expo, or @sqlite.org/sqlite-wasm for browsers.Writing a custom adapter
Each adapter is a thin function that wraps a driver into aSyncClient or AsyncClient. The existing adapters in src/adapters/ are the reference implementation.
Implement the core methods
Your adapter object must provide:
all(query)— returns rowsrun(query)— returns{rowsAffected?, lastInsertRowid?}raw(sql)— executes a multi-statement stringiterate(query)— returns a row iterator
Implement prepare
Return a disposable handle with
.all(params), .run(params), .iterate(params), and a dispose method ([Symbol.dispose] for sync, [Symbol.asyncDispose] for async).If the driver has native prepared statements, wrap them. If not, implement prepare as a shim that captures the SQL string and re-issues it through the driver on each call. The using / await using contract still works either way.Implement transaction
The
sqlfu/core/sqlite helpers provide surroundWithBeginCommitRollbackSync and surroundWithBeginCommitRollbackAsync. These implement begin / commit / rollback wrapping for you — use them rather than rolling your own.