@jepz20/conman
Advanced tools
Comparing version 0.0.8 to 0.0.9
{ | ||
"name": "@jepz20/conman", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"description": "Configuration manager that supports ttl and plugabble sources", | ||
@@ -17,3 +17,3 @@ "main": "./src/conman", | ||
}, | ||
"gitHead": "072fbce84301f7f39f0c7385d9b9c4b9e165f895", | ||
"gitHead": "86f055464765681dd538c96d5bf15e804c511571", | ||
"lint-staged": { | ||
@@ -20,0 +20,0 @@ "src/**/*.*": [ |
@@ -14,4 +14,3 @@ const { mergeDeepRight } = require('ramda'); | ||
const cacheFileName = `${appRoot}/conman.cache.json`; | ||
let isInitialized = false; | ||
const defaultCacheFileName = `${appRoot}/conman.cache.json`; | ||
@@ -23,21 +22,5 @@ const defaultOptions = { | ||
useFile: true, | ||
cacheFileName | ||
defaultCacheFileName | ||
}; | ||
let conman; | ||
let sources = []; | ||
let options = defaultOptions; | ||
let privateCache; | ||
let ttlInterval; | ||
let selector; | ||
let logger; | ||
function _prepareCacheObject(tree) { | ||
return { | ||
lastModified: new Date().getTime(), | ||
data: tree | ||
}; | ||
} | ||
const _log = opts => (type, ...args) => { | ||
@@ -49,187 +32,194 @@ if (opts.logEnabled) { | ||
// _serialPromises(() => { | ||
function conman(userOptions) { | ||
let sources = []; | ||
let privateCache; | ||
let ttlInterval; | ||
let logger; | ||
let options = { ...defaultOptions, ...userOptions }; | ||
// }) | ||
function _writeToFile(cacheObject, opts) { | ||
if (!opts.useFile) { | ||
return Promise.resolve(); | ||
logger = _log(options); | ||
const selector = nodeSelector(logger); | ||
function _prepareCacheObject(tree) { | ||
return { | ||
lastModified: new Date().getTime(), | ||
data: tree | ||
}; | ||
} | ||
logger('log', 'Writing conman cache to file'); | ||
return jsonfile | ||
.writeFile(opts.cacheFileName, cacheObject, { | ||
spaces: 2, | ||
EOL: '\r\n' | ||
}) | ||
.then(res => { | ||
logger( | ||
'log', | ||
`Succesfully wrote conman cache to file ${opts.cacheFileName}` | ||
); | ||
return res; | ||
}) | ||
.catch(error => { | ||
logger( | ||
'error', | ||
`Couldn't write cache to file ${opts.cacheFileName}`, | ||
error | ||
); | ||
}); | ||
} | ||
function _writeToFile(cacheObject, opts) { | ||
if (!opts.useFile) { | ||
return Promise.resolve(); | ||
} | ||
function _isExpired(cache, opts) { | ||
return new Date().getTime() - cache.lastModified <= opts.ttl; | ||
} | ||
logger('log', 'Writing conman cache to file'); | ||
return jsonfile | ||
.writeFile(opts.cacheFileName, cacheObject, { | ||
spaces: 2, | ||
EOL: '\r\n' | ||
}) | ||
.then(res => { | ||
logger( | ||
'log', | ||
`Succesfully wrote conman cache to file ${opts.cacheFileName}` | ||
); | ||
return res; | ||
}) | ||
.catch(error => { | ||
logger( | ||
'error', | ||
`Couldn't write cache to file ${opts.cacheFileName}`, | ||
error | ||
); | ||
}); | ||
} | ||
function _readFromFile(opts) { | ||
return jsonfile | ||
.readFile(opts.cacheFileName) | ||
.then(cache => { | ||
if (cache && cache.lastModified && _isExpired(cache, opts)) { | ||
logger('log', `Succesfully read cache from file ${opts.cacheFileName}`); | ||
return cache.data; | ||
} | ||
return null; | ||
}) | ||
.catch(err => { | ||
logger( | ||
'error', | ||
`Could not read cache config file "${opts.cacheFileName}"`, | ||
err | ||
); | ||
return null; | ||
}); | ||
} | ||
function _isExpired(cache, opts) { | ||
return new Date().getTime() - cache.lastModified <= opts.ttl; | ||
} | ||
function _validateSource(source) { | ||
if (typeof source.build !== 'function') { | ||
return 'Source should have a build function'; | ||
function _readFromFile(opts) { | ||
return jsonfile | ||
.readFile(opts.cacheFileName) | ||
.then(cache => { | ||
if (cache && cache.lastModified && _isExpired(cache, opts)) { | ||
logger( | ||
'log', | ||
`Succesfully read cache from file ${opts.cacheFileName}` | ||
); | ||
return cache.data; | ||
} | ||
return null; | ||
}) | ||
.catch(err => { | ||
logger( | ||
'error', | ||
`Could not read cache config file "${opts.cacheFileName}"`, | ||
err | ||
); | ||
return null; | ||
}); | ||
} | ||
if (!source.type) { | ||
return 'Source should have a type'; | ||
} | ||
return null; | ||
} | ||
function _triggerInterval() { | ||
clearInterval(ttlInterval); | ||
if (options.ttl <= 0) { | ||
return; | ||
function _validateSource(source) { | ||
if (typeof source.build !== 'function') { | ||
return 'Source should have a build function'; | ||
} | ||
if (!source.type) { | ||
return 'Source should have a type'; | ||
} | ||
return null; | ||
} | ||
ttlInterval = setInterval(build, options.ttl); | ||
} | ||
function _triggerInterval() { | ||
clearInterval(ttlInterval); | ||
if (options.ttl <= 0) { | ||
return; | ||
} | ||
function _init(userOptions = {}) { | ||
options = { ...defaultOptions, ...userOptions }; | ||
logger = _log(options); | ||
isInitialized = true; | ||
selector = nodeSelector(logger); | ||
return conman; | ||
} | ||
ttlInterval = setInterval(build, options.ttl); | ||
} | ||
function addSource(source) { | ||
const sourceError = _validateSource(source); | ||
if (sourceError) { | ||
throw new Error(sourceError); | ||
function addSource(source) { | ||
const sourceError = _validateSource(source); | ||
if (sourceError) { | ||
throw new Error(sourceError); | ||
} | ||
logger('log', `Source "${source.type}" added to conman`); | ||
sources.push(source); | ||
return this; | ||
} | ||
logger('log', `Source "${source.type}" added to conman`); | ||
sources.push(source); | ||
return conman; | ||
} | ||
function _get(key, _privateCache) { | ||
if (key === undefined) { | ||
return _privateCache; | ||
function _get(key, _privateCache) { | ||
const safeCache = _privateCache || {}; | ||
if (key === undefined) { | ||
return safeCache; | ||
} | ||
return selector.query(safeCache || {}, key); | ||
} | ||
return selector.query(_privateCache, key); | ||
} | ||
function get(keys) { | ||
if (Array.isArray(keys)) { | ||
return keys.map(key => _get(key, privateCache)); | ||
} | ||
function get(keys) { | ||
if (!isInitialized) { | ||
throw new Error('Conman has not been initialize'); | ||
return _get(keys, privateCache); | ||
} | ||
if (Array.isArray(keys)) { | ||
return keys.map(key => _get(key, privateCache)); | ||
function getObfuscate(keys, params) { | ||
return obfuscate(get(keys), params); | ||
} | ||
return _get(keys, privateCache); | ||
} | ||
function _buildSources(_sources) { | ||
async function buildSource(config, source) { | ||
const sourceConfig = await source.build(config); | ||
const parseConfig = source.key | ||
? { [source.key]: sourceConfig } | ||
: sourceConfig; | ||
function getObfuscate(keys, params) { | ||
return obfuscate(get(keys), params); | ||
} | ||
return mergeDeepRight(config, parseConfig); | ||
} | ||
function _buildSources(_sources) { | ||
async function buildSource(config, source) { | ||
const sourceConfig = await source.build(config); | ||
const parseConfig = source.key | ||
? { [source.key]: sourceConfig } | ||
: sourceConfig; | ||
return serialize(_sources, buildSource, {}); | ||
} | ||
return mergeDeepRight(config, parseConfig); | ||
function _setPrivateCache(config) { | ||
privateCache = config; | ||
return config; | ||
} | ||
return serialize(_sources, buildSource, {}); | ||
} | ||
async function _buildAndSaveCache(_sources) { | ||
const sourcesTypes = _sources.map(({ name, type }) => name || type); | ||
logger( | ||
'log', | ||
`Build triggered for sources: "${sourcesTypes.join()}" at ${new Date().toISOString()}` | ||
); | ||
const configs = await _buildSources(_sources); | ||
logger( | ||
'log', | ||
`Build completed for sources: "${sourcesTypes.join()}" at ${new Date().toISOString()}` | ||
); | ||
await _writeToFile(_prepareCacheObject(configs), options); | ||
return _setPrivateCache(configs); | ||
} | ||
function _setPrivateCache(config) { | ||
privateCache = config; | ||
return config; | ||
} | ||
async function build() { | ||
_triggerInterval(); | ||
async function _buildAndSaveCache(_sources) { | ||
const sourcesTypes = _sources.map(({ name, type }) => name || type); | ||
logger( | ||
'log', | ||
`Build triggered for sources: "${sourcesTypes.join()}" at ${new Date().toISOString()}` | ||
); | ||
const configs = await _buildSources(_sources); | ||
logger( | ||
'log', | ||
`Build completed for sources: "${sourcesTypes.join()}" at ${new Date().toISOString()}` | ||
); | ||
await _writeToFile(_prepareCacheObject(configs), options); | ||
return _setPrivateCache(configs); | ||
} | ||
// read from file if its the first build | ||
if (!privateCache && options.useFile) { | ||
const cache = await _readFromFile(options); | ||
if (!cache) { | ||
return _buildAndSaveCache(sources); | ||
} | ||
return _setPrivateCache(cache); | ||
} | ||
async function build() { | ||
_triggerInterval(); | ||
return _buildAndSaveCache(sources); | ||
} | ||
// read from file if its the first build | ||
if (!privateCache && options.useFile) { | ||
const cache = await _readFromFile(options); | ||
if (!cache) { | ||
return _buildAndSaveCache(sources); | ||
} | ||
return _setPrivateCache(cache); | ||
function stop() { | ||
clearInterval(ttlInterval); | ||
} | ||
return _buildAndSaveCache(sources); | ||
} | ||
function reset() { | ||
sources = []; | ||
options = defaultOptions; | ||
ttlInterval = undefined; | ||
privateCache = undefined; | ||
logger = undefined; | ||
clearInterval(ttlInterval); | ||
} | ||
function stop() { | ||
clearInterval(ttlInterval); | ||
return { | ||
build, | ||
stop, | ||
reset, | ||
addSource, | ||
get, | ||
getObfuscate | ||
}; | ||
} | ||
function reset() { | ||
sources = []; | ||
options = defaultOptions; | ||
isInitialized = false; | ||
ttlInterval = undefined; | ||
privateCache = undefined; | ||
logger = undefined; | ||
clearInterval(ttlInterval); | ||
} | ||
conman = _init; | ||
conman.build = build; | ||
conman.stop = stop; | ||
conman.reset = reset; | ||
conman.addSource = addSource; | ||
conman.get = get; | ||
conman.getObfuscate = getObfuscate; | ||
module.exports = conman; | ||
module.exports = { create: conman }; |
jest.mock('jsonfile'); | ||
const jsonfile = require('jsonfile'); | ||
const conman = require('./conman'); | ||
const Conman = require('./conman'); | ||
@@ -34,2 +34,7 @@ const source = (obj = {}, { key, name } = {}) => { | ||
const logger = { | ||
log: jest.fn(), | ||
error: jest.fn() | ||
}; | ||
describe('conman library', () => { | ||
@@ -43,7 +48,9 @@ beforeEach(() => { | ||
jsonfile.readFile.mockReset(); | ||
conman.reset(); | ||
logger.log.mockReset(); | ||
logger.error.mockReset(); | ||
}); | ||
it('should work with default options and no sources', async () => { | ||
const config = await conman().build(); | ||
const conman = Conman(); | ||
const config = await conman.build(); | ||
conman.stop(); | ||
@@ -55,5 +62,4 @@ expect(config).toEqual({}); | ||
const source1 = asyncSource({ test: 'test' }); | ||
const config = await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
const config = await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -65,5 +71,4 @@ expect(config).toEqual({ test: 'test' }); | ||
const source1 = source({ test: 'test' }, { key: 'TEST' }); | ||
const config = await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
const config = await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -76,3 +81,4 @@ expect(config).toEqual({ TEST: { test: 'test' } }); | ||
const source2 = source({ test: 'usesConfig' }); | ||
const config = await conman() | ||
const conman = Conman(); | ||
const config = await conman | ||
.addSource(source1) | ||
@@ -88,3 +94,4 @@ .addSource(source2) | ||
const source2 = asyncSource({ test: 'usesConfig' }); | ||
const config = await conman() | ||
const conman = Conman(); | ||
const config = await conman | ||
.addSource(source1) | ||
@@ -99,5 +106,4 @@ .addSource(source2) | ||
const source1 = source({ test: 'test async' }); | ||
const config = await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
const config = await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -110,3 +116,4 @@ expect(config).toEqual({ test: 'test async' }); | ||
const source2 = source({ test: 'test', test3: 'test3' }); | ||
const config = await conman() | ||
const conman = Conman(); | ||
const config = await conman | ||
.addSource(source1) | ||
@@ -123,5 +130,4 @@ .addSource(source2) | ||
const source1 = source({ test: 'test async' }); | ||
await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -137,5 +143,4 @@ expect(conman.get('test')).toBe('test async'); | ||
}); | ||
await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -153,5 +158,4 @@ expect(conman.get(['test', 'test2'])).toEqual(['test async', 'test2']); | ||
}); | ||
await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -173,5 +177,4 @@ expect(conman.get('test.test2.test3')).toBe('test3'); | ||
}); | ||
await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -199,5 +202,4 @@ expect(conman.getObfuscate('test')).toBe('te**'); | ||
}); | ||
await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -217,5 +219,4 @@ expect( | ||
}); | ||
await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -227,5 +228,4 @@ expect(conman.get('test.test2.test4')).toBe(undefined); | ||
const source1 = source({ test: 'test async', test2: 'test2' }); | ||
await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -238,5 +238,4 @@ expect(conman.get()).toEqual({ test: 'test async', test2: 'test2' }); | ||
jest.spyOn(source1, 'build'); | ||
await conman({ ttl: 1000 }) | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman({ ttl: 1000 }); | ||
await conman.addSource(source1).build(); | ||
@@ -250,6 +249,3 @@ await new Promise(r => setTimeout(r, 3000)); | ||
const options = { | ||
logger: { | ||
log: jest.fn(), | ||
error: jest.fn() | ||
}, | ||
logger, | ||
logEnabled: true, | ||
@@ -262,6 +258,4 @@ useFile: false, | ||
jest.spyOn(source1, 'build'); | ||
await conman(options) | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(options); | ||
await conman.addSource(source1).build(); | ||
expect(options.logger.log).toHaveBeenCalled(); | ||
@@ -281,5 +275,4 @@ expect(jsonfile.writeFile).toHaveBeenCalledTimes(0); | ||
}); | ||
const config = await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
const config = await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -299,5 +292,4 @@ expect(config).toEqual({ | ||
}); | ||
const config = await conman() | ||
.addSource(source1) | ||
.build(); | ||
const conman = Conman(); | ||
const config = await conman.addSource(source1).build(); | ||
conman.stop(); | ||
@@ -309,4 +301,51 @@ expect(config).toEqual({ | ||
it('should build even if failed to read file and logs an error', async () => { | ||
const source1 = source({ test: 'builtfromsource' }); | ||
jsonfile.readFile.mockRejectedValueOnce(); | ||
const conman = Conman({ | ||
logEnabled: true, | ||
logger | ||
}); | ||
const config = await conman.addSource(source1).build(); | ||
conman.stop(); | ||
expect(config).toEqual({ | ||
test: 'builtfromsource' | ||
}); | ||
expect(logger.error).toHaveBeenCalledTimes(1); | ||
}); | ||
it('should build even if failed to write file and logs an error', async () => { | ||
const source1 = source({ test: 'test', test3: 'test3' }); | ||
jsonfile.writeFile.mockRejectedValueOnce(); | ||
const conman = Conman({ | ||
logEnabled: true, | ||
logger | ||
}); | ||
const config = await conman.addSource(source1).build(); | ||
conman.stop(); | ||
expect(config).toEqual({ test: 'test', test3: 'test3' }); | ||
expect(logger.error).toHaveBeenCalledTimes(1); | ||
}); | ||
it(`should create independent instances with the provider`, async () => { | ||
const instance1 = Conman({ ttl: 0 }); | ||
const instance2 = Conman({ ttl: 0 }); | ||
await instance1 | ||
.addSource(source({ test: 'instance1' })) | ||
.build(); | ||
await instance2 | ||
.addSource(source({ test: 'instance2' })) | ||
.build(); | ||
expect(instance1.get()).toEqual({ test: 'instance1' }); | ||
expect(instance2.get()).toEqual({ test: 'instance2' }); | ||
instance1.reset(); | ||
expect(instance1.get()).toEqual({}); | ||
expect(instance2.get()).toEqual({ test: 'instance2' }); | ||
}); | ||
it('should throw an error if the source is missing its build property', async () => { | ||
expect(() => conman().addSource({ type: 'source' })).toThrow(); | ||
expect(() => Conman().addSource({ type: 'source' })).toThrow(); | ||
}); | ||
@@ -316,3 +355,3 @@ | ||
expect(() => | ||
conman().addSource({ type: 'source', build: 'not a function' }) | ||
Conman().addSource({ type: 'source', build: 'not a function' }) | ||
).toThrow(); | ||
@@ -323,3 +362,3 @@ }); | ||
expect(() => | ||
conman().addSource({ | ||
Conman().addSource({ | ||
build() { | ||
@@ -326,0 +365,0 @@ return {}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
266472
742