62 lines
1.7 KiB
TypeScript
62 lines
1.7 KiB
TypeScript
import { promises as fs } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
import type { Pool } from "pg";
|
|
|
|
export type AppliedMigration = {
|
|
id: string;
|
|
};
|
|
|
|
type MigrationFile = {
|
|
id: string;
|
|
sql: string;
|
|
};
|
|
|
|
async function loadMigrationFiles(migrationsDir: string): Promise<MigrationFile[]> {
|
|
const files = (await fs.readdir(migrationsDir)).filter((file) => file.endsWith(".sql")).sort();
|
|
|
|
return Promise.all(
|
|
files.map(async (file) => ({
|
|
id: file,
|
|
sql: await fs.readFile(path.join(migrationsDir, file), "utf8"),
|
|
}))
|
|
);
|
|
}
|
|
|
|
export async function runMigrations(pool: Pool, migrationsDir = path.resolve(process.cwd(), "migrations")): Promise<AppliedMigration[]> {
|
|
const migrations = await loadMigrationFiles(migrationsDir);
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
id text PRIMARY KEY,
|
|
applied_at timestamptz NOT NULL DEFAULT now()
|
|
)
|
|
`);
|
|
|
|
const appliedRows = await client.query<{ id: string }>("SELECT id FROM schema_migrations");
|
|
const appliedIds = new Set(appliedRows.rows.map((row) => row.id));
|
|
const newlyApplied: AppliedMigration[] = [];
|
|
|
|
for (const migration of migrations) {
|
|
if (appliedIds.has(migration.id)) {
|
|
continue;
|
|
}
|
|
|
|
await client.query(migration.sql);
|
|
await client.query("INSERT INTO schema_migrations(id) VALUES ($1)", [migration.id]);
|
|
newlyApplied.push({ id: migration.id });
|
|
}
|
|
|
|
await client.query("COMMIT");
|
|
return newlyApplied;
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|