SQLite
Choose and operate an Effect Auth SQLite adapter across Node, Bun, Durable Objects, WASM, or an application-owned executor.
Start with the database that will hold authentication records. Effect Auth owns the SQLite schema and queries; your application owns the database file or platform database, migration history, backups, and runtime lifecycle. Then choose the thinnest adapter that matches how that database is already accessed.
Database first
Already have an effect-qb/sqlite executor? Use it directly. Already have an Effect Drizzle SQLite database, or need a packaged runtime driver? Use the Drizzle bridge. Neither choice changes the auth schema or storage behavior.
Runtime matrix
| Database/runtime | Adapter path | Exact public constructor | Ownership |
|---|---|---|---|
| Application-provided generic SQLite executor | @effect-auth/core/EffectQbSqliteStorage | EffectQbSqliteAuthStorageLive(executor, options?) | Application opens, configures, and closes the effect-qb/sqlite executor |
| Node SQLite | @effect-auth/core/DrizzleNodeSqliteStorage | DrizzleNodeSqliteAuthStorageLive(sqliteConfig, options?, drizzleConfig?) | Constructor creates a scoped @effect/sql-sqlite-node client |
| Bun SQLite | @effect-auth/core/DrizzleBunSqliteStorage | DrizzleBunSqliteAuthStorageLive(sqliteConfig, options?, drizzleConfig?) | Constructor creates a scoped @effect/sql-sqlite-bun client |
| Durable Object SQLite | @effect-auth/core/DrizzleDurableObjectSqliteStorage | DrizzleDurableObjectSqliteAuthStorageLive(sqliteConfig, options?, drizzleConfig?) | Pass { storage: ctx.storage.sql }; one database belongs to each object |
| Persistent WASM SQLite | @effect-auth/core/DrizzleWasmSqliteStorage | DrizzleWasmSqliteAuthStorageLive(sqliteConfig, options?, drizzleConfig?) | Scoped client; persistence belongs to the configured worker/storage backend |
| In-memory WASM SQLite | @effect-auth/core/DrizzleWasmSqliteStorage | DrizzleWasmMemorySqliteAuthStorageLive(sqliteConfig?, options?, drizzleConfig?) | Scoped and ephemeral; tests or disposable sessions only |
If the application already owns a compatible Effect Drizzle database, use DrizzleEffectSqliteAuthStorageLive(database, options?) from @effect-auth/core/DrizzleEffectSqliteStorage; the adapter neither acquires nor closes it.
Cloudflare D1 is SQLite-compatible but operationally different from local SQLite. Use the dedicated Cloudflare D1 guide for bindings, deployment, and limits rather than applying this page's local lifecycle assumptions.
Query path
Native executor Drizzle bridge
Auth stores Auth stores
| |
Effect-QB SQLite queries Effect-QB SQLite queries
| |
effect-qb/sqlite Executor makeDrizzleEffectSqliteExecutor
| |
application SQLite driver Effect Drizzle database -> SQLiteThe native path is the smallest surface. The Drizzle path translates placeholders into Drizzle SQL and calls the database's Effect-style all(...); it then delegates to the same Effect-QB store implementation. Use Drizzle for an existing Drizzle stack or its maintained runtime clients, not for a different schema or dialect.
Minimal Node setup
Apply migrations before startup, then keep the layer inside one long-lived Effect scope:
import { DrizzleNodeSqliteAuthStorageLive } from "@effect-auth/core/DrizzleNodeSqliteStorage";
import { UserId } from "@effect-auth/core/Identifiers";
import { UserStore } from "@effect-auth/core/Storage";
import { Effect } from "effect";
const AuthStorageLive = DrizzleNodeSqliteAuthStorageLive({
filename: "./data/auth.sqlite",
});
const program = Effect.gen(function* () {
const users = yield* UserStore;
return yield* users.findById(UserId("01J00000000000000000000000"));
});
await Effect.runPromise(
Effect.scoped(program.pipe(Effect.provide(AuthStorageLive))),
);For Bun, switch to DrizzleBunSqliteAuthStorageLive from @effect-auth/core/DrizzleBunSqliteStorage; its configuration has the same role. Install only the matching optional Effect SQL driver and Drizzle peers.
Migrations and lifecycle
authStorageMigrations from @effect-auth/core/StorageMigrations is the authoritative ordered schema. Your deployment or application migration system must apply every entry exactly once, record its id, and finish before auth traffic starts. Adapters never migrate automatically. Drizzle Kit schemas and drizzle-kit push are not substitutes for these migrations.
Create one scoped Node, Bun, or WASM layer per application runtime, not per query. Scope finalization closes its client. Application-provided Effect-QB and Drizzle databases remain application-owned. In a Durable Object, construct and consume the layer within that object's lifecycle and choose object IDs so records that must be queried together share one database.
Concurrency essentials
- SQLite allows concurrent reads but serializes writes. Keep transactions and indexed auth writes short; handle busy/lock failures according to your retry policy.
- WAL can improve local reader/writer overlap, but is not safe to assume on network or serverless filesystems.
- A Durable Object serializes work per object, not across object IDs. WASM permits competing writers only when its persistence backend explicitly supports them.
- The adapter does not add a global mutex or make several store calls atomic. Put cross-call transactions at a database boundary supported by your driver.
For a non-SQLite database or application-specific persistence model, implement the service contracts in Custom Database.