@hazeljs/core 0.2.0-beta.8 → 0.2.0-beta.81
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/LICENSE +192 -21
- package/README.md +48 -10
- package/dist/__tests__/decorators.test.d.ts.map +1 -1
- package/dist/__tests__/decorators.test.js +544 -0
- package/dist/__tests__/hazel-app.test.js +128 -0
- package/dist/__tests__/router.test.js +503 -0
- package/dist/container.js +4 -4
- package/dist/decorators.d.ts +75 -1
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +196 -1
- package/dist/errors/http.error.d.ts +3 -0
- package/dist/errors/http.error.d.ts.map +1 -1
- package/dist/errors/http.error.js +8 -1
- package/dist/hazel-app.d.ts +17 -1
- package/dist/hazel-app.d.ts.map +1 -1
- package/dist/hazel-app.js +88 -25
- package/dist/hazel-module.d.ts +11 -2
- package/dist/hazel-module.d.ts.map +1 -1
- package/dist/hazel-module.js +47 -19
- package/dist/hazel-response.d.ts +5 -0
- package/dist/hazel-response.d.ts.map +1 -1
- package/dist/hazel-response.js +21 -0
- package/dist/health.js +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -2
- package/dist/interceptors/interceptor.d.ts +8 -0
- package/dist/interceptors/interceptor.d.ts.map +1 -1
- package/dist/interceptors/interceptor.js +26 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +51 -28
- package/dist/request-context.d.ts +5 -0
- package/dist/request-context.d.ts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +56 -6
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -4
|
@@ -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
|
});
|
package/dist/container.js
CHANGED
|
@@ -17,7 +17,7 @@ class Container {
|
|
|
17
17
|
constructor() {
|
|
18
18
|
this.providers = new Map();
|
|
19
19
|
this.requestScopedProviders = new Map();
|
|
20
|
-
logger_1.default.
|
|
20
|
+
logger_1.default.debug('Container initialized');
|
|
21
21
|
}
|
|
22
22
|
static getInstance() {
|
|
23
23
|
if (!Container.instance) {
|
|
@@ -36,7 +36,7 @@ class Container {
|
|
|
36
36
|
*/
|
|
37
37
|
register(token, provider, scope = Scope.SINGLETON) {
|
|
38
38
|
const tokenName = this.getTokenName(token);
|
|
39
|
-
logger_1.default.
|
|
39
|
+
logger_1.default.debug(`Registering provider: ${tokenName} with scope: ${scope}`);
|
|
40
40
|
if (this.isProvider(provider)) {
|
|
41
41
|
this.registerProvider(provider);
|
|
42
42
|
}
|
|
@@ -216,7 +216,7 @@ class Container {
|
|
|
216
216
|
// Create instance with dependencies
|
|
217
217
|
const instance = new token(...dependencies);
|
|
218
218
|
if (logger_1.default.isDebugEnabled()) {
|
|
219
|
-
logger_1.default.
|
|
219
|
+
logger_1.default.debug(`Created instance of: ${this.getTokenName(token)}`);
|
|
220
220
|
}
|
|
221
221
|
return instance;
|
|
222
222
|
}
|
|
@@ -233,7 +233,7 @@ class Container {
|
|
|
233
233
|
* Clear all providers
|
|
234
234
|
*/
|
|
235
235
|
clear() {
|
|
236
|
-
logger_1.default.
|
|
236
|
+
logger_1.default.debug('Clearing container');
|
|
237
237
|
this.providers.clear();
|
|
238
238
|
this.requestScopedProviders.clear();
|
|
239
239
|
}
|
package/dist/decorators.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import { Type } from './types';
|
|
2
|
+
import { Type, RequestContext, Request } 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';
|
|
6
|
+
import type { Container } from './container';
|
|
7
|
+
export declare const CUSTOM_METADATA_PREFIX = "hazel:meta:";
|
|
6
8
|
export interface ControllerMetadata {
|
|
7
9
|
path: string;
|
|
8
10
|
interceptors?: InterceptorMetadata[];
|
|
@@ -32,6 +34,7 @@ export interface InjectableOptions {
|
|
|
32
34
|
}
|
|
33
35
|
export interface RepositoryOptions {
|
|
34
36
|
model: string;
|
|
37
|
+
scope?: 'singleton' | 'transient' | 'request';
|
|
35
38
|
}
|
|
36
39
|
export interface OnModuleInit {
|
|
37
40
|
onModuleInit(): Promise<void>;
|
|
@@ -43,6 +46,12 @@ export interface ExecutionContext {
|
|
|
43
46
|
switchToHttp(): {
|
|
44
47
|
getRequest(): unknown;
|
|
45
48
|
getResponse(): unknown;
|
|
49
|
+
/**
|
|
50
|
+
* Returns the fully parsed RequestContext for this request.
|
|
51
|
+
* Gives guards access to `params`, `query`, `headers`, `user`, and `body`
|
|
52
|
+
* without having to re-parse the raw Node.js IncomingMessage.
|
|
53
|
+
*/
|
|
54
|
+
getContext(): RequestContext;
|
|
46
55
|
};
|
|
47
56
|
}
|
|
48
57
|
export interface CanActivate {
|
|
@@ -88,5 +97,70 @@ export declare function HttpCode(statusCode: number): MethodDecorator;
|
|
|
88
97
|
export declare function Header(name: string, value: string): MethodDecorator;
|
|
89
98
|
export declare function Redirect(url: string, statusCode?: number): MethodDecorator;
|
|
90
99
|
export declare function Res(): ParameterDecorator;
|
|
100
|
+
export declare function Ip(): ParameterDecorator;
|
|
101
|
+
export declare function Host(): ParameterDecorator;
|
|
102
|
+
/**
|
|
103
|
+
* Marks a controller or route as public (no auth required).
|
|
104
|
+
* Guards should check Reflect.getMetadata(PUBLIC_METADATA_KEY, target, propertyKey)
|
|
105
|
+
* or Reflect.getMetadata(PUBLIC_METADATA_KEY, target) and allow the request when true.
|
|
106
|
+
*/
|
|
107
|
+
export declare function Public(): ClassDecorator & MethodDecorator;
|
|
108
|
+
/** Alias for @Public(). Use when you want to skip auth for specific routes. */
|
|
109
|
+
export declare const SkipAuth: typeof Public;
|
|
110
|
+
export declare function Timeout(ms: number): MethodDecorator;
|
|
111
|
+
export declare function Optional(): ParameterDecorator;
|
|
112
|
+
export declare function Session(): ParameterDecorator;
|
|
113
|
+
export interface RetryDecoratorOptions {
|
|
114
|
+
count: number;
|
|
115
|
+
delay?: number;
|
|
116
|
+
retryIf?: (err: Error) => boolean;
|
|
117
|
+
}
|
|
118
|
+
export declare function Retry(options: RetryDecoratorOptions): MethodDecorator;
|
|
119
|
+
export declare function ApiTags(...tags: string[]): ClassDecorator & MethodDecorator;
|
|
120
|
+
export interface ApiOperationOptions {
|
|
121
|
+
summary?: string;
|
|
122
|
+
description?: string;
|
|
123
|
+
operationId?: string;
|
|
124
|
+
}
|
|
125
|
+
export declare function ApiOperation(options: ApiOperationOptions | string): MethodDecorator;
|
|
126
|
+
/**
|
|
127
|
+
* Sets arbitrary metadata on a class or method.
|
|
128
|
+
* Guards, interceptors, and other components can read it via getMetadata(key, target, propertyKey?).
|
|
129
|
+
*
|
|
130
|
+
* @param key - Metadata key (stored under hazel:meta:<key> to avoid collisions)
|
|
131
|
+
* @param value - Value to store (any serializable or object)
|
|
132
|
+
* @example
|
|
133
|
+
* SetMetadata('roles', ['admin'])(MyController)
|
|
134
|
+
* SetMetadata('roles', ['user'])(MyController.prototype, 'getProfile')
|
|
135
|
+
*/
|
|
136
|
+
export declare function SetMetadata(key: string, value: unknown): ClassDecorator & MethodDecorator;
|
|
137
|
+
/**
|
|
138
|
+
* Reads custom metadata set with SetMetadata.
|
|
139
|
+
*
|
|
140
|
+
* @param key - Key passed to SetMetadata(key, value)
|
|
141
|
+
* @param target - Class or prototype
|
|
142
|
+
* @param propertyKey - Optional method name (for method-level metadata)
|
|
143
|
+
*/
|
|
144
|
+
export declare function getMetadata<T = unknown>(key: string, target: object, propertyKey?: string | symbol): T | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* Context passed to custom parameter decorator resolvers.
|
|
147
|
+
* The router calls the resolver with (req, context, container) when invoking the handler.
|
|
148
|
+
*/
|
|
149
|
+
export interface ParamDecoratorContext {
|
|
150
|
+
req: Request;
|
|
151
|
+
context: RequestContext;
|
|
152
|
+
container: Container;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Creates a custom parameter decorator that injects a value computed from the request.
|
|
156
|
+
* The resolver receives the raw request, parsed request context, and the DI container.
|
|
157
|
+
* Return value can be a Promise for async resolution (e.g. loading the current user from DB).
|
|
158
|
+
*
|
|
159
|
+
* @param resolve - Function (req, context, container) => value | Promise<value>
|
|
160
|
+
* @example
|
|
161
|
+
* const CurrentUser = createParamDecorator(async (req, ctx, container) => ctx.user ?? req.user);
|
|
162
|
+
* // In controller: getProfile(@CurrentUser() user: User) { ... }
|
|
163
|
+
*/
|
|
164
|
+
export declare function createParamDecorator<T = unknown>(resolve: (req: Request, context: RequestContext, container: Container) => T | Promise<T>): ParameterDecorator;
|
|
91
165
|
export { HazelApp };
|
|
92
166
|
//# sourceMappingURL=decorators.d.ts.map
|
package/dist/decorators.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"}
|