UNPKG

@creditkarma/dynamic-config

Version:

Dynamic Config for Node.js backed by Consul and Vault

381 lines 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getConfigForKey = exports.readConfigValue = exports.setValueForKey = exports.isConfigPlaceholder = exports.isValidRemote = exports.normalizeConfigPlaceholder = exports.readValueForType = exports.makeTranslator = exports.setErrorForKey = exports.getErrorForKey = exports.emptyRootConfig = void 0; const Utils = require("./basic"); const object_1 = require("./object"); const json_1 = require("./json"); const errors_1 = require("../errors"); const logger_1 = require("../logger"); const errors_2 = require("../errors"); const emptyRootConfig = () => ({ type: 'root', properties: {}, watcher: null, }); exports.emptyRootConfig = emptyRootConfig; function getErrorForKey(key, errorMap) { if (key !== undefined) { return errorMap[key]; } else { const keys = Object.keys(errorMap); if (keys.length > 0) { return errorMap[keys[0]]; } else { return; } } } exports.getErrorForKey = getErrorForKey; function setErrorForKey(key, error, errorMap) { const parts = key .split('.') .map((next) => next.trim()) .filter((next) => next !== ''); let soFar = ''; for (const part of parts) { soFar = soFar !== '' ? `${soFar}.${part}` : part; errorMap[soFar] = error; } return errorMap; } exports.setErrorForKey = setErrorForKey; function makeTranslator(translators) { return function applyTranslators(obj) { return (0, object_1.deepMap)((val, path) => { try { return translators.reduce((acc, next) => { return next.translate(acc); }, val); } catch (err) { throw new errors_2.InvalidConfigValue(path, err instanceof Error ? err.message : `Non Error Thrown: ${err}`); } }, obj); }; } exports.makeTranslator = makeTranslator; async function readValueForType(key, raw, type) { const rawType = typeof raw; if (rawType === 'string') { try { switch (type) { case 'object': case 'array': return Promise.resolve(JSON.parse(raw)); case 'number': return Promise.resolve(parseFloat(raw)); case 'boolean': if (raw === 'true') { return Promise.resolve(true); } else if (raw === 'false') { return Promise.resolve(false); } else { throw new errors_1.DynamicConfigInvalidType(key, type); } default: return Promise.resolve(raw); } } catch (err) { logger_1.defaultLogger.error(`Unable to parse value[${raw}] as type[${type}]`); throw new errors_1.DynamicConfigInvalidType(key, type); } } else { logger_1.defaultLogger.log(`Raw value of type[${rawType}] being returned as is`); return Promise.resolve(raw); } } exports.readValueForType = readValueForType; function normalizeConfigPlaceholder(path, placeholder, resolvers) { const source = placeholder._source; const resolver = resolvers[source]; if (resolver === undefined) { throw new errors_1.ResolverUnavailable(placeholder._key); } else { return { path: path.join('.'), key: placeholder._key, altKey: placeholder._altKey, resolver: { name: source, type: resolver.type, }, type: placeholder._type || 'string', nullable: placeholder._nullable || false, default: placeholder._default, }; } } exports.normalizeConfigPlaceholder = normalizeConfigPlaceholder; function isValidRemote(name, resolvers) { return resolvers.has(name); } exports.isValidRemote = isValidRemote; function isConfigPlaceholder(obj) { return (0, json_1.objectMatchesSchema)({ type: 'object', properties: { _key: { type: 'string', }, _altKey: { type: 'string', }, _source: { type: 'string', }, _type: { type: 'string', }, _nullable: { type: 'boolean', }, _default: {}, }, required: ['_key', '_source'], }, obj); } exports.isConfigPlaceholder = isConfigPlaceholder; function newConfigValue(oldValue, newValue) { switch (newValue.type) { case 'array': return { source: newValue.source, type: newValue.type, items: newValue.items, watcher: oldValue.watcher, nullable: newValue.nullable, }; case 'object': return { source: newValue.source, type: newValue.type, properties: newValue.properties, watcher: oldValue.watcher, nullable: newValue.nullable, }; default: return { source: newValue.source, type: newValue.type, value: newValue.value, watcher: oldValue.watcher, nullable: newValue.nullable, }; } } function setBaseConfigValueForKey(key, newValue, oldValue, alertWatchers = false) { const [head, ...tail] = Utils.splitKey(key); if (oldValue.type === 'object') { const returnValue = { source: oldValue.source, type: oldValue.type, properties: Object.keys(oldValue.properties).reduce((acc, next) => { const oldValueAtKey = oldValue.properties[next]; if (next === head) { if (tail.length > 0) { acc[next] = setBaseConfigValueForKey(tail.join('.'), newValue, oldValueAtKey, alertWatchers); } else { acc[next] = newConfigValue(oldValueAtKey, newValue); acc[next].watcher = oldValueAtKey.watcher; if (alertWatchers && oldValueAtKey.watcher) { oldValueAtKey.watcher(undefined, readConfigValue(newValue)); } } } else { acc[next] = oldValueAtKey; } return acc; }, {}), watcher: oldValue.watcher, nullable: newValue.nullable, }; if (alertWatchers && returnValue.watcher) { returnValue.watcher(undefined, readConfigValue(returnValue)); } return returnValue; } else if (oldValue.type === 'array') { const headIndex = parseInt(head, 10); const returnValue = { source: oldValue.source, type: oldValue.type, items: oldValue.items.reduce((acc, nextValue, index) => { if (index === headIndex) { if (tail.length > 0) { acc.push(setBaseConfigValueForKey(tail.join('.'), newValue, nextValue, alertWatchers)); } else { const tempValue = newConfigValue(nextValue, newValue); tempValue.watcher = nextValue.watcher; acc.push(tempValue); if (alertWatchers && nextValue.watcher) { nextValue.watcher(undefined, readConfigValue(newValue)); } } } else { acc.push(nextValue); } return acc; }, []), watcher: oldValue.watcher, nullable: newValue.nullable, }; if (alertWatchers && returnValue.watcher) { returnValue.watcher(undefined, readConfigValue(returnValue)); } return returnValue; } else if (tail.length === 0) { const returnValue = newConfigValue(oldValue, newValue); if (alertWatchers && returnValue.watcher !== null) { returnValue.watcher(undefined, readConfigValue(newValue)); } return returnValue; } else { throw new Error(`Cannot set value at key[${key}] because it is not an object`); } } function setRootConfigValueForKey(key, newValue, oldValue, alertWatchers = false) { const [head, ...tail] = Utils.splitKey(key); const returnValue = { type: 'root', properties: Object.keys(oldValue.properties).reduce((acc, next) => { const oldValueAtKey = oldValue.properties[next]; if (next === head) { if (tail.length > 0) { acc[next] = setBaseConfigValueForKey(tail.join('.'), newValue, oldValueAtKey, alertWatchers); } else { acc[next] = newConfigValue(oldValueAtKey, newValue); acc[next].watcher = oldValueAtKey.watcher; if (alertWatchers && oldValueAtKey.watcher) { oldValueAtKey.watcher(undefined, readConfigValue(newValue)); } } } else { acc[next] = oldValueAtKey; } return acc; }, {}), watcher: oldValue.watcher, }; if (alertWatchers && returnValue.watcher) { returnValue.watcher(undefined, readConfigValue(returnValue)); } return returnValue; } function setValueForKey(key, newValue, oldConfig, alertWatchers = false) { if (oldConfig.type === 'root') { return setRootConfigValueForKey(key, newValue, oldConfig, alertWatchers); } else { return setBaseConfigValueForKey(key, newValue, oldConfig, alertWatchers); } } exports.setValueForKey = setValueForKey; function buildObjectValue(obj) { const objectValue = {}; for (const key of Object.keys(obj.properties)) { objectValue[key] = readConfigValue(obj.properties[key]); } return objectValue; } function readConfigValue(obj) { if (obj === null) { return null; } else { switch (obj.type) { case 'root': case 'object': return buildObjectValue(obj); case 'array': return obj.items.reduce((acc, next) => { acc.push(readConfigValue(next)); return acc; }, []); case 'string': case 'number': case 'boolean': return obj.value; case 'placeholder': logger_1.defaultLogger.warn(`Trying to read value of unresolved Placeholder`); return null; case 'promise': logger_1.defaultLogger.warn(`Trying to read value of unresolved Promise`); return null; default: return null; } } } exports.readConfigValue = readConfigValue; function getValueFromConfigValue(key, obj) { if (Utils.isPrimitive(obj) || Utils.isNothing(obj)) { return null; } else { const parts = Utils.splitKey(key); if (parts.length > 1) { const [head, ...tail] = parts; if (obj.type === 'object') { return getValueFromConfigValue(tail.join('.'), obj.properties[head]); } else if (obj.type === 'array' && Utils.isNumeric(head)) { return getValueFromConfigValue(tail.join('.'), obj.items[parseInt(head, 10)]); } else { return null; } } else if (obj.type === 'object' && obj.properties[key] !== undefined) { return obj.properties[key]; } else if (obj.type === 'array' && Utils.isNumeric(key)) { const headIndex = parseInt(key, 10); if (obj.items[headIndex] !== undefined) { return obj.items[headIndex]; } else { return null; } } else { return null; } } } function getConfigForKey(key, obj) { if (Utils.isPrimitive(obj) || Utils.isNothing(obj)) { return null; } else { const parts = Utils.splitKey(key); if (parts.length > 1) { const [head, ...tail] = parts; return getValueFromConfigValue(tail.join('.'), obj.properties[head]); } else if (obj.properties[parts[0]] !== undefined) { return obj.properties[parts[0]]; } else { return null; } } } exports.getConfigForKey = getConfigForKey; //# sourceMappingURL=config.js.map