UNPKG

@bernierllc/retry-policy

Version:

Atomic retry policy utilities with exponential backoff and jitter

312 lines (268 loc) 9.81 kB
#!/usr/bin/env node /* 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. */ /** * Configuration printing script for @bernierllc/retry-policy core package * * Usage: * npm run config:print - Print configuration * npm run config:print -- --sources - Show configuration sources * npm run config:print -- --json - Print in JSON format */ const fs = require('fs'); const path = require('path'); // Package configuration const PACKAGE_NAME = 'retry-policy'; const CONFIG_FILE = `${PACKAGE_NAME}.config.js`; // Environment variable mappings const ENV_MAPPINGS = { 'RETRY_MAX_RETRIES': 'maxRetries', 'RETRY_INITIAL_DELAY': 'initialDelayMs', 'RETRY_MAX_DELAY': 'maxDelayMs', 'RETRY_BACKOFF_FACTOR': 'backoffFactor', 'RETRY_JITTER': 'jitter', 'RETRY_ENABLED': 'enabled', 'RETRY_BACKOFF_TYPE': 'backoff.type', 'RETRY_BACKOFF_BASE_DELAY': 'backoff.baseDelay', 'RETRY_BACKOFF_MAX_DELAY': 'backoff.maxDelay', 'RETRY_BACKOFF_MULTIPLIER': 'backoff.factor', 'RETRY_JITTER_TYPE': 'backoff.jitter.type', 'RETRY_JITTER_FACTOR': 'backoff.jitter.factor' }; class RetryPolicyConfigurationPrinter { constructor() { this.sources = {}; } parseEnvironmentValue(value) { if (value === 'true') return true; if (value === 'false') return false; if (/^\d+$/.test(value)) return parseInt(value); if (/^\d+\.\d+$/.test(value)) return parseFloat(value); return value; } 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 in current)) { current[key] = {}; } current = current[key]; } current[keys[keys.length - 1]] = value; } 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]; } }); } loadConfiguration() { // Default configuration const config = { maxRetries: 5, initialDelayMs: 1000, maxDelayMs: 30000, backoffFactor: 2, jitter: true, enabled: true, backoff: { type: 'exponential', baseDelay: 1000, maxDelay: 30000, factor: 2, jitter: { type: 'full', factor: 0.1 } } }; // Try to load from built package if available try { const builtPackage = require('../dist/index.js'); if (builtPackage.loadOptionalRetryPolicyConfigWithSources) { const result = builtPackage.loadOptionalRetryPolicyConfigWithSources(); if (result.config && Object.keys(result.config).length > 0) { this.mergeConfiguration(config, result.config); } // Track sources result.sources?.forEach(source => { this.sources[source.key || source.source] = source; }); return config; } } catch (error) { console.log('📦 Using fallback configuration loading (package not built)'); } // Fallback: manual loading // Load configuration file if it exists const configPath = path.join(process.cwd(), CONFIG_FILE); if (fs.existsSync(configPath)) { try { delete require.cache[require.resolve(configPath)]; const fileConfig = require(configPath); this.mergeConfiguration(config, fileConfig); this.sources['file'] = { source: 'file', description: `Configuration file: ${CONFIG_FILE}` }; } catch (error) { console.warn(`⚠️ Failed to load ${CONFIG_FILE}:`, error.message); } } // Apply environment variable overrides Object.entries(ENV_MAPPINGS).forEach(([envVar, configPath]) => { if (process.env[envVar]) { const envValue = this.parseEnvironmentValue(process.env[envVar]); this.setNestedValue(config, configPath, envValue); this.sources[configPath] = { source: 'environment', description: `Environment variable: ${envVar}=${process.env[envVar]}` }; } }); return config; } printConfiguration(config, options = {}) { const { showSources = false, format = 'table' } = options; console.log(`🔧 ${PACKAGE_NAME} Configuration (${showSources ? 'with sources' : 'core package'})`); console.log('='.repeat(60)); console.log(''); if (format === 'json') { if (showSources) { const configWithSources = {}; this.flattenObject(config).forEach(([key, value]) => { configWithSources[key] = { value: value, source: this.sources[key] || { source: 'default', description: 'Package default' } }; }); console.log(JSON.stringify(configWithSources, null, 2)); } else { console.log(JSON.stringify(config, null, 2)); } return; } // Print configuration hierarchy for core packages console.log('📊 Configuration Hierarchy (Core Package):'); console.log(' 1. 📦 Package Defaults'); console.log(' 2. 🌍 Global Configuration (from service packages)'); console.log(' 3. 📄 Configuration File (retry-policy.config.js)'); console.log(' 4. 🌍 Environment Variables (RETRY_*)'); console.log(' 5. 🔄 Constructor Parameters (highest priority)'); console.log(''); // Core settings this.printSection('Retry Policy Settings', { maxRetries: config.maxRetries, initialDelayMs: config.initialDelayMs, maxDelayMs: config.maxDelayMs, backoffFactor: config.backoffFactor, jitter: config.jitter, enabled: config.enabled }, showSources); // Backoff configuration if (config.backoff) { this.printSection('Backoff Strategy', { type: config.backoff.type, baseDelay: config.backoff.baseDelay, maxDelay: config.backoff.maxDelay, factor: config.backoff.factor }, showSources); if (config.backoff.jitter) { this.printSection('Jitter Configuration', { type: config.backoff.jitter.type, factor: config.backoff.jitter.factor }, showSources); } } // Show environment variable overrides const envOverrides = Object.entries(this.sources) .filter(([, source]) => source.source === 'environment'); if (envOverrides.length > 0) { console.log('🌍 Environment Variable Overrides:'); envOverrides.forEach(([key, source]) => { console.log(` └── ${source.description}`); }); console.log(''); } // Show available environment variables console.log('🔧 Available Environment Variables:'); Object.entries(ENV_MAPPINGS).forEach(([envVar, configPath]) => { const isSet = process.env[envVar] ? '✅' : '⚪'; console.log(` ${isSet} ${envVar}${configPath}`); }); console.log(''); // Core package specific notes console.log('📖 Core Package Notes:'); console.log(' • Configuration is optional - package works without it'); console.log(' • Constructor parameters override configuration'); console.log(' • Service packages can inject global configuration'); console.log(' • No breaking changes to existing usage patterns'); console.log(''); // Show integration example console.log('🔗 Service Package Integration:'); console.log(' import { setGlobalRetryPolicyConfig } from "@bernierllc/retry-policy";'); console.log(' setGlobalRetryPolicyConfig({ maxRetries: 5, enabled: true });'); } printSection(title, section, showSources) { console.log(`📋 ${title}:`); Object.entries(section).forEach(([key, value]) => { const source = this.sources[key]; const icon = source ? { 'default': '📦', 'file': '📄', 'environment': '🌍', 'global': '🔗' }[source.source] || '❓' : '📦'; const displayValue = typeof value === 'object' ? '[object]' : String(value); if (showSources && source) { console.log(` ${icon} ${key}: ${displayValue} (${source.source})`); } else { console.log(` ${icon} ${key}: ${displayValue}`); } }); console.log(''); } flattenObject(obj, prefix = '') { const flattened = []; for (const key in obj) { if (obj.hasOwnProperty(key)) { const newKey = prefix ? `${prefix}.${key}` : key; if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { flattened.push(...this.flattenObject(obj[key], newKey)); } else { flattened.push([newKey, obj[key]]); } } } return flattened; } } function main() { const args = process.argv.slice(2); const options = { showSources: args.includes('--sources') || args.includes('-s'), format: args.includes('--json') ? 'json' : 'table' }; const printer = new RetryPolicyConfigurationPrinter(); try { const config = printer.loadConfiguration(); printer.printConfiguration(config, options); } catch (error) { console.error('❌ Failed to print configuration:', error.message); process.exit(1); } } if (require.main === module) { main(); } module.exports = { RetryPolicyConfigurationPrinter };