@onebun/core 0.1.10 → 0.1.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebun/core",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Core package for OneBun framework - decorators, DI, modules, controllers",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "RemRyahirev",
@@ -41,14 +41,15 @@
41
41
  "dependencies": {
42
42
  "effect": "^3.13.10",
43
43
  "arktype": "^2.0.0",
44
- "@onebun/logger": "^0.1.4",
45
- "@onebun/envs": "^0.1.3",
46
- "@onebun/metrics": "^0.1.5",
44
+ "@onebun/logger": "^0.1.5",
45
+ "@onebun/envs": "^0.1.4",
46
+ "@onebun/metrics": "^0.1.6",
47
47
  "@onebun/requests": "^0.1.3",
48
- "@onebun/trace": "^0.1.3"
48
+ "@onebun/trace": "^0.1.4"
49
49
  },
50
50
  "devDependencies": {
51
- "bun-types": "1.2.2"
51
+ "bun-types": "1.2.2",
52
+ "testcontainers": "^11.7.1"
52
53
  },
53
54
  "engines": {
54
55
  "bun": "1.2.2"
@@ -21,6 +21,11 @@ import {
21
21
  import { makeTraceService, TraceService } from '@onebun/trace';
22
22
 
23
23
  import { getControllerMetadata } from '../decorators/decorators';
24
+ import {
25
+ NotInitializedConfig,
26
+ type IConfig,
27
+ type OneBunAppConfig,
28
+ } from '../module/config.interface';
24
29
  import { ConfigServiceImpl } from '../module/config.service';
25
30
  import { OneBunModule } from '../module/module';
26
31
  import { QueueService, type QueueAdapter } from '../queue';
@@ -110,8 +115,7 @@ export class OneBunApplication {
110
115
  development: process.env.NODE_ENV !== 'production',
111
116
  };
112
117
  private logger: SyncLogger;
113
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
- private config: any = null;
118
+ private config: IConfig<OneBunAppConfig>;
115
119
  private configService: ConfigServiceImpl | null = null;
116
120
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
121
  private metricsService: any = null;
@@ -135,10 +139,12 @@ export class OneBunApplication {
135
139
  this.options = { ...this.options, ...options };
136
140
  }
137
141
 
138
- // Initialize configuration if schema is provided
142
+ // Initialize configuration - TypedEnv if schema provided, otherwise NotInitializedConfig
139
143
  if (this.options.envSchema) {
140
144
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
145
  this.config = TypedEnv.create(this.options.envSchema as any, this.options.envOptions);
146
+ } else {
147
+ this.config = new NotInitializedConfig();
142
148
  }
143
149
 
144
150
  // Use provided logger layer or create a default one
@@ -155,8 +161,8 @@ export class OneBunApplication {
155
161
  ) as Logger;
156
162
  this.logger = createSyncLogger(effectLogger);
157
163
 
158
- // Create configuration service if config is available
159
- if (this.config) {
164
+ // Create configuration service if config is initialized
165
+ if (this.config.isInitialized || !(this.config instanceof NotInitializedConfig)) {
160
166
  this.configService = new ConfigServiceImpl(this.logger, this.config);
161
167
  }
162
168
 
@@ -289,8 +295,8 @@ export class OneBunApplication {
289
295
  */
290
296
  async start(): Promise<void> {
291
297
  try {
292
- // Initialize configuration if provided
293
- if (this.config) {
298
+ // Initialize configuration if schema was provided
299
+ if (!(this.config instanceof NotInitializedConfig)) {
294
300
  await this.config.initialize();
295
301
  this.logger.info('Application configuration initialized');
296
302
  }
@@ -80,6 +80,7 @@ import {
80
80
  createWsServiceDefinition,
81
81
  createWsClient,
82
82
  matchPattern,
83
+ makeMockLoggerLayer,
83
84
  } from './';
84
85
 
85
86
  /**
@@ -157,6 +158,7 @@ describe('Minimal Working Example (docs/index.md)', () => {
157
158
  const app = new OneBunApplication(AppModule, {
158
159
  port: 3000,
159
160
  envSchema,
161
+ loggerLayer: makeMockLoggerLayer(),
160
162
  metrics: { enabled: true },
161
163
  tracing: { enabled: true },
162
164
  });
@@ -1302,6 +1304,7 @@ describe('OneBunApplication (docs/api/core.md)', () => {
1302
1304
 
1303
1305
  // From docs: OneBunApplication constructor
1304
1306
  const app = new OneBunApplication(AppModule, {
1307
+ loggerLayer: makeMockLoggerLayer(),
1305
1308
  port: 3000,
1306
1309
  basePath: '/api/v1',
1307
1310
  });
@@ -1330,6 +1333,7 @@ describe('OneBunApplication (docs/api/core.md)', () => {
1330
1333
  basePath: '/api/v1',
1331
1334
  routePrefix: 'myservice',
1332
1335
  development: true,
1336
+ loggerLayer: makeMockLoggerLayer(),
1333
1337
  metrics: {
1334
1338
  enabled: true,
1335
1339
  path: '/metrics',
@@ -1369,7 +1373,7 @@ describe('OneBunApplication (docs/api/core.md)', () => {
1369
1373
  httpDurationBuckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
1370
1374
  };
1371
1375
 
1372
- const app = new OneBunApplication(AppModule, { metrics: metricsOptions });
1376
+ const app = new OneBunApplication(AppModule, { metrics: metricsOptions, loggerLayer: makeMockLoggerLayer() });
1373
1377
  expect(app).toBeDefined();
1374
1378
  });
1375
1379
 
@@ -1400,7 +1404,7 @@ describe('OneBunApplication (docs/api/core.md)', () => {
1400
1404
  };
1401
1405
  /* eslint-enable @typescript-eslint/naming-convention */
1402
1406
 
1403
- const app = new OneBunApplication(AppModule, { tracing: tracingOptions });
1407
+ const app = new OneBunApplication(AppModule, { tracing: tracingOptions, loggerLayer: makeMockLoggerLayer() });
1404
1408
  expect(app).toBeDefined();
1405
1409
  });
1406
1410
  });
@@ -2182,6 +2186,7 @@ describe('Getting Started Documentation (docs/getting-started.md)', () => {
2182
2186
  loadDotEnv: true,
2183
2187
  envFilePath: '.env',
2184
2188
  },
2189
+ loggerLayer: makeMockLoggerLayer(),
2185
2190
  metrics: {
2186
2191
  enabled: true,
2187
2192
  path: '/metrics',
@@ -2589,6 +2594,7 @@ describe('WebSocket Gateway API Documentation (docs/api/websocket.md)', () => {
2589
2594
  pingTimeout: 20000,
2590
2595
  maxPayload: 1048576,
2591
2596
  },
2597
+ loggerLayer: makeMockLoggerLayer(),
2592
2598
  });
2593
2599
 
2594
2600
  expect(app).toBeDefined();
@@ -2865,6 +2871,7 @@ describe('WebSocket Chat Example (docs/examples/websocket-chat.md)', () => {
2865
2871
  pingInterval: 25000,
2866
2872
  pingTimeout: 20000,
2867
2873
  },
2874
+ loggerLayer: makeMockLoggerLayer(),
2868
2875
  });
2869
2876
 
2870
2877
  expect(app).toBeDefined();
package/src/index.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  // Re-export from external packages
2
- export { Env, type EnvSchema, EnvValidationError } from '@onebun/envs';
2
+ export {
3
+ Env,
4
+ type EnvSchema,
5
+ EnvValidationError,
6
+ type InferConfigType,
7
+ type EnvVariableConfig,
8
+ } from '@onebun/envs';
3
9
  export type { SyncLogger } from '@onebun/logger';
4
10
  export {
5
11
  createHttpClient,
@@ -56,6 +62,10 @@ export {
56
62
  // Global modules support
57
63
  clearGlobalServicesRegistry,
58
64
  getGlobalServicesRegistry,
65
+ // Config interface types
66
+ type IConfig,
67
+ type OneBunAppConfig,
68
+ NotInitializedConfig,
59
69
  } from './module';
60
70
 
61
71
  // Application
@@ -0,0 +1,60 @@
1
+ import type { DeepPaths, DeepValue } from '@onebun/envs';
2
+
3
+ /**
4
+ * Empty interface for module augmentation.
5
+ * Users extend this to provide typed config access.
6
+ *
7
+ * @example
8
+ * declare module '@onebun/core' {
9
+ * interface OneBunAppConfig {
10
+ * server: { port: number; host: string };
11
+ * }
12
+ * }
13
+ */
14
+
15
+ export interface OneBunAppConfig {}
16
+
17
+ /**
18
+ * Generic typed configuration interface.
19
+ * Provides type-safe access to configuration values.
20
+ */
21
+ export interface IConfig<T = OneBunAppConfig> {
22
+ /** Get configuration value by dot-notation path with full type inference */
23
+ get<P extends DeepPaths<T>>(path: P): DeepValue<T, P>;
24
+ /** Fallback for dynamic paths */
25
+ get(path: string): unknown;
26
+ /** Get all configuration values */
27
+ readonly values: T;
28
+ /** Get safe configuration for logging (sensitive data masked) */
29
+ getSafeConfig(): T;
30
+ /** Check if configuration is initialized */
31
+ readonly isInitialized: boolean;
32
+ /** Initialize configuration (async) */
33
+ initialize(): Promise<void>;
34
+ }
35
+
36
+ /**
37
+ * Config stub that throws on any access.
38
+ * Used when envSchema is not provided.
39
+ */
40
+ export class NotInitializedConfig implements IConfig<never> {
41
+ get(_path: string): never {
42
+ throw new Error('Configuration not initialized. Provide envSchema in ApplicationOptions.');
43
+ }
44
+
45
+ get values(): never {
46
+ throw new Error('Configuration not initialized. Provide envSchema in ApplicationOptions.');
47
+ }
48
+
49
+ getSafeConfig(): never {
50
+ throw new Error('Configuration not initialized. Provide envSchema in ApplicationOptions.');
51
+ }
52
+
53
+ get isInitialized(): boolean {
54
+ return false;
55
+ }
56
+
57
+ async initialize(): Promise<void> {
58
+ throw new Error('Configuration not initialized. Provide envSchema in ApplicationOptions.');
59
+ }
60
+ }
@@ -11,6 +11,8 @@ import {
11
11
  mock,
12
12
  } from 'bun:test';
13
13
 
14
+ import type { IConfig, OneBunAppConfig } from './config.interface';
15
+
14
16
  import type { SyncLogger } from '@onebun/logger';
15
17
 
16
18
  import {
@@ -22,7 +24,7 @@ import {
22
24
  describe('ConfigService', () => {
23
25
  let mockLogger: SyncLogger;
24
26
 
25
- let mockConfig: any;
27
+ let mockConfig: IConfig<OneBunAppConfig>;
26
28
 
27
29
  beforeEach(() => {
28
30
  mockLogger = {
@@ -38,8 +40,8 @@ describe('ConfigService', () => {
38
40
  mockConfig = {
39
41
  initialize: mock(async () => {}),
40
42
  get: mock((path: string): any => `value-for-${path}`),
41
- values: { test: 'value' },
42
- getSafeConfig: mock(() => ({ test: '***' })),
43
+ values: { test: 'value' } as unknown as OneBunAppConfig,
44
+ getSafeConfig: mock(() => ({ test: '***' })) as unknown as () => OneBunAppConfig,
43
45
  isInitialized: true,
44
46
  };
45
47
  });
@@ -63,7 +65,7 @@ describe('ConfigService', () => {
63
65
  const service = new ConfigServiceImpl(mockLogger);
64
66
 
65
67
  expect(service).toBeInstanceOf(ConfigServiceImpl);
66
- expect(service.instance).toBeUndefined();
68
+ expect(service.instance).toBeNull();
67
69
  });
68
70
 
69
71
  test('should create instance with config but without logger (uninitialized)', () => {
@@ -83,8 +85,8 @@ describe('ConfigService', () => {
83
85
  expect(mockLogger.info).toHaveBeenCalledWith('Configuration initialized successfully');
84
86
  });
85
87
 
86
- test('should not throw when config instance is null', async () => {
87
- const service = new ConfigServiceImpl(mockLogger, null);
88
+ test('should not throw when config instance is undefined', async () => {
89
+ const service = new ConfigServiceImpl(mockLogger, undefined);
88
90
 
89
91
  await expect(service.initialize()).resolves.toBeUndefined();
90
92
  expect(mockLogger.info).not.toHaveBeenCalled();
@@ -109,7 +111,7 @@ describe('ConfigService', () => {
109
111
  });
110
112
 
111
113
  test('should throw error when config not initialized', () => {
112
- const service = new ConfigServiceImpl(mockLogger, null);
114
+ const service = new ConfigServiceImpl(mockLogger, undefined);
113
115
 
114
116
  expect(() => service.get('test.path')).toThrow(
115
117
  'Configuration not initialized. Provide envSchema in ApplicationOptions.',
@@ -117,8 +119,14 @@ describe('ConfigService', () => {
117
119
  });
118
120
 
119
121
  test('should return typed value', () => {
120
- mockConfig.get.mockReturnValue(42);
121
- const service = new ConfigServiceImpl(mockLogger, mockConfig);
122
+ const typedConfig = {
123
+ initialize: mock(async () => {}),
124
+ get: mock((path: string) => path === 'test.number' ? 42 : undefined),
125
+ values: { test: 'value' } as unknown as OneBunAppConfig,
126
+ getSafeConfig: mock(() => ({ test: '***' })) as unknown as () => OneBunAppConfig,
127
+ isInitialized: true,
128
+ } as unknown as IConfig<OneBunAppConfig>;
129
+ const service = new ConfigServiceImpl(mockLogger, typedConfig);
122
130
 
123
131
  const result = service.get<number>('test.number');
124
132
 
@@ -137,7 +145,7 @@ describe('ConfigService', () => {
137
145
  });
138
146
 
139
147
  test('should throw error when config not initialized', () => {
140
- const service = new ConfigServiceImpl(mockLogger, null);
148
+ const service = new ConfigServiceImpl(mockLogger, undefined);
141
149
 
142
150
  expect(() => service.values).toThrow(
143
151
  'Configuration not initialized. Provide envSchema in ApplicationOptions.',
@@ -156,7 +164,7 @@ describe('ConfigService', () => {
156
164
  });
157
165
 
158
166
  test('should throw error when config not initialized', () => {
159
- const service = new ConfigServiceImpl(mockLogger, null);
167
+ const service = new ConfigServiceImpl(mockLogger, undefined);
160
168
 
161
169
  expect(() => service.getSafeConfig()).toThrow(
162
170
  'Configuration not initialized. Provide envSchema in ApplicationOptions.',
@@ -172,14 +180,20 @@ describe('ConfigService', () => {
172
180
  });
173
181
 
174
182
  test('should return false when config not initialized', () => {
175
- mockConfig.isInitialized = false;
176
- const service = new ConfigServiceImpl(mockLogger, mockConfig);
183
+ const uninitConfig = {
184
+ initialize: mock(async () => {}),
185
+ get: mock((path: string): any => `value-for-${path}`),
186
+ values: { test: 'value' } as unknown as OneBunAppConfig,
187
+ getSafeConfig: mock(() => ({ test: '***' })) as unknown as () => OneBunAppConfig,
188
+ isInitialized: false,
189
+ } as unknown as IConfig<OneBunAppConfig>;
190
+ const service = new ConfigServiceImpl(mockLogger, uninitConfig);
177
191
 
178
192
  expect(service.isInitialized).toBe(false);
179
193
  });
180
194
 
181
- test('should return false when config instance is null', () => {
182
- const service = new ConfigServiceImpl(mockLogger, null);
195
+ test('should return false when config instance is undefined', () => {
196
+ const service = new ConfigServiceImpl(mockLogger, undefined);
183
197
 
184
198
  expect(service.isInitialized).toBe(false);
185
199
  });
@@ -193,7 +207,7 @@ describe('ConfigService', () => {
193
207
  });
194
208
 
195
209
  test('should return null when no config provided', () => {
196
- const service = new ConfigServiceImpl(mockLogger, null);
210
+ const service = new ConfigServiceImpl(mockLogger, undefined);
197
211
 
198
212
  expect(service.instance).toBeNull();
199
213
  });
@@ -210,7 +224,7 @@ describe('ConfigService', () => {
210
224
  test('should handle undefined config in constructor', () => {
211
225
  const service = new ConfigServiceImpl(mockLogger, undefined);
212
226
 
213
- expect(service.instance).toBeUndefined();
227
+ expect(service.instance).toBeNull();
214
228
  expect(service.isInitialized).toBe(false);
215
229
  });
216
230
  });
@@ -219,8 +233,11 @@ describe('ConfigService', () => {
219
233
  test('should handle initialization error gracefully', async () => {
220
234
  const errorConfig = {
221
235
  initialize: mock(() => Promise.reject(new Error('Init failed'))),
236
+ get: mock(() => undefined),
237
+ values: {} as unknown as OneBunAppConfig,
238
+ getSafeConfig: mock(() => ({})) as unknown as () => OneBunAppConfig,
222
239
  isInitialized: false,
223
- };
240
+ } as unknown as IConfig<OneBunAppConfig>;
224
241
 
225
242
  const service = new ConfigServiceImpl(mockLogger, errorConfig);
226
243
 
@@ -229,15 +246,16 @@ describe('ConfigService', () => {
229
246
 
230
247
  test('should handle config method errors', () => {
231
248
  const errorConfig = {
249
+ initialize: mock(async () => {}),
232
250
  get: mock(() => {
233
251
  throw new Error('Get failed');
234
252
  }),
235
- values: null,
253
+ values: null as unknown as unknown as OneBunAppConfig,
236
254
  getSafeConfig: mock(() => {
237
255
  throw new Error('Safe config failed');
238
- }),
256
+ }) as unknown as () => OneBunAppConfig,
239
257
  isInitialized: true,
240
- };
258
+ } as unknown as IConfig<OneBunAppConfig>;
241
259
 
242
260
  const service = new ConfigServiceImpl(mockLogger, errorConfig);
243
261
 
@@ -249,11 +267,12 @@ describe('ConfigService', () => {
249
267
  describe('edge cases', () => {
250
268
  test('should handle config with null values', () => {
251
269
  const nullConfig = {
270
+ initialize: mock(async () => {}),
252
271
  get: mock(() => null),
253
- values: null,
254
- getSafeConfig: mock(() => null),
272
+ values: null as unknown as unknown as OneBunAppConfig,
273
+ getSafeConfig: mock(() => null as unknown as unknown as OneBunAppConfig),
255
274
  isInitialized: true,
256
- };
275
+ } as unknown as IConfig<OneBunAppConfig>;
257
276
 
258
277
  const service = new ConfigServiceImpl(mockLogger, nullConfig);
259
278
 
@@ -264,11 +283,12 @@ describe('ConfigService', () => {
264
283
 
265
284
  test('should handle config with undefined values', () => {
266
285
  const undefinedConfig = {
286
+ initialize: mock(async () => {}),
267
287
  get: mock(() => undefined),
268
- values: undefined,
269
- getSafeConfig: mock(() => undefined),
288
+ values: undefined as unknown as unknown as OneBunAppConfig,
289
+ getSafeConfig: mock(() => undefined as unknown as unknown as OneBunAppConfig),
270
290
  isInitialized: false,
271
- };
291
+ } as unknown as IConfig<OneBunAppConfig>;
272
292
 
273
293
  const service = new ConfigServiceImpl(mockLogger, undefinedConfig);
274
294
 
@@ -318,7 +338,7 @@ describe('ConfigService', () => {
318
338
  api: { key: '***', timeout: 30000 },
319
339
  })),
320
340
  isInitialized: true,
321
- };
341
+ } as unknown as IConfig<OneBunAppConfig>;
322
342
 
323
343
  const service = new ConfigServiceImpl(mockLogger, complexConfig);
324
344
 
@@ -1,5 +1,7 @@
1
1
  import { Context } from 'effect';
2
2
 
3
+ import type { IConfig, OneBunAppConfig } from './config.interface';
4
+
3
5
  import type { SyncLogger } from '@onebun/logger';
4
6
 
5
7
  import { BaseService, Service } from './service';
@@ -15,17 +17,16 @@ export const ConfigServiceTag = Context.GenericTag<ConfigServiceImpl>('ConfigSer
15
17
  */
16
18
  @Service(ConfigServiceTag)
17
19
  export class ConfigServiceImpl extends BaseService {
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
- private configInstance: any = null;
20
+ private configInstance: IConfig<OneBunAppConfig> | null = null;
20
21
 
21
- constructor(logger?: SyncLogger, config?: unknown) {
22
+ constructor(logger?: SyncLogger, config?: IConfig<OneBunAppConfig>) {
22
23
  super();
23
24
  // If logger is provided directly (for backwards compatibility in tests),
24
25
  // initialize the service immediately
25
- if (logger) {
26
+ if (logger && config) {
26
27
  this.initializeService(logger, config);
27
28
  }
28
- this.configInstance = config;
29
+ this.configInstance = config ?? null;
29
30
  }
30
31
 
31
32
  /**
@@ -46,7 +47,7 @@ export class ConfigServiceImpl extends BaseService {
46
47
  throw new Error('Configuration not initialized. Provide envSchema in ApplicationOptions.');
47
48
  }
48
49
 
49
- return this.configInstance.get(path);
50
+ return this.configInstance.get(path) as T;
50
51
  }
51
52
 
52
53
  /**
@@ -12,13 +12,17 @@ import {
12
12
  } from 'bun:test';
13
13
  import { Context } from 'effect';
14
14
 
15
+ import type { IConfig, OneBunAppConfig } from './config.interface';
16
+
15
17
  import type { SyncLogger } from '@onebun/logger';
16
18
 
19
+ import { createMockConfig } from '../testing/test-utils';
20
+
17
21
  import { Controller } from './controller';
18
22
 
19
23
  describe('Controller', () => {
20
24
  let mockLogger: SyncLogger;
21
- let mockConfig: unknown;
25
+ let mockConfig: IConfig<OneBunAppConfig>;
22
26
 
23
27
  beforeEach(() => {
24
28
  mockLogger = {
@@ -31,13 +35,11 @@ describe('Controller', () => {
31
35
  child: mock(() => mockLogger),
32
36
  };
33
37
 
34
- mockConfig = {
35
- test: 'value',
36
- database: {
37
- host: 'localhost',
38
- port: 5432,
39
- },
40
- };
38
+ mockConfig = createMockConfig({
39
+ 'test': 'value',
40
+ 'database.host': 'localhost',
41
+ 'database.port': 5432,
42
+ });
41
43
  });
42
44
 
43
45
  describe('Controller initialization', () => {
@@ -139,33 +141,28 @@ describe('Controller', () => {
139
141
  expect(mockLogger.debug).toHaveBeenCalledWith('Controller CustomNamedController initialized');
140
142
  });
141
143
 
142
- test('should work with different config types', () => {
144
+ test('should provide typed config access', () => {
143
145
  class TestController extends Controller {
144
- getConfigType() {
145
- return typeof this.config;
146
+ getConfigValue(path: string) {
147
+ return this.config.get(path);
148
+ }
149
+
150
+ checkConfigInitialized() {
151
+ return this.config.isInitialized;
146
152
  }
147
153
  }
148
154
 
149
155
  const controller = new TestController();
156
+ const typedConfig = createMockConfig({
157
+ 'server.port': 3000,
158
+ 'server.host': '0.0.0.0',
159
+ });
150
160
 
151
- // Test with object config
152
- controller.initializeController(mockLogger, { test: 'value' });
153
- expect(controller.getConfigType()).toBe('object');
161
+ controller.initializeController(mockLogger, typedConfig);
154
162
 
155
- // Test with string config
156
- const controller2 = new TestController();
157
- controller2.initializeController(mockLogger, 'string-config');
158
- expect(controller2.getConfigType()).toBe('string');
159
-
160
- // Test with null config
161
- const controller3 = new TestController();
162
- controller3.initializeController(mockLogger, null);
163
- expect(controller3.getConfigType()).toBe('object'); // null is typeof 'object'
164
-
165
- // Test with undefined config
166
- const controller4 = new TestController();
167
- controller4.initializeController(mockLogger, undefined);
168
- expect(controller4.getConfigType()).toBe('undefined');
163
+ expect(controller.getConfigValue('server.port')).toBe(3000);
164
+ expect(controller.getConfigValue('server.host')).toBe('0.0.0.0');
165
+ expect(controller.checkConfigInitialized()).toBe(true);
169
166
  });
170
167
  });
171
168
 
@@ -252,7 +249,8 @@ describe('Controller', () => {
252
249
  debug: mock(() => {}),
253
250
  };
254
251
 
255
- controller.initializeController(newMockLogger, { newConfig: true });
252
+ const newConfig = createMockConfig({ 'newConfig': true });
253
+ controller.initializeController(newMockLogger, newConfig);
256
254
  expect(newMockLogger.debug).toHaveBeenCalledWith('Controller TestController initialized');
257
255
  });
258
256
  });
@@ -1,3 +1,4 @@
1
+ import type { IConfig, OneBunAppConfig } from './config.interface';
1
2
  import type { Context } from 'effect';
2
3
 
3
4
  import type { SyncLogger } from '@onebun/logger';
@@ -14,13 +15,13 @@ export class Controller {
14
15
  // Logger instance with controller class name as context
15
16
  protected logger!: SyncLogger;
16
17
  // Configuration instance for accessing environment variables
17
- protected config!: unknown;
18
+ protected config!: IConfig<OneBunAppConfig>;
18
19
 
19
20
  /**
20
21
  * Initialize controller with logger and config (called by the framework)
21
22
  * @internal
22
23
  */
23
- initializeController(logger: SyncLogger, config: unknown): void {
24
+ initializeController(logger: SyncLogger, config: IConfig<OneBunAppConfig>): void {
24
25
  const className = this.constructor.name;
25
26
 
26
27
  if (logger) {
@@ -10,3 +10,4 @@ export * from './controller';
10
10
  export { Controller as BaseController } from './controller';
11
11
  export * from './service';
12
12
  export * from './config.service';
13
+ export * from './config.interface';