2023-10-13 02:18:51 -04:00

108 lines
4.3 KiB
JavaScript

"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