UNPKG

failover-sdk

Version:

One-line API failover with zero downtime. Native Rust performance with TypeScript interface.

247 lines 9.11 kB
"use strict"; // Config caching and refresh functionality var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.autoLoadConfig = autoLoadConfig; exports.setLocalConfig = setLocalConfig; exports.startConfigRefresh = startConfigRefresh; exports.getCachedConfig = getCachedConfig; exports.stopConfigRefresh = stopConfigRefresh; exports.setCachedConfig = setCachedConfig; const fs_1 = require("fs"); const path_1 = require("path"); // In-memory cached config - starts with safe defaults let cachedConfig = { services: { payments: { active: 'stripe', enabled: ['stripe', 'square', 'paypal', 'adyen'] }, email: { active: 'sendgrid', enabled: ['sendgrid', 'resend', 'mailgun', 'postmark'] }, }, }; // Local configuration override let localConfig = null; let refreshInterval = null; /** * Load configuration from file system */ async function loadConfigFromFile() { const configPaths = [ (0, path_1.join)(process.cwd(), 'failover.config.json'), (0, path_1.join)(process.cwd(), 'failover.config.js'), (0, path_1.join)(require('os').homedir(), '.failover.json'), ]; for (const configPath of configPaths) { try { const configExists = await fs_1.promises .access(configPath) .then(() => true) .catch(() => false); if (!configExists) continue; let config; if (configPath.endsWith('.js')) { // Dynamic import for JS config files const configModule = await Promise.resolve(`${configPath}`).then(s => __importStar(require(s))); config = configModule.default || configModule; } else { // JSON config files const configContent = await fs_1.promises.readFile(configPath, 'utf-8'); config = JSON.parse(configContent); } // Convert CLI config format to SDK format if (config.services) { return convertCliConfigToSdkConfig(config); } // Already in SDK format return config; } catch (error) { console.warn(`[Failover] Failed to load config from ${configPath}:`, error.message); continue; } } return null; } /** * Convert CLI config format to SDK config format */ function convertCliConfigToSdkConfig(cliConfig) { const config = { providers: {}, disableRemoteConfig: true, }; if (cliConfig.services?.payments) { const enabledProviders = cliConfig.services.payments.providers ?.filter((p) => p.enabled) ?.map((p) => p.name) || []; const primaryProvider = cliConfig.services.payments.order?.[0] || enabledProviders[0]; config.providers.payments = { enabled: enabledProviders, primary: primaryProvider, }; } if (cliConfig.services?.email) { const enabledProviders = cliConfig.services.email.providers?.filter((p) => p.enabled)?.map((p) => p.name) || []; const primaryProvider = cliConfig.services.email.order?.[0] || enabledProviders[0]; config.providers.email = { enabled: enabledProviders, primary: primaryProvider, }; } return config; } /** * Auto-load configuration from available sources */ async function autoLoadConfig() { try { const fileConfig = await loadConfigFromFile(); if (fileConfig) { console.log('[Failover] Configuration loaded from file'); return fileConfig; } } catch (error) { console.warn('[Failover] Failed to auto-load configuration:', error.message); } return null; } /** * Set local configuration (overrides remote config) */ function setLocalConfig(config) { localConfig = config; // Apply local config immediately if (config.providers) { if (config.providers.payments) { cachedConfig.services.payments.enabled = config.providers.payments.enabled || cachedConfig.services.payments.enabled; if (config.providers.payments.primary) { cachedConfig.services.payments.active = config.providers.payments.primary; } } if (config.providers.email) { cachedConfig.services.email.enabled = config.providers.email.enabled || cachedConfig.services.email.enabled; if (config.providers.email.primary) { cachedConfig.services.email.active = config.providers.email.primary; } } } console.log('[Failover] Local config applied:', { payments: { active: cachedConfig.services.payments.active, enabled: cachedConfig.services.payments.enabled, }, email: { active: cachedConfig.services.email.active, enabled: cachedConfig.services.email.enabled, }, }); } /** * Start background config refresh from SaaS API (when available) */ async function startConfigRefresh(projectId) { // Skip remote config if local config is set and disableRemoteConfig is true if (localConfig?.disableRemoteConfig) { console.log('[Failover] Remote config disabled, using local config only'); return; } // Check if we should try remote config (when SaaS is available) const shouldTryRemoteConfig = process.env.FAILOVER_ENABLE_REMOTE_CONFIG === 'true'; if (!shouldTryRemoteConfig) { console.log('[Failover] Using local config only (SaaS not enabled)'); return; } const refreshConfig = async () => { try { // Only try remote config if explicitly enabled const apiBaseUrl = process.env.FAILOVER_API_URL || 'https://api.failover.dev'; const response = await fetch(`${apiBaseUrl}/config/${projectId}`); if (response.ok) { const newConfig = (await response.json()); // Validate basic config structure if (newConfig.services && newConfig.services.payments && newConfig.services.email) { // Only update if not overridden by local config if (!localConfig) { cachedConfig = newConfig; console.log('[Failover] Remote config updated:', { payments: cachedConfig.services.payments.active, email: cachedConfig.services.email.active, }); } else { console.log('[Failover] Remote config received but local config takes precedence'); } } } } catch (error) { // Keep existing cached config on failure - no disruption console.warn('[Failover] Remote config not available, using local config:', error.message); } }; // Initial config load await refreshConfig(); // Set up background refresh every 60 seconds if (refreshInterval) { clearInterval(refreshInterval); } refreshInterval = setInterval(refreshConfig, 60000); } /** * Get current cached config (fast, synchronous) */ function getCachedConfig() { return cachedConfig; } /** * Stop config refresh (for testing/cleanup) */ function stopConfigRefresh() { if (refreshInterval) { clearInterval(refreshInterval); refreshInterval = null; } } /** * Override cached config (for testing) */ function setCachedConfig(config) { cachedConfig = config; } //# sourceMappingURL=config-loader.js.map