UNPKG

@sailboat-computer/resilience

Version:

Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations

483 lines 17.5 kB
"use strict"; /** * Retry mechanism with marine-specific adaptations */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RetryContexts = exports.executeWithRetry = exports.defaultRetryManager = exports.RetryManager = void 0; const marine_constants_1 = require("../types/marine-constants"); /** * Retry manager for marine operations */ class RetryManager { constructor(config = {}) { this.eventListeners = []; this.config = { maxRetries: 3, baseDelayMs: 1000, backoffMultiplier: 2.0, maxDelayMs: 30000, useJitter: true, retryableFailures: [ marine_constants_1.MarineFailureType.CONNECTIVITY_LOSS, marine_constants_1.MarineFailureType.TIMEOUT, marine_constants_1.MarineFailureType.ENVIRONMENTAL, marine_constants_1.MarineFailureType.SENSOR_FAILURE ], marineSettings: { adaptToMarineConditions: true, powerAware: true, allowedContexts: [ marine_constants_1.OperationalContext.SAILING, marine_constants_1.OperationalContext.MOTORING, marine_constants_1.OperationalContext.ANCHORED, marine_constants_1.OperationalContext.DOCKED, marine_constants_1.OperationalContext.MAINTENANCE ], limitInRoughSeas: true }, ...config }; this.metrics = this.initializeMetrics(); } /** * Execute operation with retry */ async execute(operation, context) { const startTime = Date.now(); let retryCount = 0; let lastError; const retryDelays = []; // Apply custom config if provided const effectiveConfig = this.getEffectiveConfig(context); // Check if retries are allowed in current context if (!this.isRetryAllowed(context.operationalContext)) { try { const result = await operation(); this.updateMetrics(0, true); return { success: true, data: result, retryCount: 0, totalExecutionTime: Date.now() - startTime, retryDelays: [], maxRetriesReached: false, marineContext: { operationalContext: context.operationalContext, environmentalConditions: this.marineEnvironment ? { ...this.marineEnvironment } : undefined } }; } catch (error) { this.updateMetrics(0, false); return { success: false, error: error, retryCount: 0, totalExecutionTime: Date.now() - startTime, retryDelays: [], maxRetriesReached: false, marineContext: { operationalContext: context.operationalContext, environmentalConditions: this.marineEnvironment ? { ...this.marineEnvironment } : undefined } }; } } // Execute with retry while (retryCount <= effectiveConfig.maxRetries) { try { const result = await operation(); // Success - update metrics and return if (retryCount === 0) { this.updateMetrics(0, true); } else { this.updateMetrics(retryCount, true); } return { success: true, data: result, retryCount, totalExecutionTime: Date.now() - startTime, retryDelays, maxRetriesReached: false, marineContext: { operationalContext: context.operationalContext, environmentalConditions: this.marineEnvironment ? { ...this.marineEnvironment } : undefined } }; } catch (error) { lastError = error; // Check if we should retry this error if (!this.isRetryableError(error, effectiveConfig)) { break; } // Check if we have retries remaining if (retryCount < effectiveConfig.maxRetries) { retryCount++; // Calculate delay for this retry const delay = this.calculateRetryDelay(retryCount, effectiveConfig, context); retryDelays.push(delay); // Emit retry event this.emitEvent('retry', context.operationName, { retryCount, delay, error: error.message, marineSystemType: context.marineSystemType, priority: context.priority }, 'warning'); // Wait before retry await this.delay(delay); } else { break; } } } // All retries exhausted or non-retryable error this.updateMetrics(retryCount, false); return { success: false, error: lastError || new Error('Unknown error'), retryCount, totalExecutionTime: Date.now() - startTime, retryDelays, maxRetriesReached: retryCount >= effectiveConfig.maxRetries, marineContext: { operationalContext: context.operationalContext, environmentalConditions: this.marineEnvironment ? { ...this.marineEnvironment } : undefined } }; } /** * Update marine environment for adaptive retry behavior */ updateMarineEnvironment(environment) { this.marineEnvironment = environment; this.emitEvent('recovery', 'retry-manager', { action: 'marine_environment_updated', seaState: environment.seaState, powerStatus: environment.powerStatus, criticalOperationsOnly: environment.criticalOperationsOnly }, 'info'); } /** * Get current retry metrics */ getMetrics() { return { ...this.metrics }; } /** * Add event listener */ onEvent(listener) { this.eventListeners.push(listener); } /** * Remove event listener */ removeEventListener(listener) { const index = this.eventListeners.indexOf(listener); if (index > -1) { this.eventListeners.splice(index, 1); } } /** * Get effective configuration with overrides applied */ getEffectiveConfig(context) { if (!context.customRetryConfig) { return this.config; } return { ...this.config, ...context.customRetryConfig, marineSettings: { ...this.config.marineSettings, ...(context.customRetryConfig.marineSettings || {}) } }; } /** * Check if retry is allowed in current operational context */ isRetryAllowed(operationalContext) { // Don't retry in emergency context if (operationalContext === marine_constants_1.OperationalContext.EMERGENCY) { return false; } // Check if context is in allowed contexts if (!this.config.marineSettings.allowedContexts.includes(operationalContext)) { return false; } // Check marine environment conditions if (this.marineEnvironment) { // Check sea conditions if configured if (this.config.marineSettings.limitInRoughSeas && (this.marineEnvironment.seaState === 'rough' || this.marineEnvironment.seaState === 'very_rough')) { return false; } // Check power status if configured if (this.config.marineSettings.powerAware && this.marineEnvironment.powerStatus === 'critical') { return false; } // Check critical operations only if (this.marineEnvironment.criticalOperationsOnly) { return false; } } return true; } /** * Check if error is retryable */ isRetryableError(error, config) { const errorMessage = error.message.toLowerCase(); // Check for specific marine failure types for (const failureType of config.retryableFailures) { if (errorMessage.includes(failureType.toLowerCase())) { return true; } } // Common retryable errors if (errorMessage.includes('timeout') || errorMessage.includes('connection') || errorMessage.includes('network') || errorMessage.includes('temporary')) { return true; } // Non-retryable errors if (errorMessage.includes('validation') || errorMessage.includes('not found') || errorMessage.includes('permission') || errorMessage.includes('unauthorized')) { return false; } // Default to retryable for unknown errors return true; } /** * Calculate retry delay with exponential backoff and jitter */ calculateRetryDelay(retryCount, config, context) { // Base delay with exponential backoff let delay = config.baseDelayMs * Math.pow(config.backoffMultiplier, retryCount - 1); // Add jitter if configured if (config.useJitter) { const jitter = Math.random() * 0.2 * delay; // 0-20% jitter delay += jitter; } // Apply marine environment adjustments if (this.marineEnvironment && config.marineSettings.adaptToMarineConditions) { // Adjust for sea state if (this.marineEnvironment.seaState === 'moderate') { delay *= 1.2; // 20% longer delays in moderate seas } // Adjust for power status if (this.marineEnvironment.powerStatus === 'conservation') { delay *= 1.5; // 50% longer delays in power conservation mode } // Adjust for connectivity if (this.marineEnvironment.connectivityQuality < 0.5) { delay *= 1.3; // 30% longer delays with poor connectivity } } // Adjust for operational context switch (context.operationalContext) { case marine_constants_1.OperationalContext.MAINTENANCE: delay *= 0.8; // Faster retries during maintenance break; case marine_constants_1.OperationalContext.ANCHORED: delay *= 1.2; // Slower retries when anchored break; case marine_constants_1.OperationalContext.DOCKED: delay *= 0.9; // Slightly faster retries when docked break; } // Adjust for priority switch (context.priority) { case 'critical': delay *= 0.7; // Faster retries for critical operations break; case 'low': delay *= 1.3; // Slower retries for low priority operations break; } // Ensure delay is within bounds return Math.min(Math.max(delay, config.baseDelayMs), config.maxDelayMs); } /** * Update metrics */ updateMetrics(retryCount, success) { this.metrics.totalOperations++; if (success) { if (retryCount === 0) { this.metrics.successWithoutRetry++; } else { this.metrics.successWithRetry++; this.metrics.totalRetryAttempts += retryCount; this.metrics.maxRetriesForOperation = Math.max(this.metrics.maxRetriesForOperation, retryCount); // Update marine-specific metrics if (this.marineEnvironment) { if (this.marineEnvironment.seaState === 'rough' || this.marineEnvironment.seaState === 'very_rough') { this.metrics.marineMetrics.environmentalRetries += retryCount; } if (this.marineEnvironment.connectivityQuality < 0.5) { this.metrics.marineMetrics.connectivityRetries += retryCount; } if (this.marineEnvironment.powerStatus === 'conservation' || this.marineEnvironment.powerStatus === 'critical') { this.metrics.marineMetrics.powerRetries += retryCount; } } } } else { this.metrics.failedAfterRetry++; this.metrics.totalRetryAttempts += retryCount; } // Update average metrics if (this.metrics.totalOperations > 0) { this.metrics.averageRetriesPerOperation = this.metrics.totalRetryAttempts / this.metrics.totalOperations; } } /** * Delay helper function */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Emit resilience event */ emitEvent(eventType, component, details, severity) { const event = { timestamp: new Date(), eventType, component, details, severity, marineContext: { operationalContext: details['operationalContext'] || marine_constants_1.OperationalContext.SAILING, environmentalImpact: this.marineEnvironment?.seaState === 'very_rough', safetyImpact: details['marineSystemType'] === 'safety' } }; this.eventListeners.forEach(listener => { try { listener(event); } catch (error) { console.error('Error in retry manager event listener:', error); } }); } /** * Initialize metrics */ initializeMetrics() { return { totalOperations: 0, successWithoutRetry: 0, successWithRetry: 0, failedAfterRetry: 0, totalRetryAttempts: 0, averageRetriesPerOperation: 0, maxRetriesForOperation: 0, averageRetryDelay: 0, marineMetrics: { environmentalRetries: 0, connectivityRetries: 0, powerRetries: 0, sensorRetries: 0 } }; } } exports.RetryManager = RetryManager; /** * Default retry manager instance */ exports.defaultRetryManager = new RetryManager(); /** * Convenience function to execute with retry */ async function executeWithRetry(operation, context) { return exports.defaultRetryManager.execute(operation, context); } exports.executeWithRetry = executeWithRetry; /** * Pre-configured retry contexts for marine operations */ exports.RetryContexts = { /** * Navigation sensor operations */ navigationSensor: (operationName, operationalContext = marine_constants_1.OperationalContext.SAILING) => ({ operationName, operationalContext, marineSystemType: 'navigation', priority: 'critical' }), /** * Safety system operations */ safetyOperation: (operationName, operationalContext = marine_constants_1.OperationalContext.SAILING) => ({ operationName, operationalContext, marineSystemType: 'safety', priority: 'critical' }), /** * Network operations */ networkOperation: (operationName, operationalContext = marine_constants_1.OperationalContext.SAILING) => ({ operationName, operationalContext, marineSystemType: 'comfort', priority: 'normal', customRetryConfig: { maxRetries: 5, baseDelayMs: 2000, retryableFailures: [ marine_constants_1.MarineFailureType.CONNECTIVITY_LOSS, marine_constants_1.MarineFailureType.TIMEOUT ] } }), /** * Maintenance operations */ maintenanceOperation: (operationName) => ({ operationName, operationalContext: marine_constants_1.OperationalContext.MAINTENANCE, marineSystemType: 'maintenance', priority: 'normal', customRetryConfig: { maxRetries: 10, baseDelayMs: 5000, maxDelayMs: 60000 } }), /** * Comfort system operations */ comfortOperation: (operationName, operationalContext = marine_constants_1.OperationalContext.SAILING) => ({ operationName, operationalContext, marineSystemType: 'comfort', priority: 'low', customRetryConfig: { maxRetries: 2, baseDelayMs: 3000 } }) }; //# sourceMappingURL=RetryManager.js.map