UNPKG

@bernierllc/retry-policy

Version:

Atomic retry policy utilities with exponential backoff and jitter

230 lines (229 loc) 7.88 kB
"use strict"; /* Copyright (c) 2025 Bernier LLC This file is licensed to the client under a limited-use license. The client may use and modify this code *only within the scope of the project it was delivered for*. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RetryPolicy = exports.DEFAULT_BACKOFF_CONFIG = exports.DEFAULT_JITTER_CONFIG = exports.DEFAULT_RETRY_OPTIONS = void 0; exports.createRetryPolicy = createRetryPolicy; exports.calculateRetryDelay = calculateRetryDelay; exports.shouldRetry = shouldRetry; const loadOptionalConfig_1 = require("./config/loadOptionalConfig"); /** * Default retry policy options */ exports.DEFAULT_RETRY_OPTIONS = { maxRetries: 5, initialDelayMs: 1000, maxDelayMs: 30000, backoffFactor: 2, jitter: true, shouldRetry: () => true, onRetry: () => { }, onFailure: () => { } }; /** * Default jitter configuration */ exports.DEFAULT_JITTER_CONFIG = { type: 'full', factor: 0.1 }; /** * Default backoff configuration */ exports.DEFAULT_BACKOFF_CONFIG = { type: 'exponential', baseDelay: 1000, maxDelay: 30000, factor: 2, jitter: exports.DEFAULT_JITTER_CONFIG }; /** * Core retry policy class that provides atomic retry utilities with optional runtime configuration */ class RetryPolicy { constructor(options, backoffConfig) { // Load optional runtime configuration const runtimeConfig = (0, loadOptionalConfig_1.loadOptionalRetryPolicyConfig)(); // Map runtime config to constructor parameters format, filtering out undefined values const runtimeOptions = {}; if (runtimeConfig.maxRetries !== undefined) runtimeOptions.maxRetries = runtimeConfig.maxRetries; if (runtimeConfig.initialDelayMs !== undefined) runtimeOptions.initialDelayMs = runtimeConfig.initialDelayMs; if (runtimeConfig.maxDelayMs !== undefined) runtimeOptions.maxDelayMs = runtimeConfig.maxDelayMs; if (runtimeConfig.backoffFactor !== undefined) runtimeOptions.backoffFactor = runtimeConfig.backoffFactor; if (runtimeConfig.jitter !== undefined) runtimeOptions.jitter = runtimeConfig.jitter; if (runtimeConfig.shouldRetry !== undefined) runtimeOptions.shouldRetry = runtimeConfig.shouldRetry; if (runtimeConfig.onRetry !== undefined) runtimeOptions.onRetry = runtimeConfig.onRetry; if (runtimeConfig.onFailure !== undefined) runtimeOptions.onFailure = runtimeConfig.onFailure; const runtimeBackoffConfig = runtimeConfig.backoff || {}; // Merge configuration: defaults < runtime config < constructor params this.options = { ...exports.DEFAULT_RETRY_OPTIONS, ...runtimeOptions, ...options }; this.backoffConfig = { ...exports.DEFAULT_BACKOFF_CONFIG, ...runtimeBackoffConfig, ...backoffConfig }; // Check if retry functionality is globally disabled if (runtimeConfig.enabled === false) { // Override maxRetries to 0 to disable retries this.options.maxRetries = 0; } } /** * Evaluate whether an operation should be retried based on current attempt and error */ evaluateRetry(attempt, error) { const shouldRetry = this.shouldRetry(attempt, error); const delay = shouldRetry ? this.calculateDelay(attempt) : 0; const isFinalAttempt = attempt >= this.options.maxRetries; return { shouldRetry, delay, attempt, isFinalAttempt }; } /** * Calculate the delay for the next retry attempt */ calculateDelay(attempt) { let delay; switch (this.backoffConfig.type) { case 'exponential': delay = this.calculateExponentialDelay(attempt); break; case 'linear': delay = this.calculateLinearDelay(attempt); break; case 'constant': delay = this.backoffConfig.baseDelay; break; default: delay = this.calculateExponentialDelay(attempt); } // Apply jitter if enabled if (this.options.jitter && this.backoffConfig.jitter) { delay = this.applyJitter(delay, this.backoffConfig.jitter); } // Ensure delay doesn't exceed maximum return Math.min(delay, this.backoffConfig.maxDelay); } /** * Calculate exponential backoff delay */ calculateExponentialDelay(attempt) { const factor = this.backoffConfig.factor || 2; return this.backoffConfig.baseDelay * Math.pow(factor, attempt); } /** * Calculate linear backoff delay */ calculateLinearDelay(attempt) { return this.backoffConfig.baseDelay * (attempt + 1); } /** * Apply jitter to a delay value */ applyJitter(delay, jitterConfig) { const random = Math.random(); switch (jitterConfig.type) { case 'full': // Full jitter: random value between 0 and delay return delay * random; case 'equal': // Equal jitter: random value between delay/2 and delay return delay * (0.5 + random * 0.5); case 'decorrelated': // Decorrelated jitter: random value between delay and delay * 3 return delay * (1 + random * 2); case 'none': default: return delay; } } /** * Determine if an operation should be retried based on attempt count and error */ shouldRetry(attempt, error) { // Don't retry if we've exceeded max attempts if (attempt >= this.options.maxRetries) { return false; } // Use custom retry condition if provided return this.options.shouldRetry(error); } /** * Get the current retry policy options */ getOptions() { return { ...this.options }; } /** * Get the current backoff configuration */ getBackoffConfig() { return { ...this.backoffConfig }; } /** * Update retry policy options */ updateOptions(options) { this.options = { ...this.options, ...options }; } /** * Update backoff configuration */ updateBackoffConfig(config) { this.backoffConfig = { ...this.backoffConfig, ...config }; } /** * Get configuration sources for transparency */ getConfigurationSources() { try { const { loadOptionalRetryPolicyConfigWithSources } = require('./config/loadOptionalConfig'); const { sources } = loadOptionalRetryPolicyConfigWithSources(); return sources; } catch (error) { return []; } } } exports.RetryPolicy = RetryPolicy; /** * Factory function to create a retry policy with default options */ function createRetryPolicy(options, backoffConfig) { return new RetryPolicy(options, backoffConfig); } /** * Utility function to calculate delay for a specific attempt */ function calculateRetryDelay(attempt, options = {}, backoffConfig = {}) { const policy = createRetryPolicy(options, backoffConfig); return policy.calculateDelay(attempt); } /** * Utility function to determine if an error should trigger a retry */ function shouldRetry(attempt, error, options = {}) { const policy = createRetryPolicy(options); return policy.evaluateRetry(attempt, error).shouldRetry; } //# sourceMappingURL=retry-policy.js.map