@bernierllc/retry-policy
Version:
Atomic retry policy utilities with exponential backoff and jitter
253 lines (252 loc) • 8.5 kB
JavaScript
;
/*
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.RetryPolicyConfigurationLoader = void 0;
exports.setGlobalRetryPolicyConfig = setGlobalRetryPolicyConfig;
exports.getGlobalRetryPolicyConfig = getGlobalRetryPolicyConfig;
exports.clearGlobalRetryPolicyConfig = clearGlobalRetryPolicyConfig;
exports.loadOptionalRetryPolicyConfig = loadOptionalRetryPolicyConfig;
exports.loadOptionalRetryPolicyConfigWithSources = loadOptionalRetryPolicyConfigWithSources;
exports.mergeConfigurations = mergeConfigurations;
const cosmiconfig_1 = require("cosmiconfig");
/**
* Environment variable mappings for retry policy
*/
const ENV_MAPPINGS = {
maxRetries: 'RETRY_MAX_RETRIES',
initialDelayMs: 'RETRY_INITIAL_DELAY',
maxDelayMs: 'RETRY_MAX_DELAY',
backoffFactor: 'RETRY_BACKOFF_FACTOR',
jitter: 'RETRY_JITTER',
enabled: 'RETRY_ENABLED',
'backoff.type': 'RETRY_BACKOFF_TYPE',
'backoff.baseDelay': 'RETRY_BACKOFF_BASE_DELAY',
'backoff.maxDelay': 'RETRY_BACKOFF_MAX_DELAY',
'backoff.factor': 'RETRY_BACKOFF_MULTIPLIER',
'backoff.jitter.type': 'RETRY_JITTER_TYPE',
'backoff.jitter.factor': 'RETRY_JITTER_FACTOR'
};
/**
* Global configuration storage for dependency injection
*/
let globalRetryPolicyConfig = {};
/**
* Set global configuration for dependency injection from service packages
*/
function setGlobalRetryPolicyConfig(config) {
globalRetryPolicyConfig = { ...globalRetryPolicyConfig, ...config };
console.log('⚙️ Retry Policy: Global configuration updated');
}
/**
* Get global configuration
*/
function getGlobalRetryPolicyConfig() {
return { ...globalRetryPolicyConfig };
}
/**
* Clear global configuration
*/
function clearGlobalRetryPolicyConfig() {
globalRetryPolicyConfig = {};
}
/**
* Configuration loader with source tracking
*/
class RetryPolicyConfigurationLoader {
constructor() {
this.sources = [];
}
/**
* Parse environment variable value to appropriate type
*/
parseEnvironmentValue(value) {
// Boolean values
if (value === 'true')
return true;
if (value === 'false')
return false;
// Number values
if (/^\d+$/.test(value))
return parseInt(value);
if (/^\d+\.\d+$/.test(value))
return parseFloat(value);
return value;
}
/**
* Set nested object value using dot notation
*/
setNestedValue(obj, path, value) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (key && !(key in current)) {
current[key] = {};
}
if (key) {
current = current[key];
}
}
const lastKey = keys[keys.length - 1];
if (lastKey) {
current[lastKey] = value;
}
}
/**
* Merge configuration objects recursively
*/
mergeConfiguration(target, source) {
Object.keys(source).forEach(key => {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
if (!target[key])
target[key] = {};
this.mergeConfiguration(target[key], source[key]);
}
else {
target[key] = source[key];
}
});
}
/**
* Load optional configuration from all sources
*/
loadOptionalConfiguration() {
const config = {};
this.sources = [];
// Load global configuration (from service packages)
if (Object.keys(globalRetryPolicyConfig).length > 0) {
this.mergeConfiguration(config, globalRetryPolicyConfig);
this.sources.push({
key: 'global',
value: globalRetryPolicyConfig,
source: 'global',
description: 'Global configuration from service packages'
});
}
// Load configuration file if it exists
const explorer = (0, cosmiconfig_1.cosmiconfigSync)('retry-policy', {
searchPlaces: [
'retry-policy.config.js',
'retry-policy.config.json',
'.retry-policyrc',
'.retry-policyrc.js',
'.retry-policyrc.json'
]
});
try {
const result = explorer.search();
if (result && result.config) {
this.mergeConfiguration(config, result.config);
this.sources.push({
key: 'file',
value: result.config,
source: 'file',
description: `Configuration file: ${result.filepath}`
});
}
}
catch {
// Fail silently - configuration is optional
}
// Apply environment variable overrides
const envOverrides = [];
Object.entries(ENV_MAPPINGS).forEach(([configPath, envVar]) => {
const envValue = process.env[envVar];
if (envValue !== undefined) {
const parsedValue = this.parseEnvironmentValue(envValue);
this.setNestedValue(config, configPath, parsedValue);
envOverrides.push({
key: configPath,
value: parsedValue,
source: 'environment',
description: `Environment variable: ${envVar}=${envValue}`
});
}
});
if (envOverrides.length > 0) {
this.sources.push(...envOverrides);
}
// Log configuration sources for transparency
if (this.sources.length > 0) {
console.log('⚙️ Retry Policy: Runtime configuration loaded');
this.sources.forEach(source => {
console.log(` └── ${source.source}: ${source.description}`);
});
}
return config;
}
/**
* Get configuration sources for transparency
*/
getConfigurationSources() {
return [...this.sources];
}
/**
* Get environment variable documentation
*/
getEnvironmentVariableDocumentation() {
const docs = {};
Object.entries(ENV_MAPPINGS).forEach(([configPath, envVar]) => {
docs[envVar] = `Controls ${configPath} configuration`;
});
return docs;
}
}
exports.RetryPolicyConfigurationLoader = RetryPolicyConfigurationLoader;
/**
* Load optional retry policy configuration
* This function fails gracefully and returns empty config if loading fails
*/
function loadOptionalRetryPolicyConfig() {
try {
const loader = new RetryPolicyConfigurationLoader();
return loader.loadOptionalConfiguration();
}
catch {
// Fail silently - configuration is optional for core packages
return {};
}
}
/**
* Load configuration with source tracking
*/
function loadOptionalRetryPolicyConfigWithSources() {
try {
const loader = new RetryPolicyConfigurationLoader();
const config = loader.loadOptionalConfiguration();
const sources = loader.getConfigurationSources();
return { config, sources };
}
catch {
// Fail silently - return empty config and sources
return { config: {}, sources: [] };
}
}
/**
* Merge configuration with precedence: defaults < global < file < environment < constructor
*/
function mergeConfigurations(...configs) {
const result = {};
configs.forEach(config => {
if (config) {
Object.keys(config).forEach(key => {
const value = config[key];
if (value !== undefined) {
if (key === 'backoff' && typeof value === 'object') {
result.backoff = { ...result.backoff, ...value };
}
else {
result[key] = value;
}
}
});
}
});
return result;
}
//# sourceMappingURL=loadOptionalConfig.js.map