xenv-config 2.1.7 → 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 +24 -6
- package/dist/index.js +21 -7
- package/docs/reference.md +238 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
[![NPM version][npm-image]][npm-url]
|
|
2
|
-
|
|
3
1
|
# xenv-config
|
|
4
2
|
|
|
3
|
+
[![License][license-image]][license-url]
|
|
4
|
+
[![build][build-image]][build-url]
|
|
5
|
+
|
|
6
|
+
[![Downloads][downloads-image]][downloads-url]
|
|
7
|
+
|
|
8
|
+
[![npm badge][npm-badge-png]][package-url]
|
|
9
|
+
|
|
10
|
+
- [API reference](https://fynjs.pages.dev/api/modules/xenv-config) - TypeDoc
|
|
11
|
+
- [Markdown reference](docs/reference.md) - the full API and runtime behavior in one file. Also ships in the package.
|
|
12
|
+
- [GitHub](https://github.com/jchip/fynjs/tree/main/packages/xenv-config)
|
|
13
|
+
|
|
5
14
|
Load config from env, user config, or default spec.
|
|
6
15
|
|
|
7
16
|
## Install
|
|
@@ -36,7 +45,7 @@ expect(config.__$trace__).to.deep.equal({
|
|
|
36
45
|
fooOption: { src: "default" },
|
|
37
46
|
barOption: { src: "env", name: "BAR_OPTION" },
|
|
38
47
|
zooOption: { src: "option" },
|
|
39
|
-
jsonOption: { src: "env,option" }
|
|
48
|
+
jsonOption: { src: "env,option", name: "JSON_OPTION" }
|
|
40
49
|
});
|
|
41
50
|
```
|
|
42
51
|
|
|
@@ -59,6 +68,7 @@ Returns `config` object.
|
|
|
59
68
|
|
|
60
69
|
- `_env` - Object that represents environment instead of `process.env`
|
|
61
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.
|
|
62
72
|
|
|
63
73
|
## Spec
|
|
64
74
|
|
|
@@ -104,8 +114,9 @@ When loading from `env`, in order to indicate what value to convert the string i
|
|
|
104
114
|
- `boolean` - (boolean) convert with `x === "true" || x === "yes" || x === "1" || x === "on"`
|
|
105
115
|
- `truthy` - (boolean from truthy check) convert with `!!x`
|
|
106
116
|
- `json` - (JSON) parsed with `JSON.parse`
|
|
117
|
+
- `array` - (array) parsed with `JSON.parse`. The first source found wins, and nothing is merged.
|
|
107
118
|
|
|
108
|
-
> 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.
|
|
109
120
|
|
|
110
121
|
### Trace
|
|
111
122
|
|
|
@@ -123,6 +134,7 @@ The hidden field `__$trace__` contain data for each key to indicate where its va
|
|
|
123
134
|
- Values from all three sources are combined in the following order `env`, `option`, `default`.
|
|
124
135
|
- It uses `Object.assign` to combine values unless you pass in a `merge` function in options. For example, merge from `lodash`.
|
|
125
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.
|
|
126
138
|
|
|
127
139
|
## Option Orders
|
|
128
140
|
|
|
@@ -133,5 +145,11 @@ The order of source to check are:
|
|
|
133
145
|
3. The default value from spec if it's declared
|
|
134
146
|
4. Nothing
|
|
135
147
|
|
|
136
|
-
[
|
|
137
|
-
[
|
|
148
|
+
[license-image]: https://img.shields.io/npm/l/xenv-config.svg
|
|
149
|
+
[license-url]: LICENSE
|
|
150
|
+
[build-image]: https://github.com/jchip/fynjs/actions/workflows/ci.yml/badge.svg
|
|
151
|
+
[build-url]: https://github.com/jchip/fynjs/actions/workflows/ci.yml
|
|
152
|
+
[downloads-image]: https://img.shields.io/npm/dm/xenv-config.svg
|
|
153
|
+
[downloads-url]: https://npm-stat.com/charts.html?package=xenv-config
|
|
154
|
+
[npm-badge-png]: https://nodei.co/npm/xenv-config.png?downloads=true&stars=true
|
|
155
|
+
[package-url]: https://npmjs.com/package/xenv-config
|
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);
|
|
@@ -78,9 +87,13 @@ const xenvConfig = (spec, userConfig, options) => {
|
|
|
78
87
|
return undefined;
|
|
79
88
|
}
|
|
80
89
|
};
|
|
81
|
-
const sources = (options && options.sources) || ["env", "option"];
|
|
82
|
-
sources.
|
|
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
|
+
}
|
|
83
95
|
const config = Object.keys(spec).reduce((cfg, k) => {
|
|
96
|
+
var _a;
|
|
84
97
|
const opt = spec[k];
|
|
85
98
|
const type = getType(opt);
|
|
86
99
|
let found;
|
|
@@ -90,8 +103,9 @@ const xenvConfig = (spec, userConfig, options) => {
|
|
|
90
103
|
else {
|
|
91
104
|
const mapped = sources.map(s => getters[s](opt, k)).filter((x) => Boolean(x));
|
|
92
105
|
if (mapped.length > 0) {
|
|
106
|
+
const name = (_a = mapped.find(x => x.trace.name)) === null || _a === void 0 ? void 0 : _a.trace.name;
|
|
93
107
|
found = {
|
|
94
|
-
trace: { src: mapped.map(x => x.trace.src).join(",") },
|
|
108
|
+
trace: { src: mapped.map(x => x.trace.src).join(","), ...(name && { name }) },
|
|
95
109
|
value: merge.apply(null, [{}].concat(mapped.map(x => x.value).reverse()))
|
|
96
110
|
};
|
|
97
111
|
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# xenv-config reference
|
|
2
|
+
|
|
3
|
+
`xenv-config` builds a config object from a spec. Each key is resolved from an env var, a user config object, or a default, and every resolved key gets a trace record that says where it came from. No runtime dependencies. ESM only (`"type": "module"`).
|
|
4
|
+
|
|
5
|
+
## Imports
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import xenvConfig from "xenv-config";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import xenvConfig, {
|
|
13
|
+
type XenvConfigTrace,
|
|
14
|
+
type XenvConfigSpecOption,
|
|
15
|
+
type XenvConfigSpec,
|
|
16
|
+
type XenvConfigEnv,
|
|
17
|
+
type XenvConfigUserConfig,
|
|
18
|
+
type XenvConfigMergeFn,
|
|
19
|
+
type XenvConfigOptions,
|
|
20
|
+
type XenvConfigResult
|
|
21
|
+
} from "xenv-config";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The only runtime export is the default export, the function `xenvConfig`. There are no named runtime exports and no subpaths (only `xenv-config/package.json`). The package has no CommonJS build, so `require("xenv-config")` as shown in the README does not apply to this version; use `import`.
|
|
25
|
+
|
|
26
|
+
Type-only exports: `XenvConfigTrace`, `XenvConfigSpecOption`, `XenvConfigSpec`, `XenvConfigEnv`, `XenvConfigUserConfig`, `XenvConfigMergeFn`, `XenvConfigOptions`, `XenvConfigResult`.
|
|
27
|
+
|
|
28
|
+
Not exported: the internal `typeGetters` table, `getEnvName` and `FoundValue`.
|
|
29
|
+
|
|
30
|
+
## `xenvConfig(spec, userConfig?, options?)`
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const xenvConfig: <T = Record<string, unknown>>(
|
|
34
|
+
spec: XenvConfigSpec,
|
|
35
|
+
userConfig?: XenvConfigUserConfig,
|
|
36
|
+
options?: XenvConfigOptions
|
|
37
|
+
) => XenvConfigResult<T>;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Resolves every key of `spec` and returns a new plain object. It is synchronous. `T` is only a type cast of the result; nothing is checked against it.
|
|
41
|
+
|
|
42
|
+
| Parameter | Type | Default | Behavior |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| `spec` | `XenvConfigSpec` | required | Keys are iterated with `Object.keys(spec)`. Each value is a `XenvConfigSpecOption`. `undefined` or `null` `spec` throws a `TypeError`. |
|
|
45
|
+
| `userConfig` | `XenvConfigUserConfig` | `{}` | Values supplied by the caller. A falsy value is replaced with `{}`. |
|
|
46
|
+
| `options` | `XenvConfigOptions` | none | See `XenvConfigOptions`. |
|
|
47
|
+
|
|
48
|
+
For each spec key the function:
|
|
49
|
+
|
|
50
|
+
1. Finds a value by walking the sources (see Source order).
|
|
51
|
+
2. If nothing is found, the key is skipped. It is absent from the result and from `__$trace__`. `post` is not called.
|
|
52
|
+
3. Otherwise records the trace, runs `post` if the spec has one, and stores the result.
|
|
53
|
+
|
|
54
|
+
### Source order
|
|
55
|
+
|
|
56
|
+
Three sources exist, each named by a string:
|
|
57
|
+
|
|
58
|
+
| Source name | Looks in | Found when | Trace |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| `"env"` | env object (`options._env` or `process.env`) | the spec has an own `env` property that names a variable that is an own property of the env object | `{ src: "env", name }` |
|
|
61
|
+
| `"option"` | `userConfig` | `userConfig` has the key as an own property | `{ src: "option" }` |
|
|
62
|
+
| `"default"` | spec | the spec has an own `default` property | `{ src: "default" }` |
|
|
63
|
+
|
|
64
|
+
The default order is `["env", "option"]`, and `"default"` is appended last. For non-json types the first source that finds a value wins and the rest are not consulted.
|
|
65
|
+
|
|
66
|
+
Reorder with `options.sources`. Example: `sources: ["option", "env"]` makes user config beat env. A source left out of the list is never consulted, except `"default"`, which is always appended. Rules:
|
|
67
|
+
|
|
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
|
+
- Inherited properties are not seen, on `spec` entries, `userConfig` and the env object.
|
|
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
|
+
- 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
|
+
|
|
74
|
+
### Env lookup
|
|
75
|
+
|
|
76
|
+
The env var name is chosen from `spec[key].env`, which is read only when `env` is an own property of the spec option.
|
|
77
|
+
|
|
78
|
+
| `env` value | Name used |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| absent, `false`, `""` | none, the env source finds nothing |
|
|
81
|
+
| `true` | the spec key itself |
|
|
82
|
+
| `string` | that exact name |
|
|
83
|
+
| `string[]` | the first name in the list that is an own property of the env object |
|
|
84
|
+
|
|
85
|
+
If the chosen name is not an own property of the env object, the env source finds nothing. For an array, the match requires own presence, so a present empty string wins over a later name. An empty array finds nothing.
|
|
86
|
+
|
|
87
|
+
When a name is found, the raw value is `env[name]`, then:
|
|
88
|
+
|
|
89
|
+
1. If `envMap` is set and has the raw value as an own key, the mapped value is used as is. No type coercion is applied to it (`post` still runs). The raw value is used as a property key, so a non-string raw value is converted to a string for the lookup.
|
|
90
|
+
2. Otherwise the raw value is coerced by the key's type (see Types).
|
|
91
|
+
|
|
92
|
+
## `XenvConfigSpecOption`
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
interface XenvConfigSpecOption {
|
|
96
|
+
env?: string | string[] | boolean;
|
|
97
|
+
envMap?: Record<string, unknown>;
|
|
98
|
+
type?: string;
|
|
99
|
+
default?: unknown;
|
|
100
|
+
post?: (value: unknown, trace: XenvConfigTrace) => unknown;
|
|
101
|
+
[extra: string]: unknown;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
All fields are optional. A spec option with none of them resolves only from `userConfig`. Extra properties are allowed by the type and ignored by the code.
|
|
106
|
+
|
|
107
|
+
| Field | Type | Default | Behavior |
|
|
108
|
+
| --- | --- | --- | --- |
|
|
109
|
+
| `env` | `string \| string[] \| boolean` | none | Which env var to read. See Env lookup. |
|
|
110
|
+
| `envMap` | `Record<string, unknown>` | none | Maps raw env strings to final values. Only applies to the env source. Checked before type coercion. A hit skips coercion. |
|
|
111
|
+
| `type` | `string` | inferred | How to coerce an env string. See Types. Applies only to env values, not to `userConfig` values or defaults, except that `json` also changes how all sources combine. |
|
|
112
|
+
| `default` | `unknown` | none | Fallback value, or a function that returns it. Presence is tested as an own property, so `default: undefined` counts as a default and yields `undefined`. A function default is called each time `xenvConfig` runs. Not coerced. |
|
|
113
|
+
| `post` | `(value, trace) => unknown` | none | Runs on the resolved value after a source is found. Its return value is stored in the result. Receives the same trace object that is stored in `__$trace__`, so mutating it changes the stored trace. Not called when the key is not found. Runs for every source, including `option` and `default`. |
|
|
114
|
+
|
|
115
|
+
## Types
|
|
116
|
+
|
|
117
|
+
The type for a key is decided by `type` first, then inference:
|
|
118
|
+
|
|
119
|
+
1. `opt.type` if truthy.
|
|
120
|
+
2. Else, when `default` is an own property, inferred from it (see the table below).
|
|
121
|
+
3. Else `"string"`.
|
|
122
|
+
|
|
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.
|
|
124
|
+
|
|
125
|
+
| Type | Converter for an env value `x` | Notes |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| `string` | `` `${x}` `` | Also the fallback for any unknown type name. |
|
|
128
|
+
| `number` | `parseInt(x, 10)` | Integer. A non-numeric string gives `NaN` (JSON serializes it as `null`). `"1.9"` gives `1`. |
|
|
129
|
+
| `float` | `parseFloat(x)` | |
|
|
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
|
+
| `truthy` | `!!x` | An empty string gives `false`. Any non-empty string, including `"false"` and `"0"`, gives `true`. |
|
|
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. |
|
|
134
|
+
|
|
135
|
+
Inference from `default`:
|
|
136
|
+
|
|
137
|
+
| `default` | Inferred type |
|
|
138
|
+
| --- | --- |
|
|
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` |
|
|
147
|
+
|
|
148
|
+
An explicit `type` always overrides inference. `type: "object"` is accepted as an alias of `json`.
|
|
149
|
+
|
|
150
|
+
### json merging
|
|
151
|
+
|
|
152
|
+
When the resolved type is `json`, sources are not first-wins. Every source in `sources` (plus `"default"`) is consulted. All found values are merged.
|
|
153
|
+
|
|
154
|
+
- The env value is parsed with `JSON.parse`, or taken from `envMap` when a mapping hits.
|
|
155
|
+
- Merge call: `merge({}, ...values)` where values are ordered lowest priority first. With the default order the call is `merge({}, defaultValue, optionValue, envValue)`, so env wins, then option, then default. With a custom `sources`, the earlier a source is in the list, the higher its priority.
|
|
156
|
+
- `merge` is `options.merge` or `Object.assign` (shallow). Pass a deep merge such as lodash `merge` for nested objects.
|
|
157
|
+
- The result is always a new object, so defaults and user config are not mutated by the merge.
|
|
158
|
+
- If no source finds a value, the key is skipped.
|
|
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.
|
|
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.
|
|
161
|
+
|
|
162
|
+
## `XenvConfigOptions`
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
interface XenvConfigOptions {
|
|
166
|
+
_env?: XenvConfigEnv;
|
|
167
|
+
merge?: XenvConfigMergeFn;
|
|
168
|
+
sources?: string[];
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Option | Type | Default | Behavior |
|
|
173
|
+
| --- | --- | --- | --- |
|
|
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. |
|
|
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. |
|
|
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. |
|
|
177
|
+
|
|
178
|
+
## Result
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
type XenvConfigResult<T = Record<string, unknown>> = T & {
|
|
182
|
+
readonly __$trace__: Record<string, XenvConfigTrace>;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
interface XenvConfigTrace {
|
|
186
|
+
src: string;
|
|
187
|
+
name?: string;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The result is a plain `{}` with one property per resolved spec key, in spec key order.
|
|
192
|
+
|
|
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.
|
|
194
|
+
- `__$trace__[key]` exists exactly when `key` is in the result.
|
|
195
|
+
- `trace.src` is `"env"`, `"option"`, `"default"`, or for json keys a comma-joined list of those.
|
|
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.
|
|
197
|
+
- Values from `userConfig` and `default` are stored by reference, not cloned (non-json keys).
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
const config = xenvConfig(
|
|
201
|
+
{
|
|
202
|
+
port: { env: ["APP_PORT", "PORT"], type: "number", default: 8080 },
|
|
203
|
+
debug: { env: true, type: "boolean" },
|
|
204
|
+
opts: { env: "OPTS", type: "json", default: { a: 1 } }
|
|
205
|
+
},
|
|
206
|
+
{ opts: { b: 2 } },
|
|
207
|
+
{ _env: { PORT: "9000", OPTS: '{"c":3}' } }
|
|
208
|
+
);
|
|
209
|
+
// config: { port: 9000, opts: { a: 1, b: 2, c: 3 } } (debug is absent)
|
|
210
|
+
// config.__$trace__:
|
|
211
|
+
// port: { src: "env", name: "PORT" }
|
|
212
|
+
// opts: { src: "env,option,default", name: "OPTS" }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Errors
|
|
216
|
+
|
|
217
|
+
Nothing is caught or wrapped. All errors are thrown synchronously from `xenvConfig`.
|
|
218
|
+
|
|
219
|
+
| Condition | Error |
|
|
220
|
+
| --- | --- |
|
|
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.` |
|
|
222
|
+
| `json` type and the env string is not valid JSON | `SyntaxError` from `JSON.parse` |
|
|
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. |
|
|
224
|
+
| `spec` is `undefined` or `null` | `TypeError` from `Object.keys` |
|
|
225
|
+
| A function `default`, `post`, or `merge` throws | the error propagates unchanged |
|
|
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` |
|
|
227
|
+
|
|
228
|
+
Absence of a value is not an error. Missing keys are left out of the result.
|
|
229
|
+
|
|
230
|
+
## Quirks
|
|
231
|
+
|
|
232
|
+
- `type: "json"` needs strict JSON in env. The README example `JSON_OPTION = "{b:90}"` throws `SyntaxError`; use `'{"b":90}'`.
|
|
233
|
+
- `number` env values that are not integers give `NaN` rather than an error. Empty env strings give `NaN`.
|
|
234
|
+
- `envMap` hits are returned as is, with no coercion by `type`.
|
|
235
|
+
- `userConfig` values and defaults are never coerced, only env values are.
|
|
236
|
+
- A key present in the env object with value `undefined` counts as found. For `string` type it yields the string `"undefined"`.
|
|
237
|
+
- The README says the order is env, option, default. The code matches this by default, but only `"default"` is forced last. The README does not document `options.sources`.
|
|
238
|
+
- The README says `boolean` is `x === "true" || ...`. The code lower-cases first, so `"TRUE"` is `true`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xenv-config",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Load config from env, user config, or default spec",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"engines": {
|
|
17
17
|
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
|
18
18
|
},
|
|
19
|
-
"homepage": "https://
|
|
19
|
+
"homepage": "https://fynjs.pages.dev/api/modules/xenv-config",
|
|
20
20
|
"bugs": {
|
|
21
21
|
"url": "https://github.com/jchip/fynjs/issues"
|
|
22
22
|
},
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"author": "Joel Chen",
|
|
44
44
|
"license": "Apache-2.0",
|
|
45
45
|
"files": [
|
|
46
|
-
"dist"
|
|
46
|
+
"dist",
|
|
47
|
+
"docs/reference.md"
|
|
47
48
|
],
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"@types/lodash-es": "^4.17.12",
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
"@vitest/coverage-v8": "^5.0.0",
|
|
52
53
|
"lodash-es": "^4.18.1",
|
|
53
54
|
"prettier": "^3.5.3",
|
|
54
|
-
"publish-util": "^3.1.
|
|
55
|
+
"publish-util": "^3.1.8",
|
|
55
56
|
"typescript": "^7.0.2",
|
|
56
57
|
"vite": "^8.2.2",
|
|
57
58
|
"vitest": "^5.0.0"
|