@hazeljs/core 0.2.0-beta.58 → 0.2.0-beta.60

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 +1 @@
1
- {"version":3,"file":"decorators.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"AAmBA,OAAO,kBAAkB,CAAC"}
1
+ {"version":3,"file":"decorators.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"AAyBA,OAAO,kBAAkB,CAAC"}
@@ -690,4 +690,272 @@ describe('Decorators', () => {
690
690
  expect(routes[0].method).toBe('POST');
691
691
  });
692
692
  });
693
+ // -------------------------------------------------------------------------
694
+ // @Req() parameter decorator
695
+ // -------------------------------------------------------------------------
696
+ describe('Req', () => {
697
+ it('should register request injection type', () => {
698
+ class TestController {
699
+ handleRequest(req) {
700
+ return req;
701
+ }
702
+ }
703
+ __decorate([
704
+ __param(0, (0, decorators_1.Req)()),
705
+ __metadata("design:type", Function),
706
+ __metadata("design:paramtypes", [Object]),
707
+ __metadata("design:returntype", void 0)
708
+ ], TestController.prototype, "handleRequest", null);
709
+ const injections = Reflect.getMetadata('hazel:inject', TestController, 'handleRequest');
710
+ expect(injections[0].type).toBe('request');
711
+ });
712
+ it('should throw error when used outside a method parameter', () => {
713
+ expect(() => {
714
+ const decorator = (0, decorators_1.Req)();
715
+ decorator({}, undefined, 0);
716
+ }).toThrow('Req decorator must be used on a method parameter');
717
+ });
718
+ });
719
+ // -------------------------------------------------------------------------
720
+ // @Headers() parameter decorator
721
+ // -------------------------------------------------------------------------
722
+ describe('Headers', () => {
723
+ it('should register headers injection with a specific header name', () => {
724
+ class TestController {
725
+ getAuth(auth) {
726
+ return auth;
727
+ }
728
+ }
729
+ __decorate([
730
+ __param(0, (0, decorators_1.Headers)('authorization')),
731
+ __metadata("design:type", Function),
732
+ __metadata("design:paramtypes", [String]),
733
+ __metadata("design:returntype", void 0)
734
+ ], TestController.prototype, "getAuth", null);
735
+ const injections = Reflect.getMetadata('hazel:inject', TestController, 'getAuth');
736
+ expect(injections[0].type).toBe('headers');
737
+ expect(injections[0].name).toBe('authorization');
738
+ });
739
+ it('should register headers injection without a name (all headers)', () => {
740
+ class TestController {
741
+ getAllHeaders(headers) {
742
+ return headers;
743
+ }
744
+ }
745
+ __decorate([
746
+ __param(0, (0, decorators_1.Headers)()),
747
+ __metadata("design:type", Function),
748
+ __metadata("design:paramtypes", [Object]),
749
+ __metadata("design:returntype", void 0)
750
+ ], TestController.prototype, "getAllHeaders", null);
751
+ const injections = Reflect.getMetadata('hazel:inject', TestController, 'getAllHeaders');
752
+ expect(injections[0].type).toBe('headers');
753
+ expect(injections[0].name).toBeUndefined();
754
+ });
755
+ it('should throw error when used outside a method parameter', () => {
756
+ expect(() => {
757
+ const decorator = (0, decorators_1.Headers)();
758
+ decorator({}, undefined, 0);
759
+ }).toThrow('Headers decorator must be used on a method parameter');
760
+ });
761
+ });
762
+ // -------------------------------------------------------------------------
763
+ // @HttpCode() method decorator
764
+ // -------------------------------------------------------------------------
765
+ describe('HttpCode', () => {
766
+ it('should store the status code in metadata', () => {
767
+ class TestController {
768
+ create() {
769
+ return { id: 1 };
770
+ }
771
+ }
772
+ __decorate([
773
+ (0, decorators_1.HttpCode)(201),
774
+ __metadata("design:type", Function),
775
+ __metadata("design:paramtypes", []),
776
+ __metadata("design:returntype", void 0)
777
+ ], TestController.prototype, "create", null);
778
+ const code = Reflect.getMetadata('hazel:http-code', TestController.prototype, 'create');
779
+ expect(code).toBe(201);
780
+ });
781
+ it('should allow 204 for no-content responses', () => {
782
+ class TestController {
783
+ remove() {
784
+ return undefined;
785
+ }
786
+ }
787
+ __decorate([
788
+ (0, decorators_1.HttpCode)(204),
789
+ __metadata("design:type", Function),
790
+ __metadata("design:paramtypes", []),
791
+ __metadata("design:returntype", void 0)
792
+ ], TestController.prototype, "remove", null);
793
+ const code = Reflect.getMetadata('hazel:http-code', TestController.prototype, 'remove');
794
+ expect(code).toBe(204);
795
+ });
796
+ });
797
+ // -------------------------------------------------------------------------
798
+ // @Header() method decorator (custom response headers)
799
+ // -------------------------------------------------------------------------
800
+ describe('Header', () => {
801
+ it('should store a single custom response header', () => {
802
+ class TestController {
803
+ getVersion() {
804
+ return {};
805
+ }
806
+ }
807
+ __decorate([
808
+ (0, decorators_1.Header)('X-Version', '1.0'),
809
+ __metadata("design:type", Function),
810
+ __metadata("design:paramtypes", []),
811
+ __metadata("design:returntype", void 0)
812
+ ], TestController.prototype, "getVersion", null);
813
+ const headers = Reflect.getMetadata('hazel:headers', TestController.prototype, 'getVersion');
814
+ expect(headers).toHaveLength(1);
815
+ expect(headers[0]).toEqual({ name: 'X-Version', value: '1.0' });
816
+ });
817
+ it('should accumulate multiple @Header decorators on the same method', () => {
818
+ class TestController {
819
+ getData() {
820
+ return {};
821
+ }
822
+ }
823
+ __decorate([
824
+ (0, decorators_1.Header)('X-First', 'a'),
825
+ (0, decorators_1.Header)('X-Second', 'b'),
826
+ __metadata("design:type", Function),
827
+ __metadata("design:paramtypes", []),
828
+ __metadata("design:returntype", void 0)
829
+ ], TestController.prototype, "getData", null);
830
+ const headers = Reflect.getMetadata('hazel:headers', TestController.prototype, 'getData');
831
+ expect(headers).toHaveLength(2);
832
+ expect(headers.map((h) => h.name)).toContain('X-First');
833
+ expect(headers.map((h) => h.name)).toContain('X-Second');
834
+ });
835
+ });
836
+ // -------------------------------------------------------------------------
837
+ // @Redirect() method decorator
838
+ // -------------------------------------------------------------------------
839
+ describe('Redirect', () => {
840
+ it('should store redirect url with default 302 status', () => {
841
+ class TestController {
842
+ goSomewhere() {
843
+ return undefined;
844
+ }
845
+ }
846
+ __decorate([
847
+ (0, decorators_1.Redirect)('/new-location'),
848
+ __metadata("design:type", Function),
849
+ __metadata("design:paramtypes", []),
850
+ __metadata("design:returntype", void 0)
851
+ ], TestController.prototype, "goSomewhere", null);
852
+ const meta = Reflect.getMetadata('hazel:redirect', TestController.prototype, 'goSomewhere');
853
+ expect(meta).toEqual({ url: '/new-location', statusCode: 302 });
854
+ });
855
+ it('should store redirect url with custom status code', () => {
856
+ class TestController {
857
+ movedPermanently() {
858
+ return undefined;
859
+ }
860
+ }
861
+ __decorate([
862
+ (0, decorators_1.Redirect)('/permanent', 301),
863
+ __metadata("design:type", Function),
864
+ __metadata("design:paramtypes", []),
865
+ __metadata("design:returntype", void 0)
866
+ ], TestController.prototype, "movedPermanently", null);
867
+ const meta = Reflect.getMetadata('hazel:redirect', TestController.prototype, 'movedPermanently');
868
+ expect(meta).toEqual({ url: '/permanent', statusCode: 301 });
869
+ });
870
+ });
871
+ // -------------------------------------------------------------------------
872
+ // @AITask() method decorator
873
+ // -------------------------------------------------------------------------
874
+ describe('AITask', () => {
875
+ it('should store AI task options in metadata', () => {
876
+ const options = {
877
+ name: 'summarise',
878
+ prompt: 'Summarise the following text: {{input}}',
879
+ provider: 'openai',
880
+ model: 'gpt-4o',
881
+ outputType: 'string',
882
+ };
883
+ class TestController {
884
+ summarise() {
885
+ return undefined;
886
+ }
887
+ }
888
+ __decorate([
889
+ (0, decorators_1.AITask)(options),
890
+ __metadata("design:type", Function),
891
+ __metadata("design:paramtypes", []),
892
+ __metadata("design:returntype", void 0)
893
+ ], TestController.prototype, "summarise", null);
894
+ const meta = Reflect.getMetadata('hazel:ai-task', TestController.prototype, 'summarise');
895
+ expect(meta).toEqual(options);
896
+ });
897
+ });
898
+ // -------------------------------------------------------------------------
899
+ // @UsePipes() class-level metadata verification
900
+ // -------------------------------------------------------------------------
901
+ describe('UsePipes class-level metadata', () => {
902
+ it('should store pipe metadata on the class itself when used as class decorator', () => {
903
+ class TrimPipe {
904
+ transform(value) {
905
+ return value.trim();
906
+ }
907
+ }
908
+ let TestController = class TestController {
909
+ };
910
+ TestController = __decorate([
911
+ (0, decorators_1.UsePipes)(TrimPipe)
912
+ ], TestController);
913
+ const pipeMeta = Reflect.getMetadata('hazel:pipe', TestController);
914
+ expect(pipeMeta).toBeDefined();
915
+ expect(Array.isArray(pipeMeta)).toBe(true);
916
+ expect(pipeMeta.length).toBeGreaterThan(0);
917
+ });
918
+ });
919
+ // -------------------------------------------------------------------------
920
+ // @UseGuards() class-level metadata verification
921
+ // -------------------------------------------------------------------------
922
+ describe('UseGuards class-level metadata', () => {
923
+ it('should store guard metadata on the class when used as class decorator', () => {
924
+ class AuthGuard {
925
+ canActivate() {
926
+ return true;
927
+ }
928
+ }
929
+ let TestController = class TestController {
930
+ };
931
+ TestController = __decorate([
932
+ (0, decorators_1.UseGuards)(AuthGuard)
933
+ ], TestController);
934
+ const guardMeta = Reflect.getMetadata('hazel:guards', TestController);
935
+ expect(guardMeta).toBeDefined();
936
+ expect(guardMeta).toContain(AuthGuard);
937
+ });
938
+ it('should store guard metadata on the method when used as method decorator', () => {
939
+ class RoleGuard {
940
+ canActivate() {
941
+ return true;
942
+ }
943
+ }
944
+ class TestController {
945
+ getProtected() {
946
+ return {};
947
+ }
948
+ }
949
+ __decorate([
950
+ (0, decorators_1.UseGuards)(RoleGuard),
951
+ (0, decorators_1.Get)('/protected'),
952
+ __metadata("design:type", Function),
953
+ __metadata("design:paramtypes", []),
954
+ __metadata("design:returntype", void 0)
955
+ ], TestController.prototype, "getProtected", null);
956
+ const guardMeta = Reflect.getMetadata('hazel:guards', TestController.prototype, 'getProtected');
957
+ expect(guardMeta).toBeDefined();
958
+ expect(guardMeta).toContain(RoleGuard);
959
+ });
960
+ });
693
961
  });
