@abdokouta/react-config 1.0.0 → 1.0.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.
@@ -1,22 +1,22 @@
1
1
  /**
2
2
  * @fileoverview Tests for ConfigService
3
- *
3
+ *
4
4
  * This test suite verifies the ConfigService functionality including:
5
5
  * - Getting configuration values
6
6
  * - Type-safe getters (getString, getNumber, getBoolean)
7
7
  * - Throwing methods (getOrThrow, getStringOrThrow)
8
8
  * - Default value handling
9
9
  * - Nested key access
10
- *
10
+ *
11
11
  * @module @abdokouta/config
12
12
  * @category Tests
13
13
  */
14
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";
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
18
 
19
- describe("ConfigService", () => {
19
+ describe('ConfigService', () => {
20
20
  let mockDriver: ConfigDriver;
21
21
  let configService: ConfigService;
22
22
 
@@ -25,52 +25,59 @@ describe("ConfigService", () => {
25
25
  mockDriver = {
26
26
  load: vi.fn().mockReturnValue({
27
27
  app: {
28
- name: "Test App",
28
+ name: 'Test App',
29
29
  port: 3000,
30
30
  debug: true,
31
31
  },
32
32
  database: {
33
- host: "localhost",
33
+ host: 'localhost',
34
34
  port: 5432,
35
- name: "testdb",
35
+ name: 'testdb',
36
36
  },
37
37
  api: {
38
- key: "test-key-123",
38
+ key: 'test-key-123',
39
39
  timeout: 5000,
40
40
  },
41
41
  }),
42
42
  get: vi.fn((key: string, defaultValue?: any) => {
43
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",
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
50
  };
51
51
  return data[key] ?? defaultValue;
52
52
  }),
53
53
  has: vi.fn((key: string) => {
54
- const keys = ["app.name", "app.port", "app.debug", "database.host", "database.port", "api.key"];
54
+ const keys = [
55
+ 'app.name',
56
+ 'app.port',
57
+ 'app.debug',
58
+ 'database.host',
59
+ 'database.port',
60
+ 'api.key',
61
+ ];
55
62
  return keys.includes(key);
56
63
  }),
57
64
  all: vi.fn().mockReturnValue({
58
- app: { name: "Test App", port: 3000, debug: true },
59
- database: { host: "localhost", port: 5432, name: "testdb" },
65
+ app: { name: 'Test App', port: 3000, debug: true },
66
+ database: { host: 'localhost', port: 5432, name: 'testdb' },
60
67
  }),
61
68
  };
62
69
 
63
70
  configService = new ConfigService(mockDriver);
64
71
  });
65
72
 
66
- describe("get", () => {
67
- it("should get existing configuration value", () => {
73
+ describe('get', () => {
74
+ it('should get existing configuration value', () => {
68
75
  // Act: Get config value
69
- const appName = configService.get("app.name");
76
+ const appName = configService.get('app.name');
70
77
 
71
78
  // Assert: Value is returned
72
- expect(appName).toBe("Test App");
73
- expect(mockDriver.get).toHaveBeenCalledWith("app.name", undefined);
79
+ expect(appName).toBe('Test App');
80
+ expect(mockDriver.get).toHaveBeenCalledWith('app.name', undefined);
74
81
  });
75
82
 
76
83
  it("should return default value when key doesn't exist", () => {
@@ -78,10 +85,10 @@ describe("ConfigService", () => {
78
85
  mockDriver.get = vi.fn((key, defaultValue) => defaultValue);
79
86
 
80
87
  // Act: Get with default
81
- const value = configService.get("missing.key", "default");
88
+ const value = configService.get('missing.key', 'default');
82
89
 
83
90
  // Assert: Default is returned
84
- expect(value).toBe("default");
91
+ expect(value).toBe('default');
85
92
  });
86
93
 
87
94
  it("should return undefined when key doesn't exist and no default", () => {
@@ -89,46 +96,46 @@ describe("ConfigService", () => {
89
96
  mockDriver.get = vi.fn(() => undefined);
90
97
 
91
98
  // Act: Get without default
92
- const value = configService.get("missing.key");
99
+ const value = configService.get('missing.key');
93
100
 
94
101
  // Assert: Undefined is returned
95
102
  expect(value).toBeUndefined();
96
103
  });
97
104
 
98
- it("should handle nested keys", () => {
105
+ it('should handle nested keys', () => {
99
106
  // Act: Get nested value
100
- const dbHost = configService.get("database.host");
107
+ const dbHost = configService.get('database.host');
101
108
 
102
109
  // Assert: Nested value is returned
103
- expect(dbHost).toBe("localhost");
110
+ expect(dbHost).toBe('localhost');
104
111
  });
105
112
 
106
- it("should handle numeric values", () => {
113
+ it('should handle numeric values', () => {
107
114
  // Act: Get numeric value
108
- const port = configService.get<number>("app.port");
115
+ const port = configService.get<number>('app.port');
109
116
 
110
117
  // Assert: Number is returned
111
118
  expect(port).toBe(3000);
112
- expect(typeof port).toBe("number");
119
+ expect(typeof port).toBe('number');
113
120
  });
114
121
 
115
- it("should handle boolean values", () => {
122
+ it('should handle boolean values', () => {
116
123
  // Act: Get boolean value
117
- const debug = configService.get<boolean>("app.debug");
124
+ const debug = configService.get<boolean>('app.debug');
118
125
 
119
126
  // Assert: Boolean is returned
120
127
  expect(debug).toBe(true);
121
- expect(typeof debug).toBe("boolean");
128
+ expect(typeof debug).toBe('boolean');
122
129
  });
123
130
  });
124
131
 
125
- describe("getOrThrow", () => {
126
- it("should return value when key exists", () => {
132
+ describe('getOrThrow', () => {
133
+ it('should return value when key exists', () => {
127
134
  // Act: Get existing key
128
- const appName = configService.getOrThrow("app.name");
135
+ const appName = configService.getOrThrow('app.name');
129
136
 
130
137
  // Assert: Value is returned
131
- expect(appName).toBe("Test App");
138
+ expect(appName).toBe('Test App');
132
139
  });
133
140
 
134
141
  it("should throw error when key doesn't exist", () => {
@@ -137,29 +144,29 @@ describe("ConfigService", () => {
137
144
 
138
145
  // Act & Assert: Should throw
139
146
  expect(() => {
140
- configService.getOrThrow("missing.key");
147
+ configService.getOrThrow('missing.key');
141
148
  }).toThrow();
142
149
  });
143
150
 
144
- it("should throw with descriptive error message", () => {
151
+ it('should throw with descriptive error message', () => {
145
152
  // Arrange: Mock missing key
146
153
  mockDriver.get = vi.fn(() => undefined);
147
154
 
148
155
  // Act & Assert: Should throw with message
149
156
  expect(() => {
150
- configService.getOrThrow("missing.key");
157
+ configService.getOrThrow('missing.key');
151
158
  }).toThrow(/missing\.key/);
152
159
  });
153
160
  });
154
161
 
155
- describe("getString", () => {
156
- it("should return string value", () => {
162
+ describe('getString', () => {
163
+ it('should return string value', () => {
157
164
  // Act: Get string
158
- const appName = configService.getString("app.name");
165
+ const appName = configService.getString('app.name');
159
166
 
160
167
  // Assert: String is returned
161
- expect(appName).toBe("Test App");
162
- expect(typeof appName).toBe("string");
168
+ expect(appName).toBe('Test App');
169
+ expect(typeof appName).toBe('string');
163
170
  });
164
171
 
165
172
  it("should return default string when key doesn't exist", () => {
@@ -167,32 +174,32 @@ describe("ConfigService", () => {
167
174
  mockDriver.get = vi.fn((key, defaultValue) => defaultValue);
168
175
 
169
176
  // Act: Get with default
170
- const value = configService.getString("missing.key", "default");
177
+ const value = configService.getString('missing.key', 'default');
171
178
 
172
179
  // Assert: Default is returned
173
- expect(value).toBe("default");
180
+ expect(value).toBe('default');
174
181
  });
175
182
 
176
- it("should return undefined when no default provided", () => {
183
+ it('should return undefined when no default provided', () => {
177
184
  // Arrange: Mock missing key
178
185
  mockDriver.get = vi.fn(() => undefined);
179
186
 
180
187
  // Act: Get without default
181
- const value = configService.getString("missing.key");
188
+ const value = configService.getString('missing.key');
182
189
 
183
190
  // Assert: Undefined is returned
184
191
  expect(value).toBeUndefined();
185
192
  });
186
193
  });
187
194
 
188
- describe("getStringOrThrow", () => {
189
- it("should return string value when key exists", () => {
195
+ describe('getStringOrThrow', () => {
196
+ it('should return string value when key exists', () => {
190
197
  // Act: Get existing string
191
- const apiKey = configService.getStringOrThrow("api.key");
198
+ const apiKey = configService.getStringOrThrow('api.key');
192
199
 
193
200
  // Assert: String is returned
194
- expect(apiKey).toBe("test-key-123");
195
- expect(typeof apiKey).toBe("string");
201
+ expect(apiKey).toBe('test-key-123');
202
+ expect(typeof apiKey).toBe('string');
196
203
  });
197
204
 
198
205
  it("should throw when key doesn't exist", () => {
@@ -201,19 +208,19 @@ describe("ConfigService", () => {
201
208
 
202
209
  // Act & Assert: Should throw
203
210
  expect(() => {
204
- configService.getStringOrThrow("missing.key");
211
+ configService.getStringOrThrow('missing.key');
205
212
  }).toThrow();
206
213
  });
207
214
  });
208
215
 
209
- describe("getNumber", () => {
210
- it("should return number value", () => {
216
+ describe('getNumber', () => {
217
+ it('should return number value', () => {
211
218
  // Act: Get number
212
- const port = configService.getNumber("app.port");
219
+ const port = configService.getNumber('app.port');
213
220
 
214
221
  // Assert: Number is returned
215
222
  expect(port).toBe(3000);
216
- expect(typeof port).toBe("number");
223
+ expect(typeof port).toBe('number');
217
224
  });
218
225
 
219
226
  it("should return default number when key doesn't exist", () => {
@@ -221,67 +228,67 @@ describe("ConfigService", () => {
221
228
  mockDriver.get = vi.fn((key, defaultValue) => defaultValue);
222
229
 
223
230
  // Act: Get with default
224
- const value = configService.getNumber("missing.key", 8080);
231
+ const value = configService.getNumber('missing.key', 8080);
225
232
 
226
233
  // Assert: Default is returned
227
234
  expect(value).toBe(8080);
228
235
  });
229
236
 
230
- it("should parse string numbers", () => {
237
+ it('should parse string numbers', () => {
231
238
  // Arrange: Mock string number
232
- mockDriver.get = vi.fn(() => "5432");
239
+ mockDriver.get = vi.fn(() => '5432');
233
240
 
234
241
  // Act: Get as number
235
- const port = configService.getNumber("database.port");
242
+ const port = configService.getNumber('database.port');
236
243
 
237
244
  // Assert: Parsed number is returned
238
245
  expect(port).toBe(5432);
239
- expect(typeof port).toBe("number");
246
+ expect(typeof port).toBe('number');
240
247
  });
241
248
  });
242
249
 
243
- describe("getBoolean", () => {
244
- it("should return boolean value", () => {
250
+ describe('getBoolean', () => {
251
+ it('should return boolean value', () => {
245
252
  // Act: Get boolean
246
- const debug = configService.getBoolean("app.debug");
253
+ const debug = configService.getBoolean('app.debug');
247
254
 
248
255
  // Assert: Boolean is returned
249
256
  expect(debug).toBe(true);
250
- expect(typeof debug).toBe("boolean");
257
+ expect(typeof debug).toBe('boolean');
251
258
  });
252
259
 
253
- it("should parse string booleans", () => {
260
+ it('should parse string booleans', () => {
254
261
  // Arrange: Mock string boolean
255
262
  mockDriver.get = vi.fn((key) => {
256
- if (key === "feature.enabled") return "true";
257
- if (key === "feature.disabled") return "false";
263
+ if (key === 'feature.enabled') return 'true';
264
+ if (key === 'feature.disabled') return 'false';
258
265
  return undefined;
259
266
  });
260
267
 
261
268
  // Act: Get as boolean
262
- const enabled = configService.getBoolean("feature.enabled");
263
- const disabled = configService.getBoolean("feature.disabled");
269
+ const enabled = configService.getBoolean('feature.enabled');
270
+ const disabled = configService.getBoolean('feature.disabled');
264
271
 
265
272
  // Assert: Parsed booleans are returned
266
273
  expect(enabled).toBe(true);
267
274
  expect(disabled).toBe(false);
268
275
  });
269
276
 
270
- it("should handle truthy/falsy values", () => {
277
+ it('should handle truthy/falsy values', () => {
271
278
  // Arrange: Mock various values
272
279
  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";
280
+ if (key === 'one') return 1;
281
+ if (key === 'zero') return 0;
282
+ if (key === 'yes') return 'yes';
283
+ if (key === 'no') return 'no';
277
284
  return undefined;
278
285
  });
279
286
 
280
287
  // 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");
288
+ const one = configService.getBoolean('one');
289
+ const zero = configService.getBoolean('zero');
290
+ const yes = configService.getBoolean('yes');
291
+ const no = configService.getBoolean('no');
285
292
 
286
293
  // Assert: Values are converted to boolean
287
294
  expect(one).toBe(true);
@@ -291,35 +298,35 @@ describe("ConfigService", () => {
291
298
  });
292
299
  });
293
300
 
294
- describe("Edge Cases", () => {
295
- it("should handle null values", () => {
301
+ describe('Edge Cases', () => {
302
+ it('should handle null values', () => {
296
303
  // Arrange: Mock null value
297
304
  mockDriver.get = vi.fn(() => null);
298
305
 
299
306
  // Act: Get null value
300
- const value = configService.get("null.key");
307
+ const value = configService.get('null.key');
301
308
 
302
309
  // Assert: Null is returned
303
310
  expect(value).toBeNull();
304
311
  });
305
312
 
306
- it("should handle empty string values", () => {
313
+ it('should handle empty string values', () => {
307
314
  // Arrange: Mock empty string
308
- mockDriver.get = vi.fn(() => "");
315
+ mockDriver.get = vi.fn(() => '');
309
316
 
310
317
  // Act: Get empty string
311
- const value = configService.getString("empty.key");
318
+ const value = configService.getString('empty.key');
312
319
 
313
320
  // Assert: Empty string is returned
314
- expect(value).toBe("");
321
+ expect(value).toBe('');
315
322
  });
316
323
 
317
- it("should handle zero values", () => {
324
+ it('should handle zero values', () => {
318
325
  // Arrange: Mock zero
319
326
  mockDriver.get = vi.fn(() => 0);
320
327
 
321
328
  // Act: Get zero
322
- const value = configService.getNumber("zero.key");
329
+ const value = configService.getNumber('zero.key');
323
330
 
324
331
  // Assert: Zero is returned
325
332
  expect(value).toBe(0);
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * @fileoverview Type declarations for Vitest test environment
3
- *
3
+ *
4
4
  * This file extends Vitest's type definitions to include custom matchers
5
5
  * and global test utilities.
6
- *
6
+ *
7
7
  * @module @abdokouta/config
8
8
  * @category Configuration
9
9
  */
10
10
 
11
- import "vitest/globals";
11
+ import 'vitest/globals';
@@ -1,26 +1,26 @@
1
1
  /**
2
2
  * @fileoverview Vitest setup file for @abdokouta/config package
3
- *
3
+ *
4
4
  * This file configures the testing environment before running tests.
5
5
  * It sets up container mocking for dependency injection tests.
6
- *
6
+ *
7
7
  * Setup Features:
8
8
  * - Container mocking for DI tests
9
- *
9
+ *
10
10
  * @module @abdokouta/config
11
11
  * @category Configuration
12
12
  */
13
13
 
14
- import { vi } from "vitest";
14
+ import { vi } from 'vitest';
15
15
 
16
16
  /**
17
17
  * Mock @abdokouta/react-di decorators
18
- *
18
+ *
19
19
  * This ensures that decorator metadata doesn't interfere with tests
20
20
  * and allows us to test module behavior in isolation.
21
21
  */
22
- vi.mock("@abdokouta/react-di", async () => {
23
- const actual = await vi.importActual("@abdokouta/react-di");
22
+ vi.mock('@abdokouta/react-di', async () => {
23
+ const actual = await vi.importActual('@abdokouta/react-di');
24
24
  return {
25
25
  ...actual,
26
26
  Injectable: () => (target: any) => target,
@@ -1,39 +1,39 @@
1
1
  /**
2
2
  * Config Package Configuration
3
- *
3
+ *
4
4
  * Configuration for the @abdokouta/config package.
5
5
  * Defines how environment variables are loaded and accessed.
6
- *
6
+ *
7
7
  * @module config/config
8
8
  */
9
9
 
10
- import type { ConfigModuleOptions } from '@abdokouta/config';
10
+ import { defineConfig } from '@abdokouta/config';
11
11
 
12
12
  /**
13
13
  * Config Configuration
14
- *
14
+ *
15
15
  * Settings:
16
16
  * - driver: 'env' (reads from process.env)
17
17
  * - ignoreEnvFile: true (don't load .env file in browser)
18
18
  * - isGlobal: true (available to all modules)
19
19
  * - envPrefix: 'auto' (auto-detect and strip VITE_ or NEXT_PUBLIC_ prefix)
20
- *
20
+ *
21
21
  * With envPrefix: 'auto', you can access:
22
22
  * - VITE_APP_NAME as APP_NAME
23
23
  * - NEXT_PUBLIC_API_URL as API_URL
24
- *
24
+ *
25
25
  * @example
26
26
  * ```typescript
27
27
  * // In app.module.ts
28
28
  * import { configConfig } from '@/config/config.config';
29
- *
29
+ *
30
30
  * @Module({
31
31
  * imports: [ConfigModule.forRoot(configConfig)],
32
32
  * })
33
33
  * export class AppModule {}
34
34
  * ```
35
35
  */
36
- export const configConfig: ConfigModuleOptions = {
36
+ export const configConfig = defineConfig({
37
37
  /**
38
38
  * Driver to use for loading configuration
39
39
  * 'env' reads from process.env
@@ -59,4 +59,4 @@ export const configConfig: ConfigModuleOptions = {
59
59
  * So VITE_APP_NAME becomes accessible as APP_NAME
60
60
  */
61
61
  envPrefix: 'auto',
62
- };
62
+ });
package/dist/index.d.mts CHANGED
@@ -433,6 +433,38 @@ interface ViteConfigPluginOptions {
433
433
  globalName?: string;
434
434
  }
435
435
 
436
+ /**
437
+ * Define Config Utility
438
+ *
439
+ * Helper function to define config module options with type safety.
440
+ *
441
+ * @module @abdokouta/config
442
+ */
443
+
444
+ /**
445
+ * Helper function to define config module options with type safety
446
+ *
447
+ * Provides IDE autocomplete and type checking for configuration objects.
448
+ * This pattern is consistent with modern tooling (Vite, Vitest, etc.).
449
+ *
450
+ * @param config - The config module options object
451
+ * @returns The same configuration object with proper typing
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * // config.config.ts
456
+ * import { defineConfig } from '@abdokouta/config';
457
+ *
458
+ * export default defineConfig({
459
+ * driver: 'env',
460
+ * ignoreEnvFile: true,
461
+ * isGlobal: true,
462
+ * envPrefix: 'auto',
463
+ * });
464
+ * ```
465
+ */
466
+ declare function defineConfig(config: ConfigModuleOptions): ConfigModuleOptions;
467
+
436
468
  /**
437
469
  * Get nested value from object using dot notation
438
470
  *
@@ -471,4 +503,4 @@ declare function hasNestedValue(obj: Record<string, any>, path: string): boolean
471
503
  */
472
504
  declare function loadConfigFile(filePath: string): Promise<Record<string, any>>;
473
505
 
474
- export { type ConfigDriver, ConfigModule, type ConfigModuleOptions, ConfigService, type ConfigServiceInterface, EnvDriver, FileDriver, type ViteConfigPluginOptions, getNestedValue, hasNestedValue, loadConfigFile };
506
+ export { type ConfigDriver, ConfigModule, type ConfigModuleOptions, ConfigService, type ConfigServiceInterface, EnvDriver, FileDriver, type ViteConfigPluginOptions, defineConfig, getNestedValue, hasNestedValue, loadConfigFile };
package/dist/index.d.ts CHANGED
@@ -433,6 +433,38 @@ interface ViteConfigPluginOptions {
433
433
  globalName?: string;
434
434
  }
435
435
 
436
+ /**
437
+ * Define Config Utility
438
+ *
439
+ * Helper function to define config module options with type safety.
440
+ *
441
+ * @module @abdokouta/config
442
+ */
443
+
444
+ /**
445
+ * Helper function to define config module options with type safety
446
+ *
447
+ * Provides IDE autocomplete and type checking for configuration objects.
448
+ * This pattern is consistent with modern tooling (Vite, Vitest, etc.).
449
+ *
450
+ * @param config - The config module options object
451
+ * @returns The same configuration object with proper typing
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * // config.config.ts
456
+ * import { defineConfig } from '@abdokouta/config';
457
+ *
458
+ * export default defineConfig({
459
+ * driver: 'env',
460
+ * ignoreEnvFile: true,
461
+ * isGlobal: true,
462
+ * envPrefix: 'auto',
463
+ * });
464
+ * ```
465
+ */
466
+ declare function defineConfig(config: ConfigModuleOptions): ConfigModuleOptions;
467
+
436
468
  /**
437
469
  * Get nested value from object using dot notation
438
470
  *
@@ -471,4 +503,4 @@ declare function hasNestedValue(obj: Record<string, any>, path: string): boolean
471
503
  */
472
504
  declare function loadConfigFile(filePath: string): Promise<Record<string, any>>;
473
505
 
474
- export { type ConfigDriver, ConfigModule, type ConfigModuleOptions, ConfigService, type ConfigServiceInterface, EnvDriver, FileDriver, type ViteConfigPluginOptions, getNestedValue, hasNestedValue, loadConfigFile };
506
+ export { type ConfigDriver, ConfigModule, type ConfigModuleOptions, ConfigService, type ConfigServiceInterface, EnvDriver, FileDriver, type ViteConfigPluginOptions, defineConfig, getNestedValue, hasNestedValue, loadConfigFile };