@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.
Files changed (42) hide show
  1. package/.examples/01-basic-usage.ts +289 -0
  2. package/.examples/02-env-helper.ts +282 -0
  3. package/.examples/README.md +285 -0
  4. package/.prettierrc.js +1 -0
  5. package/README.md +261 -0
  6. package/__tests__/config.module.test.ts +244 -0
  7. package/__tests__/drivers/env.driver.test.ts +259 -0
  8. package/__tests__/services/config.service.test.ts +328 -0
  9. package/__tests__/setup.d.ts +11 -0
  10. package/__tests__/vitest.setup.ts +30 -0
  11. package/config/config.config.ts +62 -0
  12. package/dist/index.d.mts +474 -0
  13. package/dist/index.d.ts +474 -0
  14. package/dist/index.js +516 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/index.mjs +501 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/eslint.config.js +13 -0
  19. package/package.json +77 -0
  20. package/src/config.module.ts +154 -0
  21. package/src/constants/index.ts +5 -0
  22. package/src/constants/tokens.constant.ts +38 -0
  23. package/src/drivers/env.driver.ts +194 -0
  24. package/src/drivers/file.driver.ts +81 -0
  25. package/src/drivers/index.ts +6 -0
  26. package/src/index.ts +92 -0
  27. package/src/interfaces/config-driver.interface.ts +30 -0
  28. package/src/interfaces/config-module-options.interface.ts +84 -0
  29. package/src/interfaces/config-service.interface.ts +71 -0
  30. package/src/interfaces/index.ts +8 -0
  31. package/src/interfaces/vite-config-plugin-options.interface.ts +56 -0
  32. package/src/plugins/index.ts +5 -0
  33. package/src/plugins/vite.plugin.ts +115 -0
  34. package/src/services/config.service.ts +172 -0
  35. package/src/services/index.ts +5 -0
  36. package/src/utils/get-nested-value.util.ts +56 -0
  37. package/src/utils/index.ts +6 -0
  38. package/src/utils/load-config-file.util.ts +37 -0
  39. package/src/utils/scan-config-files.util.ts +40 -0
  40. package/tsconfig.json +28 -0
  41. package/tsup.config.ts +105 -0
  42. 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
+ };