@tiflux/mcp
Version:
TiFlux MCP Server - Model Context Protocol integration for Claude Code and other AI clients
281 lines (247 loc) • 6.31 kB
JavaScript
/**
* Políticas de retry inteligentes para requisições HTTP
*
* Fornece diferentes estratégias de retry com configurações flexíveis:
* - Exponential backoff
* - Linear backoff
* - Fixed delay
* - Custom conditions
*/
class RetryPolicy {
constructor(options = {}) {
this.config = {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 30000,
strategy: 'exponential', // 'exponential', 'linear', 'fixed'
multiplier: 2,
jitter: true,
...options
};
}
/**
* Determina se deve fazer retry baseado no erro
*/
shouldRetry(error, attempt) {
// Máximo de tentativas atingido
if (attempt >= this.config.maxRetries) {
return false;
}
// Verifica condição customizada se fornecida
if (this.config.retryCondition) {
return this.config.retryCondition(error, attempt);
}
return this._defaultRetryCondition(error, attempt);
}
/**
* Calcula delay para próxima tentativa
*/
calculateDelay(attempt) {
let delay;
switch (this.config.strategy) {
case 'exponential':
delay = this._exponentialBackoff(attempt);
break;
case 'linear':
delay = this._linearBackoff(attempt);
break;
case 'fixed':
delay = this.config.baseDelay;
break;
default:
delay = this._exponentialBackoff(attempt);
}
// Aplica jitter se habilitado
if (this.config.jitter) {
delay = this._addJitter(delay);
}
// Garante delay máximo
return Math.min(delay, this.config.maxDelay);
}
/**
* Condição de retry padrão
*/
_defaultRetryCondition(error, attempt) {
// Network errors - sempre retry
if (error.code === 'ECONNRESET' ||
error.code === 'ECONNREFUSED' ||
error.code === 'ETIMEDOUT' ||
error.code === 'ENOTFOUND') {
return true;
}
// HTTP errors retryables
if (error.statusCode) {
const retryableStatusCodes = [
429, // Too Many Requests
500, // Internal Server Error
502, // Bad Gateway
503, // Service Unavailable
504, // Gateway Timeout
507, // Insufficient Storage
508, // Loop Detected
510, // Not Extended
511 // Network Authentication Required
];
return retryableStatusCodes.includes(error.statusCode);
}
// Timeout errors
if (error.message && error.message.includes('timeout')) {
return true;
}
return false;
}
/**
* Backoff exponencial
*/
_exponentialBackoff(attempt) {
return this.config.baseDelay * Math.pow(this.config.multiplier, attempt);
}
/**
* Backoff linear
*/
_linearBackoff(attempt) {
return this.config.baseDelay * (attempt + 1);
}
/**
* Adiciona jitter aleatório para evitar thundering herd
*/
_addJitter(delay) {
// Jitter between 50% and 150% of original delay
const jitterFactor = 0.5 + Math.random();
return Math.floor(delay * jitterFactor);
}
/**
* Cria política de retry para API TiFlux
*/
static forTiFluxAPI() {
return new RetryPolicy({
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
strategy: 'exponential',
multiplier: 2,
jitter: true,
retryCondition: (error, attempt) => {
// Sempre retry em network errors
if (error.code === 'ECONNRESET' ||
error.code === 'ECONNREFUSED' ||
error.code === 'ETIMEDOUT') {
return true;
}
// Rate limiting - retry com delay maior
if (error.statusCode === 429) {
return true;
}
// Server errors - retry apenas 2 vezes
if (error.statusCode >= 500 && attempt < 2) {
return true;
}
return false;
}
});
}
/**
* Cria política agressiva para operações críticas
*/
static aggressive() {
return new RetryPolicy({
maxRetries: 5,
baseDelay: 500,
maxDelay: 15000,
strategy: 'exponential',
multiplier: 1.5,
jitter: true
});
}
/**
* Cria política conservadora para operações pesadas
*/
static conservative() {
return new RetryPolicy({
maxRetries: 2,
baseDelay: 2000,
maxDelay: 8000,
strategy: 'linear',
jitter: false
});
}
/**
* Política sem retry
*/
static noRetry() {
return new RetryPolicy({
maxRetries: 0
});
}
/**
* Política customizada para upload de arquivos
*/
static forFileUpload() {
return new RetryPolicy({
maxRetries: 2,
baseDelay: 3000,
maxDelay: 20000,
strategy: 'exponential',
multiplier: 2,
jitter: true,
retryCondition: (error, attempt) => {
// Não retry em errors de validação (4xx exceto timeout)
if (error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
return false;
}
// Retry apenas em network/server errors
return error.statusCode >= 500 ||
error.statusCode === 429 ||
error.code === 'ETIMEDOUT' ||
error.code === 'ECONNRESET';
}
});
}
/**
* Executa uma função com retry
*/
async execute(asyncFn, logger = null) {
let lastError;
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
try {
return await asyncFn();
} catch (error) {
lastError = error;
if (!this.shouldRetry(error, attempt)) {
break;
}
const delay = this.calculateDelay(attempt);
if (logger) {
logger.warn(`Retry attempt ${attempt + 1} in ${delay}ms`, {
error: error.message,
attempt: attempt + 1,
maxRetries: this.config.maxRetries,
delay
});
}
await this._delay(delay);
}
}
throw lastError;
}
/**
* Delay helper
*/
_delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Configuração atual
*/
getConfig() {
return { ...this.config };
}
/**
* Atualiza configuração
*/
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
return this;
}
}
module.exports = RetryPolicy;