@e22m4u/ts-rest-router 0.5.0 → 0.5.2

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 (24) hide show
  1. package/README.md +535 -238
  2. package/dist/cjs/index.cjs +14 -14
  3. package/dist/esm/controller-registry.js +8 -8
  4. package/dist/esm/decorators/after-action/after-action-decorator.d.ts +2 -2
  5. package/dist/esm/decorators/after-action/after-action-decorator.js +4 -4
  6. package/dist/esm/decorators/after-action/after-action-decorator.spec.js +22 -25
  7. package/dist/esm/decorators/after-action/after-action-metadata.d.ts +1 -1
  8. package/dist/esm/decorators/after-action/after-action-reflector.spec.js +15 -15
  9. package/dist/esm/decorators/before-action/before-action-decorator.d.ts +2 -2
  10. package/dist/esm/decorators/before-action/before-action-decorator.js +4 -4
  11. package/dist/esm/decorators/before-action/before-action-decorator.spec.js +22 -25
  12. package/dist/esm/decorators/before-action/before-action-metadata.d.ts +1 -1
  13. package/dist/esm/decorators/before-action/before-action-reflector.spec.js +15 -15
  14. package/package.json +13 -13
  15. package/src/controller-registry.spec.ts +21 -21
  16. package/src/controller-registry.ts +8 -8
  17. package/src/decorators/after-action/after-action-decorator.spec.ts +22 -25
  18. package/src/decorators/after-action/after-action-decorator.ts +4 -4
  19. package/src/decorators/after-action/after-action-metadata.ts +1 -1
  20. package/src/decorators/after-action/after-action-reflector.spec.ts +15 -15
  21. package/src/decorators/before-action/before-action-decorator.spec.ts +22 -25
  22. package/src/decorators/before-action/before-action-decorator.ts +4 -4
  23. package/src/decorators/before-action/before-action-metadata.ts +1 -1
  24. package/src/decorators/before-action/before-action-reflector.spec.ts +15 -15
package/README.md CHANGED
@@ -1,24 +1,56 @@
1
1
  # @e22m4u/ts-rest-router
2
2
 
