Files
onebox/ts/database/migrations/migration-runner.ts
T

107 lines
4.1 KiB
TypeScript
Raw Normal View History

2026-03-15 12:45:13 +00:00
/**
* 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 { Migration013AppTemplateVersion } from './migration-013-app-template-version.ts';
import { Migration014ContainerArchive } from './migration-014-containerarchive.ts';
import { Migration015SmartProxyPlatformService } from './migration-015-smartproxy-platform-service.ts';
2026-03-15 12:45:13 +00:00
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(),
new Migration013AppTemplateVersion(),
new Migration014ContainerArchive(),
new Migration015SmartProxyPlatformService(),
2026-03-15 12:45:13 +00:00
].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(),
]);
}
}