forked from jsnbuchanan/crowd-funder-for-time-pwa
- Replace any[] with SqlValue[] type for SQL parameters in runMigrations - Update import to use QueryExecResult from interfaces/database - Add proper typing for SQL parameter values (string | number | null | Uint8Array) This change improves type safety and helps catch potential SQL parameter type mismatches at compile time, reducing the risk of runtime errors or data corruption.
73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
import logger from "@/utils/logger";
|
|
import { QueryExecResult } from "../interfaces/database";
|
|
|
|
interface Migration {
|
|
name: string;
|
|
sql: string;
|
|
}
|
|
|
|
export class MigrationService {
|
|
private static instance: MigrationService;
|
|
private migrations: Migration[] = [];
|
|
|
|
private constructor() {}
|
|
|
|
static getInstance(): MigrationService {
|
|
if (!MigrationService.instance) {
|
|
MigrationService.instance = new MigrationService();
|
|
}
|
|
return MigrationService.instance;
|
|
}
|
|
|
|
async registerMigration(migration: Migration): Promise<void> {
|
|
this.migrations.push(migration);
|
|
}
|
|
|
|
async runMigrations(
|
|
sqlExec: (
|
|
sql: string,
|
|
params?: unknown[],
|
|
) => Promise<Array<QueryExecResult>>,
|
|
): Promise<void> {
|
|
// Create migrations table if it doesn't exist
|
|
await sqlExec(`
|
|
CREATE TABLE IF NOT EXISTS migrations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL UNIQUE,
|
|
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
`);
|
|
|
|
// Get list of executed migrations
|
|
const result: QueryExecResult[] = await sqlExec(
|
|
"SELECT name FROM migrations;",
|
|
);
|
|
let executedMigrations: Set<string> = new Set();
|
|
// Even with that query, the QueryExecResult may be [] (which doesn't make sense to me).
|
|
if (result.length > 0) {
|
|
const singleResult = result[0];
|
|
executedMigrations = new Set(
|
|
singleResult.values.map((row: unknown[]) => row[0]),
|
|
);
|
|
}
|
|
|
|
// Run pending migrations in order
|
|
for (const migration of this.migrations) {
|
|
if (!executedMigrations.has(migration.name)) {
|
|
try {
|
|
await sqlExec(migration.sql);
|
|
await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
|
|
migration.name,
|
|
]);
|
|
logger.log(`Migration ${migration.name} executed successfully`);
|
|
} catch (error) {
|
|
logger.error(`Error executing migration ${migration.name}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default MigrationService.getInstance();
|