supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
576 lines • 24.3 kB
JavaScript
;
/**
* Fallback and Error Handling System for Association Intelligence
* Phase 3, Checkpoint C3 - Comprehensive error recovery and progress reporting
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FallbackErrorHandler = void 0;
const logger_1 = require("../core/utils/logger");
class FallbackErrorHandler {
constructor(options) {
this.fallbackStrategies = new Map();
this.options = options;
this.currentProgress = this.createInitialProgress();
this.initializeBuiltInStrategies();
}
/**
* Handle insufficient assets by generating fallbacks
*/
async handleInsufficientAssets(target, currentAssets, requiredCount) {
if (!this.options.enableFallbackGeneration) {
return [];
}
const shortfall = requiredCount - currentAssets.length;
if (shortfall <= 0) {
return [];
}
logger_1.Logger.info(`🔄 Generating ${shortfall} fallback assets for target ${target.id}`);
const requirements = {
targetType: this.inferTargetType(target, currentAssets),
requiredCount: shortfall,
constraints: target.constraints,
existingAssets: currentAssets,
targetContext: target,
templatePreferences: this.getTemplatePreferences(target)
};
// Try fallback strategies in priority order
const sortedStrategies = Array.from(this.fallbackStrategies.values())
.filter(strategy => strategy.applicableTypes.includes('*') ||
strategy.applicableTypes.includes(requirements.targetType))
.sort((a, b) => b.priority - a.priority);
const generatedAssets = [];
for (const strategy of sortedStrategies) {
if (generatedAssets.length >= shortfall)
break;
try {
const remaining = shortfall - generatedAssets.length;
const strategyRequirements = { ...requirements, requiredCount: remaining };
const assets = await strategy.generator(strategyRequirements);
generatedAssets.push(...assets);
logger_1.Logger.debug(`Generated ${assets.length} assets using strategy: ${strategy.name}`);
}
catch (error) {
logger_1.Logger.warn(`Fallback strategy ${strategy.name} failed: ${error.message}`);
}
}
this.updateProgress('error_recovery', `Generated ${generatedAssets.length}/${shortfall} fallback assets for ${target.id}`);
return generatedAssets;
}
/**
* Attempt to recover from association errors
*/
async recoverFromErrors(distributionResult, enforcementResult, originalAssets) {
const startTime = Date.now();
const recoveryAttempts = [];
const generatedFallbacks = [];
const unrecoverableErrors = [];
logger_1.Logger.info('🚑 Starting association error recovery process');
this.updateProgress('error_recovery', 'Analyzing errors and violations');
// Convert enforcement violations to recoverable errors
const errors = this.analyzeErrors(distributionResult, enforcementResult);
let currentAssignments = [...enforcementResult.finalAssignments];
let attemptNumber = 0;
for (const error of errors) {
if (attemptNumber >= this.options.maxRetryAttempts) {
logger_1.Logger.warn(`Maximum retry attempts (${this.options.maxRetryAttempts}) reached`);
unrecoverableErrors.push(...errors.slice(errors.indexOf(error)));
break;
}
if (!error.recoverable) {
unrecoverableErrors.push(error);
continue;
}
attemptNumber++;
const recoveryAttempt = await this.attemptErrorRecovery(error, currentAssignments, originalAssets, attemptNumber);
recoveryAttempts.push(recoveryAttempt);
if (recoveryAttempt.success) {
logger_1.Logger.info(`✅ Successfully recovered from error: ${error.message}`);
// Update assignments based on recovery
if (error.type === 'asset_insufficient') {
const fallbacks = await this.handleInsufficientAssets(currentAssignments.find(a => a.target.id === error.context.targetId)?.target, currentAssignments.find(a => a.target.id === error.context.targetId)?.assets || [], recoveryAttempt.recoveredItems);
generatedFallbacks.push(...fallbacks);
// Add fallbacks to assignments
const targetAssignment = currentAssignments.find(a => a.target.id === error.context.targetId);
if (targetAssignment) {
targetAssignment.assets.push(...fallbacks);
targetAssignment.fulfilled = this.checkFulfillment(targetAssignment);
}
}
}
else {
logger_1.Logger.warn(`❌ Failed to recover from error: ${error.message}`);
unrecoverableErrors.push(error);
}
}
const totalRecoveryTime = Date.now() - startTime;
const success = unrecoverableErrors.length === 0;
this.updateProgress('finalization', 'Recovery process completed');
const finalReport = this.generateFinalReport(currentAssignments, generatedFallbacks, recoveryAttempts, unrecoverableErrors);
const recommendations = this.generateRecoveryRecommendations(recoveryAttempts, unrecoverableErrors, generatedFallbacks);
logger_1.Logger.info(`🏁 Recovery completed: ${success ? 'SUCCESS' : 'PARTIAL'} (${totalRecoveryTime}ms)`);
return {
success,
finalAssignments: currentAssignments,
recoveryAttempts,
generatedFallbacks,
unrecoverableErrors,
finalReport,
recommendations,
performanceMetrics: {
totalRecoveryTime,
fallbackGenerationTime: recoveryAttempts
.filter(r => r.strategy === 'fallback_generation')
.reduce((sum, r) => sum + r.executionTime, 0),
constraintRelaxationTime: recoveryAttempts
.filter(r => r.strategy === 'constraint_relaxation')
.reduce((sum, r) => sum + r.executionTime, 0),
finalValidationTime: 0 // Would be set by validation step
}
};
}
/**
* Generate progress reports for long-running operations
*/
reportProgress() {
return { ...this.currentProgress };
}
/**
* Update progress with new information
*/
updateProgress(phase, currentStep, progressIncrement = 0) {
this.currentProgress.phase = phase;
this.currentProgress.currentStep = currentStep;
this.currentProgress.overallProgress = Math.min(100, this.currentProgress.overallProgress + progressIncrement);
this.currentProgress.completedSteps.push(currentStep);
if (this.options.reportingLevel === 'verbose') {
logger_1.Logger.info(`📊 Progress: ${this.currentProgress.overallProgress}% - ${currentStep}`);
}
}
/**
* Attempt to recover from a specific error
*/
async attemptErrorRecovery(error, assignments, originalAssets, attemptNumber) {
const startTime = Date.now();
let success = false;
let errorMessage;
let recoveredItems = 0;
let strategy = 'unknown';
let action = 'unknown';
try {
switch (error.type) {
case 'asset_insufficient':
strategy = 'fallback_generation';
action = 'Generate synthetic assets';
recoveredItems = await this.recoverInsufficientAssets(error, assignments);
success = recoveredItems > 0;
break;
case 'constraint_violation':
strategy = 'constraint_relaxation';
action = 'Relax constraints';
success = await this.recoverConstraintViolation(error, assignments);
recoveredItems = success ? 1 : 0;
break;
case 'distribution_failure':
strategy = 'redistribution';
action = 'Redistribute assets';
success = await this.recoverDistributionFailure(error, assignments, originalAssets);
recoveredItems = success ? 1 : 0;
break;
default:
strategy = 'manual_intervention';
action = 'Manual intervention required';
success = false;
errorMessage = 'Error type requires manual intervention';
}
}
catch (recoveryError) {
success = false;
errorMessage = recoveryError.message;
}
const executionTime = Date.now() - startTime;
return {
attemptNumber,
strategy,
action,
success,
errorMessage,
recoveredItems,
executionTime,
confidence: success ? 85 : 0
};
}
/**
* Recover from insufficient assets error
*/
async recoverInsufficientAssets(error, assignments) {
const targetId = error.context.targetId;
if (!targetId)
return 0;
const assignment = assignments.find(a => a.target.id === targetId);
if (!assignment)
return 0;
const minItems = assignment.target.constraints?.minItems || 0;
const currentCount = assignment.assets.length;
const needed = Math.max(0, minItems - currentCount);
if (needed === 0)
return 0;
const fallbacks = await this.handleInsufficientAssets(assignment.target, assignment.assets, minItems);
return fallbacks.length;
}
/**
* Recover from constraint violation
*/
async recoverConstraintViolation(error, assignments) {
if (!this.options.enableConstraintRelaxation) {
return false;
}
const targetId = error.context.targetId;
if (!targetId)
return false;
const assignment = assignments.find(a => a.target.id === targetId);
if (!assignment || !assignment.target.constraints)
return false;
// Simple constraint relaxation - increase limits by 50%
if (assignment.target.constraints.maxItems) {
assignment.target.constraints.maxItems = Math.ceil(assignment.target.constraints.maxItems * 1.5);
}
if (assignment.target.constraints.minItems) {
assignment.target.constraints.minItems = Math.max(1, Math.floor(assignment.target.constraints.minItems * 0.8));
}
return true;
}
/**
* Recover from distribution failure
*/
async recoverDistributionFailure(error, assignments, originalAssets) {
// Simple redistribution - move unassigned assets to targets with capacity
const availableTargets = assignments.filter(a => !a.target.constraints?.maxItems ||
a.assets.length < a.target.constraints.maxItems);
if (availableTargets.length === 0)
return false;
// This is a simplified recovery - in practice would need more sophisticated logic
return availableTargets.length > 0;
}
/**
* Check if an assignment is fulfilled based on its constraints
*/
checkFulfillment(assignment) {
const constraints = assignment.target.constraints;
if (!constraints)
return true;
if (constraints.minItems && assignment.assets.length < constraints.minItems) {
return false;
}
if (constraints.maxItems && assignment.assets.length > constraints.maxItems) {
return false;
}
return true;
}
/**
* Analyze distribution and enforcement results to identify recoverable errors
*/
analyzeErrors(distributionResult, enforcementResult) {
const errors = [];
// Convert unresolvable violations to errors
for (const violation of enforcementResult.unresolvableViolations) {
errors.push({
id: `violation-${violation.ruleId}-${Date.now()}`,
type: this.mapViolationToErrorType(violation),
severity: violation.severity === 'critical' ? 'critical' :
violation.severity === 'error' ? 'high' : 'medium',
message: violation.message,
context: {
targetId: violation.affectedTargets[0],
assetIds: violation.affectedAssets,
ruleName: violation.ruleName,
operation: 'constraint_enforcement',
timestamp: new Date()
},
recoverable: this.isViolationRecoverable(violation),
suggestedActions: violation.suggestedResolutions,
metadata: violation.metadata
});
}
// Add general distribution errors
if (distributionResult.unassigned.length > 0) {
errors.push({
id: `unassigned-assets-${Date.now()}`,
type: 'distribution_failure',
severity: 'medium',
message: `${distributionResult.unassigned.length} assets could not be assigned`,
context: {
assetIds: distributionResult.unassigned.map(a => a.id),
operation: 'distribution',
timestamp: new Date()
},
recoverable: true,
suggestedActions: ['Add more targets', 'Relax constraints', 'Generate additional capacity'],
metadata: { unassignedCount: distributionResult.unassigned.length }
});
}
return errors;
}
/**
* Map constraint violation to error type
*/
mapViolationToErrorType(violation) {
switch (violation.violationType) {
case 'insufficient':
return 'asset_insufficient';
case 'invalid':
return 'validation_error';
default:
return 'constraint_violation';
}
}
/**
* Check if a violation is recoverable
*/
isViolationRecoverable(violation) {
return violation.violationType === 'insufficient' ||
violation.violationType === 'excess' ||
(violation.violationType === 'invalid' && this.options.enableConstraintRelaxation);
}
/**
* Generate final recovery report
*/
generateFinalReport(assignments, fallbacks, attempts, errors) {
const fulfilledTargets = assignments.filter(a => a.fulfilled).length;
const assignedAssets = assignments.reduce((sum, a) => sum + a.assets.length, 0);
return {
...this.currentProgress,
phase: 'finalization',
overallProgress: 100,
currentStep: 'Recovery completed',
statistics: {
totalTargets: assignments.length,
fulfilledTargets,
totalAssets: assignedAssets,
assignedAssets,
generatedFallbacks: fallbacks.length,
recoveryAttempts: attempts.length,
executionTime: Date.now() - this.currentProgress.statistics.executionTime
},
errors: errors,
canContinue: errors.length === 0
};
}
/**
* Generate recovery recommendations
*/
generateRecoveryRecommendations(attempts, errors, fallbacks) {
const recommendations = [];
if (errors.length > 0) {
recommendations.push(`${errors.length} errors remain unresolved - consider manual intervention`);
}
if (fallbacks.length > 10) {
recommendations.push('High number of fallback assets generated - review asset pool quality');
}
const failedAttempts = attempts.filter(a => !a.success).length;
if (failedAttempts > attempts.length * 0.5) {
recommendations.push('Many recovery attempts failed - review recovery strategy configuration');
}
if (recommendations.length === 0) {
recommendations.push('All errors successfully recovered - association process completed successfully');
}
return recommendations;
}
/**
* Infer the target type from constraints and existing assets
*/
inferTargetType(target, assets) {
// Check constraints for type hints
if (target.constraints?.requiredTypes && target.constraints.requiredTypes.length > 0) {
return target.constraints.requiredTypes[0];
}
// Infer from existing assets
if (assets.length > 0) {
const typeFreq = assets.reduce((freq, asset) => {
freq[asset.type] = (freq[asset.type] || 0) + 1;
return freq;
}, {});
return Object.entries(typeFreq).sort(([, a], [, b]) => b - a)[0][0];
}
return 'generic';
}
/**
* Get template preferences for a target
*/
getTemplatePreferences(target) {
// This would analyze target metadata to suggest appropriate templates
return ['default', 'basic'];
}
/**
* Create initial progress report
*/
createInitialProgress() {
return {
operationId: `recovery-${Date.now()}`,
phase: 'initialization',
overallProgress: 0,
currentStep: 'Initializing recovery process',
completedSteps: [],
remainingSteps: ['analyze_errors', 'attempt_recovery', 'generate_fallbacks', 'finalize'],
errors: [],
warnings: [],
statistics: {
totalTargets: 0,
fulfilledTargets: 0,
totalAssets: 0,
assignedAssets: 0,
generatedFallbacks: 0,
recoveryAttempts: 0,
executionTime: Date.now()
},
canContinue: true
};
}
/**
* Initialize built-in fallback strategies
*/
initializeBuiltInStrategies() {
// Synthetic data generation strategy
this.addFallbackStrategy({
id: 'synthetic-generator',
name: 'Synthetic Data Generator',
type: 'generate_synthetic',
priority: 80,
applicableTypes: ['*'],
confidence: 70,
costWeight: 1,
generator: async (requirements) => {
const fallbacks = [];
for (let i = 0; i < requirements.requiredCount; i++) {
const asset = {
id: `synthetic-${Date.now()}-${i}`,
filePath: `/synthetic/${requirements.targetType}_${i}.json`,
type: this.normalizeAssetType(requirements.targetType),
content: JSON.stringify({
title: `Synthetic ${requirements.targetType} ${i + 1}`,
description: `Auto-generated ${requirements.targetType} for testing`,
generated: new Date().toISOString()
}),
metadata: {
filename: `synthetic_${requirements.targetType}_${i}`,
title: `Synthetic ${requirements.targetType} ${i + 1}`,
synthetic: true
},
fileSize: 100,
lastModified: new Date(),
isValid: true,
fallbackType: 'synthetic',
generatedBy: 'synthetic-generator',
fallbackReason: 'Insufficient assets for target requirements',
confidence: 70
};
fallbacks.push(asset);
}
return fallbacks;
}
});
// Template-based generation strategy
this.addFallbackStrategy({
id: 'template-generator',
name: 'Template-Based Generator',
type: 'use_template',
priority: 90,
applicableTypes: ['markdown', 'json'],
confidence: 85,
costWeight: 1.5,
generator: async (requirements) => {
// This would use predefined templates to generate assets
// For now, return basic template-based assets
const fallbacks = [];
for (let i = 0; i < requirements.requiredCount; i++) {
const asset = {
id: `template-${Date.now()}-${i}`,
filePath: `/templates/${requirements.targetType}_${i}.md`,
type: this.normalizeAssetType(requirements.targetType),
content: `# Template ${requirements.targetType}\n\nThis is a template-generated asset.`,
metadata: {
filename: `template_${requirements.targetType}_${i}`,
title: `Template ${requirements.targetType} ${i + 1}`,
template: true
},
fileSize: 150,
lastModified: new Date(),
isValid: true,
fallbackType: 'template',
generatedBy: 'template-generator',
sourceTemplate: 'basic-template',
fallbackReason: 'Using template to meet asset requirements',
confidence: 85
};
fallbacks.push(asset);
}
return fallbacks;
}
});
logger_1.Logger.info(`🔧 Initialized ${this.fallbackStrategies.size} built-in fallback strategies`);
}
/**
* Add a custom fallback strategy
*/
addFallbackStrategy(strategy) {
this.fallbackStrategies.set(strategy.id, strategy);
logger_1.Logger.debug(`Added fallback strategy: ${strategy.name}`);
}
/**
* Remove a fallback strategy
*/
removeFallbackStrategy(strategyId) {
return this.fallbackStrategies.delete(strategyId);
}
/**
* Get all available fallback strategies
*/
getFallbackStrategies() {
return Array.from(this.fallbackStrategies.values());
}
/**
* Update error recovery options
*/
updateOptions(newOptions) {
this.options = { ...this.options, ...newOptions };
logger_1.Logger.debug('Updated error recovery options');
}
/**
* Normalize asset type to ensure it's a valid LoadedAsset type
*/
normalizeAssetType(targetType) {
switch (targetType.toLowerCase()) {
case 'markdown':
case 'md':
return 'markdown';
case 'json':
return 'json';
case 'image':
case 'img':
case 'png':
case 'jpg':
case 'jpeg':
return 'image';
case 'csv':
return 'csv';
default:
return 'json'; // Default fallback type
}
}
/**
* Create a fallback error handler with default options
*/
static createDefault() {
const defaultOptions = {
enableFallbackGeneration: true,
enableConstraintRelaxation: true,
enablePartialFulfillment: true,
maxRetryAttempts: 3,
fallbackStrategies: [],
recoveryPriority: 'completeness',
reportingLevel: 'detailed'
};
return new FallbackErrorHandler(defaultOptions);
}
}
exports.FallbackErrorHandler = FallbackErrorHandler;
//# sourceMappingURL=fallback-error-handling.js.map