UNPKG

@gati-framework/runtime

Version:

Gati runtime execution engine for running handler-based applications

301 lines 9.66 kB
/** * Manages database schema versions alongside TSV versions */ export class DBSchemaManager { schemas = new Map(); activeSchemas = new Set(); schemaUsage = new Map(); // Track which TSVs use each schema config; constructor(config) { this.config = { useTransaction: true, migrationTimeout: 30000, // 30 seconds default ...config, }; } /** * Register a schema version from TSV metadata */ registerSchema(tsv, handlerPath, schemaMetadata) { const { version: schemaVersion } = schemaMetadata; // Track schema usage if (!this.schemaUsage.has(schemaVersion)) { this.schemaUsage.set(schemaVersion, new Set()); } this.schemaUsage.get(schemaVersion).add(tsv); // Register schema if not already registered if (!this.schemas.has(schemaVersion)) { this.schemas.set(schemaVersion, { schemaVersion, tsv, handlerPath, status: 'pending', }); } } /** * Apply migrations for a schema version */ async applyMigrations(schemaVersion, metadata) { const startTime = Date.now(); const schema = this.schemas.get(schemaVersion); if (!schema) { return { success: false, schemaVersion, executedMigrations: [], error: `Schema version ${schemaVersion} not registered`, duration: Date.now() - startTime, }; } // Check if already applied if (schema.status === 'applied') { return { success: true, schemaVersion, executedMigrations: [], duration: Date.now() - startTime, }; } const executedMigrations = []; try { // Execute migrations for (const migration of metadata.migrations) { const success = await this.executeMigrationWithTimeout(migration); if (!success) { throw new Error(`Migration failed: ${migration.substring(0, 100)}...`); } executedMigrations.push(migration); } // Mark as applied schema.status = 'applied'; schema.appliedAt = Date.now(); this.activeSchemas.add(schemaVersion); // Callback if (this.config.onSchemaApplied) { this.config.onSchemaApplied(schemaVersion, schema.tsv); } return { success: true, schemaVersion, executedMigrations, duration: Date.now() - startTime, }; } catch (error) { schema.status = 'failed'; schema.error = error instanceof Error ? error.message : String(error); return { success: false, schemaVersion, executedMigrations, error: schema.error, duration: Date.now() - startTime, }; } } /** * Rollback a schema version */ async rollbackSchema(schemaVersion, metadata) { const startTime = Date.now(); const schema = this.schemas.get(schemaVersion); if (!schema) { return { success: false, schemaVersion, executedRollbacks: [], error: `Schema version ${schemaVersion} not registered`, duration: Date.now() - startTime, }; } // Check if not applied if (schema.status !== 'applied') { return { success: true, schemaVersion, executedRollbacks: [], duration: Date.now() - startTime, }; } const executedRollbacks = []; try { // Execute rollbacks in reverse order for (const rollback of [...metadata.rollback].reverse()) { const success = await this.executeRollbackWithTimeout(rollback); if (!success) { throw new Error(`Rollback failed: ${rollback.substring(0, 100)}...`); } executedRollbacks.push(rollback); } // Mark as rolled back schema.status = 'rolled_back'; schema.rolledBackAt = Date.now(); this.activeSchemas.delete(schemaVersion); // Callback if (this.config.onSchemaRolledBack) { this.config.onSchemaRolledBack(schemaVersion, schema.tsv); } return { success: true, schemaVersion, executedRollbacks, duration: Date.now() - startTime, }; } catch (error) { schema.status = 'failed'; schema.error = error instanceof Error ? error.message : String(error); return { success: false, schemaVersion, executedRollbacks, error: schema.error, duration: Date.now() - startTime, }; } } /** * Check if a schema is active */ isSchemaActive(schemaVersion) { return this.activeSchemas.has(schemaVersion); } /** * Check if a schema is used by other versions */ isSchemaUsedByOthers(schemaVersion, excludeTsv) { const usage = this.schemaUsage.get(schemaVersion); if (!usage) return false; if (excludeTsv) { return Array.from(usage).some(tsv => tsv !== excludeTsv); } return usage.size > 0; } /** * Get all TSVs using a schema version */ getSchemaUsage(schemaVersion) { return Array.from(this.schemaUsage.get(schemaVersion) || []); } /** * Activate a version (apply schema if needed) */ async activateVersion(tsv, handlerPath, schemaMetadata) { if (!schemaMetadata) { return null; } const { version: schemaVersion } = schemaMetadata; // Register schema this.registerSchema(tsv, handlerPath, schemaMetadata); // Apply migrations if not already active if (!this.isSchemaActive(schemaVersion)) { return await this.applyMigrations(schemaVersion, schemaMetadata); } return null; } /** * Deactivate a version (rollback schema if no other versions use it) */ async deactivateVersion(tsv, schemaMetadata) { if (!schemaMetadata) { return null; } const { version: schemaVersion } = schemaMetadata; // Remove from usage tracking const usage = this.schemaUsage.get(schemaVersion); if (usage) { usage.delete(tsv); } // Only rollback if no other versions use this schema if (!this.isSchemaUsedByOthers(schemaVersion)) { return await this.rollbackSchema(schemaVersion, schemaMetadata); } return null; } /** * Get schema status */ getSchemaStatus(schemaVersion) { return this.schemas.get(schemaVersion); } /** * Get all active schemas */ getActiveSchemas() { return Array.from(this.activeSchemas); } /** * Get all schemas */ getAllSchemas() { return Array.from(this.schemas.values()); } /** * Check if schemas are compatible */ areCompatible(schema1, schema2, metadata) { if (schema1 === schema2) return true; const compatibleWith = metadata.compatibleWith || []; return compatibleWith.includes(schema1) || compatibleWith.includes(schema2); } /** * Get statistics */ getStatistics() { let pending = 0; let failed = 0; let rolledBack = 0; for (const schema of this.schemas.values()) { switch (schema.status) { case 'pending': pending++; break; case 'failed': failed++; break; case 'rolled_back': rolledBack++; break; } } return { totalSchemas: this.schemas.size, activeSchemas: this.activeSchemas.size, pendingSchemas: pending, failedSchemas: failed, rolledBackSchemas: rolledBack, }; } /** * Clear all schemas (for testing) */ clear() { this.schemas.clear(); this.activeSchemas.clear(); this.schemaUsage.clear(); } /** * Execute migration with timeout */ async executeMigrationWithTimeout(script) { const timeout = this.config.migrationTimeout || 30000; return Promise.race([ this.config.executeMigration(script), new Promise((_, reject) => setTimeout(() => reject(new Error('Migration timeout')), timeout)), ]); } /** * Execute rollback with timeout */ async executeRollbackWithTimeout(script) { const timeout = this.config.migrationTimeout || 30000; return Promise.race([ this.config.executeRollback(script), new Promise((_, reject) => setTimeout(() => reject(new Error('Rollback timeout')), timeout)), ]); } } //# sourceMappingURL=db-schema.js.map