envapt 4.0.1 → 4.1.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
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/banner.png" alt="Envapt – The apt way to handle environment variables" width="100%" />
2
+ <img src="/.github/assets/banner.png" alt="Envapt – The apt way to handle environment variables" width="100%" />
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -34,6 +34,7 @@
34
34
  - 🔖 **Tagged Template Resolver** - Tagged template literals with environment variable resolution
35
35
  - 🌍 **Environment Detection** - Built-in development/staging/production handling
36
36
  - 💪 **Edge Case Handling** - Robust validation and parsing for all scenarios
37
+ - 🔑 **Multi-Key Lookups** - Provide ordered lists of env keys and Envapt will use the first value it finds
37
38
  - 🛡️ **Type Safety** - Full TypeScript support with proper type inference _(TypeScript optional)_
38
39
  - 📂 **Multiple .env Files** - Load from multiple sources
39
40
  - ⚡ **Lightweight** - Minimal overhead with [`dotenv`](https://www.npmjs.com/package/dotenv) bundled
@@ -46,45 +47,45 @@
46
47
 
47
48
  - [Requirements](#requirements)
48
49
  - [Quick Start](#quick-start)
49
- - [Installation](#installation)
50
- - [Basic Usage](#basic-usage)
50
+ - [Installation](#installation)
51
+ - [Basic Usage](#basic-usage)
51
52
 
52
53
  ### 🧬 API Reference
53
54
 
54
55
  - [API Reference](#api-reference)
55
- - [Decorator API](#decorator-api)
56
- - [Modern Syntax (Recommended)](#modern-syntax-recommended)
57
- - [Classic Syntax](#classic-syntax)
58
- - [Automatic Runtime Type Detection](#automatic-runtime-type-detection)
59
- - [Primitive Converters](#primitive-converters)
60
- - [Built-in Converters](#built-in-converters)
61
- - [Custom Array Converters](#custom-array-converters)
62
- - [Custom Converters](#custom-converters)
63
- - [Handling Missing Values](#handling-missing-values)
64
- - [Functional API](#functional-api)
65
- - [Tagged Template Resolver](#tagged-template-resolver)
66
- - [Converter Type Quick Reference](#converter-type-quick-reference)
56
+ - [Decorator API](#decorator-api)
57
+ - [Modern Syntax (Recommended)](#modern-syntax-recommended)
58
+ - [Classic Syntax](#classic-syntax)
59
+ - [Automatic Runtime Type Detection](#automatic-runtime-type-detection)
60
+ - [Primitive Converters](#primitive-converters)
61
+ - [Built-in Converters](#built-in-converters)
62
+ - [Custom Array Converters](#custom-array-converters)
63
+ - [Custom Converters](#custom-converters)
64
+ - [Handling Missing Values](#handling-missing-values)
65
+ - [Functional API](#functional-api)
66
+ - [Tagged Template Resolver](#tagged-template-resolver)
67
+ - [Converter Type Quick Reference](#converter-type-quick-reference)
67
68
 
68
69
  ### 🌍 Environment & Templates
69
70
 
70
71
  - [Environment Detection](#environment-detection)
71
- - [Environment Management](#environment-management)
72
+ - [Environment Management](#environment-management)
72
73
  - [Template Variables](#template-variables)
73
- - [Circular Reference Protection](#circular-reference-protection)
74
+ - [Circular Reference Protection](#circular-reference-protection)
74
75
 
75
76
  ### 🛠 Configuration & Errors
76
77
 
77
78
  - [Configuration](#configuration)
78
- - [Multiple .env Files](#multiple-env-files)
79
- - [Dotenv Configuration](#dotenv-configuration)
79
+ - [Multiple .env Files](#multiple-env-files)
80
+ - [Dotenv Configuration](#dotenv-configuration)
80
81
  - [Error Handling](#error-handling)
81
- - [Error Code Reference](#error-code-reference)
82
+ - [Error Code Reference](#error-code-reference)
82
83
 
83
84
  ### 🚀 Examples
84
85
 
85
86
  - [Advanced Examples](#advanced-examples)
86
- - [JavaScript](#javascript)
87
- - [TypeScript](#typescript)
87
+ - [JavaScript](#javascript)
88
+ - [TypeScript](#typescript)
88
89
 
89
90
  <div align="right">
90
91
 
@@ -103,11 +104,11 @@
103
104
  ```jsonc
104
105
  // tsconfig.json (required settings for decorators)
105
106
  {
106
- "experimentalDecorators": true,
107
- "module": "esnext", // or "nodenext"
108
- "moduleResolution": "bundler", // or "nodenext"
109
- "target": "ESNext",
110
- "lib": ["ESNext"]
107
+ "experimentalDecorators": true,
108
+ "module": "esnext", // or "nodenext"
109
+ "moduleResolution": "bundler", // or "nodenext"
110
+ "target": "ESNext",
111
+ "lib": ["ESNext"]
111
112
  }
112
113
  ```
113
114
 
@@ -163,6 +164,10 @@ console.log(`URL: ${url}`); // "http://localhost:8443"
163
164
  const corsOrigins = Envapter.getUsing('ALLOWED_ORIGINS', Converters.Array, []);
164
165
  const dbConfig = Envapter.getUsing('DATABASE_CONFIG', Converters.Json, {});
165
166
 
167
+ // Multi-key lookup: try primary, then replica, then fall back
168
+ const dbUrl = Envapter.get(['PRIMARY_DB_URL', 'REPLICA_DB_URL'], 'sqlite://memory');
169
+ const httpPort = Envapter.getNumber(['APP_PORT', 'PORT'], 3000);
170
+
166
171
  // Tagged template literals
167
172
  const message = Envapter.resolve`Server ${'APP_URL'} is ready!`;
168
173
  console.log(message); // "Server http://localhost:8443 is ready!"
@@ -175,36 +180,40 @@ import { Envapt, Envapter, Converters } from 'envapt';
175
180
 
176
181
  // Global app configuration (static properties)
177
182
  class AppConfig extends Envapter {
178
- @Envapt('APP_PORT', 3000)
179
- static readonly port: number;
180
-
181
- // The Classic Syntax only works for Primitive Converters. Converters.Url is a Built-in Converter.
182
- @Envapt('APP_URL', { fallback: new URL('http://localhost:3000'), converter: Converters.Url })
183
- static readonly url: URL;
184
-
185
- @Envapt('ALLOWED_ORIGINS', {
186
- fallback: ['http://localhost:3000'],
187
- converter: Converters.Array
188
- })
189
- static readonly allowedOrigins: string[];
183
+ @Envapt('APP_PORT', 3000)
184
+ static readonly port: number;
185
+
186
+ // The Classic Syntax only works for Primitive Converters. Converters.Url is a Built-in Converter.
187
+ @Envapt('APP_URL', { fallback: new URL('http://localhost:3000'), converter: Converters.Url })
188
+ static readonly url: URL;
189
+
190
+ // Prefer CLOUD_REDIS_URL but fall back to classic REDIS_URL when missing
191
+ @Envapt(['CLOUD_REDIS_URL', 'REDIS_URL'], 'redis://localhost:6379')
192
+ static readonly redisUrl: string;
193
+
194
+ @Envapt('ALLOWED_ORIGINS', {
195
+ fallback: ['http://localhost:3000'],
196
+ converter: Converters.Array
197
+ })
198
+ static readonly allowedOrigins: string[];
190
199
  }
191
200
 
192
201
  // Service configuration (instance properties)
193
202
  class DatabaseService {
194
- @Envapt('DATABASE_URL', 'sqlite://memory')
195
- declare readonly databaseUrl: string;
203
+ @Envapt('DATABASE_URL', 'sqlite://memory')
204
+ declare readonly databaseUrl: string;
196
205
 
197
- // Will detect that '10' is a number and set the runtime type accordingly
198
- @Envapt('MAX_CONNECTIONS', 10)
199
- declare readonly maxConnections: number;
206
+ // Will detect that '10' is a number and set the runtime type accordingly
207
+ @Envapt('MAX_CONNECTIONS', 10)
208
+ declare readonly maxConnections: number;
200
209
 
201
- @Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 5000 })
202
- declare readonly timeout: number; // Converts "5s" to 5000ms
210
+ @Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 5000 })
211
+ declare readonly timeout: number; // Converts "5s" to 5000ms
203
212
 
204
- async connect() {
205
- console.log(`Connecting to ${this.databaseUrl}`);
206
- // Connection logic here
207
- }
213
+ async connect() {
214
+ console.log(`Connecting to ${this.databaseUrl}`);
215
+ // Connection logic here
216
+ }
208
217
  }
209
218
 
210
219
  // Usage
@@ -241,6 +250,11 @@ The `@Envapt` decorator can be used on both **static** and **instance** class pr
241
250
  @Envapt('ENV_VAR', { fallback?: T, converter?: EnvConverter<T> })
242
251
  ```
243
252
 
253
+ > [!NOTE]
254
+ > The first argument can be a string **or** an ordered array of strings:
255
+ > `@Envapt(['PRIMARY_URL', 'SECONDARY_URL'], { fallback: 'https://example.com' })`.
256
+ > Envapt will resolve the first key that exists.
257
+ >
244
258
  > [!TIP]
245
259
  > **Generic Typing for Better IntelliSense**
246
260
  >
@@ -261,35 +275,42 @@ The `@Envapt` decorator can be used on both **static** and **instance** class pr
261
275
  @Envapt('ENV_VAR', fallback?, converter?)
262
276
  ```
263
277
 
278
+ Need to chain multiple keys with the classic API? Pass an array instead of a string:
279
+
280
+ ```ts
281
+ @Envapt(['HOST_PRIMARY', 'HOST_SECONDARY'], 'localhost')
282
+ static readonly host: string;
283
+ ```
284
+
264
285
  #### Automatic Runtime Type Detection
265
286
 
266
287
  Types are automatically inferred from fallback values.
267
288
 
268
289
  ```ts
269
290
  class Config extends Envapter {
270
- // Static properties for global settings
271
- @Envapt('APP_NAME', 'MyApp') // string
272
- static readonly appName: string;
291
+ // Static properties for global settings
292
+ @Envapt('APP_NAME', 'MyApp') // string
293
+ static readonly appName: string;
273
294
 
274
- @Envapt('APP_PORT', 3000) // number
275
- static readonly port: number;
295
+ @Envapt('APP_PORT', 3000) // number
296
+ static readonly port: number;
276
297
 
277
- @Envapt('DEBUG_MODE', false) // boolean
278
- static readonly debugMode: boolean;
298
+ @Envapt('DEBUG_MODE', false) // boolean
299
+ static readonly debugMode: boolean;
279
300
 
280
- // Instance properties for service-specific settings
281
- @Envapt('SMTP_HOST', 'localhost') // string
282
- declare readonly smtpHost: string;
301
+ // Instance properties for service-specific settings
302
+ @Envapt('SMTP_HOST', 'localhost') // string
303
+ declare readonly smtpHost: string;
283
304
 
284
- @Envapt('SMTP_PORT', 587) // number
285
- declare readonly smtpPort: number;
305
+ @Envapt('SMTP_PORT', 587) // number
306
+ declare readonly smtpPort: number;
286
307
 
287
- @Envapt('SMTP_SECURE', true) // boolean
288
- declare readonly smtpSecure: boolean;
308
+ @Envapt('SMTP_SECURE', true) // boolean
309
+ declare readonly smtpSecure: boolean;
289
310
 
290
- sendEmail(to: string, subject: string) {
291
- console.log(`Sending via ${this.smtpHost}:${this.smtpPort}`);
292
- }
311
+ sendEmail(to: string, subject: string) {
312
+ console.log(`Sending via ${this.smtpHost}:${this.smtpPort}`);
313
+ }
293
314
  }
294
315
  ```
295
316
 
@@ -304,28 +325,28 @@ Envapt allows using the 5 "primitive" type-like converters. These **will** coerc
304
325
 
305
326
  ```ts
306
327
  class Config extends Envapter {
307
- @Envapt('PORT_STRING', { fallback: 'hello-world', converter: String })
308
- static readonly portAsString: string;
328
+ @Envapt('PORT_STRING', { fallback: 'hello-world', converter: String })
329
+ static readonly portAsString: string;
309
330
 
310
- @Envapt('DEBUG_FLAG', { fallback: true, converter: Boolean })
311
- static readonly debugMode: boolean;
331
+ @Envapt('DEBUG_FLAG', { fallback: true, converter: Boolean })
332
+ static readonly debugMode: boolean;
312
333
 
313
- @Envapt('USER_ID', { fallback: 12345, converter: Number })
314
- static readonly userId: number;
334
+ @Envapt('USER_ID', { fallback: 12345, converter: Number })
335
+ static readonly userId: number;
315
336
 
316
- @Envapt('MAX_SAFE_INT', { fallback: 9007199254740991n, converter: BigInt })
317
- static readonly maxSafeInt: bigint;
337
+ @Envapt('MAX_SAFE_INT', { fallback: 9007199254740991n, converter: BigInt })
338
+ static readonly maxSafeInt: bigint;
318
339
 
319
- @Envapt('APP_INSTANCE', { fallback: Symbol(main), converter: Symbol })
320
- static readonly appInstance: symbol;
340
+ @Envapt('APP_INSTANCE', { fallback: Symbol(main), converter: Symbol })
341
+ static readonly appInstance: symbol;
321
342
 
322
- // Instance properties work the same way
323
- @Envapt('CONNECTION_TIMEOUT', { fallback: 5000, converter: Number })
324
- declare readonly timeout: number;
343
+ // Instance properties work the same way
344
+ @Envapt('CONNECTION_TIMEOUT', { fallback: 5000, converter: Number })
345
+ declare readonly timeout: number;
325
346
 
326
- // Type coercion example
327
- @Envapt('PERMISSIONS', { fallback: '72394823472342983', converter: BigInt })
328
- declare readonly permissions: bigint; // Converts "72394823472342983" to BigInt
347
+ // Type coercion example
348
+ @Envapt('PERMISSIONS', { fallback: '72394823472342983', converter: BigInt })
349
+ declare readonly permissions: bigint; // Converts "72394823472342983" to BigInt
329
350
  }
330
351
  ```
331
352
 
@@ -354,32 +375,32 @@ Envapt provides many built-in converters for common patterns:
354
375
 
355
376
  ```ts
356
377
  class Config extends Envapter {
357
- // Basic types
358
- @Envapt('APP_NAME', { converter: Converters.String, fallback: 'MyApp' })
359
- static readonly appName: string;
378
+ // Basic types
379
+ @Envapt('APP_NAME', { converter: Converters.String, fallback: 'MyApp' })
380
+ static readonly appName: string;
360
381
 
361
- @Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
362
- static readonly port: number;
382
+ @Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
383
+ static readonly port: number;
363
384
 
364
- @Envapt('PRODUCTION_MODE', { converter: Converters.Boolean, fallback: false })
365
- static readonly productionMode: boolean;
385
+ @Envapt('PRODUCTION_MODE', { converter: Converters.Boolean, fallback: false })
386
+ static readonly productionMode: boolean;
366
387
 
367
- // Advanced types
368
- @Envapt('CORS_ORIGINS', { converter: Converters.Array, fallback: [] })
369
- static readonly corsOrigins: string[];
388
+ // Advanced types
389
+ @Envapt('CORS_ORIGINS', { converter: Converters.Array, fallback: [] })
390
+ static readonly corsOrigins: string[];
370
391
 
371
- @Envapt('CONFIG_JSON', { converter: Converters.Json, fallback: {} })
372
- static readonly config: object;
392
+ @Envapt('CONFIG_JSON', { converter: Converters.Json, fallback: {} })
393
+ static readonly config: object;
373
394
 
374
- @Envapt('API_URL', { converter: Converters.Url, fallback: new URL('http://localhost') })
375
- static readonly apiUrl: URL;
395
+ @Envapt('API_URL', { converter: Converters.Url, fallback: new URL('http://localhost') })
396
+ static readonly apiUrl: URL;
376
397
 
377
- @Envapt('TIMEOUT', { converter: Converters.Time, fallback: 5000 })
378
- static readonly timeout: number; // Converts "30s" to 30000ms
398
+ @Envapt('TIMEOUT', { converter: Converters.Time, fallback: 5000 })
399
+ static readonly timeout: number; // Converts "30s" to 30000ms
379
400
 
380
- // Instance properties work the same way
381
- @Envapt('CACHE_TTL', { converter: Converters.Time, fallback: 3600000 })
382
- declare readonly cacheTtl: number; // "1h" becomes 3600000ms
401
+ // Instance properties work the same way
402
+ @Envapt('CACHE_TTL', { converter: Converters.Time, fallback: 3600000 })
403
+ declare readonly cacheTtl: number; // "1h" becomes 3600000ms
383
404
  }
384
405
  ```
385
406
 
@@ -428,20 +449,20 @@ For more control over array parsing:
428
449
 
429
450
  ```ts
430
451
  class Config extends Envapter {
431
- // Basic array (comma-separated strings)
432
- @Envapt('TAGS', { converter: Converters.Array, fallback: [] })
433
- static readonly tags: string[];
452
+ // Basic array (comma-separated strings)
453
+ @Envapt('TAGS', { converter: Converters.Array, fallback: [] })
454
+ static readonly tags: string[];
434
455
 
435
- // Custom delimiter
436
- @Envapt('ALLOWED_METHODS', { converter: { delimiter: '|' }, fallback: ['GET'] })
437
- declare readonly allowedMethods: string[];
456
+ // Custom delimiter
457
+ @Envapt('ALLOWED_METHODS', { converter: { delimiter: '|' }, fallback: ['GET'] })
458
+ declare readonly allowedMethods: string[];
438
459
 
439
- // Custom delimiter with type conversion
440
- @Envapt('RATE_LIMITS', { converter: { delimiter: ',', type: Converters.Number }, fallback: [100] })
441
- declare readonly rateLimits: number[];
460
+ // Custom delimiter with type conversion
461
+ @Envapt('RATE_LIMITS', { converter: { delimiter: ',', type: Converters.Number }, fallback: [100] })
462
+ declare readonly rateLimits: number[];
442
463
 
443
- @Envapt('FEATURE_FLAGS', { converter: { delimiter: ';', type: 'boolean' }, fallback: [false] })
444
- declare readonly featureFlags: boolean[];
464
+ @Envapt('FEATURE_FLAGS', { converter: { delimiter: ';', type: 'boolean' }, fallback: [false] })
465
+ declare readonly featureFlags: boolean[];
445
466
  }
446
467
  ```
447
468
 
@@ -470,28 +491,28 @@ Transform environment values to any type:
470
491
 
471
492
  ```ts
472
493
  class Config extends Envapter {
473
- @Envapt('TAGS', {
474
- fallback: new Set(['default']),
475
- converter: (raw, fallback) => {
476
- if (!raw) return fallback;
477
- return new Set(raw.split(',').map((s) => s.trim()));
478
- }
479
- })
480
- static readonly tags: Set<string>;
481
-
482
- @Envapt('NOTIFICATION_CHANNELS', {
483
- fallback: new Map([['email', 'enabled']]),
484
- converter: (raw, fallback) => {
485
- if (!raw) return fallback;
486
- const map = new Map();
487
- raw.split(',').forEach((pair) => {
488
- const [key, value] = pair.split(':');
489
- map.set(key?.trim(), value?.trim() || 'enabled');
490
- });
491
- return map;
492
- }
493
- })
494
- declare readonly channels: Map<string, string>;
494
+ @Envapt('TAGS', {
495
+ fallback: new Set(['default']),
496
+ converter: (raw, fallback) => {
497
+ if (!raw) return fallback;
498
+ return new Set(raw.split(',').map((s) => s.trim()));
499
+ }
500
+ })
501
+ static readonly tags: Set<string>;
502
+
503
+ @Envapt('NOTIFICATION_CHANNELS', {
504
+ fallback: new Map([['email', 'enabled']]),
505
+ converter: (raw, fallback) => {
506
+ if (!raw) return fallback;
507
+ const map = new Map();
508
+ raw.split(',').forEach((pair) => {
509
+ const [key, value] = pair.split(':');
510
+ map.set(key?.trim(), value?.trim() || 'enabled');
511
+ });
512
+ return map;
513
+ }
514
+ })
515
+ declare readonly channels: Map<string, string>;
495
516
  }
496
517
  ```
497
518
 
@@ -520,21 +541,21 @@ Control what happens when environment variables don't exist:
520
541
 
521
542
  ```ts
522
543
  class Config extends Envapter {
523
- // Returns undefined if not found
524
- @Envapt('OPTIONAL_FEATURE', { fallback: undefined })
525
- static readonly optionalFeature: string | undefined;
544
+ // Returns undefined if not found
545
+ @Envapt('OPTIONAL_FEATURE', { fallback: undefined })
546
+ static readonly optionalFeature: string | undefined;
526
547
 
527
- // Returns null if not found (no fallback provided)
528
- @Envapt('MISSING_CONFIG', { converter: Converters.String })
529
- static readonly missingConfig: string | null;
548
+ // Returns null if not found (no fallback provided)
549
+ @Envapt('MISSING_CONFIG', { converter: Converters.String })
550
+ static readonly missingConfig: string | null;
530
551
 
531
- // Uses fallback if not found
532
- @Envapt('DEFAULT_THEME', { fallback: 'light' })
533
- static readonly defaultTheme: string;
552
+ // Uses fallback if not found
553
+ @Envapt('DEFAULT_THEME', { fallback: 'light' })
554
+ static readonly defaultTheme: string;
534
555
 
535
- // Instance properties work the same way
536
- @Envapt('LOG_FILE_PATH', { fallback: undefined })
537
- declare readonly logFilePath: string | undefined;
556
+ // Instance properties work the same way
557
+ @Envapt('LOG_FILE_PATH', { fallback: undefined })
558
+ declare readonly logFilePath: string | undefined;
538
559
  }
539
560
  ```
540
561
 
@@ -557,6 +578,9 @@ const jsonData = Envapter.getUsing('CONFIG_JSON', Converters.Json);
557
578
  const urlArray = Envapter.getUsing('API_URLS', { delimiter: ',', type: Converters.Url });
558
579
  const customData = Envapter.getWith('RAW_DATA', (raw) => raw?.split('|').map((s) => s.trim()));
559
580
 
581
+ // Multi-key inputs work everywhere: Envapt will read left-to-right
582
+ const secretsHost = Envapter.get(['SECRETS_HOST', 'DEFAULT_HOST'], 'localhost');
583
+
560
584
  // Instance methods (same API available)
561
585
  const envapter = new Envapter();
562
586
  const value = envapter.get('VAR', 'default');
@@ -571,6 +595,7 @@ import { Envapter, Converters } from 'envapt';
571
595
  // Use built-in converters directly
572
596
  const config = Envapter.getUsing('API_CONFIG', Converters.Json, { default: 'value' });
573
597
  const urls = Envapter.getUsing('SERVICE_URLS', { delimiter: '|', type: Converters.Url });
598
+ const pgUrl = Envapter.getUsing(['PRIMARY_PG_URL', 'SECONDARY_PG_URL'], Converters.Url);
574
599
 
575
600
  // TypeScript: Use type override for better type inference
576
601
  const typedConfig = Envapter.getUsing<{ host: string; port: number; ssl: boolean }>('DATABASE_CONFIG', Converters.Json);
@@ -602,9 +627,9 @@ const result = envapter.getUsing('DATABASE_CONFIG', Converters.Json);
602
627
  >
603
628
  > // Override with specific interface
604
629
  > interface DatabaseConfig {
605
- > host: string;
606
- > port: number;
607
- > ssl: boolean;
630
+ > host: string;
631
+ > port: number;
632
+ > ssl: boolean;
608
633
  > }
609
634
  > const dbConfig = Envapter.getUsing<DatabaseConfig>('DB_CONFIG', Converters.Json);
610
635
  > // dbConfig is now properly typed as DatabaseConfig
@@ -738,11 +763,11 @@ import { Envapter } from 'envapt';
738
763
 
739
764
  // Set dotenv configuration options
740
765
  Envapter.dotenvConfig = {
741
- encoding: 'latin1', // File encoding (default: 'utf8')
742
- debug: true, // Enable debug logging
743
- override: true, // Override existing environment variables
744
- quiet: false, // Suppress non-error output (default: true)
745
- DOTENV_KEY: 'key...' // Decryption key for .env.vault files
766
+ encoding: 'latin1', // File encoding (default: 'utf8')
767
+ debug: true, // Enable debug logging
768
+ override: true, // Override existing environment variables
769
+ quiet: false, // Suppress non-error output (default: true)
770
+ DOTENV_KEY: 'key...' // Decryption key for .env.vault files
746
771
  };
747
772
 
748
773
  // Get current configuration
@@ -795,26 +820,26 @@ Envapt provides detailed error codes for better debugging and error handling:
795
820
  import { EnvaptError, EnvaptErrorCodes } from 'envapt';
796
821
 
797
822
  try {
798
- // This will throw an error for invalid configuration
799
- Envapter.dotenvConfig = { path: '.env.custom' };
823
+ // This will throw an error for invalid configuration
824
+ Envapter.dotenvConfig = { path: '.env.custom' };
800
825
  } catch (error) {
801
- if (error instanceof EnvaptError) {
802
- console.log('Error code:', error.code);
803
- console.log('Error message:', error.message);
804
-
805
- // Handle specific error types
806
- switch (error.code) {
807
- case EnvaptErrorCodes.InvalidUserDefinedConfig:
808
- console.log('Invalid configuration provided');
809
- break;
810
- case EnvaptErrorCodes.EnvFileNotFound:
811
- console.log('Environment file not found');
812
- break;
813
- default:
814
- console.warn('Unhandled error code:', error.code);
815
- break;
826
+ if (error instanceof EnvaptError) {
827
+ console.log('Error code:', error.code);
828
+ console.log('Error message:', error.message);
829
+
830
+ // Handle specific error types
831
+ switch (error.code) {
832
+ case EnvaptErrorCodes.InvalidUserDefinedConfig:
833
+ console.log('Invalid configuration provided');
834
+ break;
835
+ case EnvaptErrorCodes.EnvFileNotFound:
836
+ console.log('Environment file not found');
837
+ break;
838
+ default:
839
+ console.warn('Unhandled error code:', error.code);
840
+ break;
841
+ }
816
842
  }
817
- }
818
843
  }
819
844
  ```
820
845
 
@@ -846,6 +871,7 @@ try {
846
871
  | `MissingDelimiter` (301) | Delimiter is missing in array converter config |
847
872
  | `InvalidUserDefinedConfig` (302) | Invalid user-defined configuration provided |
848
873
  | `EnvFilesNotFound` (303) | Specified environment file doesn't exist |
874
+ | `InvalidKeyInput` (304) | Invalid key input (not string or string array) |
849
875
 
850
876
  <div align="right">
851
877
 
@@ -862,35 +888,35 @@ import { Envapter, Converters } from 'envapt';
862
888
 
863
889
  // Global configuration
864
890
  const config = {
865
- port: Envapter.getNumber('PORT', 3000),
866
- requestTimeout: Envapter.getUsing('REQUEST_TIMEOUT', Converters.Time, 10000), // "5s" -> 5000ms
867
- featureFlags: Envapter.getWith(
868
- 'FEATURE_FLAGS',
869
- (raw, fallback) => {
870
- if (!raw) return fallback;
871
- return new Set(raw.split(',').map((s) => s.trim()));
872
- },
873
- new Set(['basic'])
874
- )
891
+ port: Envapter.getNumber('PORT', 3000),
892
+ requestTimeout: Envapter.getUsing('REQUEST_TIMEOUT', Converters.Time, 10000), // "5s" -> 5000ms
893
+ featureFlags: Envapter.getWith(
894
+ 'FEATURE_FLAGS',
895
+ (raw, fallback) => {
896
+ if (!raw) return fallback;
897
+ return new Set(raw.split(',').map((s) => s.trim()));
898
+ },
899
+ new Set(['basic'])
900
+ )
875
901
  };
876
902
 
877
903
  // Service configuration
878
904
  class DatabaseService {
879
- constructor() {
880
- this.databaseUrl = Envapter.get('DB_URL', 'sqlite://memory');
881
- this.cacheTtl = Envapter.getUsing('CACHE_TTL', Converters.Time, 3600000); // "1h" -> 3600000ms
882
- this.redisUrls = Envapter.getWith(
883
- 'REDIS_URLS',
884
- (raw, fallback) => (raw ? raw.split(',').map((s) => new URL(s)) : fallback),
885
- [new URL('redis://localhost:6379')]
886
- );
887
- }
888
-
889
- async initialize() {
890
- console.log(`App running on port ${config.port}`);
891
- console.log(`Database: ${this.databaseUrl}`);
892
- console.log(`Cache TTL: ${this.cacheTtl}ms`);
893
- }
905
+ constructor() {
906
+ this.databaseUrl = Envapter.get('DB_URL', 'sqlite://memory');
907
+ this.cacheTtl = Envapter.getUsing('CACHE_TTL', Converters.Time, 3600000); // "1h" -> 3600000ms
908
+ this.redisUrls = Envapter.getWith(
909
+ 'REDIS_URLS',
910
+ (raw, fallback) => (raw ? raw.split(',').map((s) => new URL(s)) : fallback),
911
+ [new URL('redis://localhost:6379')]
912
+ );
913
+ }
914
+
915
+ async initialize() {
916
+ console.log(`App running on port ${config.port}`);
917
+ console.log(`Database: ${this.databaseUrl}`);
918
+ console.log(`Cache TTL: ${this.cacheTtl}ms`);
919
+ }
894
920
  }
895
921
  ```
896
922
 
@@ -900,40 +926,40 @@ class DatabaseService {
900
926
  import { Envapt, Envapter, Converters } from 'envapt';
901
927
 
902
928
  class AppConfig extends Envapter {
903
- // Global settings (static)
904
- @Envapt('PORT', 3000)
905
- static readonly port: number;
906
-
907
- @Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 10000 })
908
- static readonly requestTimeout: number; // "5s" -> 5000ms (if env is set to "5s")
909
-
910
- @Envapt('FEATURE_FLAGS', {
911
- fallback: new Set(['basic']),
912
- converter: (raw, fallback) => {
913
- if (!raw) return fallback;
914
- return new Set(raw.split(',').map((s) => s.trim()));
929
+ // Global settings (static)
930
+ @Envapt('PORT', 3000)
931
+ static readonly port: number;
932
+
933
+ @Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 10000 })
934
+ static readonly requestTimeout: number; // "5s" -> 5000ms (if env is set to "5s")
935
+
936
+ @Envapt('FEATURE_FLAGS', {
937
+ fallback: new Set(['basic']),
938
+ converter: (raw, fallback) => {
939
+ if (!raw) return fallback;
940
+ return new Set(raw.split(',').map((s) => s.trim()));
941
+ }
942
+ })
943
+ static readonly featureFlags: Set<string>;
944
+
945
+ // Service settings (instance)
946
+ @Envapt('DB_URL', 'sqlite://memory')
947
+ declare readonly databaseUrl: string;
948
+
949
+ @Envapt('CACHE_TTL', { converter: Converters.Time, fallback: 3600000 })
950
+ declare readonly cacheTtl: number; // "1h" -> 3600000ms
951
+
952
+ @Envapt('REDIS_URLS', {
953
+ fallback: [new URL('redis://localhost:6379')],
954
+ converter: (raw, fallback) => (raw ? raw.split(',').map((s) => new URL(s)) : fallback)
955
+ })
956
+ declare readonly redisUrls: URL[];
957
+
958
+ async initialize() {
959
+ console.log(`App running on port ${AppConfig.port}`);
960
+ console.log(`Database: ${this.databaseUrl}`);
961
+ console.log(`Cache TTL: ${this.cacheTtl}ms`);
915
962
  }
916
- })
917
- static readonly featureFlags: Set<string>;
918
-
919
- // Service settings (instance)
920
- @Envapt('DB_URL', 'sqlite://memory')
921
- declare readonly databaseUrl: string;
922
-
923
- @Envapt('CACHE_TTL', { converter: Converters.Time, fallback: 3600000 })
924
- declare readonly cacheTtl: number; // "1h" -> 3600000ms
925
-
926
- @Envapt('REDIS_URLS', {
927
- fallback: [new URL('redis://localhost:6379')],
928
- converter: (raw, fallback) => (raw ? raw.split(',').map((s) => new URL(s)) : fallback)
929
- })
930
- declare readonly redisUrls: URL[];
931
-
932
- async initialize() {
933
- console.log(`App running on port ${AppConfig.port}`);
934
- console.log(`Database: ${this.databaseUrl}`);
935
- console.log(`Cache TTL: ${this.cacheTtl}ms`);
936
- }
937
963
  }
938
964
  ```
939
965