@sailboat-computer/resilience
Version:
Enhanced resilience patterns for sailboat computer v3 with marine-specific adaptations
483 lines • 17.5 kB
JavaScript
"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