L8 - Drivers
Handle database connections and execute queries
Location: src/drivers/
Why This Layer Exists
Different database clients have different APIs:
// node-postgres
const result = await client.query(sql, params);
// PGlite (embedded)
const result = await db.exec(sql, params);
// mysql2
const [rows] = await connection.execute(sql, params);
// better-sqlite3
const rows = db.prepare(sql).all(...params);
Drivers abstract these differences behind a common interface.
Driver Interface
Drivers extend the abstract Driver base class (see src/drivers/driver.ts). It is generic over the underlying client and transaction handle, carries the SQL adapter for its dialect, and manages lazy connection, transaction queuing, and error mapping. Subclasses implement five methods:
abstract class Driver<TClient, TTransaction> {
readonly dialect: Dialect; // "postgres" | "mysql" | "sqlite"
readonly driverName: string;
abstract readonly adapter: DatabaseAdapter; // SQL adapter for this dialect
readonly supportsTransactions: boolean; // false for batch-only clients (D1 binding, Neon HTTP)
readonly supportsBatch: boolean; // true for atomic-batch APIs (D1, Neon HTTP)
// Subclasses implement:
protected abstract initClient(): Promise<TClient>;
protected abstract closeClient(client: TClient | TTransaction): Promise<void>;
protected abstract execute<T>(client, sql, params, context?): Promise<QueryResult<T>>;
protected abstract executeRaw<T>(client, sql, params, context?): Promise<QueryResult<T>>;
protected abstract transaction<T>(client, fn: (tx: TTransaction) => Promise<T>, context?): Promise<T>;
}
Available Drivers
| Driver | Entry Point | Client | Use Case |
|---|---|---|---|
PgDriver |
viborm/pg |
node-postgres | PostgreSQL servers |
PostgresDriver |
viborm/postgres |
postgres.js | PostgreSQL servers |
PGliteDriver |
viborm/pglite |
PGlite | Embedded PostgreSQL |
NeonHTTPDriver |
viborm/neon-http |
Neon HTTP | Serverless PostgreSQL |
BunSQLDriver |
viborm/bun-sql |
Bun SQL | PostgreSQL from Bun |
MySQL2Driver |
viborm/mysql2 |
mysql2 | MySQL servers |
PlanetScaleDriver |
viborm/planetscale |
PlanetScale | Serverless MySQL |
SQLite3Driver |
viborm/sqlite3 |
better-sqlite3 | SQLite files |
LibSQLDriver |
viborm/libsql |
libSQL | Turso / libSQL |
BunSQLiteDriver |
viborm/bun-sqlite |
bun | SQLite from Bun |
D1Driver |
viborm/d1 |
D1 binding | Cloudflare Workers |
What Drivers Handle
Connection Management
Drivers connect lazily and accept either an existing client or connection options. For example, PostgresDriver (postgres.js):
const driver = new PostgresDriver({
databaseUrl: "postgres://...", // or options: { host, port, database, ... }
// client: existingPostgresJsInstance,
// pgvector: true,
// postgis: true,
});
Parameterization
Drivers ensure queries use parameterized values:
// Adapter-built SQL fragment composed by the query engine
sql`SELECT * FROM users WHERE id = ${userId}`
// Driver separates template and values
query: "SELECT * FROM users WHERE id = $1"
params: [userId]
Result Transformation
Some databases return results in different formats. Drivers normalize this:
// MySQL returns [rows, fields]
// PostgreSQL returns { rows, rowCount }
// Drivers return just the rows
Error Handling
Database-specific errors are normalized into typed VibORM errors (see src/drivers/error-mapping.ts):
// PostgreSQL: error code "23505"
// MySQL: errno 1062 / "ER_DUP_ENTRY"
// SQLite: "SQLITE_CONSTRAINT_UNIQUE"
// Driver: throws UniqueConstraintError
Provider recognition stops at this boundary. The driver maps provider codes,
constraint names, and assertion markers. The query engine can then combine a
typed unique failure with the structural racePin on a write; it does not parse
provider SQL or error text itself.
Fragment Execution
Drivers do not interpret PlanningFragment, OperationFragment, relation
programs, or guards. OperationExecutor lowers those concepts into the surface
the driver owns:
- one statement for a structural direct candidate;
- ordered statements inside a transaction;
- one atomic
BatchQuery[]envelope for a batch-capable driver.
Adapter batchRefs lower produced-value references for atomic batches. Drivers
execute the resulting batch and return normalized results in the same order.
Connection to Other Layers
- L6 (Query Engine): Query engine lowers portable fragments to driver statements or batches
- L7 (Adapters): Adapters determine SQL dialect, drivers execute it
- L9 (Client): Client uses driver through query engine