supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
368 lines • 15 kB
JavaScript
;
/**
* Graceful Degradation System
* Phase 6, Checkpoint F1 - Ensures system functionality even when AI services fail
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.GracefulDegradation = void 0;
exports.withGracefulDegradation = withGracefulDegradation;
const logger_1 = require("./logger");
const error_handler_1 = require("./error-handler");
const performance_monitor_1 = require("./performance-monitor");
class GracefulDegradation {
/**
* Initialize graceful degradation system
*/
static initialize() {
// Default circuit breaker configurations
this.setCircuitBreakerConfig('ai_service', {
failureThreshold: 5,
resetTimeoutMs: 60000, // 1 minute
monitoringPeriodMs: 300000, // 5 minutes
halfOpenMaxCalls: 3
});
this.setCircuitBreakerConfig('database', {
failureThreshold: 3,
resetTimeoutMs: 30000, // 30 seconds
monitoringPeriodMs: 120000, // 2 minutes
halfOpenMaxCalls: 1
});
this.setCircuitBreakerConfig('template_service', {
failureThreshold: 10,
resetTimeoutMs: 120000, // 2 minutes
monitoringPeriodMs: 600000, // 10 minutes
halfOpenMaxCalls: 5
});
// Initialize AI service fallback strategies
this.registerFallbackStrategy('ai_seed_generation', {
serviceName: 'ai_service',
fallbackMethod: 'faker',
priority: 1,
description: 'Use Faker.js for realistic but non-AI seed data',
implementation: this.fakerFallback
});
this.registerFallbackStrategy('ai_template_generation', {
serviceName: 'ai_service',
fallbackMethod: 'static',
priority: 1,
description: 'Use pre-built static templates',
implementation: this.staticTemplateFallback
});
this.registerFallbackStrategy('ai_schema_analysis', {
serviceName: 'ai_service',
fallbackMethod: 'simplified',
priority: 1,
description: 'Use rule-based schema analysis',
implementation: this.simplifiedSchemaAnalysis
});
logger_1.Logger.info('🛡️ Graceful degradation system initialized');
}
/**
* Set circuit breaker configuration for a service
*/
static setCircuitBreakerConfig(serviceName, config) {
this.degradationConfig.set(serviceName, config);
if (!this.circuitBreakers.has(serviceName)) {
this.circuitBreakers.set(serviceName, {
state: 'closed',
failureCount: 0,
lastFailureTime: new Date(0),
halfOpenCallCount: 0
});
}
logger_1.Logger.debug(`⚙️ Circuit breaker configured for ${serviceName}:`, config);
}
/**
* Register a fallback strategy for a service
*/
static registerFallbackStrategy(operationName, strategy) {
if (!this.fallbackStrategies.has(operationName)) {
this.fallbackStrategies.set(operationName, []);
}
this.fallbackStrategies.get(operationName).push(strategy);
// Sort by priority (lower number = higher priority)
this.fallbackStrategies.get(operationName).sort((a, b) => a.priority - b.priority);
logger_1.Logger.debug(`📋 Registered fallback strategy for ${operationName}:`, {
method: strategy.fallbackMethod,
priority: strategy.priority
});
}
/**
* Execute operation with graceful degradation
*/
static async executeWithFallback(serviceName, operationName, primaryOperation, context) {
const operationId = performance_monitor_1.PerformanceMonitor.startOperation(operationName, serviceName);
try {
// Check circuit breaker
if (this.isCircuitOpen(serviceName)) {
logger_1.Logger.warn(`🚫 Circuit breaker open for ${serviceName}, using fallback`);
return await this.executeFallback(operationName, context);
}
// Attempt primary operation
const result = await primaryOperation();
// Record success
this.recordSuccess(serviceName);
performance_monitor_1.PerformanceMonitor.endOperation(operationId, true);
return result;
}
catch (error) {
logger_1.Logger.warn(`❌ Primary operation failed for ${serviceName}.${operationName}:`, error);
// Record failure
this.recordFailure(serviceName, error);
performance_monitor_1.PerformanceMonitor.endOperation(operationId, false, error.name);
// Try fallback
try {
const fallbackResult = await this.executeFallback(operationName, context);
logger_1.Logger.info(`✅ Fallback successful for ${operationName}`);
return fallbackResult;
}
catch (fallbackError) {
logger_1.Logger.error(`🚨 Both primary and fallback failed for ${operationName}:`, fallbackError);
throw fallbackError;
}
}
}
/**
* Execute fallback strategy for an operation
*/
static async executeFallback(operationName, context) {
const strategies = this.fallbackStrategies.get(operationName);
if (!strategies || strategies.length === 0) {
throw error_handler_1.ErrorHandler.createConfigurationError(`No fallback strategy registered for ${operationName}`, { component: 'graceful_degradation', operation: operationName });
}
// Try each strategy in priority order
for (const strategy of strategies) {
try {
logger_1.Logger.info(`🔄 Attempting fallback: ${strategy.description}`);
const result = await strategy.implementation();
logger_1.Logger.info(`✅ Fallback successful: ${strategy.fallbackMethod}`);
return result;
}
catch (error) {
logger_1.Logger.warn(`❌ Fallback strategy failed: ${strategy.fallbackMethod}`, error);
continue;
}
}
throw error_handler_1.ErrorHandler.createAIError(`All fallback strategies failed for ${operationName}`, { component: 'graceful_degradation', operation: operationName });
}
/**
* Record successful operation
*/
static recordSuccess(serviceName) {
const health = this.getOrCreateServiceHealth(serviceName);
health.consecutiveFailures = 0;
health.lastCheck = new Date();
health.isHealthy = true;
// Reset circuit breaker on success
const breaker = this.circuitBreakers.get(serviceName);
if (breaker && breaker.state === 'half-open') {
breaker.state = 'closed';
breaker.failureCount = 0;
breaker.halfOpenCallCount = 0;
logger_1.Logger.info(`🔓 Circuit breaker closed for ${serviceName}`);
}
}
/**
* Record failed operation
*/
static recordFailure(serviceName, error) {
const health = this.getOrCreateServiceHealth(serviceName);
health.consecutiveFailures += 1;
health.lastCheck = new Date();
health.lastError = error.message;
health.isHealthy = false;
// Update circuit breaker
const breaker = this.circuitBreakers.get(serviceName);
const config = this.degradationConfig.get(serviceName);
if (breaker && config) {
breaker.failureCount += 1;
breaker.lastFailureTime = new Date();
if (breaker.failureCount >= config.failureThreshold && breaker.state === 'closed') {
breaker.state = 'open';
logger_1.Logger.warn(`🔒 Circuit breaker opened for ${serviceName} after ${breaker.failureCount} failures`);
}
}
}
/**
* Check if circuit breaker is open
*/
static isCircuitOpen(serviceName) {
const breaker = this.circuitBreakers.get(serviceName);
const config = this.degradationConfig.get(serviceName);
if (!breaker || !config) {
return false;
}
const now = new Date();
switch (breaker.state) {
case 'closed':
return false;
case 'open':
// Check if enough time has passed to try half-open
const timeSinceFailure = now.getTime() - breaker.lastFailureTime.getTime();
if (timeSinceFailure >= config.resetTimeoutMs) {
breaker.state = 'half-open';
breaker.halfOpenCallCount = 0;
logger_1.Logger.info(`🔓 Circuit breaker half-open for ${serviceName}`);
return false;
}
return true;
case 'half-open':
// Allow limited calls in half-open state
if (breaker.halfOpenCallCount < config.halfOpenMaxCalls) {
breaker.halfOpenCallCount += 1;
return false;
}
return true;
default:
return false;
}
}
/**
* Get or create service health record
*/
static getOrCreateServiceHealth(serviceName) {
if (!this.serviceHealth.has(serviceName)) {
this.serviceHealth.set(serviceName, {
serviceName,
isHealthy: true,
lastCheck: new Date(),
consecutiveFailures: 0,
averageResponseTime: 0,
errorRate: 0
});
}
return this.serviceHealth.get(serviceName);
}
/**
* Get system degradation status
*/
static getDegradationStatus() {
const services = Array.from(this.serviceHealth.values()).map(health => ({
...health,
circuitBreakerState: this.circuitBreakers.get(health.serviceName)?.state || 'closed'
}));
const activeFallbacks = services
.filter(s => !s.isHealthy || s.circuitBreakerState !== 'closed')
.map(s => s.serviceName);
// Determine overall system health
let systemHealth = 'healthy';
const criticalServices = services.filter(s => !s.isHealthy && s.consecutiveFailures > 10);
const degradedServices = services.filter(s => !s.isHealthy);
if (criticalServices.length > 0) {
systemHealth = 'critical';
}
else if (degradedServices.length > 0) {
systemHealth = 'degraded';
}
// Generate recommendations
const recommendations = [];
if (activeFallbacks.includes('ai_service')) {
recommendations.push('AI service is degraded - check Ollama installation and model availability');
}
if (activeFallbacks.includes('database')) {
recommendations.push('Database connectivity issues detected - verify Supabase connection');
}
if (systemHealth === 'critical') {
recommendations.push('Multiple critical services failing - consider system restart');
}
return {
services,
activeFallbacks,
systemHealth,
recommendations
};
}
/**
* Force reset circuit breaker
*/
static resetCircuitBreaker(serviceName) {
const breaker = this.circuitBreakers.get(serviceName);
if (breaker) {
breaker.state = 'closed';
breaker.failureCount = 0;
breaker.halfOpenCallCount = 0;
logger_1.Logger.info(`🔄 Manual circuit breaker reset for ${serviceName}`);
return true;
}
return false;
}
/**
* Fallback implementations
*/
static async fakerFallback() {
// This would integrate with existing Faker.js functionality
return {
records: [
{ id: 1, name: 'Fallback User 1', email: 'fallback1@example.com' },
{ id: 2, name: 'Fallback User 2', email: 'fallback2@example.com' }
],
metadata: {
source: 'faker_fallback',
generated_at: new Date().toISOString()
}
};
}
static async staticTemplateFallback() {
return {
template: {
id: 'static_fallback_template',
name: 'Basic Seeder Template',
description: 'Fallback template when AI is unavailable',
category: 'seeder',
tags: ['fallback', 'static'],
version: '1.0.0',
compatibility: { supaSeedVersion: '1.0.0' },
variables: [],
files: [{
path: 'seeder.ts',
content: '// Basic seeder implementation\nexport default class BasicSeeder {}'
}],
metadata: { created: new Date(), updated: new Date() }
},
confidence: 60,
reasoning: 'Static fallback template - basic functionality only',
suggestedVariables: []
};
}
static async simplifiedSchemaAnalysis() {
return {
improvements: [
{
category: 'seeding',
title: 'Basic seeding order optimization',
description: 'Seed users before posts to maintain referential integrity',
priority: 'medium',
implementation: 'Order tables by foreign key dependencies'
}
],
seedingStrategy: {
order: ['users', 'profiles', 'posts', 'comments'],
relationships: [
{ from: 'posts', to: 'users', type: 'belongs_to' },
{ from: 'comments', to: 'posts', type: 'belongs_to' }
],
estimatedTime: 120 // seconds
},
qualityScore: 75
};
}
}
exports.GracefulDegradation = GracefulDegradation;
GracefulDegradation.serviceHealth = new Map();
GracefulDegradation.fallbackStrategies = new Map();
GracefulDegradation.circuitBreakers = new Map();
GracefulDegradation.degradationConfig = new Map();
/**
* Decorator for automatic graceful degradation
*/
function withGracefulDegradation(serviceName, operationName) {
return function (target, propertyName, descriptor) {
const method = descriptor.value;
const opName = operationName || propertyName;
descriptor.value = async function (...args) {
return await GracefulDegradation.executeWithFallback(serviceName, opName, () => method.apply(this, args), { args: args.length });
};
};
}
exports.default = GracefulDegradation;
//# sourceMappingURL=graceful-degradation.js.map