DB Kit
Database abstraction using Drizzle ORM. It supports PostgreSQL, MySQL and SQLite.
Quick Start
Section titled “Quick Start”import { App } from '@iskra-bun/core';import { DbDriver } from '@iskra-bun/db-kit';
const app = new App({ name: 'MiApp', db: { driver: 'sqlite', url: 'app.db', },});
const db = new DbDriver();app.register(db);
await app.start();
// Usar Drizzle ORM — db.db is undefined until the driver starts, so guard itconst result = db.db!.select().from(users).all();db.db is typed IskraDrizzleDb<TSchema> | undefined: it is undefined
before app.start() and again after stop(). Guard it (if (!db.db) ...),
use the non-null assertion when you know the app has started, or check
liveness with db.ping().
Supported Drivers
Section titled “Supported Drivers”| Driver | Package | Example URL |
|---|---|---|
postgres |
postgres.js | postgres://user:pass@localhost:5432/db |
mysql |
mysql2 (connection pool) | mysql://user:pass@localhost:3306/db |
sqlite |
better-sqlite3 / bun:sqlite | app.db or :memory: |
The DbDriver automatically detects whether it is running on Bun and uses bun:sqlite instead of better-sqlite3. The mysql driver opens a connection pool (mysql2.createPool) rather than a single connection.
Typed Schema (Generics)
Section titled “Typed Schema (Generics)”DbDriver accepts an optional Drizzle schema type parameter so db.query.* is
fully typed. Without the type argument the driver behaves exactly as before
(untyped, Record<string, never>).
import * as schema from './src/db/schema';
// Pass your schema as the type argumentconst db = new DbDriver<typeof schema>();app.register(db);
await app.start();
// db.db is now typed (and still `| undefined` until started — guard it)const users = await db.db!.query.users.findMany({ where: eq(schema.users.active, true) });app.context.get('db') returns the same driver, but typed with the default
schema (DbDriver | undefined): the context registry cannot know the schema you
passed, so db.query.* is untyped through it. Keep a reference to the typed
driver instance and use it, or cast what the context returns:
// Recommended: export the typed instance and import it where you need itexport const db = new DbDriver<typeof schema>();
// Where only the app is at handconst db = app.context.get('db') as DbDriver<typeof schema> | undefined;The exported helper types IskraDrizzleDb<TSchema> and IskraDrizzleTx<TSchema>
are available if you need to annotate function parameters:
import type { IskraDrizzleDb, IskraDrizzleTx } from '@iskra-bun/db-kit';Schemas with Drizzle
Section titled “Schemas with Drizzle”import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', { id: integer('id').primaryKey({ autoIncrement: true }), name: text('name').notNull(), email: text('email').notNull().unique(), createdAt: text('created_at').default('CURRENT_TIMESTAMP'),});Transactions
Section titled “Transactions”Use DbDriver.transaction(fn) instead of reaching into the raw db handle.
Drizzle rolls back automatically on throw; failures are wrapped in QueryError.
const result = await db.transaction(async (tx) => { await tx.insert(schema.orders).values({ userId: 1, total: 99 }); await tx.update(schema.inventory).set({ stock: sql`stock - 1` }).where(eq(schema.inventory.id, 42)); return { ok: true };});Signature:
transaction<R>(fn: (tx: IskraDrizzleTx<TSchema>) => Promise<R>): Promise<R>Observability Hook
Section titled “Observability Hook”Register a callback that receives every SQL statement and its bound parameters.
Must be called before app.start() — the logger is wired at connection time.
db.setOnQuery((query, params) => { console.log('[sql]', query, params); // forward to OpenTelemetry, Datadog, etc.});
app.register(db);await app.start();Signature:
setOnQuery(onQuery: (query: string, params: unknown[]) => void): voidA throwing callback is swallowed so observability never breaks a real query.
Liveness Probe
Section titled “Liveness Probe”DbDriver.ping() runs a trivial SELECT 1 and resolves true on success or
false on any failure — it never rejects. Use it for readiness probes or health
endpoints (pairs well with web-kit’s addReadinessCheck).
const alive = await db.ping();// true → database is reachable// false → not started, or connection droppedSignature:
ping(): Promise<boolean>Migrations
Section titled “Migrations”See Migrations for the full guide.
// Run migrations programmaticallyawait db.runMigrations(undefined, './drizzle');runMigrations applies the migrations generated in migrationsDir with Drizzle’s migrator, over the connection opened by start() (it used to run drizzle-kit migrate, which ignored both arguments and failed without a drizzle.config.ts). MigrationHelper still passes --schema and --out to drizzle-kit generate and --config to every command.
MigrationHelper (and the db-kit CLI) only run the drizzle-kit installed in the project, found in node_modules/.bin of the working directory or a parent, through bunx --no-install drizzle-kit. drizzle-kit is a devDependency: where it is not installed (e.g. after bun install --production) they fail with a MigrationError asking to bun add -d drizzle-kit. They used to run bunx drizzle-kit, which then downloaded the latest release from npm and ran it with DATABASE_URL in its environment.
When a migration command exits non-zero, the captured drizzle-kit stderr is
scrubbed of credentials before it is attached to MigrationError.context.stderr.
drizzle-kit echoes the full connection string on failure, so any embedded
//user:password@host is rewritten to //***:***@host — the rest of the
diagnostic survives intact, so connection strings never leak into logs.
You can also use MigrationHelper directly if you need finer control:
import { MigrationHelper } from '@iskra-bun/db-kit';
const helper = new MigrationHelper({ dialect: 'sqlite', // 'postgresql' | 'mysql' | 'sqlite' dbUrl: 'app.db', schemaPath: './src/db/schema.ts', migrationsDir: './drizzle', configPath: './drizzle.config.ts', // optional — passed as --config});
await helper.generate('add_users'); // drizzle-kit generate --name add_usersawait helper.migrate(); // drizzle-kit migrateawait helper.push(); // drizzle-kit push (dev shortcut)Flag mapping per command:
| Method | --schema |
--out |
--config |
|---|---|---|---|
generate |
yes | yes | if set |
migrate |
— | — | if set |
push |
yes | — | if set |
drop |
— | yes | if set |
Errors
Section titled “Errors”import { ConnectionError, QueryError, MigrationError } from '@iskra-bun/db-kit';
// Thrown automatically on failure:// ConnectionError: code='CONNECTION_ERROR'// QueryError: code='QUERY_ERROR' (also thrown by transaction())// MigrationError: code='MIGRATION_ERROR'