@abdokouta/react-config 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.examples/01-basic-usage.ts +289 -0
- package/.examples/02-env-helper.ts +282 -0
- package/.examples/README.md +285 -0
- package/.prettierrc.js +1 -0
- package/README.md +261 -0
- package/__tests__/config.module.test.ts +244 -0
- package/__tests__/drivers/env.driver.test.ts +259 -0
- package/__tests__/services/config.service.test.ts +328 -0
- package/__tests__/setup.d.ts +11 -0
- package/__tests__/vitest.setup.ts +30 -0
- package/config/config.config.ts +62 -0
- package/dist/index.d.mts +474 -0
- package/dist/index.d.ts +474 -0
- package/dist/index.js +516 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +501 -0
- package/dist/index.mjs.map +1 -0
- package/eslint.config.js +13 -0
- package/package.json +77 -0
- package/src/config.module.ts +154 -0
- package/src/constants/index.ts +5 -0
- package/src/constants/tokens.constant.ts +38 -0
- package/src/drivers/env.driver.ts +194 -0
- package/src/drivers/file.driver.ts +81 -0
- package/src/drivers/index.ts +6 -0
- package/src/index.ts +92 -0
- package/src/interfaces/config-driver.interface.ts +30 -0
- package/src/interfaces/config-module-options.interface.ts +84 -0
- package/src/interfaces/config-service.interface.ts +71 -0
- package/src/interfaces/index.ts +8 -0
- package/src/interfaces/vite-config-plugin-options.interface.ts +56 -0
- package/src/plugins/index.ts +5 -0
- package/src/plugins/vite.plugin.ts +115 -0
- package/src/services/config.service.ts +172 -0
- package/src/services/index.ts +5 -0
- package/src/utils/get-nested-value.util.ts +56 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/load-config-file.util.ts +37 -0
- package/src/utils/scan-config-files.util.ts +40 -0
- package/tsconfig.json +28 -0
- package/tsup.config.ts +105 -0
- package/vitest.config.ts +66 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for ConfigService
|
|
3
|
+
*
|
|
4
|
+
* This test suite verifies the ConfigService functionality including:
|
|
5
|
+
* - Getting configuration values
|
|
6
|
+
* - Type-safe getters (getString, getNumber, getBoolean)
|
|
7
|
+
* - Throwing methods (getOrThrow, getStringOrThrow)
|
|
8
|
+
* - Default value handling
|
|
9
|
+
* - Nested key access
|
|
10
|
+
*
|
|
11
|
+
* @module @abdokouta/config
|
|
12
|
+
* @category Tests
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
16
|
+
import { ConfigService } from "@/services/config.service";
|
|
17
|
+
import type { ConfigDriver } from "@/interfaces/config-driver.interface";
|
|
18
|
+
|
|
19
|
+
describe("ConfigService", () => {
|
|
20
|
+
let mockDriver: ConfigDriver;
|
|
21
|
+
let configService: ConfigService;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// Arrange: Create mock driver
|
|
25
|
+
mockDriver = {
|
|
26
|
+
load: vi.fn().mockReturnValue({
|
|
27
|
+
app: {
|
|
28
|
+
name: "Test App",
|
|
29
|
+
port: 3000,
|
|
30
|
+
debug: true,
|
|
31
|
+
},
|
|
32
|
+
database: {
|
|
33
|
+
host: "localhost",
|
|
34
|
+
port: 5432,
|
|
35
|
+
name: "testdb",
|
|
36
|
+
},
|
|
37
|
+
api: {
|
|
38
|
+
key: "test-key-123",
|
|
39
|
+
timeout: 5000,
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
get: vi.fn((key: string, defaultValue?: any) => {
|
|
43
|
+
const data: any = {
|
|
44
|
+
"app.name": "Test App",
|
|
45
|
+
"app.port": 3000,
|
|
46
|
+
"app.debug": true,
|
|
47
|
+
"database.host": "localhost",
|
|
48
|
+
"database.port": 5432,
|
|
49
|
+
"api.key": "test-key-123",
|
|
50
|
+
};
|
|
51
|
+
return data[key] ?? defaultValue;
|
|
52
|
+
}),
|
|
53
|
+
has: vi.fn((key: string) => {
|
|
54
|
+
const keys = ["app.name", "app.port", "app.debug", "database.host", "database.port", "api.key"];
|
|
55
|
+
return keys.includes(key);
|
|
56
|
+
}),
|
|
57
|
+
all: vi.fn().mockReturnValue({
|
|
58
|
+
app: { name: "Test App", port: 3000, debug: true },
|
|
59
|
+
database: { host: "localhost", port: 5432, name: "testdb" },
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
configService = new ConfigService(mockDriver);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("get", () => {
|
|
67
|
+
it("should get existing configuration value", () => {
|
|
68
|
+
// Act: Get config value
|
|
69
|
+
const appName = configService.get("app.name");
|
|
70
|
+
|
|
71
|
+
// Assert: Value is returned
|
|
72
|
+
expect(appName).toBe("Test App");
|
|
73
|
+
expect(mockDriver.get).toHaveBeenCalledWith("app.name", undefined);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should return default value when key doesn't exist", () => {
|
|
77
|
+
// Arrange: Mock missing key
|
|
78
|
+
mockDriver.get = vi.fn((key, defaultValue) => defaultValue);
|
|
79
|
+
|
|
80
|
+
// Act: Get with default
|
|
81
|
+
const value = configService.get("missing.key", "default");
|
|
82
|
+
|
|
83
|
+
// Assert: Default is returned
|
|
84
|
+
expect(value).toBe("default");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should return undefined when key doesn't exist and no default", () => {
|
|
88
|
+
// Arrange: Mock missing key
|
|
89
|
+
mockDriver.get = vi.fn(() => undefined);
|
|
90
|
+
|
|
91
|
+
// Act: Get without default
|
|
92
|
+
const value = configService.get("missing.key");
|
|
93
|
+
|
|
94
|
+
// Assert: Undefined is returned
|
|
95
|
+
expect(value).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should handle nested keys", () => {
|
|
99
|
+
// Act: Get nested value
|
|
100
|
+
const dbHost = configService.get("database.host");
|
|
101
|
+
|
|
102
|
+
// Assert: Nested value is returned
|
|
103
|
+
expect(dbHost).toBe("localhost");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should handle numeric values", () => {
|
|
107
|
+
// Act: Get numeric value
|
|
108
|
+
const port = configService.get<number>("app.port");
|
|
109
|
+
|
|
110
|
+
// Assert: Number is returned
|
|
111
|
+
expect(port).toBe(3000);
|
|
112
|
+
expect(typeof port).toBe("number");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should handle boolean values", () => {
|
|
116
|
+
// Act: Get boolean value
|
|
117
|
+
const debug = configService.get<boolean>("app.debug");
|
|
118
|
+
|
|
119
|
+
// Assert: Boolean is returned
|
|
120
|
+
expect(debug).toBe(true);
|
|
121
|
+
expect(typeof debug).toBe("boolean");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("getOrThrow", () => {
|
|
126
|
+
it("should return value when key exists", () => {
|
|
127
|
+
// Act: Get existing key
|
|
128
|
+
const appName = configService.getOrThrow("app.name");
|
|
129
|
+
|
|
130
|
+
// Assert: Value is returned
|
|
131
|
+
expect(appName).toBe("Test App");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should throw error when key doesn't exist", () => {
|
|
135
|
+
// Arrange: Mock missing key
|
|
136
|
+
mockDriver.get = vi.fn(() => undefined);
|
|
137
|
+
|
|
138
|
+
// Act & Assert: Should throw
|
|
139
|
+
expect(() => {
|
|
140
|
+
configService.getOrThrow("missing.key");
|
|
141
|
+
}).toThrow();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should throw with descriptive error message", () => {
|
|
145
|
+
// Arrange: Mock missing key
|
|
146
|
+
mockDriver.get = vi.fn(() => undefined);
|
|
147
|
+
|
|
148
|
+
// Act & Assert: Should throw with message
|
|
149
|
+
expect(() => {
|
|
150
|
+
configService.getOrThrow("missing.key");
|
|
151
|
+
}).toThrow(/missing\.key/);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("getString", () => {
|
|
156
|
+
it("should return string value", () => {
|
|
157
|
+
// Act: Get string
|
|
158
|
+
const appName = configService.getString("app.name");
|
|
159
|
+
|
|
160
|
+
// Assert: String is returned
|
|
161
|
+
expect(appName).toBe("Test App");
|
|
162
|
+
expect(typeof appName).toBe("string");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("should return default string when key doesn't exist", () => {
|
|
166
|
+
// Arrange: Mock missing key
|
|
167
|
+
mockDriver.get = vi.fn((key, defaultValue) => defaultValue);
|
|
168
|
+
|
|
169
|
+
// Act: Get with default
|
|
170
|
+
const value = configService.getString("missing.key", "default");
|
|
171
|
+
|
|
172
|
+
// Assert: Default is returned
|
|
173
|
+
expect(value).toBe("default");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should return undefined when no default provided", () => {
|
|
177
|
+
// Arrange: Mock missing key
|
|
178
|
+
mockDriver.get = vi.fn(() => undefined);
|
|
179
|
+
|
|
180
|
+
// Act: Get without default
|
|
181
|
+
const value = configService.getString("missing.key");
|
|
182
|
+
|
|
183
|
+
// Assert: Undefined is returned
|
|
184
|
+
expect(value).toBeUndefined();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("getStringOrThrow", () => {
|
|
189
|
+
it("should return string value when key exists", () => {
|
|
190
|
+
// Act: Get existing string
|
|
191
|
+
const apiKey = configService.getStringOrThrow("api.key");
|
|
192
|
+
|
|
193
|
+
// Assert: String is returned
|
|
194
|
+
expect(apiKey).toBe("test-key-123");
|
|
195
|
+
expect(typeof apiKey).toBe("string");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should throw when key doesn't exist", () => {
|
|
199
|
+
// Arrange: Mock missing key
|
|
200
|
+
mockDriver.get = vi.fn(() => undefined);
|
|
201
|
+
|
|
202
|
+
// Act & Assert: Should throw
|
|
203
|
+
expect(() => {
|
|
204
|
+
configService.getStringOrThrow("missing.key");
|
|
205
|
+
}).toThrow();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("getNumber", () => {
|
|
210
|
+
it("should return number value", () => {
|
|
211
|
+
// Act: Get number
|
|
212
|
+
const port = configService.getNumber("app.port");
|
|
213
|
+
|
|
214
|
+
// Assert: Number is returned
|
|
215
|
+
expect(port).toBe(3000);
|
|
216
|
+
expect(typeof port).toBe("number");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should return default number when key doesn't exist", () => {
|
|
220
|
+
// Arrange: Mock missing key
|
|
221
|
+
mockDriver.get = vi.fn((key, defaultValue) => defaultValue);
|
|
222
|
+
|
|
223
|
+
// Act: Get with default
|
|
224
|
+
const value = configService.getNumber("missing.key", 8080);
|
|
225
|
+
|
|
226
|
+
// Assert: Default is returned
|
|
227
|
+
expect(value).toBe(8080);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should parse string numbers", () => {
|
|
231
|
+
// Arrange: Mock string number
|
|
232
|
+
mockDriver.get = vi.fn(() => "5432");
|
|
233
|
+
|
|
234
|
+
// Act: Get as number
|
|
235
|
+
const port = configService.getNumber("database.port");
|
|
236
|
+
|
|
237
|
+
// Assert: Parsed number is returned
|
|
238
|
+
expect(port).toBe(5432);
|
|
239
|
+
expect(typeof port).toBe("number");
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("getBoolean", () => {
|
|
244
|
+
it("should return boolean value", () => {
|
|
245
|
+
// Act: Get boolean
|
|
246
|
+
const debug = configService.getBoolean("app.debug");
|
|
247
|
+
|
|
248
|
+
// Assert: Boolean is returned
|
|
249
|
+
expect(debug).toBe(true);
|
|
250
|
+
expect(typeof debug).toBe("boolean");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should parse string booleans", () => {
|
|
254
|
+
// Arrange: Mock string boolean
|
|
255
|
+
mockDriver.get = vi.fn((key) => {
|
|
256
|
+
if (key === "feature.enabled") return "true";
|
|
257
|
+
if (key === "feature.disabled") return "false";
|
|
258
|
+
return undefined;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Act: Get as boolean
|
|
262
|
+
const enabled = configService.getBoolean("feature.enabled");
|
|
263
|
+
const disabled = configService.getBoolean("feature.disabled");
|
|
264
|
+
|
|
265
|
+
// Assert: Parsed booleans are returned
|
|
266
|
+
expect(enabled).toBe(true);
|
|
267
|
+
expect(disabled).toBe(false);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("should handle truthy/falsy values", () => {
|
|
271
|
+
// Arrange: Mock various values
|
|
272
|
+
mockDriver.get = vi.fn((key) => {
|
|
273
|
+
if (key === "one") return 1;
|
|
274
|
+
if (key === "zero") return 0;
|
|
275
|
+
if (key === "yes") return "yes";
|
|
276
|
+
if (key === "no") return "no";
|
|
277
|
+
return undefined;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Act: Get as booleans
|
|
281
|
+
const one = configService.getBoolean("one");
|
|
282
|
+
const zero = configService.getBoolean("zero");
|
|
283
|
+
const yes = configService.getBoolean("yes");
|
|
284
|
+
const no = configService.getBoolean("no");
|
|
285
|
+
|
|
286
|
+
// Assert: Values are converted to boolean
|
|
287
|
+
expect(one).toBe(true);
|
|
288
|
+
expect(zero).toBe(false);
|
|
289
|
+
expect(yes).toBe(true);
|
|
290
|
+
expect(no).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe("Edge Cases", () => {
|
|
295
|
+
it("should handle null values", () => {
|
|
296
|
+
// Arrange: Mock null value
|
|
297
|
+
mockDriver.get = vi.fn(() => null);
|
|
298
|
+
|
|
299
|
+
// Act: Get null value
|
|
300
|
+
const value = configService.get("null.key");
|
|
301
|
+
|
|
302
|
+
// Assert: Null is returned
|
|
303
|
+
expect(value).toBeNull();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should handle empty string values", () => {
|
|
307
|
+
// Arrange: Mock empty string
|
|
308
|
+
mockDriver.get = vi.fn(() => "");
|
|
309
|
+
|
|
310
|
+
// Act: Get empty string
|
|
311
|
+
const value = configService.getString("empty.key");
|
|
312
|
+
|
|
313
|
+
// Assert: Empty string is returned
|
|
314
|
+
expect(value).toBe("");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("should handle zero values", () => {
|
|
318
|
+
// Arrange: Mock zero
|
|
319
|
+
mockDriver.get = vi.fn(() => 0);
|
|
320
|
+
|
|
321
|
+
// Act: Get zero
|
|
322
|
+
const value = configService.getNumber("zero.key");
|
|
323
|
+
|
|
324
|
+
// Assert: Zero is returned
|
|
325
|
+
expect(value).toBe(0);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Type declarations for Vitest test environment
|
|
3
|
+
*
|
|
4
|
+
* This file extends Vitest's type definitions to include custom matchers
|
|
5
|
+
* and global test utilities.
|
|
6
|
+
*
|
|
7
|
+
* @module @abdokouta/config
|
|
8
|
+
* @category Configuration
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import "vitest/globals";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Vitest setup file for @abdokouta/config package
|
|
3
|
+
*
|
|
4
|
+
* This file configures the testing environment before running tests.
|
|
5
|
+
* It sets up container mocking for dependency injection tests.
|
|
6
|
+
*
|
|
7
|
+
* Setup Features:
|
|
8
|
+
* - Container mocking for DI tests
|
|
9
|
+
*
|
|
10
|
+
* @module @abdokouta/config
|
|
11
|
+
* @category Configuration
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { vi } from "vitest";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Mock @abdokouta/react-di decorators
|
|
18
|
+
*
|
|
19
|
+
* This ensures that decorator metadata doesn't interfere with tests
|
|
20
|
+
* and allows us to test module behavior in isolation.
|
|
21
|
+
*/
|
|
22
|
+
vi.mock("@abdokouta/react-di", async () => {
|
|
23
|
+
const actual = await vi.importActual("@abdokouta/react-di");
|
|
24
|
+
return {
|
|
25
|
+
...actual,
|
|
26
|
+
Injectable: () => (target: any) => target,
|
|
27
|
+
Inject: () => (target: any, propertyKey: string, parameterIndex: number) => {},
|
|
28
|
+
Module: (metadata: any) => (target: any) => target,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Package Configuration
|
|
3
|
+
*
|
|
4
|
+
* Configuration for the @abdokouta/config package.
|
|
5
|
+
* Defines how environment variables are loaded and accessed.
|
|
6
|
+
*
|
|
7
|
+
* @module config/config
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ConfigModuleOptions } from '@abdokouta/config';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Config Configuration
|
|
14
|
+
*
|
|
15
|
+
* Settings:
|
|
16
|
+
* - driver: 'env' (reads from process.env)
|
|
17
|
+
* - ignoreEnvFile: true (don't load .env file in browser)
|
|
18
|
+
* - isGlobal: true (available to all modules)
|
|
19
|
+
* - envPrefix: 'auto' (auto-detect and strip VITE_ or NEXT_PUBLIC_ prefix)
|
|
20
|
+
*
|
|
21
|
+
* With envPrefix: 'auto', you can access:
|
|
22
|
+
* - VITE_APP_NAME as APP_NAME
|
|
23
|
+
* - NEXT_PUBLIC_API_URL as API_URL
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* // In app.module.ts
|
|
28
|
+
* import { configConfig } from '@/config/config.config';
|
|
29
|
+
*
|
|
30
|
+
* @Module({
|
|
31
|
+
* imports: [ConfigModule.forRoot(configConfig)],
|
|
32
|
+
* })
|
|
33
|
+
* export class AppModule {}
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const configConfig: ConfigModuleOptions = {
|
|
37
|
+
/**
|
|
38
|
+
* Driver to use for loading configuration
|
|
39
|
+
* 'env' reads from process.env
|
|
40
|
+
*/
|
|
41
|
+
driver: 'env',
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Ignore .env file loading
|
|
45
|
+
* Set to true in browser environments
|
|
46
|
+
* Set to false in Node.js environments
|
|
47
|
+
*/
|
|
48
|
+
ignoreEnvFile: true,
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Make config service globally available
|
|
52
|
+
* When true, no need to import ConfigModule in feature modules
|
|
53
|
+
*/
|
|
54
|
+
isGlobal: true,
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Auto-detect and strip environment variable prefix
|
|
58
|
+
* 'auto' detects VITE_ or NEXT_PUBLIC_ and strips it
|
|
59
|
+
* So VITE_APP_NAME becomes accessible as APP_NAME
|
|
60
|
+
*/
|
|
61
|
+
envPrefix: 'auto',
|
|
62
|
+
};
|