@onebun/core 0.1.9 → 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.9",
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
  }
@@ -13,7 +13,13 @@ import {
13
13
  afterEach,
14
14
  } from 'bun:test';
15
15
 
16
- import { Service } from '../module';
16
+ import { OneBunApplication } from '../application';
17
+ import {
18
+ BaseController,
19
+ BaseService,
20
+ Service,
21
+ } from '../module';
22
+ import { makeMockLoggerLayer } from '../testing';
17
23
  import { HttpMethod, ParamType } from '../types';
18
24
 
19
25
  import {
@@ -809,6 +815,53 @@ describe('decorators', () => {
809
815
  const deps = getConstructorParamTypes(malformedClass);
810
816
  expect(deps).toBeUndefined();
811
817
  });
818
+
819
+ test('should properly inject module service into controller without @Inject', async () => {
820
+ @Service()
821
+ class TestService extends BaseService {
822
+ getValue() {
823
+ return 'injected-value';
824
+ }
825
+ }
826
+
827
+ // No @Inject needed - automatic DI via emitDecoratorMetadata
828
+ @Controller('')
829
+ class TestController extends BaseController {
830
+ constructor(private service: TestService) {
831
+ super();
832
+ }
833
+
834
+ getServiceValue() {
835
+ return this.service.getValue();
836
+ }
837
+ }
838
+
839
+ @Module({
840
+ controllers: [TestController],
841
+ providers: [TestService],
842
+ })
843
+ class TestModule {}
844
+
845
+ const app = new OneBunApplication(TestModule, {
846
+ loggerLayer: makeMockLoggerLayer(),
847
+ });
848
+
849
+ // Access rootModule to setup and verify DI
850
+ const rootModule = (app as any).rootModule;
851
+
852
+ // Setup module to trigger dependency injection
853
+ const { Effect } = await import('effect');
854
+ await Effect.runPromise(rootModule.setup());
855
+
856
+ // Get controller instance and verify service was injected
857
+ const controllerInstance = rootModule.getControllerInstance(TestController) as TestController;
858
+
859
+ expect(controllerInstance).toBeDefined();
860
+ expect(controllerInstance).toBeInstanceOf(TestController);
861
+
862
+ // Verify the injected service works correctly
863
+ expect(controllerInstance.getServiceValue()).toBe('injected-value');
864
+ });
812
865
  });
813
866
 
814
867
  describe('ApiResponse decorator', () => {
@@ -111,9 +111,17 @@ export function registerControllerDependencies(
111
111
 
112
112
  /**
113
113
  * Get constructor parameter types (automatically detected or explicitly set)
114
+ * Priority: 1) Explicit @Inject registrations, 2) TypeScript's design:paramtypes
114
115
  */
115
116
  export function getConstructorParamTypes(target: Function): Function[] | undefined {
116
- return META_CONSTRUCTOR_PARAMS.get(target);
117
+ // First check explicit @Inject registrations
118
+ const explicitDeps = META_CONSTRUCTOR_PARAMS.get(target);
119
+ if (explicitDeps && explicitDeps.length > 0) {
120
+ return explicitDeps;
121
+ }
122
+
123
+ // Fallback to TypeScript's design:paramtypes (automatic DI)
124
+ return getDesignParamTypes(target);
117
125
  }
118
126
 
119
127
  /**
@@ -174,6 +182,15 @@ export function controllerDecorator(basePath: string = '') {
174
182
  // Mark controller as injectable automatically
175
183
  injectable()(WrappedController);
176
184
 
185
+ // Copy design:paramtypes from original class to wrapped class
186
+ // This enables automatic DI without @Inject when emitDecoratorMetadata is enabled
187
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
188
+ const designParamTypes = (globalThis as any).Reflect?.getMetadata?.('design:paramtypes', target);
189
+ if (designParamTypes) {
190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
+ (globalThis as any).Reflect?.defineMetadata?.('design:paramtypes', designParamTypes, WrappedController);
192
+ }
193
+
177
194
  // Copy constructor params metadata from original class to wrapped class
178
195
  // This is needed for @Inject decorator to work correctly with @Controller wrapping
179
196
  const existingDeps = META_CONSTRUCTOR_PARAMS.get(target);
@@ -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
  });
@@ -1974,17 +1978,19 @@ describe('Architecture Documentation (docs/architecture.md)', () => {
1974
1978
  });
1975
1979
 
1976
1980
  /**
1977
- * @source docs/architecture.md#explicit-injection
1981
+ * @source docs/architecture.md#automatic-injection
1978
1982
  */
1979
- it('should demonstrate explicit injection pattern', () => {
1980
- // From docs: Explicit Injection example
1983
+ it('should demonstrate automatic DI without @Inject', () => {
1984
+ // From docs: Automatic DI example
1985
+ // TypeScript's emitDecoratorMetadata provides type info automatically
1981
1986
  @Service()
1982
1987
  class UserService extends BaseService {}
1983
1988
 
1984
1989
  @Service()
1985
1990
  class CacheService extends BaseService {}
1986
1991
 
1987
- // For complex cases, use @Inject() - here we just verify pattern works
1992
+ // No @Inject needed - automatic DI works via emitDecoratorMetadata
1993
+ // @Inject is only needed for: interfaces, abstract classes, token-based injection
1988
1994
  @Controller('/users')
1989
1995
  class UserController extends BaseController {
1990
1996
  constructor(
@@ -2180,6 +2186,7 @@ describe('Getting Started Documentation (docs/getting-started.md)', () => {
2180
2186
  loadDotEnv: true,
2181
2187
  envFilePath: '.env',
2182
2188
  },
2189
+ loggerLayer: makeMockLoggerLayer(),
2183
2190
  metrics: {
2184
2191
  enabled: true,
2185
2192
  path: '/metrics',
@@ -2587,6 +2594,7 @@ describe('WebSocket Gateway API Documentation (docs/api/websocket.md)', () => {
2587
2594
  pingTimeout: 20000,
2588
2595
  maxPayload: 1048576,
2589
2596
  },
2597
+ loggerLayer: makeMockLoggerLayer(),
2590
2598
  });
2591
2599
 
2592
2600
  expect(app).toBeDefined();
@@ -2863,6 +2871,7 @@ describe('WebSocket Chat Example (docs/examples/websocket-chat.md)', () => {
2863
2871
  pingInterval: 25000,
2864
2872
  pingTimeout: 20000,
2865
2873
  },
2874
+ loggerLayer: makeMockLoggerLayer(),
2866
2875
  });
2867
2876
 
2868
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
  /**