supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
499 lines • 20.3 kB
JavaScript
;
/**
* Constraint-Aware Workflow Execution Engine
* Pre-validates and executes database operations based on discovered constraints
* Part of supa-seed v2.2.0 constraint-aware architecture
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConstraintAwareExecutor = void 0;
const logger_1 = require("../../core/utils/logger");
const constraint_discovery_engine_1 = require("./constraint-discovery-engine");
class ConstraintAwareExecutor {
constructor(client) {
this.constraints = null;
this.client = client;
this.constraintEngine = new constraint_discovery_engine_1.ConstraintDiscoveryEngine(client);
}
/**
* Execute a workflow with full constraint awareness
*/
async executeWorkflow(workflow, inputData) {
logger_1.Logger.info('🚀 Starting constraint-aware workflow execution...');
const startTime = Date.now();
// Initialize execution context
const context = {
inputData,
generatedData: {},
stepResults: {},
constraints: await this.getOrDiscoverConstraints(workflow),
currentStep: 0,
totalSteps: workflow.steps.length
};
const result = {
success: false,
stepsExecuted: [],
stepsSkipped: [],
constraintViolations: [],
autoFixesApplied: [],
executionSummary: {
totalSteps: workflow.steps.length,
successfulSteps: 0,
skippedSteps: 0,
failedSteps: 0,
constraintViolationsFound: 0,
autoFixesApplied: 0,
duration: 0
}
};
try {
// Pre-execution validation if enabled
if (workflow.validation.preExecution) {
await this.preValidateWorkflow(workflow, context);
}
// Execute each step with constraint validation
for (const step of workflow.steps) {
context.currentStep++;
logger_1.Logger.debug(`Executing step ${context.currentStep}/${context.totalSteps}: ${step.id}`);
const stepResult = await this.executeStep(step, context);
if (stepResult.success) {
result.stepsExecuted.push(stepResult);
result.executionSummary.successfulSteps++;
context.stepResults[step.id] = stepResult;
}
else {
if (step.required && workflow.errorHandling.type === 'fail_fast') {
logger_1.Logger.error(`Required step ${step.id} failed, stopping execution`);
result.executionSummary.failedSteps++;
break;
}
else if (stepResult.constraintViolations.length > 0) {
result.stepsSkipped.push({
stepId: step.id,
reason: 'Constraint violations detected',
constraintViolations: stepResult.constraintViolations
});
result.executionSummary.skippedSteps++;
}
else {
result.executionSummary.failedSteps++;
}
}
// Collect violations and auto-fixes
result.constraintViolations.push(...stepResult.constraintViolations);
result.autoFixesApplied.push(...stepResult.autoFixesApplied);
}
// Post-execution validation if enabled
if (workflow.validation.postExecution) {
await this.postValidateWorkflow(workflow, context, result);
}
// Determine overall success
result.success = result.executionSummary.failedSteps === 0 &&
result.executionSummary.successfulSteps > 0;
const duration = Date.now() - startTime;
result.executionSummary.duration = duration;
result.executionSummary.constraintViolationsFound = result.constraintViolations.length;
result.executionSummary.autoFixesApplied = result.autoFixesApplied.length;
logger_1.Logger.success(`✅ Workflow execution completed in ${duration}ms`);
this.logExecutionSummary(result.executionSummary);
}
catch (error) {
logger_1.Logger.error('Workflow execution failed:', error);
// Attempt rollback if enabled
if (workflow.rollback.enabled) {
await this.rollbackSteps(result.stepsExecuted, workflow.rollback);
}
throw error;
}
return result;
}
/**
* Execute a single workflow step with constraint validation
*/
async executeStep(step, context) {
const startTime = Date.now();
const stepResult = {
stepId: step.id,
success: false,
warnings: [],
constraintViolations: [],
autoFixesApplied: [],
duration: 0
};
try {
// Step 1: Validate step conditions
const conditionValidation = await this.validateStepConditions(step, context);
if (!conditionValidation.valid) {
stepResult.constraintViolations.push(...conditionValidation.violations);
// Attempt auto-fixes if enabled
if (step.autoFixes && step.autoFixes.length > 0) {
const autoFixResults = await this.applyAutoFixes(conditionValidation.violations, step, context);
stepResult.autoFixesApplied.push(...autoFixResults);
// Re-validate after auto-fixes
const revalidation = await this.validateStepConditions(step, context);
if (!revalidation.valid) {
logger_1.Logger.warn(`Step ${step.id} still invalid after auto-fixes`);
stepResult.success = false;
stepResult.duration = Date.now() - startTime;
return stepResult;
}
}
else {
logger_1.Logger.warn(`Step ${step.id} failed validation, no auto-fixes available`);
stepResult.success = false;
stepResult.duration = Date.now() - startTime;
return stepResult;
}
}
// Step 2: Execute the actual database operation
switch (step.operation) {
case 'insert':
stepResult.data = await this.executeInsert(step, context);
break;
case 'update':
stepResult.data = await this.executeUpdate(step, context);
break;
case 'validate':
stepResult.data = await this.executeValidation(step, context);
break;
case 'skip':
logger_1.Logger.info(`Step ${step.id} explicitly skipped`);
stepResult.data = { skipped: true };
break;
default:
throw new Error(`Unknown operation: ${step.operation}`);
}
stepResult.success = true;
logger_1.Logger.debug(`✅ Step ${step.id} completed successfully`);
}
catch (error) {
logger_1.Logger.error(`Step ${step.id} failed:`, error);
stepResult.error = error.message;
// Handle error based on step configuration
await this.handleStepError(step, context, error, stepResult);
}
stepResult.duration = Date.now() - startTime;
return stepResult;
}
/**
* Validate step conditions against discovered constraints
*/
async validateStepConditions(step, context) {
const violations = [];
if (!step.conditions || step.conditions.length === 0) {
return { valid: true, violations: [] };
}
for (const condition of step.conditions) {
const violation = await this.validateCondition(condition, step, context);
if (violation) {
violations.push(violation);
}
}
return {
valid: violations.length === 0,
violations
};
}
/**
* Validate a single condition
*/
async validateCondition(condition, step, context) {
try {
switch (condition.type) {
case 'exists':
return await this.validateExistsCondition(condition, step, context);
case 'equals':
return await this.validateEqualsCondition(condition, step, context);
case 'custom':
return await this.validateCustomCondition(condition, step, context);
case 'business_rule':
return await this.validateBusinessRuleCondition(condition, step, context);
default:
logger_1.Logger.warn(`Unknown condition type: ${condition.type}`);
return null;
}
}
catch (error) {
logger_1.Logger.error(`Condition validation failed: ${error.message}`);
return null;
}
}
/**
* Validate EXISTS condition
*/
async validateExistsCondition(condition, step, context) {
if (!condition.table || !condition.field) {
return null;
}
const { data, error } = await this.client
.from(condition.table)
.select('id')
.eq(condition.field, condition.value)
.limit(1);
if (error) {
logger_1.Logger.warn(`EXISTS validation error: ${error.message}`);
return null;
}
if (!data || data.length === 0) {
const businessRule = context.constraints.businessRules.find(r => r.table === condition.table &&
r.condition.includes(condition.field));
return {
rule: businessRule || this.createDummyRule(condition, step),
violationType: 'dependency',
message: `Required dependency not found: ${condition.table}.${condition.field} = ${condition.value}`,
suggestedFix: businessRule?.autoFix,
canAutoFix: businessRule?.autoFix !== undefined
};
}
return null;
}
/**
* Validate EQUALS condition
*/
async validateEqualsCondition(condition, step, context) {
// This would check that a field equals a specific value
// Implementation depends on the specific condition structure
return null;
}
/**
* Validate custom SQL condition
*/
async validateCustomCondition(condition, step, context) {
if (!condition.customSQL) {
return null;
}
try {
const { data, error } = await this.client.rpc('exec_sql', {
sql: condition.customSQL,
params: []
});
if (error) {
logger_1.Logger.warn(`Custom SQL validation error: ${error.message}`);
return null;
}
// Assume custom SQL returns boolean or count
const result = data && Array.isArray(data) && data.length > 0 ? data[0] : false;
if (!result) {
return {
rule: this.createDummyRule(condition, step),
violationType: 'validation',
message: condition.description || 'Custom validation failed',
canAutoFix: false
};
}
}
catch (error) {
logger_1.Logger.warn(`Custom SQL execution failed: ${error.message}`);
return null;
}
return null;
}
/**
* Validate business rule condition
*/
async validateBusinessRuleCondition(condition, step, context) {
if (!condition.businessRuleId) {
return null;
}
const businessRule = context.constraints.businessRules.find(r => r.id === condition.businessRuleId);
if (!businessRule) {
return null;
}
// Validate the business rule based on its type and condition
const isValid = await this.validateBusinessRule(businessRule, step, context);
if (!isValid) {
return {
rule: businessRule,
violationType: 'business_logic',
message: businessRule.errorMessage || `Business rule violation: ${businessRule.condition}`,
suggestedFix: businessRule.autoFix,
canAutoFix: businessRule.autoFix !== undefined
};
}
return null;
}
/**
* Validate a business rule
*/
async validateBusinessRule(rule, step, context) {
// This would implement the actual business rule validation logic
// For now, return true (would be implemented based on rule.sqlPattern)
return true;
}
/**
* Apply auto-fixes for constraint violations
*/
async applyAutoFixes(violations, step, context) {
const appliedFixes = [];
for (const violation of violations) {
if (violation.canAutoFix && violation.suggestedFix) {
try {
const success = await this.applyAutoFix(violation.suggestedFix, step, context);
appliedFixes.push({
originalViolation: violation,
fixApplied: violation.suggestedFix,
success
});
}
catch (error) {
logger_1.Logger.warn(`Auto-fix failed for ${violation.rule.name}: ${error.message}`);
appliedFixes.push({
originalViolation: violation,
fixApplied: violation.suggestedFix,
success: false
});
}
}
}
return appliedFixes;
}
/**
* Apply a single auto-fix
*/
async applyAutoFix(fix, step, context) {
switch (fix.type) {
case 'set_field':
return await this.applySetFieldFix(fix, step, context);
case 'create_dependency':
return await this.applyCreateDependencyFix(fix, step, context);
case 'skip_operation':
return await this.applySkipOperationFix(fix, step, context);
case 'modify_workflow':
return await this.applyModifyWorkflowFix(fix, step, context);
default:
logger_1.Logger.warn(`Unknown auto-fix type: ${fix.type}`);
return false;
}
}
/**
* Apply set field auto-fix
*/
async applySetFieldFix(fix, step, context) {
if (!fix.action.field || !fix.action.table) {
return false;
}
// Update the step's field mappings to include the fix
const fieldMapping = step.fields.find(f => f.name === fix.action.field);
if (fieldMapping) {
fieldMapping.value = fix.action.value;
}
else {
step.fields.push({
name: fix.action.field,
source: 'auto_fix',
value: fix.action.value,
required: true
});
}
logger_1.Logger.info(`✅ Applied set_field fix: ${fix.action.field} = ${fix.action.value}`);
return true;
}
/**
* Execute database operations
*/
async executeInsert(step, context) {
const insertData = this.buildInsertData(step, context);
const { data, error } = await this.client
.from(step.table)
.insert(insertData)
.select()
.single();
if (error) {
throw new Error(`Insert failed: ${error.message}`);
}
// Store rollback data
if (data) {
context.stepResults[step.id] = {
...context.stepResults[step.id],
rollbackData: { operation: 'delete', table: step.table, id: data.id }
};
}
return data;
}
/**
* Build insert data from field mappings
*/
buildInsertData(step, context) {
const data = {};
for (const field of step.fields) {
let value = field.value;
if (field.source.startsWith('input.')) {
const inputField = field.source.replace('input.', '');
value = context.inputData[inputField];
}
else if (field.source.startsWith('generated.')) {
const generatedField = field.source.replace('generated.', '');
value = context.generatedData[generatedField] || this.generateValue(generatedField);
}
else if (field.source.includes('.')) {
// Reference to another step's result
value = this.resolveStepReference(field.source, context);
}
if (value !== undefined) {
data[field.name] = value;
}
}
return data;
}
/**
* Utility methods
*/
async getOrDiscoverConstraints(workflow) {
if (this.constraints) {
return this.constraints;
}
// Extract table names from workflow steps
const tableNames = [...new Set(workflow.steps.map(step => step.table))];
this.constraints = await this.constraintEngine.discoverConstraints(tableNames);
return this.constraints;
}
createDummyRule(condition, step) {
return {
id: `dummy_${step.id}_${Date.now()}`,
name: 'Generated Rule',
type: 'validation',
table: step.table,
condition: condition.description,
action: 'deny',
confidence: 0.5,
sqlPattern: condition.customSQL || '',
dependencies: []
};
}
generateValue(field) {
// Generate default values for common fields
switch (field) {
case 'id': return crypto.randomUUID();
case 'created_at': return new Date().toISOString();
case 'updated_at': return new Date().toISOString();
default: return null;
}
}
resolveStepReference(source, context) {
// Parse references like "create_user.id" or "auth_user.id"
const [stepId, field] = source.split('.');
const stepResult = context.stepResults[stepId];
return stepResult?.data?.[field];
}
logExecutionSummary(summary) {
logger_1.Logger.info('📊 Execution Summary:');
logger_1.Logger.info(` Total steps: ${summary.totalSteps}`);
logger_1.Logger.info(` Successful: ${summary.successfulSteps}`);
logger_1.Logger.info(` Skipped: ${summary.skippedSteps}`);
logger_1.Logger.info(` Failed: ${summary.failedSteps}`);
logger_1.Logger.info(` Constraint violations: ${summary.constraintViolationsFound}`);
logger_1.Logger.info(` Auto-fixes applied: ${summary.autoFixesApplied}`);
logger_1.Logger.info(` Duration: ${summary.duration}ms`);
}
// Placeholder methods for remaining operations
async executeUpdate(step, context) { return {}; }
async executeValidation(step, context) { return {}; }
async handleStepError(step, context, error, stepResult) { }
async preValidateWorkflow(workflow, context) { }
async postValidateWorkflow(workflow, context, result) { }
async rollbackSteps(steps, rollbackStrategy) { }
async applyCreateDependencyFix(fix, step, context) { return false; }
async applySkipOperationFix(fix, step, context) { return false; }
async applyModifyWorkflowFix(fix, step, context) { return false; }
}
exports.ConstraintAwareExecutor = ConstraintAwareExecutor;
//# sourceMappingURL=constraint-aware-executor.js.map