@@ -679,4 +679,132 @@ describe('HazelApp', () => {
679
679
  expect(app).toBeDefined();
680
680
  });
681
681
  });
682
+ // -------------------------------------------------------------------------
683
+ // getRouter
684
+ // -------------------------------------------------------------------------
685
+ describe('getRouter', () => {
686
+ let app;
687
+ beforeEach(() => {
688
+ class AppModule {
689
+ }
690
+ Reflect.defineMetadata('hazel:module', {}, AppModule);
691
+ app = new hazel_app_1.HazelApp(AppModule);
692
+ });
693
+ it('should return the internal Router instance', () => {
694
+ const router = app.getRouter();
695
+ expect(router).toBeDefined();
696
+ expect(typeof router.registerController).toBe('function');
697
+ });
698
+ });
699
+ // -------------------------------------------------------------------------
700
+ // registerShutdownHandler
701
+ // -------------------------------------------------------------------------
702
+ describe('registerShutdownHandler', () => {
703
+ let app;
704
+ beforeEach(() => {
705
+ class AppModule {
706
+ }
707
+ Reflect.defineMetadata('hazel:module', {}, AppModule);
708
+ app = new hazel_app_1.HazelApp(AppModule);
709
+ });
710
+ it('should register a shutdown handler without throwing', () => {
711
+ expect(() => {
712
+ app.registerShutdownHandler({
713
+ name: 'db',
714
+ handler: async () => { },
715
+ });
716
+ }).not.toThrow();
717
+ });
718
+ it('should register a shutdown handler with a timeout', () => {
719
+ expect(() => {
720
+ app.registerShutdownHandler({
721
+ name: 'cache',
722
+ handler: async () => { },
723
+ timeout: 5000,
724
+ });
725
+ }).not.toThrow();
726
+ });
727
+ });
728
+ // -------------------------------------------------------------------------
729
+ // registerHealthCheck
730
+ // -------------------------------------------------------------------------
731
+ describe('registerHealthCheck', () => {
732
+ let app;
733
+ beforeEach(() => {
734
+ class AppModule {
735
+ }
736
+ Reflect.defineMetadata('hazel:module', {}, AppModule);
737
+ app = new hazel_app_1.HazelApp(AppModule);
738
+ });
739
+ it('should register a health check without throwing', () => {
740
+ expect(() => {
741
+ app.registerHealthCheck({
742
+ name: 'db',
743
+ check: async () => ({ status: 'healthy' }),
744
+ });
745
+ }).not.toThrow();
746
+ });
747
+ it('should register a critical health check with a timeout', () => {
748
+ expect(() => {
749
+ app.registerHealthCheck({
750
+ name: 'redis',
751
+ check: async () => ({ status: 'healthy', message: 'connected' }),
752
+ critical: true,
753
+ timeout: 3000,
754
+ });
755
+ }).not.toThrow();
756
+ });
757
+ });
758
+ // -------------------------------------------------------------------------
759
+ // addEarlyHandler
760
+ // -------------------------------------------------------------------------
761
+ describe('addEarlyHandler', () => {
762
+ let app;
763
+ beforeEach(() => {
764
+ class AppModule {
765
+ }
766
+ Reflect.defineMetadata('hazel:module', {}, AppModule);
767
+ app = new hazel_app_1.HazelApp(AppModule);
768
+ });
769
+ it('should register an early handler without throwing', () => {
770
+ const handler = jest.fn().mockResolvedValue(false);
771
+ expect(() => {
772
+ app.addEarlyHandler('/internal', handler);
773
+ }).not.toThrow();
774
+ });
775
+ it('should register multiple early handlers', () => {
776
+ const h1 = jest.fn().mockResolvedValue(false);
777
+ const h2 = jest.fn().mockResolvedValue(false);
778
+ expect(() => {
779
+ app.addEarlyHandler('/a', h1);
780
+ app.addEarlyHandler('/b', h2);
781
+ }).not.toThrow();
782
+ });
783
+ });
784
+ // -------------------------------------------------------------------------
785
+ // addProxyHandler
786
+ // -------------------------------------------------------------------------
787
+ describe('addProxyHandler', () => {
788
+ let app;
789
+ beforeEach(() => {
790
+ class AppModule {
791
+ }
792
+ Reflect.defineMetadata('hazel:module', {}, AppModule);
793
+ app = new hazel_app_1.HazelApp(AppModule);
794
+ });
795
+ it('should register a proxy handler without throwing', () => {
796
+ const handler = jest.fn().mockResolvedValue(false);
797
+ expect(() => {
798
+ app.addProxyHandler('/api', handler);
799
+ }).not.toThrow();
800
+ });
801
+ it('should register multiple proxy handlers for different prefixes', () => {
802
+ const h1 = jest.fn().mockResolvedValue(false);
803
+ const h2 = jest.fn().mockResolvedValue(false);
804
+ expect(() => {
805
+ app.addProxyHandler('/service-a', h1);
806
+ app.addProxyHandler('/service-b', h2);
807
+ }).not.toThrow();
808
+ });
809
+ });
682
810
  });
