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 +257 -231
- package/dist/index.cjs +51 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -39
- package/dist/index.d.ts +165 -39
- package/dist/index.mjs +51 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -16
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
|
-
|
|
50
|
-
|
|
50
|
+
- [Installation](#installation)
|
|
51
|
+
- [Basic Usage](#basic-usage)
|
|
51
52
|
|
|
52
53
|
### 🧬 API Reference
|
|
53
54
|
|
|
54
55
|
- [API Reference](#api-reference)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
72
|
+
- [Environment Management](#environment-management)
|
|
72
73
|
- [Template Variables](#template-variables)
|
|
73
|
-
|
|
74
|
+
- [Circular Reference Protection](#circular-reference-protection)
|
|
74
75
|
|
|
75
76
|
### 🛠 Configuration & Errors
|
|
76
77
|
|
|
77
78
|
- [Configuration](#configuration)
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
- [Multiple .env Files](#multiple-env-files)
|
|
80
|
+
- [Dotenv Configuration](#dotenv-configuration)
|
|
80
81
|
- [Error Handling](#error-handling)
|
|
81
|
-
|
|
82
|
+
- [Error Code Reference](#error-code-reference)
|
|
82
83
|
|
|
83
84
|
### 🚀 Examples
|
|
84
85
|
|
|
85
86
|
- [Advanced Examples](#advanced-examples)
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
195
|
-
|
|
203
|
+
@Envapt('DATABASE_URL', 'sqlite://memory')
|
|
204
|
+
declare readonly databaseUrl: string;
|
|
196
205
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
202
|
-
|
|
210
|
+
@Envapt('REQUEST_TIMEOUT', { converter: Converters.Time, fallback: 5000 })
|
|
211
|
+
declare readonly timeout: number; // Converts "5s" to 5000ms
|
|
203
212
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
291
|
+
// Static properties for global settings
|
|
292
|
+
@Envapt('APP_NAME', 'MyApp') // string
|
|
293
|
+
static readonly appName: string;
|
|
273
294
|
|
|
274
|
-
|
|
275
|
-
|
|
295
|
+
@Envapt('APP_PORT', 3000) // number
|
|
296
|
+
static readonly port: number;
|
|
276
297
|
|
|
277
|
-
|
|
278
|
-
|
|
298
|
+
@Envapt('DEBUG_MODE', false) // boolean
|
|
299
|
+
static readonly debugMode: boolean;
|
|
279
300
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
301
|
+
// Instance properties for service-specific settings
|
|
302
|
+
@Envapt('SMTP_HOST', 'localhost') // string
|
|
303
|
+
declare readonly smtpHost: string;
|
|
283
304
|
|
|
284
|
-
|
|
285
|
-
|
|
305
|
+
@Envapt('SMTP_PORT', 587) // number
|
|
306
|
+
declare readonly smtpPort: number;
|
|
286
307
|
|
|
287
|
-
|
|
288
|
-
|
|
308
|
+
@Envapt('SMTP_SECURE', true) // boolean
|
|
309
|
+
declare readonly smtpSecure: boolean;
|
|
289
310
|
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
308
|
-
|
|
328
|
+
@Envapt('PORT_STRING', { fallback: 'hello-world', converter: String })
|
|
329
|
+
static readonly portAsString: string;
|
|
309
330
|
|
|
310
|
-
|
|
311
|
-
|
|
331
|
+
@Envapt('DEBUG_FLAG', { fallback: true, converter: Boolean })
|
|
332
|
+
static readonly debugMode: boolean;
|
|
312
333
|
|
|
313
|
-
|
|
314
|
-
|
|
334
|
+
@Envapt('USER_ID', { fallback: 12345, converter: Number })
|
|
335
|
+
static readonly userId: number;
|
|
315
336
|
|
|
316
|
-
|
|
317
|
-
|
|
337
|
+
@Envapt('MAX_SAFE_INT', { fallback: 9007199254740991n, converter: BigInt })
|
|
338
|
+
static readonly maxSafeInt: bigint;
|
|
318
339
|
|
|
319
|
-
|
|
320
|
-
|
|
340
|
+
@Envapt('APP_INSTANCE', { fallback: Symbol(main), converter: Symbol })
|
|
341
|
+
static readonly appInstance: symbol;
|
|
321
342
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
343
|
+
// Instance properties work the same way
|
|
344
|
+
@Envapt('CONNECTION_TIMEOUT', { fallback: 5000, converter: Number })
|
|
345
|
+
declare readonly timeout: number;
|
|
325
346
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
378
|
+
// Basic types
|
|
379
|
+
@Envapt('APP_NAME', { converter: Converters.String, fallback: 'MyApp' })
|
|
380
|
+
static readonly appName: string;
|
|
360
381
|
|
|
361
|
-
|
|
362
|
-
|
|
382
|
+
@Envapt('PORT', { converter: Converters.Number, fallback: 3000 })
|
|
383
|
+
static readonly port: number;
|
|
363
384
|
|
|
364
|
-
|
|
365
|
-
|
|
385
|
+
@Envapt('PRODUCTION_MODE', { converter: Converters.Boolean, fallback: false })
|
|
386
|
+
static readonly productionMode: boolean;
|
|
366
387
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
388
|
+
// Advanced types
|
|
389
|
+
@Envapt('CORS_ORIGINS', { converter: Converters.Array, fallback: [] })
|
|
390
|
+
static readonly corsOrigins: string[];
|
|
370
391
|
|
|
371
|
-
|
|
372
|
-
|
|
392
|
+
@Envapt('CONFIG_JSON', { converter: Converters.Json, fallback: {} })
|
|
393
|
+
static readonly config: object;
|
|
373
394
|
|
|
374
|
-
|
|
375
|
-
|
|
395
|
+
@Envapt('API_URL', { converter: Converters.Url, fallback: new URL('http://localhost') })
|
|
396
|
+
static readonly apiUrl: URL;
|
|
376
397
|
|
|
377
|
-
|
|
378
|
-
|
|
398
|
+
@Envapt('TIMEOUT', { converter: Converters.Time, fallback: 5000 })
|
|
399
|
+
static readonly timeout: number; // Converts "30s" to 30000ms
|
|
379
400
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
452
|
+
// Basic array (comma-separated strings)
|
|
453
|
+
@Envapt('TAGS', { converter: Converters.Array, fallback: [] })
|
|
454
|
+
static readonly tags: string[];
|
|
434
455
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
456
|
+
// Custom delimiter
|
|
457
|
+
@Envapt('ALLOWED_METHODS', { converter: { delimiter: '|' }, fallback: ['GET'] })
|
|
458
|
+
declare readonly allowedMethods: string[];
|
|
438
459
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
444
|
-
|
|
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
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
544
|
+
// Returns undefined if not found
|
|
545
|
+
@Envapt('OPTIONAL_FEATURE', { fallback: undefined })
|
|
546
|
+
static readonly optionalFeature: string | undefined;
|
|
526
547
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
532
|
-
|
|
533
|
-
|
|
552
|
+
// Uses fallback if not found
|
|
553
|
+
@Envapt('DEFAULT_THEME', { fallback: 'light' })
|
|
554
|
+
static readonly defaultTheme: string;
|
|
534
555
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
>
|
|
606
|
-
>
|
|
607
|
-
>
|
|
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
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
-
|
|
799
|
-
|
|
823
|
+
// This will throw an error for invalid configuration
|
|
824
|
+
Envapter.dotenvConfig = { path: '.env.custom' };
|
|
800
825
|
} catch (error) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
|