@hazeljs/config 0.2.0-beta.1
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 +508 -0
- package/dist/config.module.d.ts +46 -0
- package/dist/config.module.d.ts.map +1 -0
- package/dist/config.module.js +30 -0
- package/dist/config.service.d.ts +49 -0
- package/dist/config.service.d.ts.map +1 -0
- package/dist/config.service.js +186 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# @hazeljs/config
|
|
2
|
+
|
|
3
|
+
**Configuration Module for HazelJS - Environment Variables and Type-Safe Configuration**
|
|
4
|
+
|
|
5
|
+
Manage application configuration with environment variables, validation, and type safety.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@hazeljs/config)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- 🔐 **Environment Variables** - Load from .env files
|
|
13
|
+
- ✅ **Validation** - Validate configuration on startup
|
|
14
|
+
- 🎯 **Type Safety** - Full TypeScript support
|
|
15
|
+
- 🏗️ **Schema-Based** - Define configuration schema
|
|
16
|
+
- 🔄 **Hot Reload** - Reload configuration without restart (optional)
|
|
17
|
+
- 📁 **Multiple Environments** - Support for .env.development, .env.production, etc.
|
|
18
|
+
- 🎨 **Decorator Support** - Inject configuration with decorators
|
|
19
|
+
- 🔒 **Secret Management** - Secure handling of sensitive data
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @hazeljs/config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Create Configuration Schema
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { IsString, IsNumber, IsEnum } from 'class-validator';
|
|
33
|
+
|
|
34
|
+
export enum Environment {
|
|
35
|
+
Development = 'development',
|
|
36
|
+
Production = 'production',
|
|
37
|
+
Test = 'test',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class AppConfig {
|
|
41
|
+
@IsEnum(Environment)
|
|
42
|
+
NODE_ENV: Environment;
|
|
43
|
+
|
|
44
|
+
@IsNumber()
|
|
45
|
+
PORT: number;
|
|
46
|
+
|
|
47
|
+
@IsString()
|
|
48
|
+
DATABASE_URL: string;
|
|
49
|
+
|
|
50
|
+
@IsString()
|
|
51
|
+
JWT_SECRET: string;
|
|
52
|
+
|
|
53
|
+
@IsString()
|
|
54
|
+
REDIS_URL: string;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Configure Module
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { HazelModule } from '@hazeljs/core';
|
|
62
|
+
import { ConfigModule } from '@hazeljs/config';
|
|
63
|
+
import { AppConfig } from './app.config';
|
|
64
|
+
|
|
65
|
+
@HazelModule({
|
|
66
|
+
imports: [
|
|
67
|
+
ConfigModule.forRoot({
|
|
68
|
+
schema: AppConfig,
|
|
69
|
+
envFilePath: '.env',
|
|
70
|
+
validate: true,
|
|
71
|
+
}),
|
|
72
|
+
],
|
|
73
|
+
})
|
|
74
|
+
export class AppModule {}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Use Configuration
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { Injectable } from '@hazeljs/core';
|
|
81
|
+
import { ConfigService } from '@hazeljs/config';
|
|
82
|
+
|
|
83
|
+
@Injectable()
|
|
84
|
+
export class DatabaseService {
|
|
85
|
+
constructor(private configService: ConfigService<AppConfig>) {}
|
|
86
|
+
|
|
87
|
+
connect() {
|
|
88
|
+
const dbUrl = this.configService.get('DATABASE_URL');
|
|
89
|
+
const port = this.configService.get('PORT');
|
|
90
|
+
|
|
91
|
+
console.log(`Connecting to database: ${dbUrl}`);
|
|
92
|
+
console.log(`Server will run on port: ${port}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Environment Files
|
|
98
|
+
|
|
99
|
+
### Multiple Environment Files
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
ConfigModule.forRoot({
|
|
103
|
+
schema: AppConfig,
|
|
104
|
+
envFilePath: [
|
|
105
|
+
'.env',
|
|
106
|
+
`.env.${process.env.NODE_ENV}`,
|
|
107
|
+
'.env.local',
|
|
108
|
+
],
|
|
109
|
+
validate: true,
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### File Priority
|
|
114
|
+
|
|
115
|
+
Files are loaded in order, with later files overriding earlier ones:
|
|
116
|
+
1. `.env` - Base configuration
|
|
117
|
+
2. `.env.${NODE_ENV}` - Environment-specific
|
|
118
|
+
3. `.env.local` - Local overrides (gitignored)
|
|
119
|
+
|
|
120
|
+
### Example .env File
|
|
121
|
+
|
|
122
|
+
```env
|
|
123
|
+
NODE_ENV=development
|
|
124
|
+
PORT=3000
|
|
125
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
|
|
126
|
+
JWT_SECRET=your-super-secret-key
|
|
127
|
+
REDIS_URL=redis://localhost:6379
|
|
128
|
+
|
|
129
|
+
# API Keys
|
|
130
|
+
OPENAI_API_KEY=sk-...
|
|
131
|
+
STRIPE_API_KEY=sk_test_...
|
|
132
|
+
|
|
133
|
+
# Feature Flags
|
|
134
|
+
ENABLE_ANALYTICS=true
|
|
135
|
+
ENABLE_CACHING=true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Configuration Validation
|
|
139
|
+
|
|
140
|
+
### Basic Validation
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { IsString, IsNumber, IsUrl, IsBoolean } from 'class-validator';
|
|
144
|
+
|
|
145
|
+
export class AppConfig {
|
|
146
|
+
@IsString()
|
|
147
|
+
NODE_ENV: string;
|
|
148
|
+
|
|
149
|
+
@IsNumber()
|
|
150
|
+
PORT: number;
|
|
151
|
+
|
|
152
|
+
@IsUrl()
|
|
153
|
+
DATABASE_URL: string;
|
|
154
|
+
|
|
155
|
+
@IsString()
|
|
156
|
+
JWT_SECRET: string;
|
|
157
|
+
|
|
158
|
+
@IsBoolean()
|
|
159
|
+
ENABLE_CACHING: boolean;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Custom Validation
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { IsString, ValidateIf, MinLength } from 'class-validator';
|
|
167
|
+
|
|
168
|
+
export class AppConfig {
|
|
169
|
+
@IsString()
|
|
170
|
+
@MinLength(32, { message: 'JWT_SECRET must be at least 32 characters' })
|
|
171
|
+
JWT_SECRET: string;
|
|
172
|
+
|
|
173
|
+
@ValidateIf(o => o.NODE_ENV === 'production')
|
|
174
|
+
@IsString()
|
|
175
|
+
SSL_CERT_PATH: string;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Transform Values
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { Transform } from 'class-transformer';
|
|
183
|
+
|
|
184
|
+
export class AppConfig {
|
|
185
|
+
@Transform(({ value }) => parseInt(value, 10))
|
|
186
|
+
@IsNumber()
|
|
187
|
+
PORT: number;
|
|
188
|
+
|
|
189
|
+
@Transform(({ value }) => value === 'true')
|
|
190
|
+
@IsBoolean()
|
|
191
|
+
ENABLE_CACHING: boolean;
|
|
192
|
+
|
|
193
|
+
@Transform(({ value }) => value.split(','))
|
|
194
|
+
ALLOWED_ORIGINS: string[];
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Nested Configuration
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
export class DatabaseConfig {
|
|
202
|
+
@IsString()
|
|
203
|
+
host: string;
|
|
204
|
+
|
|
205
|
+
@IsNumber()
|
|
206
|
+
port: number;
|
|
207
|
+
|
|
208
|
+
@IsString()
|
|
209
|
+
username: string;
|
|
210
|
+
|
|
211
|
+
@IsString()
|
|
212
|
+
password: string;
|
|
213
|
+
|
|
214
|
+
@IsString()
|
|
215
|
+
database: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export class AppConfig {
|
|
219
|
+
@IsString()
|
|
220
|
+
NODE_ENV: string;
|
|
221
|
+
|
|
222
|
+
@ValidateNested()
|
|
223
|
+
@Type(() => DatabaseConfig)
|
|
224
|
+
database: DatabaseConfig;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Dependency Injection
|
|
229
|
+
|
|
230
|
+
### Inject ConfigService
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { Injectable } from '@hazeljs/core';
|
|
234
|
+
import { ConfigService } from '@hazeljs/config';
|
|
235
|
+
|
|
236
|
+
@Injectable()
|
|
237
|
+
export class MyService {
|
|
238
|
+
constructor(private config: ConfigService<AppConfig>) {}
|
|
239
|
+
|
|
240
|
+
doSomething() {
|
|
241
|
+
const apiKey = this.config.get('OPENAI_API_KEY');
|
|
242
|
+
const port = this.config.get('PORT');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Inject Specific Config Values
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { InjectConfig } from '@hazeljs/config';
|
|
251
|
+
|
|
252
|
+
@Injectable()
|
|
253
|
+
export class MyService {
|
|
254
|
+
constructor(
|
|
255
|
+
@InjectConfig('DATABASE_URL') private dbUrl: string,
|
|
256
|
+
@InjectConfig('PORT') private port: number
|
|
257
|
+
) {}
|
|
258
|
+
|
|
259
|
+
connect() {
|
|
260
|
+
console.log(`Connecting to ${this.dbUrl} on port ${this.port}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Configuration Namespaces
|
|
266
|
+
|
|
267
|
+
Organize configuration into logical groups:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
export class DatabaseConfig {
|
|
271
|
+
@IsString()
|
|
272
|
+
url: string;
|
|
273
|
+
|
|
274
|
+
@IsNumber()
|
|
275
|
+
poolSize: number;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export class RedisConfig {
|
|
279
|
+
@IsString()
|
|
280
|
+
url: string;
|
|
281
|
+
|
|
282
|
+
@IsNumber()
|
|
283
|
+
ttl: number;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export class AppConfig {
|
|
287
|
+
@ValidateNested()
|
|
288
|
+
@Type(() => DatabaseConfig)
|
|
289
|
+
database: DatabaseConfig;
|
|
290
|
+
|
|
291
|
+
@ValidateNested()
|
|
292
|
+
@Type(() => RedisConfig)
|
|
293
|
+
redis: RedisConfig;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Usage
|
|
297
|
+
const dbConfig = this.config.get('database');
|
|
298
|
+
console.log(dbConfig.url);
|
|
299
|
+
console.log(dbConfig.poolSize);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Default Values
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
export class AppConfig {
|
|
306
|
+
@IsNumber()
|
|
307
|
+
@Default(3000)
|
|
308
|
+
PORT: number;
|
|
309
|
+
|
|
310
|
+
@IsBoolean()
|
|
311
|
+
@Default(false)
|
|
312
|
+
ENABLE_DEBUG: boolean;
|
|
313
|
+
|
|
314
|
+
@IsString()
|
|
315
|
+
@Default('info')
|
|
316
|
+
LOG_LEVEL: string;
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Dynamic Configuration
|
|
321
|
+
|
|
322
|
+
Load configuration from external sources:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
ConfigModule.forRoot({
|
|
326
|
+
schema: AppConfig,
|
|
327
|
+
load: [
|
|
328
|
+
async () => {
|
|
329
|
+
// Load from database
|
|
330
|
+
const settings = await database.settings.findMany();
|
|
331
|
+
return settings.reduce((acc, s) => ({
|
|
332
|
+
...acc,
|
|
333
|
+
[s.key]: s.value,
|
|
334
|
+
}), {});
|
|
335
|
+
},
|
|
336
|
+
async () => {
|
|
337
|
+
// Load from API
|
|
338
|
+
const response = await fetch('https://api.example.com/config');
|
|
339
|
+
return await response.json();
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
})
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Configuration Service API
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
class ConfigService<T = any> {
|
|
349
|
+
// Get configuration value
|
|
350
|
+
get<K extends keyof T>(key: K): T[K];
|
|
351
|
+
get<K extends keyof T>(key: K, defaultValue: T[K]): T[K];
|
|
352
|
+
|
|
353
|
+
// Get all configuration
|
|
354
|
+
getAll(): T;
|
|
355
|
+
|
|
356
|
+
// Check if key exists
|
|
357
|
+
has(key: keyof T): boolean;
|
|
358
|
+
|
|
359
|
+
// Get with type casting
|
|
360
|
+
getOrThrow<K extends keyof T>(key: K): T[K];
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Best Practices
|
|
365
|
+
|
|
366
|
+
### 1. Never Commit Secrets
|
|
367
|
+
|
|
368
|
+
```gitignore
|
|
369
|
+
# .gitignore
|
|
370
|
+
.env
|
|
371
|
+
.env.local
|
|
372
|
+
.env.*.local
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 2. Use Environment-Specific Files
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
.env # Base configuration
|
|
379
|
+
.env.development # Development overrides
|
|
380
|
+
.env.production # Production overrides
|
|
381
|
+
.env.test # Test overrides
|
|
382
|
+
.env.local # Local overrides (gitignored)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### 3. Validate on Startup
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
ConfigModule.forRoot({
|
|
389
|
+
schema: AppConfig,
|
|
390
|
+
validate: true,
|
|
391
|
+
validationOptions: {
|
|
392
|
+
allowUnknown: false,
|
|
393
|
+
abortEarly: false,
|
|
394
|
+
},
|
|
395
|
+
})
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### 4. Use Type-Safe Access
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
// Good - Type-safe
|
|
402
|
+
const port = this.config.get('PORT'); // number
|
|
403
|
+
|
|
404
|
+
// Bad - Not type-safe
|
|
405
|
+
const port = process.env.PORT; // string | undefined
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### 5. Document Required Variables
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
/**
|
|
412
|
+
* Application Configuration
|
|
413
|
+
*
|
|
414
|
+
* Required Environment Variables:
|
|
415
|
+
* - NODE_ENV: Application environment (development|production|test)
|
|
416
|
+
* - PORT: Server port number
|
|
417
|
+
* - DATABASE_URL: PostgreSQL connection string
|
|
418
|
+
* - JWT_SECRET: Secret key for JWT tokens (min 32 characters)
|
|
419
|
+
* - REDIS_URL: Redis connection string
|
|
420
|
+
*/
|
|
421
|
+
export class AppConfig {
|
|
422
|
+
// ...
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Examples
|
|
427
|
+
|
|
428
|
+
### Complete Example
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
// config/app.config.ts
|
|
432
|
+
import { IsString, IsNumber, IsEnum, IsUrl } from 'class-validator';
|
|
433
|
+
import { Transform } from 'class-transformer';
|
|
434
|
+
|
|
435
|
+
export enum Environment {
|
|
436
|
+
Development = 'development',
|
|
437
|
+
Production = 'production',
|
|
438
|
+
Test = 'test',
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export class AppConfig {
|
|
442
|
+
@IsEnum(Environment)
|
|
443
|
+
NODE_ENV: Environment;
|
|
444
|
+
|
|
445
|
+
@Transform(({ value }) => parseInt(value, 10))
|
|
446
|
+
@IsNumber()
|
|
447
|
+
PORT: number;
|
|
448
|
+
|
|
449
|
+
@IsUrl()
|
|
450
|
+
DATABASE_URL: string;
|
|
451
|
+
|
|
452
|
+
@IsString()
|
|
453
|
+
JWT_SECRET: string;
|
|
454
|
+
|
|
455
|
+
@IsUrl()
|
|
456
|
+
REDIS_URL: string;
|
|
457
|
+
|
|
458
|
+
@IsString()
|
|
459
|
+
OPENAI_API_KEY: string;
|
|
460
|
+
|
|
461
|
+
@Transform(({ value }) => value === 'true')
|
|
462
|
+
ENABLE_CACHING: boolean;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// app.module.ts
|
|
466
|
+
@HazelModule({
|
|
467
|
+
imports: [
|
|
468
|
+
ConfigModule.forRoot({
|
|
469
|
+
schema: AppConfig,
|
|
470
|
+
envFilePath: ['.env', `.env.${process.env.NODE_ENV}`],
|
|
471
|
+
validate: true,
|
|
472
|
+
}),
|
|
473
|
+
],
|
|
474
|
+
})
|
|
475
|
+
export class AppModule {}
|
|
476
|
+
|
|
477
|
+
// database.service.ts
|
|
478
|
+
@Injectable()
|
|
479
|
+
export class DatabaseService {
|
|
480
|
+
constructor(private config: ConfigService<AppConfig>) {}
|
|
481
|
+
|
|
482
|
+
async connect() {
|
|
483
|
+
const url = this.config.get('DATABASE_URL');
|
|
484
|
+
// Connect to database
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Testing
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
npm test
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Contributing
|
|
496
|
+
|
|
497
|
+
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
498
|
+
|
|
499
|
+
## License
|
|
500
|
+
|
|
501
|
+
MIT © [HazelJS](https://hazeljs.com)
|
|
502
|
+
|
|
503
|
+
## Links
|
|
504
|
+
|
|
505
|
+
- [Documentation](https://hazeljs.com/docs/packages/config)
|
|
506
|
+
- [GitHub](https://github.com/hazel-js/hazeljs)
|
|
507
|
+
- [Issues](https://github.com/hazeljs/hazel-js/issues)
|
|
508
|
+
- [Discord](https://discord.gg/hazeljs)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare class ConfigModule {
|
|
2
|
+
/**
|
|
3
|
+
* Register ConfigModule with options
|
|
4
|
+
*/
|
|
5
|
+
static forRoot(options?: ConfigModuleOptions): typeof ConfigModule;
|
|
6
|
+
}
|
|
7
|
+
export interface ConfigModuleOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Path to .env file
|
|
10
|
+
*/
|
|
11
|
+
envFilePath?: string | string[];
|
|
12
|
+
/**
|
|
13
|
+
* Whether to ignore .env file
|
|
14
|
+
*/
|
|
15
|
+
ignoreEnvFile?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether to ignore environment variables
|
|
18
|
+
*/
|
|
19
|
+
ignoreEnvVars?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Validation schema for configuration
|
|
22
|
+
*/
|
|
23
|
+
validationSchema?: ValidationSchema;
|
|
24
|
+
/**
|
|
25
|
+
* Validation options
|
|
26
|
+
*/
|
|
27
|
+
validationOptions?: {
|
|
28
|
+
allowUnknown?: boolean;
|
|
29
|
+
abortEarly?: boolean;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Whether configuration is global
|
|
33
|
+
*/
|
|
34
|
+
isGlobal?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Custom configuration loader
|
|
37
|
+
*/
|
|
38
|
+
load?: Array<() => Record<string, unknown>>;
|
|
39
|
+
}
|
|
40
|
+
export interface ValidationSchema {
|
|
41
|
+
validate(config: Record<string, unknown>): {
|
|
42
|
+
error?: Error;
|
|
43
|
+
value: Record<string, unknown>;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=config.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.module.d.ts","sourceRoot":"","sources":["../src/config.module.ts"],"names":[],"mappings":"AAGA,qBAIa,YAAY;IACvB;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,YAAY;CAMnE;AAED,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEhC;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAEpC;;OAEG;IACH,iBAAiB,CAAC,EAAE;QAClB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IAEF;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QACzC,KAAK,CAAC,EAAE,KAAK,CAAC;QACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAChC,CAAC;CACH"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var ConfigModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ConfigModule = void 0;
|
|
11
|
+
const core_1 = require("@hazeljs/core");
|
|
12
|
+
const config_service_1 = require("./config.service");
|
|
13
|
+
let ConfigModule = ConfigModule_1 = class ConfigModule {
|
|
14
|
+
/**
|
|
15
|
+
* Register ConfigModule with options
|
|
16
|
+
*/
|
|
17
|
+
static forRoot(options) {
|
|
18
|
+
if (options) {
|
|
19
|
+
config_service_1.ConfigService.setOptions(options);
|
|
20
|
+
}
|
|
21
|
+
return ConfigModule_1;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.ConfigModule = ConfigModule;
|
|
25
|
+
exports.ConfigModule = ConfigModule = ConfigModule_1 = __decorate([
|
|
26
|
+
(0, core_1.HazelModule)({
|
|
27
|
+
providers: [config_service_1.ConfigService],
|
|
28
|
+
exports: [config_service_1.ConfigService],
|
|
29
|
+
})
|
|
30
|
+
], ConfigModule);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ConfigModuleOptions } from './config.module';
|
|
2
|
+
export declare class ConfigService {
|
|
3
|
+
private static options;
|
|
4
|
+
private config;
|
|
5
|
+
private isLoaded;
|
|
6
|
+
constructor();
|
|
7
|
+
/**
|
|
8
|
+
* Set module options (called by ConfigModule.forRoot)
|
|
9
|
+
*/
|
|
10
|
+
static setOptions(options: ConfigModuleOptions): void;
|
|
11
|
+
/**
|
|
12
|
+
* Load configuration
|
|
13
|
+
*/
|
|
14
|
+
private load;
|
|
15
|
+
/**
|
|
16
|
+
* Load environment files
|
|
17
|
+
*/
|
|
18
|
+
private loadEnvFiles;
|
|
19
|
+
/**
|
|
20
|
+
* Validate configuration against schema
|
|
21
|
+
*/
|
|
22
|
+
private validate;
|
|
23
|
+
/**
|
|
24
|
+
* Get a configuration value
|
|
25
|
+
*/
|
|
26
|
+
get<T = unknown>(key: string): T | undefined;
|
|
27
|
+
get<T = unknown>(key: string, defaultValue: T): T;
|
|
28
|
+
/**
|
|
29
|
+
* Get nested value using dot notation
|
|
30
|
+
*/
|
|
31
|
+
private getNestedValue;
|
|
32
|
+
/**
|
|
33
|
+
* Get all configuration
|
|
34
|
+
*/
|
|
35
|
+
getAll(): Record<string, unknown>;
|
|
36
|
+
/**
|
|
37
|
+
* Set a configuration value
|
|
38
|
+
*/
|
|
39
|
+
set(key: string, value: unknown): void;
|
|
40
|
+
/**
|
|
41
|
+
* Check if a key exists
|
|
42
|
+
*/
|
|
43
|
+
has(key: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Get configuration as a specific type
|
|
46
|
+
*/
|
|
47
|
+
getOrThrow<T = unknown>(key: string): T;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=config.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.service.d.ts","sourceRoot":"","sources":["../src/config.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAoB,MAAM,iBAAiB,CAAC;AAMxE,qBACa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,OAAO,CAA2B;IACjD,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,QAAQ,CAAS;;IAMzB;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAIrD;;OAEG;IACH,OAAO,CAAC,IAAI;IAgCZ;;OAEG;IACH,OAAO,CAAC,YAAY;IAuBpB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAkBhB;;OAEG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAC5C,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC;IAMjD;;OAEG;IACH,OAAO,CAAC,cAAc;IAetB;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIjC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAItC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB;;OAEG;IACH,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC;CASxC"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
var ConfigService_1;
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.ConfigService = void 0;
|
|
50
|
+
const core_1 = require("@hazeljs/core");
|
|
51
|
+
const dotenv = __importStar(require("dotenv"));
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
55
|
+
let ConfigService = ConfigService_1 = class ConfigService {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.config = {};
|
|
58
|
+
this.isLoaded = false;
|
|
59
|
+
this.load();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Set module options (called by ConfigModule.forRoot)
|
|
63
|
+
*/
|
|
64
|
+
static setOptions(options) {
|
|
65
|
+
ConfigService_1.options = options;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Load configuration
|
|
69
|
+
*/
|
|
70
|
+
load() {
|
|
71
|
+
if (this.isLoaded)
|
|
72
|
+
return;
|
|
73
|
+
const options = ConfigService_1.options;
|
|
74
|
+
// Load from .env files
|
|
75
|
+
if (!options.ignoreEnvFile) {
|
|
76
|
+
this.loadEnvFiles(options.envFilePath);
|
|
77
|
+
}
|
|
78
|
+
// Load from environment variables
|
|
79
|
+
if (!options.ignoreEnvVars) {
|
|
80
|
+
this.config = { ...this.config, ...process.env };
|
|
81
|
+
}
|
|
82
|
+
// Load custom configurations
|
|
83
|
+
if (options.load) {
|
|
84
|
+
options.load.forEach((loader) => {
|
|
85
|
+
const customConfig = loader();
|
|
86
|
+
this.config = { ...this.config, ...customConfig };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Validate configuration
|
|
90
|
+
if (options.validationSchema) {
|
|
91
|
+
this.validate(options.validationSchema, options.validationOptions);
|
|
92
|
+
}
|
|
93
|
+
this.isLoaded = true;
|
|
94
|
+
core_2.default.info('Configuration loaded successfully');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Load environment files
|
|
98
|
+
*/
|
|
99
|
+
loadEnvFiles(envFilePath) {
|
|
100
|
+
const paths = Array.isArray(envFilePath) ? envFilePath : envFilePath ? [envFilePath] : ['.env'];
|
|
101
|
+
for (const filePath of paths) {
|
|
102
|
+
const fullPath = path.resolve(process.cwd(), filePath);
|
|
103
|
+
if (fs.existsSync(fullPath)) {
|
|
104
|
+
core_2.default.debug(`Loading environment file: ${fullPath}`);
|
|
105
|
+
const result = dotenv.config({ path: fullPath });
|
|
106
|
+
if (result.parsed) {
|
|
107
|
+
this.config = { ...this.config, ...result.parsed };
|
|
108
|
+
}
|
|
109
|
+
if (result.error) {
|
|
110
|
+
core_2.default.warn(`Error loading ${fullPath}:`, result.error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
core_2.default.debug(`Environment file not found: ${fullPath}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Validate configuration against schema
|
|
120
|
+
*/
|
|
121
|
+
validate(schema, options) {
|
|
122
|
+
const result = schema.validate(this.config);
|
|
123
|
+
if (result.error) {
|
|
124
|
+
const message = `Configuration validation error: ${result.error.message}`;
|
|
125
|
+
core_2.default.error(message);
|
|
126
|
+
if (!options?.abortEarly) {
|
|
127
|
+
throw new Error(message);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.config = result.value;
|
|
131
|
+
}
|
|
132
|
+
get(key, defaultValue) {
|
|
133
|
+
const value = this.getNestedValue(key);
|
|
134
|
+
return value !== undefined ? value : defaultValue;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get nested value using dot notation
|
|
138
|
+
*/
|
|
139
|
+
getNestedValue(key) {
|
|
140
|
+
const keys = key.split('.');
|
|
141
|
+
let value = this.config;
|
|
142
|
+
for (const k of keys) {
|
|
143
|
+
if (value && typeof value === 'object' && value !== null && k in value) {
|
|
144
|
+
value = value[k];
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get all configuration
|
|
154
|
+
*/
|
|
155
|
+
getAll() {
|
|
156
|
+
return { ...this.config };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Set a configuration value
|
|
160
|
+
*/
|
|
161
|
+
set(key, value) {
|
|
162
|
+
this.config[key] = value;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Check if a key exists
|
|
166
|
+
*/
|
|
167
|
+
has(key) {
|
|
168
|
+
return this.getNestedValue(key) !== undefined;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get configuration as a specific type
|
|
172
|
+
*/
|
|
173
|
+
getOrThrow(key) {
|
|
174
|
+
const value = this.get(key);
|
|
175
|
+
if (value === undefined) {
|
|
176
|
+
throw new Error(`Configuration key "${key}" is required but not found`);
|
|
177
|
+
}
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
exports.ConfigService = ConfigService;
|
|
182
|
+
ConfigService.options = {};
|
|
183
|
+
exports.ConfigService = ConfigService = ConfigService_1 = __decorate([
|
|
184
|
+
(0, core_1.Injectable)(),
|
|
185
|
+
__metadata("design:paramtypes", [])
|
|
186
|
+
], ConfigService);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hazeljs/config - Configuration module for HazelJS
|
|
3
|
+
*/
|
|
4
|
+
export { ConfigModule, type ConfigModuleOptions, type ValidationSchema as ConfigValidationSchema, } from './config.module';
|
|
5
|
+
export { ConfigService } from './config.service';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,IAAI,sBAAsB,GAChD,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @hazeljs/config - Configuration module for HazelJS
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ConfigService = exports.ConfigModule = void 0;
|
|
7
|
+
var config_module_1 = require("./config.module");
|
|
8
|
+
Object.defineProperty(exports, "ConfigModule", { enumerable: true, get: function () { return config_module_1.ConfigModule; } });
|
|
9
|
+
var config_service_1 = require("./config.service");
|
|
10
|
+
Object.defineProperty(exports, "ConfigService", { enumerable: true, get: function () { return config_service_1.ConfigService; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hazeljs/config",
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
|
+
"description": "Configuration module for HazelJS framework",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest --coverage --passWithNoTests",
|
|
13
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
14
|
+
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@hazeljs/core": "file:../core",
|
|
19
|
+
"dotenv": "^16.5.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.17.50",
|
|
23
|
+
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
|
24
|
+
"@typescript-eslint/parser": "^8.18.2",
|
|
25
|
+
"eslint": "^8.56.0",
|
|
26
|
+
"jest": "^29.7.0",
|
|
27
|
+
"ts-jest": "^29.1.2",
|
|
28
|
+
"typescript": "^5.3.3"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/hazel-js/hazeljs.git",
|
|
36
|
+
"directory": "packages/config"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"hazeljs",
|
|
40
|
+
"config",
|
|
41
|
+
"configuration",
|
|
42
|
+
"environment"
|
|
43
|
+
],
|
|
44
|
+
"author": "Muhammad Arslan <marslan@hazeljs.com>",
|
|
45
|
+
"license": "MIT"
|
|
46
|
+
}
|