UNPKG

supa-seed

Version:

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

451 lines 18.9 kB
"use strict"; /** * Schema-Driven Adapter * Replaces hardcoded MakerKit workflows with configurable execution patterns * Integrates all schema-first components to provide a unified interface */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaDrivenAdapter = void 0; const schema_introspector_1 = require("./schema-introspector"); const workflow_builder_1 = require("../features/generation/workflow-builder"); const workflow_executor_1 = require("../features/generation/workflow-executor"); const constraint_validator_1 = require("../features/analysis/constraint-validator"); const dynamic_column_mapper_1 = require("./dynamic-column-mapper"); const logger_1 = require("../core/utils/logger"); class SchemaDrivenAdapter { constructor(client, config = {}) { // Cached data this.cachedSchema = null; this.cachedMappings = null; this.cachedWorkflow = null; this.cacheTimestamp = null; this.client = client; this.config = { enableConstraintValidation: true, enableRollback: true, enableAutoFixes: true, maxRetries: 3, timeoutMs: 30000, continueOnError: false, mappingConfig: { strictMode: false, enablePatternMatching: true, enableFuzzyMatching: true, minimumConfidence: 0.3, preferExactMatches: true, allowMultipleMappings: false }, enableSchemaCache: true, cacheTimeout: 30, ...config }; // Initialize components this.introspector = new schema_introspector_1.SchemaIntrospector(client); this.workflowBuilder = new workflow_builder_1.WorkflowBuilder(this.introspector); this.executor = new workflow_executor_1.WorkflowExecutor(client, { enableRollback: this.config.enableRollback, enableConstraintValidation: this.config.enableConstraintValidation, continueOnError: this.config.continueOnError, maxRetries: this.config.maxRetries, timeoutMs: this.config.timeoutMs, generateMissingFields: true }); this.validator = new constraint_validator_1.ConstraintValidator(client); this.columnMapper = new dynamic_column_mapper_1.DynamicColumnMapper({ ...this.config.mappingConfig, customMappings: this.config.customColumnMappings }); } /** * Create a user using the schema-driven approach * This replaces all hardcoded MakerKit workflows */ async createUser(request) { const startTime = Date.now(); logger_1.Logger.info(`🚀 Starting schema-driven user creation for ${request.email}`); const performance = { introspectionTime: 0, workflowBuildTime: 0, executionTime: 0, totalTime: 0 }; try { // Step 1: Analyze schema (with caching) const analysisStart = Date.now(); const analysis = await this.analyzeSchema(); performance.introspectionTime = Date.now() - analysisStart; logger_1.Logger.info(`📊 Schema analysis complete: ${analysis.schemaInfo.framework.type} ${analysis.schemaInfo.framework.version} (confidence: ${(analysis.confidence * 100).toFixed(1)}%)`); // Step 2: Build workflow const workflowStart = Date.now(); const workflow = await this.getOrBuildWorkflow(analysis); performance.workflowBuildTime = Date.now() - workflowStart; logger_1.Logger.info(`🔨 Workflow built with ${workflow.steps.length} steps`); // Step 3: Validate and apply auto-fixes let validationResult; let appliedFixes = []; if (this.config.enableConstraintValidation) { const primaryUserTable = this.determinePrimaryUserTable(analysis.schemaInfo); validationResult = await this.validator.validateOperation({ operation: 'insert', table: primaryUserTable, data: this.normalizeUserData(request, analysis.columnMappings.get(primaryUserTable)), schemaInfo: analysis.schemaInfo }); if (!validationResult.valid && this.config.enableAutoFixes) { const autoFixResult = await this.validator.applyAutoFixes(this.normalizeUserData(request, analysis.columnMappings.get(primaryUserTable)), validationResult.autoFixes); appliedFixes = autoFixResult.appliedFixes.map(fix => ({ field: fix.field, oldValue: fix.originalValue, newValue: fix.fixedValue })); // Update request with fixed data Object.assign(request, autoFixResult.data); } } // Step 4: Execute workflow const executionStart = Date.now(); const executionResult = await this.executor.executeWorkflow(workflow, request); performance.executionTime = Date.now() - executionStart; performance.totalTime = Date.now() - startTime; const response = { success: executionResult.success, userId: executionResult.userId, executionId: executionResult.executionId, workflow, executionResult, validationResult, appliedFixes: appliedFixes.length > 0 ? appliedFixes : undefined, schemaInfo: { framework: analysis.schemaInfo.framework.type, version: analysis.schemaInfo.framework.version, confidence: analysis.schemaInfo.framework.confidence, primaryUserTable: this.determinePrimaryUserTable(analysis.schemaInfo) }, performance, recommendations: analysis.recommendations }; if (response.success) { logger_1.Logger.success(`✅ User created successfully: ${request.email} (${performance.totalTime}ms)`); } else { logger_1.Logger.error(`❌ User creation failed: ${executionResult.error}`); } return response; } catch (error) { logger_1.Logger.error('Schema-driven user creation failed:', error); performance.totalTime = Date.now() - startTime; return { success: false, executionId: crypto.randomUUID(), workflow: await this.getEmptyWorkflow(), executionResult: { success: false, executionId: crypto.randomUUID(), duration: performance.totalTime, completedSteps: [], error: error.message, rollbackRequired: false, rollbackCompleted: false, stepResults: {} }, schemaInfo: { framework: 'unknown', version: 'unknown', confidence: 0, primaryUserTable: 'unknown' }, performance, recommendations: [`Error occurred: ${error.message}`] }; } } /** * Analyze schema and build column mappings */ async analyzeSchema() { // Check cache first if (this.isCacheValid()) { logger_1.Logger.debug('Using cached schema analysis'); return { schemaInfo: this.cachedSchema, columnMappings: this.cachedMappings, workflow: this.cachedWorkflow, recommendations: [], confidence: this.cachedSchema.framework.confidence }; } logger_1.Logger.info('🔍 Performing fresh schema analysis...'); // Introspect schema const schemaInfo = await this.introspector.introspectSchema(); // Initialize validator with schema info await this.validator.initialize(schemaInfo); // Create column mappings const columnMappings = await this.columnMapper.createMappings(schemaInfo); // Build workflow const primaryUserTable = this.determinePrimaryUserTable(schemaInfo); const workflowConfig = { primaryUserTable, enableConstraintValidation: this.config.enableConstraintValidation, enableRollback: this.config.enableRollback, generateOptionalFields: true, respectExistingData: true, customFieldMappings: this.extractCustomFieldMappings(columnMappings) }; const workflow = await this.workflowBuilder.buildUserCreationWorkflow(workflowConfig); // Generate recommendations const recommendations = this.generateRecommendations(schemaInfo, columnMappings, workflow); // Cache results if (this.config.enableSchemaCache) { this.cachedSchema = schemaInfo; this.cachedMappings = columnMappings; this.cachedWorkflow = workflow; this.cacheTimestamp = new Date(); } return { schemaInfo, columnMappings, workflow, recommendations, confidence: schemaInfo.framework.confidence }; } /** * Get the strategy that would be used for user creation * This replaces the old getUserCreationStrategy method */ async getUserCreationStrategy() { const analysis = await this.analyzeSchema(); const primaryTable = this.determinePrimaryUserTable(analysis.schemaInfo); return { strategy: 'schema-driven', primaryTable, workflow: analysis.workflow, confidence: analysis.confidence, evidence: analysis.schemaInfo.framework.evidence }; } /** * Create a user using the legacy interface for backward compatibility * This replaces createUserForSchema from the old SchemaAdapter */ async createUserForSchema(userData) { try { const request = { email: userData.email, name: userData.name, username: userData.username, bio: userData.bio, picture_url: userData.picture_url, customFields: userData.id ? { id: userData.id } : undefined }; const response = await this.createUser(request); if (response.success && response.userId) { return { id: response.userId, success: true }; } else { return { id: '', success: false, error: response.executionResult.error || 'User creation failed' }; } } catch (error) { return { id: '', success: false, error: error.message }; } } /** * Validate the current schema and mappings */ async validateSchema() { const analysis = await this.analyzeSchema(); const issues = []; const recommendations = []; // Validate workflow const workflowValidation = await this.workflowBuilder.validateWorkflow(analysis.workflow); if (!workflowValidation.isValid) { issues.push(...workflowValidation.errors); } recommendations.push(...workflowValidation.warnings); // Validate mappings const mappingValidation = await this.columnMapper.validateMappings(analysis.columnMappings, analysis.schemaInfo); if (!mappingValidation.valid) { issues.push(...mappingValidation.invalidMappings.map(m => `Invalid mapping in ${m.table}.${m.field}: ${m.reason}`)); issues.push(...mappingValidation.missingTables.map(t => `Missing table: ${t}`)); } // Add general recommendations recommendations.push(...analysis.recommendations); return { valid: issues.length === 0, issues, recommendations, schemaHash: analysis.workflow.metadata.schemaHash }; } /** * Get current schema information */ async getSchemaInfo() { const analysis = await this.analyzeSchema(); return { framework: analysis.schemaInfo.framework, tables: analysis.schemaInfo.patterns.map(p => ({ name: p.name, role: p.suggestedRole, confidence: p.confidence })), relationships: analysis.schemaInfo.relationships.length, constraints: Object.values(analysis.schemaInfo.constraints).flat().length, mappings: this.columnMapper.exportMappings(analysis.columnMappings) }; } /** * Clear all caches and force fresh analysis */ clearCache() { this.cachedSchema = null; this.cachedMappings = null; this.cachedWorkflow = null; this.cacheTimestamp = null; this.introspector.clearCache(); this.validator.clearCache(); logger_1.Logger.debug('Schema cache cleared'); } /** * Update configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; // Update component configurations this.executor = new workflow_executor_1.WorkflowExecutor(this.client, { enableRollback: this.config.enableRollback, enableConstraintValidation: this.config.enableConstraintValidation, continueOnError: this.config.continueOnError, maxRetries: this.config.maxRetries, timeoutMs: this.config.timeoutMs, generateMissingFields: true }); // Clear cache to force reconfiguration this.clearCache(); logger_1.Logger.debug('Schema adapter configuration updated'); } /** * Private helper methods */ isCacheValid() { if (!this.config.enableSchemaCache) return false; if (!this.cacheTimestamp || !this.cachedSchema) return false; const cacheAge = Date.now() - this.cacheTimestamp.getTime(); const cacheLimit = this.config.cacheTimeout * 60 * 1000; // Convert to milliseconds return cacheAge < cacheLimit; } determinePrimaryUserTable(schemaInfo) { // Check configuration override if (this.config.primaryUserTable) { return this.config.primaryUserTable; } // Find user table pattern with highest confidence const userTables = schemaInfo.patterns.filter(p => p.suggestedRole === 'user'); if (userTables.length === 0) { logger_1.Logger.warn('No user tables detected, defaulting to "profiles"'); return 'profiles'; } // Sort by confidence and return the best match userTables.sort((a, b) => b.confidence - a.confidence); return userTables[0].name; } normalizeUserData(request, tableMap) { const normalized = { email: request.email, name: request.name }; // Add optional fields if (request.username) normalized.username = request.username; if (request.bio) normalized.bio = request.bio; if (request.picture_url) normalized.picture_url = request.picture_url; if (request.customFields) Object.assign(normalized, request.customFields); // Apply column mappings if available if (tableMap) { const mappedData = {}; for (const mapping of tableMap.mappings) { const semanticField = mapping.semanticField; const actualColumn = mapping.actualColumn; if (normalized[semanticField] !== undefined) { mappedData[actualColumn] = normalized[semanticField]; } } return mappedData; } return normalized; } extractCustomFieldMappings(columnMappings) { const mappings = {}; for (const [tableName, tableMap] of columnMappings) { for (const mapping of tableMap.mappings) { mappings[mapping.semanticField] = mapping.actualColumn; } } return Object.keys(mappings).length > 0 ? mappings : undefined; } generateRecommendations(schemaInfo, columnMappings, workflow) { const recommendations = []; // Framework confidence recommendations if (schemaInfo.framework.confidence < 0.7) { recommendations.push(`Low framework detection confidence (${(schemaInfo.framework.confidence * 100).toFixed(1)}%). Consider specifying framework in configuration.`); } // Column mapping recommendations for (const [tableName, tableMap] of columnMappings) { if (tableMap.confidence < 0.6) { recommendations.push(`Low column mapping confidence for ${tableName} (${(tableMap.confidence * 100).toFixed(1)}%). Review column mappings.`); } if (tableMap.recommendations.length > 0) { recommendations.push(...tableMap.recommendations.map(r => r.message)); } } // Workflow recommendations if (workflow.steps.length > 10) { recommendations.push(`Complex workflow with ${workflow.steps.length} steps. Consider simplifying schema or enabling error continuation.`); } // Schema recommendations recommendations.push(...schemaInfo.recommendations.map(r => r.message)); return recommendations; } async getOrBuildWorkflow(analysis) { // Return cached workflow if available if (this.cachedWorkflow && this.isCacheValid()) { return this.cachedWorkflow; } return analysis.workflow; } async getEmptyWorkflow() { return { id: 'empty_workflow', name: 'Empty Workflow', description: 'Fallback workflow for error cases', steps: [], rollbackSteps: [], metadata: { framework: 'unknown', version: 'unknown', createdAt: new Date().toISOString(), schemaHash: 'error' } }; } } exports.SchemaDrivenAdapter = SchemaDrivenAdapter; //# sourceMappingURL=schema-driven-adapter.js.map