@jeengbe/config 0.0.7 → 0.0.9
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 +128 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,136 @@
|
|
|
1
1
|
<h1 align="center">@jeengbe/config</h1>
|
|
2
2
|
<div align="center">
|
|
3
3
|
|
|
4
|
+
A declarative, strongly typed schema for parsing and validating environment variables in TypeScript.
|
|
5
|
+
|
|
4
6
|
[](https://github.com/jeengbe/ts-packages/blob/master/packages/config/LICENSE)
|
|
5
7
|
[](https://www.npmjs.com/package/@jeengbe/config)
|
|
6
8
|
[](https://jsr.io/@jeengbe/config)
|
|
9
|
+
[](https://app.codecov.io/gh/jeengbe/ts-packages/tree/master/packages/config)
|
|
7
10
|
|
|
8
11
|
</div>
|
|
12
|
+
|
|
13
|
+
Define your environment variables once as a schema, and get back a plain, fully typed config object. Missing or invalid values are collected across the whole schema and reported together, so you find out about every misconfigured variable at once, rather than one crash at a time.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
The package is published to [npm](https://www.npmjs.com/package/@jeengbe/config) and [JSR](https://jsr.io/@jeengbe/config) as `@jeengbe/config`. Versions follow Semantic Versioning.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Defining and loading a schema
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { env } from '@jeengbe/config';
|
|
25
|
+
|
|
26
|
+
const config = env.load({
|
|
27
|
+
port: env.number('PORT', 3000),
|
|
28
|
+
host: env.string('HOST', '0.0.0.0'),
|
|
29
|
+
logLevel: env.enum('LOG_LEVEL', ['debug', 'info', 'warn', 'error'], 'info'),
|
|
30
|
+
});
|
|
31
|
+
// config: { port: number; host: string; logLevel: 'debug' | 'info' | 'warn' | 'error' }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`env.load` reads from `process.env` (values are trimmed, and a missing or whitespace-only value is treated as absent), validates every field, and returns a plain object typed to match the schema. If anything is missing or invalid, it throws a single error combining every failure:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Failed to load config: FOO ($.foo): required, NUM ($.num): invalid number
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Schemas nest using plain objects:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const config = env.load({
|
|
44
|
+
server: {
|
|
45
|
+
port: env.number('PORT', 3000),
|
|
46
|
+
},
|
|
47
|
+
database: {
|
|
48
|
+
url: env.string('DATABASE_URL'),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
// config: { server: { port: number }; database: { url: string } }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Scalars
|
|
55
|
+
|
|
56
|
+
- `env.string(key, defaultValue?)`
|
|
57
|
+
- `env.number(key, defaultValue?)` — accepts integers and decimals, including negative numbers.
|
|
58
|
+
- `env.boolean(key, defaultValue?)` — accepts `'true'`/`'false'`, case-insensitively.
|
|
59
|
+
- `env.enum(key, values, defaultValue?)` — restricts the value to one of a fixed list, typed as a literal union of `values`.
|
|
60
|
+
|
|
61
|
+
Without a `defaultValue`, all of these are required and fail validation when the variable is missing.
|
|
62
|
+
|
|
63
|
+
### Custom scalars (`env.custom`)
|
|
64
|
+
|
|
65
|
+
For anything else, write your own parser with `env.custom`. It returns a `ValidationResult<T>` (an `Either<readonly string[], T>` from `@jeengbe/prelude`):
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { env } from '@jeengbe/config';
|
|
69
|
+
import { Either } from '@jeengbe/prelude';
|
|
70
|
+
|
|
71
|
+
const apiUrl = env.custom('API_URL', (value) => {
|
|
72
|
+
try {
|
|
73
|
+
return Either.right(new URL(value));
|
|
74
|
+
} catch {
|
|
75
|
+
return Either.left(['must be a valid URL']);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Optional values (`.optional()`)
|
|
81
|
+
|
|
82
|
+
Any scalar node can be made optional. This resolves to `undefined` when the variable is missing, ignoring the default value on the underlying node, instead of failing validation:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const timeoutMs = env.number('TIMEOUT_MS').optional();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Transforming values (`.transform()`)
|
|
89
|
+
|
|
90
|
+
Every node can be transformed into a different value. The transform function receives the already-validated value and itself returns a `ValidationResult`, so it can also fail validation:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const port = env
|
|
94
|
+
.number('PORT')
|
|
95
|
+
.transform((n) =>
|
|
96
|
+
n > 0 && n < 65536 ? Either.right(n) : Either.left(['must be between 1 and 65535']),
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Arrays (`env.array`)
|
|
101
|
+
|
|
102
|
+
`env.array` splits a comma-separated string and validates each item against a scalar node:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const ports = env.array(env.number('PORTS'));
|
|
106
|
+
// PORTS="3000,3001,3002" -> [3000, 3001, 3002]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
An empty item between commas (e.g. `"1,,3"`) is treated as `undefined` for the item schema (mark the item node `.optional()` to allow that). A missing variable falls back to the array's own `defaultValue`, if one was given; the item schema's default is not applied per-missing-item.
|
|
110
|
+
|
|
111
|
+
### Discriminated variants (`env.discriminate`)
|
|
112
|
+
|
|
113
|
+
Use `env.discriminate` to pick between several shapes based on the value of another variable, similar to a discriminated union:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const storage = env.discriminate('type', env.enum('STORAGE_TYPE', ['s3', 'local']), {
|
|
117
|
+
s3: { bucket: env.string('S3_BUCKET') },
|
|
118
|
+
local: { path: env.string('LOCAL_PATH') },
|
|
119
|
+
});
|
|
120
|
+
// storage: { type: 's3'; bucket: string } | { type: 'local'; path: string }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Feature flags (`ifEnabled`)
|
|
124
|
+
|
|
125
|
+
`ifEnabled` wraps `env.discriminate` for the common case of gating a block of config behind a boolean flag:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { ifEnabled } from '@jeengbe/config';
|
|
129
|
+
|
|
130
|
+
const feature = ifEnabled('FEATURE_ENABLED', {
|
|
131
|
+
apiKey: env.string('FEATURE_API_KEY'),
|
|
132
|
+
});
|
|
133
|
+
// feature: { enabled: true; apiKey: string } | { enabled: false }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This resolves to `{ enabled: true, apiKey: string }` when `FEATURE_ENABLED` is `'true'`, or `{ enabled: false }` otherwise.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeengbe/config",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "A declarative, strongly typed schema for parsing and validating environment variables in TypeScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"config",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"!src/**/fake.ts"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@jeengbe/prelude": "0.1.
|
|
48
|
+
"@jeengbe/prelude": "0.1.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"oxfmt": "^0.60.0",
|