xenv-config 2.1.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/index.js +20 -5
- package/docs/reference.md +18 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ expect(config.__$trace__).to.deep.equal({
|
|
|
45
45
|
fooOption: { src: "default" },
|
|
46
46
|
barOption: { src: "env", name: "BAR_OPTION" },
|
|
47
47
|
zooOption: { src: "option" },
|
|
48
|
-
jsonOption: { src: "env,option" }
|
|
48
|
+
jsonOption: { src: "env,option", name: "JSON_OPTION" }
|
|
49
49
|
});
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -68,6 +68,7 @@ Returns `config` object.
|
|
|
68
68
|
|
|
69
69
|
- `_env` - Object that represents environment instead of `process.env`
|
|
70
70
|
- `merge` - function to merge `json` object instead of using `Object.assign`
|
|
71
|
+
- `sources` - ordered list of sources to check, from `env`, `option` and `default`. Default is `["env", "option"]`, and `default` is always checked last. An unknown name throws.
|
|
71
72
|
|
|
72
73
|
## Spec
|
|
73
74
|
|
|
@@ -113,8 +114,9 @@ When loading from `env`, in order to indicate what value to convert the string i
|
|
|
113
114
|
- `boolean` - (boolean) convert with `x === "true" || x === "yes" || x === "1" || x === "on"`
|
|
114
115
|
- `truthy` - (boolean from truthy check) convert with `!!x`
|
|
115
116
|
- `json` - (JSON) parsed with `JSON.parse`
|
|
117
|
+
- `array` - (array) parsed with `JSON.parse`. The first source found wins, and nothing is merged.
|
|
116
118
|
|
|
117
|
-
> If `type` is not specified, and `default` exist and not a function, then `typeof default` will be used.
|
|
119
|
+
> If `type` is not specified, and `default` exist and not a function, then `typeof default` will be used. A `null` default is `string`, and an array default is `array`: env is parsed with `JSON.parse`, and the first source found wins with no merging.
|
|
118
120
|
|
|
119
121
|
### Trace
|
|
120
122
|
|
|
@@ -132,6 +134,7 @@ The hidden field `__$trace__` contain data for each key to indicate where its va
|
|
|
132
134
|
- Values from all three sources are combined in the following order `env`, `option`, `default`.
|
|
133
135
|
- It uses `Object.assign` to combine values unless you pass in a `merge` function in options. For example, merge from `lodash`.
|
|
134
136
|
- `trace.src` would be a comma separate list of them. For example, `"env,option"`, `"env,option,default"`, or `"option,default"`
|
|
137
|
+
- When env contributed, the trace also has `name`, the env var name used.
|
|
135
138
|
|
|
136
139
|
## Option Orders
|
|
137
140
|
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,8 @@ const typeGetters = {
|
|
|
9
9
|
return lower === "true" || lower === "yes" || lower === "1" || lower === "on";
|
|
10
10
|
},
|
|
11
11
|
truthy: x => !!x,
|
|
12
|
-
json: x => JSON.parse(x)
|
|
12
|
+
json: x => JSON.parse(x),
|
|
13
|
+
array: x => JSON.parse(x)
|
|
13
14
|
};
|
|
14
15
|
const getEnvName = (optKey, opt, env) => {
|
|
15
16
|
if (!Object.prototype.hasOwnProperty.call(opt, "env"))
|
|
@@ -40,9 +41,17 @@ const xenvConfig = (spec, userConfig, options) => {
|
|
|
40
41
|
userConfig = userConfig || {};
|
|
41
42
|
const trace = {};
|
|
42
43
|
const getType = (opt) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if (opt.type)
|
|
45
|
+
return opt.type === "object" ? "json" : opt.type;
|
|
46
|
+
if (!Object.prototype.hasOwnProperty.call(opt, "default"))
|
|
47
|
+
return "string";
|
|
48
|
+
// typeof says "object" for both, but neither is a json object to merge into
|
|
49
|
+
if (opt.default === null)
|
|
50
|
+
return "string";
|
|
51
|
+
if (Array.isArray(opt.default))
|
|
52
|
+
return "array";
|
|
53
|
+
const tod = typeof opt.default;
|
|
54
|
+
return tod === "object" ? "json" : tod === "function" ? "string" : tod;
|
|
46
55
|
};
|
|
47
56
|
const getGetter = (opt) => {
|
|
48
57
|
const type = getType(opt);
|
|
@@ -79,7 +88,12 @@ const xenvConfig = (spec, userConfig, options) => {
|
|
|
79
88
|
}
|
|
80
89
|
};
|
|
81
90
|
const sources = [...((options && options.sources) || ["env", "option"]), "default"];
|
|
91
|
+
const badSource = sources.find(s => !Object.prototype.hasOwnProperty.call(getters, s));
|
|
92
|
+
if (badSource !== undefined) {
|
|
93
|
+
throw new Error(`xenv-config: unknown source "${badSource}"; valid sources are ${Object.keys(getters).join(", ")}`);
|
|
94
|
+
}
|
|
82
95
|
const config = Object.keys(spec).reduce((cfg, k) => {
|
|
96
|
+
var _a;
|
|
83
97
|
const opt = spec[k];
|
|
84
98
|
const type = getType(opt);
|
|
85
99
|
let found;
|
|
@@ -89,8 +103,9 @@ const xenvConfig = (spec, userConfig, options) => {
|
|
|
89
103
|
else {
|
|
90
104
|
const mapped = sources.map(s => getters[s](opt, k)).filter((x) => Boolean(x));
|
|
91
105
|
if (mapped.length > 0) {
|
|
106
|
+
const name = (_a = mapped.find(x => x.trace.name)) === null || _a === void 0 ? void 0 : _a.trace.name;
|
|
92
107
|
found = {
|
|
93
|
-
trace: { src: mapped.map(x => x.trace.src).join(",") },
|
|
108
|
+
trace: { src: mapped.map(x => x.trace.src).join(","), ...(name && { name }) },
|
|
94
109
|
value: merge.apply(null, [{}].concat(mapped.map(x => x.value).reverse()))
|
|
95
110
|
};
|
|
96
111
|
}
|
package/docs/reference.md
CHANGED
|
@@ -67,8 +67,8 @@ Reorder with `options.sources`. Example: `sources: ["option", "env"]` makes user
|
|
|
67
67
|
|
|
68
68
|
- Presence is tested with `Object.prototype.hasOwnProperty`. A present key whose value is `undefined`, `null`, `""` or `false` still counts as found. An empty string env var is found.
|
|
69
69
|
- Inherited properties are not seen, on `spec` entries, `userConfig` and the env object.
|
|
70
|
-
- `options.sources` is mutated.
|
|
71
|
-
- A source name other than `"env"`, `"option"` or `"default"` throws
|
|
70
|
+
- `options.sources` is not mutated. `"default"` is appended to a copy.
|
|
71
|
+
- A source name other than `"env"`, `"option"` or `"default"` throws before any key is resolved: `xenv-config: unknown source "<name>"; valid sources are env, option, default`.
|
|
72
72
|
- The `"default"` entry: if `default` is a function it is called with no arguments and its return value is used. A function is never type-coerced.
|
|
73
73
|
|
|
74
74
|
### Env lookup
|
|
@@ -117,7 +117,7 @@ All fields are optional. A spec option with none of them resolves only from `use
|
|
|
117
117
|
The type for a key is decided by `type` first, then inference:
|
|
118
118
|
|
|
119
119
|
1. `opt.type` if truthy.
|
|
120
|
-
2. Else
|
|
120
|
+
2. Else, when `default` is an own property, inferred from it (see the table below).
|
|
121
121
|
3. Else `"string"`.
|
|
122
122
|
|
|
123
123
|
Then `"object"` is rewritten to `"json"`. A type name not listed below falls back to the `string` converter. Only `"json"` (after the rewrite) triggers merge behavior.
|
|
@@ -130,17 +130,20 @@ Then `"object"` is rewritten to `"json"`. A type name not listed below falls bac
|
|
|
130
130
|
| `boolean` | lower-cased `x` is `"true"`, `"yes"`, `"1"` or `"on"` | Case-insensitive. Any other string gives `false`. A non-string raw value throws (see Errors). |
|
|
131
131
|
| `truthy` | `!!x` | An empty string gives `false`. Any non-empty string, including `"false"` and `"0"`, gives `true`. |
|
|
132
132
|
| `json` | `JSON.parse(x)` | Needs strict JSON. `{b:90}` is invalid. See json merging. |
|
|
133
|
+
| `array` | `JSON.parse(x)` | Same parse as `json`, but first-wins like other types. Nothing is merged. |
|
|
133
134
|
|
|
134
135
|
Inference from `default`:
|
|
135
136
|
|
|
136
|
-
| `
|
|
137
|
+
| `default` | Inferred type |
|
|
137
138
|
| --- | --- |
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
| `
|
|
142
|
-
|
|
|
143
|
-
|
|
|
139
|
+
| a string | `string` |
|
|
140
|
+
| a number | `number` (integer, `parseInt`) |
|
|
141
|
+
| a boolean | `boolean` |
|
|
142
|
+
| `null` | `string`. With no other source the value stays `null`. |
|
|
143
|
+
| an array | `array`. The first source found wins and its value is returned as is. |
|
|
144
|
+
| any other object | `json` |
|
|
145
|
+
| `undefined` (own property set to `undefined`) | no converter of that name, so `string` conversion |
|
|
146
|
+
| a function, or no `default` | `string` |
|
|
144
147
|
|
|
145
148
|
An explicit `type` always overrides inference. `type: "object"` is accepted as an alias of `json`.
|
|
146
149
|
|
|
@@ -154,7 +157,7 @@ When the resolved type is `json`, sources are not first-wins. Every source in `s
|
|
|
154
157
|
- The result is always a new object, so defaults and user config are not mutated by the merge.
|
|
155
158
|
- If no source finds a value, the key is skipped.
|
|
156
159
|
- Values are merged as given. With `Object.assign`, a string merges as indexed characters, an array as indexed keys, and `null` or `undefined` is ignored.
|
|
157
|
-
- The trace is `{ src: "<names joined by comma>" }`, in `sources` order, only for the sources that found a value, for example `"env,option,default"` or `"option,default"`.
|
|
160
|
+
- The trace is `{ src: "<names joined by comma>" }`, in `sources` order, only for the sources that found a value, for example `"env,option,default"` or `"option,default"`. When env contributed, the trace also has `name`, the env var name used.
|
|
158
161
|
|
|
159
162
|
## `XenvConfigOptions`
|
|
160
163
|
|
|
@@ -170,7 +173,7 @@ interface XenvConfigOptions {
|
|
|
170
173
|
| --- | --- | --- | --- |
|
|
171
174
|
| `_env` | `XenvConfigEnv` (`Record<string, unknown>`) | `process.env` | Env-like object to read. Used when truthy. Values need not be strings, but `boolean` rejects non-strings. |
|
|
172
175
|
| `merge` | `XenvConfigMergeFn` | `Object.assign` | Called as `merge.apply(null, [{}, ...values])` for json keys. Must return the merged object. Its return value is used as the key's value. |
|
|
173
|
-
| `sources` | `string[]` | `["env", "option"]` | Ordered source names to consult. `"default"` is always appended, after your list. The array is
|
|
176
|
+
| `sources` | `string[]` | `["env", "option"]` | Ordered source names to consult. `"default"` is always appended, after your list. The array you pass is not changed. An unknown name throws. |
|
|
174
177
|
|
|
175
178
|
## Result
|
|
176
179
|
|
|
@@ -190,7 +193,7 @@ The result is a plain `{}` with one property per resolved spec key, in spec key
|
|
|
190
193
|
- `__$trace__` is defined with `Object.defineProperty`: `enumerable: false`, `writable: false`, `configurable: false`. It is skipped by `Object.keys`, spread, `JSON.stringify` and deep-equal checks, but is read as `config.__$trace__`. Copying the object with spread or `Object.assign` drops it.
|
|
191
194
|
- `__$trace__[key]` exists exactly when `key` is in the result.
|
|
192
195
|
- `trace.src` is `"env"`, `"option"`, `"default"`, or for json keys a comma-joined list of those.
|
|
193
|
-
- `trace.name` is set
|
|
196
|
+
- `trace.name` is set when env provided the value, or contributed to a json value. It holds the env var name actually used, which is useful with an `env` array.
|
|
194
197
|
- Values from `userConfig` and `default` are stored by reference, not cloned (non-json keys).
|
|
195
198
|
|
|
196
199
|
```js
|
|
@@ -206,7 +209,7 @@ const config = xenvConfig(
|
|
|
206
209
|
// config: { port: 9000, opts: { a: 1, b: 2, c: 3 } } (debug is absent)
|
|
207
210
|
// config.__$trace__:
|
|
208
211
|
// port: { src: "env", name: "PORT" }
|
|
209
|
-
// opts: { src: "env,option,default" }
|
|
212
|
+
// opts: { src: "env,option,default", name: "OPTS" }
|
|
210
213
|
```
|
|
211
214
|
|
|
212
215
|
## Errors
|
|
@@ -217,7 +220,7 @@ Nothing is caught or wrapped. All errors are thrown synchronously from `xenvConf
|
|
|
217
220
|
| --- | --- |
|
|
218
221
|
| `boolean` type and the env value (or `_env` value) is not a string | `AssertionError` from `node:assert`, message `xenv-config: [<key>] trying to convert non-string env value <value> to boolean.` |
|
|
219
222
|
| `json` type and the env string is not valid JSON | `SyntaxError` from `JSON.parse` |
|
|
220
|
-
| `sources` contains an unknown name | `
|
|
223
|
+
| `sources` contains an unknown name | `Error`, message `xenv-config: unknown source "<name>"; valid sources are env, option, default`. Thrown before any key is resolved. |
|
|
221
224
|
| `spec` is `undefined` or `null` | `TypeError` from `Object.keys` |
|
|
222
225
|
| A function `default`, `post`, or `merge` throws | the error propagates unchanged |
|
|
223
226
|
| `type: "json"` where `x` is not a string, such as a non-string `_env` value | `JSON.parse` coerces it to a string first, so it may parse or throw `SyntaxError` |
|
|
@@ -226,9 +229,7 @@ Absence of a value is not an error. Missing keys are left out of the result.
|
|
|
226
229
|
|
|
227
230
|
## Quirks
|
|
228
231
|
|
|
229
|
-
- `options.sources` is mutated by `push("default")`. Reusing the same array in a later call adds another `"default"`. For json keys this repeats the default in the merge and in the trace (`"option,default,default"`). Pass a fresh array each call.
|
|
230
232
|
- `type: "json"` needs strict JSON in env. The README example `JSON_OPTION = "{b:90}"` throws `SyntaxError`; use `'{"b":90}'`.
|
|
231
|
-
- `default: null` or an array default infers type `json`, so the key resolves to a new merged object (`{}` for `null`, `{0: ...}` for an array), not to `null` or the array.
|
|
232
233
|
- `number` env values that are not integers give `NaN` rather than an error. Empty env strings give `NaN`.
|
|
233
234
|
- `envMap` hits are returned as is, with no coercion by `type`.
|
|
234
235
|
- `userConfig` values and defaults are never coerced, only env values are.
|