nestjs-env-getter 0.1.0 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +345 -69
- package/bin/nestjs-env-getter.mjs +253 -0
- package/dist/app-config/app-config.module.d.ts +16 -0
- package/dist/app-config/app-config.module.js +56 -0
- package/dist/env-getter/env-getter.module.js +2 -4
- package/dist/env-getter/env-getter.service.d.ts +196 -58
- package/dist/env-getter/env-getter.service.js +471 -44
- package/dist/env-getter/types/array-validator.type.d.ts +1 -1
- package/dist/env-getter/types/config-events.type.d.ts +62 -0
- package/dist/env-getter/types/config-events.type.js +2 -0
- package/dist/env-getter/types/file-watcher-options.type.d.ts +22 -0
- package/dist/env-getter/types/file-watcher-options.type.js +2 -0
- package/dist/env-getter/types/index.d.ts +3 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/shared/types/index.d.ts +2 -2
- package/package.json +19 -12
- package/src/app-config/app.config.ts.example +47 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
<!-- markdownlint-disable MD024 -->
|
|
4
|
+
|
|
5
|
+
All notable changes to this project will be documented in this file.
|
|
6
|
+
|
|
7
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
8
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
9
|
+
|
|
10
|
+
## [1.0.0-beta.2] - 2025-10-17
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Inject Custom Providers**: Added the ability to inject custom providers into `AppConfigModule` via `forRoot` and `forRootAsync`. This allows `AppConfig` classes to depend on other services for dynamic configuration.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Updated Example**: The `nestjs-server` example was updated to demonstrate how to inject a custom `AppConfigOptionsService` to dynamically select configuration files based on the environment.
|
|
19
|
+
|
|
20
|
+
## [1.0.0-beta.1] - 2025-10-08
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Configuration from Files**:
|
|
25
|
+
- Added `getRequiredConfigFromFile` and `getOptionalConfigFromFile` to read, parse, and validate JSON configuration files.
|
|
26
|
+
- Implemented automatic file watching with hot-reload, which updates configuration in-place without requiring an application restart.
|
|
27
|
+
- Configuration objects are enhanced with `on`, `once`, and `off` methods for subscribing to file change events.
|
|
28
|
+
- **Graceful Error Handling**:
|
|
29
|
+
- `getOptionalConfigFromFile` now gracefully handles missing files or JSON parsing errors by returning a default value or `undefined`, preventing process termination.
|
|
30
|
+
- Process now only terminates on critical errors, such as validation failures in class-based configs.
|
|
31
|
+
- **Project Scaffolding and Examples**:
|
|
32
|
+
- Added an example NestJS application demonstrating usage with a MongoDB connection.
|
|
33
|
+
- Included executable scripts for bootstrapping the library.
|
|
34
|
+
- Integrated `AppConfigModule` to streamline configuration setup.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Updated all dependencies to their latest versions.
|
|
39
|
+
|
|
40
|
+
## [0.1.0] - 2025-05-08
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Bumped version.
|
|
45
|
+
- Bumped development dependencies and changed the registry.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Updated the GitHub Action for publishing.
|
|
50
|
+
- Fixed issues in the CI/CD workflows.
|
|
51
|
+
|
|
52
|
+
## [0.0.0-beta1] - 2025-02-04
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- Initial release with total refactoring.
|
package/README.md
CHANGED
|
@@ -1,125 +1,401 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NestJS ENV Getter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`nestjs-env-getter` is a powerful and lightweight NestJS module designed to streamline the process of retrieving and validating environment variables. It ensures your application starts with a correct and type-safe configuration by providing a robust API for handling various data types and validation scenarios.
|
|
4
4
|
|
|
5
|
-
## Features
|
|
5
|
+
## Key Features
|
|
6
6
|
|
|
7
|
-
- **Type-
|
|
8
|
-
- **Validation**: Validate
|
|
9
|
-
- **
|
|
10
|
-
- **Error
|
|
7
|
+
- **Type-Safe Retrieval**: Get environment variables as strings, numbers, booleans, URLs, objects, arrays, or time periods.
|
|
8
|
+
- **Built-in Validation**: Validate variables against allowed values or use custom validation functions.
|
|
9
|
+
- **Required vs. Optional**: Clearly distinguish between required variables that terminate the process if missing and optional ones with default values.
|
|
10
|
+
- **Error Handling**: Automatically terminates the process with a descriptive error message if a required variable is missing or invalid.
|
|
11
|
+
- **`.env` Support**: Automatically loads environment variables from a `.env` file using `dotenv`.
|
|
12
|
+
- **Configuration Scaffolding**: Includes a CLI helper to quickly set up a centralized configuration file.
|
|
11
13
|
|
|
12
14
|
## Installation
|
|
13
15
|
|
|
14
|
-
Install the package using npm:
|
|
15
|
-
|
|
16
16
|
```bash
|
|
17
17
|
npm install nestjs-env-getter
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
## Quick Start
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
yarn add nestjs-env-getter
|
|
24
|
-
```
|
|
22
|
+
The recommended way to use `nestjs-env-getter` is by creating a centralized configuration service.
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
### 1. Create a Configuration Service
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
Create a file `src/app.config.ts` to define and validate all your environment variables.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// src/app.config.ts
|
|
30
|
+
import { Injectable } from "@nestjs/common";
|
|
31
|
+
import { EnvGetterService } from "nestjs-env-getter";
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
@Injectable()
|
|
34
|
+
export class AppConfig {
|
|
35
|
+
readonly port: number;
|
|
36
|
+
readonly mongoConnectionString: string;
|
|
37
|
+
readonly nodeEnv: string;
|
|
38
|
+
|
|
39
|
+
constructor(private readonly envGetter: EnvGetterService) {
|
|
40
|
+
this.port = this.envGetter.getRequiredNumericEnv("PORT");
|
|
41
|
+
this.mongoConnectionString = this.envGetter.getRequiredEnv(
|
|
42
|
+
"MONGO_CONNECTION_STRING",
|
|
43
|
+
);
|
|
44
|
+
this.nodeEnv = this.envGetter.getRequiredEnv("NODE_ENV", [
|
|
45
|
+
"development",
|
|
46
|
+
"production",
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Register the Configuration
|
|
53
|
+
|
|
54
|
+
In your `AppModule`, import `AppConfigModule` and provide your custom `AppConfig` class. This makes `AppConfig` a singleton provider available for dependency injection across your application.
|
|
31
55
|
|
|
32
56
|
```typescript
|
|
57
|
+
// src/app.module.ts
|
|
33
58
|
import { Module } from "@nestjs/common";
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
|
|
59
|
+
import { AppConfigModule } from "nestjs-env-getter";
|
|
60
|
+
import { AppConfig } from "./app.config";
|
|
61
|
+
|
|
62
|
+
@Module({
|
|
63
|
+
imports: [
|
|
64
|
+
// Register the custom AppConfig class
|
|
65
|
+
AppConfigModule.forRoot({ useClass: AppConfig }),
|
|
66
|
+
],
|
|
67
|
+
})
|
|
37
68
|
export class AppModule {}
|
|
38
69
|
```
|
|
39
70
|
|
|
40
|
-
###
|
|
71
|
+
### 3. Use the Configuration
|
|
41
72
|
|
|
42
|
-
|
|
73
|
+
Now you can inject `AppConfig` anywhere in your application.
|
|
43
74
|
|
|
44
75
|
```typescript
|
|
45
76
|
import { Injectable } from "@nestjs/common";
|
|
46
|
-
import {
|
|
77
|
+
import { AppConfig } from "./app.config";
|
|
47
78
|
|
|
48
79
|
@Injectable()
|
|
49
|
-
export class
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
80
|
+
export class DatabaseService {
|
|
81
|
+
private readonly connectionString: string;
|
|
82
|
+
|
|
83
|
+
constructor(private readonly config: AppConfig) {
|
|
84
|
+
this.connectionString = config.mongoConnectionString;
|
|
85
|
+
// Use the connection string...
|
|
53
86
|
}
|
|
54
87
|
}
|
|
55
88
|
```
|
|
56
89
|
|
|
57
|
-
|
|
90
|
+
This approach centralizes your environment variable management, making your application cleaner and easier to maintain.
|
|
58
91
|
|
|
59
|
-
|
|
92
|
+
## CLI Helper
|
|
60
93
|
|
|
61
|
-
|
|
62
|
-
|
|
94
|
+
You can scaffold the configuration file and module setup automatically with the built-in CLI command. Run this in the root of your NestJS project:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx nestjs-env-getter init
|
|
63
98
|
```
|
|
64
99
|
|
|
65
|
-
|
|
100
|
+
This command (shorthand `npx nestjs-env-getter i`) will:
|
|
66
101
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```
|
|
102
|
+
1. Create `src/app.config.ts` from a template.
|
|
103
|
+
2. Import `AppConfigModule.forRoot({ useClass: AppConfig })` into your `src/app.module.ts`.
|
|
70
104
|
|
|
71
|
-
|
|
105
|
+
## API Reference (`EnvGetterService`)
|
|
72
106
|
|
|
73
|
-
|
|
74
|
-
const environment = this.envGetter.getRequiredEnv("NODE_ENV", [
|
|
75
|
-
"development",
|
|
76
|
-
"production",
|
|
77
|
-
"test",
|
|
78
|
-
]);
|
|
79
|
-
```
|
|
107
|
+
Here are detailed examples of the methods available in `EnvGetterService`.
|
|
80
108
|
|
|
81
|
-
|
|
109
|
+
### String
|
|
82
110
|
|
|
83
|
-
|
|
84
|
-
const timeout = this.envGetter.getRequiredNumericEnv("TIMEOUT");
|
|
85
|
-
```
|
|
111
|
+
- **`getRequiredEnv(name: string, allowed?: string[]): string`**
|
|
86
112
|
|
|
87
|
-
|
|
113
|
+
```typescript
|
|
114
|
+
// Throws an error if DB_HOST is not set
|
|
115
|
+
const dbHost = this.envGetter.getRequiredEnv("DB_HOST");
|
|
88
116
|
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
117
|
+
// Throws an error if NODE_ENV is not 'development' or 'production'
|
|
118
|
+
const nodeEnv = this.envGetter.getRequiredEnv("NODE_ENV", [
|
|
119
|
+
"development",
|
|
120
|
+
"production",
|
|
121
|
+
]);
|
|
122
|
+
```
|
|
92
123
|
|
|
93
|
-
|
|
124
|
+
- **`getOptionalEnv(name: string, default?: string, allowed?: string[]): string | undefined`**
|
|
94
125
|
|
|
95
|
-
```typescript
|
|
96
|
-
|
|
97
|
-
|
|
126
|
+
```typescript
|
|
127
|
+
// Returns 'info' if LOG_LEVEL is not set
|
|
128
|
+
const logLevel = this.envGetter.getOptionalEnv("LOG_LEVEL", "info");
|
|
129
|
+
```
|
|
98
130
|
|
|
99
|
-
|
|
131
|
+
### Number
|
|
100
132
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
);
|
|
106
|
-
```
|
|
133
|
+
- **`getRequiredNumericEnv(name: string): number`**
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// Throws an error if PORT is not a valid number
|
|
137
|
+
const port = this.envGetter.getRequiredNumericEnv("PORT");
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **`getOptionalNumericEnv(name: string, default?: number): number | undefined`**
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Returns 3000 if TIMEOUT is not set or not a valid number
|
|
144
|
+
const timeout = this.envGetter.getOptionalNumericEnv("TIMEOUT", 3000);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Boolean
|
|
148
|
+
|
|
149
|
+
- **`getRequiredBooleanEnv(name: string): boolean`**
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// Throws an error if DEBUG_MODE is not 'true' or 'false'
|
|
153
|
+
const isDebug = this.envGetter.getRequiredBooleanEnv("DEBUG_MODE");
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- **`getOptionalBooleanEnv(name: string, default?: boolean): boolean | undefined`**
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Returns false if ENABLE_SSL is not set
|
|
160
|
+
const useSsl = this.envGetter.getOptionalBooleanEnv("ENABLE_SSL", false);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### URL
|
|
164
|
+
|
|
165
|
+
- **`getRequiredURL(name: string): URL`**
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// Throws an error if API_URL is not a valid URL
|
|
169
|
+
const apiUrl = this.envGetter.getRequiredURL("API_URL");
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- **`getOptionalURL(name: string, default?: URL): URL | undefined`**
|
|
107
173
|
|
|
108
|
-
|
|
174
|
+
```typescript
|
|
175
|
+
const defaultUrl = new URL("https://fallback.example.com");
|
|
176
|
+
// Returns defaultUrl if CDN_URL is not set or not a valid URL
|
|
177
|
+
const cdnUrl = this.envGetter.getOptionalURL("CDN_URL", defaultUrl);
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Time Period
|
|
181
|
+
|
|
182
|
+
Parses a string like `'10s'`, `'5m'`, `'2h'`, `'1d'` into a numeric value.
|
|
183
|
+
|
|
184
|
+
- **`getRequiredTimePeriod(name: string, resultIn: 'ms' | 's' | 'm' | 'h' | 'd' = 'ms'): number`**
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Parses '30s' into 30000
|
|
188
|
+
const cacheTtl = this.envGetter.getRequiredTimePeriod("CACHE_TTL");
|
|
189
|
+
|
|
190
|
+
// Parses '2h' into 2
|
|
191
|
+
const sessionHours = this.envGetter.getRequiredTimePeriod(
|
|
192
|
+
"SESSION_DURATION",
|
|
193
|
+
"h",
|
|
194
|
+
);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- **`getOptionalTimePeriod(name: string, default: string, resultIn: ...): number`**
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Returns 60 (seconds) if JWT_EXPIRES_IN is not set
|
|
201
|
+
const expiresIn = this.envGetter.getOptionalTimePeriod(
|
|
202
|
+
"JWT_EXPIRES_IN",
|
|
203
|
+
"1m",
|
|
204
|
+
"s",
|
|
205
|
+
);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Object (from JSON string)
|
|
209
|
+
|
|
210
|
+
- **`getRequiredObject<T>(name: string, cls?: ClassConstructor<T>): T`**
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// ENV: '{"user": "admin", "port": 5432}'
|
|
214
|
+
const dbConfig = this.envGetter.getRequiredObject<{
|
|
215
|
+
user: string;
|
|
216
|
+
port: number;
|
|
217
|
+
}>("DB_CONFIG");
|
|
218
|
+
console.log(dbConfig.port); // 5432
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Array (from JSON string)
|
|
222
|
+
|
|
223
|
+
- **`getRequiredArray<T>(name: string, validate?: (el: T) => boolean | string): T[]`**
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// ENV: '["192.168.1.1", "10.0.0.1"]'
|
|
227
|
+
const allowedIPs = this.envGetter.getRequiredArray<string>("ALLOWED_IPS");
|
|
228
|
+
|
|
229
|
+
// With validation
|
|
230
|
+
const servicePorts = this.envGetter.getRequiredArray<number>(
|
|
231
|
+
"SERVICE_PORTS",
|
|
232
|
+
(port) => {
|
|
233
|
+
if (typeof port !== "number" || port < 1024) {
|
|
234
|
+
return `Port ${port} is invalid. Must be a number >= 1024.`;
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Configuration from JSON Files
|
|
242
|
+
|
|
243
|
+
Load and validate configuration from JSON files with automatic file watching and hot-reload support.
|
|
244
|
+
|
|
245
|
+
- **`getRequiredConfigFromFile<T>(filePath: string, cls?: ClassConstructor<T>, watcherOptions?: FileWatcherOptions): T`**
|
|
246
|
+
|
|
247
|
+
Reads a required JSON configuration file. Automatically watches for file changes and updates the cached config.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// With class validation
|
|
251
|
+
class MongoCredentials {
|
|
252
|
+
connectionString: string;
|
|
253
|
+
|
|
254
|
+
constructor(data: any) {
|
|
255
|
+
if (!data.connectionString) {
|
|
256
|
+
throw new Error("connectionString is required");
|
|
257
|
+
}
|
|
258
|
+
this.connectionString = data.connectionString;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const mongoCreds = this.envGetter.getRequiredConfigFromFile(
|
|
263
|
+
"configs/mongo-creds.json",
|
|
264
|
+
MongoCredentials,
|
|
265
|
+
);
|
|
266
|
+
console.log(mongoCreds.connectionString);
|
|
267
|
+
|
|
268
|
+
// Disable file watching
|
|
269
|
+
const staticConfig = this.envGetter.getRequiredConfigFromFile(
|
|
270
|
+
"config.json",
|
|
271
|
+
undefined,
|
|
272
|
+
{ enabled: false },
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// Custom debounce timing (default is 200ms)
|
|
276
|
+
const config = this.envGetter.getRequiredConfigFromFile(
|
|
277
|
+
"config.json",
|
|
278
|
+
undefined,
|
|
279
|
+
{ debounceMs: 1000 },
|
|
280
|
+
);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- **`getOptionalConfigFromFile<T>(filePath: string, defaultValue?: T, cls?: ClassConstructor<T>, watcherOptions?: FileWatcherOptions): T | undefined`**
|
|
284
|
+
|
|
285
|
+
Reads an optional JSON configuration file. Returns the default value if the file doesn't exist or cannot be parsed as JSON. Only terminates the process if validation fails (when using a class constructor).
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// With class validation
|
|
289
|
+
class TestConfig {
|
|
290
|
+
testConfigStringFromFile: string;
|
|
291
|
+
|
|
292
|
+
constructor(data: any) {
|
|
293
|
+
if (!data.testConfigStringFromFile) {
|
|
294
|
+
throw new Error("testConfigStringFromFile is required");
|
|
295
|
+
}
|
|
296
|
+
this.testConfigStringFromFile = data.testConfigStringFromFile;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const testConfig = this.envGetter.getOptionalConfigFromFile(
|
|
301
|
+
"configs/test-configs.json",
|
|
302
|
+
TestConfig,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// With default value - gracefully handles missing files and JSON parsing errors
|
|
306
|
+
const config = this.envGetter.getOptionalConfigFromFile(
|
|
307
|
+
"optional-config.json",
|
|
308
|
+
{ fallbackValue: "default" },
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Note: Optional config files are graceful - missing files or JSON parsing errors
|
|
312
|
+
// return the default value (or undefined). Only validation errors crash the process.
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### ⚠️ Important: Error Handling Behavior
|
|
316
|
+
|
|
317
|
+
**Required vs Optional Config Files:**
|
|
318
|
+
|
|
319
|
+
- **`getRequiredConfigFromFile`**: Crashes the process if the file is missing, has invalid JSON, or fails validation.
|
|
320
|
+
- **`getOptionalConfigFromFile`**: Only crashes the process on validation errors (when using class constructors). Missing files or JSON parsing errors gracefully return the default value or `undefined`.
|
|
321
|
+
|
|
322
|
+
This design ensures that optional configurations truly are optional - your application won't crash due to missing or malformed optional config files, but validation constraints are still enforced when files are present and parsable.
|
|
323
|
+
|
|
324
|
+
#### ⚠️ Important: Using Config Values by Reference
|
|
325
|
+
|
|
326
|
+
To ensure your application automatically receives updated config values when files change, you **must store config objects by reference**, not by extracting primitive values:
|
|
109
327
|
|
|
110
328
|
```typescript
|
|
111
|
-
|
|
112
|
-
|
|
329
|
+
// ✅ CORRECT - Store the object reference
|
|
330
|
+
@Injectable()
|
|
331
|
+
export class AppConfig {
|
|
332
|
+
mongoConfigs: MongoCredentials; // Store the entire object
|
|
333
|
+
testConfig?: TestConfig; // Store the entire object
|
|
334
|
+
|
|
335
|
+
constructor(protected readonly envGetter: EnvGetterService) {
|
|
336
|
+
// Store references to config objects
|
|
337
|
+
this.mongoConfigs = this.envGetter.getRequiredConfigFromFile(
|
|
338
|
+
"configs/mongo-creds.json",
|
|
339
|
+
MongoCredentials,
|
|
340
|
+
);
|
|
341
|
+
this.testConfig = this.envGetter.getOptionalConfigFromFile(
|
|
342
|
+
"configs/test-configs.json",
|
|
343
|
+
TestConfig,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
113
347
|
|
|
114
|
-
|
|
348
|
+
// Access via the reference - values will update automatically
|
|
349
|
+
@Module({
|
|
350
|
+
imports: [
|
|
351
|
+
MongooseModule.forRootAsync({
|
|
352
|
+
useFactory: (config: AppConfig) => ({
|
|
353
|
+
uri: config.mongoConfigs.connectionString, // Access via reference
|
|
354
|
+
}),
|
|
355
|
+
inject: [AppConfig],
|
|
356
|
+
}),
|
|
357
|
+
],
|
|
358
|
+
})
|
|
359
|
+
export class AppModule {}
|
|
360
|
+
```
|
|
115
361
|
|
|
116
362
|
```typescript
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
363
|
+
// ❌ WRONG - Extracting primitive values breaks hot-reload
|
|
364
|
+
@Injectable()
|
|
365
|
+
export class AppConfig {
|
|
366
|
+
mongoConnectionString: string; // Primitive value - won't update!
|
|
367
|
+
|
|
368
|
+
constructor(protected readonly envGetter: EnvGetterService) {
|
|
369
|
+
const mongoCreds = this.envGetter.getRequiredConfigFromFile(
|
|
370
|
+
"configs/mongo-creds.json",
|
|
371
|
+
MongoCredentials,
|
|
372
|
+
);
|
|
373
|
+
// This extracts the VALUE at startup time - it won't update!
|
|
374
|
+
this.mongoConnectionString = mongoCreds.connectionString;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
121
377
|
```
|
|
122
378
|
|
|
379
|
+
When the file watcher detects changes, it updates the **same object instance** in memory. If you store the object reference, your application automatically sees the updated values. If you extract primitive values (strings, numbers, booleans), they become snapshots that never update.
|
|
380
|
+
|
|
381
|
+
**File Watcher Options:**
|
|
382
|
+
|
|
383
|
+
- `enabled` (boolean, default: `true`): Enable or disable automatic file watching
|
|
384
|
+
- `debounceMs` (number, default: `200`): Delay in milliseconds before re-reading the file after a change
|
|
385
|
+
|
|
386
|
+
**Features:**
|
|
387
|
+
|
|
388
|
+
- ✅ Supports both absolute and relative paths (relative to `process.cwd()`)
|
|
389
|
+
- ✅ Automatic JSON parsing and validation
|
|
390
|
+
- ✅ **Hot-reload with reference preservation** - updates existing object instances in-place
|
|
391
|
+
- ✅ File watching with automatic change detection
|
|
392
|
+
- ✅ Debouncing to prevent excessive re-reads
|
|
393
|
+
- ✅ Class-based validation with constructor pattern
|
|
394
|
+
- ✅ **Graceful error handling for optional configs** - missing files or JSON parsing errors return default values
|
|
395
|
+
- ✅ Process termination only on validation errors (required configs crash on any error)
|
|
396
|
+
- ✅ **Event methods attached to default values** - consistent API even when files don't exist
|
|
397
|
+
- ✅ No application restart needed - changes apply immediately when using object references
|
|
398
|
+
|
|
123
399
|
## License
|
|
124
400
|
|
|
125
401
|
This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.
|