UNPKG

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
"use strict"; /** * 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