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:
// SQL fragment from 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
Connection to Other Layers
- L6 (Query Engine): Query engine produces SQL, driver executes it
- L7 (Adapters): Adapters determine SQL dialect, drivers execute it
- L9 (Client): Client uses driver through query engine