@hazeljs/config 0.2.0-alpha.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/LICENSE +192 -0
- package/README.md +509 -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/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +287 -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 +53 -0
|
@@ -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.Service)(),
|
|
185
|
+
__metadata("design:paramtypes", [])
|
|
186
|
+
], ConfigService);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,287 @@
|
|
|
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 __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
/// <reference types="jest" />
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const dotenv = __importStar(require("dotenv"));
|
|
39
|
+
const config_service_1 = require("./config.service");
|
|
40
|
+
const config_module_1 = require("./config.module");
|
|
41
|
+
jest.mock('fs');
|
|
42
|
+
jest.mock('dotenv');
|
|
43
|
+
// Use __esModule: true so default import (`import logger from`) resolves correctly
|
|
44
|
+
jest.mock('@hazeljs/core', () => ({
|
|
45
|
+
__esModule: true,
|
|
46
|
+
Service: () => () => undefined,
|
|
47
|
+
HazelModule: () => () => undefined,
|
|
48
|
+
logger: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
49
|
+
default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
50
|
+
}));
|
|
51
|
+
const mockedFs = fs;
|
|
52
|
+
const mockedDotenv = dotenv;
|
|
53
|
+
describe('ConfigService', () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
// Reset static options between tests
|
|
56
|
+
config_service_1.ConfigService.setOptions({});
|
|
57
|
+
// Clear mock call history
|
|
58
|
+
jest.clearAllMocks();
|
|
59
|
+
// Default: fs.existsSync returns false (no .env files)
|
|
60
|
+
mockedFs.existsSync.mockReturnValue(false);
|
|
61
|
+
// Default: dotenv.config returns empty parsed
|
|
62
|
+
mockedDotenv.config.mockReturnValue({ parsed: {} });
|
|
63
|
+
});
|
|
64
|
+
describe('get()', () => {
|
|
65
|
+
it('returns undefined for missing key', () => {
|
|
66
|
+
const svc = new config_service_1.ConfigService();
|
|
67
|
+
expect(svc.get('MISSING_KEY_HAZEL_TEST')).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
it('returns default value for missing key', () => {
|
|
70
|
+
const svc = new config_service_1.ConfigService();
|
|
71
|
+
expect(svc.get('MISSING_KEY_HAZEL_TEST', 'default')).toBe('default');
|
|
72
|
+
});
|
|
73
|
+
it('returns value for existing env var', () => {
|
|
74
|
+
process.env.__HAZEL_TEST_VAR__ = 'hello';
|
|
75
|
+
const svc = new config_service_1.ConfigService();
|
|
76
|
+
expect(svc.get('__HAZEL_TEST_VAR__')).toBe('hello');
|
|
77
|
+
delete process.env.__HAZEL_TEST_VAR__;
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('set()', () => {
|
|
81
|
+
it('sets a configuration value', () => {
|
|
82
|
+
const svc = new config_service_1.ConfigService();
|
|
83
|
+
svc.set('myKey', 'myValue');
|
|
84
|
+
expect(svc.get('myKey')).toBe('myValue');
|
|
85
|
+
});
|
|
86
|
+
it('overwrites existing value', () => {
|
|
87
|
+
const svc = new config_service_1.ConfigService();
|
|
88
|
+
svc.set('myKey', 'first');
|
|
89
|
+
svc.set('myKey', 'second');
|
|
90
|
+
expect(svc.get('myKey')).toBe('second');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('has()', () => {
|
|
94
|
+
it('returns false for missing key', () => {
|
|
95
|
+
const svc = new config_service_1.ConfigService();
|
|
96
|
+
expect(svc.has('NO_SUCH_KEY_XYZ')).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
it('returns true for existing key', () => {
|
|
99
|
+
const svc = new config_service_1.ConfigService();
|
|
100
|
+
svc.set('existingKey', 'val');
|
|
101
|
+
expect(svc.has('existingKey')).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('getAll()', () => {
|
|
105
|
+
it('returns all configuration values', () => {
|
|
106
|
+
const svc = new config_service_1.ConfigService();
|
|
107
|
+
svc.set('k1', 'v1');
|
|
108
|
+
svc.set('k2', 'v2');
|
|
109
|
+
const all = svc.getAll();
|
|
110
|
+
expect(all.k1).toBe('v1');
|
|
111
|
+
expect(all.k2).toBe('v2');
|
|
112
|
+
});
|
|
113
|
+
it('returns a copy, not a reference', () => {
|
|
114
|
+
const svc = new config_service_1.ConfigService();
|
|
115
|
+
svc.set('k', 'v');
|
|
116
|
+
const all = svc.getAll();
|
|
117
|
+
all.k = 'mutated';
|
|
118
|
+
expect(svc.get('k')).toBe('v');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('getOrThrow()', () => {
|
|
122
|
+
it('returns value when key exists', () => {
|
|
123
|
+
const svc = new config_service_1.ConfigService();
|
|
124
|
+
svc.set('req', 'present');
|
|
125
|
+
expect(svc.getOrThrow('req')).toBe('present');
|
|
126
|
+
});
|
|
127
|
+
it('throws when key is missing', () => {
|
|
128
|
+
const svc = new config_service_1.ConfigService();
|
|
129
|
+
expect(() => svc.getOrThrow('DEFINITELY_NOT_SET_HAZEL')).toThrow('Configuration key "DEFINITELY_NOT_SET_HAZEL" is required but not found');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe('dot-notation nested keys', () => {
|
|
133
|
+
it('resolves nested config via dot notation', () => {
|
|
134
|
+
config_service_1.ConfigService.setOptions({
|
|
135
|
+
ignoreEnvVars: true,
|
|
136
|
+
ignoreEnvFile: true,
|
|
137
|
+
load: [() => ({ database: { host: 'localhost', port: 5432 } })],
|
|
138
|
+
});
|
|
139
|
+
const svc = new config_service_1.ConfigService();
|
|
140
|
+
expect(svc.get('database.host')).toBe('localhost');
|
|
141
|
+
expect(svc.get('database.port')).toBe(5432);
|
|
142
|
+
});
|
|
143
|
+
it('returns undefined for missing nested key', () => {
|
|
144
|
+
config_service_1.ConfigService.setOptions({
|
|
145
|
+
ignoreEnvVars: true,
|
|
146
|
+
ignoreEnvFile: true,
|
|
147
|
+
load: [() => ({ database: { host: 'localhost' } })],
|
|
148
|
+
});
|
|
149
|
+
const svc = new config_service_1.ConfigService();
|
|
150
|
+
expect(svc.get('database.missing')).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
it('returns undefined when parent key does not exist', () => {
|
|
153
|
+
config_service_1.ConfigService.setOptions({ ignoreEnvVars: true, ignoreEnvFile: true });
|
|
154
|
+
const svc = new config_service_1.ConfigService();
|
|
155
|
+
expect(svc.get('no.such.path')).toBeUndefined();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('custom loaders', () => {
|
|
159
|
+
it('merges custom loader config', () => {
|
|
160
|
+
config_service_1.ConfigService.setOptions({
|
|
161
|
+
ignoreEnvVars: true,
|
|
162
|
+
ignoreEnvFile: true,
|
|
163
|
+
load: [() => ({ APP_NAME: 'hazel' }), () => ({ APP_VERSION: '1.0.0' })],
|
|
164
|
+
});
|
|
165
|
+
const svc = new config_service_1.ConfigService();
|
|
166
|
+
expect(svc.get('APP_NAME')).toBe('hazel');
|
|
167
|
+
expect(svc.get('APP_VERSION')).toBe('1.0.0');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe('ignoreEnvVars', () => {
|
|
171
|
+
it('skips loading process.env when ignoreEnvVars is true', () => {
|
|
172
|
+
process.env.__HAZEL_IGNORE_TEST__ = 'should-not-appear';
|
|
173
|
+
config_service_1.ConfigService.setOptions({ ignoreEnvVars: true, ignoreEnvFile: true });
|
|
174
|
+
const svc = new config_service_1.ConfigService();
|
|
175
|
+
expect(svc.get('__HAZEL_IGNORE_TEST__')).toBeUndefined();
|
|
176
|
+
delete process.env.__HAZEL_IGNORE_TEST__;
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
describe('ignoreEnvFile', () => {
|
|
180
|
+
it('skips .env loading when ignoreEnvFile is true', () => {
|
|
181
|
+
config_service_1.ConfigService.setOptions({ ignoreEnvFile: true });
|
|
182
|
+
new config_service_1.ConfigService();
|
|
183
|
+
expect(mockedFs.existsSync).not.toHaveBeenCalled();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe('.env file loading', () => {
|
|
187
|
+
it('loads values from an existing .env file', () => {
|
|
188
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
189
|
+
mockedDotenv.config.mockReturnValue({ parsed: { FROM_ENV_FILE: 'env-value' } });
|
|
190
|
+
config_service_1.ConfigService.setOptions({ ignoreEnvVars: true, envFilePath: '/custom/.env' });
|
|
191
|
+
const svc = new config_service_1.ConfigService();
|
|
192
|
+
expect(mockedDotenv.config).toHaveBeenCalled();
|
|
193
|
+
expect(svc.get('FROM_ENV_FILE')).toBe('env-value');
|
|
194
|
+
});
|
|
195
|
+
it('handles multiple env file paths', () => {
|
|
196
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
197
|
+
mockedDotenv.config
|
|
198
|
+
.mockReturnValueOnce({ parsed: { KEY_A: 'a' } })
|
|
199
|
+
.mockReturnValueOnce({ parsed: { KEY_B: 'b' } });
|
|
200
|
+
config_service_1.ConfigService.setOptions({
|
|
201
|
+
ignoreEnvVars: true,
|
|
202
|
+
envFilePath: ['/path/one.env', '/path/two.env'],
|
|
203
|
+
});
|
|
204
|
+
const svc = new config_service_1.ConfigService();
|
|
205
|
+
expect(mockedDotenv.config).toHaveBeenCalledTimes(2);
|
|
206
|
+
expect(svc.get('KEY_A')).toBe('a');
|
|
207
|
+
expect(svc.get('KEY_B')).toBe('b');
|
|
208
|
+
});
|
|
209
|
+
it('handles dotenv parse errors gracefully', () => {
|
|
210
|
+
mockedFs.existsSync.mockReturnValue(true);
|
|
211
|
+
mockedDotenv.config.mockReturnValue({ error: new Error('parse error') });
|
|
212
|
+
// Should not throw
|
|
213
|
+
config_service_1.ConfigService.setOptions({ ignoreEnvVars: true, envFilePath: '/bad.env' });
|
|
214
|
+
expect(() => new config_service_1.ConfigService()).not.toThrow();
|
|
215
|
+
});
|
|
216
|
+
it('skips non-existent env files gracefully', () => {
|
|
217
|
+
mockedFs.existsSync.mockReturnValue(false);
|
|
218
|
+
config_service_1.ConfigService.setOptions({ ignoreEnvVars: true, envFilePath: '/missing.env' });
|
|
219
|
+
expect(() => new config_service_1.ConfigService()).not.toThrow();
|
|
220
|
+
expect(mockedDotenv.config).not.toHaveBeenCalled();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe('validation schema', () => {
|
|
224
|
+
it('uses validated config value when validation passes', () => {
|
|
225
|
+
const schema = {
|
|
226
|
+
validate: (config) => ({
|
|
227
|
+
value: { ...config, EXTRA_KEY: 'added-by-schema' },
|
|
228
|
+
}),
|
|
229
|
+
};
|
|
230
|
+
config_service_1.ConfigService.setOptions({
|
|
231
|
+
ignoreEnvVars: true,
|
|
232
|
+
ignoreEnvFile: true,
|
|
233
|
+
validationSchema: schema,
|
|
234
|
+
});
|
|
235
|
+
const svc = new config_service_1.ConfigService();
|
|
236
|
+
expect(svc.get('EXTRA_KEY')).toBe('added-by-schema');
|
|
237
|
+
});
|
|
238
|
+
it('throws when validation fails and abortEarly is not set', () => {
|
|
239
|
+
const schema = {
|
|
240
|
+
validate: () => ({
|
|
241
|
+
error: new Error('validation failed'),
|
|
242
|
+
value: {},
|
|
243
|
+
}),
|
|
244
|
+
};
|
|
245
|
+
config_service_1.ConfigService.setOptions({
|
|
246
|
+
ignoreEnvVars: true,
|
|
247
|
+
ignoreEnvFile: true,
|
|
248
|
+
validationSchema: schema,
|
|
249
|
+
});
|
|
250
|
+
expect(() => new config_service_1.ConfigService()).toThrow('Configuration validation error: validation failed');
|
|
251
|
+
});
|
|
252
|
+
it('does not throw when validation fails and abortEarly is true', () => {
|
|
253
|
+
const schema = {
|
|
254
|
+
validate: () => ({
|
|
255
|
+
error: new Error('validation failed'),
|
|
256
|
+
value: {},
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
config_service_1.ConfigService.setOptions({
|
|
260
|
+
ignoreEnvVars: true,
|
|
261
|
+
ignoreEnvFile: true,
|
|
262
|
+
validationSchema: schema,
|
|
263
|
+
validationOptions: { abortEarly: true },
|
|
264
|
+
});
|
|
265
|
+
expect(() => new config_service_1.ConfigService()).not.toThrow();
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe('ConfigModule', () => {
|
|
270
|
+
beforeEach(() => {
|
|
271
|
+
config_service_1.ConfigService.setOptions({});
|
|
272
|
+
});
|
|
273
|
+
it('forRoot returns ConfigModule', () => {
|
|
274
|
+
const result = config_module_1.ConfigModule.forRoot();
|
|
275
|
+
expect(result).toBe(config_module_1.ConfigModule);
|
|
276
|
+
});
|
|
277
|
+
it('forRoot sets options on ConfigService', () => {
|
|
278
|
+
config_module_1.ConfigModule.forRoot({ ignoreEnvFile: true, ignoreEnvVars: true });
|
|
279
|
+
// If options were set, a new ConfigService should not try loading env files
|
|
280
|
+
mockedFs.existsSync.mockReturnValue(false);
|
|
281
|
+
expect(() => new config_service_1.ConfigService()).not.toThrow();
|
|
282
|
+
});
|
|
283
|
+
it('forRoot without options returns ConfigModule', () => {
|
|
284
|
+
const result = config_module_1.ConfigModule.forRoot();
|
|
285
|
+
expect(result).toBe(config_module_1.ConfigModule);
|
|
286
|
+
});
|
|
287
|
+
});
|
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; } });
|