"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.migrate = exports.readMigrations = void 0;
const fs = require("fs");
const path = require("path");
async function readMigrations(migrationPath) {
    const migrationsPath = migrationPath || path.join(process.cwd(), 'migrations');
    const location = path.resolve(migrationsPath);
    // Get the list of migration files, for example:
    //   { id: 1, name: 'initial', filename: '001-initial.sql' }
    //   { id: 2, name: 'feature', filename: '002-feature.sql' }
    const migrationFiles = await new Promise((resolve, reject) => {
        fs.readdir(location, (err, files) => {
            if (err) {
                return reject(err);
            }
            resolve(files
                .map(x => x.match(/^(\d+).(.*?)\.sql$/))
                .filter(x => x !== null)
                .map(x => ({ id: Number(x[1]), name: x[2], filename: x[0] }))
                .sort((a, b) => Math.sign(a.id - b.id)));
        });
    });
    if (!migrationFiles.length) {
        throw new Error(`No migration files found in '${location}'.`);
    }
    // Get the list of migrations, for example:
    //   { id: 1, name: 'initial', filename: '001-initial.sql', up: ..., down: ... }
    //   { id: 2, name: 'feature', filename: '002-feature.sql', up: ..., down: ... }
    return Promise.all(migrationFiles.map(migration => new Promise((resolve, reject) => {
        const filename = path.join(location, migration.filename);
        fs.readFile(filename, 'utf-8', (err, data) => {
            if (err) {
                return reject(err);
            }
            const [up, down] = data.split(/^--\s+?down\b/im);
            const migrationData = migration;
            migrationData.up = up.replace(/^-- .*?$/gm, '').trim(); // Remove comments
            migrationData.down = down ? down.trim() : ''; // and trim whitespaces
            resolve(migrationData);
        });
    })));
}
exports.readMigrations = readMigrations;
/**
 * Migrates database schema to the latest version
 */
async function migrate(db, config = {}) {
    config.force = config.force || false;
    config.table = config.table || 'migrations';
    const { force, table } = config;
    const migrations = config.migrations
        ? config.migrations
        : await readMigrations(config.migrationsPath);
    // Create a database table for migrations meta data if it doesn't exist
    await db.run(`CREATE TABLE IF NOT EXISTS "${table}" (
  id   INTEGER PRIMARY KEY,
  name TEXT    NOT NULL,
  up   TEXT    NOT NULL,
  down TEXT    NOT NULL
)`);
    // Get the list of already applied migrations
    let dbMigrations = await db.all(`SELECT id, name, up, down FROM "${table}" ORDER BY id ASC`);
    // Undo migrations that exist only in the database but not in files,
    // also undo the last migration if the `force` option is enabled.
    const lastMigration = migrations[migrations.length - 1];
    for (const migration of dbMigrations
        .slice()
        .sort((a, b) => Math.sign(b.id - a.id))) {
        if (!migrations.some(x => x.id === migration.id) ||
            (force && migration.id === lastMigration.id)) {
            await db.run('BEGIN');
            try {
                await db.exec(migration.down);
                await db.run(`DELETE FROM "${table}" WHERE id = ?`, migration.id);
                await db.run('COMMIT');
                dbMigrations = dbMigrations.filter(x => x.id !== migration.id);
            }
            catch (err) {
                await db.run('ROLLBACK');
                throw err;
            }
        }
        else {
            break;
        }
    }
    // Apply pending migrations
    const lastMigrationId = dbMigrations.length
        ? dbMigrations[dbMigrations.length - 1].id
        : 0;
    for (const migration of migrations) {
        if (migration.id > lastMigrationId) {
            await db.run('BEGIN');
            try {
                await db.exec(migration.up);
                await db.run(`INSERT INTO "${table}" (id, name, up, down) VALUES (?, ?, ?, ?)`, migration.id, migration.name, migration.up, migration.down);
                await db.run('COMMIT');
            }
            catch (err) {
                await db.run('ROLLBACK');
                throw err;
            }
        }
    }
}
exports.migrate = migrate;
//# sourceMappingURL=migrate.js.map