Ir al contenido

DB Kit

Abstraccion de base de datos usando Drizzle ORM. Soporta PostgreSQL, MySQL y SQLite.

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 es undefined hasta que el driver arranca, protegelo
const result = db.db!.select().from(users).all();

db.db tiene el tipo IskraDrizzleDb<TSchema> | undefined: es undefined antes de app.start() y de nuevo despues de stop(). Protegelo (if (!db.db) ...), usa el operador de aseveracion non-null cuando sepas que la app ya arranco, o verifica la conexion con db.ping().

Driver Paquete URL de ejemplo
postgres postgres.js postgres://user:pass@localhost:5432/db
mysql mysql2 (pool de conexiones) mysql://user:pass@localhost:3306/db
sqlite better-sqlite3 / bun:sqlite app.db o :memory:

El DbDriver detecta automaticamente si esta corriendo en Bun y usa bun:sqlite en vez de better-sqlite3. El driver mysql abre un pool de conexiones (mysql2.createPool) en lugar de una sola conexion.

DbDriver acepta un parametro de tipo Drizzle schema opcional para que db.query.* quede completamente tipado. Sin el argumento de tipo, el driver se comporta exactamente igual que antes (sin tipado, Record<string, never>).

import * as schema from './src/db/schema';
// Pasar el schema como argumento de tipo
const db = new DbDriver<typeof schema>();
app.register(db);
await app.start();
// db.db queda tipado (y sigue siendo `| undefined` hasta arrancar — protegelo)
const users = await db.db!.query.users.findMany({ where: eq(schema.users.active, true) });

app.context.get('db') devuelve el mismo driver, pero tipado con el schema por defecto (DbDriver | undefined): el registro del contexto no puede conocer el schema que pasaste, asi que a traves de el db.query.* queda sin tipar. Guarda una referencia a la instancia tipada del driver y usala, o castea lo que devuelve el contexto:

// Recomendado: exportar la instancia tipada e importarla donde haga falta
export const db = new DbDriver<typeof schema>();
// Donde solo tienes la app a mano
const db = app.context.get('db') as DbDriver<typeof schema> | undefined;

Los tipos auxiliares IskraDrizzleDb<TSchema> e IskraDrizzleTx<TSchema> estan disponibles para anotar parametros de funciones:

import type { IskraDrizzleDb, IskraDrizzleTx } from '@iskra-bun/db-kit';
src/db/schema.ts
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'),
});

Usa DbDriver.transaction(fn) en lugar de acceder directamente al handle db. Drizzle hace rollback automaticamente al lanzar una excepcion; los fallos se envuelven en 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 };
});

Firma:

transaction<R>(fn: (tx: IskraDrizzleTx<TSchema>) => Promise<R>): Promise<R>

Registra un callback que recibe cada sentencia SQL y sus parametros vinculados. Debe llamarse antes de app.start(), ya que el logger se configura al momento de la conexion.

db.setOnQuery((query, params) => {
console.log('[sql]', query, params);
// reenviar a OpenTelemetry, Datadog, etc.
});
app.register(db);
await app.start();

Firma:

setOnQuery(onQuery: (query: string, params: unknown[]) => void): void

Un callback que lanza una excepcion es capturado silenciosamente para que la observabilidad nunca interrumpa una consulta real.

DbDriver.ping() ejecuta un SELECT 1 trivial y resuelve true si tiene exito o false ante cualquier fallo — nunca rechaza. Usalo para sondas de readiness o endpoints de salud (funciona bien con addReadinessCheck de web-kit).

const alive = await db.ping();
// true → la base de datos es accesible
// false → no iniciado, o conexion caida

Firma:

ping(): Promise<boolean>

Ver Migraciones para la guia completa.

// Ejecutar migraciones programaticamente
await db.runMigrations(undefined, './drizzle');

runMigrations aplica las migraciones generadas en migrationsDir con el migrador de Drizzle, sobre la conexion que abrio start() (antes lanzaba drizzle-kit migrate, que ignoraba ambos argumentos y fallaba sin drizzle.config.ts). MigrationHelper sigue pasando --schema y --out a drizzle-kit generate y --config a todos los comandos.

MigrationHelper (y la CLI de db-kit) solo ejecutan el drizzle-kit instalado en el proyecto, buscado en node_modules/.bin del directorio de trabajo o de uno padre, mediante bunx --no-install drizzle-kit. drizzle-kit es una devDependency: donde no esta instalado (por ejemplo tras bun install --production) fallan con un MigrationError que pide bun add -d drizzle-kit. Antes ejecutaban bunx drizzle-kit, que entonces descargaba la ultima version de npm y la ejecutaba con DATABASE_URL en su entorno.

Cuando un comando de migracion termina con codigo distinto de cero, el stderr capturado de drizzle-kit se depura de credenciales antes de adjuntarse a MigrationError.context.stderr. drizzle-kit imprime la cadena de conexion completa al fallar, asi que cualquier //user:password@host embebido se reescribe como //***:***@host — el resto del diagnostico queda intacto, de modo que las cadenas de conexion nunca se filtran en los logs.

Tambien puedes usar MigrationHelper directamente para mayor 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', // opcional — se pasa como --config
});
await helper.generate('add_users'); // drizzle-kit generate --name add_users
await helper.migrate(); // drizzle-kit migrate
await helper.push(); // drizzle-kit push (atajo para desarrollo)

Mapeo de flags por comando:

Metodo --schema --out --config
generate si si si se define
migrate — — si se define
push si — si se define
drop — si si se define
import { ConnectionError, QueryError, MigrationError } from '@iskra-bun/db-kit';
// Se tiran automaticamente cuando falla la conexion:
// ConnectionError: code='CONNECTION_ERROR'
// QueryError: code='QUERY_ERROR' (tambien lanzado por transaction())
// MigrationError: code='MIGRATION_ERROR'