UNPKG

tdpw

Version:

CLI tool for uploading Playwright test reports to TestDino platform with TestDino storage support

122 lines 4.69 kB
"use strict"; /** * 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