@e22m4u/ts-rest-router 0.4.3 → 0.5.1
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 +115 -1
- 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 +10 -10
- 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
@@ -17,6 +17,7 @@ REST маршрутизатор на основе контроллеров дл
|
|
17
17
|
- [Базовый пример](#базовый-пример)
|
18
18
|
- [Валидация](#валидация)
|
19
19
|
- [Декораторы](#декораторы)
|
20
|
+
- [Жизненный цикл контроллера](#жизненный-цикл-контроллера)
|
20
21
|
- [Отладка](#отладка)
|
21
22
|
- [Тесты](#тесты)
|
22
23
|
|
@@ -58,7 +59,7 @@ class UserController {
|
|
58
59
|
@postAction('login')
|
59
60
|
async login(
|
60
61
|
// инъекция значений указанных полей
|
61
|
-
//
|
62
|
+
// извлекаемых из тела запроса
|
62
63
|
@requestField('username') username?: string,
|
63
64
|
@requestField('password') password?: string,
|
64
65
|
) {
|
@@ -209,6 +210,7 @@ class UserController {
|
|
209
210
|
- `@requestCookie` - определенный cookie запроса;
|
210
211
|
- `@requestCookies` - все cookies запроса как объект;
|
211
212
|
- `@requestContext` - доступ к контексту запроса;
|
213
|
+
- `@requestContainer` - сервис-контейнер запроса;
|
212
214
|
- `@requestData` - доступ к данным запроса;
|
213
215
|
- `@httpRequest` - экземпляр `IncomingMessage`;
|
214
216
|
- `@httpResponse` - экземпляр `ServerResponse`;
|
@@ -344,6 +346,118 @@ class UserController { // класс контроллера
|
|
344
346
|
- `pathname: string` путь запроса, например `/myMath`
|
345
347
|
- `body: unknown` тело запроса
|
346
348
|
|
349
|
+
## Жизненный цикл контроллера
|
350
|
+
|
351
|
+
Важной архитектурной особенностью модуля является управление жизненным циклом
|
352
|
+
контроллеров, основанное на библиотеке
|
353
|
+
[@e22m4u/js-service](https://www.npmjs.com/package/@e22m4u/js-service).
|
354
|
+
|
355
|
+
#### Изоляция запросов
|
356
|
+
|
357
|
+
Для каждого входящего HTTP-запроса фреймворк создает **новый, изолированный
|
358
|
+
экземпляр контроллера**. Это гарантирует, что состояние одного запроса
|
359
|
+
(например, данные пользователя или временные вычисления) никогда не "протечет"
|
360
|
+
в другой, одновременно обрабатываемый запрос. Такой подход устраняет целый
|
361
|
+
класс потенциальных ошибок, связанных с состоянием гонки (race conditions).
|
362
|
+
|
363
|
+
#### Сервис-контейнер запроса
|
364
|
+
|
365
|
+
Каждый экземпляр контроллера создается с помощью сервис-контейнера, который
|
366
|
+
"живет" только в рамках одного запроса. Чтобы контроллер мог взаимодействовать
|
367
|
+
с другими сервисами, он должен наследоваться от базового класса `Service`.
|
368
|
+
Это дает ему доступ к методу `this.getService()` для получения зависимостей.
|
369
|
+
|
370
|
+
Рассмотрим пример с сервисом аутентификации. Наша задача - идентифицировать
|
371
|
+
пользователя в Middleware и сделать информацию о нем доступной в контроллере.
|
372
|
+
|
373
|
+
**1. Создание Middleware для подготовки сервиса**
|
374
|
+
|
375
|
+
Middleware - идеальное место для подготовки сервисов, специфичных для запроса.
|
376
|
+
В данном примере мы вручную создаем экземпляр `AuthService`, выполняем
|
377
|
+
аутентификацию, а затем регистрируем этот экземпляр в контейнере запроса
|
378
|
+
с помощью метода `.set()`.
|
379
|
+
|
380
|
+
```ts
|
381
|
+
// src/auth.middleware.ts
|
382
|
+
import {AuthService} from './auth.service.ts';
|
383
|
+
import {RequestContext} from '@e22m4u/js-trie-router';
|
384
|
+
|
385
|
+
export async function authMiddleware(context: RequestContext) {
|
386
|
+
// Получение контейнера текущего запроса
|
387
|
+
const requestContainer = context.container;
|
388
|
+
// Создание нового экземпляра AuthService
|
389
|
+
const authService = new AuthService();
|
390
|
+
// Регистрация созданного экземпляра в контейнере.
|
391
|
+
// Теперь любой другой сервис в рамках этого запроса
|
392
|
+
// сможет получить этот конкретный экземпляр.
|
393
|
+
requestContainer.set(AuthService, authService);
|
394
|
+
// Выполнение логики аутентификации (например, по токену)
|
395
|
+
await authService.authenticate(context.headers.authorization);
|
396
|
+
}
|
397
|
+
```
|
398
|
+
|
399
|
+
**2. Создание `AuthService`**
|
400
|
+
|
401
|
+
В этом примере `AuthService` - это простой класс. Он хранит состояние
|
402
|
+
(`currentUser`), которое будет установлено в middleware.
|
403
|
+
|
404
|
+
```ts
|
405
|
+
// src/auth.service.ts
|
406
|
+
export class AuthService {
|
407
|
+
public currentUser?: { id: number; name: string; };
|
408
|
+
|
409
|
+
async authenticate(token?: string) {
|
410
|
+
// Здесь ваша логика проверки токена и поиска пользователя в БД...
|
411
|
+
if (token === 'valid-token') {
|
412
|
+
this.currentUser = { id: 1, name: 'John Doe' };
|
413
|
+
}
|
414
|
+
}
|
415
|
+
}
|
416
|
+
```
|
417
|
+
|
418
|
+
*Примечание: если бы `AuthService` сам наследовал `Service` (чтобы, например,
|
419
|
+
использовать `this.getService()` внутри), то его конструктор нужно было бы
|
420
|
+
вызывать с передачей контейнера: `new AuthService(requestContainer)`.*
|
421
|
+
|
422
|
+
**3. Использование сервиса в контроллере**
|
423
|
+
|
424
|
+
Контроллер, унаследованный от `Service`, теперь может получить доступ
|
425
|
+
к предварительно настроенному экземпляру `AuthService` через `this.getService()`.
|
426
|
+
Поскольку middleware уже выполнил `.set(AuthService, authService)`,
|
427
|
+
вызов `this.getService(AuthService)` вернет именно тот экземпляр, который
|
428
|
+
был создан и настроен на предыдущем шаге.
|
429
|
+
|
430
|
+
```ts
|
431
|
+
// src/profile.controller.ts
|
432
|
+
import {createError} from 'http-errors';
|
433
|
+
import {Service} from '@e22m4u/js-service';
|
434
|
+
import {AuthService} from './auth.service.ts';
|
435
|
+
import {getAction} from '@e22m4u/ts-rest-router';
|
436
|
+
import {beforeAction} from '@e22m4u/ts-rest-router';
|
437
|
+
import {authMiddleware} from './auth.middleware.ts';
|
438
|
+
import {restController} from '@e22m4u/ts-rest-router';
|
439
|
+
|
440
|
+
@restController('profile')
|
441
|
+
@beforeAction(authMiddleware) // применение middleware ко всем методам контроллера
|
442
|
+
export class ProfileController extends Service {
|
443
|
+
@getAction('me')
|
444
|
+
getProfile() {
|
445
|
+
// Получение request-scoped экземпляра AuthService,
|
446
|
+
// который был создан и зарегистрирован в middleware.
|
447
|
+
const authService = this.getService(AuthService);
|
448
|
+
|
449
|
+
if (!authService.currentUser)
|
450
|
+
throw createError(401, 'Unauthorized');
|
451
|
+
|
452
|
+
return authService.currentUser;
|
453
|
+
}
|
454
|
+
}
|
455
|
+
```
|
456
|
+
|
457
|
+
Таким образом, контейнер запроса выступает в роли моста между middleware
|
458
|
+
и контроллером, позволяя безопасно передавать состояние, изолированное
|
459
|
+
в рамках одного HTTP-запроса.
|
460
|
+
|
347
461
|
## Отладка
|
348
462
|
|
349
463
|
Установка переменной `DEBUG` включает вывод логов.
|
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
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@e22m4u/ts-rest-router",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.5.1",
|
4
4
|
"description": "REST маршрутизатор на основе контроллеров для TypeScript",
|
5
5
|
"author": "Mikhail Evstropov <e22m4u@yandex.ru>",
|
6
6
|
"license": "MIT",
|
@@ -42,9 +42,9 @@
|
|
42
42
|
},
|
43
43
|
"dependencies": {
|
44
44
|
"@e22m4u/js-debug": "~0.2.2",
|
45
|
-
"@e22m4u/js-format": "~0.
|
46
|
-
"@e22m4u/js-service": "~0.3.
|
47
|
-
"@e22m4u/js-trie-router": "~0.1.
|
45
|
+
"@e22m4u/js-format": "~0.2.0",
|
46
|
+
"@e22m4u/js-service": "~0.3.7",
|
47
|
+
"@e22m4u/js-trie-router": "~0.1.4",
|
48
48
|
"@e22m4u/ts-data-schema": "~0.3.1",
|
49
49
|
"@e22m4u/ts-reflector": "~0.1.7",
|
50
50
|
"http-errors": "~2.0.0"
|
@@ -52,25 +52,25 @@
|
|
52
52
|
"devDependencies": {
|
53
53
|
"@commitlint/cli": "~19.8.1",
|
54
54
|
"@commitlint/config-conventional": "~19.8.1",
|
55
|
-
"@eslint/js": "~9.
|
55
|
+
"@eslint/js": "~9.35.0",
|
56
56
|
"@types/chai": "~5.2.2",
|
57
57
|
"@types/debug": "~4.1.12",
|
58
58
|
"@types/http-errors": "~2.0.5",
|
59
59
|
"@types/mocha": "~10.0.10",
|
60
|
-
"@types/node": "~24.
|
60
|
+
"@types/node": "~24.5.2",
|
61
61
|
"c8": "~10.1.3",
|
62
62
|
"chai": "~6.0.1",
|
63
|
-
"esbuild": "~0.25.
|
64
|
-
"eslint": "~9.
|
63
|
+
"esbuild": "~0.25.10",
|
64
|
+
"eslint": "~9.35.0",
|
65
65
|
"eslint-config-prettier": "~10.1.8",
|
66
66
|
"eslint-plugin-chai-expect": "~3.1.0",
|
67
67
|
"eslint-plugin-mocha": "~11.1.0",
|
68
68
|
"husky": "~9.1.7",
|
69
|
-
"mocha": "~11.7.
|
69
|
+
"mocha": "~11.7.2",
|
70
70
|
"prettier": "~3.6.2",
|
71
71
|
"rimraf": "~6.0.1",
|
72
72
|
"tsx": "~4.20.5",
|
73
73
|
"typescript": "~5.9.2",
|
74
|
-
"typescript-eslint": "~8.
|
74
|
+
"typescript-eslint": "~8.44.0"
|
75
75
|
}
|
76
76
|
}
|
@@ -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
|
});
|