@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 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
+ [![npm version](https://img.shields.io/npm/v/@hazeljs/config.svg)](https://www.npmjs.com/package/@hazeljs/config)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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);
@@ -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
+ }