failover-sdk
Version:
One-line API failover with zero downtime. Native Rust performance with TypeScript interface.
247 lines • 9.11 kB
JavaScript
;
// 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