@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 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
- declare function replacePlaceholders(obj: unknown): unknown;
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
- export { type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, deepMerge, loadConfig, maskSecrets, replacePlaceholders, stripEmpty };
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
- declare function replacePlaceholders(obj: unknown): unknown;
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
- export { type ConfigLoaderOptions, type ConfigLoaderResult, type DotenvOptions, deepMerge, loadConfig, maskSecrets, replacePlaceholders, stripEmpty };
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 };