@bernierllc/retry-policy
Version:
Atomic retry policy utilities with exponential backoff and jitter
230 lines (229 loc) • 7.88 kB
JavaScript
"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