@onebun/core 0.1.9 → 0.1.10

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.10",
4
4
  "description": "Core package for OneBun framework - decorators, DI, modules, controllers",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "RemRyahirev",
@@ -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);
@@ -1974,17 +1974,19 @@ describe('Architecture Documentation (docs/architecture.md)', () => {
1974
1974
  });
1975
1975
 
1976
1976
  /**
1977
- * @source docs/architecture.md#explicit-injection
1977
+ * @source docs/architecture.md#automatic-injection
1978
1978
  */
1979
- it('should demonstrate explicit injection pattern', () => {
1980
- // From docs: Explicit Injection example
1979
+ it('should demonstrate automatic DI without @Inject', () => {
1980
+ // From docs: Automatic DI example
1981
+ // TypeScript's emitDecoratorMetadata provides type info automatically
1981
1982
  @Service()
1982
1983
  class UserService extends BaseService {}
1983
1984
 
1984
1985
  @Service()
1985
1986
  class CacheService extends BaseService {}
1986
1987
 
1987
- // For complex cases, use @Inject() - here we just verify pattern works
1988
+ // No @Inject needed - automatic DI works via emitDecoratorMetadata
1989
+ // @Inject is only needed for: interfaces, abstract classes, token-based injection
1988
1990
  @Controller('/users')
1989
1991
  class UserController extends BaseController {
1990
1992
  constructor(