Files
crowd-funder-from-jason/src/services/migrationService.ts
Matt Raymer ee441d1aea refactor(db): improve type safety in migration system
- 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.
2025-05-25 23:09:53 -04:00

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();