UNPKG

figue

Version:

Platform agnostic configuration management library, with environmental variables and validation, like convict but cross-env and zod schemas

115 lines (109 loc) 3.74 kB
'use strict'; const zod = require('zod'); function createFigueError({ message, code }) { const error = Object.assign(new Error(message), { code, isFigueError: true }); return error; } function createConfigValidationError({ issues }) { const message = issues.map(({ path, message: message2 }) => `${path.join(".")}: ${message2}`).join("\n"); return createFigueError({ message, code: "CONFIG_VALIDATION_ERROR" }); } function mapValues(obj, fn) { return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value, key)])); } function mergeDeep(...sources) { return sources.reduce((acc, source) => { for (const key in source) { const value = source[key]; if (value && value instanceof Object && !Array.isArray(value)) { acc[key] = mergeDeep(acc[key], value); } else if (value !== void 0) { Object.assign(acc, { [key]: value }); } } return acc; }, {}); } function castArray(value) { return Array.isArray(value) ? value : [value]; } function buildConfigSchema({ configDefinition }) { const schema = mapValues(configDefinition, (config) => { if (isConfigDefinitionElement(config)) { return config.schema; } else { return buildConfigSchema({ configDefinition: config }); } }); return zod.z.object(schema); } function isConfigDefinitionElement(config) { try { return config instanceof Object && "schema" in config && config.schema instanceof zod.z.ZodType; } catch (_ignored) { return false; } } function buildEnvConfig({ configDefinition, env }) { return mapValues(configDefinition, (config) => { if (isConfigDefinitionElement(config)) { const { env: envKey } = config; if (envKey === void 0) { return void 0; } const value = env[envKey]; return value; } else { return buildEnvConfig({ configDefinition: config, env }); } }); } function getConfigDefaults({ configDefinition }) { return mapValues(configDefinition, (config) => { if (isConfigDefinitionElement(config)) { const { default: defaultValue } = config; return defaultValue; } else { return getConfigDefaults({ configDefinition: config }); } }); } const isNotFalsy = (value) => Boolean(value); function buildDefaultsConfig({ rawDefaults, getDefaults, envConfig, configDefaults }) { const config = mergeDeep(configDefaults, envConfig); const defaults = castArray(rawDefaults).filter(isNotFalsy); const gotDefaultsRaw = getDefaults?.({ configDefaults, envConfig, config }); const gotDefaults = castArray(gotDefaultsRaw).filter(isNotFalsy); return mergeDeep(...defaults, ...gotDefaults); } function defineConfig(configDefinition, { envSources = [], envSource = {}, defaults: rawDefaults = [], priority = "env", getDefaults } = {}) { const env = [...envSources, envSource].reduce((acc, env2) => ({ ...acc, ...env2 }), {}); const schema = buildConfigSchema({ configDefinition }); const configDefaults = getConfigDefaults({ configDefinition }); const envConfig = buildEnvConfig({ configDefinition, env }); const defaultsConfig = buildDefaultsConfig({ rawDefaults, envConfig, configDefaults, getDefaults }); const mergedConfig = priority === "env" ? mergeDeep(configDefaults, envConfig, defaultsConfig) : mergeDeep(configDefaults, defaultsConfig, envConfig); const parsingResult = schema.safeParse(mergedConfig); if (!parsingResult.success) { throw createConfigValidationError({ issues: parsingResult.error.issues }); } const { data: config } = parsingResult; return { config, env, envConfig, schema }; } exports.buildConfigSchema = buildConfigSchema; exports.defineConfig = defineConfig;