UNPKG

@bernierllc/retry-policy

Version:

Atomic retry policy utilities with exponential backoff and jitter

253 lines (252 loc) 8.5 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.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