@e22m4u/ts-rest-router 0.4.3 → 0.5.0
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/README.md +1 -0
- package/dist/cjs/index.cjs +9 -1
- package/dist/esm/controller-registry.js +4 -1
- package/dist/esm/decorators/request-context/request-context-decorator.d.ts +4 -0
- package/dist/esm/decorators/request-context/request-context-decorator.js +6 -0
- package/dist/esm/decorators/request-context/request-context-decorator.spec.js +48 -6
- package/package.json +1 -1
- package/src/controller-registry.spec.ts +70 -0
- package/src/controller-registry.ts +7 -1
- package/src/decorators/request-context/request-context-decorator.spec.ts +42 -7
- package/src/decorators/request-context/request-context-decorator.ts +7 -0
package/README.md
CHANGED
@@ -209,6 +209,7 @@ class UserController {
|
|
209
209
|
- `@requestCookie` - определенный cookie запроса;
|
210
210
|
- `@requestCookies` - все cookies запроса как объект;
|
211
211
|
- `@requestContext` - доступ к контексту запроса;
|
212
|
+
- `@requestContainer` - сервис-контейнер запроса;
|
212
213
|
- `@requestData` - доступ к данным запроса;
|
213
214
|
- `@httpRequest` - экземпляр `IncomingMessage`;
|
214
215
|
- `@httpResponse` - экземпляр `ServerResponse`;
|
package/dist/cjs/index.cjs
CHANGED
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
51
51
|
postAction: () => postAction,
|
52
52
|
putAction: () => putAction,
|
53
53
|
requestBody: () => requestBody,
|
54
|
+
requestContainer: () => requestContainer,
|
54
55
|
requestContext: () => requestContext,
|
55
56
|
requestCookie: () => requestCookie,
|
56
57
|
requestCookies: () => requestCookies,
|
@@ -602,6 +603,10 @@ function httpResponse() {
|
|
602
603
|
return requestContext("res");
|
603
604
|
}
|
604
605
|
__name(httpResponse, "httpResponse");
|
606
|
+
function requestContainer() {
|
607
|
+
return requestContext("container");
|
608
|
+
}
|
609
|
+
__name(requestContainer, "requestContainer");
|
605
610
|
|
606
611
|
// dist/esm/controller-registry.js
|
607
612
|
var import_ts_data_schema4 = require("@e22m4u/ts-data-schema");
|
@@ -955,7 +960,9 @@ var _ControllerRegistry = class _ControllerRegistry extends DebuggableService {
|
|
955
960
|
debug("No RequestDataMetadata specified for %v argument.", index);
|
956
961
|
}
|
957
962
|
});
|
958
|
-
|
963
|
+
if (requestContext2.container.has(controllerCtor))
|
964
|
+
throw new import_js_format3.Errorf("The controller %v is already registered, which breaks controller isolation per request.", controllerCtor.name);
|
965
|
+
const controller = requestContext2.container.get(controllerCtor);
|
959
966
|
return controller[actionName](...args);
|
960
967
|
};
|
961
968
|
}
|
@@ -1016,6 +1023,7 @@ var RestRouter = _RestRouter;
|
|
1016
1023
|
postAction,
|
1017
1024
|
putAction,
|
1018
1025
|
requestBody,
|
1026
|
+
requestContainer,
|
1019
1027
|
requestContext,
|
1020
1028
|
requestCookie,
|
1021
1029
|
requestCookies,
|
@@ -417,7 +417,10 @@ export class ControllerRegistry extends DebuggableService {
|
|
417
417
|
}
|
418
418
|
});
|
419
419
|
// выполнение операции контроллера
|
420
|
-
|
420
|
+
if (requestContext.container.has(controllerCtor))
|
421
|
+
throw new Errorf('The controller %v is already registered, which breaks ' +
|
422
|
+
'controller isolation per request.', controllerCtor.name);
|
423
|
+
const controller = requestContext.container.get(controllerCtor);
|
421
424
|
return controller[actionName](...args);
|
422
425
|
};
|
423
426
|
}
|
@@ -14,3 +14,7 @@ export declare function httpRequest(): (target: Prototype<object>, propertyKey:
|
|
14
14
|
* HttpResponse decorator.
|
15
15
|
*/
|
16
16
|
export declare function httpResponse(): (target: Prototype<object>, propertyKey: string, indexOrDescriptor: number) => void;
|
17
|
+
/**
|
18
|
+
* RequestContainer decorator.
|
19
|
+
*/
|
20
|
+
export declare function requestContainer(): (target: Prototype<object>, propertyKey: string, indexOrDescriptor: number) => void;
|
@@ -15,13 +15,10 @@ import { expect } from 'chai';
|
|
15
15
|
import { httpRequest } from './request-context-decorator.js';
|
16
16
|
import { httpResponse } from './request-context-decorator.js';
|
17
17
|
import { requestContext } from './request-context-decorator.js';
|
18
|
+
import { requestContainer } from './request-context-decorator.js';
|
18
19
|
import { RequestContextReflector } from './request-context-reflector.js';
|
19
20
|
describe('requestContext', function () {
|
20
|
-
it('
|
21
|
-
expect(httpRequest).to.be.instanceOf(Function);
|
22
|
-
expect(httpResponse).to.be.instanceOf(Function);
|
23
|
-
});
|
24
|
-
it('does not require options', function () {
|
21
|
+
it('should not require options', function () {
|
25
22
|
class Target {
|
26
23
|
method(prop) { }
|
27
24
|
}
|
@@ -34,7 +31,7 @@ describe('requestContext', function () {
|
|
34
31
|
const res = RequestContextReflector.getMetadata(Target, 'method');
|
35
32
|
expect(res.get(0)).to.be.eql({ property: undefined });
|
36
33
|
});
|
37
|
-
it('
|
34
|
+
it('should set the given property to target metadata', function () {
|
38
35
|
class Target {
|
39
36
|
method(prop) { }
|
40
37
|
}
|
@@ -47,4 +44,49 @@ describe('requestContext', function () {
|
|
47
44
|
const res = RequestContextReflector.getMetadata(Target, 'method');
|
48
45
|
expect(res.get(0)).to.be.eql({ property: 'res' });
|
49
46
|
});
|
47
|
+
describe('httpRequest', function () {
|
48
|
+
it('should set the "req" property to target metadata', function () {
|
49
|
+
class Target {
|
50
|
+
method(prop) { }
|
51
|
+
}
|
52
|
+
__decorate([
|
53
|
+
__param(0, httpRequest()),
|
54
|
+
__metadata("design:type", Function),
|
55
|
+
__metadata("design:paramtypes", [Object]),
|
56
|
+
__metadata("design:returntype", void 0)
|
57
|
+
], Target.prototype, "method", null);
|
58
|
+
const res = RequestContextReflector.getMetadata(Target, 'method');
|
59
|
+
expect(res.get(0)).to.be.eql({ property: 'req' });
|
60
|
+
});
|
61
|
+
});
|
62
|
+
describe('httpResponse', function () {
|
63
|
+
it('should set the "res" property to target metadata', function () {
|
64
|
+
class Target {
|
65
|
+
method(prop) { }
|
66
|
+
}
|
67
|
+
__decorate([
|
68
|
+
__param(0, httpResponse()),
|
69
|
+
__metadata("design:type", Function),
|
70
|
+
__metadata("design:paramtypes", [Object]),
|
71
|
+
__metadata("design:returntype", void 0)
|
72
|
+
], Target.prototype, "method", null);
|
73
|
+
const res = RequestContextReflector.getMetadata(Target, 'method');
|
74
|
+
expect(res.get(0)).to.be.eql({ property: 'res' });
|
75
|
+
});
|
76
|
+
});
|
77
|
+
describe('requestContainer', function () {
|
78
|
+
it('should set the "container" property to target metadata', function () {
|
79
|
+
class Target {
|
80
|
+
method(prop) { }
|
81
|
+
}
|
82
|
+
__decorate([
|
83
|
+
__param(0, requestContainer()),
|
84
|
+
__metadata("design:type", Function),
|
85
|
+
__metadata("design:paramtypes", [Object]),
|
86
|
+
__metadata("design:returntype", void 0)
|
87
|
+
], Target.prototype, "method", null);
|
88
|
+
const res = RequestContextReflector.getMetadata(Target, 'method');
|
89
|
+
expect(res.get(0)).to.be.eql({ property: 'container' });
|
90
|
+
});
|
91
|
+
});
|
50
92
|
});
|
package/package.json
CHANGED
@@ -13,6 +13,7 @@ import {
|
|
13
13
|
RouteRegistry,
|
14
14
|
TrieRouter,
|
15
15
|
} from '@e22m4u/js-trie-router';
|
16
|
+
|
16
17
|
import {
|
17
18
|
afterAction,
|
18
19
|
beforeAction,
|
@@ -34,6 +35,7 @@ import {
|
|
34
35
|
import {expect} from 'chai';
|
35
36
|
import {DataType} from '@e22m4u/ts-data-schema';
|
36
37
|
import {ControllerRegistry} from './controller-registry.js';
|
38
|
+
import {Service, ServiceContainer} from '@e22m4u/js-service';
|
37
39
|
|
38
40
|
const PRE_HANDLER_1 = () => undefined;
|
39
41
|
const PRE_HANDLER_2 = () => undefined;
|
@@ -969,5 +971,73 @@ describe('ControllerRegistry', function () {
|
|
969
971
|
await handler(ctx);
|
970
972
|
expect(invoked).to.be.true;
|
971
973
|
});
|
974
|
+
|
975
|
+
it('should create a new controller instance for each request', async function () {
|
976
|
+
@restController()
|
977
|
+
class MyStatefulController {
|
978
|
+
public readonly instanceId: number;
|
979
|
+
constructor() {
|
980
|
+
this.instanceId = Math.random();
|
981
|
+
}
|
982
|
+
@getAction('/test')
|
983
|
+
myAction() {
|
984
|
+
return this;
|
985
|
+
}
|
986
|
+
}
|
987
|
+
const S = new ControllerRegistry();
|
988
|
+
S.addController(MyStatefulController);
|
989
|
+
// поиск обработчика
|
990
|
+
const router = S.getService(RouteRegistry);
|
991
|
+
const req = createRequestMock({method: HttpMethod.GET, path: '/test'});
|
992
|
+
const matching = router.matchRouteByRequest(req);
|
993
|
+
const handler = matching!.route.handler;
|
994
|
+
// симуляция первого запроса
|
995
|
+
const req1 = createRequestMock();
|
996
|
+
const res1 = createResponseMock();
|
997
|
+
const cont1 = new ServiceContainer(S.container);
|
998
|
+
const ctx1 = new RequestContext(cont1, req1, res1);
|
999
|
+
const controllerInstance1 = (await handler(ctx1)) as MyStatefulController;
|
1000
|
+
// симуляция второго запроса
|
1001
|
+
const req2 = createRequestMock();
|
1002
|
+
const res2 = createResponseMock();
|
1003
|
+
const cont2 = new ServiceContainer(S.container);
|
1004
|
+
const ctx2 = new RequestContext(cont2, req2, res2);
|
1005
|
+
const controllerInstance2 = (await handler(ctx2)) as MyStatefulController;
|
1006
|
+
// проверка, что это два разных экземпляра
|
1007
|
+
expect(controllerInstance1).to.be.instanceOf(MyStatefulController);
|
1008
|
+
expect(controllerInstance2).to.be.instanceOf(MyStatefulController);
|
1009
|
+
expect(controllerInstance1).to.not.equal(controllerInstance2);
|
1010
|
+
expect(controllerInstance1.instanceId).to.not.equal(
|
1011
|
+
controllerInstance2.instanceId,
|
1012
|
+
'Controller instance IDs should be different',
|
1013
|
+
);
|
1014
|
+
});
|
1015
|
+
|
1016
|
+
it('should create controller using a request-scoped container parented by the application container', async function () {
|
1017
|
+
let counter = 0;
|
1018
|
+
const appContainer = new ServiceContainer();
|
1019
|
+
@restController()
|
1020
|
+
class MyStatefulController extends Service {
|
1021
|
+
@getAction('/test')
|
1022
|
+
myAction() {
|
1023
|
+
expect(this.container).to.be.not.eq(appContainer);
|
1024
|
+
expect(this.container.getParent()).to.be.eq(appContainer);
|
1025
|
+
counter++;
|
1026
|
+
}
|
1027
|
+
}
|
1028
|
+
const S = new ControllerRegistry(appContainer);
|
1029
|
+
S.addController(MyStatefulController);
|
1030
|
+
// поиск обработчика
|
1031
|
+
const router = S.getService(RouteRegistry);
|
1032
|
+
const req = createRequestMock({method: HttpMethod.GET, path: '/test'});
|
1033
|
+
const matching = router.matchRouteByRequest(req);
|
1034
|
+
const handler = matching!.route.handler;
|
1035
|
+
// симуляция запроса
|
1036
|
+
const res = createResponseMock();
|
1037
|
+
const cont1 = new ServiceContainer(S.container);
|
1038
|
+
const ctx1 = new RequestContext(cont1, req, res);
|
1039
|
+
await handler(ctx1);
|
1040
|
+
expect(counter).to.be.eq(1);
|
1041
|
+
});
|
972
1042
|
});
|
973
1043
|
});
|
@@ -501,7 +501,13 @@ export class ControllerRegistry extends DebuggableService {
|
|
501
501
|
}
|
502
502
|
});
|
503
503
|
// выполнение операции контроллера
|
504
|
-
|
504
|
+
if (requestContext.container.has(controllerCtor))
|
505
|
+
throw new Errorf(
|
506
|
+
'The controller %v is already registered, which breaks ' +
|
507
|
+
'controller isolation per request.',
|
508
|
+
controllerCtor.name,
|
509
|
+
);
|
510
|
+
const controller = requestContext.container.get(controllerCtor);
|
505
511
|
return (controller as AnyObject)[actionName](...args);
|
506
512
|
};
|
507
513
|
}
|
@@ -3,15 +3,11 @@ import {expect} from 'chai';
|
|
3
3
|
import {httpRequest} from './request-context-decorator.js';
|
4
4
|
import {httpResponse} from './request-context-decorator.js';
|
5
5
|
import {requestContext} from './request-context-decorator.js';
|
6
|
+
import {requestContainer} from './request-context-decorator.js';
|
6
7
|
import {RequestContextReflector} from './request-context-reflector.js';
|
7
8
|
|
8
9
|
describe('requestContext', function () {
|
9
|
-
it('
|
10
|
-
expect(httpRequest).to.be.instanceOf(Function);
|
11
|
-
expect(httpResponse).to.be.instanceOf(Function);
|
12
|
-
});
|
13
|
-
|
14
|
-
it('does not require options', function () {
|
10
|
+
it('should not require options', function () {
|
15
11
|
class Target {
|
16
12
|
method(
|
17
13
|
@requestContext()
|
@@ -22,7 +18,7 @@ describe('requestContext', function () {
|
|
22
18
|
expect(res.get(0)).to.be.eql({property: undefined});
|
23
19
|
});
|
24
20
|
|
25
|
-
it('
|
21
|
+
it('should set the given property to target metadata', function () {
|
26
22
|
class Target {
|
27
23
|
method(
|
28
24
|
@requestContext('res')
|
@@ -32,4 +28,43 @@ describe('requestContext', function () {
|
|
32
28
|
const res = RequestContextReflector.getMetadata(Target, 'method');
|
33
29
|
expect(res.get(0)).to.be.eql({property: 'res'});
|
34
30
|
});
|
31
|
+
|
32
|
+
describe('httpRequest', function () {
|
33
|
+
it('should set the "req" property to target metadata', function () {
|
34
|
+
class Target {
|
35
|
+
method(
|
36
|
+
@httpRequest()
|
37
|
+
prop: unknown,
|
38
|
+
) {}
|
39
|
+
}
|
40
|
+
const res = RequestContextReflector.getMetadata(Target, 'method');
|
41
|
+
expect(res.get(0)).to.be.eql({property: 'req'});
|
42
|
+
});
|
43
|
+
});
|
44
|
+
|
45
|
+
describe('httpResponse', function () {
|
46
|
+
it('should set the "res" property to target metadata', function () {
|
47
|
+
class Target {
|
48
|
+
method(
|
49
|
+
@httpResponse()
|
50
|
+
prop: unknown,
|
51
|
+
) {}
|
52
|
+
}
|
53
|
+
const res = RequestContextReflector.getMetadata(Target, 'method');
|
54
|
+
expect(res.get(0)).to.be.eql({property: 'res'});
|
55
|
+
});
|
56
|
+
});
|
57
|
+
|
58
|
+
describe('requestContainer', function () {
|
59
|
+
it('should set the "container" property to target metadata', function () {
|
60
|
+
class Target {
|
61
|
+
method(
|
62
|
+
@requestContainer()
|
63
|
+
prop: unknown,
|
64
|
+
) {}
|
65
|
+
}
|
66
|
+
const res = RequestContextReflector.getMetadata(Target, 'method');
|
67
|
+
expect(res.get(0)).to.be.eql({property: 'container'});
|
68
|
+
});
|
69
|
+
});
|
35
70
|
});
|