UNPKG

supa-seed

Version:

A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support

424 lines 18.5 kB
"use strict"; /** * Constraint-Aware Workflow Generator * Generates executable workflows based on discovered PostgreSQL constraints * Part of supa-seed v2.2.0 constraint-aware architecture */ Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkflowGenerator = void 0; const logger_1 = require("../../core/utils/logger"); const constraint_discovery_engine_1 = require("../analysis/constraint-discovery-engine"); class WorkflowGenerator { constructor(client) { this.templates = new Map(); this.client = client; this.constraintEngine = new constraint_discovery_engine_1.ConstraintDiscoveryEngine(client); this.loadBuiltInTemplates(); } /** * Generate a complete workflow configuration from discovered constraints */ async generateWorkflowConfiguration(tableNames, options) { logger_1.Logger.info('🏗️ Generating constraint-aware workflow configuration...'); // Step 1: Discover constraints const constraints = await this.constraintEngine.discoverConstraints(tableNames); // Step 2: Build dependency graph const dependencyGraph = await this.constraintEngine.buildDependencyGraph(constraints); // Step 3: Generate user creation workflow const userCreationWorkflow = await this.generateUserCreationWorkflow(constraints, dependencyGraph, options); // Step 4: Build complete configuration const configuration = { version: '2.2.0', strategy: 'constraint-aware', workflows: { userCreation: userCreationWorkflow }, constraints: { discovery: { enabled: true, analyzeTriggers: true, parseFunctions: true, buildDependencyGraph: true, cacheResults: true }, validation: { preValidation: true, continueOnWarnings: options.constraintHandling === 'permissive', stopOnErrors: options.constraintHandling === 'strict', validateDependencies: true }, handling: { autoFix: options.enableAutoFixes, suggestFixes: true, skipInvalidOperations: options.constraintHandling === 'permissive', createDependenciesOnDemand: options.includeDependencyCreation } } }; // Step 5: Generate metadata const metadata = { generatedAt: new Date().toISOString(), constraintMetadata: constraints, dependencyGraph, generationOptions: options, confidence: this.calculateGenerationConfidence(constraints, dependencyGraph), warnings: [], recommendations: [] }; // Step 6: Add warnings and recommendations await this.addWarningsAndRecommendations(metadata, constraints, dependencyGraph); logger_1.Logger.success(`✅ Workflow configuration generated (confidence: ${(metadata.confidence * 100).toFixed(1)}%)`); return { configuration, metadata }; } /** * Generate user creation workflow based on constraints */ async generateUserCreationWorkflow(constraints, dependencyGraph, options) { logger_1.Logger.debug('Generating user creation workflow...'); // Determine creation order based on dependencies const creationOrder = dependencyGraph.creationOrder.length > 0 ? dependencyGraph.creationOrder : this.inferCreationOrder(constraints); const steps = []; let stepCounter = 0; // Generate steps in dependency order for (const tableName of creationOrder) { const tableConstraints = constraints.tables.find(t => t.tableName === tableName); if (!tableConstraints) continue; const step = await this.generateTableStep(tableName, tableConstraints, constraints, options, stepCounter++); if (step) { steps.push(step); } } // Generate validation steps if needed if (options.userCreationStrategy === 'comprehensive') { const validationSteps = await this.generateValidationSteps(constraints, options); steps.push(...validationSteps); } return { steps, errorHandling: { type: options.constraintHandling === 'strict' ? 'fail_fast' : 'graceful_degradation', maxFailures: options.constraintHandling === 'permissive' ? 10 : 1, failureThreshold: 0.8 }, rollback: { enabled: true, onCriticalFailure: true, preserveSuccessfulSteps: options.constraintHandling === 'permissive' }, validation: { preExecution: true, postExecution: options.userCreationStrategy === 'comprehensive', dependencyValidation: true } }; } /** * Generate a workflow step for a specific table */ async generateTableStep(tableName, tableConstraints, allConstraints, options, stepIndex) { // Skip system tables and auth tables (handled separately) if (tableName.startsWith('auth.') || this.isSystemTable(tableName)) { return null; } logger_1.Logger.debug(`Generating step for table: ${tableName}`); const stepId = `create_${tableName}_${stepIndex}`; // Generate field mappings based on table role and constraints const fieldMappings = await this.generateFieldMappings(tableName, tableConstraints, allConstraints, options); // Generate conditions based on business rules const conditions = await this.generateStepConditions(tableName, tableConstraints, allConstraints); // Determine if this step is required const isRequired = this.isRequiredTable(tableName, allConstraints); // Generate auto-fixes for common constraint violations const autoFixes = await this.generateAutoFixes(tableName, tableConstraints, allConstraints); return { id: stepId, table: tableName, operation: 'insert', required: isRequired, conditions, fields: fieldMappings, onError: { type: options.enableAutoFixes ? 'auto_fix' : options.constraintHandling === 'permissive' ? 'skip' : 'fail', maxRetries: options.enableAutoFixes ? 3 : 1 }, dependencies: this.extractStepDependencies(tableName, allConstraints), autoFixes: options.enableAutoFixes ? autoFixes : undefined }; } /** * Generate field mappings for a table */ async generateFieldMappings(tableName, tableConstraints, allConstraints, options) { const mappings = []; // Standard ID field mappings.push({ name: 'id', source: 'generated.uuid', required: true }); // Table-specific mappings based on discovered patterns switch (tableName) { case 'accounts': mappings.push({ name: 'email', source: 'input.email', required: true }, { name: 'name', source: 'input.name', required: false }, { name: 'is_personal_account', source: 'generated.true', required: false }); break; case 'profiles': mappings.push({ name: 'id', source: 'auth_user.id', required: true }, { name: 'email', source: 'input.email', required: false }, { name: 'username', source: 'input.username', required: false }, { name: 'name', source: 'input.name', required: false }, { name: 'bio', source: 'input.bio', required: false }, { name: 'avatar_url', source: 'input.picture_url', required: false }); break; case 'users': mappings.push({ name: 'email', source: 'input.email', required: true }, { name: 'name', source: 'input.name', required: false }, { name: 'username', source: 'input.username', required: false }); break; default: // Generic mappings for unknown tables mappings.push({ name: 'name', source: 'input.name', required: false }, { name: 'title', source: 'input.title', required: false }, { name: 'description', source: 'input.description', required: false }); } // Add timestamp fields mappings.push({ name: 'created_at', source: 'generated.now', required: false }, { name: 'updated_at', source: 'generated.now', required: false }); // Add constraint-specific field mappings for (const rule of tableConstraints.constraints || []) { const constraintMapping = this.generateConstraintFieldMapping(rule, allConstraints); if (constraintMapping) { mappings.push(constraintMapping); } } return mappings; } /** * Generate constraint-specific field mapping */ generateConstraintFieldMapping(rule, allConstraints) { // Parse the rule to extract field requirements if (rule.autoFix && rule.autoFix.type === 'set_field') { return { name: rule.autoFix.action.field, source: `constraint_fix.${rule.autoFix.action.value}`, value: rule.autoFix.action.value, required: rule.action === 'require' }; } return null; } /** * Generate step conditions based on business rules */ async generateStepConditions(tableName, tableConstraints, allConstraints) { const conditions = []; // Add business rule conditions for (const rule of tableConstraints.constraints || []) { if (rule.type === 'validation' || rule.type === 'business_logic') { conditions.push({ type: 'business_rule', businessRuleId: rule.id, description: rule.errorMessage || rule.condition }); } } // Add dependency conditions for (const dependency of tableConstraints.dependencies || []) { if (dependency.relationship === 'required') { conditions.push({ type: 'exists', table: dependency.toTable, field: 'id', description: `Requires ${dependency.toTable} to exist` }); } } // Add MakerKit-specific conditions if (tableName === 'profiles') { conditions.push({ type: 'equals', table: 'accounts', field: 'is_personal_account', value: true, description: 'Profiles can only be created for personal accounts' }); } return conditions; } /** * Generate auto-fixes for common constraint violations */ async generateAutoFixes(tableName, tableConstraints, allConstraints) { const autoFixes = []; // Extract auto-fixes from business rules for (const rule of allConstraints.businessRules) { if (rule.table === tableName && rule.autoFix) { autoFixes.push(rule.autoFix); } } return autoFixes; } /** * Generate validation steps */ async generateValidationSteps(constraints, options) { const validationSteps = []; // Generate post-creation validation steps validationSteps.push({ id: 'validate_user_creation', table: 'validation', operation: 'validate', required: false, fields: [], onError: { type: 'skip' }, conditions: [{ type: 'custom', customSQL: 'SELECT COUNT(*) > 0 FROM profiles WHERE created_at > NOW() - INTERVAL \'1 minute\'', description: 'Verify user profile was created successfully' }] }); return validationSteps; } /** * Load built-in workflow templates */ loadBuiltInTemplates() { // MakerKit v2/v3 template this.templates.set('makerkit_v2', { id: 'makerkit_v2', name: 'MakerKit v2/v3 User Creation', description: 'Standard MakerKit user creation with accounts and profiles', frameworkType: 'makerkit', steps: [ { id: 'create_auth_user', table: 'auth.users', operation: 'insert', priority: 1, fieldMappings: [ { fieldName: 'email', sourceType: 'input', required: true }, { fieldName: 'password', sourceType: 'input', required: true } ] }, { id: 'create_account', table: 'accounts', operation: 'insert', priority: 2, fieldMappings: [ { fieldName: 'id', sourceType: 'dependency', required: true }, { fieldName: 'is_personal_account', sourceType: 'generated', required: true } ] }, { id: 'create_profile', table: 'profiles', operation: 'insert', priority: 3, fieldMappings: [ { fieldName: 'id', sourceType: 'dependency', required: true }, { fieldName: 'username', sourceType: 'input', required: false } ] } ], conditions: [ { type: 'business_rule', ruleName: 'validate_personal_account_profile', condition: 'accounts.is_personal_account = true', required: true } ] }); // Generic template this.templates.set('generic', { id: 'generic', name: 'Generic User Creation', description: 'Basic user creation for custom schemas', frameworkType: 'custom', steps: [ { id: 'create_user', table: 'users', operation: 'insert', priority: 1, fieldMappings: [ { fieldName: 'email', sourceType: 'input', required: true }, { fieldName: 'name', sourceType: 'input', required: false } ] } ], conditions: [] }); } /** * Utility methods */ inferCreationOrder(constraints) { // Simple dependency-based ordering const tables = new Set(); const dependencies = new Map(); // Collect all tables and their dependencies constraints.dependencies.forEach(dep => { tables.add(dep.fromTable); tables.add(dep.toTable); if (!dependencies.has(dep.fromTable)) { dependencies.set(dep.fromTable, []); } dependencies.get(dep.fromTable).push(dep.toTable); }); // Simple topological sort const ordered = []; const visited = new Set(); const visit = (table) => { if (visited.has(table)) return; visited.add(table); const deps = dependencies.get(table) || []; deps.forEach(dep => visit(dep)); ordered.push(table); }; Array.from(tables).forEach(table => visit(table)); return ordered.reverse(); // Dependencies first } isSystemTable(tableName) { const systemTables = ['pg_', 'information_schema', 'auth.', '_']; return systemTables.some(prefix => tableName.startsWith(prefix)); } isRequiredTable(tableName, constraints) { // Consider tables with high dependency count as required const dependencyCount = constraints.dependencies.filter(d => d.fromTable === tableName).length; return dependencyCount > 0 || ['users', 'accounts', 'profiles'].includes(tableName); } extractStepDependencies(tableName, constraints) { return constraints.dependencies .filter(dep => dep.fromTable === tableName) .map(dep => `create_${dep.toTable}`); } calculateGenerationConfidence(constraints, dependencyGraph) { let confidence = constraints.confidence || 0; // Boost confidence based on discovered business rules if (constraints.businessRules.length > 0) { confidence += 0.2; } // Boost confidence based on dependency graph completeness if (dependencyGraph.nodes.length > 0) { confidence += 0.1; } return Math.min(confidence, 1.0); } async addWarningsAndRecommendations(metadata, constraints, dependencyGraph) { // Add warnings for low confidence business rules const lowConfidenceRules = constraints.businessRules.filter(rule => rule.confidence < 0.7); if (lowConfidenceRules.length > 0) { metadata.warnings.push(`${lowConfidenceRules.length} business rules have low confidence scores`); } // Add warnings for circular dependencies if (dependencyGraph.cycles.length > 0) { metadata.warnings.push(`${dependencyGraph.cycles.length} circular dependencies detected`); } // Add recommendations if (constraints.businessRules.length === 0) { metadata.recommendations.push('No business rules discovered. Consider adding triggers or constraints to improve data integrity.'); } if (metadata.confidence < 0.8) { metadata.recommendations.push('Low generation confidence. Review and test the generated workflow thoroughly.'); } } } exports.WorkflowGenerator = WorkflowGenerator; //# sourceMappingURL=workflow-generator.js.map