UNPKG

figue

Version:

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

154 lines (151 loc) 4.72 kB
//#region src/figue.errors.ts 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: message$1, definition }) => `${path?.join(".")}${definition?.env ? ` (${definition.env})` : ""}: ${message$1}`).join("\n"); return createFigueError({ message, code: "CONFIG_VALIDATION_ERROR" }); } //#endregion //#region src/utils.ts 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]; } //#endregion //#region src/figue.ts function validateConfig({ configDefinition, configValues, path = [] }) { const config = {}; const issues = []; for (const key in configDefinition) { const currentPath = [...path, key]; const value = configValues[key]; const definition = configDefinition[key]; const nestedResult = validateConfigElement({ definition, value, currentPath }); config[key] = nestedResult.config; issues.push(...nestedResult.issues); } return { config, issues }; } function validateConfigDefinitionElement({ definition, value, currentPath }) { const validation = definition.schema["~standard"].validate(value); if (validation instanceof Promise) throw new TypeError("Schema validation must be synchronous"); if (validation.issues) return { issues: validation.issues.map((issue) => ({ ...issue, path: currentPath, definition })) }; return { config: validation.value, issues: [] }; } function validateConfigElement({ definition, value, currentPath }) { if (isConfigDefinitionElement(definition)) return validateConfigDefinitionElement({ definition, value, currentPath }); if (typeof value === "object" && value !== null) return validateConfig({ configDefinition: definition, configValues: value, path: currentPath }); return { issues: [{ path: currentPath, message: `Expected object with schema at ${currentPath.join(".")}, got ${typeof value}` }] }; } function isConfigDefinitionElement(config) { return typeof config === "object" && config !== null && "schema" in config && typeof config.schema === "object" && config.schema !== null && "~standard" in config.schema; } 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, env$1) => ({ ...acc, ...env$1 }), {}); 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 { issues, config } = validateConfig({ configDefinition, configValues: mergedConfig }); if (issues.length > 0) throw createConfigValidationError({ issues }); return { config, env, envConfig }; } //#endregion export { defineConfig }; //# sourceMappingURL=index.js.map