@@ -676,5 +676,508 @@ describe('Router', () => {
676
676
  }));
677
677
  }
678
678
  });
679
+ // -----------------------------------------------------------------------
680
+ // Parameter injection — extended types
681
+ // -----------------------------------------------------------------------
682
+ it('should inject named header via { type: "headers", name }', async () => {
683
+ class TestController {
684
+ getHeader(authHeader) {
685
+ return { auth: authHeader };
686
+ }
687
+ }
688
+ container.register(TestController, new TestController());
689
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
690
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/header-named', propertyKey: 'getHeader' }], TestController);
691
+ Reflect.defineMetadata('hazel:inject', [{ type: 'headers', name: 'authorization' }], TestController, 'getHeader');
692
+ router.registerController(TestController);
693
+ const context = {
694
+ params: {},
695
+ query: {},
696
+ body: {},
697
+ headers: { authorization: 'Bearer token123' },
698
+ method: 'GET',
699
+ url: '/test/header-named',
700
+ };
701
+ const route = await router.match('GET', '/test/header-named', context);
702
+ expect(route).toBeDefined();
703
+ if (route) {
704
+ await route.handler(mockReq, mockRes, context);
705
+ expect(mockRes.json).toHaveBeenCalledWith({ auth: 'Bearer token123' });
706
+ }
707
+ });
708
+ it('should inject all headers when { type: "headers" } has no name', async () => {
709
+ class TestController {
710
+ getAllHeaders(headers) {
711
+ return headers;
712
+ }
713
+ }
714
+ container.register(TestController, new TestController());
715
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
716
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/headers-all', propertyKey: 'getAllHeaders' }], TestController);
717
+ Reflect.defineMetadata('hazel:inject', [{ type: 'headers' }], TestController, 'getAllHeaders');
718
+ router.registerController(TestController);
719
+ const context = {
720
+ params: {},
721
+ query: {},
722
+ body: {},
723
+ headers: { 'x-custom': 'value' },
724
+ method: 'GET',
725
+ url: '/test/headers-all',
726
+ };
727
+ const route = await router.match('GET', '/test/headers-all', context);
728
+ expect(route).toBeDefined();
729
+ if (route) {
730
+ await route.handler(mockReq, mockRes, context);
731
+ expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({ 'x-custom': 'value' }));
732
+ }
733
+ });
734
+ it('should inject user object via { type: "user" } without field', async () => {
735
+ class TestController {
736
+ whoAmI(user) {
737
+ return user;
738
+ }
739
+ }
740
+ container.register(TestController, new TestController());
741
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
742
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/me', propertyKey: 'whoAmI' }], TestController);
743
+ Reflect.defineMetadata('hazel:inject', [{ type: 'user' }], TestController, 'whoAmI');
744
+ router.registerController(TestController);
745
+ const context = {
746
+ params: {},
747
+ query: {},
748
+ body: {},
749
+ headers: {},
750
+ method: 'GET',
751
+ url: '/test/me',
752
+ };
753
+ const reqWithUser = { ...mockReq, user: { sub: 'u1', role: 'admin' } };
754
+ const route = await router.match('GET', '/test/me', context);
755
+ expect(route).toBeDefined();
756
+ if (route) {
757
+ await route.handler(reqWithUser, mockRes, context);
758
+ expect(mockRes.json).toHaveBeenCalledWith({ sub: 'u1', role: 'admin' });
759
+ }
760
+ });
761
+ it('should inject specific user field via { type: "user", field }', async () => {
762
+ class TestController {
763
+ getRole(role) {
764
+ return { role };
765
+ }
766
+ }
767
+ container.register(TestController, new TestController());
768
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
769
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/role', propertyKey: 'getRole' }], TestController);
770
+ Reflect.defineMetadata('hazel:inject', [{ type: 'user', field: 'role' }], TestController, 'getRole');
771
+ router.registerController(TestController);
772
+ const context = {
773
+ params: {},
774
+ query: {},
775
+ body: {},
776
+ headers: {},
777
+ method: 'GET',
778
+ url: '/test/role',
779
+ };
780
+ const reqWithUser = { ...mockReq, user: { sub: 'u1', role: 'manager' } };
781
+ const route = await router.match('GET', '/test/role', context);
782
+ expect(route).toBeDefined();
783
+ if (route) {
784
+ await route.handler(reqWithUser, mockRes, context);
785
+ expect(mockRes.json).toHaveBeenCalledWith({ role: 'manager' });
786
+ }
787
+ });
788
+ it('should invoke a custom resolver via { type: "custom", resolve }', async () => {
789
+ const resolvedValue = { custom: 'injected' };
790
+ const resolveFn = jest.fn().mockReturnValue(resolvedValue);
791
+ class TestController {
792
+ getCustom(val) {
793
+ return val;
794
+ }
795
+ }
796
+ container.register(TestController, new TestController());
797
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
798
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/custom', propertyKey: 'getCustom' }], TestController);
799
+ Reflect.defineMetadata('hazel:inject', [{ type: 'custom', resolve: resolveFn }], TestController, 'getCustom');
800
+ router.registerController(TestController);
801
+ const context = {
802
+ params: {},
803
+ query: {},
804
+ body: {},
805
+ headers: {},
806
+ method: 'GET',
807
+ url: '/test/custom',
808
+ };
809
+ const route = await router.match('GET', '/test/custom', context);
810
+ expect(route).toBeDefined();
811
+ if (route) {
812
+ await route.handler(mockReq, mockRes, context);
813
+ expect(resolveFn).toHaveBeenCalled();
814
+ expect(mockRes.json).toHaveBeenCalledWith(resolvedValue);
815
+ }
816
+ });
817
+ it('should auto-inject RequestContext for undecorated parameters', async () => {
818
+ class TestController {
819
+ contextReceiver(ctx) {
820
+ return { url: ctx.url };
821
+ }
822
+ }
823
+ container.register(TestController, new TestController());
824
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
825
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/ctx', propertyKey: 'contextReceiver' }], TestController);
826
+ // No injection metadata — relies on design:paramtypes auto-inject
827
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'contextReceiver');
828
+ Reflect.defineMetadata('design:paramtypes', [Object], // one undecorated parameter
829
+ TestController.prototype, 'contextReceiver');
830
+ router.registerController(TestController);
831
+ const context = {
832
+ params: {},
833
+ query: {},
834
+ body: {},
835
+ headers: {},
836
+ method: 'GET',
837
+ url: '/test/ctx',
838
+ };
839
+ const route = await router.match('GET', '/test/ctx', context);
840
+ expect(route).toBeDefined();
841
+ if (route) {
842
+ await route.handler(mockReq, mockRes, context);
843
+ expect(mockRes.json).toHaveBeenCalledWith({ url: '/test/ctx' });
844
+ }
845
+ });
846
+ // -----------------------------------------------------------------------
847
+ // @Redirect metadata
848
+ // -----------------------------------------------------------------------
849
+ it('should send a redirect response when @Redirect metadata is set', async () => {
850
+ class TestController {
851
+ goHome() {
852
+ return undefined;
853
+ }
854
+ }
855
+ container.register(TestController, new TestController());
856
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
857
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/redirect', propertyKey: 'goHome' }], TestController);
858
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'goHome');
859
+ // Simulate @Redirect('/home', 301) on the prototype
860
+ Reflect.defineMetadata('hazel:redirect', { url: '/home', statusCode: 301 }, TestController.prototype, 'goHome');
861
+ router.registerController(TestController);
862
+ const context = {
863
+ params: {},
864
+ query: {},
865
+ body: {},
866
+ headers: {},
867
+ method: 'GET',
868
+ url: '/test/redirect',
869
+ };
870
+ const route = await router.match('GET', '/test/redirect', context);
871
+ expect(route).toBeDefined();
872
+ if (route) {
873
+ mockRes.setHeader = jest.fn();
874
+ await route.handler(mockReq, mockRes, context);
875
+ expect(mockRes.status).toHaveBeenCalledWith(301);
876
+ expect(mockRes.setHeader).toHaveBeenCalledWith('Location', '/home');
877
+ }
878
+ });
879
+ // -----------------------------------------------------------------------
880
+ // @Header metadata (custom response headers)
881
+ // -----------------------------------------------------------------------
882
+ it('should set custom response headers when @Header metadata is set', async () => {
883
+ class TestController {
884
+ headered() {
885
+ return { ok: true };
886
+ }
887
+ }
888
+ container.register(TestController, new TestController());
889
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
890
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/headered', propertyKey: 'headered' }], TestController);
891
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'headered');
892
+ Reflect.defineMetadata('hazel:headers', [{ name: 'X-Custom', value: 'hello' }], TestController.prototype, 'headered');
893
+ router.registerController(TestController);
894
+ const context = {
895
+ params: {},
896
+ query: {},
897
+ body: {},
898
+ headers: {},
899
+ method: 'GET',
900
+ url: '/test/headered',
901
+ };
902
+ const route = await router.match('GET', '/test/headered', context);
903
+ expect(route).toBeDefined();
904
+ if (route) {
905
+ await route.handler(mockReq, mockRes, context);
906
+ expect(mockRes.setHeader).toHaveBeenCalledWith('X-Custom', 'hello');
907
+ expect(mockRes.json).toHaveBeenCalledWith({ ok: true });
908
+ }
909
+ });
910
+ // -----------------------------------------------------------------------
911
+ // @HttpCode metadata
912
+ // -----------------------------------------------------------------------
913
+ it('should use custom HTTP status code from @HttpCode when result is defined', async () => {
914
+ class TestController {
915
+ create() {
916
+ return { created: true };
917
+ }
918
+ }
919
+ container.register(TestController, new TestController());
920
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
921
+ Reflect.defineMetadata('hazel:routes', [{ method: 'POST', path: '/http-code', propertyKey: 'create' }], TestController);
922
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'create');
923
+ Reflect.defineMetadata('hazel:http-code', 201, TestController.prototype, 'create');
924
+ router.registerController(TestController);
925
+ const context = {
926
+ params: {},
927
+ query: {},
928
+ body: {},
929
+ headers: {},
930
+ method: 'POST',
931
+ url: '/test/http-code',
932
+ };
933
+ const route = await router.match('POST', '/test/http-code', context);
934
+ expect(route).toBeDefined();
935
+ if (route) {
936
+ await route.handler(mockReq, mockRes, context);
937
+ expect(mockRes.status).toHaveBeenCalledWith(201);
938
+ expect(mockRes.json).toHaveBeenCalledWith({ created: true });
939
+ }
940
+ });
941
+ it('should use custom HTTP status code from @HttpCode when result is undefined', async () => {
942
+ class TestController {
943
+ noContent() {
944
+ return undefined;
945
+ }
946
+ }
947
+ container.register(TestController, new TestController());
948
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
949
+ Reflect.defineMetadata('hazel:routes', [{ method: 'DELETE', path: '/no-content', propertyKey: 'noContent' }], TestController);
950
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'noContent');
951
+ Reflect.defineMetadata('hazel:http-code', 204, TestController.prototype, 'noContent');
952
+ router.registerController(TestController);
953
+ const context = {
954
+ params: {},
955
+ query: {},
956
+ body: {},
957
+ headers: {},
958
+ method: 'DELETE',
959
+ url: '/test/no-content',
960
+ };
961
+ const route = await router.match('DELETE', '/test/no-content', context);
962
+ expect(route).toBeDefined();
963
+ if (route) {
964
+ await route.handler(mockReq, mockRes, context);
965
+ expect(mockRes.status).toHaveBeenCalledWith(204);
966
+ expect(mockRes.end).toHaveBeenCalled();
967
+ }
968
+ });
969
+ // -----------------------------------------------------------------------
970
+ // Guard execution
971
+ // -----------------------------------------------------------------------
972
+ it('should throw UnauthorizedError when guard canActivate returns false', async () => {
973
+ class DenyGuard {
974
+ canActivate() {
975
+ return false;
976
+ }
977
+ }
978
+ container.register(DenyGuard, new DenyGuard());
979
+ class TestController {
980
+ secret() {
981
+ return { secret: true };
982
+ }
983
+ }
984
+ container.register(TestController, new TestController());
985
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
986
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/secret', propertyKey: 'secret' }], TestController);
987
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'secret');
988
+ Reflect.defineMetadata('hazel:guards', [DenyGuard], TestController);
989
+ router.registerController(TestController);
990
+ const context = {
991
+ params: {},
992
+ query: {},
993
+ body: {},
994
+ headers: {},
995
+ method: 'GET',
996
+ url: '/test/secret',
997
+ };
998
+ const route = await router.match('GET', '/test/secret', context);
999
+ expect(route).toBeDefined();
1000
+ if (route) {
1001
+ await route.handler(mockReq, mockRes, context);
1002
+ expect(mockRes.status).toHaveBeenCalledWith(401);
1003
+ }
1004
+ });
1005
+ it('should propagate req.user to context.user after guards pass', async () => {
1006
+ class AllowGuard {
1007
+ canActivate() {
1008
+ return true;
1009
+ }
1010
+ }
1011
+ container.register(AllowGuard, new AllowGuard());
1012
+ class TestController {
1013
+ whoAmI(user) {
1014
+ return user;
1015
+ }
1016
+ }
1017
+ container.register(TestController, new TestController());
1018
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
1019
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/user-propagate', propertyKey: 'whoAmI' }], TestController);
1020
+ Reflect.defineMetadata('hazel:inject', [{ type: 'user' }], TestController, 'whoAmI');
1021
+ Reflect.defineMetadata('hazel:guards', [AllowGuard], TestController);
1022
+ router.registerController(TestController);
1023
+ const context = {
1024
+ params: {},
1025
+ query: {},
1026
+ body: {},
1027
+ headers: {},
1028
+ method: 'GET',
1029
+ url: '/test/user-propagate',
1030
+ };
1031
+ const reqWithUser = { ...mockReq, user: { sub: 'u99' } };
1032
+ const route = await router.match('GET', '/test/user-propagate', context);
1033
+ expect(route).toBeDefined();
1034
+ if (route) {
1035
+ await route.handler(reqWithUser, mockRes, context);
1036
+ expect(mockRes.json).toHaveBeenCalledWith({ sub: 'u99' });
1037
+ }
1038
+ });
1039
+ // -----------------------------------------------------------------------
1040
+ // Legacy string-based injection
1041
+ // -----------------------------------------------------------------------
1042
+ it('should handle legacy string injection "body"', async () => {
1043
+ class TestController {
1044
+ legacyBody(body) {
1045
+ return body;
1046
+ }
1047
+ }
1048
+ container.register(TestController, new TestController());
1049
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
1050
+ Reflect.defineMetadata('hazel:routes', [{ method: 'POST', path: '/legacy-body', propertyKey: 'legacyBody' }], TestController);
1051
+ Reflect.defineMetadata('hazel:inject', ['body'], TestController, 'legacyBody');
1052
+ router.registerController(TestController);
1053
+ const context = {
1054
+ params: {},
1055
+ query: {},
1056
+ body: { name: 'legacy' },
1057
+ headers: {},
1058
+ method: 'POST',
1059
+ url: '/test/legacy-body',
1060
+ };
1061
+ const route = await router.match('POST', '/test/legacy-body', context);
1062
+ if (route) {
1063
+ await route.handler(mockReq, mockRes, context);
1064
+ expect(mockRes.json).toHaveBeenCalledWith({ name: 'legacy' });
1065
+ }
1066
+ });
1067
+ it('should handle legacy string injection "param"', async () => {
1068
+ class TestController {
1069
+ legacyParam(params) {
1070
+ return params;
1071
+ }
1072
+ }
1073
+ container.register(TestController, new TestController());
1074
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
1075
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/legacy-param/:id', propertyKey: 'legacyParam' }], TestController);
1076
+ Reflect.defineMetadata('hazel:inject', ['param'], TestController, 'legacyParam');
1077
+ router.registerController(TestController);
1078
+ const context = {
1079
+ params: { id: '42' },
1080
+ query: {},
1081
+ body: {},
1082
+ headers: {},
1083
+ method: 'GET',
1084
+ url: '/test/legacy-param/42',
1085
+ };
1086
+ const route = await router.match('GET', '/test/legacy-param/42', context);
1087
+ if (route) {
1088
+ await route.handler(mockReq, mockRes, context);
1089
+ expect(mockRes.json).toHaveBeenCalledWith({ id: '42' });
1090
+ }
1091
+ });
1092
+ it('should handle legacy string injection "query"', async () => {
1093
+ class TestController {
1094
+ legacyQuery(query) {
1095
+ return query;
1096
+ }
1097
+ }
1098
+ container.register(TestController, new TestController());
1099
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
1100
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/legacy-query', propertyKey: 'legacyQuery' }], TestController);
1101
+ Reflect.defineMetadata('hazel:inject', ['query'], TestController, 'legacyQuery');
1102
+ router.registerController(TestController);
1103
+ const context = {
1104
+ params: {},
1105
+ query: { filter: 'active' },
1106
+ body: {},
1107
+ headers: {},
1108
+ method: 'GET',
1109
+ url: '/test/legacy-query',
1110
+ };
1111
+ const route = await router.match('GET', '/test/legacy-query', context);
1112
+ if (route) {
1113
+ await route.handler(mockReq, mockRes, context);
1114
+ expect(mockRes.json).toHaveBeenCalledWith({ filter: 'active' });
1115
+ }
1116
+ });
1117
+ it('should handle legacy string injection "headers"', async () => {
1118
+ class TestController {
1119
+ legacyHeaders(headers) {
1120
+ return headers;
1121
+ }
1122
+ }
1123
+ container.register(TestController, new TestController());
1124
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
1125
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/legacy-headers', propertyKey: 'legacyHeaders' }], TestController);
1126
+ Reflect.defineMetadata('hazel:inject', ['headers'], TestController, 'legacyHeaders');
1127
+ router.registerController(TestController);
1128
+ const context = {
1129
+ params: {},
1130
+ query: {},
1131
+ body: {},
1132
+ headers: { 'x-trace': 'abc' },
1133
+ method: 'GET',
1134
+ url: '/test/legacy-headers',
1135
+ };
1136
+ const route = await router.match('GET', '/test/legacy-headers', context);
1137
+ if (route) {
1138
+ await route.handler(mockReq, mockRes, context);
1139
+ expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({ 'x-trace': 'abc' }));
1140
+ }
1141
+ });
1142
+ // -----------------------------------------------------------------------
1143
+ // handleRequest edge cases
1144
+ // -----------------------------------------------------------------------
1145
+ it('should return "Internal Server Error" when a non-Error is thrown in handleRequest', async () => {
1146
+ jest.spyOn(router, 'match').mockRejectedValue('string-error');
1147
+ mockReq.method = 'GET';
1148
+ mockReq.url = '/test';
1149
+ await router.handleRequest(mockReq, mockRes);
1150
+ expect(mockRes.status).toHaveBeenCalledWith(500);
1151
+ expect(mockRes.json).toHaveBeenCalledWith({ error: 'Internal Server Error' });
1152
+ });
1153
+ it('should use production error message when NODE_ENV is production', async () => {
1154
+ const originalEnv = process.env.NODE_ENV;
1155
+ process.env.NODE_ENV = 'production';
1156
+ class TestController {
1157
+ throwInProd() {
1158
+ throw new Error('Sensitive internal details');
1159
+ }
1160
+ }
1161
+ container.register(TestController, new TestController());
1162
+ Reflect.defineMetadata('hazel:controller', { path: '/test' }, TestController);
1163
+ Reflect.defineMetadata('hazel:routes', [{ method: 'GET', path: '/prod-error', propertyKey: 'throwInProd' }], TestController);
1164
+ Reflect.defineMetadata('hazel:inject', [], TestController, 'throwInProd');
1165
+ router.registerController(TestController);
1166
+ const context = {
1167
+ params: {},
1168
+ query: {},
1169
+ body: {},
1170
+ headers: {},
1171
+ method: 'GET',
1172
+ url: '/test/prod-error',
1173
+ };
1174
+ const route = await router.match('GET', '/test/prod-error', context);
1175
+ if (route) {
1176
+ await route.handler(mockReq, mockRes, context);
1177
+ expect(mockRes.status).toHaveBeenCalledWith(500);
1178
+ expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({ message: 'Internal server error' }));
1179
+ }
1180
+ process.env.NODE_ENV = originalEnv;
1181
+ });
679
1182
  });
