envapt 4.0.0 → 4.0.2

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">
@@ -46,45 +46,45 @@
46
46
 
47
47
  - [Requirements](#requirements)
48
48
  - [Quick Start](#quick-start)
49
- - [Installation](#installation)
50
- - [Basic Usage](#basic-usage)
49
+ - [Installation](#installation)
50
+ - [Basic Usage](#basic-usage)
51
51
 
52
52
  ### 🧬 API Reference
53
53
 
54
54
  - [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)
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)
67
67
 
68
68
  ### 🌍 Environment & Templates
69
69
 
70
70
  - [Environment Detection](#environment-detection)
71
- - [Environment Management](#environment-management)
71
+ - [Environment Management](#environment-management)
72
72
  - [Template Variables](#template-variables)
73
- - [Circular Reference Protection](#circular-reference-protection)
73
+ - [Circular Reference Protection](#circular-reference-protection)
74
74
 
75
75
  ### 🛠 Configuration & Errors
76
76
 
77
77
  - [Configuration](#configuration)
78
- - [Multiple .env Files](#multiple-env-files)
79
- - [Dotenv Configuration](#dotenv-configuration)
78
+ - [Multiple .env Files](#multiple-env-files)
79
+ - [Dotenv Configuration](#dotenv-configuration)
80
80
  - [Error Handling](#error-handling)
81
- - [Error Code Reference](#error-code-reference)
81
+ - [Error Code Reference](#error-code-reference)
82
82
 
83
83
  ### 🚀 Examples
84
84
 
85
85
  - [Advanced Examples](#advanced-examples)
86
- - [JavaScript](#javascript)
87
- - [TypeScript](#typescript)
86
+ - [JavaScript](#javascript)
87
+ - [TypeScript](#typescript)
88
88
 
89
89
  <div align="right">
90
90
 
@@ -103,11 +103,11 @@
103
103
  ```jsonc
104
104
  // tsconfig.json (required settings for decorators)
105
105
  {
106
- "experimentalDecorators": true,
107
- "module": "esnext", // or "nodenext"
108
- "moduleResolution": "bundler", // or "nodenext"
109
- "target": "ESNext",
110
- "lib": ["ESNext"]
106
+ "experimentalDecorators": true,
107
+ "module": "esnext", // or "nodenext"
108
+ "moduleResolution": "bundler", // or "nodenext"
109
+ "target": "ESNext",
110
+ "lib": ["ESNext"]
111
111
  }
112
112
  ```
113
113
 
@@ -175,36 +175,36 @@ import { Envapt, Envapter, Converters } from 'envapt';
175
175
 
176
176
  // Global app configuration (static properties)
177
177
  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[];
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[];
190
190
  }
191
191
 
192
192
  // Service configuration (instance properties)
193
193
  class DatabaseService {
194
- @Envapt('DATABASE_URL', 'sqlite://memory')
195
- declare readonly databaseUrl: string;
194
+ @Envapt('DATABASE_URL', 'sqlite://memory')
195
+ declare readonly databaseUrl: string;
196
196
 
197
- // Will detect that '10' is a number and set the runtime type accordingly
198
- @Envapt('MAX_CONNECTIONS', 10)
199
- declare readonly maxConnections: number;
197
+ // Will detect that '10' is a number and set the runtime type accordingly
198
+ @Envapt('MAX_CONNECTIONS', 10)
199
+ declare readonly maxConnections: number;
200
200
 
201
- @Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 5000 })
202
- declare readonly timeout: number; // Converts "5s" to 5000ms
201
+ @Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 5000 })
202
+ declare readonly timeout: number; // Converts "5s" to 5000ms
203
203
 
204
- async connect() {
205
- console.log(`Connecting to ${this.databaseUrl}`);
206
- // Connection logic here
207
- }
204
+ async connect() {
205
+ console.log(`Connecting to ${this.databaseUrl}`);
206
+ // Connection logic here
207
+ }
208
208
  }
209
209
 
210
210
  // Usage
@@ -267,29 +267,29 @@ Types are automatically inferred from fallback values.
267
267
 
268
268
  ```ts
269
269
  class Config extends Envapter {
270
- // Static properties for global settings
271
- @Envapt('APP_NAME', 'MyApp') // string
272
- static readonly appName: string;
270
+ // Static properties for global settings
271
+ @Envapt('APP_NAME', 'MyApp') // string
272
+ static readonly appName: string;
273
273
 
274
- @Envapt('APP_PORT', 3000) // number
275
- static readonly port: number;
274
+ @Envapt('APP_PORT', 3000) // number
275
+ static readonly port: number;
276
276
 
277
- @Envapt('DEBUG_MODE', false) // boolean
278
- static readonly debugMode: boolean;
277
+ @Envapt('DEBUG_MODE', false) // boolean
278
+ static readonly debugMode: boolean;
279
279
 
280
- // Instance properties for service-specific settings
281
- @Envapt('SMTP_HOST', 'localhost') // string
282
- declare readonly smtpHost: string;
280
+ // Instance properties for service-specific settings
281
+ @Envapt('SMTP_HOST', 'localhost') // string
282
+ declare readonly smtpHost: string;
283
283
 
284
- @Envapt('SMTP_PORT', 587) // number
285
- declare readonly smtpPort: number;
284
+ @Envapt('SMTP_PORT', 587) // number
285
+ declare readonly smtpPort: number;
286
286
 
287
- @Envapt('SMTP_SECURE', true) // boolean
288
- declare readonly smtpSecure: boolean;
287
+ @Envapt('SMTP_SECURE', true) // boolean
288
+ declare readonly smtpSecure: boolean;
289
289
 
290
- sendEmail(to: string, subject: string) {
291
- console.log(`Sending via ${this.smtpHost}:${this.smtpPort}`);
292
- }
290
+ sendEmail(to: string, subject: string) {
291
+ console.log(`Sending via ${this.smtpHost}:${this.smtpPort}`);
292
+ }
293
293
  }
294
294
  ```
295
295
 
@@ -304,28 +304,28 @@ Envapt allows using the 5 "primitive" type-like converters. These **will** coerc
304
304
 
305
305
  ```ts
306
306
  class Config extends Envapter {
307
- @Envapt('PORT_STRING', { fallback: 'hello-world', converter: String })
308
- static readonly portAsString: string;
307
+ @Envapt('PORT_STRING', { fallback: 'hello-world', converter: String })
308
+ static readonly portAsString: string;
309
309
 
310
- @Envapt('DEBUG_FLAG', { fallback: true, converter: Boolean })
311
- static readonly debugMode: boolean;
310
+ @Envapt('DEBUG_FLAG', { fallback: true, converter: Boolean })
311
+ static readonly debugMode: boolean;
312
312
 
313
- @Envapt('USER_ID', { fallback: 12345, converter: Number })
314
- static readonly userId: number;
313
+ @Envapt('USER_ID', { fallback: 12345, converter: Number })
314
+ static readonly userId: number;
315
315
 
316
- @Envapt('MAX_SAFE_INT', { fallback: 9007199254740991n, converter: BigInt })
317
- static readonly maxSafeInt: bigint;
316
+ @Envapt('MAX_SAFE_INT', { fallback: 9007199254740991n, converter: BigInt })
317
+ static readonly maxSafeInt: bigint;
318
318
 
319
- @Envapt('APP_INSTANCE', { fallback: Symbol(main), converter: Symbol })
320
- static readonly appInstance: symbol;
319
+ @Envapt('APP_INSTANCE', { fallback: Symbol(main), converter: Symbol })
320
+ static readonly appInstance: symbol;
321
321
 
322
- // Instance properties work the same way
323
- @Envapt('CONNECTION_TIMEOUT', { fallback: 5000, converter: Number })
324
- declare readonly timeout: number;
322
+ // Instance properties work the same way
323
+ @Envapt('CONNECTION_TIMEOUT', { fallback: 5000, converter: Number })
324
+ declare readonly timeout: number;
325
325
 
326
- // Type coercion example
327
- @Envapt('PERMISSIONS', { fallback: '72394823472342983', converter: BigInt })
328
- declare readonly permissions: bigint; // Converts "72394823472342983" to BigInt
326
+ // Type coercion example
327
+ @Envapt('PERMISSIONS', { fallback: '72394823472342983', converter: BigInt })
328
+ declare readonly permissions: bigint; // Converts "72394823472342983" to BigInt
329
329
  }
330
330
  ```
331
331
 
@@ -354,32 +354,32 @@ Envapt provides many built-in converters for common patterns:
354
354
 
355
355
  ```ts
356
356
  class Config extends Envapter {
357
- // Basic types
358
- @Envapt('APP_NAME', { converter: Converters.String, fallback: 'MyApp' })
359
- static readonly appName: string;
357
+ // Basic types
358
+ @Envapt('APP_NAME', { converter: Converters.String, fallback: 'MyApp' })
359
+ static readonly appName: string;
360
360
 
361
- @Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
362
- static readonly port: number;
361
+ @Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
362
+ static readonly port: number;
363
363
 
364
- @Envapt('PRODUCTION_MODE', { converter: Converters.Boolean, fallback: false })
365
- static readonly productionMode: boolean;
364
+ @Envapt('PRODUCTION_MODE', { converter: Converters.Boolean, fallback: false })
365
+ static readonly productionMode: boolean;
366
366
 
367
- // Advanced types
368
- @Envapt('CORS_ORIGINS', { converter: Converters.Array, fallback: [] })
369
- static readonly corsOrigins: string[];
367
+ // Advanced types
368
+ @Envapt('CORS_ORIGINS', { converter: Converters.Array, fallback: [] })
369
+ static readonly corsOrigins: string[];
370
370
 
371
- @Envapt('CONFIG_JSON', { converter: Converters.Json, fallback: {} })
372
- static readonly config: object;
371
+ @Envapt('CONFIG_JSON', { converter: Converters.Json, fallback: {} })
372
+ static readonly config: object;
373
373
 
374
- @Envapt('API_URL', { converter: Converters.Url, fallback: new URL('http://localhost') })
375
- static readonly apiUrl: URL;
374
+ @Envapt('API_URL', { converter: Converters.Url, fallback: new URL('http://localhost') })
375
+ static readonly apiUrl: URL;
376
376
 
377
- @Envapt('TIMEOUT', { converter: Converters.Time, fallback: 5000 })
378
- static readonly timeout: number; // Converts "30s" to 30000ms
377
+ @Envapt('TIMEOUT', { converter: Converters.Time, fallback: 5000 })
378
+ static readonly timeout: number; // Converts "30s" to 30000ms
379
379
 
380
- // Instance properties work the same way
381
- @Envapt('CACHE_TTL', { converter: Converters.Time, fallback: 3600000 })
382
- declare readonly cacheTtl: number; // "1h" becomes 3600000ms
380
+ // Instance properties work the same way
381
+ @Envapt('CACHE_TTL', { converter: Converters.Time, fallback: 3600000 })
382
+ declare readonly cacheTtl: number; // "1h" becomes 3600000ms
383
383
  }
384
384
  ```
385
385
 
@@ -428,20 +428,20 @@ For more control over array parsing:
428
428
 
429
429
  ```ts
430
430
  class Config extends Envapter {
431
- // Basic array (comma-separated strings)
432
- @Envapt('TAGS', { converter: Converters.Array, fallback: [] })
433
- static readonly tags: string[];
431
+ // Basic array (comma-separated strings)
432
+ @Envapt('TAGS', { converter: Converters.Array, fallback: [] })
433
+ static readonly tags: string[];
434
434
 
435
- // Custom delimiter
436
- @Envapt('ALLOWED_METHODS', { converter: { delimiter: '|' }, fallback: ['GET'] })
437
- declare readonly allowedMethods: string[];
435
+ // Custom delimiter
436
+ @Envapt('ALLOWED_METHODS', { converter: { delimiter: '|' }, fallback: ['GET'] })
437
+ declare readonly allowedMethods: string[];
438
438
 
439
- // Custom delimiter with type conversion
440
- @Envapt('RATE_LIMITS', { converter: { delimiter: ',', type: Converters.Number }, fallback: [100] })
441
- declare readonly rateLimits: number[];
439
+ // Custom delimiter with type conversion
440
+ @Envapt('RATE_LIMITS', { converter: { delimiter: ',', type: Converters.Number }, fallback: [100] })
441
+ declare readonly rateLimits: number[];
442
442
 
443
- @Envapt('FEATURE_FLAGS', { converter: { delimiter: ';', type: 'boolean' }, fallback: [false] })
444
- declare readonly featureFlags: boolean[];
443
+ @Envapt('FEATURE_FLAGS', { converter: { delimiter: ';', type: 'boolean' }, fallback: [false] })
444
+ declare readonly featureFlags: boolean[];
445
445
  }
446
446
  ```
447
447
 
@@ -470,28 +470,28 @@ Transform environment values to any type:
470
470
 
471
471
  ```ts
472
472
  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>;
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>;
495
495
  }
496
496
  ```
497
497
 
@@ -520,21 +520,21 @@ Control what happens when environment variables don't exist:
520
520
 
521
521
  ```ts
522
522
  class Config extends Envapter {
523
- // Returns undefined if not found
524
- @Envapt('OPTIONAL_FEATURE', { fallback: undefined })
525
- static readonly optionalFeature: string | undefined;
523
+ // Returns undefined if not found
524
+ @Envapt('OPTIONAL_FEATURE', { fallback: undefined })
525
+ static readonly optionalFeature: string | undefined;
526
526
 
527
- // Returns null if not found (no fallback provided)
528
- @Envapt('MISSING_CONFIG', { converter: Converters.String })
529
- static readonly missingConfig: string | null;
527
+ // Returns null if not found (no fallback provided)
528
+ @Envapt('MISSING_CONFIG', { converter: Converters.String })
529
+ static readonly missingConfig: string | null;
530
530
 
531
- // Uses fallback if not found
532
- @Envapt('DEFAULT_THEME', { fallback: 'light' })
533
- static readonly defaultTheme: string;
531
+ // Uses fallback if not found
532
+ @Envapt('DEFAULT_THEME', { fallback: 'light' })
533
+ static readonly defaultTheme: string;
534
534
 
535
- // Instance properties work the same way
536
- @Envapt('LOG_FILE_PATH', { fallback: undefined })
537
- declare readonly logFilePath: string | undefined;
535
+ // Instance properties work the same way
536
+ @Envapt('LOG_FILE_PATH', { fallback: undefined })
537
+ declare readonly logFilePath: string | undefined;
538
538
  }
539
539
  ```
540
540
 
@@ -602,9 +602,9 @@ const result = envapter.getUsing('DATABASE_CONFIG', Converters.Json);
602
602
  >
603
603
  > // Override with specific interface
604
604
  > interface DatabaseConfig {
605
- > host: string;
606
- > port: number;
607
- > ssl: boolean;
605
+ > host: string;
606
+ > port: number;
607
+ > ssl: boolean;
608
608
  > }
609
609
  > const dbConfig = Envapter.getUsing<DatabaseConfig>('DB_CONFIG', Converters.Json);
610
610
  > // dbConfig is now properly typed as DatabaseConfig
@@ -738,11 +738,11 @@ import { Envapter } from 'envapt';
738
738
 
739
739
  // Set dotenv configuration options
740
740
  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
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
746
746
  };
747
747
 
748
748
  // Get current configuration
@@ -795,26 +795,26 @@ Envapt provides detailed error codes for better debugging and error handling:
795
795
  import { EnvaptError, EnvaptErrorCodes } from 'envapt';
796
796
 
797
797
  try {
798
- // This will throw an error for invalid configuration
799
- Envapter.dotenvConfig = { path: '.env.custom' };
798
+ // This will throw an error for invalid configuration
799
+ Envapter.dotenvConfig = { path: '.env.custom' };
800
800
  } 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;
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;
816
+ }
816
817
  }
817
- }
818
818
  }
819
819
  ```
820
820
 
@@ -862,35 +862,35 @@ import { Envapter, Converters } from 'envapt';
862
862
 
863
863
  // Global configuration
864
864
  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
- )
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
+ )
875
875
  };
876
876
 
877
877
  // Service configuration
878
878
  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
- }
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
+ }
894
894
  }
895
895
  ```
896
896
 
@@ -900,40 +900,40 @@ class DatabaseService {
900
900
  import { Envapt, Envapter, Converters } from 'envapt';
901
901
 
902
902
  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()));
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()));
915
+ }
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`);
915
936
  }
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
937
  }
938
938
  ```
939
939
 
package/dist/index.cjs CHANGED
@@ -228,7 +228,10 @@ var Validator = class {
228
228
  `Failed to coerce fallback value using ${converter.name}: ${error.message}`
229
229
  );
230
230
  }
231
- throw new EnvaptError(205 /* PrimitiveCoercionFailed */, `Unknown primitive converter: ${converter.name}`);
231
+ throw new EnvaptError(
232
+ 205 /* PrimitiveCoercionFailed */,
233
+ `Unknown primitive converter: ${converter.name}`
234
+ );
232
235
  }
233
236
  /**
234
237
  * Make sure the user hasn't provided prohibited options in their dotenv config
@@ -338,8 +341,8 @@ var BuiltInConverters = class _BuiltInConverters {
338
341
  static {
339
342
  __name(this, "BuiltInConverters");
340
343
  }
341
- static string(raw, fallback) {
342
- return String(raw) || fallback;
344
+ static string(raw, _fallback) {
345
+ return String(raw);
343
346
  }
344
347
  static number(raw, fallback) {
345
348
  const parsed = Number(raw);
@@ -682,9 +685,12 @@ var PrimitiveMethods = class _PrimitiveMethods extends EnvironmentMethods {
682
685
  const parsed = this.parser.resolveTemplate(key, String(rawVal));
683
686
  let result;
684
687
  if (type === 1 /* Number */) result = BuiltInConverters.number(parsed, def);
685
- else if (type === 2 /* Boolean */) result = BuiltInConverters.boolean(parsed, def);
686
- else if (type === 3 /* BigInt */) result = BuiltInConverters.bigint(parsed, def);
687
- else if (type === 4 /* Symbol */) result = BuiltInConverters.symbol(parsed, def);
688
+ else if (type === 2 /* Boolean */)
689
+ result = BuiltInConverters.boolean(parsed, def);
690
+ else if (type === 3 /* BigInt */)
691
+ result = BuiltInConverters.bigint(parsed, def);
692
+ else if (type === 4 /* Symbol */)
693
+ result = BuiltInConverters.symbol(parsed, def);
688
694
  else result = BuiltInConverters.string(parsed, def);
689
695
  return result;
690
696
  }
@@ -885,6 +891,7 @@ var Converters = /* @__PURE__ */ ((Converters2) => {
885
891
  Converters2["Time"] = "time";
886
892
  return Converters2;
887
893
  })(Converters || {});
894
+ /* v8 ignore next -- @preserve */
888
895
 
889
896
  exports.Converters = Converters;
890
897
  exports.Envapt = Envapt;