@e22m4u/ts-rest-router 0.6.6 → 0.6.8
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 +298 -210
- package/dist/cjs/index.cjs +8 -5
- package/dist/esm/controller-registry.js +1 -1
- package/dist/esm/decorators/request-data/request-data-decorator.js +2 -2
- package/dist/esm/decorators/request-data/request-data-decorator.spec.js +5 -5
- package/dist/esm/decorators/request-data/request-data-metadata.d.ts +1 -1
- package/dist/esm/decorators/request-data/request-data-metadata.js +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/package.json +9 -9
- package/src/controller-registry.ts +1 -1
- package/src/decorators/request-data/request-data-decorator.spec.ts +5 -5
- package/src/decorators/request-data/request-data-decorator.ts +2 -2
- package/src/decorators/request-data/request-data-metadata.ts +1 -1
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -1,53 +1,31 @@
|
|
|
1
1
|
# @e22m4u/ts-rest-router
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **Декларативная маршрутизация**
|
|
13
|
-
Определение маршрутов непосредственно над методами контроллера с помощью
|
|
14
|
-
декораторов (`@getAction`, `@postAction` и т.д.).
|
|
15
|
-
- **Типобезопасная обработка данных**
|
|
16
|
-
Автоматическое извлечение, преобразование и валидация данных из `body`,
|
|
17
|
-
`query`, `params`, `headers` и `cookie` с привязкой к типизированным
|
|
18
|
-
аргументам методов.
|
|
19
|
-
- **Встроенная валидация**
|
|
20
|
-
Использование схем данных из
|
|
21
|
-
[@e22m4u/js-data-schema](https://www.npmjs.com/package/@e22m4u/js-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
|
-
для внедрения зависимостей.
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
REST-маршрутизатор на основе контроллеров и TypeScript декораторов.
|
|
7
|
+
|
|
8
|
+
Данный модуль позволяет создавать структурированное и масштабируемое REST API.
|
|
9
|
+
В его основе лежит декларативный подход с использованием TypeScript декораторов
|
|
10
|
+
для определения маршрутов, обработки входящих данных и управления жизненным
|
|
11
|
+
циклом запроса.
|
|
35
12
|
|
|
36
13
|
## Содержание
|
|
37
14
|
|
|
38
15
|
- [Установка](#установка)
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [URL-параметры
|
|
42
|
-
- [Query-параметры
|
|
43
|
-
- [Тело запроса
|
|
44
|
-
- [Заголовки
|
|
45
|
-
- [
|
|
46
|
-
- [
|
|
47
|
-
- [
|
|
48
|
-
- [
|
|
49
|
-
- [
|
|
50
|
-
- [
|
|
16
|
+
- [Базовый пример](#базовый-пример)
|
|
17
|
+
- [Данные запроса](#данные-запроса)
|
|
18
|
+
- [URL-параметры](#url-параметры)
|
|
19
|
+
- [Query-параметры](#query-параметры)
|
|
20
|
+
- [Тело запроса](#тело-запроса)
|
|
21
|
+
- [Заголовки](#заголовки)
|
|
22
|
+
- [Cookies](#cookies)
|
|
23
|
+
- [Контекст запроса](#контекст-запроса)
|
|
24
|
+
- [Валидация данных](#валидация-данных)
|
|
25
|
+
- [Хуки](#хуки)
|
|
26
|
+
- [Архитектура](#архитектура)
|
|
27
|
+
- [Производительность](#производительность)
|
|
28
|
+
- [Декораторы](#декораторы)
|
|
51
29
|
- [Отладка](#отладка)
|
|
52
30
|
- [Тесты](#тесты)
|
|
53
31
|
- [Лицензия](#лицензия)
|
|
@@ -70,7 +48,7 @@ npm install @e22m4u/ts-rest-router
|
|
|
70
48
|
}
|
|
71
49
|
```
|
|
72
50
|
|
|
73
|
-
##
|
|
51
|
+
## Базовый пример
|
|
74
52
|
|
|
75
53
|
Пример создания простого сервера для управления списком пользователей.
|
|
76
54
|
|
|
@@ -78,42 +56,40 @@ npm install @e22m4u/ts-rest-router
|
|
|
78
56
|
|
|
79
57
|
```ts
|
|
80
58
|
import {
|
|
81
|
-
|
|
59
|
+
DataType,
|
|
82
60
|
getAction,
|
|
83
61
|
postAction,
|
|
84
62
|
requestBody,
|
|
85
|
-
|
|
63
|
+
restController,
|
|
86
64
|
} from '@e22m4u/ts-rest-router';
|
|
87
65
|
|
|
88
|
-
//
|
|
89
|
-
const users = [
|
|
90
|
-
{id: 1, name: 'John Doe'},
|
|
91
|
-
];
|
|
66
|
+
// временное хранилище данных
|
|
67
|
+
const users = [{id: 1, name: 'John Doe'}];
|
|
92
68
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
69
|
+
// декоратор @restController определяет класс как контроллер
|
|
70
|
+
// и устанавливает базовый путь для всех его маршрутов
|
|
95
71
|
@restController('users')
|
|
96
72
|
export class UserController {
|
|
97
|
-
//
|
|
98
|
-
//
|
|
73
|
+
// декоратор @getAction создает маршрут для GET-запросов,
|
|
74
|
+
// полный путь: GET /users
|
|
99
75
|
@getAction()
|
|
100
76
|
getAllUsers() {
|
|
101
|
-
//
|
|
77
|
+
// результат автоматически сериализуется в JSON
|
|
102
78
|
return users;
|
|
103
79
|
}
|
|
104
80
|
|
|
105
|
-
//
|
|
106
|
-
//
|
|
81
|
+
// декоратор @postAction создает маршрут для POST-запросов,
|
|
82
|
+
// полный путь: POST /users
|
|
107
83
|
@postAction()
|
|
108
84
|
createUser(
|
|
109
|
-
//
|
|
110
|
-
//
|
|
85
|
+
// декоратор @requestBody извлекает тело запроса,
|
|
86
|
+
// проверяет его по схеме и передает в аргумент `newUser`
|
|
111
87
|
@requestBody({
|
|
112
88
|
type: DataType.OBJECT,
|
|
113
89
|
properties: {
|
|
114
90
|
name: {
|
|
115
91
|
type: DataType.STRING,
|
|
116
|
-
required: true
|
|
92
|
+
required: true,
|
|
117
93
|
},
|
|
118
94
|
},
|
|
119
95
|
})
|
|
@@ -121,7 +97,7 @@ export class UserController {
|
|
|
121
97
|
) {
|
|
122
98
|
const user = {id: users.length + 1, ...newUser};
|
|
123
99
|
users.push(user);
|
|
124
|
-
//
|
|
100
|
+
// возвращаемый объект будет отправлен клиенту как JSON
|
|
125
101
|
return user;
|
|
126
102
|
}
|
|
127
103
|
}
|
|
@@ -135,20 +111,19 @@ import {UserController} from './user.controller';
|
|
|
135
111
|
import {RestRouter} from '@e22m4u/ts-rest-router';
|
|
136
112
|
|
|
137
113
|
async function bootstrap() {
|
|
138
|
-
//
|
|
114
|
+
// создание экземпляра роутера
|
|
139
115
|
const router = new RestRouter();
|
|
140
|
-
//
|
|
116
|
+
// регистрация контроллера
|
|
141
117
|
router.addController(UserController);
|
|
142
|
-
|
|
143
|
-
// Создание HTTP-сервера с обработчиком запросов из роутера
|
|
118
|
+
// создание HTTP-сервера с обработчиком запросов из роутера
|
|
144
119
|
const server = http.createServer(router.requestListener);
|
|
145
120
|
|
|
146
|
-
//
|
|
121
|
+
// запуск сервера
|
|
147
122
|
server.listen(3000, () => {
|
|
148
123
|
console.log('Server is running on http://localhost:3000');
|
|
149
124
|
console.log('Try GET http://localhost:3000/users');
|
|
150
125
|
console.log(
|
|
151
|
-
'Try POST http://localhost:3000/users with body {"name": "Jane Doe"}'
|
|
126
|
+
'Try POST http://localhost:3000/users with body {"name": "Jane Doe"}',
|
|
152
127
|
);
|
|
153
128
|
});
|
|
154
129
|
}
|
|
@@ -156,50 +131,74 @@ async function bootstrap() {
|
|
|
156
131
|
bootstrap();
|
|
157
132
|
```
|
|
158
133
|
|
|
159
|
-
##
|
|
134
|
+
## Данные запроса
|
|
135
|
+
|
|
136
|
+
Модуль предоставляет TypeScript декораторы для инъекции данных входящего
|
|
137
|
+
запроса через аргументы метода контроллера.
|
|
138
|
+
|
|
139
|
+
### URL-параметры
|
|
140
|
+
|
|
141
|
+
Извлечение динамических частей URL (например, `:id`).
|
|
142
|
+
|
|
143
|
+
Декораторы:
|
|
160
144
|
|
|
161
|
-
|
|
162
|
-
|
|
145
|
+
- `@requestParam(name, schema)`
|
|
146
|
+
\- извлечение одного параметра;
|
|
163
147
|
|
|
164
|
-
|
|
148
|
+
- `@requestParams(schema)`
|
|
149
|
+
\- извлечение всех URL-параметров в виде объекта;
|
|
165
150
|
|
|
166
|
-
|
|
151
|
+
Пример:
|
|
167
152
|
|
|
168
153
|
```ts
|
|
169
|
-
import {
|
|
154
|
+
import {
|
|
155
|
+
DataType,
|
|
156
|
+
getAction,
|
|
157
|
+
requestParam,
|
|
158
|
+
restController,
|
|
159
|
+
} from '@e22m4u/ts-rest-router';
|
|
170
160
|
|
|
171
161
|
@restController('articles')
|
|
172
162
|
class ArticleController {
|
|
173
|
-
//
|
|
163
|
+
// GET /articles/42
|
|
174
164
|
@getAction(':id')
|
|
175
165
|
getArticleById(
|
|
176
|
-
//
|
|
177
|
-
// типу "number"
|
|
166
|
+
// извлечение параметра 'id' с проверкой
|
|
167
|
+
// на соответствие типу "number"
|
|
178
168
|
@requestParam('id', DataType.NUMBER) id: number,
|
|
179
169
|
) {
|
|
180
|
-
//
|
|
181
|
-
// 400 Bad Request
|
|
170
|
+
// если id не является числом,
|
|
171
|
+
// то выбрасывается ошибка 400 Bad Request
|
|
182
172
|
return {articleId: id, content: '...'};
|
|
183
173
|
}
|
|
184
174
|
}
|
|
185
175
|
```
|
|
186
176
|
|
|
187
|
-
|
|
177
|
+
### Query-параметры
|
|
178
|
+
|
|
179
|
+
Извлечение параметров из строки запроса (например, `?sort=desc`).
|
|
188
180
|
|
|
189
|
-
|
|
190
|
-
- `@requestParams(schema)` - извлечение всех URL-параметров в виде объекта;
|
|
181
|
+
Декораторы:
|
|
191
182
|
|
|
192
|
-
|
|
183
|
+
- `@requestQuery(name, schema)`
|
|
184
|
+
\- извлечение одного query-параметра;
|
|
193
185
|
|
|
194
|
-
|
|
195
|
-
|
|
186
|
+
- `@requestQueries(schema)`
|
|
187
|
+
\- извлечение всех query-параметров в виде объекта;
|
|
188
|
+
|
|
189
|
+
Пример:
|
|
196
190
|
|
|
197
191
|
```ts
|
|
198
|
-
import {
|
|
192
|
+
import {
|
|
193
|
+
DataType,
|
|
194
|
+
getAction,
|
|
195
|
+
requestQuery,
|
|
196
|
+
restController,
|
|
197
|
+
} from '@e22m4u/ts-rest-router';
|
|
199
198
|
|
|
200
199
|
@restController('products')
|
|
201
200
|
class ProductController {
|
|
202
|
-
//
|
|
201
|
+
// GET /products/search?q=phone&limit=10
|
|
203
202
|
@getAction('search')
|
|
204
203
|
searchProducts(
|
|
205
204
|
@requestQuery('q', {
|
|
@@ -213,19 +212,14 @@ class ProductController {
|
|
|
213
212
|
})
|
|
214
213
|
limit: number,
|
|
215
214
|
) {
|
|
216
|
-
// searchTerm будет 'phone', limit будет 10
|
|
217
|
-
//
|
|
218
|
-
// будет использовано значение по умолчанию 20
|
|
215
|
+
// searchTerm будет 'phone', limit будет 10;
|
|
216
|
+
// при отсутствии 'q' будет ошибка;
|
|
217
|
+
// при отсутствии 'limit' будет использовано значение по умолчанию 20;
|
|
219
218
|
return {results: [], query: {searchTerm, limit}};
|
|
220
219
|
}
|
|
221
220
|
}
|
|
222
221
|
```
|
|
223
222
|
|
|
224
|
-
**Декораторы:**
|
|
225
|
-
|
|
226
|
-
- `@requestQuery(name, schema)` - извлечение одного query-параметра;
|
|
227
|
-
- `@requestQueries(schema)` - извлечение всех query-параметров в виде объекта;
|
|
228
|
-
|
|
229
223
|
**Именование Query-декораторов**
|
|
230
224
|
|
|
231
225
|
Разделение на декораторы в единственном `@requestQuery` и множественном
|
|
@@ -235,21 +229,32 @@ class ProductController {
|
|
|
235
229
|
извлечения и валидации отдельных значений, в то время как множественное число
|
|
236
230
|
служит для получения всех данных в виде единого объекта.
|
|
237
231
|
|
|
238
|
-
### Тело запроса
|
|
232
|
+
### Тело запроса
|
|
233
|
+
|
|
234
|
+
Извлечение тела запроса POST, PUT, PATCH методов.
|
|
235
|
+
|
|
236
|
+
Декораторы:
|
|
237
|
+
|
|
238
|
+
- `@requestBody(schema)`
|
|
239
|
+
\- извлечение тела запроса;
|
|
240
|
+
|
|
241
|
+
- `@requestField(name, schema)`
|
|
242
|
+
\- извлечение отдельного поля из тела запроса;
|
|
239
243
|
|
|
240
|
-
|
|
244
|
+
Пример:
|
|
241
245
|
|
|
242
246
|
```ts
|
|
243
247
|
import {
|
|
248
|
+
DataType,
|
|
244
249
|
postAction,
|
|
245
250
|
requestBody,
|
|
246
251
|
requestField,
|
|
247
|
-
|
|
252
|
+
restController,
|
|
248
253
|
} from '@e22m4u/ts-rest-router';
|
|
249
254
|
|
|
250
255
|
@restController('users')
|
|
251
256
|
class UserController {
|
|
252
|
-
//
|
|
257
|
+
// пример с @requestBody: получение и валидация всего тела запроса
|
|
253
258
|
@postAction()
|
|
254
259
|
createUser(
|
|
255
260
|
@requestBody({
|
|
@@ -265,12 +270,15 @@ class UserController {
|
|
|
265
270
|
},
|
|
266
271
|
},
|
|
267
272
|
})
|
|
268
|
-
body: {
|
|
273
|
+
body: {
|
|
274
|
+
username: string;
|
|
275
|
+
email: string;
|
|
276
|
+
},
|
|
269
277
|
) {
|
|
270
278
|
return {id: 1, ...body};
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
//
|
|
281
|
+
// пример с @requestField: получение только одного поля из тела
|
|
274
282
|
@postAction('login')
|
|
275
283
|
login(
|
|
276
284
|
@requestField('username', DataType.STRING) username: string,
|
|
@@ -282,19 +290,89 @@ class UserController {
|
|
|
282
290
|
}
|
|
283
291
|
```
|
|
284
292
|
|
|
285
|
-
|
|
293
|
+
### Заголовки
|
|
294
|
+
|
|
295
|
+
Извлечение HTTP-заголовков запроса.
|
|
296
|
+
|
|
297
|
+
Декораторы:
|
|
298
|
+
|
|
299
|
+
- `@requestHeader(name, schema)`
|
|
300
|
+
\- извлечение отдельного заголовка;
|
|
301
|
+
|
|
302
|
+
- `@requestHeaders(schema)`
|
|
303
|
+
\- извлечение всех заголовков;
|
|
304
|
+
|
|
305
|
+
Пример:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import {
|
|
309
|
+
DataType,
|
|
310
|
+
getAction,
|
|
311
|
+
requestHeader,
|
|
312
|
+
} from '@e22m4u/ts-rest-router';
|
|
313
|
+
|
|
314
|
+
@restController('content')
|
|
315
|
+
class ContentController {
|
|
316
|
+
// GET /content
|
|
317
|
+
@getAction()
|
|
318
|
+
getContentForLanguage(
|
|
319
|
+
// извлечение заголовка 'Accept-Language'
|
|
320
|
+
// со значением по умолчанию 'en'
|
|
321
|
+
@requestHeader('Accept-Language', {
|
|
322
|
+
type: DataType.STRING,
|
|
323
|
+
default: 'en',
|
|
324
|
+
})
|
|
325
|
+
lang: string,
|
|
326
|
+
) {
|
|
327
|
+
// lang будет 'en', если заголовок отсутствует,
|
|
328
|
+
// или будет содержать значение заголовка, например 'ru-RU'
|
|
329
|
+
return {content: `Content in ${lang} language`};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Cookies
|
|
286
335
|
|
|
287
|
-
|
|
288
|
-
- `@requestField(name, schema)` - извлечение отдельного поля из тела запроса;
|
|
336
|
+
Извлечение разобранного заголовка Cookies входящего запроса.
|
|
289
337
|
|
|
290
|
-
|
|
338
|
+
Декораторы:
|
|
291
339
|
|
|
292
|
-
|
|
340
|
+
- `@requestCookie(name, schema)`
|
|
341
|
+
\- извлечение отдельного Cookie по ключу;
|
|
342
|
+
|
|
343
|
+
- `@requestCookies(schema)`
|
|
344
|
+
\- извлечение всех Cookies;
|
|
345
|
+
|
|
346
|
+
Пример:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
import {
|
|
350
|
+
DataType,
|
|
351
|
+
getAction,
|
|
352
|
+
requestCookie,
|
|
353
|
+
} from '@e22m4u/ts-rest-router';
|
|
293
354
|
|
|
294
|
-
|
|
295
|
-
|
|
355
|
+
@restController('session')
|
|
356
|
+
class SessionController {
|
|
357
|
+
// GET /session/info
|
|
358
|
+
@getAction('info')
|
|
359
|
+
getSessionInfo(
|
|
360
|
+
// извлечение cookie с именем 'sessionId'
|
|
361
|
+
// и проверка, что значение является строкой
|
|
362
|
+
@requestCookie('sessionId', {
|
|
363
|
+
type: DataType.STRING,
|
|
364
|
+
required: true,
|
|
365
|
+
})
|
|
366
|
+
sessionId: string,
|
|
367
|
+
) {
|
|
368
|
+
// если cookie 'sessionId' отсутствует,
|
|
369
|
+
// будет выброшена ошибка 400 Bad Request
|
|
370
|
+
return {sessionId, info: 'User session data...'};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
296
374
|
|
|
297
|
-
### Контекст запроса
|
|
375
|
+
### Контекст запроса
|
|
298
376
|
|
|
299
377
|
Для доступа к "сырым" объектам запроса/ответа Node.js или другим частям
|
|
300
378
|
контекста используются следующие декораторы:
|
|
@@ -303,11 +381,14 @@ class UserController {
|
|
|
303
381
|
- `@requestContext('req')` - инъекция нативного `IncomingMessage`;
|
|
304
382
|
- `@requestContext('res')` - инъекция нативного `ServerResponse`;
|
|
305
383
|
- `@requestContext('container')` - инъекция DI-контейнера запроса;
|
|
306
|
-
-
|
|
384
|
+
- Псевдонимы: `@httpRequest()`, `@httpResponse()`, `@requestContainer()`;
|
|
307
385
|
|
|
308
386
|
```ts
|
|
309
|
-
import {
|
|
310
|
-
|
|
387
|
+
import {
|
|
388
|
+
getAction,
|
|
389
|
+
requestContext,
|
|
390
|
+
RequestContext,
|
|
391
|
+
} from '@e22m4u/ts-rest-router';
|
|
311
392
|
|
|
312
393
|
@restController('system')
|
|
313
394
|
class SystemController {
|
|
@@ -323,14 +404,14 @@ class SystemController {
|
|
|
323
404
|
}
|
|
324
405
|
```
|
|
325
406
|
|
|
326
|
-
## Валидация
|
|
407
|
+
## Валидация данных
|
|
327
408
|
|
|
328
409
|
Модуль интегрирован с
|
|
329
410
|
[@e22m4u/js-data-schema](https://www.npmjs.com/package/@e22m4u/js-data-schema)
|
|
330
411
|
для гибкой проверки данных. Это дает возможность определять типы данных
|
|
331
412
|
и сложные правила.
|
|
332
413
|
|
|
333
|
-
|
|
414
|
+
Базовые типы:
|
|
334
415
|
|
|
335
416
|
- `DataType.ANY`
|
|
336
417
|
- `DataType.STRING`
|
|
@@ -339,23 +420,28 @@ class SystemController {
|
|
|
339
420
|
- `DataType.ARRAY`
|
|
340
421
|
- `DataType.OBJECT`
|
|
341
422
|
|
|
342
|
-
Для более сложных проверок используется объект
|
|
423
|
+
Для более сложных проверок используется объект схемы:
|
|
343
424
|
|
|
344
425
|
```ts
|
|
345
426
|
type DataSchema = {
|
|
346
|
-
type: DataType; //
|
|
347
|
-
required?: boolean; //
|
|
348
|
-
default?: unknown; //
|
|
349
|
-
items?: DataSchema; //
|
|
350
|
-
properties?: {[key: string]: DataSchema}; //
|
|
351
|
-
validate?: (value: any) => boolean | string; //
|
|
352
|
-
}
|
|
427
|
+
type: DataType; // обязательное поле
|
|
428
|
+
required?: boolean; // значение не может быть null или undefined
|
|
429
|
+
default?: unknown; // значение по умолчанию, если `required: false`
|
|
430
|
+
items?: DataSchema; // схема для элементов массива (для type: DataType.ARRAY)
|
|
431
|
+
properties?: {[key: string]: DataSchema}; // схема для свойств объекта
|
|
432
|
+
validate?: (value: any) => boolean | string; // пользовательская функция
|
|
433
|
+
};
|
|
353
434
|
```
|
|
354
435
|
|
|
355
|
-
|
|
436
|
+
Пример сложной валидации:
|
|
356
437
|
|
|
357
438
|
```ts
|
|
358
|
-
import {
|
|
439
|
+
import {
|
|
440
|
+
DataType,
|
|
441
|
+
postAction,
|
|
442
|
+
requestBody,
|
|
443
|
+
restController,
|
|
444
|
+
} from '@e22m4u/ts-rest-router';
|
|
359
445
|
|
|
360
446
|
@restController('orders')
|
|
361
447
|
class OrderController {
|
|
@@ -371,68 +457,74 @@ class OrderController {
|
|
|
371
457
|
products: {
|
|
372
458
|
type: DataType.ARRAY,
|
|
373
459
|
required: true,
|
|
374
|
-
//
|
|
460
|
+
// описание схемы для каждого элемента массива
|
|
375
461
|
items: {
|
|
376
462
|
type: DataType.OBJECT,
|
|
377
463
|
properties: {
|
|
378
464
|
id: {
|
|
379
465
|
type: DataType.NUMBER,
|
|
380
|
-
required: true
|
|
466
|
+
required: true,
|
|
381
467
|
},
|
|
382
468
|
quantity: {
|
|
383
469
|
type: DataType.NUMBER,
|
|
384
470
|
required: true,
|
|
385
|
-
//
|
|
386
|
-
validate:
|
|
471
|
+
// валидатор: количество должно быть больше 0
|
|
472
|
+
validate: qty => qty > 0 || 'Quantity must be positive',
|
|
387
473
|
},
|
|
388
474
|
},
|
|
389
475
|
},
|
|
390
476
|
},
|
|
391
477
|
},
|
|
392
478
|
})
|
|
393
|
-
orderData: {
|
|
479
|
+
orderData: {
|
|
480
|
+
/* ... */
|
|
481
|
+
},
|
|
394
482
|
) {
|
|
395
483
|
// ...
|
|
396
484
|
}
|
|
397
485
|
}
|
|
398
486
|
```
|
|
399
487
|
|
|
400
|
-
## Хуки
|
|
488
|
+
## Хуки
|
|
489
|
+
|
|
490
|
+
Функции, выполняющиеся до или после основного метода контроллера именуются
|
|
491
|
+
«Хуками». Они предназначены для сквозной логики, такой как аутентификация,
|
|
492
|
+
логирование или модификация ответа. Применение хуков возможно как ко всему
|
|
493
|
+
контроллеру, так и к отдельному методу.
|
|
401
494
|
|
|
402
|
-
|
|
403
|
-
(`@afterAction`) основного метода контроллера. Они предназначены для
|
|
404
|
-
сквозной логики, такой как аутентификация, логирование или модификация
|
|
405
|
-
ответа.
|
|
495
|
+
Декораторы:
|
|
406
496
|
|
|
407
|
-
|
|
408
|
-
|
|
497
|
+
- `@beforeAction(preHandler)` - выполняется перед методом контроллера;
|
|
498
|
+
- `@afterAction(postHandler)` - выполняется после метода контроллера;
|
|
499
|
+
|
|
500
|
+
Пример:
|
|
409
501
|
|
|
410
502
|
```ts
|
|
411
|
-
import {RequestContext} from '@e22m4u/js-trie-router';
|
|
412
503
|
import createError from 'http-errors';
|
|
504
|
+
import {RequestContext} from '@e22m4u/ts-rest-router';
|
|
413
505
|
|
|
414
|
-
//
|
|
506
|
+
// хук для проверки аутентификации
|
|
415
507
|
async function authHook(ctx: RequestContext) {
|
|
416
508
|
const token = ctx.headers.authorization;
|
|
417
509
|
if (!token /* || !isValidToken(token) */) {
|
|
418
|
-
//
|
|
419
|
-
// соответствующий HTTP
|
|
510
|
+
// выброс ошибки прерывает выполнение и отправляет
|
|
511
|
+
// клиенту соответствующий HTTP-статус
|
|
420
512
|
throw createError(401, 'Unauthorized');
|
|
421
513
|
}
|
|
422
514
|
}
|
|
423
515
|
|
|
424
|
-
//
|
|
516
|
+
// хук для логирования и модификации ответа
|
|
425
517
|
async function loggerHook(ctx: RequestContext, data: any) {
|
|
426
518
|
console.log(`Response for ${ctx.pathname}:`, data);
|
|
427
|
-
//
|
|
519
|
+
// хуки @afterAction могут модифицировать ответ
|
|
428
520
|
return {...data, timestamp: new Date()};
|
|
429
521
|
}
|
|
430
522
|
|
|
431
|
-
@beforeAction(authHook) //
|
|
523
|
+
@beforeAction(authHook) // применение ко всем методам контроллера
|
|
432
524
|
@restController('profile')
|
|
433
525
|
class ProfileController {
|
|
434
526
|
@getAction('me')
|
|
435
|
-
@afterAction(loggerHook) //
|
|
527
|
+
@afterAction(loggerHook) // применение только к этому методу
|
|
436
528
|
getMyProfile() {
|
|
437
529
|
return {id: 1, name: 'Current User'};
|
|
438
530
|
}
|
|
@@ -444,23 +536,23 @@ class ProfileController {
|
|
|
444
536
|
}
|
|
445
537
|
```
|
|
446
538
|
|
|
447
|
-
##
|
|
539
|
+
## Архитектура
|
|
448
540
|
|
|
449
541
|
Понимание архитектурных принципов `ts-rest-router` является ключом
|
|
450
542
|
к созданию надежных и масштабируемых приложений. Модуль построен на
|
|
451
543
|
базе библиотеки `@e22m4u/js-service`, реализующей паттерн
|
|
452
544
|
Service Locator / Dependency Injection.
|
|
453
545
|
|
|
454
|
-
####
|
|
546
|
+
#### Изоляция запросов
|
|
455
547
|
|
|
456
|
-
Для
|
|
457
|
-
экземпляр
|
|
548
|
+
Для каждого входящего HTTP-запроса создается новый, изолированный
|
|
549
|
+
экземпляр контроллера. Этот фундаментальный принцип гарантирует, что
|
|
458
550
|
состояние одного запроса (например, данные аутентифицированного
|
|
459
551
|
пользователя) никогда не будет разделено с другим, одновременно
|
|
460
552
|
обрабатываемым запросом. Это устраняет целый класс потенциальных
|
|
461
553
|
ошибок, связанных с состоянием гонки.
|
|
462
554
|
|
|
463
|
-
####
|
|
555
|
+
#### Внедрение зависимостей
|
|
464
556
|
|
|
465
557
|
Каждый экземпляр контроллера создается с помощью своего собственного
|
|
466
558
|
DI-контейнера, который существует только в рамках одного запроса. Чтобы
|
|
@@ -471,31 +563,31 @@ DI-контейнера, который существует только в р
|
|
|
471
563
|
|
|
472
564
|
**Практический пример с сервисом аутентификации:**
|
|
473
565
|
|
|
474
|
-
|
|
566
|
+
1\. Создание хука для подготовки сервиса.
|
|
475
567
|
|
|
476
|
-
|
|
477
|
-
В
|
|
568
|
+
С помощью хука можно подготовить сервис, специфичный для каждого запроса.
|
|
569
|
+
В данном примере хук `@beforeAction` создает экземпляр `AuthService`,
|
|
478
570
|
выполняет аутентификацию и регистрирует его в контейнере запроса.
|
|
479
571
|
|
|
480
572
|
```ts
|
|
481
573
|
// src/auth.hook.ts
|
|
482
574
|
import {AuthService} from './auth.service';
|
|
483
|
-
import {RequestContext} from '@e22m4u/
|
|
575
|
+
import {RequestContext} from '@e22m4u/ts-rest-router';
|
|
484
576
|
|
|
485
577
|
export async function authHook(context: RequestContext) {
|
|
486
578
|
const requestContainer = context.container;
|
|
487
|
-
//
|
|
579
|
+
// создание сервиса с передачей ему контейнера запроса
|
|
488
580
|
const authService = new AuthService(requestContainer);
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
// сможет получить
|
|
581
|
+
// регистрация созданного экземпляра в контейнере
|
|
582
|
+
// (теперь любой другой сервис в рамках текущего
|
|
583
|
+
// запроса сможет получить данный экземпляр)
|
|
492
584
|
requestContainer.set(AuthService, authService);
|
|
493
|
-
//
|
|
585
|
+
// выполнение логики аутентификации
|
|
494
586
|
await authService.authenticate(context.headers.authorization);
|
|
495
587
|
}
|
|
496
588
|
```
|
|
497
589
|
|
|
498
|
-
|
|
590
|
+
2\. Создание `AuthService`.
|
|
499
591
|
|
|
500
592
|
`AuthService` наследуется от `Service`, что позволяет ему запрашивать другие
|
|
501
593
|
зависимости (например, `this.getService(DatabaseService)`).
|
|
@@ -505,18 +597,19 @@ export async function authHook(context: RequestContext) {
|
|
|
505
597
|
import {Service} from '@e22m4u/js-service';
|
|
506
598
|
|
|
507
599
|
export class AuthService extends Service {
|
|
508
|
-
public currentUser?: {id: number; name: string
|
|
600
|
+
public currentUser?: {id: number; name: string};
|
|
509
601
|
|
|
510
602
|
async authenticate(token?: string) {
|
|
511
|
-
//
|
|
603
|
+
// логика проверки токена и поиска
|
|
604
|
+
// пользователя в базе данных...
|
|
512
605
|
if (token === 'valid-token') {
|
|
513
|
-
this.currentUser = {
|
|
606
|
+
this.currentUser = {id: 1, name: 'John Doe'};
|
|
514
607
|
}
|
|
515
608
|
}
|
|
516
609
|
}
|
|
517
610
|
```
|
|
518
611
|
|
|
519
|
-
|
|
612
|
+
3\. Использование сервиса в контроллере.
|
|
520
613
|
|
|
521
614
|
Контроллер, унаследованный от `Service`, теперь может получить
|
|
522
615
|
доступ к предварительно настроенному экземпляру `AuthService`
|
|
@@ -528,24 +621,21 @@ import {authHook} from './auth.hook';
|
|
|
528
621
|
import createError from 'http-errors';
|
|
529
622
|
import {Service} from '@e22m4u/js-service';
|
|
530
623
|
import {AuthService} from './auth.service';
|
|
531
|
-
import {
|
|
532
|
-
getAction,
|
|
533
|
-
restController,
|
|
534
|
-
beforeAction,
|
|
535
|
-
} from '@e22m4u/ts-rest-router';
|
|
624
|
+
import {getAction, beforeAction, restController} from '@e22m4u/ts-rest-router';
|
|
536
625
|
|
|
537
626
|
@beforeAction(authHook)
|
|
538
627
|
@restController('profile')
|
|
539
628
|
export class ProfileController extends Service {
|
|
540
629
|
@getAction('me')
|
|
541
630
|
getProfile() {
|
|
542
|
-
//
|
|
543
|
-
// который был создан и зарегистрирован в
|
|
631
|
+
// получение request-scoped экземпляра AuthService,
|
|
632
|
+
// который был создан и зарегистрирован в хуке
|
|
544
633
|
const authService = this.getService(AuthService);
|
|
545
634
|
|
|
546
|
-
if (!authService.currentUser)
|
|
635
|
+
if (!authService.currentUser) {
|
|
547
636
|
throw createError(401, 'Unauthorized');
|
|
548
|
-
|
|
637
|
+
}
|
|
638
|
+
|
|
549
639
|
return authService.currentUser;
|
|
550
640
|
}
|
|
551
641
|
}
|
|
@@ -555,12 +645,12 @@ export class ProfileController extends Service {
|
|
|
555
645
|
и контроллером, позволяя безопасно передавать состояние, изолированное
|
|
556
646
|
в рамках одного HTTP-запроса.
|
|
557
647
|
|
|
558
|
-
##
|
|
648
|
+
## Производительность
|
|
559
649
|
|
|
560
650
|
В основе данного модуля лежит
|
|
561
651
|
[@e22m4u/js-trie-router](https://www.npmjs.com/package/@e22m4u/js-trie-router),
|
|
562
652
|
который использует структуру данных
|
|
563
|
-
|
|
653
|
+
*префиксного дерева ([Trie](https://en.wikipedia.org/wiki/Trie))*
|
|
564
654
|
для хранения маршрутов и их сопоставления. Такое архитектурное решение
|
|
565
655
|
обеспечивает высокую производительность, особенно в приложениях
|
|
566
656
|
с большим количеством маршрутов.
|
|
@@ -576,41 +666,39 @@ export class ProfileController extends Service {
|
|
|
576
666
|
маршрутизатор не перебирает все существующие маршруты. Вместо этого
|
|
577
667
|
он последовательно спускается по дереву:
|
|
578
668
|
|
|
579
|
-
1.
|
|
580
|
-
2.
|
|
581
|
-
3.
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
4.
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
## Полный список декораторов
|
|
669
|
+
1. Находит корневой узел для метода `GET`.
|
|
670
|
+
2. От него переходит к дочернему узлу `users`.
|
|
671
|
+
3. Далее, не найдя статического узла `123`, он ищет динамический
|
|
672
|
+
узел (`:id`) и сопоставляет его, сохраняя `123` как значение
|
|
673
|
+
параметра `id`.
|
|
674
|
+
4. Наконец, он переходит к узлу `posts` и находит совпадение.
|
|
675
|
+
|
|
676
|
+
**Эффективный поиск маршрута O(k)**
|
|
677
|
+
|
|
678
|
+
Самое главное преимущество - скорость поиска. Вместо того чтобы
|
|
679
|
+
перебирать массив из `N` маршрутов и проверять каждый с помощью
|
|
680
|
+
регулярного выражения (сложность `O(N)`), префиксное дерево
|
|
681
|
+
находит совпадение за время, пропорциональное количеству сегментов
|
|
682
|
+
`K` в URL-пути (сложность `O(K)`). Это означает, что производительность
|
|
683
|
+
поиска не падает с ростом общего числа маршрутов в приложении.
|
|
684
|
+
|
|
685
|
+
**Быстрая обработка 404 (ранний выход)**
|
|
686
|
+
|
|
687
|
+
Если приходит запрос на несуществующий путь, например
|
|
688
|
+
`/users/123/comments`, поиск по дереву прекратится сразу после
|
|
689
|
+
того, как маршрутизатор не найдет дочерний узел `comments`
|
|
690
|
+
у узла `:id`. Ему не нужно проверять остальные сотни маршрутов,
|
|
691
|
+
чтобы убедиться, что совпадений нет. Это делает обработку
|
|
692
|
+
неопределенных маршрутов (404) почти мгновенной.
|
|
693
|
+
|
|
694
|
+
**Оптимизация для статических и динамических маршрутов**
|
|
695
|
+
|
|
696
|
+
При поиске маршрутизатор всегда отдает приоритет статическим
|
|
697
|
+
сегментам перед динамическими. Маршрут `/users/me` всегда будет
|
|
698
|
+
найден раньше и быстрее, чем `/users/:id` при запросе `/users/me`,
|
|
699
|
+
поскольку не требуется проверка на соответствие шаблону.
|
|
700
|
+
|
|
701
|
+
## Декораторы
|
|
614
702
|
|
|
615
703
|
#### Контроллер и методы:
|
|
616
704
|
|
|
@@ -667,4 +755,4 @@ npm run test
|
|
|
667
755
|
|
|
668
756
|
## Лицензия
|
|
669
757
|
|
|
670
|
-
MIT
|
|
758
|
+
MIT
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -32,7 +32,7 @@ __export(index_exports, {
|
|
|
32
32
|
RESPONSE_BODY_METADATA_KEY: () => RESPONSE_BODY_METADATA_KEY,
|
|
33
33
|
REST_ACTIONS_METADATA_KEY: () => REST_ACTIONS_METADATA_KEY,
|
|
34
34
|
REST_CONTROLLER_METADATA_KEY: () => REST_CONTROLLER_METADATA_KEY,
|
|
35
|
-
RequestContext: () =>
|
|
35
|
+
RequestContext: () => import_js_trie_router5.RequestContext,
|
|
36
36
|
RequestContextReflector: () => RequestContextReflector,
|
|
37
37
|
RequestDataReflector: () => RequestDataReflector,
|
|
38
38
|
RequestDataSource: () => RequestDataSource,
|
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
RestActionReflector: () => RestActionReflector,
|
|
41
41
|
RestControllerReflector: () => RestControllerReflector,
|
|
42
42
|
RestRouter: () => RestRouter,
|
|
43
|
+
RouterHookType: () => import_js_trie_router4.RouterHookType,
|
|
43
44
|
afterAction: () => afterAction,
|
|
44
45
|
beforeAction: () => beforeAction,
|
|
45
46
|
deleteAction: () => deleteAction,
|
|
@@ -204,7 +205,7 @@ var RequestDataSource;
|
|
|
204
205
|
RequestDataSource2["PARAMS"] = "params";
|
|
205
206
|
RequestDataSource2["QUERY"] = "query";
|
|
206
207
|
RequestDataSource2["HEADERS"] = "headers";
|
|
207
|
-
RequestDataSource2["
|
|
208
|
+
RequestDataSource2["COOKIES"] = "cookies";
|
|
208
209
|
RequestDataSource2["BODY"] = "body";
|
|
209
210
|
})(RequestDataSource || (RequestDataSource = {}));
|
|
210
211
|
var REQUEST_DATA_METADATA_KEY = new import_ts_reflector5.MetadataKey("requestDataMetadataKey");
|
|
@@ -304,8 +305,8 @@ var requestQueries = createRequestDataDecoratorWithSource(RequestDataSource.QUER
|
|
|
304
305
|
var requestQuery = createRequestDataPropertyDecoratorWithSource(RequestDataSource.QUERY);
|
|
305
306
|
var requestHeaders = createRequestDataDecoratorWithSource(RequestDataSource.HEADERS);
|
|
306
307
|
var requestHeader = createRequestDataPropertyDecoratorWithSource(RequestDataSource.HEADERS);
|
|
307
|
-
var requestCookies = createRequestDataDecoratorWithSource(RequestDataSource.
|
|
308
|
-
var requestCookie = createRequestDataPropertyDecoratorWithSource(RequestDataSource.
|
|
308
|
+
var requestCookies = createRequestDataDecoratorWithSource(RequestDataSource.COOKIES);
|
|
309
|
+
var requestCookie = createRequestDataPropertyDecoratorWithSource(RequestDataSource.COOKIES);
|
|
309
310
|
var requestBody = createRequestDataDecoratorWithSource(RequestDataSource.BODY);
|
|
310
311
|
var requestField = createRequestDataPropertyDecoratorWithSource(RequestDataSource.BODY);
|
|
311
312
|
|
|
@@ -895,7 +896,7 @@ var _ControllerRegistry = class _ControllerRegistry extends DebuggableService {
|
|
|
895
896
|
case RequestDataSource.HEADERS:
|
|
896
897
|
data = requestContext2.headers;
|
|
897
898
|
break;
|
|
898
|
-
case RequestDataSource.
|
|
899
|
+
case RequestDataSource.COOKIES:
|
|
899
900
|
data = requestContext2.cookies;
|
|
900
901
|
break;
|
|
901
902
|
case RequestDataSource.BODY:
|
|
@@ -980,6 +981,7 @@ var RestRouter = _RestRouter;
|
|
|
980
981
|
|
|
981
982
|
// dist/esm/index.js
|
|
982
983
|
var import_js_trie_router4 = require("@e22m4u/js-trie-router");
|
|
984
|
+
var import_js_trie_router5 = require("@e22m4u/js-trie-router");
|
|
983
985
|
// Annotate the CommonJS export names for ESM import in node:
|
|
984
986
|
0 && (module.exports = {
|
|
985
987
|
AFTER_ACTION_METADATA_KEY,
|
|
@@ -1001,6 +1003,7 @@ var import_js_trie_router4 = require("@e22m4u/js-trie-router");
|
|
|
1001
1003
|
RestActionReflector,
|
|
1002
1004
|
RestControllerReflector,
|
|
1003
1005
|
RestRouter,
|
|
1006
|
+
RouterHookType,
|
|
1004
1007
|
afterAction,
|
|
1005
1008
|
beforeAction,
|
|
1006
1009
|
deleteAction,
|
|
@@ -372,7 +372,7 @@ export class ControllerRegistry extends DebuggableService {
|
|
|
372
372
|
case RequestDataSource.HEADERS:
|
|
373
373
|
data = requestContext.headers;
|
|
374
374
|
break;
|
|
375
|
-
case RequestDataSource.
|
|
375
|
+
case RequestDataSource.COOKIES:
|
|
376
376
|
data = requestContext.cookies;
|
|
377
377
|
break;
|
|
378
378
|
case RequestDataSource.BODY:
|
|
@@ -82,7 +82,7 @@ export const requestQueries = createRequestDataDecoratorWithSource(RequestDataSo
|
|
|
82
82
|
export const requestQuery = createRequestDataPropertyDecoratorWithSource(RequestDataSource.QUERY);
|
|
83
83
|
export const requestHeaders = createRequestDataDecoratorWithSource(RequestDataSource.HEADERS);
|
|
84
84
|
export const requestHeader = createRequestDataPropertyDecoratorWithSource(RequestDataSource.HEADERS);
|
|
85
|
-
export const requestCookies = createRequestDataDecoratorWithSource(RequestDataSource.
|
|
86
|
-
export const requestCookie = createRequestDataPropertyDecoratorWithSource(RequestDataSource.
|
|
85
|
+
export const requestCookies = createRequestDataDecoratorWithSource(RequestDataSource.COOKIES);
|
|
86
|
+
export const requestCookie = createRequestDataPropertyDecoratorWithSource(RequestDataSource.COOKIES);
|
|
87
87
|
export const requestBody = createRequestDataDecoratorWithSource(RequestDataSource.BODY);
|
|
88
88
|
export const requestField = createRequestDataPropertyDecoratorWithSource(RequestDataSource.BODY);
|
|
@@ -126,7 +126,7 @@ describe('requestData', function () {
|
|
|
126
126
|
], Target.prototype, "myMethod", null);
|
|
127
127
|
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
128
128
|
expect(res.get(0)).to.be.eql({
|
|
129
|
-
source: RequestDataSource.
|
|
129
|
+
source: RequestDataSource.COOKIES,
|
|
130
130
|
schema: { type: DataType.ANY },
|
|
131
131
|
});
|
|
132
132
|
});
|
|
@@ -554,7 +554,7 @@ describe('requestData', function () {
|
|
|
554
554
|
], Target.prototype, "myMethod", null);
|
|
555
555
|
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
556
556
|
expect(res.get(0)).to.be.eql({
|
|
557
|
-
source: RequestDataSource.
|
|
557
|
+
source: RequestDataSource.COOKIES,
|
|
558
558
|
schema: {
|
|
559
559
|
type: DataType.OBJECT,
|
|
560
560
|
properties: {
|
|
@@ -580,7 +580,7 @@ describe('requestData', function () {
|
|
|
580
580
|
], Target.prototype, "myMethod", null);
|
|
581
581
|
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
582
582
|
expect(res.get(0)).to.be.eql({
|
|
583
|
-
source: RequestDataSource.
|
|
583
|
+
source: RequestDataSource.COOKIES,
|
|
584
584
|
schema: {
|
|
585
585
|
type: DataType.OBJECT,
|
|
586
586
|
properties: {
|
|
@@ -609,7 +609,7 @@ describe('requestData', function () {
|
|
|
609
609
|
], Target.prototype, "myMethod", null);
|
|
610
610
|
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
611
611
|
expect(res.get(0)).to.be.eql({
|
|
612
|
-
source: RequestDataSource.
|
|
612
|
+
source: RequestDataSource.COOKIES,
|
|
613
613
|
schema: {
|
|
614
614
|
type: DataType.OBJECT,
|
|
615
615
|
properties: {
|
|
@@ -637,7 +637,7 @@ describe('requestData', function () {
|
|
|
637
637
|
], Target.prototype, "myMethod", null);
|
|
638
638
|
const mdMap = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
639
639
|
const md = mdMap.get(0);
|
|
640
|
-
expect(md.source).to.be.eq(RequestDataSource.
|
|
640
|
+
expect(md.source).to.be.eq(RequestDataSource.COOKIES);
|
|
641
641
|
expect(md.schema).to.be.a('function');
|
|
642
642
|
expect(md.property).to.be.eq(propertyKey);
|
|
643
643
|
const res1 = md.schema;
|
|
@@ -7,7 +7,7 @@ export var RequestDataSource;
|
|
|
7
7
|
RequestDataSource["PARAMS"] = "params";
|
|
8
8
|
RequestDataSource["QUERY"] = "query";
|
|
9
9
|
RequestDataSource["HEADERS"] = "headers";
|
|
10
|
-
RequestDataSource["
|
|
10
|
+
RequestDataSource["COOKIES"] = "cookies";
|
|
11
11
|
RequestDataSource["BODY"] = "body";
|
|
12
12
|
})(RequestDataSource || (RequestDataSource = {}));
|
|
13
13
|
/**
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './decorators/index.js';
|
|
|
4
4
|
export * from './data-schema-types.js';
|
|
5
5
|
export * from './controller-registry.js';
|
|
6
6
|
export { RouteHandler } from '@e22m4u/js-trie-router';
|
|
7
|
+
export { RouterHookType } from '@e22m4u/js-trie-router';
|
|
7
8
|
export { RequestContext } from '@e22m4u/js-trie-router';
|
|
8
9
|
export { RoutePreHandler } from '@e22m4u/js-trie-router';
|
|
9
10
|
export { RoutePostHandler } from '@e22m4u/js-trie-router';
|
package/dist/esm/index.js
CHANGED
|
@@ -3,4 +3,5 @@ export * from './errors/index.js';
|
|
|
3
3
|
export * from './decorators/index.js';
|
|
4
4
|
export * from './data-schema-types.js';
|
|
5
5
|
export * from './controller-registry.js';
|
|
6
|
+
export { RouterHookType } from '@e22m4u/js-trie-router';
|
|
6
7
|
export { RequestContext } from '@e22m4u/js-trie-router';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e22m4u/ts-rest-router",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "Декларативный REST-маршрутизатор на основе контроллеров для TypeScript",
|
|
5
5
|
"author": "Mikhail Evstropov <e22m4u@yandex.ru>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@e22m4u/js-data-schema": "~0.4.6",
|
|
45
45
|
"@e22m4u/js-debug": "~0.3.2",
|
|
46
46
|
"@e22m4u/js-format": "~0.2.0",
|
|
47
|
-
"@e22m4u/js-service": "~0.4.
|
|
47
|
+
"@e22m4u/js-service": "~0.4.5",
|
|
48
48
|
"@e22m4u/js-trie-router": "~0.3.4",
|
|
49
49
|
"@e22m4u/ts-reflector": "~0.1.8",
|
|
50
50
|
"http-errors": "~2.0.0"
|
|
@@ -52,25 +52,25 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@commitlint/cli": "~20.1.0",
|
|
54
54
|
"@commitlint/config-conventional": "~20.0.0",
|
|
55
|
-
"@eslint/js": "~9.
|
|
55
|
+
"@eslint/js": "~9.39.1",
|
|
56
56
|
"@types/chai": "~5.2.3",
|
|
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.10.0",
|
|
61
61
|
"c8": "~10.1.3",
|
|
62
62
|
"chai": "~6.2.0",
|
|
63
|
-
"esbuild": "~0.25.
|
|
64
|
-
"eslint": "~9.
|
|
63
|
+
"esbuild": "~0.25.12",
|
|
64
|
+
"eslint": "~9.39.1",
|
|
65
65
|
"eslint-config-prettier": "~10.1.8",
|
|
66
66
|
"eslint-plugin-chai-expect": "~3.1.0",
|
|
67
67
|
"eslint-plugin-mocha": "~11.2.0",
|
|
68
68
|
"husky": "~9.1.7",
|
|
69
|
-
"mocha": "~11.7.
|
|
69
|
+
"mocha": "~11.7.5",
|
|
70
70
|
"prettier": "~3.6.2",
|
|
71
|
-
"rimraf": "~6.0
|
|
71
|
+
"rimraf": "~6.1.0",
|
|
72
72
|
"tsx": "~4.20.6",
|
|
73
73
|
"typescript": "~5.9.3",
|
|
74
|
-
"typescript-eslint": "~8.46.
|
|
74
|
+
"typescript-eslint": "~8.46.3"
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -450,7 +450,7 @@ export class ControllerRegistry extends DebuggableService {
|
|
|
450
450
|
case RequestDataSource.HEADERS:
|
|
451
451
|
data = requestContext.headers;
|
|
452
452
|
break;
|
|
453
|
-
case RequestDataSource.
|
|
453
|
+
case RequestDataSource.COOKIES:
|
|
454
454
|
data = requestContext.cookies;
|
|
455
455
|
break;
|
|
456
456
|
case RequestDataSource.BODY:
|
|
@@ -108,7 +108,7 @@ describe('requestData', function () {
|
|
|
108
108
|
}
|
|
109
109
|
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
110
110
|
expect(res.get(0)).to.be.eql({
|
|
111
|
-
source: RequestDataSource.
|
|
111
|
+
source: RequestDataSource.COOKIES,
|
|
112
112
|
schema: {type: DataType.ANY},
|
|
113
113
|
});
|
|
114
114
|
});
|
|
@@ -502,7 +502,7 @@ describe('requestData', function () {
|
|
|
502
502
|
}
|
|
503
503
|
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
504
504
|
expect(res.get(0)).to.be.eql({
|
|
505
|
-
source: RequestDataSource.
|
|
505
|
+
source: RequestDataSource.COOKIES,
|
|
506
506
|
schema: {
|
|
507
507
|
type: DataType.OBJECT,
|
|
508
508
|
properties: {
|
|
@@ -526,7 +526,7 @@ describe('requestData', function () {
|
|
|
526
526
|
}
|
|
527
527
|
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
528
528
|
expect(res.get(0)).to.be.eql({
|
|
529
|
-
source: RequestDataSource.
|
|
529
|
+
source: RequestDataSource.COOKIES,
|
|
530
530
|
schema: {
|
|
531
531
|
type: DataType.OBJECT,
|
|
532
532
|
properties: {
|
|
@@ -553,7 +553,7 @@ describe('requestData', function () {
|
|
|
553
553
|
}
|
|
554
554
|
const res = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
555
555
|
expect(res.get(0)).to.be.eql({
|
|
556
|
-
source: RequestDataSource.
|
|
556
|
+
source: RequestDataSource.COOKIES,
|
|
557
557
|
schema: {
|
|
558
558
|
type: DataType.OBJECT,
|
|
559
559
|
properties: {
|
|
@@ -579,7 +579,7 @@ describe('requestData', function () {
|
|
|
579
579
|
}
|
|
580
580
|
const mdMap = RequestDataReflector.getMetadata(Target, 'myMethod');
|
|
581
581
|
const md = mdMap.get(0) as RequestDataMetadata;
|
|
582
|
-
expect(md.source).to.be.eq(RequestDataSource.
|
|
582
|
+
expect(md.source).to.be.eq(RequestDataSource.COOKIES);
|
|
583
583
|
expect(md.schema).to.be.a('function');
|
|
584
584
|
expect(md.property).to.be.eq(propertyKey);
|
|
585
585
|
const res1 = md.schema as DataSchemaFactory;
|
|
@@ -118,10 +118,10 @@ export const requestHeader = createRequestDataPropertyDecoratorWithSource(
|
|
|
118
118
|
RequestDataSource.HEADERS,
|
|
119
119
|
);
|
|
120
120
|
export const requestCookies = createRequestDataDecoratorWithSource(
|
|
121
|
-
RequestDataSource.
|
|
121
|
+
RequestDataSource.COOKIES,
|
|
122
122
|
);
|
|
123
123
|
export const requestCookie = createRequestDataPropertyDecoratorWithSource(
|
|
124
|
-
RequestDataSource.
|
|
124
|
+
RequestDataSource.COOKIES,
|
|
125
125
|
);
|
|
126
126
|
export const requestBody = createRequestDataDecoratorWithSource(
|
|
127
127
|
RequestDataSource.BODY,
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './data-schema-types.js';
|
|
|
5
5
|
export * from './controller-registry.js';
|
|
6
6
|
|
|
7
7
|
export {RouteHandler} from '@e22m4u/js-trie-router';
|
|
8
|
+
export {RouterHookType} from '@e22m4u/js-trie-router';
|
|
8
9
|
export {RequestContext} from '@e22m4u/js-trie-router';
|
|
9
10
|
export {RoutePreHandler} from '@e22m4u/js-trie-router';
|
|
10
11
|
export {RoutePostHandler} from '@e22m4u/js-trie-router';
|