@kysera/migrations
Lightweight, type-safe database migration system.
Installation
npm install @kysera/migrations
Overview
Version: 0.7.0 Bundle Size: ~12 KB (minified) Dependencies: @kysera/core
Creating Migrations
createMigration
Create a simple migration.
function createMigration(
name: string,
up: (db: Kysely<any>) => Promise<void>,
down?: (db: Kysely<any>) => Promise<void>
): Migration
interface Migration {
name: string
up: (db: Kysely<any>) => Promise<void>
down?: (db: Kysely<any>) => Promise<void>
}
Example
import { createMigration } from '@kysera/migrations'
import { sql } from 'kysely'
const migrations = [
createMigration(
'001_create_users',
async (db) => {
await db.schema
.createTable('users')
.addColumn('id', 'serial', col => col.primaryKey())
.addColumn('email', 'varchar(255)', col => col.notNull().unique())
.addColumn('name', 'varchar(100)', col => col.notNull())
.addColumn('created_at', 'timestamp', col =>
col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)
)
.execute()
},
async (db) => {
await db.schema.dropTable('users').execute()
}
)
]
createMigrationWithMeta
Create a migration with metadata.
function createMigrationWithMeta(
name: string,
options: {
up: (db: Kysely<any>) => Promise<void>
down?: (db: Kysely<any>) => Promise<void>
description?: string
breaking?: boolean
estimatedDuration?: number
tags?: string[]
}
): MigrationWithMeta
defineMigrations
Define multiple migrations concisely.
function defineMigrations(definitions: MigrationDefinitions): MigrationWithMeta[]
type MigrationDefinitions = Record<string, MigrationDefinition>
Example
const migrations = defineMigrations({
'001_create_users': {
description: 'Create users table',
up: async (db) => {
await db.schema.createTable('users')./* ... */.execute()
},
down: async (db) => {
await db.schema.dropTable('users').execute()
}
},
'002_add_posts': {
description: 'Create posts table',
breaking: false,
up: async (db) => { /* ... */ },
down: async (db) => { /* ... */ }
}
})
Migration Runner
createMigrationRunner
Create a migration runner.
function createMigrationRunner(
db: Kysely<any>,
migrations: Migration[],
options?: MigrationRunnerOptions
): MigrationRunner
interface MigrationRunnerOptions {
dryRun?: boolean
logger?: KyseraLogger
useTransactions?: boolean
stopOnError?: boolean
verbose?: boolean
}
Runner Methods
class MigrationRunner {
// Run all pending migrations
async up(): Promise<MigrationResult>
// Rollback migrations (default: 1 step)
async down(steps = 1): Promise<MigrationResult>
// Get status
async status(): Promise<MigrationStatus>
// Reset all migrations
async reset(): Promise<MigrationResult>
// Run up to specific migration
async upTo(targetName: string): Promise<MigrationResult>
// Get executed migrations
async getExecutedMigrations(): Promise<string[]>
// Manual marking
async markAsExecuted(name: string): Promise<void>
async markAsRolledBack(name: string): Promise<void>
}
MigrationResult
interface MigrationResult {
executed: string[]
skipped: string[]
failed: string[]
duration: number
dryRun: boolean
}
MigrationStatus
interface MigrationStatus {
executed: string[]
pending: string[]
total: number
}
Example
import { createMigrationRunner } from '@kysera/migrations'
const runner = createMigrationRunner(db, migrations, {
verbose: true
})
// Run all pending
const result = await runner.up()
console.log(`Executed: ${result.executed.join(', ')}`)
// Check status
const status = await runner.status()
console.log(`Pending: ${status.pending.length}`)
// Rollback last migration
await runner.down(1)
// Reset all
await runner.reset()
One-Liner Functions
runMigrations
async function runMigrations(
db: Kysely<any>,
migrations: Migration[],
options?: MigrationRunnerOptions
): Promise<MigrationResult>
rollbackMigrations
async function rollbackMigrations(
db: Kysely<any>,
migrations: Migration[],
steps?: number,
options?: MigrationRunnerOptions
): Promise<MigrationResult>
getMigrationStatus
async function getMigrationStatus(
db: Kysely<any>,
migrations: Migration[],
options?: Pick<MigrationRunnerOptions, 'logger' | 'verbose'>
): Promise<MigrationStatus>
Example
// Quick usage
await runMigrations(db, migrations)
await rollbackMigrations(db, migrations, 1)
const status = await getMigrationStatus(db, migrations)
Dry Run
Preview migrations without executing:
const result = await runMigrations(db, migrations, { dryRun: true })
console.log('Would execute:', result.executed)
Plugin System
MigrationPlugin
interface MigrationPlugin {
name: string
version: string
onInit?(runner: MigrationRunner): Promise<void> | void
beforeMigration?(migration: Migration, operation: 'up' | 'down'): Promise<void> | void
afterMigration?(migration: Migration, operation: 'up' | 'down', duration: number): Promise<void> | void
onMigrationError?(migration: Migration, operation: 'up' | 'down', error: unknown): Promise<void> | void
}
Built-in Plugins
// Logging plugin
const loggingPlugin = createLoggingPlugin(logger)
// Metrics plugin
const metricsPlugin = createMetricsPlugin()
Usage
import { createMigrationRunnerWithPlugins, createLoggingPlugin } from '@kysera/migrations'
const runner = await createMigrationRunnerWithPlugins(db, migrations, {
plugins: [createLoggingPlugin()]
})
Error Handling
import { MigrationError } from '@kysera/migrations'
try {
await runner.up()
} catch (error) {
if (error instanceof MigrationError) {
console.error(`Migration ${error.migrationName} failed:`, error.cause)
console.error(`Operation: ${error.operation}`)
}
}
Best Practices
1. Name Migrations Sequentially
001_create_users.ts
002_create_posts.ts
003_add_email_to_users.ts
2. Always Include Down Migration
createMigration(
'001_create_users',
async (db) => { /* up */ },
async (db) => { /* down - always include! */ }
)
3. Use Dry Run First
// Preview changes with dry run runner
const dryRunner = createMigrationRunner(db, migrations, { dryRun: true })
await dryRunner.up()
// Then execute with normal runner
const runner = createMigrationRunner(db, migrations)
await runner.up()
4. Test Migrations
it('should migrate up and down', async () => {
await runner.up()
const upStatus = await runner.status()
expect(upStatus.pending).toHaveLength(0)
await runner.down()
const downStatus = await runner.status()
expect(downStatus.pending).toHaveLength(migrations.length)
})