@onebun/core 0.1.10 → 0.1.12
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 +7 -6
- package/src/application/application.test.ts +92 -0
- package/src/application/application.ts +50 -13
- package/src/docs-examples.test.ts +9 -2
- package/src/index.ts +11 -1
- package/src/module/config.interface.ts +60 -0
- package/src/module/config.service.test.ts +56 -32
- package/src/module/config.service.ts +26 -9
- package/src/module/controller.test.ts +27 -29
- package/src/module/controller.ts +3 -2
- package/src/module/index.ts +1 -0
- package/src/module/module.test.ts +31 -32
- package/src/module/module.ts +9 -5
- package/src/module/service.test.ts +22 -20
- package/src/module/service.ts +5 -3
- package/src/queue/adapters/memory.adapter.test.ts +19 -3
- package/src/queue/adapters/redis.adapter.test.ts +289 -0
- package/src/queue/queue.service.test.ts +240 -0
- package/src/queue/scheduler.test.ts +22 -9
- package/src/redis/shared-redis.test.ts +255 -0
- package/src/testing/test-utils.ts +56 -0
- package/src/websocket/ws-client.test.ts +96 -48
- package/src/websocket/ws-integration.test.ts +21 -19
- package/src/websocket/ws-storage-redis.test.ts +517 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Context } from 'effect';
|
|
2
2
|
|
|
3
|
+
import type { IConfig, OneBunAppConfig } from './config.interface';
|
|
4
|
+
|
|
5
|
+
import type { DeepPaths, DeepValue } from '@onebun/envs';
|
|
3
6
|
import type { SyncLogger } from '@onebun/logger';
|
|
4
7
|
|
|
5
8
|
import { BaseService, Service } from './service';
|
|
@@ -15,17 +18,16 @@ export const ConfigServiceTag = Context.GenericTag<ConfigServiceImpl>('ConfigSer
|
|
|
15
18
|
*/
|
|
16
19
|
@Service(ConfigServiceTag)
|
|
17
20
|
export class ConfigServiceImpl extends BaseService {
|
|
18
|
-
|
|
19
|
-
private configInstance: any = null;
|
|
21
|
+
private configInstance: IConfig<OneBunAppConfig> | null = null;
|
|
20
22
|
|
|
21
|
-
constructor(logger?: SyncLogger, config?:
|
|
23
|
+
constructor(logger?: SyncLogger, config?: IConfig<OneBunAppConfig>) {
|
|
22
24
|
super();
|
|
23
25
|
// If logger is provided directly (for backwards compatibility in tests),
|
|
24
26
|
// initialize the service immediately
|
|
25
|
-
if (logger) {
|
|
27
|
+
if (logger && config) {
|
|
26
28
|
this.initializeService(logger, config);
|
|
27
29
|
}
|
|
28
|
-
this.configInstance = config;
|
|
30
|
+
this.configInstance = config ?? null;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -39,9 +41,24 @@ export class ConfigServiceImpl extends BaseService {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
|
-
* Get configuration value by path
|
|
44
|
+
* Get configuration value by path with full type inference.
|
|
45
|
+
* Uses module augmentation of OneBunAppConfig for type-safe access.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // With module augmentation:
|
|
49
|
+
* declare module '@onebun/core' {
|
|
50
|
+
* interface OneBunAppConfig {
|
|
51
|
+
* server: { port: number; host: string };
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* const port = configService.get('server.port'); // number
|
|
56
|
+
* const host = configService.get('server.host'); // string
|
|
43
57
|
*/
|
|
44
|
-
get<
|
|
58
|
+
get<P extends DeepPaths<OneBunAppConfig>>(path: P): DeepValue<OneBunAppConfig, P>;
|
|
59
|
+
/** Fallback for dynamic paths */
|
|
60
|
+
get<T = unknown>(path: string): T;
|
|
61
|
+
get(path: string): unknown {
|
|
45
62
|
if (!this.configInstance) {
|
|
46
63
|
throw new Error('Configuration not initialized. Provide envSchema in ApplicationOptions.');
|
|
47
64
|
}
|
|
@@ -52,7 +69,7 @@ export class ConfigServiceImpl extends BaseService {
|
|
|
52
69
|
/**
|
|
53
70
|
* Get all configuration values
|
|
54
71
|
*/
|
|
55
|
-
get values():
|
|
72
|
+
get values(): OneBunAppConfig {
|
|
56
73
|
if (!this.configInstance) {
|
|
57
74
|
throw new Error('Configuration not initialized. Provide envSchema in ApplicationOptions.');
|
|
58
75
|
}
|
|
@@ -63,7 +80,7 @@ export class ConfigServiceImpl extends BaseService {
|
|
|
63
80
|
/**
|
|
64
81
|
* Get safe configuration for logging (sensitive data masked)
|
|
65
82
|
*/
|
|
66
|
-
getSafeConfig():
|
|
83
|
+
getSafeConfig(): OneBunAppConfig {
|
|
67
84
|
if (!this.configInstance) {
|
|
68
85
|
throw new Error('Configuration not initialized. Provide envSchema in ApplicationOptions.');
|
|
69
86
|
}
|
|
@@ -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:
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|
144
|
+
test('should provide typed config access', () => {
|
|
143
145
|
class TestController extends Controller {
|
|
144
|
-
|
|
145
|
-
return
|
|
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
|
-
|
|
152
|
-
controller.initializeController(mockLogger, { test: 'value' });
|
|
153
|
-
expect(controller.getConfigType()).toBe('object');
|
|
161
|
+
controller.initializeController(mockLogger, typedConfig);
|
|
154
162
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
252
|
+
const newConfig = createMockConfig({ 'newConfig': true });
|
|
253
|
+
controller.initializeController(newMockLogger, newConfig);
|
|
256
254
|
expect(newMockLogger.debug).toHaveBeenCalledWith('Controller TestController initialized');
|
|
257
255
|
});
|
|
258
256
|
});
|
package/src/module/controller.ts
CHANGED
|
@@ -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!:
|
|
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:
|
|
24
|
+
initializeController(logger: SyncLogger, config: IConfig<OneBunAppConfig>): void {
|
|
24
25
|
const className = this.constructor.name;
|
|
25
26
|
|
|
26
27
|
if (logger) {
|
package/src/module/index.ts
CHANGED
|
@@ -12,18 +12,17 @@ import {
|
|
|
12
12
|
} from 'bun:test';
|
|
13
13
|
import { Context } from 'effect';
|
|
14
14
|
|
|
15
|
-
import { makeDevLogger } from '@onebun/logger';
|
|
16
|
-
|
|
17
15
|
import { Module } from '../decorators/decorators';
|
|
16
|
+
import { makeMockLoggerLayer } from '../testing/test-utils';
|
|
18
17
|
|
|
19
18
|
import { OneBunModule } from './module';
|
|
20
19
|
import { Service } from './service';
|
|
21
20
|
|
|
22
21
|
describe('OneBunModule', () => {
|
|
23
|
-
let
|
|
22
|
+
let mockLoggerLayer: any;
|
|
24
23
|
|
|
25
24
|
beforeEach(() => {
|
|
26
|
-
|
|
25
|
+
mockLoggerLayer = makeMockLoggerLayer();
|
|
27
26
|
});
|
|
28
27
|
|
|
29
28
|
describe('Module initialization', () => {
|
|
@@ -31,7 +30,7 @@ describe('OneBunModule', () => {
|
|
|
31
30
|
@Module({})
|
|
32
31
|
class TestModule {}
|
|
33
32
|
|
|
34
|
-
const module = new OneBunModule(TestModule,
|
|
33
|
+
const module = new OneBunModule(TestModule, mockLoggerLayer);
|
|
35
34
|
expect(module).toBeInstanceOf(OneBunModule);
|
|
36
35
|
expect((module as any).logger).toBeDefined(); // Logger wrapper is created
|
|
37
36
|
expect((module as any).moduleClass).toBe(TestModule);
|
|
@@ -49,7 +48,7 @@ describe('OneBunModule', () => {
|
|
|
49
48
|
test('should handle module without metadata', () => {
|
|
50
49
|
class TestModuleWithoutDecorator {}
|
|
51
50
|
|
|
52
|
-
expect(() => new OneBunModule(TestModuleWithoutDecorator,
|
|
51
|
+
expect(() => new OneBunModule(TestModuleWithoutDecorator, mockLoggerLayer)).toThrow();
|
|
53
52
|
});
|
|
54
53
|
});
|
|
55
54
|
|
|
@@ -58,7 +57,7 @@ describe('OneBunModule', () => {
|
|
|
58
57
|
@Module({})
|
|
59
58
|
class EmptyModule {}
|
|
60
59
|
|
|
61
|
-
const module = new OneBunModule(EmptyModule,
|
|
60
|
+
const module = new OneBunModule(EmptyModule, mockLoggerLayer);
|
|
62
61
|
const layer = module.getLayer();
|
|
63
62
|
expect(layer).toBeDefined();
|
|
64
63
|
});
|
|
@@ -72,7 +71,7 @@ describe('OneBunModule', () => {
|
|
|
72
71
|
})
|
|
73
72
|
class ModuleWithProviders {}
|
|
74
73
|
|
|
75
|
-
const module = new OneBunModule(ModuleWithProviders,
|
|
74
|
+
const module = new OneBunModule(ModuleWithProviders, mockLoggerLayer);
|
|
76
75
|
const layer = module.getLayer();
|
|
77
76
|
expect(layer).toBeDefined();
|
|
78
77
|
});
|
|
@@ -85,7 +84,7 @@ describe('OneBunModule', () => {
|
|
|
85
84
|
})
|
|
86
85
|
class ModuleWithControllers {}
|
|
87
86
|
|
|
88
|
-
const module = new OneBunModule(ModuleWithControllers,
|
|
87
|
+
const module = new OneBunModule(ModuleWithControllers, mockLoggerLayer);
|
|
89
88
|
const layer = module.getLayer();
|
|
90
89
|
expect(layer).toBeDefined();
|
|
91
90
|
});
|
|
@@ -99,7 +98,7 @@ describe('OneBunModule', () => {
|
|
|
99
98
|
})
|
|
100
99
|
class ModuleWithImports {}
|
|
101
100
|
|
|
102
|
-
const module = new OneBunModule(ModuleWithImports,
|
|
101
|
+
const module = new OneBunModule(ModuleWithImports, mockLoggerLayer);
|
|
103
102
|
const layer = module.getLayer();
|
|
104
103
|
expect(layer).toBeDefined();
|
|
105
104
|
});
|
|
@@ -110,7 +109,7 @@ describe('OneBunModule', () => {
|
|
|
110
109
|
@Module({})
|
|
111
110
|
class TestModule {}
|
|
112
111
|
|
|
113
|
-
const module = new OneBunModule(TestModule,
|
|
112
|
+
const module = new OneBunModule(TestModule, mockLoggerLayer);
|
|
114
113
|
const layer = module.getLayer();
|
|
115
114
|
expect(layer).toBeDefined();
|
|
116
115
|
});
|
|
@@ -136,7 +135,7 @@ describe('OneBunModule', () => {
|
|
|
136
135
|
})
|
|
137
136
|
class ComplexModule {}
|
|
138
137
|
|
|
139
|
-
const module = new OneBunModule(ComplexModule,
|
|
138
|
+
const module = new OneBunModule(ComplexModule, mockLoggerLayer);
|
|
140
139
|
const layer = module.getLayer();
|
|
141
140
|
expect(layer).toBeDefined();
|
|
142
141
|
});
|
|
@@ -144,8 +143,8 @@ describe('OneBunModule', () => {
|
|
|
144
143
|
|
|
145
144
|
describe('Error handling', () => {
|
|
146
145
|
test('should handle invalid module class', () => {
|
|
147
|
-
expect(() => new OneBunModule(null as any,
|
|
148
|
-
expect(() => new OneBunModule(undefined as any,
|
|
146
|
+
expect(() => new OneBunModule(null as any, mockLoggerLayer)).toThrow();
|
|
147
|
+
expect(() => new OneBunModule(undefined as any, mockLoggerLayer)).toThrow();
|
|
149
148
|
});
|
|
150
149
|
|
|
151
150
|
test('should handle malformed metadata', () => {
|
|
@@ -153,7 +152,7 @@ describe('OneBunModule', () => {
|
|
|
153
152
|
const TestClass = class {};
|
|
154
153
|
(TestClass as any)[Symbol.for('module:metadata')] = 'invalid';
|
|
155
154
|
|
|
156
|
-
expect(() => new OneBunModule(TestClass,
|
|
155
|
+
expect(() => new OneBunModule(TestClass, mockLoggerLayer)).toThrow();
|
|
157
156
|
});
|
|
158
157
|
});
|
|
159
158
|
|
|
@@ -162,7 +161,7 @@ describe('OneBunModule', () => {
|
|
|
162
161
|
@Module({})
|
|
163
162
|
class NamedModule {}
|
|
164
163
|
|
|
165
|
-
const module = new OneBunModule(NamedModule,
|
|
164
|
+
const module = new OneBunModule(NamedModule, mockLoggerLayer);
|
|
166
165
|
expect((module as any).moduleClass.name).toBe('NamedModule');
|
|
167
166
|
});
|
|
168
167
|
|
|
@@ -170,7 +169,7 @@ describe('OneBunModule', () => {
|
|
|
170
169
|
@Module({})
|
|
171
170
|
class InitModule {}
|
|
172
171
|
|
|
173
|
-
const module = new OneBunModule(InitModule,
|
|
172
|
+
const module = new OneBunModule(InitModule, mockLoggerLayer);
|
|
174
173
|
|
|
175
174
|
// Test that initialization completes without error
|
|
176
175
|
expect(() => module.getLayer()).not.toThrow();
|
|
@@ -191,7 +190,7 @@ describe('OneBunModule', () => {
|
|
|
191
190
|
})
|
|
192
191
|
class ServiceModule {}
|
|
193
192
|
|
|
194
|
-
const module = new OneBunModule(ServiceModule,
|
|
193
|
+
const module = new OneBunModule(ServiceModule, mockLoggerLayer);
|
|
195
194
|
const layer = module.getLayer();
|
|
196
195
|
expect(layer).toBeDefined();
|
|
197
196
|
});
|
|
@@ -208,7 +207,7 @@ describe('OneBunModule', () => {
|
|
|
208
207
|
})
|
|
209
208
|
class ControllerModule {}
|
|
210
209
|
|
|
211
|
-
const module = new OneBunModule(ControllerModule,
|
|
210
|
+
const module = new OneBunModule(ControllerModule, mockLoggerLayer);
|
|
212
211
|
const layer = module.getLayer();
|
|
213
212
|
expect(layer).toBeDefined();
|
|
214
213
|
});
|
|
@@ -227,7 +226,7 @@ describe('OneBunModule', () => {
|
|
|
227
226
|
})
|
|
228
227
|
class TestModule {}
|
|
229
228
|
|
|
230
|
-
const module = new OneBunModule(TestModule,
|
|
229
|
+
const module = new OneBunModule(TestModule, mockLoggerLayer);
|
|
231
230
|
|
|
232
231
|
// Access private method via type assertion
|
|
233
232
|
const instances = (module as any).getControllerInstances();
|
|
@@ -248,7 +247,7 @@ describe('OneBunModule', () => {
|
|
|
248
247
|
})
|
|
249
248
|
class TestModule {}
|
|
250
249
|
|
|
251
|
-
const module = new OneBunModule(TestModule,
|
|
250
|
+
const module = new OneBunModule(TestModule, mockLoggerLayer);
|
|
252
251
|
module.getLayer(); // Initialize the module
|
|
253
252
|
|
|
254
253
|
// Access private method via type assertion
|
|
@@ -262,7 +261,7 @@ describe('OneBunModule', () => {
|
|
|
262
261
|
@Module({})
|
|
263
262
|
class TestModule {}
|
|
264
263
|
|
|
265
|
-
const module = new OneBunModule(TestModule,
|
|
264
|
+
const module = new OneBunModule(TestModule, mockLoggerLayer);
|
|
266
265
|
|
|
267
266
|
// Access private deprecated method via type assertion
|
|
268
267
|
const resolved = (module as any).resolveDependencyByName('SomeService');
|
|
@@ -295,7 +294,7 @@ describe('OneBunModule', () => {
|
|
|
295
294
|
})
|
|
296
295
|
class TagModule {}
|
|
297
296
|
|
|
298
|
-
const module = new OneBunModule(TagModule,
|
|
297
|
+
const module = new OneBunModule(TagModule, mockLoggerLayer);
|
|
299
298
|
const layer = module.getLayer();
|
|
300
299
|
|
|
301
300
|
expect(layer).toBeDefined();
|
|
@@ -321,7 +320,7 @@ describe('OneBunModule', () => {
|
|
|
321
320
|
})
|
|
322
321
|
class TestModule {}
|
|
323
322
|
|
|
324
|
-
const module = new OneBunModule(TestModule,
|
|
323
|
+
const module = new OneBunModule(TestModule, mockLoggerLayer);
|
|
325
324
|
module.getLayer(); // Initialize the module
|
|
326
325
|
|
|
327
326
|
// Try to resolve ServiceB which is not in the module
|
|
@@ -350,7 +349,7 @@ describe('OneBunModule', () => {
|
|
|
350
349
|
})
|
|
351
350
|
class TestModule {}
|
|
352
351
|
|
|
353
|
-
const module = new OneBunModule(TestModule,
|
|
352
|
+
const module = new OneBunModule(TestModule, mockLoggerLayer);
|
|
354
353
|
module.getLayer(); // Initialize the module
|
|
355
354
|
|
|
356
355
|
// Try to resolve by base type
|
|
@@ -400,7 +399,7 @@ describe('OneBunModule', () => {
|
|
|
400
399
|
})
|
|
401
400
|
class RootModule {}
|
|
402
401
|
|
|
403
|
-
const module = new OneBunModule(RootModule,
|
|
402
|
+
const module = new OneBunModule(RootModule, mockLoggerLayer);
|
|
404
403
|
module.getLayer();
|
|
405
404
|
|
|
406
405
|
// Check that global service is registered
|
|
@@ -452,7 +451,7 @@ describe('OneBunModule', () => {
|
|
|
452
451
|
})
|
|
453
452
|
class RootModule {}
|
|
454
453
|
|
|
455
|
-
const module = new OneBunModule(RootModule,
|
|
454
|
+
const module = new OneBunModule(RootModule, mockLoggerLayer);
|
|
456
455
|
module.getLayer();
|
|
457
456
|
|
|
458
457
|
// GlobalDbService should be in the global registry
|
|
@@ -498,7 +497,7 @@ describe('OneBunModule', () => {
|
|
|
498
497
|
})
|
|
499
498
|
class RootModule {}
|
|
500
499
|
|
|
501
|
-
const module = new OneBunModule(RootModule,
|
|
500
|
+
const module = new OneBunModule(RootModule, mockLoggerLayer);
|
|
502
501
|
module.getLayer();
|
|
503
502
|
|
|
504
503
|
// Should have only one SingletonService instance in global registry
|
|
@@ -532,7 +531,7 @@ describe('OneBunModule', () => {
|
|
|
532
531
|
})
|
|
533
532
|
class RootModule {}
|
|
534
533
|
|
|
535
|
-
const module = new OneBunModule(RootModule,
|
|
534
|
+
const module = new OneBunModule(RootModule, mockLoggerLayer);
|
|
536
535
|
module.getLayer();
|
|
537
536
|
|
|
538
537
|
// LocalService should NOT be in the global registry
|
|
@@ -567,7 +566,7 @@ describe('OneBunModule', () => {
|
|
|
567
566
|
})
|
|
568
567
|
class RootModule {}
|
|
569
568
|
|
|
570
|
-
const module = new OneBunModule(RootModule,
|
|
569
|
+
const module = new OneBunModule(RootModule, mockLoggerLayer);
|
|
571
570
|
module.getLayer();
|
|
572
571
|
|
|
573
572
|
// Should have service in registry
|
|
@@ -693,7 +692,7 @@ describe('OneBunModule', () => {
|
|
|
693
692
|
class TestModule {}
|
|
694
693
|
|
|
695
694
|
// Initialize module
|
|
696
|
-
const module = new ModuleInstance(TestModule);
|
|
695
|
+
const module = new ModuleInstance(TestModule, mockLoggerLayer);
|
|
697
696
|
|
|
698
697
|
// Verify services are created correctly
|
|
699
698
|
const { getServiceTag } = require('./service');
|
|
@@ -752,7 +751,7 @@ describe('OneBunModule', () => {
|
|
|
752
751
|
class ChainModule {}
|
|
753
752
|
|
|
754
753
|
// Should not throw
|
|
755
|
-
const module = new ModuleInstance(ChainModule);
|
|
754
|
+
const module = new ModuleInstance(ChainModule, mockLoggerLayer);
|
|
756
755
|
|
|
757
756
|
const { getServiceTag } = require('./service');
|
|
758
757
|
const apiServiceTag = getServiceTag(ApiService);
|
package/src/module/module.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
type SyncLogger,
|
|
16
16
|
} from '@onebun/logger';
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
import {
|
|
20
19
|
autoDetectDependencies,
|
|
21
20
|
getConstructorParamTypes,
|
|
@@ -25,6 +24,11 @@ import {
|
|
|
25
24
|
} from '../decorators/decorators';
|
|
26
25
|
import { isWebSocketGateway } from '../websocket/ws-decorators';
|
|
27
26
|
|
|
27
|
+
import {
|
|
28
|
+
NotInitializedConfig,
|
|
29
|
+
type IConfig,
|
|
30
|
+
type OneBunAppConfig,
|
|
31
|
+
} from './config.interface';
|
|
28
32
|
import { getServiceMetadata, getServiceTag } from './service';
|
|
29
33
|
|
|
30
34
|
/**
|
|
@@ -65,12 +69,12 @@ export class OneBunModule implements ModuleInstance {
|
|
|
65
69
|
private controllerInstances: Map<Function, Controller> = new Map();
|
|
66
70
|
private serviceInstances: Map<Context.Tag<unknown, unknown>, unknown> = new Map();
|
|
67
71
|
private logger: SyncLogger;
|
|
68
|
-
private config:
|
|
72
|
+
private config: IConfig<OneBunAppConfig>;
|
|
69
73
|
|
|
70
74
|
constructor(
|
|
71
75
|
private moduleClass: Function,
|
|
72
76
|
private loggerLayer?: Layer.Layer<never, never, unknown>,
|
|
73
|
-
config?:
|
|
77
|
+
config?: IConfig<OneBunAppConfig>,
|
|
74
78
|
) {
|
|
75
79
|
// Initialize logger with module class name as context
|
|
76
80
|
const effectLogger = Effect.runSync(
|
|
@@ -82,7 +86,7 @@ export class OneBunModule implements ModuleInstance {
|
|
|
82
86
|
) as Effect.Effect<Logger, never, never>,
|
|
83
87
|
) as Logger;
|
|
84
88
|
this.logger = createSyncLogger(effectLogger);
|
|
85
|
-
this.config = config;
|
|
89
|
+
this.config = config ?? new NotInitializedConfig();
|
|
86
90
|
|
|
87
91
|
this.logger.debug(`Initializing OneBunModule for ${moduleClass.name}`);
|
|
88
92
|
const { layer, controllers } = this.initModule();
|
|
@@ -520,7 +524,7 @@ export class OneBunModule implements ModuleInstance {
|
|
|
520
524
|
static create(
|
|
521
525
|
moduleClass: Function,
|
|
522
526
|
loggerLayer?: Layer.Layer<never, never, unknown>,
|
|
523
|
-
config?:
|
|
527
|
+
config?: IConfig<OneBunAppConfig>,
|
|
524
528
|
): ModuleInstance {
|
|
525
529
|
// Using console.log here because we don't have access to the logger instance yet
|
|
526
530
|
// The instance will create its own logger in the constructor
|
|
@@ -10,11 +10,18 @@ import {
|
|
|
10
10
|
} from 'bun:test';
|
|
11
11
|
import { Effect } from 'effect';
|
|
12
12
|
|
|
13
|
+
import { createMockConfig } from '../testing/test-utils';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
NotInitializedConfig,
|
|
17
|
+
type IConfig,
|
|
18
|
+
type OneBunAppConfig,
|
|
19
|
+
} from './config.interface';
|
|
13
20
|
import { BaseService } from './service';
|
|
14
21
|
|
|
15
22
|
describe('BaseService', () => {
|
|
16
23
|
let mockLogger: any;
|
|
17
|
-
let mockConfig:
|
|
24
|
+
let mockConfig: IConfig<OneBunAppConfig>;
|
|
18
25
|
|
|
19
26
|
beforeEach(() => {
|
|
20
27
|
mockLogger = {
|
|
@@ -25,19 +32,12 @@ describe('BaseService', () => {
|
|
|
25
32
|
error: mock(),
|
|
26
33
|
};
|
|
27
34
|
|
|
28
|
-
mockConfig = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return 'test-app';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return undefined;
|
|
38
|
-
}),
|
|
39
|
-
getSafeConfig: mock(() => ({ app: { name: 'test-app' } })),
|
|
40
|
-
};
|
|
35
|
+
mockConfig = createMockConfig({
|
|
36
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
37
|
+
'database.host': 'localhost',
|
|
38
|
+
'app.name': 'test-app',
|
|
39
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
40
|
+
});
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
describe('Initialization via initializeService', () => {
|
|
@@ -56,15 +56,16 @@ describe('BaseService', () => {
|
|
|
56
56
|
expect((service as any).config).toBe(mockConfig);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
test('should initialize service with logger and
|
|
59
|
+
test('should initialize service with logger and NotInitializedConfig', () => {
|
|
60
60
|
class TestService extends BaseService {}
|
|
61
61
|
|
|
62
62
|
const service = new TestService();
|
|
63
|
-
|
|
63
|
+
const notInitConfig = new NotInitializedConfig();
|
|
64
|
+
service.initializeService(mockLogger, notInitConfig);
|
|
64
65
|
|
|
65
66
|
expect(service.isInitialized).toBe(true);
|
|
66
67
|
expect((service as any).logger).toBeDefined();
|
|
67
|
-
expect((service as any).config).
|
|
68
|
+
expect((service as any).config).toBeInstanceOf(NotInitializedConfig);
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
test('should throw error when logger is not provided to initializeService', () => {
|
|
@@ -90,7 +91,8 @@ describe('BaseService', () => {
|
|
|
90
91
|
service.initializeService(mockLogger, mockConfig);
|
|
91
92
|
|
|
92
93
|
const otherLogger = { ...mockLogger, child: mock(() => ({ ...mockLogger })) };
|
|
93
|
-
|
|
94
|
+
const otherConfig = createMockConfig({ 'other': 'config' });
|
|
95
|
+
service.initializeService(otherLogger, otherConfig);
|
|
94
96
|
|
|
95
97
|
// Should still have original logger (no reinit)
|
|
96
98
|
expect((service as any).config).toBe(mockConfig);
|
|
@@ -174,7 +176,7 @@ describe('BaseService', () => {
|
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
const service = new TestService();
|
|
177
|
-
service.initializeService(mockLogger,
|
|
179
|
+
service.initializeService(mockLogger, mockConfig);
|
|
178
180
|
const result = await service.testComplexEffect();
|
|
179
181
|
expect(result).toBe(84);
|
|
180
182
|
});
|
|
@@ -190,7 +192,7 @@ describe('BaseService', () => {
|
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
const service = new TestService();
|
|
193
|
-
service.initializeService(mockLogger,
|
|
195
|
+
service.initializeService(mockLogger, mockConfig);
|
|
194
196
|
service.testStackTrace();
|
|
195
197
|
});
|
|
196
198
|
});
|
package/src/module/service.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
Layer,
|
|
5
5
|
} from 'effect';
|
|
6
6
|
|
|
7
|
+
import type { IConfig, OneBunAppConfig } from './config.interface';
|
|
8
|
+
|
|
7
9
|
import type { SyncLogger } from '@onebun/logger';
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -66,7 +68,7 @@ export class BaseService {
|
|
|
66
68
|
// Logger instance with service class name as context
|
|
67
69
|
protected logger!: SyncLogger;
|
|
68
70
|
// Configuration instance for accessing environment variables
|
|
69
|
-
protected config!:
|
|
71
|
+
protected config!: IConfig<OneBunAppConfig>;
|
|
70
72
|
// Flag to track initialization status
|
|
71
73
|
private _initialized = false;
|
|
72
74
|
|
|
@@ -74,7 +76,7 @@ export class BaseService {
|
|
|
74
76
|
* Initialize service with logger and config (called by the framework)
|
|
75
77
|
* @internal
|
|
76
78
|
*/
|
|
77
|
-
initializeService(logger: SyncLogger, config:
|
|
79
|
+
initializeService(logger: SyncLogger, config: IConfig<OneBunAppConfig>): void {
|
|
78
80
|
if (this._initialized) {
|
|
79
81
|
return; // Already initialized
|
|
80
82
|
}
|
|
@@ -143,7 +145,7 @@ export class BaseService {
|
|
|
143
145
|
export function createServiceLayer<T>(
|
|
144
146
|
serviceClass: new (...args: unknown[]) => T,
|
|
145
147
|
logger?: SyncLogger,
|
|
146
|
-
config?:
|
|
148
|
+
config?: IConfig<OneBunAppConfig>,
|
|
147
149
|
): Layer.Layer<never, never, unknown> {
|
|
148
150
|
const metadata = getServiceMetadata(serviceClass);
|
|
149
151
|
if (!metadata) {
|