680
1183
  });
@@ -1,5 +1,5 @@
1
1
  import 'reflect-metadata';
2
- import { Type } from './types';
2
+ import { Type, RequestContext } from './types';
3
3
  import { PipeTransform, PipeMetadata } from './pipes/pipe';
4
4
  import { Interceptor, InterceptorMetadata } from './interceptors/interceptor';
5
5
  import { HazelApp } from './hazel-app';
@@ -44,6 +44,12 @@ export interface ExecutionContext {
44
44
  switchToHttp(): {
45
45
  getRequest(): unknown;
46
46
  getResponse(): unknown;
47
+ /**
48
+ * Returns the fully parsed RequestContext for this request.
49
+ * Gives guards access to `params`, `query`, `headers`, `user`, and `body`
50
+ * without having to re-parse the raw Node.js IncomingMessage.
51
+ */
52
+ getContext(): RequestContext;
47
53
  };
48
54
  }
49
55
  export interface CanActivate {
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAMxC,OAAO,kBAAkB,CAAC;AAY1B,UAAU,UAAU;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;AAEpF,qBAAa,MAAM;IAKL,OAAO,CAAC,SAAS;IAJ7B,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,cAAc,CAAuD;IAC7E,OAAO,CAAC,iBAAiB,CAAoB;gBAEzB,SAAS,EAAE,SAAS;IAUxC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI;YA4DrC,UAAU;YAsBV,iBAAiB;IAe/B,OAAO,CAAC,kBAAkB;IAwQ1B,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAiBjB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IAOf,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA0C7F,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIjD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIlD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIjD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIpD,OAAO,CAAC,QAAQ;IAKV,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAkChE"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAMxC,OAAO,kBAAkB,CAAC;AAY1B,UAAU,UAAU;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;AAEpF,qBAAa,MAAM;IAKL,OAAO,CAAC,SAAS;IAJ7B,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,cAAc,CAAuD;IAC7E,OAAO,CAAC,iBAAiB,CAAoB;gBAEzB,SAAS,EAAE,SAAS;IAUxC,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI;YA4DrC,UAAU;YAsBV,iBAAiB;IAe/B,OAAO,CAAC,kBAAkB;IAiR1B,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAiBjB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IAOf,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA0C7F,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIjD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIlD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIjD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI;IAIpD,OAAO,CAAC,QAAQ;IAKV,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAkChE"}
package/dist/router.js CHANGED
@@ -142,6 +142,7 @@ class Router {
142
142
  switchToHttp: () => ({
143
143
  getRequest: () => req,
144
144
  getResponse: () => res,
145
+ getContext: () => context,
145
146
  }),
146
147
  };
