tdpw
Version:
CLI tool for uploading Playwright test reports to TestDino platform with TestDino storage support
122 lines • 4.69 kB
JavaScript
;
/**
* Retry utility with exponential backoff and circuit breaker
* Handles network failures and temporary service unavailability
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.withRetry = withRetry;
exports.withFileUploadRetry = withFileUploadRetry;
const types_1 = require("../types");
/**
* Default retry configuration
*/
const DEFAULT_OPTIONS = {
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 30000,
factor: 2,
timeout: 120000, // 2 minutes as specified
shouldRetry: (error) => {
// Check if error is explicitly marked as non-retryable
if ('retryable' in error &&
error.retryable === false) {
return false;
}
// Don't retry on conflict errors (409) - cache already exists
if (error.message.includes('already exists'))
return false;
// Retry on network errors, timeouts, and 5xx server errors
if (error instanceof types_1.NetworkError)
return true;
if (error.message.includes('timeout'))
return true;
if (error.message.includes('ECONNRESET'))
return true;
if (error.message.includes('ENOTFOUND'))
return true;
if (error.message.includes('fetch failed'))
return true;
// Check for HTTP status codes that warrant retry
if (error.message.match(/50[0-9]/))
return true; // 500-509
if (error.message.includes('429'))
return true; // Rate limiting
return false;
},
};
/**
* Execute an operation with retry logic and exponential backoff
*/
async function withRetry(operation, options = {}) {
const config = { ...DEFAULT_OPTIONS, ...options };
let lastError;
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
try {
// Wrap operation with timeout
const result = await Promise.race([
operation(),
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Operation timeout after ${config.timeout}ms`));
}, config.timeout);
}),
]);
return result;
}
catch (error) {
lastError = error;
// Check if we should retry this error
if (!config.shouldRetry(lastError)) {
throw lastError;
}
// If this is the last attempt, throw the error
if (attempt === config.maxAttempts) {
throw new types_1.NetworkError(`Operation failed after ${config.maxAttempts} attempts: ${lastError.message}`, lastError);
}
// Calculate delay with exponential backoff
const delay = Math.min(config.baseDelay * config.factor ** (attempt - 1), config.maxDelay);
console.warn(`⚠️ Attempt ${attempt}/${config.maxAttempts} failed: ${lastError.message}`);
console.warn(` Retrying in ${delay}ms...`);
// Wait before next attempt
await new Promise(resolve => setTimeout(resolve, delay));
}
}
// This should never be reached, but TypeScript requires it
throw lastError;
}
/**
* Specialized retry for file upload operations
* Uses different retry logic optimized for large file uploads
*/
async function withFileUploadRetry(operation, _fileName) {
return withRetry(operation, {
maxAttempts: 3,
baseDelay: 2000, // Longer initial delay for file operations
maxDelay: 60000, // Allow longer delays for large files
timeout: 300000, // 5 minutes for file uploads
shouldRetry: (error) => {
// More lenient retry for file uploads
const message = error.message.toLowerCase();
// Retry on common file upload failures
if (message.includes('network'))
return true;
if (message.includes('timeout'))
return true;
if (message.includes('connection'))
return true;
if (message.includes('upload'))
return true;
if (message.includes('blob'))
return true;
if (message.includes('storage'))
return true;
// Don't retry on authentication or permission errors
if (message.includes('unauthorized') || message.includes('403'))
return false;
if (message.includes('forbidden'))
return false;
return DEFAULT_OPTIONS.shouldRetry(error);
},
});
}
//# sourceMappingURL=retry.js.map