@dima_aryze/reforge
Version:
TypeScript/JavaScript SDK for Reforge - Cross-chain token operations
154 lines • 5.33 kB
JavaScript
"use strict";
/**
* Retry handler for HTTP requests with exponential backoff
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RetryHandler = void 0;
const errors_1 = require("../errors");
const utils_1 = require("../utils");
/**
* Handles retry logic for HTTP requests with intelligent backoff strategies
*/
class RetryHandler {
constructor(config,
// eslint-disable-next-line no-unused-vars
logger) {
Object.defineProperty(this, "logger", {
enumerable: true,
configurable: true,
writable: true,
value: logger
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.config = {
attempts: config.attempts ?? 3,
delay: config.delay ?? 1000,
maxDelay: config.maxDelay ?? 30000,
backoffMultiplier: config.backoffMultiplier ?? 2,
};
}
/**
* Execute a function with retry logic
*/
async execute(fn, customConfig) {
const retryConfig = customConfig ? { ...this.config, ...customConfig } : this.config;
let lastError;
const maxAttempts = retryConfig.attempts;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
const result = await fn(attempt);
if (attempt > 0) {
this.logger?.info(`Request succeeded after ${attempt + 1} attempts`);
}
return result;
}
catch (error) {
lastError = error instanceof errors_1.ReforgeError ? error : new errors_1.ReforgeError(String(error));
// Don't retry on the last attempt
if (attempt === maxAttempts - 1) {
this.logger?.error(`Request failed after ${maxAttempts} attempts`, {
error: lastError.message,
finalAttempt: true,
});
break;
}
// Check if error is retryable
if (!this.shouldRetry(lastError, attempt)) {
this.logger?.debug('Error is not retryable, stopping attempts', {
error: lastError.message,
attempt: attempt + 1,
});
break;
}
// Calculate delay and wait
const delay = (0, utils_1.exponentialBackoff)(attempt, retryConfig.delay, retryConfig.maxDelay, retryConfig.backoffMultiplier);
this.logger?.debug(`Retrying request after ${delay}ms`, {
attempt: attempt + 1,
maxAttempts,
error: lastError.message,
delay,
});
await (0, utils_1.sleep)(delay);
}
}
throw lastError;
}
/**
* Determine if an error should trigger a retry
*/
shouldRetry(error, attempt) {
// Don't retry if we've exceeded max attempts
if (attempt >= this.config.attempts - 1) {
return false;
}
// Always retry network errors
if (error.name === 'ReforgeNetworkError') {
return true;
}
// Handle API errors
if (error instanceof errors_1.ReforgeApiError) {
return this.shouldRetryApiError(error);
}
// Retry generic errors by default (unless they're validation errors)
return error.name !== 'ReforgeValidationError';
}
/**
* Determine if an API error should be retried
*/
shouldRetryApiError(error) {
// Always retry server errors (5xx)
if (error.isServerError()) {
return true;
}
// Retry specific client errors
if (error.isClientError()) {
// Retry rate limit errors
if (error.hasCode('RATE_LIMIT') || error.status === 429) {
return true;
}
// Retry request timeout
if (error.status === 408) {
return true;
}
// Don't retry other client errors (authentication, validation, etc.)
return false;
}
// Don't retry other status codes
return false;
}
/**
* Get current retry configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Update retry configuration
*/
updateConfig(newConfig) {
Object.assign(this.config, newConfig);
}
/**
* Calculate the total maximum time that could be spent retrying
*/
calculateMaxRetryTime() {
let totalTime = 0;
for (let attempt = 0; attempt < this.config.attempts - 1; attempt++) {
totalTime += (0, utils_1.exponentialBackoff)(attempt, this.config.delay, this.config.maxDelay, this.config.backoffMultiplier);
}
return totalTime;
}
/**
* Create a child retry handler with modified configuration
*/
child(config) {
return new RetryHandler({ ...this.config, ...config }, this.logger);
}
}
exports.RetryHandler = RetryHandler;
//# sourceMappingURL=retry.js.map