Drivers & Dialects

The driver interface #

Drivers implement a template-based interface. Each method receives (TemplateStringsArray, values[]) and builds SQL with the driver's native placeholder syntax:

interface Driver {
  // Query methods โ€” build SQL with native placeholders (? or $1, $2, ...)
  all<T>(strings: TemplateStringsArray, values: unknown[]): Promise<T[]>;
  get<T>(strings: TemplateStringsArray, values: unknown[]): Promise<T | null>;
  run(strings: TemplateStringsArray, values: unknown[]): Promise<number>;
  val<T>(strings: TemplateStringsArray, values: unknown[]): Promise<T | null>;

  // Connection management
  close(): Promise<void>;
  transaction<T>(fn: (tx: Driver) => Promise<T>): Promise<T>;

  // Capabilities
  readonly supportsReturning: boolean;

  // Optional
  withMigrationLock?<T>(fn: () => Promise<T>): Promise<T>;
}

Why templates? Drivers receive raw template parts and build SQL with their native placeholders (? for SQLite/MySQL, $1, $2, โ€ฆ for PostgreSQL). No SQL parsing is needed.

Driver exports #

// Bun (built-in, auto-detects dialect)
import BunDriver from "@b9g/zen/bun";

// Node.js SQLite (better-sqlite3)
import SQLiteDriver from "@b9g/zen/sqlite";

// PostgreSQL (postgres.js)
import PostgresDriver from "@b9g/zen/postgres";

// MySQL (mysql2)
import MySQLDriver from "@b9g/zen/mysql";

Dialect support #

FeatureSQLitePostgreSQLMySQL
RETURNINGโœ…โœ…โš ๏ธ fallback
IF NOT EXISTS (CREATE TABLE)โœ…โœ…โœ…
IF NOT EXISTS (ADD COLUMN)โœ…โœ…โš ๏ธ may error
Migration locksBEGIN EXCLUSIVEpg_advisory_lockGET_LOCK
Advisory locksโ€”โœ…โœ…

Default column types #

FeatureSQLitePostgreSQLMySQL
Date typeTEXTTIMESTAMPTZDATETIME
Date defaultCURRENT_TIMESTAMPNOW()CURRENT_TIMESTAMP
BooleanINTEGERBOOLEANBOOLEAN
JSONTEXTJSONBTEXT
Quoting"double""double"`backtick`

Zod to SQL type mapping #

Zod TypeSQLitePostgreSQLMySQL
z.string()TEXTTEXTTEXT
z.string().max(n) (n โ‰ค 255)TEXTVARCHAR(n)VARCHAR(n)
z.number()REALDOUBLE PRECISIONREAL
z.number().int()INTEGERINTEGERINTEGER
z.boolean()INTEGERBOOLEANBOOLEAN
z.date()TEXTTIMESTAMPTZDATETIME
z.enum([...])TEXTTEXTTEXT
z.object({...})TEXTJSONBTEXT
z.array(...)TEXTJSONBTEXT

Override any of these with .db.type("CUSTOM") โ€” see Defining Tables.

Edit on GitHub