147
148
  for (const guardType of allGuards) {
@@ -235,6 +236,16 @@ class Router {
235
236
  args[i] = queryValue;
236
237
  }
237
238
  }
239
+ else if (injection.type === 'user') {
240
+ // Handle @CurrentUser() decorator — reads from context.user (set by a guard)
241
+ const user = context.user ?? req.user;
242
+ args[i] = injection.field ? user?.[injection.field] : user;
243
+ }
244
+ else if (injection.type === 'custom' && typeof injection.resolve === 'function') {
245
+ // Handle custom parameter decorators (e.g. @Ability() from @hazeljs/casl).
246
+ // The decorator stores a resolver function; call it with request, context, container.
247
+ args[i] = await injection.resolve(req, context, this.container);
248
+ }
238
249
  }
239
250
  }
240
251
  // Auto-inject RequestContext for undecorated parameters
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazeljs/core",
3
- "version": "0.2.0-beta.58",
3
+ "version": "0.2.0-beta.60",
4
4
  "description": "Core HazelJS framework - Dependency injection, routing, decorators, and base functionality",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -63,5 +63,5 @@
63
63
  "url": "https://github.com/hazeljs/hazel-js/issues"
64
64
  },
65
65
  "homepage": "https://hazeljs.com",
66
- "gitHead": "ce3821b373043284ac4847315371efb137dc860d"
66
+ "gitHead": "8cd6f3cf5b77aba504ec10691324937130feb2c3"
67
67
  }