@e22m4u/ts-rest-router 0.5.0 → 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.
Files changed (2) hide show
  1. package/README.md +114 -1
  2. package/package.json +10 -10
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
  ) {
@@ -345,6 +346,118 @@ class UserController { // класс контроллера
345
346
  - `pathname: string` путь запроса, например `/myMath`
346
347
  - `body: unknown` тело запроса
347
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
+
348
461
  ## Отладка
349
462
 
350
463
  Установка переменной `DEBUG` включает вывод логов.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e22m4u/ts-rest-router",
3
- "version": "0.5.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.1.8",
46
- "@e22m4u/js-service": "~0.3.6",
47
- "@e22m4u/js-trie-router": "~0.1.3",
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.34.0",
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.3.0",
60
+ "@types/node": "~24.5.2",
61
61
  "c8": "~10.1.3",
62
62
  "chai": "~6.0.1",
63
- "esbuild": "~0.25.9",
64
- "eslint": "~9.34.0",
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.1",
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.41.0"
74
+ "typescript-eslint": "~8.44.0"
75
75
  }
76
76
  }