@rawnodes/config-loader 1.4.0 → 1.6.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 +176 -1
- package/dist/index.d.mts +82 -2
- package/dist/index.d.ts +82 -2
- package/dist/index.js +334 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +328 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ Flexible YAML configuration loader for Node.js applications with environment ove
|
|
|
6
6
|
|
|
7
7
|
- **YAML Configuration** - Load config from YAML files with environment-specific overrides
|
|
8
8
|
- **Environment Variables** - Replace `${VAR}` placeholders with env values
|
|
9
|
+
- **HashiCorp Vault** - Read secrets from Vault using `${vault:path:field}` syntax
|
|
10
|
+
- **AWS Secrets Manager** - Read secrets from AWS using `${aws:secret-name:field}` syntax
|
|
9
11
|
- **Zod Validation** - Optional schema validation with detailed error messages
|
|
10
12
|
- **Docker/K8s Ready** - Mount additional config files via `overrideDir`
|
|
11
13
|
- **Secret Masking** - Automatic masking of sensitive values in logs
|
|
@@ -103,6 +105,21 @@ interface ConfigLoaderOptions<T> {
|
|
|
103
105
|
// Remove empty strings and empty objects from config (default: false)
|
|
104
106
|
// Useful for optional fields with ${VAR:} placeholders
|
|
105
107
|
stripEmpty?: boolean;
|
|
108
|
+
|
|
109
|
+
// HashiCorp Vault options (requires loadConfigAsync)
|
|
110
|
+
vault?: {
|
|
111
|
+
endpoint: string; // Vault server URL
|
|
112
|
+
roleId: string; // AppRole role_id
|
|
113
|
+
secretId: string; // AppRole secret_id
|
|
114
|
+
namespace?: string; // Optional Vault Enterprise namespace
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// AWS Secrets Manager options (requires loadConfigAsync)
|
|
118
|
+
aws?: {
|
|
119
|
+
region: string; // AWS region
|
|
120
|
+
accessKeyId: string; // AWS access key ID
|
|
121
|
+
secretAccessKey: string; // AWS secret access key
|
|
122
|
+
};
|
|
106
123
|
}
|
|
107
124
|
```
|
|
108
125
|
|
|
@@ -161,6 +178,116 @@ const { config } = loadConfig({
|
|
|
161
178
|
- Arrays: empty strings filtered out
|
|
162
179
|
- Other falsy values (`0`, `false`, `null`) are preserved
|
|
163
180
|
|
|
181
|
+
## HashiCorp Vault Integration
|
|
182
|
+
|
|
183
|
+
Read secrets directly from HashiCorp Vault using AppRole authentication.
|
|
184
|
+
|
|
185
|
+
### Setup
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
# config/base.yml
|
|
189
|
+
database:
|
|
190
|
+
host: localhost
|
|
191
|
+
password: ${vault:secret/data/api:DB_PASSWORD}
|
|
192
|
+
port: ${vault:secret/data/api:DB_PORT:5432} # with default value
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { loadConfigAsync } from '@rawnodes/config-loader';
|
|
197
|
+
|
|
198
|
+
const { config } = await loadConfigAsync({
|
|
199
|
+
configDir: './config',
|
|
200
|
+
vault: {
|
|
201
|
+
endpoint: 'https://vault.example.com',
|
|
202
|
+
roleId: process.env.VAULT_ROLE_ID!,
|
|
203
|
+
secretId: process.env.VAULT_SECRET_ID!,
|
|
204
|
+
namespace: 'optional-namespace', // for Vault Enterprise
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Syntax
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
${vault:PATH:FIELD} - Required secret
|
|
213
|
+
${vault:PATH:FIELD:DEFAULT} - With default value (used if secret not found)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Examples:**
|
|
217
|
+
```yaml
|
|
218
|
+
database:
|
|
219
|
+
# Read DB_PASSWORD from secret/data/api
|
|
220
|
+
password: ${vault:secret/data/api:DB_PASSWORD}
|
|
221
|
+
|
|
222
|
+
# With default value (supports colons in default)
|
|
223
|
+
url: ${vault:secret/data/db:URL:postgres://localhost:5432/app}
|
|
224
|
+
|
|
225
|
+
# Mix with env variables
|
|
226
|
+
host: ${DB_HOST:localhost}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Notes
|
|
230
|
+
|
|
231
|
+
- Use `loadConfigAsync()` instead of `loadConfig()` when using Vault
|
|
232
|
+
- Secrets are cached per path during config load (multiple fields from same path = 1 API call)
|
|
233
|
+
- Supports Vault KV v1 and v2 secret engines
|
|
234
|
+
|
|
235
|
+
## AWS Secrets Manager Integration
|
|
236
|
+
|
|
237
|
+
Read secrets from AWS Secrets Manager.
|
|
238
|
+
|
|
239
|
+
### Setup
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
# config/base.yml
|
|
243
|
+
database:
|
|
244
|
+
password: ${aws:my-app/database:DB_PASSWORD}
|
|
245
|
+
host: ${aws:my-app/database:DB_HOST:localhost}
|
|
246
|
+
api:
|
|
247
|
+
key: ${aws:my-app/api-key} # plain string secret (no field)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { loadConfigAsync } from '@rawnodes/config-loader';
|
|
252
|
+
|
|
253
|
+
const { config } = await loadConfigAsync({
|
|
254
|
+
configDir: './config',
|
|
255
|
+
aws: {
|
|
256
|
+
region: 'us-east-1',
|
|
257
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
258
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Syntax
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
${aws:SECRET_NAME} - Plain string secret (entire value)
|
|
267
|
+
${aws:SECRET_NAME:FIELD} - JSON secret, extract field
|
|
268
|
+
${aws:SECRET_NAME:FIELD:DEFAULT} - With default value
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Examples:**
|
|
272
|
+
```yaml
|
|
273
|
+
database:
|
|
274
|
+
# JSON secret: {"DB_PASSWORD": "secret", "DB_USER": "admin"}
|
|
275
|
+
password: ${aws:my-app/database:DB_PASSWORD}
|
|
276
|
+
user: ${aws:my-app/database:DB_USER}
|
|
277
|
+
|
|
278
|
+
# Plain string secret (no field)
|
|
279
|
+
api_key: ${aws:my-app/api-key}
|
|
280
|
+
|
|
281
|
+
# With default value
|
|
282
|
+
host: ${aws:my-app/database:DB_HOST:localhost}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Notes
|
|
286
|
+
|
|
287
|
+
- Use `loadConfigAsync()` instead of `loadConfig()` when using AWS
|
|
288
|
+
- Secrets are cached per secret name during config load
|
|
289
|
+
- Supports both JSON and plain string secrets
|
|
290
|
+
|
|
164
291
|
## Zod Validation
|
|
165
292
|
|
|
166
293
|
```typescript
|
|
@@ -204,6 +331,39 @@ server:
|
|
|
204
331
|
|
|
205
332
|
Files are merged in alphabetical order.
|
|
206
333
|
|
|
334
|
+
## Environment Helpers
|
|
335
|
+
|
|
336
|
+
### `env`
|
|
337
|
+
|
|
338
|
+
Environment detection helpers:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { env } from '@rawnodes/config-loader';
|
|
342
|
+
|
|
343
|
+
env.nodeEnv // process.env.NODE_ENV || 'local'
|
|
344
|
+
env.isProduction // true if NODE_ENV === 'production'
|
|
345
|
+
env.isDevelopment // true if NODE_ENV === 'development'
|
|
346
|
+
env.isTest // true if NODE_ENV === 'test'
|
|
347
|
+
env.isLocal // true if NODE_ENV is 'local' or not set
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Usage with Vault/AWS
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { loadConfigAsync, env } from '@rawnodes/config-loader';
|
|
354
|
+
|
|
355
|
+
const { config } = await loadConfigAsync({
|
|
356
|
+
configDir: './config',
|
|
357
|
+
vault: !env.isLocal && {
|
|
358
|
+
endpoint: process.env.VAULT_ENDPOINT,
|
|
359
|
+
roleId: process.env.VAULT_ROLE_ID,
|
|
360
|
+
secretId: process.env.VAULT_SECRET_ID,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
// If any value is missing, you'll get a clear error:
|
|
364
|
+
// "Vault endpoint is not set" or "Vault roleId is not set"
|
|
365
|
+
```
|
|
366
|
+
|
|
207
367
|
## NestJS Integration
|
|
208
368
|
|
|
209
369
|
```typescript
|
|
@@ -234,7 +394,7 @@ export class ConfigModule {}
|
|
|
234
394
|
|
|
235
395
|
### `loadConfig<T>(options?): ConfigLoaderResult<T>`
|
|
236
396
|
|
|
237
|
-
Loads and merges configuration files.
|
|
397
|
+
Loads and merges configuration files synchronously.
|
|
238
398
|
|
|
239
399
|
**Returns:**
|
|
240
400
|
```typescript
|
|
@@ -245,6 +405,21 @@ interface ConfigLoaderResult<T> {
|
|
|
245
405
|
}
|
|
246
406
|
```
|
|
247
407
|
|
|
408
|
+
### `loadConfigAsync<T>(options?): Promise<ConfigLoaderResult<T>>`
|
|
409
|
+
|
|
410
|
+
Async version with Vault support. Required when using `vault` option.
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
const { config } = await loadConfigAsync({
|
|
414
|
+
configDir: './config',
|
|
415
|
+
vault: {
|
|
416
|
+
endpoint: 'https://vault.example.com',
|
|
417
|
+
roleId: 'role-id',
|
|
418
|
+
secretId: 'secret-id',
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
```
|
|
422
|
+
|
|
248
423
|
### `maskSecrets(obj): unknown`
|
|
249
424
|
|
|
250
425
|
Masks sensitive values in an object. Useful for logging.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
interface VaultOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Vault server endpoint URL
|
|
6
|
+
* @example 'https://vault.example.com'
|
|
7
|
+
*/
|
|
8
|
+
endpoint: string;
|
|
9
|
+
/**
|
|
10
|
+
* AppRole role_id for authentication
|
|
11
|
+
*/
|
|
12
|
+
roleId: string;
|
|
13
|
+
/**
|
|
14
|
+
* AppRole secret_id for authentication
|
|
15
|
+
*/
|
|
16
|
+
secretId: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional namespace for Vault Enterprise
|
|
19
|
+
*/
|
|
20
|
+
namespace?: string;
|
|
21
|
+
}
|
|
22
|
+
interface AwsSecretsOptions {
|
|
23
|
+
/**
|
|
24
|
+
* AWS region
|
|
25
|
+
* @example 'us-east-1'
|
|
26
|
+
*/
|
|
27
|
+
region: string;
|
|
28
|
+
/**
|
|
29
|
+
* AWS access key ID
|
|
30
|
+
*/
|
|
31
|
+
accessKeyId: string;
|
|
32
|
+
/**
|
|
33
|
+
* AWS secret access key
|
|
34
|
+
*/
|
|
35
|
+
secretAccessKey: string;
|
|
36
|
+
}
|
|
3
37
|
interface DotenvOptions {
|
|
4
38
|
/**
|
|
5
39
|
* Path to .env file
|
|
@@ -63,6 +97,18 @@ interface ConfigLoaderOptions<T = unknown> {
|
|
|
63
97
|
* @default false
|
|
64
98
|
*/
|
|
65
99
|
stripEmpty?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* HashiCorp Vault options for secret retrieval
|
|
102
|
+
* If provided, ${vault:path:field} placeholders will be resolved
|
|
103
|
+
* Requires using loadConfigAsync() instead of loadConfig()
|
|
104
|
+
*/
|
|
105
|
+
vault?: VaultOptions;
|
|
106
|
+
/**
|
|
107
|
+
* AWS Secrets Manager options for secret retrieval
|
|
108
|
+
* If provided, ${aws:secret-name:field} placeholders will be resolved
|
|
109
|
+
* Requires using loadConfigAsync() instead of loadConfig()
|
|
110
|
+
*/
|
|
111
|
+
aws?: AwsSecretsOptions;
|
|
66
112
|
}
|
|
67
113
|
interface ConfigLoaderResult<T> {
|
|
68
114
|
config: T;
|
|
@@ -71,14 +117,48 @@ interface ConfigLoaderResult<T> {
|
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
declare function loadConfig<T>(options?: ConfigLoaderOptions<T>): ConfigLoaderResult<T>;
|
|
120
|
+
declare function loadConfigAsync<T>(options?: ConfigLoaderOptions<T>): Promise<ConfigLoaderResult<T>>;
|
|
74
121
|
|
|
75
122
|
type DeepObject = Record<string, unknown>;
|
|
76
123
|
declare function deepMerge(base: DeepObject, override: DeepObject): DeepObject;
|
|
77
124
|
|
|
78
|
-
|
|
125
|
+
interface VaultSecretCache {
|
|
126
|
+
[path: string]: Record<string, unknown>;
|
|
127
|
+
}
|
|
128
|
+
interface VaultClient {
|
|
129
|
+
fetchSecrets(obj: unknown): Promise<VaultSecretCache>;
|
|
130
|
+
getSecret(path: string, field: string): unknown;
|
|
131
|
+
}
|
|
132
|
+
declare function createVaultClient(options: VaultOptions): Promise<VaultClient>;
|
|
133
|
+
|
|
134
|
+
interface AwsSecretsCache {
|
|
135
|
+
[secretName: string]: string | Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
interface AwsSecretsClient {
|
|
138
|
+
fetchSecrets(obj: unknown): Promise<AwsSecretsCache>;
|
|
139
|
+
getSecret(secretName: string, field?: string): unknown;
|
|
140
|
+
}
|
|
141
|
+
declare function createAwsSecretsClient(options: AwsSecretsOptions): Promise<AwsSecretsClient>;
|
|
142
|
+
|
|
143
|
+
interface ReplacePlaceholdersOptions {
|
|
144
|
+
vaultClient?: VaultClient;
|
|
145
|
+
awsClient?: AwsSecretsClient;
|
|
146
|
+
}
|
|
147
|
+
declare function replacePlaceholders(obj: unknown, options?: ReplacePlaceholdersOptions): unknown;
|
|
79
148
|
|
|
80
149
|
declare function maskSecrets(obj: unknown): unknown;
|
|
81
150
|
|
|
82
151
|
declare function stripEmpty(obj: unknown): unknown;
|
|
83
152
|
|
|
84
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Environment helpers
|
|
155
|
+
*/
|
|
156
|
+
declare const env: {
|
|
157
|
+
readonly nodeEnv: string;
|
|
158
|
+
readonly isProduction: boolean;
|
|
159
|
+
readonly isDevelopment: boolean;
|
|
160
|
+
readonly isTest: boolean;
|
|
161
|
+
readonly isLocal: boolean;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export { type AwsSecretsCache, type AwsSecretsClient, type AwsSecretsOptions, type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, type VaultClient, type VaultOptions, type VaultSecretCache, createAwsSecretsClient, createVaultClient, deepMerge, env, loadConfig, loadConfigAsync, maskSecrets, replacePlaceholders, stripEmpty };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
interface VaultOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Vault server endpoint URL
|
|
6
|
+
* @example 'https://vault.example.com'
|
|
7
|
+
*/
|
|
8
|
+
endpoint: string;
|
|
9
|
+
/**
|
|
10
|
+
* AppRole role_id for authentication
|
|
11
|
+
*/
|
|
12
|
+
roleId: string;
|
|
13
|
+
/**
|
|
14
|
+
* AppRole secret_id for authentication
|
|
15
|
+
*/
|
|
16
|
+
secretId: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional namespace for Vault Enterprise
|
|
19
|
+
*/
|
|
20
|
+
namespace?: string;
|
|
21
|
+
}
|
|
22
|
+
interface AwsSecretsOptions {
|
|
23
|
+
/**
|
|
24
|
+
* AWS region
|
|
25
|
+
* @example 'us-east-1'
|
|
26
|
+
*/
|
|
27
|
+
region: string;
|
|
28
|
+
/**
|
|
29
|
+
* AWS access key ID
|
|
30
|
+
*/
|
|
31
|
+
accessKeyId: string;
|
|
32
|
+
/**
|
|
33
|
+
* AWS secret access key
|
|
34
|
+
*/
|
|
35
|
+
secretAccessKey: string;
|
|
36
|
+
}
|
|
3
37
|
interface DotenvOptions {
|
|
4
38
|
/**
|
|
5
39
|
* Path to .env file
|
|
@@ -63,6 +97,18 @@ interface ConfigLoaderOptions<T = unknown> {
|
|
|
63
97
|
* @default false
|
|
64
98
|
*/
|
|
65
99
|
stripEmpty?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* HashiCorp Vault options for secret retrieval
|
|
102
|
+
* If provided, ${vault:path:field} placeholders will be resolved
|
|
103
|
+
* Requires using loadConfigAsync() instead of loadConfig()
|
|
104
|
+
*/
|
|
105
|
+
vault?: VaultOptions;
|
|
106
|
+
/**
|
|
107
|
+
* AWS Secrets Manager options for secret retrieval
|
|
108
|
+
* If provided, ${aws:secret-name:field} placeholders will be resolved
|
|
109
|
+
* Requires using loadConfigAsync() instead of loadConfig()
|
|
110
|
+
*/
|
|
111
|
+
aws?: AwsSecretsOptions;
|
|
66
112
|
}
|
|
67
113
|
interface ConfigLoaderResult<T> {
|
|
68
114
|
config: T;
|
|
@@ -71,14 +117,48 @@ interface ConfigLoaderResult<T> {
|
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
declare function loadConfig<T>(options?: ConfigLoaderOptions<T>): ConfigLoaderResult<T>;
|
|
120
|
+
declare function loadConfigAsync<T>(options?: ConfigLoaderOptions<T>): Promise<ConfigLoaderResult<T>>;
|
|
74
121
|
|
|
75
122
|
type DeepObject = Record<string, unknown>;
|
|
76
123
|
declare function deepMerge(base: DeepObject, override: DeepObject): DeepObject;
|
|
77
124
|
|
|
78
|
-
|
|
125
|
+
interface VaultSecretCache {
|
|
126
|
+
[path: string]: Record<string, unknown>;
|
|
127
|
+
}
|
|
128
|
+
interface VaultClient {
|
|
129
|
+
fetchSecrets(obj: unknown): Promise<VaultSecretCache>;
|
|
130
|
+
getSecret(path: string, field: string): unknown;
|
|
131
|
+
}
|
|
132
|
+
declare function createVaultClient(options: VaultOptions): Promise<VaultClient>;
|
|
133
|
+
|
|
134
|
+
interface AwsSecretsCache {
|
|
135
|
+
[secretName: string]: string | Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
interface AwsSecretsClient {
|
|
138
|
+
fetchSecrets(obj: unknown): Promise<AwsSecretsCache>;
|
|
139
|
+
getSecret(secretName: string, field?: string): unknown;
|
|
140
|
+
}
|
|
141
|
+
declare function createAwsSecretsClient(options: AwsSecretsOptions): Promise<AwsSecretsClient>;
|
|
142
|
+
|
|
143
|
+
interface ReplacePlaceholdersOptions {
|
|
144
|
+
vaultClient?: VaultClient;
|
|
145
|
+
awsClient?: AwsSecretsClient;
|
|
146
|
+
}
|
|
147
|
+
declare function replacePlaceholders(obj: unknown, options?: ReplacePlaceholdersOptions): unknown;
|
|
79
148
|
|
|
80
149
|
declare function maskSecrets(obj: unknown): unknown;
|
|
81
150
|
|
|
82
151
|
declare function stripEmpty(obj: unknown): unknown;
|
|
83
152
|
|
|
84
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Environment helpers
|
|
155
|
+
*/
|
|
156
|
+
declare const env: {
|
|
157
|
+
readonly nodeEnv: string;
|
|
158
|
+
readonly isProduction: boolean;
|
|
159
|
+
readonly isDevelopment: boolean;
|
|
160
|
+
readonly isTest: boolean;
|
|
161
|
+
readonly isLocal: boolean;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export { type AwsSecretsCache, type AwsSecretsClient, type AwsSecretsOptions, type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, type VaultClient, type VaultOptions, type VaultSecretCache, createAwsSecretsClient, createVaultClient, deepMerge, env, loadConfig, loadConfigAsync, maskSecrets, replacePlaceholders, stripEmpty };
|