Files
onebox/ts/database/migrations/migration-runner.ts
Juergen Kunz 49998c4c32
Some checks failed
CI / Type Check & Lint (push) Failing after 36s
CI / Build Test (Current Platform) (push) Failing after 1m8s
CI / Build All Platforms (push) Successful in 8m29s
add migration
2026-03-15 12:45:13 +00:00

101 lines
3.7 KiB
TypeScript

/**
* Migration runner - discovers, orders, and executes database migrations.
* Mirrors the pattern from @serve.zone/nupst.
*/
import type { TQueryFunction } from '../types.ts';
import { logger } from '../../logging.ts';
import { getErrorMessage } from '../../utils/error.ts';
import { Migration001Initial } from './migration-001-initial.ts';
import { Migration002TimestampsToReal } from './migration-002-timestamps-to-real.ts';
import { Migration003DomainManagement } from './migration-003-domain-management.ts';
import { Migration004RegistryColumns } from './migration-004-registry-columns.ts';
import { Migration005RegistryTokens } from './migration-005-registry-tokens.ts';
import { Migration006DropRegistryToken } from './migration-006-drop-registry-token.ts';
import { Migration007PlatformServices } from './migration-007-platform-services.ts';
import { Migration008CertPemContent } from './migration-008-cert-pem-content.ts';
import { Migration009BackupSystem } from './migration-009-backup-system.ts';
import { Migration010BackupSchedules } from './migration-010-backup-schedules.ts';
import { Migration011ScopeColumns } from './migration-011-scope-columns.ts';
import { Migration012GfsRetention } from './migration-012-gfs-retention.ts';
import type { BaseMigration } from './base-migration.ts';
export class MigrationRunner {
private query: TQueryFunction;
private migrations: BaseMigration[];
constructor(query: TQueryFunction) {
this.query = query;
// Register all migrations in order
this.migrations = [
new Migration001Initial(),
new Migration002TimestampsToReal(),
new Migration003DomainManagement(),
new Migration004RegistryColumns(),
new Migration005RegistryTokens(),
new Migration006DropRegistryToken(),
new Migration007PlatformServices(),
new Migration008CertPemContent(),
new Migration009BackupSystem(),
new Migration010BackupSchedules(),
new Migration011ScopeColumns(),
new Migration012GfsRetention(),
].sort((a, b) => a.version - b.version);
}
/** Run all pending migrations */
run(): void {
try {
const currentVersion = this.getMigrationVersion();
logger.info(`Current database migration version: ${currentVersion}`);
let applied = 0;
for (const migration of this.migrations) {
if (migration.version <= currentVersion) continue;
logger.info(`Running ${migration.getName()}...`);
migration.up(this.query);
this.setMigrationVersion(migration.version);
logger.success(`${migration.getName()} completed`);
applied++;
}
if (applied > 0) {
logger.success(`Applied ${applied} migration(s)`);
}
} catch (error) {
logger.error(`Migration failed: ${getErrorMessage(error)}`);
if (error instanceof Error && error.stack) {
logger.error(`Stack: ${error.stack}`);
}
throw error;
}
}
/** Get current migration version from the migrations table */
private getMigrationVersion(): number {
try {
const result = this.query<{ version?: number | null; [key: number]: unknown }>(
'SELECT MAX(version) as version FROM migrations',
);
if (result.length === 0) return 0;
const versionValue = result[0].version ?? (result[0] as Record<number, unknown>)[0];
return versionValue !== null && versionValue !== undefined ? Number(versionValue) : 0;
} catch {
// Table might not exist yet on fresh databases
return 0;
}
}
/** Record a migration version as applied */
private setMigrationVersion(version: number): void {
this.query('INSERT INTO migrations (version, applied_at) VALUES (?, ?)', [
version,
Date.now(),
]);
}
}