@creditkarma/dynamic-config
Version:
Dynamic Config for Node.js backed by Consul and Vault
381 lines • 13.2 kB
JavaScript
;
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