@gati-framework/runtime
Version:
Gati runtime execution engine for running handler-based applications
301 lines • 9.66 kB
JavaScript
/**
* 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