3
- REST маршрутизатор на основе контроллеров для TypeScript.
4
-
5
- #### Основные возможности
6
-
7
- - Декларативное определение маршрутов через декораторы.
8
- - Типизированные параметры запросов (body, query, params).
9
- - Поддержка middleware до и после обработки запроса.
10
- - Валидация входящих данных.
11
- - Поддержка всех HTTP методов (GET, POST, PUT, PATCH и DELETE).
3
+ **REST-маршрутизатор на основе контроллеров и TypeScript декораторов.**
4
+
5
+ Модуль `@e22m4u/ts-rest-router` позволяет создавать структурированное
6
+ и масштабируемое REST API. В его основе лежит декларативный подход
7
+ с использованием TypeScript декораторов для определения маршрутов,
8
+ обработки входящих данных и управления жизненным циклом запроса.
9
+
10
+ ### Особенности
11
+
12
+ - **Декларативная маршрутизация**
13
+ Определение маршрутов непосредственно над методами контроллера с помощью
14
+ декораторов (`@getAction`, `@postAction` и т.д.).
15
+ - **Типобезопасная обработка данных**
16
+ Автоматическое извлечение, преобразование и валидация данных из `body`,
17
+ `query`, `params`, `headers` и `cookie` с привязкой к типизированным
18
+ аргументам методов.
19
+ - **Встроенная валидация**
20
+ Использование схем данных из
21
+ [@e22m4u/ts-data-schema](https://www.npmjs.com/package/@e22m4u/ts-data-schema)
22
+ для описания сложных правил проверки.
23
+ - **Хуки (`@beforeAction`, `@afterAction`)**
24
+ Поддержка хуков для выполнения сквозной логики (например, аутентификация
25
+ или логирование) на уровне контроллера и отдельных методов.
26
+ - **Изоляция запросов**
27
+ Обработка каждого запроса в отдельном DI-контейнере, что гарантирует
28
+ отсутствие конфликтов состояний и повышает надежность приложения.
29
+ - **Производительность и гибкая архитектура**
30
+ Основан на
31
+ [@e22m4u/js-trie-router](https://www.npmjs.com/package/@e22m4u/js-trie-router)
32
+ для маршрутизации на базе *префиксного дерева* и
33
+ [@e22m4u/js-service](https://www.npmjs.com/package/@e22m4u/js-service)
34
+ для внедрения зависимостей.
12
35
 
13
36
  ## Содержание
14
37
 
15
38
  - [Установка](#установка)
16
- - [Поддержка декораторов](#поддержка-декораторов)
17
- - [Базовый пример](#базовый-пример)
18
- - [Валидация](#валидация)
19
- - [Декораторы](#декораторы)
39
+ - [Быстрый старт: Пример сервера](#быстрый-старт-пример-сервера)
40
+ - [Обработка данных запроса](#обработка-данных-запроса)
41
+ - [URL-параметры (`@requestParam`)](#url-параметры-requestparam)
42
+ - [Query-параметры (`@requestQuery`)](#query-параметры-requestquery)
43
+ - [Тело запроса (`@requestBody`, `@requestField`)](#тело-запроса-requestbody-requestfield)
44
+ - [Заголовки и Cookies](#заголовки-и-cookies)
45
+ - [Контекст запроса (`@requestContext`)](#контекст-запроса-requestcontext)
46
+ - [Валидация и схемы данных](#валидация-и-схемы-данных)
47
+ - [Хуки (`@beforeAction`, `@afterAction`)](#хуки-beforeaction-afteraction)
48
+ - [Архитектура: Жизненный цикл контроллера и DI](#архитектура-жизненный-цикл-контроллера-и-di)
49
+ - [Производительность: Префиксное дерево](#производительность-префиксное-дерево)
50
+ - [Полный список декораторов](#полный-список-декораторов)
20
51
  - [Отладка](#отладка)
21
52
  - [Тесты](#тесты)
53
+ - [Лицензия](#лицензия)
22
54
 
23
55
  ## Установка
24
56
 
@@ -26,10 +58,10 @@ REST маршрутизатор на основе контроллеров дл
26
58
  npm install @e22m4u/ts-rest-router
27
59
  ```
28
60
 
29
- #### Поддержка декораторов
61
+ **Поддержка декораторов**
30
62
 
31
- Для включения поддержки декораторов, добавьте указанные
32
- ниже опции в файл `tsconfig.json` вашего проекта.
63
+ Для включения поддержки декораторов, добавьте указанные ниже опции в файл
64
+ `tsconfig.json` вашего проекта.
33
65
 
34
66
  ```json
35
67
  {
@@ -38,327 +70,592 @@ npm install @e22m4u/ts-rest-router
38
70
  }
39
71
  ```
40
72
 
41
- ## Базовый пример
73
+ ## Быстрый старт: Пример сервера
74
+
75
+ Пример создания простого сервера для управления списком пользователей.
42
76
 
43
- Создание контроллера и методов.
77
+ **`user.controller.ts`**
44
78
 
45
79
  ```ts
46
- import {DataType} from '@e22m4u/ts-rest-router';
47
- import {getAction} from '@e22m4u/ts-rest-router';
48
- import {postAction} from '@e22m4u/ts-rest-router';
49
- import {requestField} from '@e22m4u/ts-rest-router';
50
- import {restController} from '@e22m4u/ts-rest-router';
51
-
52
- // объявление контроллера
53
- // и базового пути /users
80
+ import {
81
+ restController,
82
+ getAction,
83
+ postAction,
84
+ requestBody,
85
+ DataType,
86
+ } from '@e22m4u/ts-rest-router';
87
+
88
+ // Временное хранилище данных
89
+ const users = [
90
+ {id: 1, name: 'John Doe'},
91
+ ];
92
+
93
+ // 1. Декоратор @restController определяет класс как контроллер
94
+ // и устанавливает базовый путь для всех его маршрутов.
54
95
  @restController('users')
55
- class UserController {
56
- // объявление метода POST /users/login
57
- // (использует базовый путь контроллера)
58
- @postAction('login')
59
- async login(
60
- // инъекция значений указанных полей
61
- // извлеизвлекаемых из тела запроса
62
- @requestField('username') username?: string,
63
- @requestField('password') password?: string,
96
+ export class UserController {
97
+ // 2. Декоратор @getAction создает маршрут для GET-запросов.
98
+ // Полный путь: GET /users
99
+ @getAction()
100
+ getAllUsers() {
101
+ // Результат автоматически сериализуется в JSON
102
+ return users;
103
+ }
104
+
105
+ // 3. Декоратор @postAction создает маршрут для POST-запросов.
106
+ // Полный путь: POST /users
107
+ @postAction()
108
+ createUser(
109
+ // 4. Декоратор @requestBody извлекает тело запроса,
110
+ // проверяет его по схеме и передает в аргумент `newUser`.
111
+ @requestBody({
112
+ type: DataType.OBJECT,
113
+ properties: {
114
+ name: {
115
+ type: DataType.STRING,
116
+ required: true
117
+ },
118
+ },
119
+ })
120
+ newUser: {name: string},
64
121
  ) {
65
- // так как метод возвращает объект,
66
- // результат будет представлен как
67
- // "Content-Type: application/json"
68
- return {
69
- id: 1,
70
- firstName: 'John',
71
- lastName: 'Doe',
72
- };
122
+ const user = {id: users.length + 1, ...newUser};
123
+ users.push(user);
124
+ // Возвращаемый объект будет отправлен клиенту как JSON.
125
+ return user;
73
126
  }
74
127
  }
75
128
  ```
76
129
 
77
- Регистрация контроллеров и запуск сервера.
130
+ **`index.ts`**
78
131
 
79
132
  ```ts
80
133
  import http from 'http';
134
+ import {UserController} from './user.controller';
81
135
  import {RestRouter} from '@e22m4u/ts-rest-router';
82
136
 
83
- // создание маршрутизатора и регистрация контроллеров
84
- const router = new RestRouter();
85
- router.registerController(UserController);
86
- router.registerController(ProductController);
137
+ async function bootstrap() {
138
+ // Создание экземпляра роутера
139
+ const router = new RestRouter();
140
+ // Регистрация контроллера
141
+ router.addController(UserController);
142
+
143
+ // Создание HTTP-сервера с обработчиком запросов из роутера
144
+ const server = http.createServer(router.requestListener);
145
+
146
+ // Запуск сервера
147
+ server.listen(3000, () => {
148
+ console.log('Server is running on http://localhost:3000');
149
+ console.log('Try GET http://localhost:3000/users');
150
+ console.log(
151
+ 'Try POST http://localhost:3000/users with body {"name": "Jane Doe"}'
152
+ );
153
+ });
154
+ }
155
+
156
+ bootstrap();
157
+ ```
158
+
159
+ ## Обработка данных запроса
160
+
161
+ Модуль предоставляет удобные и типобезопасные механизмы для доступа
162
+ к данным входящего запроса.
163
+
164
+ ### URL-параметры (`@requestParam`)
165
+
166
+ Используются для извлечения динамических частей URL (например, `:id`).
87
167
 
88
- // создание сервера и регистрация обработчика запросов
89
- const server = new http.Server();
90
- server.on('request', router.requestListener);
168
+ ```ts
169
+ import {getAction, requestParam, DataType} from '@e22m4u/ts-rest-router';
91
170
 
92
- // запуск сервера
93
- server.listen('8080', '0.0.0.0', () => {
94
- console.log(`Server is running on http://localhost:8080`);
95
- });
171
+ @restController('articles')
172
+ class ArticleController {
173
+ // Маршрут: GET /articles/42
174
+ @getAction(':id')
175
+ getArticleById(
176
+ // Извлечение параметра 'id' с проверкой на соответствие
177
+ // типу "number"
178
+ @requestParam('id', DataType.NUMBER) id: number,
179
+ ) {
180
+ // Если id не является числом, фреймворк вернет ошибку
181
+ // 400 Bad Request
182
+ return {articleId: id, content: '...'};
183
+ }
184
+ }
96
185
  ```
97
186
 
98
- ## Валидация
99
-
100
- Указанные ниже декораторы используются для инъекции соответствующих параметров
101
- запроса в качестве аргументов метода контроллера. Каждый из указанных декораторов
102
- имеет параметр `schemaOrType`, в котором определяется тип ожидаемого значения
103
- или схема для проверки данных.
104
-
105
- - `@requestParam(name: string, schemaOrType?: DataSchema | DataType)`
106
- *- извлечение URL параметра по названию;*
107
- - `@requestQuery(name: string, schemaOrType?: DataSchema | DataType)`
108
- *- извлечение query параметра по названию;*
109
- - `@requestBody(schemaOrType?: DataSchema | DataType)`
110
- *- извлечение тела запроса;*
111
- - `@requestField(name: string, schemaOrType?: DataSchema | DataType)`
112
- *- извлечение свойства из тела запроса;*
113
- - `@requestHeader(name: string, schemaOrType?: DataSchema | DataType)`
114
- *- извлечение заголовка запроса по названию;*
115
- - `@requestCookie(name: string, schemaOrType?: DataSchema | DataType)`
116
- *- извлечение cookie запроса по названию;*
117
-
118
- Проверка входящих данных выполняется встроенным модулем
119
- [@e22m4u/ts-data-schema](https://www.npmjs.com/package/@e22m4u/ts-data-schema)
120
- (не требует установки). Ниже приводятся константы для определения допустимых
121
- типов извлекаемого значения.
187
+ **Декораторы:**
122
188
 
123
- - `DataType.ANY` - принимает любой тип
124
- - `DataType.STRING` - строковые значения
125
- - `DataType.NUMBER` - числовые значения
126
- - `DataType.BOOLEAN` - логические значения
127
- - `DataType.ARRAY` - массивы
128
- - `DataType.OBJECT` - объекты (не экземпляры)
189
+ - `@requestParam(name, schema)` - извлечение одного параметра;
190
+ - `@requestParams(schema)` - извлечение всех URL-параметров в виде объекта;
129
191
 
130
- Для определения дополнительных условий, используется объект `DataSchema`,
131
- с помощью которого можно определить структуру ожидаемого объекта, допустимые
132
- элементы массива, функции-валидаторы и другие ограничения входящих данных.
192
+ ### Query-параметры (`@requestQuery`)
193
+
194
+ Применяются для извлечения параметров из строки запроса
195
+ (например, `?sort=desc`).
133
196
 
134
197
  ```ts
135
- type DataSchema = {
136
- type: DataType;
137
- items?: DataSchema;
138
- properties?: {[key: string]: DataSchema};
139
- required?: boolean;
140
- validate?: CallableValidator | CallableValidator[];
141
- default?: unknown;
198
+ import {getAction, requestQuery, DataType} from '@e22m4u/ts-rest-router';
199
+
200
+ @restController('products')
201
+ class ProductController {
202
+ // Маршрут: GET /products/search?q=phone&limit=10
203
+ @getAction('search')
204
+ searchProducts(
205
+ @requestQuery('q', {
206
+ type: DataType.STRING,
207
+ required: true,
208
+ })
209
+ searchTerm: string,
210
+ @requestQuery('limit', {
211
+ type: DataType.NUMBER,
212
+ default: 20,
213
+ })
214
+ limit: number,
215
+ ) {
216
+ // searchTerm будет 'phone', limit будет 10.
217
+ // При отсутствии 'q' будет ошибка. При отсутствии 'limit'
218
+ // будет использовано значение по умолчанию 20.
219
+ return {results: [], query: {searchTerm, limit}};
220
+ }
142
221
  }
143
222
  ```
144
223
 
145
- Пример проверки передаваемого объекта методом POST:
224
+ **Декораторы:**
225
+
226
+ - `@requestQuery(name, schema)` - извлечение одного query-параметра;
227
+ - `@requestQueries(schema)` - извлечение всех query-параметров в виде объекта;
228
+
229
+ ### Тело запроса (`@requestBody`, `@requestField`)
230
+
231
+ Для работы с данными, отправленными в теле POST, PUT, PATCH запросов.
146
232
 
147
233
  ```ts
148
- import {DataType} from '@e22m4u/ts-rest-router';
149
- import {getAction} from '@e22m4u/ts-rest-router';
150
- import {postAction} from '@e22m4u/ts-rest-router';
151
- import {requestField} from '@e22m4u/ts-rest-router';
152
- import {restController} from '@e22m4u/ts-rest-router';
234
+ import {
235
+ postAction,
236
+ requestBody,
237
+ requestField,
238
+ DataType,
239
+ } from '@e22m4u/ts-rest-router';
153
240
 
154
241
  @restController('users')
155
242
  class UserController {
156
- @postAction() // POST /users
157
- async create(
158
- @requestBody({ // декоратор тела запроса
159
- type: DataType.OBJECT, // в теле запроса ожидается объект
243
+ // Пример с @requestBody: получение и валидация всего тела запроса
244
+ @postAction()
245
+ createUser(
246
+ @requestBody({
247
+ type: DataType.OBJECT,
160
248
  properties: {
161
- name: { // схема свойства "name"
162
- type: DataType.STRING, // свойство должно содержать строку
163
- required: true, // свойство не может содержать undefined или null
164
- validate: v => v.length > 2, // проверка длины строки
249
+ username: {
250
+ type: DataType.STRING,
251
+ required: true,
252
+ },
253
+ email: {
254
+ type: DataType.STRING,
255
+ required: true,
165
256
  },
166
- age: { // схема свойства "age"
167
- type: DataType.NUMBER, // свойство должно являться числом
168
- }
169
257
  },
170
258
  })
171
- body: {name: string, age?: number},
259
+ body: {username: string; email: string},
260
+ ) {
261
+ return {id: 1, ...body};
262
+ }
263
+
264
+ // Пример с @requestField: получение только одного поля из тела
265
+ @postAction('login')
266
+ login(
267
+ @requestField('username', DataType.STRING) username: string,
268
+ @requestField('password', DataType.STRING) password: string,
172
269
  ) {
173
- return {
174
- id: 1,
175
- name: body.name,
176
- age: body.age,
177
- };
270
+ // ... логика аутентификации
271
+ return {token: 'jwt-token'};
178
272
  }
179
273
  }
180
274
  ```
181
275
 
182
- ## Декораторы
183
-
184
- Контроллер и методы:
276
+ **Декораторы:**
185
277
 
186
- - `@restController` - определяет класс как контроллер;
187
- - `@restAction` - базовый декоратор для методов;
188
- - `@getAction` - метод GET;
189
- - `@postAction` - метод POST;
190
- - `@putAction` - метод PUT;
191
- - `@patchAction` - метод PATCH;
192
- - `@deleteAction` - метод DELETE;
278
+ - `@requestBody(schema)` - извлечение тела запроса;
279
+ - `@requestField(name, schema)` - извлечение отдельного поля из тела запроса;
193
280
 
194
- Хуки запроса:
281
+ ### Заголовки и Cookies
195
282
 
196
- - `@beforeAction` - middleware перед обработкой запроса;
197
- - `@afterAction` - middleware после обработки запроса;
283
+ Работа с заголовками и cookies осуществляется аналогичным образом:
198
284
 
199
- Параметры запроса:
285
+ - `@requestHeader(name, schema)` / `@requestHeaders(schema)`
286
+ - `@requestCookie(name, schema)` / `@requestCookies(schema)`
200
287
 
201
- - `@requestParam` - определенный URL параметр;
202
- - `@requestParams` - все параметры URL как объект;
203
- - `@requestQuery` - определенный query параметр;
204
- - `@requestQueries` - все query параметры как объект;
205
- - `@requestBody` - тело запроса;
206
- - `@requestField` - поле в теле запроса;
207
- - `@requestHeader` - определенный заголовок запроса;
208
- - `@requestHeaders` - все заголовки запроса как объект;
209
- - `@requestCookie` - определенный cookie запроса;
210
- - `@requestCookies` - все cookies запроса как объект;
211
- - `@requestContext` - доступ к контексту запроса;
212
- - `@requestContainer` - сервис-контейнер запроса;
213
- - `@requestData` - доступ к данным запроса;
214
- - `@httpRequest` - экземпляр `IncomingMessage`;
215
- - `@httpResponse` - экземпляр `ServerResponse`;
288
+ ### Контекст запроса (`@requestContext`)
216
289
 
217
- #### `@restController(options?: ControllerOptions)`
290
+ Для доступа к "сырым" объектам запроса/ответа Node.js или другим частям
291
+ контекста используются следующие декораторы:
218
292
 
219
- Определение контроллера.
293
+ - `@requestContext()` - инъекция всего объекта `RequestContext`;
294
+ - `@requestContext('req')` - инъекция нативного `IncomingMessage`;
295
+ - `@requestContext('res')` - инъекция нативного `ServerResponse`;
296
+ - `@requestContext('container')` - инъекция DI-контейнера запроса;
297
+ - **Алиасы:** `@httpRequest()`, `@httpResponse()`, `@requestContainer()`;
220
298
 
221
299
  ```ts
222
- @restController()
223
- class UserController {
224
- // ...
300
+ import {getAction, requestContext} from '@e22m4u/ts-rest-router';
301
+ import {RequestContext} from '@e22m4u/js-trie-router';
302
+
303
+ @restController('system')
304
+ class SystemController {
305
+ @getAction('ip')
306
+ getClientIp(
307
+ @requestContext()
308
+ ctx: RequestContext,
309
+ ) {
310
+ // ctx.req - это нативный объект IncomingMessage
311
+ const ip = ctx.req.socket.remoteAddress;
312
+ return {ip};
313
+ }
225
314
  }
226
315
  ```
227
316
 
228
- Определение базового пути.
317
+ ## Валидация и схемы данных
318
+
319
+ Модуль интегрирован с
320
+ [@e22m4u/ts-data-schema](https://www.npmjs.com/package/@e22m4u/ts-data-schema)
321
+ для гибкой проверки данных. Это дает возможность определять типы данных
322
+ и сложные правила.
323
+
324
+ **Базовые типы `DataType`:**
325
+
326
+ - `DataType.ANY`
327
+ - `DataType.STRING`
328
+ - `DataType.NUMBER`
329
+ - `DataType.BOOLEAN`
330
+ - `DataType.ARRAY`
331
+ - `DataType.OBJECT`
332
+
333
+ Для более сложных проверок используется объект `DataSchema`:
229
334
 
230
335
  ```ts
231
- @restController('users')
232
- class UserController {
233
- // ...
336
+ type DataSchema = {
337
+ type: DataType; // Обязательное поле
338
+ required?: boolean; // Значение не может быть null или undefined
339
+ default?: unknown; // Значение по умолчанию, если `required: false`
340
+ items?: DataSchema; // Схема для элементов массива (для type: DataType.ARRAY)
341
+ properties?: {[key: string]: DataSchema}; // Схема для свойств объекта
342
+ validate?: (value: any) => boolean | string; // Пользовательская функция
234
343
  }
235
344
  ```
236
345
 
237
- Дополнительные параметры декоратора.
346
+ **Пример сложной валидации:**
238
347
 
239
348
  ```ts
240
- @restController({
241
- path: 'api', // базовый путь
242
- before: [authMiddleware], // middleware до обработки запроса
243
- after: [loggerMiddleware], // middleware после обработки запроса
244
- })
245
- class UserController {
246
- // ...
349
+ import {postAction, requestBody, DataType} from '@e22m4u/ts-rest-router';
350
+
351
+ @restController('orders')
352
+ class OrderController {
353
+ @postAction()
354
+ createOrder(
355
+ @requestBody({
356
+ type: DataType.OBJECT,
357
+ properties: {
358
+ userId: {
359
+ type: DataType.NUMBER,
360
+ required: true,
361
+ },
362
+ products: {
363
+ type: DataType.ARRAY,
364
+ required: true,
365
+ // Описание схемы для каждого элемента массива:
366
+ items: {
367
+ type: DataType.OBJECT,
368
+ properties: {
369
+ id: {
370
+ type: DataType.NUMBER,
371
+ required: true
372
+ },
373
+ quantity: {
374
+ type: DataType.NUMBER,
375
+ required: true,
376
+ // Валидатор: количество должно быть больше 0
377
+ validate: (qty) => qty > 0 || 'Quantity must be positive',
378
+ },
379
+ },
380
+ },
381
+ },
382
+ },
383
+ })
384
+ orderData: { /* ... */ },
385
+ ) {
386
+ // ...
387
+ }
247
388
  }
248
389
  ```
249
390
 
250
- #### `@getAction(path: string, options?: ActionOptions)`
391
+ ## Хуки (`@beforeAction`, `@afterAction`)
392
+
393
+ Хуки — это функции, выполняющиеся до (`@beforeAction`) или после
394
+ (`@afterAction`) основного метода контроллера. Они предназначены для
395
+ сквозной логики, такой как аутентификация, логирование или модификация
396
+ ответа.
251
397
 
252
- Определение метода GET.
398
+ Применение хуков возможно как ко всему контроллеру, так и к отдельному
399
+ методу.
253
400
 
254
401
  ```ts
255
- @restController('users')
256
- class UserController {
257
- @getAction('whoAmI') // маршрут GET /users/whoAmI
258
- async whoAmI() {
259
- return { // если метод возвращает объект,
260
- name: 'John', // то результат будет представлен
261
- surname: 'Doe', // как "Content-Type: application/json"
262
- };
402
+ import {RequestContext} from '@e22m4u/js-trie-router';
403
+ import createError from 'http-errors';
404
+
405
+ // Хук для проверки аутентификации
406
+ async function authHook(ctx: RequestContext) {
407
+ const token = ctx.headers.authorization;
408
+ if (!token /* || !isValidToken(token) */) {
409
+ // Выброс ошибки прерывает выполнение и отправляет клиенту
410
+ // соответствующий HTTP-статус.
411
+ throw createError(401, 'Unauthorized');
263
412
  }
264
413
  }
265
- ```
266
414
 
267
- Дополнительные параметры декоратора.
415
+ // Хук для логирования и модификации ответа
416
+ async function loggerHook(ctx: RequestContext, data: any) {
417
+ console.log(`Response for ${ctx.pathname}:`, data);
418
+ // Хуки @afterAction могут модифицировать ответ
419
+ return {...data, timestamp: new Date()};
420
+ }
268
421
 
269
- ```ts
270
- @restController('users')
271
- class UserController {
272
- @getAction('whoAmI', { // маршрут GET /users/whoAmI
273
- before: [authMiddleware], // middleware до обработки запроса
274
- after: [loggerMiddleware], // middleware после обработки запроса
275
- })
276
- async whoAmI() {
277
- return {
278
- name: 'John',
279
- surname: 'Doe',
280
- };
422
+ @restController('profile')
423
+ @beforeAction(authHook) // Применение ко всем методам контроллера
424
+ class ProfileController {
425
+ @getAction('me')
426
+ @afterAction(loggerHook) // Применение только к этому методу
427
+ getMyProfile() {
428
+ return {id: 1, name: 'Current User'};
429
+ }
430
+
431
+ @getAction('settings')
432
+ getMySettings() {
433
+ return {theme: 'dark'};
281
434
  }
282
435
  }
283
436
  ```
284
437
 
285
- #### `@requestContext(propertyName?: string)`
438
+ ## Архитектура: Жизненный цикл контроллера и DI
286
439
 
287
- Доступ к контексту запроса.
440
+ Понимание архитектурных принципов `ts-rest-router` является ключом
441
+ к созданию надежных и масштабируемых приложений. Модуль построен на
442
+ базе библиотеки `@e22m4u/js-service`, реализующей паттерн
443
+ Service Locator / Dependency Injection.
444
+
445
+ #### Принцип №1: Изоляция запросов
446
+
447
+ Для **каждого** входящего HTTP-запроса создается **новый, изолированный
448
+ экземпляр контроллера**. Этот фундаментальный принцип гарантирует, что
449
+ состояние одного запроса (например, данные аутентифицированного
450
+ пользователя) никогда не будет разделено с другим, одновременно
451
+ обрабатываемым запросом. Это устраняет целый класс потенциальных
452
+ ошибок, связанных с состоянием гонки.
453
+
454
+ #### Принцип №2: Request-Scoped Service Container
455
+
456
+ Каждый экземпляр контроллера создается с помощью своего собственного
457
+ DI-контейнера, который существует только в рамках одного запроса. Чтобы
458
+ контроллер мог взаимодействовать с другими сервисами (например, с сервисом
459
+ для работы с базой данных), его класс должен наследоваться от базового
460
+ класса `Service`. Это дает доступ к методу `this.getService()` для
461
+ получения зависимостей.
462
+
463
+ **Практический пример с сервисом аутентификации:**
464
+
465
+ **Шаг 1: Создание хука для подготовки сервиса**
466
+
467
+ Хуки - идеальное место для подготовки сервисов, специфичных для запроса.
468
+ В этом примере хук `@beforeAction` создает экземпляр `AuthService`,
469
+ выполняет аутентификацию и регистрирует его в контейнере запроса.
288
470
 
289
471
  ```ts
472
+ // src/auth.hook.ts
473
+ import {AuthService} from './auth.service';
290
474
  import {RequestContext} from '@e22m4u/js-trie-router';
291
475
 
292
- @restController('users')
293
- class UserController {
294
- @getAction(':id')
295
- findById(
296
- @requestContext() // инъекция контекста запроса
297
- ctx: RequestContext, // в качестве аргумента
298
- ) {
299
- console.log(ctx.req); // IncomingMessage
300
- console.log(ctx.res); // ServerResponse
301
- console.log(ctx.params); // {id: 10}
302
- console.log(ctx.query); // {include: 'city'}
303
- console.log(ctx.headers); // {cookie: 'foo=bar; baz=qux;'}
304
- console.log(ctx.cookie); // {foo: 'bar', baz: 'qux'}
305
- console.log(ctx.method); // "GET"
306
- console.log(ctx.path); // "/users/10?include=city"
307
- console.log(ctx.pathname); // "/users/10"
308
- // ...
309
- }
476
+ export async function authHook(context: RequestContext) {
477
+ const requestContainer = context.container;
478
+ // Создание сервиса с передачей ему контейнера запроса
479
+ const authService = new AuthService(requestContainer);
480
+ // Регистрация созданного экземпляра в контейнере.
481
+ // Теперь любой другой сервис в рамках этого запроса
482
+ // сможет получить этот конкретный экземпляр.
483
+ requestContainer.set(AuthService, authService);
484
+ // Выполнение логики аутентификации
485
+ await authService.authenticate(context.headers.authorization);
310
486
  }
311
487
  ```
312
488
 
313
- Доступ к свойствам контекста.
489
+ **Шаг 2: Создание `AuthService`**
490
+
491
+ `AuthService` наследуется от `Service`, что позволяет ему запрашивать другие
492
+ зависимости (например, `this.getService(DatabaseService)`).
314
493
 
315
494
  ```ts
316
- import {ServerResponse} from 'http';
317
- import {IncomingMessage} from 'http';
318
-
319
- @restController('/users') // путь контроллера
320
- class UserController { // класс контроллера
321
- @getAction('/:id') // маршрут GET /users/:id
322
- findById(
323
- @requestContext('req') // декоратор контекста запроса
324
- req: IncomingMessage, // включающий свойство "req"
325
- @requestContext('res') // декоратор контекста запроса
326
- res: ServerResponse, // включающий свойство "res"
327
- ) {
328
- console.log(req); // IncomingMessage
329
- console.log(res); // ServerResponse
495
+ // src/auth.service.ts
496
+ import {Service} from '@e22m4u/js-service';
497
+
498
+ export class AuthService extends Service {
499
+ public currentUser?: {id: number; name: string;};
500
+
501
+ async authenticate(token?: string) {
502
+ // Логика проверки токена и поиска пользователя в БД...
503
+ if (token === 'valid-token') {
504
+ this.currentUser = { id: 1, name: 'John Doe' };
505
+ }
330
506
  }
331
507
  }
332
508
  ```
333
509
 
334
- Свойства контекста:
510
+ **Шаг 3: Использование сервиса в контроллере**
511
+
512
+ Контроллер, унаследованный от `Service`, теперь может получить
513
+ доступ к предварительно настроенному экземпляру `AuthService`
514
+ через `this.getService()`.
335
515
 
336
- - `container: ServiceContainer` экземпляр [сервис-контейнера](https://npmjs.com/package/@e22m4u/js-service)
337
- - `req: IncomingMessage` нативный поток входящего запроса
338
- - `res: ServerResponse` нативный поток ответа сервера
339
- - `params: ParsedParams` объект ключ-значение с параметрами пути
340
- - `query: ParsedQuery` объект ключ-значение с параметрами строки запроса
341
- - `headers: ParsedHeaders` объект ключ-значение с заголовками запроса
342
- - `cookie: ParsedCookie` объект ключ-значение разобранного заголовка `cookie`
343
- - `method: string` метод запроса в верхнем регистре, например `GET`, `POST` и т.д.
344
- - `path: string` путь включающий строку запроса, например `/myPath?foo=bar`
345
- - `pathname: string` путь запроса, например `/myMath`
346
- - `body: unknown` тело запроса
516
+ ```ts
517
+ // src/profile.controller.ts
518
+ import {authHook} from './auth.hook';
519
+ import createError from 'http-errors';
520
+ import {Service} from '@e22m4u/js-service';
521
+ import {AuthService} from './auth.service';
522
+ import {
523
+ getAction,
524
+ restController,
525
+ beforeAction,
526
+ } from '@e22m4u/ts-rest-router';
527
+
528
+ @beforeAction(authHook)
529
+ @restController('profile')
530
+ export class ProfileController extends Service {
531
+ @getAction('me')
532
+ getProfile() {
533
+ // Получение request-scoped экземпляра AuthService,
534
+ // который был создан и зарегистрирован в хуке.
535
+ const authService = this.getService(AuthService);
536
+
537
+ if (!authService.currentUser)
538
+ throw createError(401, 'Unauthorized');
539
+
540
+ return authService.currentUser;
541
+ }
542
+ }
543
+ ```
544
+
545
+ Таким образом, DI-контейнер запроса выступает в роли моста между хуками
546
+ и контроллером, позволяя безопасно передавать состояние, изолированное
547
+ в рамках одного HTTP-запроса.
548
+
549
+ ## Производительность: Префиксное дерево
550
+
551
+ В основе данного модуля лежит
552
+ [@e22m4u/js-trie-router](https://www.npmjs.com/package/@e22m4u/js-trie-router),
553
+ который использует структуру данных
554
+ **префиксного дерева** ([Trie](https://en.wikipedia.org/wiki/Trie))
555
+ для хранения маршрутов и их сопоставления. Такое архитектурное решение
556
+ обеспечивает высокую производительность, особенно в приложениях
557
+ с большим количеством маршрутов.
558
+
559
+ #### Как это работает?
560
+
561
+ Вместо хранения маршрутов в виде плоского списка, префиксное дерево
562
+ организует их в древовидную структуру. Каждый маршрут, например
563
+ `GET /users/:id/posts`, разбивается на сегменты (`GET`, `users`, `:id`,
564
+ `posts`), где каждый сегмент становится узлом в дереве.
565
+
566
+ Когда поступает новый запрос, например `GET /users/123/posts`,
567
+ маршрутизатор не перебирает все существующие маршруты. Вместо этого
568
+ он последовательно спускается по дереву:
569
+
570
+ 1. Находит корневой узел для метода `GET`.
571
+ 2. От него переходит к дочернему узлу `users`.
572
+ 3. Далее, не найдя статического узла `123`, он ищет динамический
573
+ узел (`:id`) и сопоставляет его, сохраняя `123` как значение
574
+ параметра `id`.
575
+ 4. Наконец, он переходит к узлу `posts` и находит совпадение.
576
+
577
+ #### Преимущества для производительности
578
+
579
+ 1. **Эффективный поиск (O(k))**
580
+ Самое главное преимущество — скорость поиска. Вместо того чтобы
581
+ перебирать массив из `N` маршрутов и проверять каждый с помощью
582
+ регулярного выражения (сложность `O(N)`), префиксное дерево
583
+ находит совпадение за время, пропорциональное количеству сегментов
584
+ `k` в URL-пути (сложность `O(k)`). Это означает, что производительность
585
+ поиска **не падает** с ростом общего числа маршрутов в приложении.
586
+
587
+ 2. **Быстрая обработка 404 (ранний выход)**
588
+ Если приходит запрос на несуществующий путь, например
589
+ `/users/123/comments`, поиск по дереву прекратится сразу после
590
+ того, как маршрутизатор не найдет дочерний узел `comments`
591
+ у узла `:id`. Ему не нужно проверять остальные сотни маршрутов,
592
+ чтобы убедиться, что совпадений нет. Это делает обработку
593
+ ненайденных маршрутов (404) почти мгновенной.
594
+
595
+ 3. **Оптимизация для статических и динамических маршрутов**
596
+ При поиске маршрутизатор всегда отдает приоритет статическим
597
+ сегментам перед динамическими. Маршрут `/users/me` всегда будет
598
+ найден раньше и быстрее, чем `/users/:id` при запросе `/users/me`,
599
+ поскольку не требуется проверка на соответствие шаблону.
600
+
601
+ Этот подход делает `ts-rest-router` производительным решением для крупных
602
+ приложений с большим количеством маршрутов.
603
+
604
+ ## Полный список декораторов
605
+
606
+ #### Контроллер и методы:
607
+
608
+ - `@restController(options)` - определение класса как контроллера;
609
+ - `@getAction(path, options)` - метод GET;
610
+ - `@postAction(path, options)` - метод POST;
611
+ - `@putAction(path, options)` - метод PUT;
612
+ - `@patchAction(path, options)` - метод PATCH;
613
+ - `@deleteAction(path, options)` - метод DELETE;
614
+ - `@restAction(options)` - базовый декоратор для определения метода;
615
+
616
+ #### Хуки:
617
+
618
+ - `@beforeAction(hook)` - выполнение перед методом;
619
+ - `@afterAction(hook)` - выполнение после метода;
620
+
621
+ #### Параметры запроса:
622
+
623
+ - `@requestParam(name, schema)` - URL-параметр;
624
+ - `@requestParams(schema)` - все URL-параметры;
625
+ - `@requestQuery(name, schema)` - query-параметр;
626
+ - `@requestQueries(schema)` - все query-параметры;
627
+ - `@requestBody(schema)` - тело запроса;
628
+ - `@requestField(name, schema)` - поле из тела запроса;
629
+ - `@requestHeader(name, schema)` - заголовок запроса;
630
+ - `@requestHeaders(schema)` - все заголовки;
631
+ - `@requestCookie(name, schema)` - cookie;
632
+ - `@requestCookies(schema)` - все cookies;
633
+ - `@requestData(options)` - базовый декоратор для доступа к данным;
634
+
635
+ #### Контекст:
636
+
637
+ - `@requestContext(property)` - доступ к `RequestContext` или его свойствам;
638
+ - `@requestContainer()` - DI-контейнер запроса;
639
+ - `@httpRequest()` - экземпляр `IncomingMessage`;
640
+ - `@httpResponse()` - экземпляр `ServerResponse`;
347
641
 
348
642
  ## Отладка
349
643
 
350
- Установка переменной `DEBUG` включает вывод логов.
644
+ Включение вывода отладочных логов в консоль осуществляется
645
+ установкой переменной окружения `DEBUG`:
351
646
 
352
647
  ```bash
353
- DEBUG=tsRestRouter* npm run test
648
+ DEBUG=tsRestRouter* npm start
354
649
  ```
355
650
 
356
651
  ## Тесты
357
652
 
653
+ Запуск тестов выполняется командой:
654
+
358
655
  ```bash
359
656
  npm run test
360
657
  ```
361
658
 
362
659
  ## Лицензия
363
660
 
364
- MIT
661
+ MIT