Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,29 @@ const config = configPackage({
```

Leaving a source out excludes it from resolution.

## Passthrough branches

Use `passthrough()` when a branch should keep accepting whatever shows up below
that path instead of using `defaults` as a coercion shape.

```javascript
const configPackage = require('@iteam/config')

const config = configPackage({
defaults: {
logging: {
provider: 'graylog',
config: configPackage.passthrough({
enabled: true
})
}
}
})
```

Below a `passthrough()` branch:

1. Defaults still provide fallback values.
2. Environment, config file, and secrets still merge as usual.
3. Values are not coerced from the defaults shape.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"prettier:fix": "prettier --write \"{src,test}/**/*.js\"",
"prettier": "prettier --check \"{src,test}/**/*.js\"",
"lint": "eslint \"src/**/*.js\"",
"test": "npm run lint && mocha \"test/*.spec.js\" --recursive"
"test": "npm run lint && mocha \"test/**/*.spec.js\""
},
"author": "Dennis Pettersson <dennis.pettersson@iteam.se>",
"dependencies": {
Expand Down
2 changes: 2 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ declare namespace conf {
readonly FILE: 'file'
readonly SECRETS: 'secrets'
}

function passthrough<T = any>(defaults?: T): T
}

export = conf
80 changes: 78 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const fs = require('fs')
const utils = require('./utils')
const secrets = require('./secrets')
const { passthrough, isPassthrough } = require('./utils/passthrough')
const isDocker = require('is-docker')()

const Source = Object.freeze({
Expand Down Expand Up @@ -42,8 +43,75 @@ const useSecrets = () => {
return !fs.readFileSync('/proc/self/cgroup', 'utf8').includes('kubepods')
}

const isObject = (value) => {
return value && typeof value === 'object' && !Array.isArray(value)
}

const decomposeDefaults = (values) => {
if (isPassthrough(values)) {
return {
defaults: values.defaults,
coercion: passthrough(),
}
}

if (Array.isArray(values)) {
return values.reduce(
(result, value) => {
const decomposed = decomposeDefaults(value)
result.defaults.push(decomposed.defaults)
result.coercion.push(decomposed.coercion)
return result
},
{
defaults: [],
coercion: [],
}
)
}

if (!isObject(values)) {
return {
defaults: values,
coercion: values,
}
}

return Object.keys(values).reduce(
(result, key) => {
const decomposed = decomposeDefaults(values[key])
result.defaults[key] = decomposed.defaults
result.coercion[key] = decomposed.coercion
return result
},
{
defaults: {},
coercion: {},
}
)
}

const getCoercion = (coercion, key) => {
let current = coercion

for (const part of key.split('.')) {
if (isPassthrough(current)) {
return current
}

if (typeof current === 'undefined' || current === null) {
return current
}

current = current[part]
}

return current
}

const createConfig = (options) => {
const _conf = new WeakMap()
const _coercion = new WeakMap()
const _confEnv = new WeakMap()
const _confFile = new WeakMap()
const _secrets = new WeakMap()
Expand Down Expand Up @@ -118,14 +186,17 @@ const createConfig = (options) => {

set defaults(values = {}) {
const { env, file } = this
const defaults = utils.changeCase(values)
const decomposed = decomposeDefaults(values)
const defaults = utils.changeCase(decomposed.defaults)
const coercion = utils.changeCase(decomposed.coercion)

_conf.set(
this,
new utils.Config({
defaults,
})
)
_coercion.set(this, coercion)
_confEnv.set(
this,
this.resolutionOrder.includes(Source.ENV)
Expand Down Expand Up @@ -156,6 +227,7 @@ const createConfig = (options) => {
const _e = _confEnv.get(this)
const _f = _confFile.get(this)
const _d = _conf.get(this)
const _c = _coercion.get(this)

const [env, file, defaults, secrets] = [
_e && _e.get ? _e.get(key) : undefined,
Expand All @@ -177,7 +249,10 @@ const createConfig = (options) => {

const out = changeCase(merged, 'camel')

const cast = utils.type.cast(out, changeCase(defaults, 'camel'))
const cast = utils.type.cast(
out,
changeCase(getCoercion(_c, key), 'camel')
)

if (
!isObject(out) &&
Expand All @@ -196,5 +271,6 @@ const createConfig = (options) => {
}

createConfig.Source = Source
createConfig.passthrough = passthrough

module.exports = createConfig
5 changes: 5 additions & 0 deletions src/utils/changeCase.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
const identifier = require('./identifier')
const { isPassthrough } = require('./passthrough')

const changeCase = (keys, casing = 'snake') => {
if (keys === null) {
return null
}

if (isPassthrough(keys)) {
return keys
}

if (typeof keys !== 'object') {
return keys
}
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
copy: require('./copy'),
identifier: require('./identifier'),
mergeDeep: require('./mergeDeep'),
passthrough: require('./passthrough'),
type: require('./type'),
castString: require('./type/castString'),
isObject: require('./type/isObject'),
Expand Down
22 changes: 22 additions & 0 deletions src/utils/passthrough.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const PASSTHROUGH_BRAND = '__iteamConfigPassthrough__'

const passthrough = (defaults) =>
Object.freeze({
[PASSTHROUGH_BRAND]: true,
defaults,
})

const isPassthrough = (value) => {
return (
value &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.prototype.hasOwnProperty.call(value, PASSTHROUGH_BRAND) &&
value[PASSTHROUGH_BRAND] === true
)
}

module.exports = {
passthrough,
isPassthrough,
}
5 changes: 5 additions & 0 deletions src/utils/type/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const isObject = require('./isObject')
const castString = require('./castString')
const toBoolean = require('./toBoolean')
const toJSON = require('./toJSON')
const { isPassthrough } = require('../passthrough')

const toObjectLoose = (array, output, from, defaults) => {
return array.reduce((object, key) => {
Expand Down Expand Up @@ -60,6 +61,10 @@ const cast = (output, from, isStrict = true, defaults) => {
return output
}

if (isPassthrough(from)) {
return output
}

if (isObject(from)) {
return toObject(toJSON(output), from, isStrict, defaults)
}
Expand Down
10 changes: 10 additions & 0 deletions test/configs/passthrough.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"logging": {
"config": {
"enabled": "false",
"adapter": {
"retry_count": "5"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
5
1 change: 1 addition & 0 deletions test/secrets/passthrough/logging__config__enabled
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
false
Loading
Loading