@makebelieve21213-packages/nest-common 1.1.0 → 1.1.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.
- package/LICENSE +0 -1
- package/README.md +278 -1775
- package/dist/decorators/permissions.decorator.d.ts.map +1 -1
- package/dist/decorators/permissions.decorator.js.map +1 -1
- package/dist/errors/__tests__/http.error.spec.js.map +1 -1
- package/dist/errors/__tests__/json-rpc.error.spec.js.map +1 -1
- package/dist/errors/http.error.d.ts.map +1 -1
- package/dist/errors/http.error.js +3 -6
- package/dist/errors/http.error.js.map +1 -1
- package/dist/filters/__tests__/json-rpc-exception.filter.spec.js +3 -1
- package/dist/filters/__tests__/json-rpc-exception.filter.spec.js.map +1 -1
- package/dist/filters/__tests__/websocket-exception-handler.spec.js.map +1 -1
- package/dist/filters/rpc-exception-handler.d.ts.map +1 -1
- package/dist/filters/rpc-exception-handler.js.map +1 -1
- package/dist/guards/api-key.guard.d.ts.map +1 -1
- package/dist/guards/api-key.guard.js +1 -1
- package/dist/guards/api-key.guard.js.map +1 -1
- package/dist/guards/jwt-auth.guard.d.ts.map +1 -1
- package/dist/guards/jwt-auth.guard.js +1 -1
- package/dist/guards/jwt-auth.guard.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/__tests__/http-logging.interceptor.spec.js.map +1 -1
- package/dist/interceptors/__tests__/unified.interceptor.spec.js.map +1 -1
- package/dist/interceptors/__tests__/websocket-logging.interceptor.spec.js.map +1 -1
- package/dist/interceptors/http-logging.interceptor.d.ts.map +1 -1
- package/dist/interceptors/http-logging.interceptor.js.map +1 -1
- package/dist/interceptors/response.interceptor.d.ts.map +1 -1
- package/dist/interceptors/response.interceptor.js.map +1 -1
- package/dist/interceptors/websocket-logging.interceptor.d.ts.map +1 -1
- package/dist/interceptors/websocket-logging.interceptor.js.map +1 -1
- package/dist/pipes/__tests__/file-validation.pipe.spec.js.map +1 -1
- package/dist/pipes/__tests__/header-validation.pipe.spec.js.map +1 -1
- package/dist/pipes/__tests__/http-validation.pipe.spec.js.map +1 -1
- package/dist/pipes/__tests__/json-rpc-validation.pipe.spec.js.map +1 -1
- package/dist/pipes/file-validation.pipe.d.ts.map +1 -1
- package/dist/pipes/file-validation.pipe.js +1 -1
- package/dist/pipes/file-validation.pipe.js.map +1 -1
- package/dist/pipes/http-validation.pipe.d.ts.map +1 -1
- package/dist/pipes/http-validation.pipe.js +1 -3
- package/dist/pipes/http-validation.pipe.js.map +1 -1
- package/dist/pipes/json-rpc-validation.pipe.d.ts.map +1 -1
- package/dist/pipes/json-rpc-validation.pipe.js +3 -7
- package/dist/pipes/json-rpc-validation.pipe.js.map +1 -1
- package/dist/pipes/rpc-validation.pipe.d.ts.map +1 -1
- package/dist/pipes/rpc-validation.pipe.js +1 -3
- package/dist/pipes/rpc-validation.pipe.js.map +1 -1
- package/dist/utils/__tests__/circuit-breaker.spec.js.map +1 -1
- package/dist/utils/__tests__/file.utils.spec.js.map +1 -1
- package/dist/utils/compression.utils.d.ts.map +1 -1
- package/dist/utils/compression.utils.js.map +1 -1
- package/dist/utils/file.utils.d.ts.map +1 -1
- package/dist/utils/file.utils.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,1793 +1,376 @@
|
|
|
1
1
|
# @makebelieve21213-packages/nest-common
|
|
2
2
|
|
|
3
|
-
Общий пакет с утилитами для NestJS
|
|
3
|
+
Общий пакет с утилитами для NestJS микросервисов. Предоставляет единую систему обработки ошибок, валидации, логирования и базовые классы для HTTP, RPC и WebSocket контекстов.
|
|
4
4
|
|
|
5
5
|
## 📋 Содержание
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
|
|
7
|
+
- [Возможности](#-возможности)
|
|
8
|
+
- [Требования](#-требования)
|
|
9
|
+
- [Установка](#-установка)
|
|
10
|
+
- [Быстрый старт](#-быстрый-старт)
|
|
11
|
+
- [Система обработки ошибок](#-система-обработки-ошибок)
|
|
12
|
+
- [API Reference](#-api-reference)
|
|
13
|
+
- [Best Practices](#-best-practices)
|
|
14
|
+
- [Тестирование](#-тестирование)
|
|
15
|
+
- [Разработка](#-разработка)
|
|
16
|
+
|
|
17
|
+
## 🚀 Возможности
|
|
18
|
+
|
|
19
|
+
- ✅ **Единая система обработки ошибок** - автоматическое преобразование ошибок между HTTP, RPC и WebSocket контекстами
|
|
20
|
+
- ✅ **Глобальные фильтры** - автоматическая обработка всех ошибок без try-catch в контроллерах
|
|
21
|
+
- ✅ **Валидация** - готовые пайпы для валидации DTO, файлов, query параметров и заголовков
|
|
22
|
+
- ✅ **Логирование** - автоматическое логирование всех HTTP, RPC и WebSocket запросов
|
|
23
|
+
- ✅ **Guards** - готовые guards для аутентификации, авторизации и rate limiting
|
|
24
|
+
- ✅ **Декораторы** - удобные декораторы для метаданных (@Public, @Roles, @Permissions, @ApiKey, @Serialize)
|
|
25
|
+
- ✅ **Утилиты** - функции для работы с контекстом, файлами, CORS, compression, versioning
|
|
26
|
+
- ✅ **100% покрытие тестами** - надежность и качество кода
|
|
27
|
+
- ✅ **TypeScript типизация** - полная типобезопасность
|
|
28
|
+
|
|
29
|
+
## 📋 Требования
|
|
30
|
+
|
|
31
|
+
- **Node.js**: >= 22.11.0
|
|
32
|
+
- **npm**: >= 10.0.0
|
|
33
|
+
- **NestJS**: >= 11.0.0
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## 🔄 Система обработки ошибок
|
|
35
|
-
|
|
36
|
-
### Архитектура
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
40
|
-
│ HTTP Контекст (api-service) │
|
|
41
|
-
├─────────────────────────────────────────────────────────────┤
|
|
42
|
-
│ │
|
|
43
|
-
│ HTTP Controller (БЕЗ catch блоков) │
|
|
44
|
-
│ ↓ │
|
|
45
|
-
│ Сервис (может иметь catch для логирования) │
|
|
46
|
-
│ ↓ (await rabbitMQService.publish) │
|
|
47
|
-
│ RpcException от RabbitMQ │
|
|
48
|
-
│ ↓ (пробрасывается дальше) │
|
|
49
|
-
│ UnifiedExceptionFilter (глобальный) │
|
|
50
|
-
│ ↓ HttpExceptionFilter.handleException() │
|
|
51
|
-
│ ↓ HttpError.fromUnknown() │
|
|
52
|
-
│ HttpError с правильным статусом │
|
|
53
|
-
│ ↓ │
|
|
54
|
-
│ HTTP Response для фронтенда │
|
|
55
|
-
│ │
|
|
56
|
-
└─────────────────────────────────────────────────────────────┘
|
|
57
|
-
|
|
58
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
59
|
-
│ RPC Контекст (analytics-service) │
|
|
60
|
-
├─────────────────────────────────────────────────────────────┤
|
|
61
|
-
│ │
|
|
62
|
-
│ RPC Controller (БЕЗ catch блоков) │
|
|
63
|
-
│ ↓ │
|
|
64
|
-
│ Сервис (ошибка в бизнес-логике) │
|
|
65
|
-
│ ↓ (пробрасывается дальше) │
|
|
66
|
-
│ UnifiedExceptionFilter (глобальный) │
|
|
67
|
-
│ ↓ RpcExceptionFilter.handleException() │
|
|
68
|
-
│ ↓ RpcError.fromUnknown() │
|
|
69
|
-
│ RpcError с типом (временная/постоянная) │
|
|
70
|
-
│ ↓ (определяет retry/DLX логику) │
|
|
71
|
-
│ Отправка через RabbitMQ в api-service │
|
|
72
|
-
│ │
|
|
73
|
-
└─────────────────────────────────────────────────────────────┘
|
|
74
|
-
|
|
75
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
76
|
-
│ WebSocket Контекст (api-service) │
|
|
77
|
-
├─────────────────────────────────────────────────────────────┤
|
|
78
|
-
│ │
|
|
79
|
-
│ SocketGateway.publish() │
|
|
80
|
-
│ ↓ (ошибка при отправке события) │
|
|
81
|
-
│ SocketError.fromUnknown() │
|
|
82
|
-
│ ↓ │
|
|
83
|
-
│ Логирование + сохранение в Redis │
|
|
84
|
-
│ ↓ (опционально) │
|
|
85
|
-
│ Отправка события ERROR на фронт через socket │
|
|
86
|
-
│ │
|
|
87
|
-
└─────────────────────────────────────────────────────────────┘
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Принципы работы
|
|
91
|
-
|
|
92
|
-
1. **Глобальные фильтры** - все ошибки обрабатываются автоматически через глобальные фильтры
|
|
93
|
-
2. **Без catch блоков в контроллерах** - контроллеры не должны иметь try-catch блоков
|
|
94
|
-
3. **Автоматическое преобразование** - ошибки автоматически преобразуются между контекстами
|
|
95
|
-
4. **Сохранение типа и статуса** - тип ошибки и статус-код сохраняются при преобразовании
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## 🚨 Классы ошибок
|
|
100
|
-
|
|
101
|
-
### HttpError
|
|
102
|
-
|
|
103
|
-
Класс ошибок для HTTP контекста. Используется в `api-service` для обработки HTTP запросов.
|
|
104
|
-
|
|
105
|
-
**Расположение:** `src/errors/http.error.ts`
|
|
106
|
-
|
|
107
|
-
**Наследование:** `HttpException` (NestJS)
|
|
108
|
-
|
|
109
|
-
**Особенности:**
|
|
110
|
-
- Автоматически преобразует `RpcException`/`RpcError` в HTTP ошибку
|
|
111
|
-
- Автоматически преобразует `SocketError` в HTTP ошибку
|
|
112
|
-
- Сохраняет правильный HTTP статус-код
|
|
113
|
-
- Включает stack trace в ответе, если он доступен в исходной ошибке
|
|
114
|
-
|
|
115
|
-
**Пример использования:**
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
// В HTTP контроллере (БЕЗ catch блока)
|
|
119
|
-
@Get("/analytics/global")
|
|
120
|
-
async getGlobal() {
|
|
121
|
-
// Ошибка автоматически обработается UnifiedExceptionFilter
|
|
122
|
-
return await this.analyticsService.getGlobalData();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// В сервисе (опционально для логирования)
|
|
126
|
-
async getGlobalData() {
|
|
127
|
-
try {
|
|
128
|
-
const data = await this.rabbitMQService.publish(...);
|
|
129
|
-
return data;
|
|
130
|
-
} catch (error) {
|
|
131
|
-
// Дополнительное логирование (опционально)
|
|
132
|
-
this.logger.error(`Ошибка RPC: ${error}`);
|
|
133
|
-
// Пробрасываем дальше - фильтр преобразует в HttpError
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**Преобразование ошибок:**
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// HttpError.fromUnknown() автоматически обрабатывает:
|
|
143
|
-
- HttpError → возвращает как есть (с опциональным descriptionPrefix)
|
|
144
|
-
- HttpException → извлекает статус и тип, сохраняет оригинальный формат message (массив остается массивом в getResponse())
|
|
145
|
-
- RpcException/RpcError → преобразует в HTTP с правильным статусом
|
|
146
|
-
- SocketError → преобразует в HTTP
|
|
147
|
-
- Error → определяет тип и создает HttpError
|
|
148
|
-
- Объекты с полем message → преобразует в HttpError
|
|
149
|
-
- Неизвестные типы → преобразует в строку и создает HttpError
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Важно:**
|
|
153
|
-
- При преобразовании `HttpException` с массивом сообщений, оригинальный массив сохраняется в `getResponse()`, но для внутреннего `message` используется строка (массив объединяется через `"; "`).
|
|
154
|
-
- Опциональный параметр `descriptionPrefix` добавляется к сообщению ошибки для дополнительного контекста.
|
|
155
|
-
|
|
156
|
-
### RpcError
|
|
157
|
-
|
|
158
|
-
Класс ошибок для RabbitMQ RPC контекста. Используется в `analytics-service`, `data-service`, `token-service`.
|
|
159
|
-
|
|
160
|
-
**Расположение:** `src/errors/rpc.error.ts`
|
|
161
|
-
|
|
162
|
-
**Наследование:** `RpcException` (NestJS)
|
|
163
|
-
|
|
164
|
-
**Особенности:**
|
|
165
|
-
- Автоматически определяет тип ошибки (временная/постоянная)
|
|
166
|
-
- Сохраняет статус-код для будущего преобразования в HTTP
|
|
167
|
-
- Сериализуется через RabbitMQ с сохранением типа и сообщения
|
|
168
|
-
- Используется в `RpcExceptionFilter` для retry/DLX логики
|
|
169
|
-
|
|
170
|
-
**Типы ошибок:**
|
|
171
|
-
|
|
172
|
-
**Временные (retry):**
|
|
173
|
-
- `RPC_TIMEOUT` - таймаут соединения
|
|
174
|
-
- `SERVICE_UNAVAILABLE` - сервис недоступен
|
|
175
|
-
- `RPC_SERVICE_UNAVAILABLE` - RPC сервис недоступен
|
|
176
|
-
|
|
177
|
-
**Постоянные (DLX сразу):**
|
|
178
|
-
- `BAD_REQUEST` - неправильный запрос
|
|
179
|
-
- `UNAUTHORIZED` - ошибка авторизации
|
|
180
|
-
- `FORBIDDEN` - ошибка доступа
|
|
181
|
-
- `NOT_FOUND` - ресурс не найден
|
|
182
|
-
- `VALIDATION_ERROR` - ошибка валидации
|
|
183
|
-
- `RPC_VALIDATION_ERROR` - ошибка валидации RPC
|
|
184
|
-
|
|
185
|
-
**Методы:**
|
|
186
|
-
- `isTransient(): boolean` - проверяет, является ли ошибка временной (требует retry)
|
|
187
|
-
|
|
188
|
-
**Пример использования:**
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
// В RPC контроллере (БЕЗ catch блока)
|
|
192
|
-
@MessagePattern(ROUTING_KEYS.ANALYTICS_GLOBAL)
|
|
193
|
-
async getGlobalData(
|
|
194
|
-
@Payload() _: GlobalDataIncomeDto,
|
|
195
|
-
@Ctx() ctx: RmqContext,
|
|
196
|
-
): Promise<GlobalDataOutcomeDto> {
|
|
197
|
-
// Ошибка автоматически обработается UnifiedExceptionFilter
|
|
198
|
-
const data = await this.analyticsService.getGlobalData();
|
|
199
|
-
this.acknowledge(ctx);
|
|
200
|
-
return data;
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
**Преобразование ошибок:**
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
// RpcError.fromUnknown() автоматически:
|
|
208
|
-
- Распознает ошибки с свойством isAxiosError и преобразует их с сохранением типа и статуса
|
|
209
|
-
- Определяет тип ошибки из сообщения
|
|
210
|
-
- Классифицирует как временную или постоянную
|
|
211
|
-
- Сохраняет оригинальную ошибку для логирования
|
|
212
|
-
- Обрабатывает сериализованные объекты ошибок из RabbitMQ
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
**Интеграция с AxiosError:**
|
|
216
|
-
|
|
217
|
-
`RpcError.fromUnknown()` автоматически распознает ошибки с свойством `isAxiosError` и преобразует их в `RpcError` с сохранением типа ошибки и статус-кода из оригинальной Axios ошибки.
|
|
218
|
-
|
|
219
|
-
### SocketError
|
|
220
|
-
|
|
221
|
-
Класс ошибок для WebSocket контекста. Используется в `api-service` для обработки ошибок Socket.io.
|
|
222
|
-
|
|
223
|
-
**Расположение:** `src/errors/socket.error.ts`
|
|
224
|
-
|
|
225
|
-
**Наследование:** `HttpException` (NestJS) - для совместимости с HTTP фильтрами
|
|
226
|
-
|
|
227
|
-
**Особенности:**
|
|
228
|
-
- Используется для обработки ошибок при отправке событий через Socket.io
|
|
229
|
-
- Логируется и сохраняется в Redis
|
|
230
|
-
- Может быть отправлена на фронт через событие `SOCKET_EVENTS.ERROR`
|
|
231
|
-
- Не связана с HTTP или RPC ошибками
|
|
232
|
-
|
|
233
|
-
**Пример использования:**
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
// В SocketGateway
|
|
237
|
-
async publish(userId: string, event: SOCKET_EVENTS, payload: unknown) {
|
|
238
|
-
try {
|
|
239
|
-
await this.io.timeout(5000).to(`user:${userId}`).emitWithAck(event, payload);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
const socketError = SocketError.fromUnknown(error);
|
|
242
|
-
|
|
243
|
-
// Логирование
|
|
244
|
-
this.logger.error(`Ошибка отправки события: ${socketError.message}`);
|
|
245
|
-
|
|
246
|
-
// Сохранение в Redis
|
|
247
|
-
await this.redisService.hSet(
|
|
248
|
-
REDIS_H_KEYS.SOCKET_ERROR,
|
|
249
|
-
userId,
|
|
250
|
-
socketError.message,
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
// Опционально: отправка ошибки на фронт
|
|
254
|
-
await this.io.to(`user:${userId}`).emit(SOCKET_EVENTS.ERROR, {
|
|
255
|
-
status: "error",
|
|
256
|
-
message: socketError.message,
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
throw socketError;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### JsonRpcException
|
|
265
|
-
|
|
266
|
-
Класс исключений для JSON-RPC 2.0 протокола. Используется для обработки ошибок в формате JSON-RPC.
|
|
267
|
-
|
|
268
|
-
**Расположение:** `src/errors/json-rpc.error.ts`
|
|
269
|
-
|
|
270
|
-
**Наследование:** `HttpException` (NestJS) - для совместимости с NestJS фильтрами
|
|
271
|
-
|
|
272
|
-
**Особенности:**
|
|
273
|
-
- Поддерживает стандартные коды ошибок JSON-RPC 2.0
|
|
274
|
-
- Автоматически маппит HTTP статусы на JSON-RPC коды ошибок
|
|
275
|
-
- Поддерживает дополнительные данные в поле `data`
|
|
276
|
-
- Может быть создан из `HttpException` или обычной `Error`
|
|
277
|
-
- Возвращает ответ в формате JSON-RPC 2.0
|
|
278
|
-
|
|
279
|
-
**Пример использования:**
|
|
280
|
-
|
|
281
|
-
```typescript
|
|
282
|
-
import { JsonRpcException, JsonRpcErrorCode } from "@packages/nest-common";
|
|
283
|
-
|
|
284
|
-
// Создание исключения с кодом ошибки
|
|
285
|
-
throw new JsonRpcException(
|
|
286
|
-
JsonRpcErrorCode.INVALID_REQUEST,
|
|
287
|
-
"Invalid request parameters",
|
|
288
|
-
{ requestId: "123", details: "Missing required field" },
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
// Создание из HttpException
|
|
292
|
-
const httpException = new HttpException("Not found", HttpStatus.NOT_FOUND);
|
|
293
|
-
const rpcException = JsonRpcException.fromHttpException(httpException, "request-id");
|
|
294
|
-
|
|
295
|
-
// Создание из обычной Error
|
|
296
|
-
const error = new Error("Something went wrong");
|
|
297
|
-
const rpcException = JsonRpcException.fromError(error, "request-id");
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
**Коды ошибок JSON-RPC 2.0:**
|
|
301
|
-
- `PARSE_ERROR` (-32700) - Ошибка парсинга JSON
|
|
302
|
-
- `INVALID_REQUEST` (-32600) - Невалидный запрос
|
|
303
|
-
- `METHOD_NOT_FOUND` (-32601) - Метод не найден
|
|
304
|
-
- `INVALID_PARAMS` (-32602) - Невалидные параметры
|
|
305
|
-
- `INTERNAL_ERROR` (-32603) - Внутренняя ошибка сервера
|
|
306
|
-
- `UNAUTHORIZED` (-32001) - Неавторизованный доступ
|
|
307
|
-
- `FORBIDDEN` (-32002) - Доступ запрещен
|
|
308
|
-
- `NOT_FOUND` (-32003) - Ресурс не найден
|
|
309
|
-
- И другие кастомные коды (-32004 до -32099)
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
## 🛡️ Глобальные фильтры
|
|
314
|
-
|
|
315
|
-
### UnifiedExceptionFilter
|
|
316
|
-
|
|
317
|
-
Единый глобальный фильтр для обработки HTTP и RPC ошибок. Автоматически определяет тип контекста и использует соответствующий обработчик.
|
|
318
|
-
|
|
319
|
-
**Расположение:** `src/filters/unified-exception.filter.ts`
|
|
320
|
-
|
|
321
|
-
### JsonRpcExceptionFilter
|
|
322
|
-
|
|
323
|
-
Фильтр исключений для обработки ошибок в формате JSON-RPC 2.0. Преобразует все исключения NestJS в формат JSON-RPC 2.0.
|
|
324
|
-
|
|
325
|
-
**Расположение:** `src/filters/json-rpc-exception.filter.ts`
|
|
326
|
-
|
|
327
|
-
**Особенности:**
|
|
328
|
-
- Автоматически извлекает `requestId` из тела запроса
|
|
329
|
-
- Преобразует `JsonRpcException` в JSON-RPC формат ответа
|
|
330
|
-
- Преобразует обычные `Error` в JSON-RPC формат с автоматическим определением статуса
|
|
331
|
-
- Обрабатывает неизвестные типы исключений
|
|
332
|
-
- Логирует все ошибки с контекстом
|
|
333
|
-
|
|
334
|
-
**Использование:**
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
import { Controller, Post, Body, UseFilters } from "@nestjs/common";
|
|
338
|
-
import { JsonRpcExceptionFilter, JsonRpcValidationPipe } from "@packages/nest-common";
|
|
339
|
-
import type { JsonRpcRequest } from "@packages/nest-common";
|
|
340
|
-
|
|
341
|
-
@Controller("mcp")
|
|
342
|
-
@UseFilters(JsonRpcExceptionFilter)
|
|
343
|
-
export default class McpController extends BaseController {
|
|
344
|
-
constructor(private readonly logger: LoggerService) {
|
|
345
|
-
super();
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
@Post()
|
|
349
|
-
async handleJsonRpcRequest(
|
|
350
|
-
@Body(JsonRpcValidationPipe) request: JsonRpcRequest,
|
|
351
|
-
) {
|
|
352
|
-
// Обработка запроса
|
|
353
|
-
if (request.method === "test.method") {
|
|
354
|
-
return {
|
|
355
|
-
jsonrpc: "2.0",
|
|
356
|
-
id: request.id,
|
|
357
|
-
result: { success: true },
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
throw new JsonRpcException(
|
|
362
|
-
JsonRpcErrorCode.METHOD_NOT_FOUND,
|
|
363
|
-
`Method ${request.method} not found`,
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
**Регистрация глобально:**
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
// В main.ts
|
|
373
|
-
import { JsonRpcExceptionFilter } from "@packages/nest-common";
|
|
374
|
-
|
|
375
|
-
async function bootstrap() {
|
|
376
|
-
const app = await NestFactory.create(AppModule);
|
|
377
|
-
const logger = await connectLogger(app, "ServiceName");
|
|
378
|
-
|
|
379
|
-
app.useGlobalFilters(new JsonRpcExceptionFilter(logger));
|
|
380
|
-
|
|
381
|
-
await app.listen(PORT);
|
|
382
|
-
}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
**Регистрация:**
|
|
386
|
-
|
|
387
|
-
```typescript
|
|
388
|
-
// В main.ts микросервиса
|
|
389
|
-
import { UnifiedExceptionFilter } from "@packages/nest-common";
|
|
390
|
-
|
|
391
|
-
async function bootstrap() {
|
|
392
|
-
const app = await NestFactory.create(AppModule);
|
|
393
|
-
const logger = await connectLogger(app, "ServiceName");
|
|
394
|
-
|
|
395
|
-
// Регистрируем глобальный фильтр
|
|
396
|
-
// Для RPC контекста можно передать dlxExchange для DLX логики
|
|
397
|
-
app.useGlobalFilters(
|
|
398
|
-
new UnifiedExceptionFilter(logger, dlxExchange), // dlxExchange опционально
|
|
399
|
-
);
|
|
400
|
-
|
|
401
|
-
await app.listen(PORT);
|
|
402
|
-
}
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
**Логика работы:**
|
|
406
|
-
|
|
407
|
-
1. Определяет тип контекста (HTTP или RPC)
|
|
408
|
-
2. Использует соответствующий обработчик:
|
|
409
|
-
- **HTTP контекст** → `HttpExceptionFilter.handleException()`
|
|
410
|
-
- **RPC контекст** → `RpcExceptionFilter.handleException()`
|
|
411
|
-
3. Обработчики преобразуют ошибки и формируют ответы
|
|
412
|
-
|
|
413
|
-
**Внутренние обработчики:**
|
|
414
|
-
|
|
415
|
-
- **HttpExceptionFilter** (`src/filters/http-exception-handler.ts`) - обрабатывает HTTP ошибки
|
|
416
|
-
- **RpcExceptionFilter** (`src/filters/rpc-exception-handler.ts`) - обрабатывает RPC ошибки с поддержкой retry/DLX
|
|
417
|
-
|
|
418
|
-
**Формат HTTP ответа:**
|
|
419
|
-
|
|
420
|
-
```json
|
|
421
|
-
{
|
|
422
|
-
"statusCode": 500,
|
|
423
|
-
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
424
|
-
"path": "/api/analytics/global",
|
|
425
|
-
"error": "INTERNAL_SERVER_ERROR",
|
|
426
|
-
"message": "Ошибка получения данных",
|
|
427
|
-
"stack": "..." // Если доступен в исходной ошибке
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
**Retry логика для RPC:**
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
// Постоянная ошибка (BAD_REQUEST, UNAUTHORIZED, etc.)
|
|
435
|
-
if (!isTransient) {
|
|
436
|
-
// Сразу в DLX, не тратим попытки retry
|
|
437
|
-
channel.publish(dlxExchange, routingKey, content);
|
|
438
|
-
channel.ack(msg);
|
|
439
|
-
throw rpcError; // Отправляем клиенту
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Временная ошибка (TIMEOUT, SERVICE_UNAVAILABLE)
|
|
443
|
-
if (retries < MAX_RETRIES) {
|
|
444
|
-
// Отправляем в retry очередь
|
|
445
|
-
channel.nack(msg, false, false);
|
|
446
|
-
return; // НЕ throw - сообщение будет обработано повторно
|
|
447
|
-
} else {
|
|
448
|
-
// Превышен лимит - в DLX
|
|
449
|
-
channel.publish(dlxExchange, routingKey, content);
|
|
450
|
-
channel.ack(msg);
|
|
451
|
-
throw rpcError; // Отправляем клиенту
|
|
452
|
-
}
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
---
|
|
456
|
-
|
|
457
|
-
## 🔍 Перехватчики
|
|
458
|
-
|
|
459
|
-
Перехватчики (Interceptors) используются для логирования и мониторинга всех запросов в приложении. Они автоматически перехватывают запросы до и после их выполнения, позволяя логировать метрики производительности и отслеживать все входящие запросы.
|
|
460
|
-
|
|
461
|
-
### UnifiedInterceptor
|
|
462
|
-
|
|
463
|
-
Единый глобальный перехватчик для логирования HTTP и RPC запросов. Автоматически определяет тип контекста и использует соответствующий обработчик.
|
|
464
|
-
|
|
465
|
-
**Расположение:** `src/interceptors/unified.interceptor.ts`
|
|
466
|
-
|
|
467
|
-
**Особенности:**
|
|
468
|
-
- Автоматически определяет тип контекста (HTTP или RPC)
|
|
469
|
-
- Использует соответствующий обработчик для логирования
|
|
470
|
-
- Логирует все входящие запросы с метриками производительности
|
|
471
|
-
- Работает с любыми эндпоинтами автоматически
|
|
472
|
-
|
|
473
|
-
**Регистрация:**
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
// В main.ts микросервиса
|
|
477
|
-
import { UnifiedInterceptor } from "@packages/nest-common";
|
|
478
|
-
import { LoggerService } from "@makebelieve21213-packages/logger";
|
|
479
|
-
|
|
480
|
-
async function bootstrap() {
|
|
481
|
-
const app = await NestFactory.create(AppModule);
|
|
482
|
-
const logger = await connectLogger(app, "ServiceName");
|
|
483
|
-
|
|
484
|
-
// Регистрируем глобальный перехватчик
|
|
485
|
-
app.useGlobalInterceptors(new UnifiedInterceptor(logger));
|
|
486
|
-
|
|
487
|
-
await app.listen(PORT);
|
|
488
|
-
}
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
**Внутренние обработчики:**
|
|
492
|
-
|
|
493
|
-
- **HttpLoggingInterceptor** (`src/interceptors/http-logging.interceptor.ts`) - логирует HTTP запросы
|
|
494
|
-
- **RpcLoggingInterceptor** (`src/interceptors/rpc-logging.interceptor.ts`) - логирует RPC запросы
|
|
495
|
-
- **WebSocketLoggingInterceptor** (`src/interceptors/websocket-logging.interceptor.ts`) - логирует WebSocket запросы
|
|
496
|
-
|
|
497
|
-
**Логика работы:**
|
|
498
|
-
|
|
499
|
-
1. Определяет тип контекста (HTTP, RPC или WebSocket)
|
|
500
|
-
2. Использует соответствующий обработчик:
|
|
501
|
-
- **HTTP контекст** → `HttpLoggingInterceptor.intercept()`
|
|
502
|
-
- **RPC контекст** → `RpcLoggingInterceptor.intercept()`
|
|
503
|
-
3. Обработчики логируют запросы с метриками времени выполнения
|
|
504
|
-
|
|
505
|
-
**Формат логов HTTP:**
|
|
506
|
-
|
|
507
|
-
```
|
|
508
|
-
[HTTP] Incoming request [GET /api/analytics/global] from 127.0.0.1 (Mozilla/5.0...)
|
|
509
|
-
[HTTP] Request completed [GET /api/analytics/global] 200 45ms
|
|
510
|
-
[HTTP] Request failed [GET /api/analytics/global] 500 120ms - Internal server error
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
**Формат логов RPC:**
|
|
514
|
-
|
|
515
|
-
```
|
|
516
|
-
[RPC] Incoming request [pattern: analytics.global]
|
|
517
|
-
[RPC] Request completed [pattern: analytics.global, duration: 45ms]
|
|
518
|
-
[RPC] Request failed [pattern: analytics.global, duration: 120ms, error: Service unavailable]
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
---
|
|
522
|
-
|
|
523
|
-
## 🎛️ Базовые классы
|
|
524
|
-
|
|
525
|
-
### BaseController
|
|
526
|
-
|
|
527
|
-
Базовый контроллер для всех контроллеров проекта. Предоставляет общую функциональность для HTTP и RPC контроллеров.
|
|
528
|
-
|
|
529
|
-
**Расположение:** `src/base/base.controller.ts`
|
|
530
|
-
|
|
531
|
-
**Особенности:**
|
|
532
|
-
- Предоставляет общие методы для HTTP и RPC контроллеров
|
|
533
|
-
- Метод `acknowledge(ctx: RmqContext)` для подтверждения RPC сообщений
|
|
534
|
-
- Автоматически настраивает контекст логирования на имя класса контроллера
|
|
535
|
-
|
|
536
|
-
**Методы:**
|
|
537
|
-
- `protected acknowledge(ctx: RmqContext): void` - отправляет acknowledge сообщение в RabbitMQ
|
|
538
|
-
|
|
539
|
-
**Пример использования:**
|
|
540
|
-
|
|
541
|
-
```typescript
|
|
542
|
-
import { Controller, Get } from "@nestjs/common";
|
|
543
|
-
import { BaseController } from "@packages/nest-common";
|
|
544
|
-
import { LoggerService } from "@makebelieve21213-packages/logger";
|
|
545
|
-
|
|
546
|
-
@Controller("analytics")
|
|
547
|
-
export default class AnalyticsController extends BaseController {
|
|
548
|
-
constructor(
|
|
549
|
-
private readonly analyticsService: AnalyticsService,
|
|
550
|
-
logger: LoggerService,
|
|
551
|
-
) {
|
|
552
|
-
super(logger);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
@Get("global")
|
|
556
|
-
async getGlobal() {
|
|
557
|
-
// БЕЗ try-catch - глобальный фильтр все обработает
|
|
558
|
-
return await this.analyticsService.getGlobalData();
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
---
|
|
564
|
-
|
|
565
|
-
## 🔧 Пайпы валидации
|
|
566
|
-
|
|
567
|
-
### HttpValidationPipe
|
|
568
|
-
|
|
569
|
-
Валидация DTO для HTTP контроллеров с использованием `class-validator`.
|
|
570
|
-
|
|
571
|
-
**Расположение:** `src/pipes/http-validation.pipe.ts`
|
|
572
|
-
|
|
573
|
-
**Особенности:**
|
|
574
|
-
- Автоматическое преобразование plain objects в DTO экземпляры через `plainToInstance` с `enableImplicitConversion: true`
|
|
575
|
-
- Валидация с `whitelist: true` и `forbidNonWhitelisted: true`
|
|
576
|
-
- Выбрасывает `BadRequestException` при ошибках валидации
|
|
577
|
-
- Сообщения об ошибках объединяются через `"; "`
|
|
578
|
-
|
|
579
|
-
**Использование:**
|
|
580
|
-
|
|
581
|
-
```typescript
|
|
582
|
-
import { Controller, Post, Body } from "@nestjs/common";
|
|
583
|
-
import { HttpValidationPipe } from "@packages/nest-common";
|
|
584
|
-
import { CreateDto } from "@packages/dtos";
|
|
585
|
-
|
|
586
|
-
@Controller("tokens")
|
|
587
|
-
export default class TokenController extends BaseController {
|
|
588
|
-
@Post("create")
|
|
589
|
-
@UsePipes(new HttpValidationPipe(CreateDto))
|
|
590
|
-
async create(@Body() dto: CreateDto) {
|
|
591
|
-
return await this.tokenService.create(dto);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
### RpcValidationPipe
|
|
597
|
-
|
|
598
|
-
Валидация DTO для RabbitMQ микросервисов с использованием `class-validator`.
|
|
599
|
-
|
|
600
|
-
**Расположение:** `src/pipes/rpc-validation.pipe.ts`
|
|
601
|
-
|
|
602
|
-
**Особенности:**
|
|
603
|
-
- Автоматическое преобразование plain objects в DTO экземпляры через `plainToInstance` с `enableImplicitConversion: true`
|
|
604
|
-
- Обрабатывает случаи, когда value может быть `undefined` или `null` (для пустых DTO передает пустой объект)
|
|
605
|
-
- Автоматически извлекает и исключает `correlationId` и `correlationTimestamp` из сообщения перед валидацией (для поддержки идемпотентности)
|
|
606
|
-
- Валидация с `whitelist: true` и `forbidNonWhitelisted: true`
|
|
607
|
-
- Выбрасывает `RpcException` при ошибках валидации
|
|
608
|
-
- Сообщения об ошибках объединяются через `"; "`
|
|
609
|
-
|
|
610
|
-
**Использование:**
|
|
611
|
-
|
|
612
|
-
```typescript
|
|
613
|
-
import { Controller } from "@nestjs/common";
|
|
614
|
-
import { MessagePattern, Payload } from "@nestjs/microservices";
|
|
615
|
-
import { RpcValidationPipe } from "@packages/nest-common";
|
|
616
|
-
import { GlobalDataIncomeDto } from "@packages/dtos";
|
|
617
|
-
|
|
618
|
-
@Controller()
|
|
619
|
-
export default class AnalyticsController extends BaseController {
|
|
620
|
-
@MessagePattern(ROUTING_KEYS.ANALYTICS_GLOBAL)
|
|
621
|
-
@UsePipes(new RpcValidationPipe(GlobalDataIncomeDto))
|
|
622
|
-
async getGlobalData(@Payload() dto: GlobalDataIncomeDto) {
|
|
623
|
-
return await this.analyticsService.getGlobalData();
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
**Обработка correlationId и correlationTimestamp:**
|
|
629
|
-
|
|
630
|
-
Pipe автоматически извлекает поля `correlationId` и `correlationTimestamp` из входящего сообщения (если они присутствуют) и исключает их из валидации DTO. Это позволяет использовать эти поля для идемпотентности без необходимости добавлять их в DTO классы.
|
|
631
|
-
|
|
632
|
-
### FileValidationPipe
|
|
633
|
-
|
|
634
|
-
Валидация загруженных файлов с проверкой размера, MIME типа и расширения.
|
|
635
|
-
|
|
636
|
-
**Расположение:** `src/pipes/file-validation.pipe.ts`
|
|
637
|
-
|
|
638
|
-
**Особенности:**
|
|
639
|
-
- Валидирует одиночные файлы и массивы файлов
|
|
640
|
-
- Проверяет размер файла (по умолчанию максимум 10MB)
|
|
641
|
-
- Проверяет MIME тип файла
|
|
642
|
-
- Проверяет расширение файла
|
|
643
|
-
- Выбрасывает `BadRequestException` при ошибках валидации
|
|
644
|
-
|
|
645
|
-
**Использование:**
|
|
646
|
-
|
|
647
|
-
```typescript
|
|
648
|
-
import { Controller, Post, UploadedFile, UseInterceptors } from "@nestjs/common";
|
|
649
|
-
import { FileInterceptor } from "@nestjs/platform-express";
|
|
650
|
-
import { FileValidationPipe } from "@packages/nest-common";
|
|
651
|
-
|
|
652
|
-
@Controller("upload")
|
|
653
|
-
export default class UploadController extends BaseController {
|
|
654
|
-
@Post("file")
|
|
655
|
-
@UseInterceptors(FileInterceptor("file"))
|
|
656
|
-
async uploadFile(
|
|
657
|
-
@UploadedFile(
|
|
658
|
-
new FileValidationPipe({
|
|
659
|
-
maxSize: 5 * 1024 * 1024, // 5MB
|
|
660
|
-
allowedMimeTypes: ["image/jpeg", "image/png"],
|
|
661
|
-
allowedExtensions: ["jpg", "jpeg", "png"],
|
|
662
|
-
}),
|
|
663
|
-
)
|
|
664
|
-
file: MulterFile,
|
|
665
|
-
) {
|
|
666
|
-
return await this.uploadService.saveFile(file);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
### QueryValidationPipe
|
|
672
|
-
|
|
673
|
-
Валидация query параметров HTTP запросов с использованием `class-validator`.
|
|
674
|
-
|
|
675
|
-
**Расположение:** `src/pipes/query-validation.pipe.ts`
|
|
676
|
-
|
|
677
|
-
**Особенности:**
|
|
678
|
-
- Автоматическое преобразование plain objects в DTO экземпляры через `plainToInstance`
|
|
679
|
-
- Валидация только для query параметров (`metadata.type === "query"`)
|
|
680
|
-
- Выбрасывает `BadRequestException` с детальными ошибками валидации
|
|
681
|
-
- Поддерживает вложенные объекты и массивы
|
|
682
|
-
|
|
683
|
-
**Использование:**
|
|
684
|
-
|
|
685
|
-
```typescript
|
|
686
|
-
import { Controller, Get, Query } from "@nestjs/common";
|
|
687
|
-
import { QueryValidationPipe } from "@packages/nest-common";
|
|
688
|
-
import { PaginationDto } from "@packages/dtos";
|
|
689
|
-
|
|
690
|
-
@Controller("users")
|
|
691
|
-
export default class UsersController extends BaseController {
|
|
692
|
-
@Get()
|
|
693
|
-
async getUsers(
|
|
694
|
-
@Query(new QueryValidationPipe(PaginationDto)) query: PaginationDto,
|
|
695
|
-
) {
|
|
696
|
-
return await this.usersService.findAll(query);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
### HeaderValidationPipe
|
|
702
|
-
|
|
703
|
-
Валидация заголовков HTTP запросов с использованием `class-validator`.
|
|
704
|
-
|
|
705
|
-
**Расположение:** `src/pipes/header-validation.pipe.ts`
|
|
706
|
-
|
|
707
|
-
**Особенности:**
|
|
708
|
-
- Автоматическое преобразование plain objects в DTO экземпляры через `plainToInstance`
|
|
709
|
-
- Валидация только для custom параметров (`metadata.type === "custom"`)
|
|
710
|
-
- Выбрасывает `BadRequestException` с детальными ошибками валидации
|
|
711
|
-
- Поддерживает вложенные объекты и массивы
|
|
712
|
-
|
|
713
|
-
**Использование:**
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
import { Controller, Get, Headers } from "@nestjs/common";
|
|
717
|
-
import { HeaderValidationPipe } from "@packages/nest-common";
|
|
718
|
-
import { ApiHeadersDto } from "@packages/dtos";
|
|
719
|
-
|
|
720
|
-
@Controller("api")
|
|
721
|
-
export default class ApiController extends BaseController {
|
|
722
|
-
@Get("data")
|
|
723
|
-
async getData(
|
|
724
|
-
@Headers(new HeaderValidationPipe(ApiHeadersDto)) headers: ApiHeadersDto,
|
|
725
|
-
) {
|
|
726
|
-
return await this.apiService.getData(headers);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
### JsonRpcValidationPipe
|
|
732
|
-
|
|
733
|
-
Валидация JSON-RPC 2.0 запросов согласно спецификации протокола.
|
|
734
|
-
|
|
735
|
-
**Расположение:** `src/pipes/json-rpc-validation.pipe.ts`
|
|
736
|
-
|
|
737
|
-
**Особенности:**
|
|
738
|
-
- Проверяет структуру запроса согласно спецификации JSON-RPC 2.0
|
|
739
|
-
- Валидирует версию протокола (`jsonrpc: "2.0"`)
|
|
740
|
-
- Проверяет наличие и тип поля `method` (должно быть строкой)
|
|
741
|
-
- Валидирует тип поля `id` (должно быть string, number или null)
|
|
742
|
-
- Валидирует тип поля `params` (должно быть object или undefined)
|
|
743
|
-
- Игнорирует объекты с NestJS метаданными (constructorRef, handler, contextType)
|
|
744
|
-
- Выбрасывает `JsonRpcException` при ошибках валидации
|
|
745
|
-
|
|
746
|
-
**Использование:**
|
|
747
|
-
|
|
748
|
-
```typescript
|
|
749
|
-
import { Controller, Post, Body } from "@nestjs/common";
|
|
750
|
-
import { JsonRpcValidationPipe, JsonRpcExceptionFilter } from "@packages/nest-common";
|
|
751
|
-
import type { JsonRpcRequest } from "@packages/nest-common";
|
|
752
|
-
|
|
753
|
-
@Controller("mcp")
|
|
754
|
-
@UseFilters(JsonRpcExceptionFilter)
|
|
755
|
-
export default class McpController extends BaseController {
|
|
756
|
-
@Post()
|
|
757
|
-
async handleJsonRpcRequest(
|
|
758
|
-
@Body(JsonRpcValidationPipe) request: JsonRpcRequest,
|
|
759
|
-
) {
|
|
760
|
-
// Обработка JSON-RPC запроса
|
|
761
|
-
return {
|
|
762
|
-
jsonrpc: "2.0",
|
|
763
|
-
id: request.id,
|
|
764
|
-
result: { /* результат */ },
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
---
|
|
771
|
-
|
|
772
|
-
## 🔍 Перехватчики (дополнительные)
|
|
773
|
-
|
|
774
|
-
### ResponseInterceptor
|
|
775
|
-
|
|
776
|
-
Перехватчик для стандартизации формата HTTP ответов. Автоматически оборачивает ответы в стандартный формат `StandardResponse`.
|
|
777
|
-
|
|
778
|
-
**Расположение:** `src/interceptors/response.interceptor.ts`
|
|
779
|
-
|
|
780
|
-
**Особенности:**
|
|
781
|
-
- Автоматически оборачивает ответы в формат `{ success: true, data: T, meta: {...} }`
|
|
782
|
-
- Добавляет метаданные: timestamp, path, requestId
|
|
783
|
-
- Если ответ уже в стандартном формате, возвращает как есть
|
|
784
|
-
- Работает только с HTTP контекстом
|
|
785
|
-
|
|
786
|
-
**Использование:**
|
|
787
|
-
|
|
788
|
-
```typescript
|
|
789
|
-
import { Controller, Get, UseInterceptors } from "@nestjs/common";
|
|
790
|
-
import { ResponseInterceptor } from "@packages/nest-common";
|
|
791
|
-
|
|
792
|
-
@Controller("users")
|
|
793
|
-
@UseInterceptors(ResponseInterceptor)
|
|
794
|
-
export default class UsersController extends BaseController {
|
|
795
|
-
@Get()
|
|
796
|
-
async getUsers() {
|
|
797
|
-
// Ответ автоматически обернется в StandardResponse
|
|
798
|
-
return await this.usersService.findAll();
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
**Формат ответа:**
|
|
804
|
-
|
|
805
|
-
```json
|
|
806
|
-
{
|
|
807
|
-
"success": true,
|
|
808
|
-
"data": { /* ваши данные */ },
|
|
809
|
-
"meta": {
|
|
810
|
-
"timestamp": "2024-01-01T00:00:00.000Z",
|
|
811
|
-
"path": "/api/users",
|
|
812
|
-
"requestId": "req-123"
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
### SerializeInterceptor
|
|
818
|
-
|
|
819
|
-
Перехватчик для сериализации ответов с исключением чувствительных данных на основе DTO классов.
|
|
820
|
-
|
|
821
|
-
**Расположение:** `src/interceptors/serialize.interceptor.ts`
|
|
822
|
-
|
|
823
|
-
**Особенности:**
|
|
824
|
-
- Использует декоратор `@Serialize()` для указания DTO класса
|
|
825
|
-
- Автоматически исключает поля, не помеченные `@Expose()` в DTO
|
|
826
|
-
- Поддерживает массивы данных
|
|
827
|
-
- Работает с `class-transformer` и `excludeExtraneousValues: true`
|
|
828
|
-
|
|
829
|
-
**Использование:**
|
|
830
|
-
|
|
831
|
-
```typescript
|
|
832
|
-
import { Controller, Get, UseInterceptors } from "@nestjs/common";
|
|
833
|
-
import { SerializeInterceptor, Serialize } from "@packages/nest-common";
|
|
834
|
-
import { UserResponseDto } from "@packages/dtos";
|
|
835
|
-
|
|
836
|
-
@Controller("users")
|
|
837
|
-
@UseInterceptors(SerializeInterceptor)
|
|
838
|
-
export default class UsersController extends BaseController {
|
|
839
|
-
@Get(":id")
|
|
840
|
-
@Serialize(UserResponseDto) // Указываем DTO для сериализации
|
|
841
|
-
async getUser(@Param("id") id: string) {
|
|
842
|
-
// Поля, не помеченные @Expose() в UserResponseDto, будут исключены
|
|
843
|
-
return await this.usersService.findOne(id);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
### CompressionInterceptor
|
|
849
|
-
|
|
850
|
-
Перехватчик для автоматического сжатия больших HTTP ответов с использованием gzip.
|
|
851
|
-
|
|
852
|
-
**Расположение:** `src/interceptors/compression.interceptor.ts`
|
|
853
|
-
|
|
854
|
-
**Особенности:**
|
|
855
|
-
- Автоматически сжимает ответы больше заданного порога (по умолчанию 1024 байт)
|
|
856
|
-
- Использует gzip сжатие
|
|
857
|
-
- Проверяет эффективность сжатия (минимальный коэффициент сжатия 0.8)
|
|
858
|
-
- Устанавливает заголовки `Content-Encoding: gzip` и `Content-Type: application/json`
|
|
859
|
-
- Если сжатие неэффективно, возвращает исходные данные
|
|
860
|
-
|
|
861
|
-
**Использование:**
|
|
862
|
-
|
|
863
|
-
```typescript
|
|
864
|
-
import { Controller, Get, UseInterceptors } from "@nestjs/common";
|
|
865
|
-
import { CompressionInterceptor } from "@packages/nest-common";
|
|
866
|
-
|
|
867
|
-
@Controller("data")
|
|
868
|
-
@UseInterceptors(new CompressionInterceptor(2048, 0.7)) // Порог 2KB, коэффициент 0.7
|
|
869
|
-
export default class DataController extends BaseController {
|
|
870
|
-
@Get("large")
|
|
871
|
-
async getLargeData() {
|
|
872
|
-
// Большие ответы автоматически сжимаются
|
|
873
|
-
return await this.dataService.getLargeDataset();
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
```
|
|
877
|
-
|
|
878
|
-
### RequestIdResponseInterceptor
|
|
879
|
-
|
|
880
|
-
Перехватчик для добавления Request ID в заголовки HTTP ответов.
|
|
881
|
-
|
|
882
|
-
**Расположение:** `src/interceptors/request-id-response.interceptor.ts`
|
|
883
|
-
|
|
884
|
-
**Особенности:**
|
|
885
|
-
- Извлекает Request ID из заголовков запроса (`x-request-id`) или `request.id`
|
|
886
|
-
- Добавляет Request ID в заголовок ответа `X-Request-ID`
|
|
887
|
-
- Работает только с HTTP контекстом
|
|
888
|
-
|
|
889
|
-
**Использование:**
|
|
890
|
-
|
|
891
|
-
```typescript
|
|
892
|
-
import { Controller, Get, UseInterceptors } from "@nestjs/common";
|
|
893
|
-
import { RequestIdResponseInterceptor } from "@packages/nest-common";
|
|
894
|
-
|
|
895
|
-
@Controller("api")
|
|
896
|
-
@UseInterceptors(RequestIdResponseInterceptor)
|
|
897
|
-
export default class ApiController extends BaseController {
|
|
898
|
-
@Get("data")
|
|
899
|
-
async getData() {
|
|
900
|
-
// Request ID автоматически добавится в заголовки ответа
|
|
901
|
-
return await this.apiService.getData();
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
```
|
|
905
|
-
|
|
906
|
-
---
|
|
907
|
-
|
|
908
|
-
## 🛡️ Guards (Охранники)
|
|
909
|
-
|
|
910
|
-
Guards используются для контроля доступа к эндпоинтам на основе аутентификации, авторизации и других условий.
|
|
911
|
-
|
|
912
|
-
### JwtAuthGuard
|
|
913
|
-
|
|
914
|
-
Guard для проверки JWT токена и аутентификации пользователя.
|
|
915
|
-
|
|
916
|
-
**Расположение:** `src/guards/jwt-auth.guard.ts`
|
|
917
|
-
|
|
918
|
-
**Особенности:**
|
|
919
|
-
- Проверяет наличие пользователя в `request.user`
|
|
920
|
-
- Уважает декоратор `@Public()` - пропускает публичные эндпоинты
|
|
921
|
-
- Выбрасывает `UnauthorizedException` если пользователь не аутентифицирован
|
|
922
|
-
|
|
923
|
-
**Использование:**
|
|
924
|
-
|
|
925
|
-
```typescript
|
|
926
|
-
import { Controller, Get, UseGuards } from "@nestjs/common";
|
|
927
|
-
import { JwtAuthGuard } from "@packages/nest-common";
|
|
928
|
-
|
|
929
|
-
@Controller("users")
|
|
930
|
-
@UseGuards(JwtAuthGuard)
|
|
931
|
-
export default class UsersController extends BaseController {
|
|
932
|
-
@Get("profile")
|
|
933
|
-
async getProfile(@Request() req) {
|
|
934
|
-
// req.user доступен после прохождения JwtAuthGuard
|
|
935
|
-
return await this.usersService.getProfile(req.user);
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
```
|
|
939
|
-
|
|
940
|
-
### ApiKeyGuard
|
|
941
|
-
|
|
942
|
-
Guard для проверки API ключа в заголовках запроса.
|
|
943
|
-
|
|
944
|
-
**Расположение:** `src/guards/api-key.guard.ts`
|
|
945
|
-
|
|
946
|
-
**Особенности:**
|
|
947
|
-
- Проверяет наличие API ключа в заголовке (по умолчанию `x-api-key`)
|
|
948
|
-
- Уважает декоратор `@Public()` - пропускает публичные эндпоинты
|
|
949
|
-
- Работает только с эндпоинтами, помеченными декоратором `@ApiKey()`
|
|
950
|
-
- Опционально проверяет ключ против списка валидных ключей
|
|
951
|
-
- Выбрасывает `UnauthorizedException` при отсутствии или невалидном ключе
|
|
952
|
-
|
|
953
|
-
**Использование:**
|
|
954
|
-
|
|
955
|
-
```typescript
|
|
956
|
-
import { Controller, Get, UseGuards } from "@nestjs/common";
|
|
957
|
-
import { ApiKeyGuard, ApiKey } from "@packages/nest-common";
|
|
958
|
-
|
|
959
|
-
@Controller("api")
|
|
960
|
-
export default class ApiController extends BaseController {
|
|
961
|
-
constructor() {
|
|
962
|
-
// Создаем guard с валидными ключами
|
|
963
|
-
super();
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
@Get("data")
|
|
967
|
-
@UseGuards(new ApiKeyGuard(undefined, "x-api-key", new Set(["key1", "key2"])))
|
|
968
|
-
@ApiKey() // Помечаем эндпоинт как требующий API ключ
|
|
969
|
-
async getData() {
|
|
970
|
-
return await this.apiService.getData();
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
```
|
|
974
|
-
|
|
975
|
-
### RolesGuard
|
|
976
|
-
|
|
977
|
-
Guard для проверки ролей пользователя.
|
|
978
|
-
|
|
979
|
-
**Расположение:** `src/guards/roles.guard.ts`
|
|
980
|
-
|
|
981
|
-
**Особенности:**
|
|
982
|
-
- Проверяет наличие требуемых ролей у пользователя
|
|
983
|
-
- Использует декоратор `@Roles()` для указания требуемых ролей
|
|
984
|
-
- Проверяет, что у пользователя есть хотя бы одна из указанных ролей
|
|
985
|
-
- Выбрасывает `ForbiddenException` при недостаточных правах
|
|
986
|
-
|
|
987
|
-
**Использование:**
|
|
988
|
-
|
|
989
|
-
```typescript
|
|
990
|
-
import { Controller, Get, UseGuards } from "@nestjs/common";
|
|
991
|
-
import { JwtAuthGuard, RolesGuard, Roles } from "@packages/nest-common";
|
|
992
|
-
|
|
993
|
-
@Controller("admin")
|
|
994
|
-
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
995
|
-
export default class AdminController extends BaseController {
|
|
996
|
-
@Get("users")
|
|
997
|
-
@Roles("admin", "moderator") // Требуются роли admin или moderator
|
|
998
|
-
async getUsers() {
|
|
999
|
-
return await this.adminService.getUsers();
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
```
|
|
1003
|
-
|
|
1004
|
-
### PermissionsGuard
|
|
1005
|
-
|
|
1006
|
-
Guard для проверки разрешений пользователя.
|
|
1007
|
-
|
|
1008
|
-
**Расположение:** `src/guards/permissions.guard.ts`
|
|
1009
|
-
|
|
1010
|
-
**Особенности:**
|
|
1011
|
-
- Проверяет наличие требуемых разрешений у пользователя
|
|
1012
|
-
- Использует декоратор `@Permissions()` для указания требуемых разрешений
|
|
1013
|
-
- Проверяет, что у пользователя есть ВСЕ указанные разрешения
|
|
1014
|
-
- Выбрасывает `ForbiddenException` при недостаточных правах
|
|
1015
|
-
|
|
1016
|
-
**Использование:**
|
|
1017
|
-
|
|
1018
|
-
```typescript
|
|
1019
|
-
import { Controller, Delete, UseGuards } from "@nestjs/common";
|
|
1020
|
-
import { JwtAuthGuard, PermissionsGuard, Permissions } from "@packages/nest-common";
|
|
1021
|
-
|
|
1022
|
-
@Controller("users")
|
|
1023
|
-
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
|
1024
|
-
export default class UsersController extends BaseController {
|
|
1025
|
-
@Delete(":id")
|
|
1026
|
-
@Permissions("users:delete", "users:write") // Требуются ВСЕ указанные разрешения
|
|
1027
|
-
async deleteUser(@Param("id") id: string) {
|
|
1028
|
-
return await this.usersService.delete(id);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
### RateLimitGuard
|
|
1034
|
-
|
|
1035
|
-
Guard для ограничения частоты запросов (Rate Limiting).
|
|
1036
|
-
|
|
1037
|
-
**Расположение:** `src/guards/rate-limit.guard.ts`
|
|
1038
|
-
|
|
1039
|
-
**Особенности:**
|
|
1040
|
-
- Ограничивает количество запросов за временное окно
|
|
1041
|
-
- По умолчанию: 100 запросов в минуту для аутентифицированных пользователей
|
|
1042
|
-
- Для публичных эндпоинтов применяется более строгий лимит (50% от базового)
|
|
1043
|
-
- Использует IP адрес и путь запроса для идентификации клиента
|
|
1044
|
-
- Поддерживает кастомный генератор ключей
|
|
1045
|
-
- Выбрасывает `HttpException` с кодом 429 (Too Many Requests) при превышении лимита
|
|
1046
|
-
- Автоматически очищает устаревшие записи
|
|
1047
|
-
|
|
1048
|
-
**Использование:**
|
|
1049
|
-
|
|
1050
|
-
```typescript
|
|
1051
|
-
import { Controller, Get, UseGuards } from "@nestjs/common";
|
|
1052
|
-
import { RateLimitGuard } from "@packages/nest-common";
|
|
1053
|
-
|
|
1054
|
-
@Controller("api")
|
|
1055
|
-
@UseGuards(new RateLimitGuard(undefined, 200, 60000)) // 200 запросов в минуту
|
|
1056
|
-
export default class ApiController extends BaseController {
|
|
1057
|
-
@Get("data")
|
|
1058
|
-
async getData() {
|
|
1059
|
-
return await this.apiService.getData();
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
```
|
|
1063
|
-
|
|
1064
|
-
### WebSocketAuthGuard
|
|
1065
|
-
|
|
1066
|
-
Guard для проверки аутентификации при подключении к WebSocket.
|
|
1067
|
-
|
|
1068
|
-
**Расположение:** `src/guards/websocket-auth.guard.ts`
|
|
1069
|
-
|
|
1070
|
-
**Особенности:**
|
|
1071
|
-
- Проверяет наличие токена в данных подключения или handshake
|
|
1072
|
-
- Поддерживает кастомный валидатор токена через коллбек `TokenValidator`
|
|
1073
|
-
- Выбрасывает `WsException` при отсутствии или невалидном токене
|
|
1074
|
-
|
|
1075
|
-
**Использование:**
|
|
1076
|
-
|
|
1077
|
-
```typescript
|
|
1078
|
-
import { WebSocketGateway, SubscribeMessage, UseGuards } from "@nestjs/websockets";
|
|
1079
|
-
import { WebSocketAuthGuard } from "@packages/nest-common";
|
|
1080
|
-
|
|
1081
|
-
@WebSocketGateway()
|
|
1082
|
-
export default class ChatGateway {
|
|
1083
|
-
constructor(
|
|
1084
|
-
@Inject(WebSocketAuthGuard)
|
|
1085
|
-
private readonly authGuard: WebSocketAuthGuard,
|
|
1086
|
-
) {
|
|
1087
|
-
// Создаем guard с валидатором токена
|
|
1088
|
-
this.authGuard = new WebSocketAuthGuard(async (token, context) => {
|
|
1089
|
-
return await this.authService.validateToken(token);
|
|
1090
|
-
});
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
@SubscribeMessage("message")
|
|
1094
|
-
@UseGuards(this.authGuard)
|
|
1095
|
-
async handleMessage(client: Socket, payload: unknown) {
|
|
1096
|
-
// Токен проверен через WebSocketAuthGuard
|
|
1097
|
-
return await this.chatService.sendMessage(payload);
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
```
|
|
1101
|
-
|
|
1102
|
-
---
|
|
1103
|
-
|
|
1104
|
-
## 🎨 Декораторы
|
|
1105
|
-
|
|
1106
|
-
Декораторы используются для добавления метаданных к эндпоинтам и контроллерам.
|
|
1107
|
-
|
|
1108
|
-
### @Public()
|
|
1109
|
-
|
|
1110
|
-
Декоратор для пометки эндпоинта как публичного (без аутентификации).
|
|
1111
|
-
|
|
1112
|
-
**Расположение:** `src/decorators/public.decorator.ts`
|
|
1113
|
-
|
|
1114
|
-
**Использование:**
|
|
1115
|
-
|
|
1116
|
-
```typescript
|
|
1117
|
-
import { Controller, Get } from "@nestjs/common";
|
|
1118
|
-
import { Public } from "@packages/nest-common";
|
|
1119
|
-
|
|
1120
|
-
@Controller("public")
|
|
1121
|
-
export default class PublicController extends BaseController {
|
|
1122
|
-
@Get("info")
|
|
1123
|
-
@Public() // Эндпоинт доступен без аутентификации
|
|
1124
|
-
async getInfo() {
|
|
1125
|
-
return { message: "Public information" };
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
```
|
|
1129
|
-
|
|
1130
|
-
### @Roles()
|
|
1131
|
-
|
|
1132
|
-
Декоратор для указания ролей, необходимых для доступа к эндпоинту.
|
|
1133
|
-
|
|
1134
|
-
**Расположение:** `src/decorators/roles.decorator.ts`
|
|
1135
|
-
|
|
1136
|
-
**Использование:**
|
|
1137
|
-
|
|
1138
|
-
```typescript
|
|
1139
|
-
import { Controller, Get } from "@nestjs/common";
|
|
1140
|
-
import { Roles } from "@packages/nest-common";
|
|
1141
|
-
|
|
1142
|
-
@Controller("admin")
|
|
1143
|
-
export default class AdminController extends BaseController {
|
|
1144
|
-
@Get("dashboard")
|
|
1145
|
-
@Roles("admin", "moderator") // Требуются роли admin или moderator
|
|
1146
|
-
async getDashboard() {
|
|
1147
|
-
return await this.adminService.getDashboard();
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
```
|
|
1151
|
-
|
|
1152
|
-
### @Permissions()
|
|
1153
|
-
|
|
1154
|
-
Декоратор для указания разрешений, необходимых для доступа к эндпоинту.
|
|
1155
|
-
|
|
1156
|
-
**Расположение:** `src/decorators/permissions.decorator.ts`
|
|
1157
|
-
|
|
1158
|
-
**Использование:**
|
|
1159
|
-
|
|
1160
|
-
```typescript
|
|
1161
|
-
import { Controller, Delete } from "@nestjs/common";
|
|
1162
|
-
import { Permissions } from "@packages/nest-common";
|
|
1163
|
-
|
|
1164
|
-
@Controller("users")
|
|
1165
|
-
export default class UsersController extends BaseController {
|
|
1166
|
-
@Delete(":id")
|
|
1167
|
-
@Permissions("users:delete", "users:write") // Требуются ВСЕ указанные разрешения
|
|
1168
|
-
async deleteUser(@Param("id") id: string) {
|
|
1169
|
-
return await this.usersService.delete(id);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
```
|
|
1173
|
-
|
|
1174
|
-
### @ApiKey()
|
|
1175
|
-
|
|
1176
|
-
Декоратор для пометки эндпоинта как требующего API ключ.
|
|
1177
|
-
|
|
1178
|
-
**Расположение:** `src/decorators/api-key.decorator.ts`
|
|
1179
|
-
|
|
1180
|
-
**Использование:**
|
|
1181
|
-
|
|
1182
|
-
```typescript
|
|
1183
|
-
import { Controller, Get } from "@nestjs/common";
|
|
1184
|
-
import { ApiKey } from "@packages/nest-common";
|
|
1185
|
-
|
|
1186
|
-
@Controller("api")
|
|
1187
|
-
export default class ApiController extends BaseController {
|
|
1188
|
-
@Get("data")
|
|
1189
|
-
@ApiKey() // Эндпоинт требует API ключ в заголовке x-api-key
|
|
1190
|
-
async getData() {
|
|
1191
|
-
return await this.apiService.getData();
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
```
|
|
1195
|
-
|
|
1196
|
-
### @Serialize()
|
|
1197
|
-
|
|
1198
|
-
Декоратор для указания класса DTO для сериализации ответа.
|
|
1199
|
-
|
|
1200
|
-
**Расположение:** `src/decorators/serialize.decorator.ts`
|
|
1201
|
-
|
|
1202
|
-
**Использование:**
|
|
1203
|
-
|
|
1204
|
-
```typescript
|
|
1205
|
-
import { Controller, Get } from "@nestjs/common";
|
|
1206
|
-
import { Serialize } from "@packages/nest-common";
|
|
1207
|
-
import { UserResponseDto } from "@packages/dtos";
|
|
1208
|
-
|
|
1209
|
-
@Controller("users")
|
|
1210
|
-
export default class UsersController extends BaseController {
|
|
1211
|
-
@Get(":id")
|
|
1212
|
-
@Serialize(UserResponseDto) // Ответ будет сериализован через UserResponseDto
|
|
1213
|
-
async getUser(@Param("id") id: string) {
|
|
1214
|
-
return await this.usersService.findOne(id);
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
```
|
|
1218
|
-
|
|
1219
|
-
---
|
|
1220
|
-
|
|
1221
|
-
## 🛠️ Утилиты
|
|
1222
|
-
|
|
1223
|
-
### validateEnv
|
|
1224
|
-
|
|
1225
|
-
Функция валидации переменных окружения с использованием Joi.
|
|
1226
|
-
|
|
1227
|
-
**Расположение:** `src/utils/env-validator.ts`
|
|
1228
|
-
|
|
1229
|
-
**Особенности:**
|
|
1230
|
-
- Валидация обязательных переменных окружения
|
|
1231
|
-
- Выбрасывает `Error` с описанием отсутствующих ключей
|
|
1232
|
-
- Возвращает валидированный объект env
|
|
1233
|
-
- Использует `Joi.string().required()` для каждого обязательного ключа
|
|
1234
|
-
- Разрешает дополнительные ключи через `unknown(true)`
|
|
1235
|
-
- Использует `abortEarly: false` для получения всех ошибок валидации
|
|
1236
|
-
|
|
1237
|
-
**Использование:**
|
|
1238
|
-
|
|
1239
|
-
```typescript
|
|
1240
|
-
import { validateEnv } from "@packages/nest-common";
|
|
1241
|
-
|
|
1242
|
-
const env = process.env;
|
|
1243
|
-
const requiredKeys = ["DATABASE_URL", "REDIS_URL", "RABBITMQ_URL"];
|
|
1244
|
-
|
|
1245
|
-
// Выбрасывает Error если ключи отсутствуют
|
|
1246
|
-
const validatedEnv = validateEnv(env, requiredKeys);
|
|
1247
|
-
```
|
|
1248
|
-
|
|
1249
|
-
### Утилиты для работы с контекстом
|
|
1250
|
-
|
|
1251
|
-
Функции для извлечения данных из контекста выполнения запроса.
|
|
1252
|
-
|
|
1253
|
-
**Расположение:** `src/utils/context.utils.ts`
|
|
1254
|
-
|
|
1255
|
-
**Функции:**
|
|
1256
|
-
- `getUserFromContext(context: ExecutionContext): UserFromContext | undefined` - извлекает пользователя из контекста
|
|
1257
|
-
- `getIpFromContext(context: ExecutionContext): string` - извлекает IP адрес из контекста
|
|
1258
|
-
- `getUserAgentFromContext(context: ExecutionContext): string` - извлекает User-Agent из контекста
|
|
1259
|
-
- `getRequestIdFromContext(context: ExecutionContext): string` - извлекает Request ID из контекста
|
|
1260
|
-
|
|
1261
|
-
**Использование:**
|
|
1262
|
-
|
|
1263
|
-
```typescript
|
|
1264
|
-
import { getUserFromContext, getIpFromContext } from "@packages/nest-common";
|
|
35
|
+
## 📦 Установка
|
|
1265
36
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
@Get("profile")
|
|
1269
|
-
async getProfile(@ExecutionContext() context: ExecutionContext) {
|
|
1270
|
-
const user = getUserFromContext(context);
|
|
1271
|
-
const ip = getIpFromContext(context);
|
|
1272
|
-
|
|
1273
|
-
return await this.usersService.getProfile(user, ip);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
37
|
+
```bash
|
|
38
|
+
npm install @makebelieve21213-packages/nest-common
|
|
1276
39
|
```
|
|
1277
40
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
Функция для создания опций CORS для NestJS приложения.
|
|
1281
|
-
|
|
1282
|
-
**Расположение:** `src/utils/cors.utils.ts`
|
|
41
|
+
## 🔧 Быстрый старт
|
|
1283
42
|
|
|
1284
|
-
|
|
1285
|
-
- Настраивает CORS с разумными значениями по умолчанию
|
|
1286
|
-
- Поддерживает кастомные настройки origin, methods, headers и т.д.
|
|
1287
|
-
- По умолчанию разрешает все источники, основные HTTP методы и стандартные заголовки
|
|
1288
|
-
|
|
1289
|
-
**Использование:**
|
|
43
|
+
### Шаг 1: Регистрация глобальных фильтров и перехватчиков
|
|
1290
44
|
|
|
1291
45
|
```typescript
|
|
46
|
+
// main.ts
|
|
1292
47
|
import { NestFactory } from "@nestjs/core";
|
|
1293
|
-
import {
|
|
48
|
+
import { UnifiedExceptionFilter, UnifiedInterceptor } from "@makebelieve21213-packages/nest-common";
|
|
49
|
+
import { LoggerService } from "@makebelieve21213-packages/logger";
|
|
1294
50
|
|
|
1295
51
|
async function bootstrap() {
|
|
1296
52
|
const app = await NestFactory.create(AppModule);
|
|
53
|
+
const logger = await connectLogger(app, "ServiceName");
|
|
1297
54
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
credentials: true,
|
|
1301
|
-
}));
|
|
1302
|
-
|
|
1303
|
-
await app.listen(3000);
|
|
1304
|
-
}
|
|
1305
|
-
```
|
|
1306
|
-
|
|
1307
|
-
### createCompressionOptions
|
|
1308
|
-
|
|
1309
|
-
Функция для создания опций compression для NestJS приложения.
|
|
1310
|
-
|
|
1311
|
-
**Расположение:** `src/utils/compression.utils.ts`
|
|
1312
|
-
|
|
1313
|
-
**Особенности:**
|
|
1314
|
-
- Настраивает compression middleware с разумными значениями по умолчанию
|
|
1315
|
-
- По умолчанию сжимает только текстовые типы контента
|
|
1316
|
-
- Поддерживает кастомные настройки уровня сжатия, порога и фильтров
|
|
1317
|
-
|
|
1318
|
-
**Использование:**
|
|
1319
|
-
|
|
1320
|
-
```typescript
|
|
1321
|
-
import { NestFactory } from "@nestjs/core";
|
|
1322
|
-
import { createCompressionOptions } from "@packages/nest-common";
|
|
1323
|
-
import compression from "compression";
|
|
1324
|
-
|
|
1325
|
-
async function bootstrap() {
|
|
1326
|
-
const app = await NestFactory.create(AppModule);
|
|
55
|
+
// Регистрируем глобальный фильтр ошибок
|
|
56
|
+
app.useGlobalFilters(new UnifiedExceptionFilter(logger, dlxExchange)); // dlxExchange опционально
|
|
1327
57
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
threshold: 512, // Сжимать ответы больше 512 байт
|
|
1331
|
-
})));
|
|
58
|
+
// Регистрируем глобальный перехватчик логирования
|
|
59
|
+
app.useGlobalInterceptors(new UnifiedInterceptor(logger));
|
|
1332
60
|
|
|
1333
61
|
await app.listen(3000);
|
|
1334
62
|
}
|
|
1335
63
|
```
|
|
1336
64
|
|
|
1337
|
-
###
|
|
1338
|
-
|
|
1339
|
-
Функция для создания опций версионирования API для NestJS приложения.
|
|
1340
|
-
|
|
1341
|
-
**Расположение:** `src/utils/versioning.utils.ts`
|
|
1342
|
-
|
|
1343
|
-
**Особенности:**
|
|
1344
|
-
- Поддерживает три стратегии версионирования: URI, Header, Media-Type
|
|
1345
|
-
- Настраивает версионирование с указанием типа и версии по умолчанию
|
|
1346
|
-
|
|
1347
|
-
**Использование:**
|
|
65
|
+
### Шаг 2: HTTP контроллер
|
|
1348
66
|
|
|
1349
67
|
```typescript
|
|
1350
|
-
import {
|
|
1351
|
-
import {
|
|
68
|
+
import { Controller, Get } from "@nestjs/common";
|
|
69
|
+
import { BaseController } from "@makebelieve21213-packages/nest-common";
|
|
70
|
+
import { LoggerService } from "@makebelieve21213-packages/logger";
|
|
1352
71
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
72
|
+
@Controller("test")
|
|
73
|
+
export default class TestController extends BaseController {
|
|
74
|
+
constructor(
|
|
75
|
+
private readonly testService: TestService,
|
|
76
|
+
logger: LoggerService,
|
|
77
|
+
) {
|
|
78
|
+
super(logger);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Get("data")
|
|
82
|
+
async getData() {
|
|
83
|
+
// БЕЗ try-catch - глобальный фильтр все обработает
|
|
84
|
+
return await this.testService.getData();
|
|
85
|
+
}
|
|
1362
86
|
}
|
|
1363
87
|
```
|
|
1364
88
|
|
|
1365
|
-
###
|
|
1366
|
-
|
|
1367
|
-
Функции для валидации и работы с файлами.
|
|
1368
|
-
|
|
1369
|
-
**Расположение:** `src/utils/file.utils.ts`
|
|
1370
|
-
|
|
1371
|
-
**Функции:**
|
|
1372
|
-
- `validateFile(file: MulterFile, options?: FileValidationOptions): FileValidationResult` - валидирует файл по заданным опциям
|
|
1373
|
-
- `getFileExtension(filename: string): string` - получает расширение файла из имени
|
|
1374
|
-
- `formatFileSize(bytes: number): string` - форматирует размер файла в читаемый формат
|
|
1375
|
-
|
|
1376
|
-
**Использование:**
|
|
89
|
+
### Шаг 3: RPC контроллер
|
|
1377
90
|
|
|
1378
91
|
```typescript
|
|
1379
|
-
import {
|
|
92
|
+
import { Controller } from "@nestjs/common";
|
|
93
|
+
import { MessagePattern, Payload, Ctx } from "@nestjs/microservices";
|
|
94
|
+
import { BaseController, RpcValidationPipe } from "@makebelieve21213-packages/nest-common";
|
|
95
|
+
import { LoggerService } from "@makebelieve21213-packages/logger";
|
|
1380
96
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
97
|
+
@Controller()
|
|
98
|
+
export default class TestController extends BaseController {
|
|
99
|
+
constructor(
|
|
100
|
+
private readonly testService: TestService,
|
|
101
|
+
logger: LoggerService,
|
|
102
|
+
) {
|
|
103
|
+
super(logger);
|
|
104
|
+
}
|
|
1386
105
|
|
|
1387
|
-
|
|
1388
|
-
|
|
106
|
+
@MessagePattern("test.pattern")
|
|
107
|
+
@UsePipes(new RpcValidationPipe(TestRequestDto))
|
|
108
|
+
async getData(
|
|
109
|
+
@Payload() dto: TestRequestDto,
|
|
110
|
+
@Ctx() ctx: RmqContext,
|
|
111
|
+
): Promise<TestResponseDto> {
|
|
112
|
+
const data = await this.testService.getData();
|
|
113
|
+
this.acknowledge(ctx);
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
1389
116
|
}
|
|
1390
|
-
|
|
1391
|
-
const size = formatFileSize(1024 * 1024); // "1 MB"
|
|
1392
117
|
```
|
|
1393
118
|
|
|
1394
|
-
###
|
|
1395
|
-
|
|
1396
|
-
Сервис для реализации паттерна Circuit Breaker для защиты от каскадных сбоев.
|
|
1397
|
-
|
|
1398
|
-
**Расположение:** `src/utils/circuit-breaker.ts`
|
|
1399
|
-
|
|
1400
|
-
**Особенности:**
|
|
1401
|
-
- Реализует три состояния: CLOSED (нормальная работа), OPEN (разомкнут), HALF_OPEN (тестирование)
|
|
1402
|
-
- Автоматически открывает circuit при превышении порога ошибок
|
|
1403
|
-
- Автоматически закрывает circuit при успешных запросах в HALF_OPEN состоянии
|
|
1404
|
-
- Поддерживает настройку порогов ошибок и успешных запросов
|
|
1405
|
-
- Логирует переходы между состояниями
|
|
1406
|
-
|
|
1407
|
-
**Использование:**
|
|
119
|
+
### Шаг 4: Использование Guards
|
|
1408
120
|
|
|
1409
121
|
```typescript
|
|
1410
|
-
import {
|
|
1411
|
-
import {
|
|
122
|
+
import { Controller, Get, UseGuards } from "@nestjs/common";
|
|
123
|
+
import { JwtAuthGuard, RolesGuard, Roles, Public } from "@makebelieve21213-packages/nest-common";
|
|
1412
124
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
125
|
+
@Controller("test")
|
|
126
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
127
|
+
export default class TestController extends BaseController {
|
|
128
|
+
@Get("public")
|
|
129
|
+
@Public() // Публичный эндпоинт
|
|
130
|
+
async getPublicInfo() {
|
|
131
|
+
return { message: "Public information" };
|
|
132
|
+
}
|
|
1418
133
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
return await
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
|
|
134
|
+
@Get("profile")
|
|
135
|
+
async getProfile(@Request() req) {
|
|
136
|
+
return await this.testService.getProfile(req.user);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@Get("admin")
|
|
140
|
+
@Roles("admin", "moderator") // Требуются роли
|
|
141
|
+
async getAdminData() {
|
|
142
|
+
return await this.testService.getData();
|
|
143
|
+
}
|
|
1425
144
|
}
|
|
1426
145
|
```
|
|
1427
146
|
|
|
1428
|
-
|
|
147
|
+
## 🔄 Система обработки ошибок
|
|
1429
148
|
|
|
1430
|
-
|
|
149
|
+
Пакет предоставляет единую систему обработки ошибок для трех типов контекстов:
|
|
150
|
+
- **HTTP** - для HTTP контроллеров
|
|
151
|
+
- **RPC** - для RabbitMQ RPC запросов
|
|
152
|
+
- **WebSocket** - для Socket.io соединений
|
|
1431
153
|
|
|
1432
|
-
|
|
154
|
+
Все ошибки автоматически преобразуются между контекстами с сохранением типа и статус-кода.
|
|
1433
155
|
|
|
1434
|
-
|
|
1435
|
-
- Работает одинаково в dev (src) и production (dist) режимах
|
|
1436
|
-
- Всегда использует пути относительно src/
|
|
1437
|
-
- Поддерживает три типа путей: locales, srcRoot, file
|
|
1438
|
-
- Автоматически находит корень сервиса по наличию папки src
|
|
156
|
+
### Принципы работы
|
|
1439
157
|
|
|
1440
|
-
|
|
158
|
+
1. **Глобальные фильтры** - все ошибки обрабатываются автоматически через `UnifiedExceptionFilter`
|
|
159
|
+
2. **Без catch блоков в контроллерах** - контроллеры не должны иметь try-catch блоков
|
|
160
|
+
3. **Автоматическое преобразование** - ошибки автоматически преобразуются между контекстами
|
|
161
|
+
4. **Сохранение типа и статуса** - тип ошибки и статус-код сохраняются при преобразовании
|
|
1441
162
|
|
|
1442
|
-
|
|
1443
|
-
import { getServicePath } from "@packages/nest-common";
|
|
1444
|
-
|
|
1445
|
-
// Получить путь к локалям
|
|
1446
|
-
const localesPath = getServicePath({
|
|
1447
|
-
serviceName: "api-service",
|
|
1448
|
-
dirname: __dirname,
|
|
1449
|
-
pathType: "locales",
|
|
1450
|
-
relativePath: "locales",
|
|
1451
|
-
});
|
|
163
|
+
### Классы ошибок
|
|
1452
164
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
pathType: "file",
|
|
1458
|
-
relativePath: "config/app.config.ts",
|
|
1459
|
-
});
|
|
1460
|
-
```
|
|
165
|
+
**HttpError** - для HTTP контекста
|
|
166
|
+
- Автоматически преобразует `RpcException`/`RpcError` в HTTP ошибку
|
|
167
|
+
- Сохраняет правильный HTTP статус-код
|
|
168
|
+
- Метод: `HttpError.fromUnknown(error, descriptionPrefix?)`
|
|
1461
169
|
|
|
1462
|
-
|
|
170
|
+
**RpcError** - для RabbitMQ RPC контекста
|
|
171
|
+
- Автоматически определяет тип ошибки (временная/постоянная)
|
|
172
|
+
- Используется в `RpcExceptionFilter` для retry/DLX логики
|
|
173
|
+
- Метод: `RpcError.fromUnknown(error)`
|
|
174
|
+
- Метод: `isTransient(): boolean`
|
|
1463
175
|
|
|
1464
|
-
|
|
176
|
+
**SocketError** - для WebSocket контекста
|
|
177
|
+
- Используется для обработки ошибок при отправке событий через Socket.io
|
|
178
|
+
- Метод: `SocketError.fromUnknown(error)`
|
|
1465
179
|
|
|
1466
|
-
|
|
180
|
+
**JsonRpcException** - для JSON-RPC 2.0 протокола
|
|
181
|
+
- Поддерживает стандартные коды ошибок JSON-RPC 2.0
|
|
182
|
+
- Автоматически маппит HTTP статусы на JSON-RPC коды ошибок
|
|
1467
183
|
|
|
1468
|
-
|
|
184
|
+
## 📚 API Reference
|
|
1469
185
|
|
|
1470
|
-
|
|
186
|
+
### Фильтры
|
|
1471
187
|
|
|
188
|
+
**UnifiedExceptionFilter** - единый глобальный фильтр для обработки HTTP и RPC ошибок
|
|
1472
189
|
```typescript
|
|
1473
|
-
|
|
1474
|
-
statusCode: number;
|
|
1475
|
-
timestamp: string;
|
|
1476
|
-
path: string;
|
|
1477
|
-
error: string;
|
|
1478
|
-
message: string;
|
|
1479
|
-
stack?: string; // Если доступен в исходной ошибке
|
|
1480
|
-
}
|
|
190
|
+
app.useGlobalFilters(new UnifiedExceptionFilter(logger, dlxExchange));
|
|
1481
191
|
```
|
|
1482
192
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
Интерфейс стандартизированного ответа для HTTP запросов.
|
|
1486
|
-
|
|
1487
|
-
**Расположение:** `src/types/http-response.ts`
|
|
1488
|
-
|
|
193
|
+
**JsonRpcExceptionFilter** - фильтр для обработки JSON-RPC 2.0 ошибок
|
|
1489
194
|
```typescript
|
|
1490
|
-
|
|
1491
|
-
success: boolean;
|
|
1492
|
-
data: T;
|
|
1493
|
-
meta?: {
|
|
1494
|
-
timestamp: string;
|
|
1495
|
-
path?: string;
|
|
1496
|
-
requestId?: string;
|
|
1497
|
-
[key: string]: unknown;
|
|
1498
|
-
};
|
|
1499
|
-
}
|
|
195
|
+
@UseFilters(JsonRpcExceptionFilter)
|
|
1500
196
|
```
|
|
1501
197
|
|
|
1502
|
-
###
|
|
1503
|
-
|
|
1504
|
-
Enum типов ошибок RPC для классификации временных и постоянных ошибок.
|
|
1505
|
-
|
|
1506
|
-
**Расположение:** `src/types/rpc-types.ts`
|
|
1507
|
-
|
|
1508
|
-
**Временные ошибки (retry):**
|
|
1509
|
-
- `RPC_TIMEOUT` - таймаут соединения
|
|
1510
|
-
- `SERVICE_UNAVAILABLE` - сервис недоступен
|
|
1511
|
-
- `RPC_SERVICE_UNAVAILABLE` - RPC сервис недоступен
|
|
1512
|
-
|
|
1513
|
-
**Постоянные ошибки (DLX сразу):**
|
|
1514
|
-
- `BAD_REQUEST` - неправильный запрос
|
|
1515
|
-
- `UNAUTHORIZED` - ошибка авторизации
|
|
1516
|
-
- `FORBIDDEN` - ошибка доступа
|
|
1517
|
-
- `NOT_FOUND` - ресурс не найден
|
|
1518
|
-
- `VALIDATION_ERROR` - ошибка валидации
|
|
1519
|
-
- `RPC_VALIDATION_ERROR` - ошибка валидации RPC
|
|
1520
|
-
|
|
1521
|
-
### UserFromContext
|
|
1522
|
-
|
|
1523
|
-
Интерфейс пользователя из контекста запроса.
|
|
1524
|
-
|
|
1525
|
-
**Расположение:** `src/types/context-types.ts`
|
|
198
|
+
### Перехватчики
|
|
1526
199
|
|
|
200
|
+
**UnifiedInterceptor** - единый глобальный перехватчик для логирования запросов
|
|
1527
201
|
```typescript
|
|
1528
|
-
|
|
1529
|
-
id?: string | number;
|
|
1530
|
-
email?: string;
|
|
1531
|
-
roles?: string[];
|
|
1532
|
-
permissions?: string[];
|
|
1533
|
-
[key: string]: unknown;
|
|
1534
|
-
}
|
|
202
|
+
app.useGlobalInterceptors(new UnifiedInterceptor(logger));
|
|
1535
203
|
```
|
|
1536
204
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
Типы для валидации файлов.
|
|
1540
|
-
|
|
1541
|
-
**Расположение:** `src/types/file-validation-types.ts`
|
|
1542
|
-
|
|
205
|
+
**ResponseInterceptor** - стандартизация формата HTTP ответов
|
|
1543
206
|
```typescript
|
|
1544
|
-
|
|
1545
|
-
maxSize?: number; // Максимальный размер в байтах
|
|
1546
|
-
allowedMimeTypes?: string[]; // Разрешенные MIME типы
|
|
1547
|
-
allowedExtensions?: string[]; // Разрешенные расширения файлов
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
interface FileValidationResult {
|
|
1551
|
-
isValid: boolean;
|
|
1552
|
-
errors: string[];
|
|
1553
|
-
}
|
|
207
|
+
@UseInterceptors(ResponseInterceptor)
|
|
1554
208
|
```
|
|
1555
209
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
Типы для Circuit Breaker.
|
|
1559
|
-
|
|
1560
|
-
**Расположение:** `src/types/circuit-breaker-types.ts`
|
|
1561
|
-
|
|
210
|
+
**SerializeInterceptor** - сериализация ответов с исключением чувствительных данных
|
|
1562
211
|
```typescript
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
OPEN = "OPEN", // Разомкнут (ошибки превысили порог)
|
|
1566
|
-
HALF_OPEN = "HALF_OPEN", // Полуоткрыт (тестирование восстановления)
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
interface CircuitBreakerOptions {
|
|
1570
|
-
failureThreshold?: number; // Порог ошибок для открытия
|
|
1571
|
-
successThreshold?: number; // Порог успешных запросов для закрытия
|
|
1572
|
-
timeout?: number; // Время ожидания в открытом состоянии (мс)
|
|
1573
|
-
resetTimeout?: number; // Время до перехода в HALF_OPEN (мс)
|
|
1574
|
-
}
|
|
212
|
+
@UseInterceptors(SerializeInterceptor)
|
|
213
|
+
@Serialize(UserResponseDto)
|
|
1575
214
|
```
|
|
1576
215
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
Тип для настройки CORS.
|
|
1580
|
-
|
|
1581
|
-
**Расположение:** `src/types/cors-types.ts`
|
|
1582
|
-
|
|
216
|
+
**CompressionInterceptor** - автоматическое сжатие больших ответов
|
|
1583
217
|
```typescript
|
|
1584
|
-
|
|
1585
|
-
origin?: string | string[] | boolean | RegExp | ((origin: string) => boolean);
|
|
1586
|
-
methods?: string | string[];
|
|
1587
|
-
allowedHeaders?: string | string[];
|
|
1588
|
-
exposedHeaders?: string | string[];
|
|
1589
|
-
credentials?: boolean;
|
|
1590
|
-
maxAge?: number;
|
|
1591
|
-
preflightContinue?: boolean;
|
|
1592
|
-
optionsSuccessStatus?: number;
|
|
1593
|
-
}
|
|
218
|
+
@UseInterceptors(new CompressionInterceptor(2048, 0.7))
|
|
1594
219
|
```
|
|
1595
220
|
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
Тип для настройки compression.
|
|
1599
|
-
|
|
1600
|
-
**Расположение:** `src/types/compression-types.ts`
|
|
1601
|
-
|
|
221
|
+
**RequestIdResponseInterceptor** - добавление Request ID в заголовки ответов
|
|
1602
222
|
```typescript
|
|
1603
|
-
|
|
1604
|
-
filter?: (req: Request, res: Response) => boolean;
|
|
1605
|
-
level?: number;
|
|
1606
|
-
threshold?: number;
|
|
1607
|
-
chunkSize?: number;
|
|
1608
|
-
windowBits?: number;
|
|
1609
|
-
memLevel?: number;
|
|
1610
|
-
strategy?: number;
|
|
1611
|
-
dictionary?: Buffer | Buffer[] | string;
|
|
1612
|
-
}
|
|
223
|
+
@UseInterceptors(RequestIdResponseInterceptor)
|
|
1613
224
|
```
|
|
1614
225
|
|
|
1615
|
-
###
|
|
1616
|
-
|
|
1617
|
-
Тип для настройки версионирования API.
|
|
1618
|
-
|
|
1619
|
-
**Расположение:** `src/types/versioning-types.ts`
|
|
226
|
+
### Пайпы валидации
|
|
1620
227
|
|
|
228
|
+
**HttpValidationPipe** - валидация DTO для HTTP контроллеров
|
|
1621
229
|
```typescript
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
interface VersioningOptionsConfig {
|
|
1625
|
-
type: VersioningStrategy;
|
|
1626
|
-
defaultVersion?: string;
|
|
1627
|
-
header?: string;
|
|
1628
|
-
key?: string;
|
|
1629
|
-
}
|
|
230
|
+
@UsePipes(new HttpValidationPipe(CreateDto))
|
|
1630
231
|
```
|
|
1631
232
|
|
|
1632
|
-
|
|
233
|
+
**RpcValidationPipe** - валидация DTO для RabbitMQ (автоматически извлекает correlationId и correlationTimestamp)
|
|
234
|
+
```typescript
|
|
235
|
+
@UsePipes(new RpcValidationPipe(TestRequestDto))
|
|
236
|
+
```
|
|
1633
237
|
|
|
1634
|
-
|
|
238
|
+
**JsonRpcValidationPipe** - валидация JSON-RPC 2.0 запросов
|
|
239
|
+
```typescript
|
|
240
|
+
@Body(JsonRpcValidationPipe) request: JsonRpcRequest
|
|
241
|
+
```
|
|
1635
242
|
|
|
1636
|
-
|
|
243
|
+
**FileValidationPipe** - валидация загруженных файлов
|
|
244
|
+
```typescript
|
|
245
|
+
@UploadedFile(new FileValidationPipe({ maxSize: 5 * 1024 * 1024 }))
|
|
246
|
+
```
|
|
1637
247
|
|
|
248
|
+
**QueryValidationPipe** - валидация query параметров
|
|
1638
249
|
```typescript
|
|
1639
|
-
|
|
250
|
+
@Query(new QueryValidationPipe(PaginationDto)) query: PaginationDto
|
|
251
|
+
```
|
|
1640
252
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
pathType: ServicePathType;
|
|
1645
|
-
relativePath: string;
|
|
1646
|
-
}
|
|
253
|
+
**HeaderValidationPipe** - валидация заголовков
|
|
254
|
+
```typescript
|
|
255
|
+
@Headers(new HeaderValidationPipe(ApiHeadersDto)) headers: ApiHeadersDto
|
|
1647
256
|
```
|
|
1648
257
|
|
|
1649
|
-
###
|
|
258
|
+
### Guards
|
|
1650
259
|
|
|
1651
|
-
|
|
260
|
+
**JwtAuthGuard** - проверка JWT токена
|
|
261
|
+
```typescript
|
|
262
|
+
@UseGuards(JwtAuthGuard)
|
|
263
|
+
```
|
|
1652
264
|
|
|
1653
|
-
|
|
265
|
+
**ApiKeyGuard** - проверка API ключа (работает с декоратором `@ApiKey()`)
|
|
266
|
+
```typescript
|
|
267
|
+
@UseGuards(new ApiKeyGuard(undefined, "x-api-key", new Set(["key1", "key2"])))
|
|
268
|
+
@ApiKey()
|
|
269
|
+
```
|
|
1654
270
|
|
|
271
|
+
**RolesGuard** - проверка ролей пользователя (работает с декоратором `@Roles()`)
|
|
1655
272
|
```typescript
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
originalname: string;
|
|
1659
|
-
encoding: string;
|
|
1660
|
-
mimetype: string;
|
|
1661
|
-
size: number;
|
|
1662
|
-
buffer: Buffer;
|
|
1663
|
-
destination: string;
|
|
1664
|
-
filename: string;
|
|
1665
|
-
path: string;
|
|
1666
|
-
stream: NodeJS.ReadableStream;
|
|
1667
|
-
}
|
|
273
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
274
|
+
@Roles("admin", "moderator")
|
|
1668
275
|
```
|
|
1669
276
|
|
|
1670
|
-
|
|
277
|
+
**PermissionsGuard** - проверка разрешений пользователя (работает с декоратором `@Permissions()`)
|
|
278
|
+
```typescript
|
|
279
|
+
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
|
280
|
+
@Permissions("users:delete", "users:write")
|
|
281
|
+
```
|
|
1671
282
|
|
|
1672
|
-
|
|
283
|
+
**RateLimitGuard** - ограничение частоты запросов
|
|
284
|
+
```typescript
|
|
285
|
+
@UseGuards(new RateLimitGuard(undefined, 200, 60000)) // 200 запросов в минуту
|
|
286
|
+
```
|
|
1673
287
|
|
|
1674
|
-
|
|
288
|
+
**WebSocketAuthGuard** - проверка аутентификации WebSocket
|
|
289
|
+
```typescript
|
|
290
|
+
@UseGuards(new WebSocketAuthGuard(async (token, context) => {
|
|
291
|
+
return await this.authService.validateToken(token);
|
|
292
|
+
}))
|
|
293
|
+
```
|
|
1675
294
|
|
|
1676
|
-
|
|
295
|
+
### Декораторы
|
|
1677
296
|
|
|
1678
|
-
|
|
297
|
+
- `@Public()` - пометка публичного эндпоинта
|
|
298
|
+
- `@Roles(...)` - указание требуемых ролей
|
|
299
|
+
- `@Permissions(...)` - указание требуемых разрешений
|
|
300
|
+
- `@ApiKey()` - пометка эндпоинта как требующего API ключ
|
|
301
|
+
- `@Serialize(DtoClass)` - указание DTO для сериализации ответа
|
|
1679
302
|
|
|
1680
|
-
|
|
303
|
+
### Утилиты
|
|
1681
304
|
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
305
|
+
**validateEnv** - валидация переменных окружения с Joi
|
|
306
|
+
```typescript
|
|
307
|
+
const validatedEnv = validateEnv(env, ["DATABASE_URL", "REDIS_URL"]);
|
|
308
|
+
```
|
|
1686
309
|
|
|
1687
|
-
|
|
310
|
+
**getUserFromContext** - извлечение пользователя из контекста
|
|
311
|
+
```typescript
|
|
312
|
+
const user = getUserFromContext(context);
|
|
313
|
+
```
|
|
1688
314
|
|
|
315
|
+
**getIpFromContext** - извлечение IP адреса из контекста
|
|
1689
316
|
```typescript
|
|
1690
|
-
|
|
317
|
+
const ip = getIpFromContext(context);
|
|
318
|
+
```
|
|
1691
319
|
|
|
1692
|
-
|
|
320
|
+
**getRequestIdFromContext** - извлечение Request ID из контекста
|
|
321
|
+
```typescript
|
|
322
|
+
const requestId = getRequestIdFromContext(context);
|
|
1693
323
|
```
|
|
1694
324
|
|
|
1695
|
-
|
|
325
|
+
**createCorsOptions** - создание опций CORS
|
|
326
|
+
```typescript
|
|
327
|
+
app.enableCors(createCorsOptions({ origin: ["https://example.com"] }));
|
|
328
|
+
```
|
|
1696
329
|
|
|
1697
|
-
|
|
330
|
+
**createCompressionOptions** - создание опций compression
|
|
331
|
+
```typescript
|
|
332
|
+
app.use(compression(createCompressionOptions({ level: 9 })));
|
|
333
|
+
```
|
|
1698
334
|
|
|
1699
|
-
|
|
335
|
+
**createVersioningOptions** - создание опций версионирования
|
|
336
|
+
```typescript
|
|
337
|
+
app.enableVersioning(createVersioningOptions({ type: "uri", defaultVersion: "1" }));
|
|
338
|
+
```
|
|
1700
339
|
|
|
340
|
+
**validateFile** - валидация файла
|
|
1701
341
|
```typescript
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
import { LoggerService } from "@makebelieve21213-packages/logger";
|
|
342
|
+
const result = validateFile(file, { maxSize: 5 * 1024 * 1024 });
|
|
343
|
+
```
|
|
1705
344
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
345
|
+
**CircuitBreakerService** - реализация паттерна Circuit Breaker
|
|
346
|
+
```typescript
|
|
347
|
+
const circuitBreaker = new CircuitBreakerService(logger, {
|
|
348
|
+
failureThreshold: 5,
|
|
349
|
+
successThreshold: 2,
|
|
350
|
+
resetTimeout: 60000,
|
|
351
|
+
});
|
|
352
|
+
```
|
|
1714
353
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
return await this.analyticsService.getGlobalData();
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
354
|
+
**getServicePath** - определение пути в сервисе
|
|
355
|
+
```typescript
|
|
356
|
+
const path = getServicePath({ serviceName: "test-service", dirname: __dirname, pathType: "locales", relativePath: "locales" });
|
|
1721
357
|
```
|
|
1722
358
|
|
|
1723
|
-
###
|
|
359
|
+
### BaseController
|
|
1724
360
|
|
|
1725
|
-
|
|
1726
|
-
import { Controller } from "@nestjs/common";
|
|
1727
|
-
import { MessagePattern, Payload, Ctx } from "@nestjs/microservices";
|
|
1728
|
-
import { BaseController } from "@packages/nest-common";
|
|
1729
|
-
import { LoggerService } from "@makebelieve21213-packages/logger";
|
|
361
|
+
Базовый контроллер для всех контроллеров проекта. Предоставляет метод `acknowledge(ctx: RmqContext)` для подтверждения RPC сообщений.
|
|
1730
362
|
|
|
1731
|
-
|
|
1732
|
-
export default class
|
|
363
|
+
```typescript
|
|
364
|
+
export default class TestController extends BaseController {
|
|
1733
365
|
constructor(
|
|
1734
|
-
private readonly
|
|
366
|
+
private readonly testService: TestService,
|
|
1735
367
|
logger: LoggerService,
|
|
1736
368
|
) {
|
|
1737
369
|
super(logger);
|
|
1738
370
|
}
|
|
1739
|
-
|
|
1740
|
-
@MessagePattern(ROUTING_KEYS.ANALYTICS_GLOBAL)
|
|
1741
|
-
async getGlobalData(
|
|
1742
|
-
@Payload() _: GlobalDataIncomeDto,
|
|
1743
|
-
@Ctx() ctx: RmqContext,
|
|
1744
|
-
): Promise<GlobalDataOutcomeDto> {
|
|
1745
|
-
// БЕЗ try-catch - глобальный фильтр все обработает
|
|
1746
|
-
const data = await this.analyticsService.getGlobalData();
|
|
1747
|
-
this.acknowledge(ctx);
|
|
1748
|
-
return data;
|
|
1749
|
-
}
|
|
1750
371
|
}
|
|
1751
372
|
```
|
|
1752
373
|
|
|
1753
|
-
### WebSocket Gateway (api-service)
|
|
1754
|
-
|
|
1755
|
-
```typescript
|
|
1756
|
-
import { WebSocketGateway, WebSocketServer } from "@nestjs/websockets";
|
|
1757
|
-
import { SocketError } from "@packages/nest-common";
|
|
1758
|
-
import { SOCKET_EVENTS } from "@packages/types";
|
|
1759
|
-
|
|
1760
|
-
@WebSocketGateway()
|
|
1761
|
-
export default class SocketGateway extends BaseGateway {
|
|
1762
|
-
@WebSocketServer()
|
|
1763
|
-
io!: Server;
|
|
1764
|
-
|
|
1765
|
-
async publish(userId: string, event: SOCKET_EVENTS, payload: unknown) {
|
|
1766
|
-
try {
|
|
1767
|
-
await this.io.timeout(5000).to(`user:${userId}`).emitWithAck(event, payload);
|
|
1768
|
-
} catch (error) {
|
|
1769
|
-
const socketError = SocketError.fromUnknown(error);
|
|
1770
|
-
|
|
1771
|
-
// Логирование
|
|
1772
|
-
this.logger.error(`Ошибка отправки события: ${socketError.message}`);
|
|
1773
|
-
|
|
1774
|
-
// Сохранение в Redis
|
|
1775
|
-
await this.redisService.hSet(REDIS_H_KEYS.SOCKET_ERROR, userId, socketError.message);
|
|
1776
|
-
|
|
1777
|
-
// Опционально: отправка на фронт
|
|
1778
|
-
await this.io.to(`user:${userId}`).emit(SOCKET_EVENTS.ERROR, {
|
|
1779
|
-
status: "error",
|
|
1780
|
-
message: socketError.message,
|
|
1781
|
-
});
|
|
1782
|
-
|
|
1783
|
-
throw socketError;
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
```
|
|
1788
|
-
|
|
1789
|
-
---
|
|
1790
|
-
|
|
1791
374
|
## ✅ Best Practices
|
|
1792
375
|
|
|
1793
376
|
### 1. Не используйте catch блоки в контроллерах
|
|
@@ -1826,25 +409,7 @@ async getData() {
|
|
|
1826
409
|
}
|
|
1827
410
|
```
|
|
1828
411
|
|
|
1829
|
-
### 3.
|
|
1830
|
-
|
|
1831
|
-
```typescript
|
|
1832
|
-
// ✅ ХОРОШО - отправка ошибки на фронт
|
|
1833
|
-
try {
|
|
1834
|
-
await this.processData();
|
|
1835
|
-
} catch (error) {
|
|
1836
|
-
await this.socketGateway.publish(
|
|
1837
|
-
userId,
|
|
1838
|
-
SOCKET_EVENTS.ERROR,
|
|
1839
|
-
{
|
|
1840
|
-
status: "error",
|
|
1841
|
-
message: error.message,
|
|
1842
|
-
},
|
|
1843
|
-
);
|
|
1844
|
-
}
|
|
1845
|
-
```
|
|
1846
|
-
|
|
1847
|
-
### 4. Не преобразуйте ошибки вручную в контроллерах
|
|
412
|
+
### 3. Не преобразуйте ошибки вручную в контроллерах
|
|
1848
413
|
|
|
1849
414
|
```typescript
|
|
1850
415
|
// ❌ ПЛОХО
|
|
@@ -1865,7 +430,7 @@ async getGlobal() {
|
|
|
1865
430
|
}
|
|
1866
431
|
```
|
|
1867
432
|
|
|
1868
|
-
###
|
|
433
|
+
### 4. Используйте правильный тип ошибки для контекста
|
|
1869
434
|
|
|
1870
435
|
```typescript
|
|
1871
436
|
// HTTP контекст → HttpError (автоматически через UnifiedExceptionFilter)
|
|
@@ -1873,65 +438,38 @@ async getGlobal() {
|
|
|
1873
438
|
// Socket контекст → SocketError (вручную в SocketGateway.publish())
|
|
1874
439
|
```
|
|
1875
440
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
## 🔍 Обработка ошибок WebSocket
|
|
1879
|
-
|
|
1880
|
-
### Типы ошибок WebSocket
|
|
1881
|
-
|
|
1882
|
-
1. **Ошибки отправки событий** - обрабатываются через `SocketError` в `SocketGateway.publish()`
|
|
1883
|
-
2. **Ошибки подключения** - обрабатываются автоматически Socket.io (событие `disconnect`)
|
|
1884
|
-
3. **Внутренние ошибки сервера** - логируются, опционально отправляются на фронт через событие `ERROR`
|
|
1885
|
-
|
|
1886
|
-
### Отправка ошибок на фронт
|
|
441
|
+
### 5. Используйте валидацию через пайпы
|
|
1887
442
|
|
|
1888
443
|
```typescript
|
|
1889
|
-
//
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
message: "Произошла ошибка при обработке данных",
|
|
1896
|
-
},
|
|
1897
|
-
);
|
|
444
|
+
// ✅ ХОРОШО
|
|
445
|
+
@Post("create")
|
|
446
|
+
@UsePipes(new HttpValidationPipe(CreateDto))
|
|
447
|
+
async create(@Body() dto: CreateDto) {
|
|
448
|
+
return await this.service.create(dto);
|
|
449
|
+
}
|
|
1898
450
|
```
|
|
1899
451
|
|
|
1900
|
-
###
|
|
452
|
+
### 6. Используйте guards с соответствующими декораторами
|
|
1901
453
|
|
|
1902
454
|
```typescript
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
// Подтверждаем получение
|
|
1911
|
-
ack({ status: "received", ts: Date.now() });
|
|
1912
|
-
});
|
|
455
|
+
// ✅ ХОРОШО
|
|
456
|
+
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
457
|
+
@Roles("admin", "moderator")
|
|
458
|
+
async getAdminData() {
|
|
459
|
+
return await this.testService.getData();
|
|
460
|
+
}
|
|
1913
461
|
```
|
|
1914
462
|
|
|
1915
|
-
### Внутренние ошибки Socket.io
|
|
1916
|
-
|
|
1917
|
-
Внутренние ошибки (подключение/отключение) обрабатываются автоматически:
|
|
1918
|
-
|
|
1919
|
-
- **Ошибка подключения** → Socket.io разрывает соединение, фронт получает событие `disconnect`
|
|
1920
|
-
- **Ошибка отключения** → Socket.io очищает соединение, фронт получает событие `disconnect`
|
|
1921
|
-
- **Таймаут соединения** → Socket.io автоматически переподключается, фронт получает событие `reconnect`
|
|
1922
|
-
|
|
1923
|
-
---
|
|
1924
|
-
|
|
1925
463
|
## 🧪 Тестирование
|
|
1926
464
|
|
|
1927
465
|
Пакет имеет высокое покрытие тестами (>95% для веток, 100% для statements и функций).
|
|
1928
466
|
|
|
1929
467
|
```bash
|
|
1930
468
|
# Запуск тестов
|
|
1931
|
-
|
|
469
|
+
npm test
|
|
1932
470
|
|
|
1933
471
|
# Запуск тестов с покрытием
|
|
1934
|
-
|
|
472
|
+
npm run test:coverage
|
|
1935
473
|
```
|
|
1936
474
|
|
|
1937
475
|
**Покрытие тестами:**
|
|
@@ -1940,52 +478,17 @@ pnpm test:coverage
|
|
|
1940
478
|
- Functions: 100%
|
|
1941
479
|
- Lines: 100%
|
|
1942
480
|
|
|
1943
|
-
**Тестовые сценарии:**
|
|
1944
|
-
- Все классы ошибок (HttpError, RpcError, SocketError, NestCommonError)
|
|
1945
|
-
- Глобальные фильтры (UnifiedExceptionFilter, HttpExceptionFilter, RpcExceptionFilter, WebSocketExceptionHandler)
|
|
1946
|
-
- Перехватчики (UnifiedInterceptor, HttpLoggingInterceptor, RpcLoggingInterceptor, WebSocketLoggingInterceptor, ResponseInterceptor, SerializeInterceptor, CompressionInterceptor, RequestIdResponseInterceptor)
|
|
1947
|
-
- Пайпы валидации (HttpValidationPipe, RpcValidationPipe, FileValidationPipe, QueryValidationPipe, HeaderValidationPipe)
|
|
1948
|
-
- Guards (JwtAuthGuard, ApiKeyGuard, RolesGuard, PermissionsGuard, RateLimitGuard, WebSocketAuthGuard)
|
|
1949
|
-
- Декораторы (Public, Roles, Permissions, ApiKey, Serialize)
|
|
1950
|
-
- Базовые классы (BaseController)
|
|
1951
|
-
- Утилиты (validateEnv, getUserFromContext, getIpFromContext, getUserAgentFromContext, getRequestIdFromContext, createCorsOptions, createCompressionOptions, createVersioningOptions, validateFile, getFileExtension, formatFileSize, CircuitBreakerService, getServicePath)
|
|
1952
|
-
- Преобразование ошибок между контекстами
|
|
1953
|
-
- Обработка граничных случаев и различных типов ошибок
|
|
1954
|
-
- Логирование HTTP, RPC и WebSocket запросов с метриками времени выполнения
|
|
1955
|
-
- Валидация файлов, query параметров и заголовков
|
|
1956
|
-
- Авторизация и аутентификация через guards
|
|
1957
|
-
- Rate limiting и circuit breaker паттерны
|
|
1958
|
-
|
|
1959
|
-
## 📚 Дополнительные ресурсы
|
|
1960
|
-
|
|
1961
|
-
- [NestJS Exception Filters](https://docs.nestjs.com/exception-filters)
|
|
1962
|
-
- [NestJS Microservices](https://docs.nestjs.com/microservices/basics)
|
|
1963
|
-
- [Socket.io Error Handling](https://socket.io/docs/v4/error-handling/)
|
|
1964
|
-
|
|
1965
|
-
## 📦 Установка
|
|
1966
|
-
|
|
1967
|
-
```bash
|
|
1968
|
-
pnpm add @makebelieve21213-packages/nest-common
|
|
1969
|
-
```
|
|
1970
|
-
|
|
1971
|
-
Или добавьте в `package.json` вашего микросервиса:
|
|
1972
|
-
```json
|
|
1973
|
-
{
|
|
1974
|
-
"dependencies": {
|
|
1975
|
-
"@makebelieve21213-packages/nest-common": "workspace:*"
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
```
|
|
1979
|
-
|
|
1980
481
|
## 🏗️ Разработка
|
|
1981
482
|
|
|
1982
483
|
### Технический стек
|
|
484
|
+
|
|
1983
485
|
- **TypeScript 5.7+** - строгая типизация
|
|
1984
486
|
- **ESM модули** - современный стандарт модулей JavaScript
|
|
1985
487
|
- **NestJS 11.x** - фреймворк для микросервисов
|
|
1986
488
|
- **Jest** - тестирование
|
|
1987
489
|
|
|
1988
490
|
### Процесс сборки
|
|
491
|
+
|
|
1989
492
|
Пакет использует многоэтапную сборку для корректной работы ESM:
|
|
1990
493
|
1. **TypeScript компиляция** (`tsc --build`) - компиляция TypeScript в JavaScript
|
|
1991
494
|
2. **Замена алиасов** (`tsc-alias`) - замена путей `src/*` на относительные
|
|
@@ -1993,24 +496,24 @@ pnpm add @makebelieve21213-packages/nest-common
|
|
|
1993
496
|
|
|
1994
497
|
```bash
|
|
1995
498
|
# Установка зависимостей
|
|
1996
|
-
|
|
499
|
+
npm install
|
|
1997
500
|
|
|
1998
501
|
# Сборка
|
|
1999
|
-
|
|
502
|
+
npm run build
|
|
2000
503
|
|
|
2001
504
|
# Запуск тестов
|
|
2002
|
-
|
|
505
|
+
npm test
|
|
2003
506
|
|
|
2004
507
|
# Запуск тестов с покрытием
|
|
2005
|
-
|
|
508
|
+
npm run test:coverage
|
|
2006
509
|
|
|
2007
510
|
# Линтер
|
|
2008
|
-
|
|
2009
|
-
|
|
511
|
+
npm run lint
|
|
512
|
+
npm run lint:fix
|
|
2010
513
|
|
|
2011
514
|
# Форматирование
|
|
2012
|
-
|
|
2013
|
-
|
|
515
|
+
npm run format
|
|
516
|
+
npm run format:fix
|
|
2014
517
|
```
|
|
2015
518
|
|
|
2016
519
|
### Git Hooks (Husky)
|
|
@@ -2041,7 +544,7 @@ docker run -d \
|
|
|
2041
544
|
## Совместимость
|
|
2042
545
|
|
|
2043
546
|
- **Node.js**: >=22.11.0
|
|
2044
|
-
- **
|
|
547
|
+
- **npm**: >=10.0.0
|
|
2045
548
|
- **@nestjs/common**: ^11.1.6
|
|
2046
549
|
- **@nestjs/microservices**: ^11.1.3
|
|
2047
550
|
- **rxjs**: ^7.8.2
|