Files
platform/apps/api/src/database/migrate.ts
T

107 lines
3.1 KiB
TypeScript

import * as path from 'path'
import { promises as fs } from 'fs'
import { FileMigrationProvider, Migrator, type MigrationResultSet } from 'kysely/migration'
import { db } from './db'
import { argv } from 'bun'
import { env, log } from '../util'
type HandlerResult = {
message: string;
};
function processResults(label: string, { error, results }: MigrationResultSet): string {
let message = "";
results?.forEach((it) => {
if (it.status === 'Success') {
message = `migration ${label} succeeded: "${it.migrationName}"`;
} else if (it.status === 'Error') {
throw new Error(`migration ${label} failed: "${it.migrationName}"`);
}
});
if (error) throw error;
return message;
}
const handlers: Record<string, (migrator: Migrator) => Promise<HandlerResult>> = {
"up": async function (migrator: Migrator) {
const resultSet = await migrator.migrateUp();
const message = processResults("up", resultSet);
return { message };
},
"down": async function (migrator: Migrator) {
const resultSet = await migrator.migrateDown();
const message = processResults("down", resultSet);
return { message };
},
"latest": async function (migrator: Migrator) {
const resultSet = await migrator.migrateToLatest();
const message = processResults("to latest", resultSet);
return { message };
},
"rollback": async function (migrator: Migrator) {
let count = 0;
while (true) {
const resultSet = await migrator.migrateDown();
if (!resultSet.results?.length) break;
processResults("rollback", resultSet);
++count;
}
return { message: `rolled back ${count} migration(s)` };
},
"reset": async function (migrator: Migrator) {
if (env.NODE_ENV !== "development") throw new Error("Development only command");
if (typeof handlers["rollback"] !== "function") throw new Error("Missing rollback handler");
await handlers["rollback"](migrator);
if (typeof handlers["latest"] !== "function") throw new Error("Missing latest handler");
await handlers["latest"](migrator);
return { message: "database fully reset" };
}
};
// Main
(async () => {
const command = argv[2] ?? "";
if (!handlers[command]) throw new Error("Unrecognized command");
const answer = prompt(`Run "${command}" migration against ${env.DATABASE_URL}? (y/N)`);
if (answer?.trim().toLowerCase() !== "y") {
log.info("Aborted");
await db.destroy();
return;
}
const migrator = new Migrator({
db,
provider: new FileMigrationProvider({
fs,
path,
// This needs to be an absolute path.
migrationFolder: path.join(__dirname, "migrations"),
}),
});
try {
const result = await handlers[command](migrator);
log.info({ name: command, result }, "finished");
}
catch (err) {
log.error({ name: command, err }, "failed");
process.exit(1)
}
finally {
await db.destroy()
}
})();