import { promises as fs } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { FileMigrationProvider, Migrator } from 'kysely/migration'; import type { Database } from './database.ts'; import type { Logger } from '../../utils/logger.ts'; /** * Applies versioned schema migrations from the on-disk migrations folder. */ export class MigrationService { private readonly debug: Logger; private readonly migrator: Migrator; /** * @param database - Open database whose schema will be migrated. * @param debug - Root logger extended with a migrations namespace. */ constructor(database: Database, debug: Logger) { // Extend the debug logger to include the migrations namespace. this.debug = debug.extend('migrations'); // Resolve the migrations directory relative to this module file. const currentFilePath = fileURLToPath(import.meta.url); const currentDirectory = path.dirname(currentFilePath); const migrationsPath = path.join(currentDirectory, 'migrations'); // Create the migrator backed by filesystem migration files. this.migrator = new Migrator({ db: database.db, provider: new FileMigrationProvider({ fs, path, migrationFolder: migrationsPath, }), }); } /** * Migrates the database to the latest version. * * Throws when any pending migration fails so startup can abort cleanly. */ async migrateToLatest(): Promise { this.debug('migrating database to latest'); const { error } = await this.migrator.migrateToLatest(); if (error) { const errorInstance = error instanceof Error ? error : new Error(String(error)); this.debug('migration failed: %O', errorInstance); throw errorInstance; } this.debug('database migrations complete'); } }