Changing database schema with Kysely migrations across SQLite, PostgreSQL, and MariaDB. Every migration must ship with a colocated `*.spec.ts` exercising `up`/`down` on every supported dialect, keep patch coverage ≥ 90% on changed lines, update affected query tests, and pass `make fix`, `make test-unit`, `make test-integration`, and `make test-e2e` before submission.
Parent: AGENTS.md | Related: backend-service, backend-route
Changing database schema — adding tables, columns, indexes, or modifying existing structures.
src/db/migrations/*.ts — numbered migration files (000_, 001_, 002_, etc.)src/db/migrations/*.spec.ts — colocated migration testssrc/db/queries/*.ts — query functions to update after schema changessrc/db/client.ts — database clientsrc/db/dialect/ — multi-DB dialect support (SQLite, PostgreSQL, MariaDB)// src/db/migrations/00N_my_migration.ts
import { type Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema.createTable('my_table')
.addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement())
.addColumn('name', 'varchar(255)', (col) => col.notNull())
.addColumn('created_at', 'timestamp', (col) => col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`))
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('my_table').execute();
}
Use DB_PATH=:memory: for fast, isolated tests:
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
let db: Kysely<any>;
beforeEach(async () => {
db = createTestDb(); // in-memory SQLite
await up(db);
});
afterEach(async () => { await db.destroy(); });
it('creates table with expected columns', async () => {
const result = await db.selectFrom('my_table').selectAll().execute();
expect(result).toEqual([]);
});
it('down removes the table', async () => {
await down(db);
// Verify table no longer exists
});
bun test src/db/migrations/00N_my_migration.spec.ts # Run migration test
make fix # Lint
AUTOINCREMENT vs AUTO_INCREMENT, TEXT vs VARCHAR, etc.). Use Kysely's schema builder which abstracts these differences.up() and down() — down() is needed for rollbacks and testing.src/db/queries/. A migration without query updates is incomplete.DB_PATH=:memory: for speed and isolation.deleted_at column). Queries must include WHERE deleted_at IS NULL or results will include deactivated records.up() and down() both implemented.spec.ts colocated, tests both up() and down()src/db/queries/ updatedmake fix passes clean