nestjs-api-forge 1.0.3 → 1.0.4
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 +118 -61
- package/dist/decorators/api-response.decorator.d.ts +4 -0
- package/dist/decorators/api-response.decorator.js +7 -1
- package/dist/decorators/api-response.decorator.js.map +1 -1
- package/dist/dto/api-response.dto.d.ts +1 -0
- package/dist/dto/api-response.dto.js +3 -0
- package/dist/dto/api-response.dto.js.map +1 -1
- package/dist/filters/global-exception.filter.d.ts +1 -0
- package/dist/filters/global-exception.filter.js +23 -4
- package/dist/filters/global-exception.filter.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/response.interceptor.d.ts +1 -0
- package/dist/interceptors/response.interceptor.js +45 -5
- package/dist/interceptors/response.interceptor.js.map +1 -1
- package/dist/interfaces/api-response.interface.d.ts +6 -0
- package/dist/pipes/forge-validation.pipe.d.ts +4 -0
- package/dist/pipes/forge-validation.pipe.js +34 -0
- package/dist/pipes/forge-validation.pipe.js.map +1 -0
- package/dist/pipes/index.d.ts +1 -0
- package/dist/pipes/index.js +18 -0
- package/dist/pipes/index.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nestjs-api-forge
|
|
2
2
|
|
|
3
|
-
[](https://github.com/mirzasaikatahmmed/nestjs-api-forge/actions/workflows/publish.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/nestjs-api-forge)
|
|
5
5
|
[](https://www.npmjs.com/package/nestjs-api-forge)
|
|
6
6
|
[](https://github.com/mirzasaikatahmmed/nestjs-api-forge/blob/main/LICENSE)
|
|
@@ -18,10 +18,15 @@ Plug-and-play response envelope, exception filter, and error formatting for [Nes
|
|
|
18
18
|
- **Built-in typed exceptions** — drop-in replacements for NestJS built-ins with structured error codes
|
|
19
19
|
- **`@ForgeMessage`** — override per-route success message
|
|
20
20
|
- **`@ForgeRawResponse`** — opt a route out of envelope wrapping
|
|
21
|
+
- **`@ForgeMeta`** — merge custom fields into `meta` per route or controller
|
|
22
|
+
- **`@ForgeDeprecated`** — mark a route as deprecated (adds `meta.deprecated` + `Deprecation` header)
|
|
21
23
|
- **`@ApiForge`** — apply filter + interceptor to a single controller without going global
|
|
24
|
+
- **`ForgeValidationPipe`** — drop-in `ValidationPipe` that throws structured `ValidationException` with typed field details
|
|
22
25
|
- **`ApiForgeModule.forRoot()`** — one-line global registration
|
|
23
26
|
- **`ApiForgeModule.forRootAsync()`** — config-service-driven async options
|
|
24
|
-
- **
|
|
27
|
+
- **Correlation ID passthrough** — read `X-Request-ID` / `X-Correlation-ID` from requests, echo on responses
|
|
28
|
+
- **Response time** — optional `meta.responseTime` for every response
|
|
29
|
+
- **Request ID tracing** — optional auto-generated UUID `requestId` in every response `meta`
|
|
25
30
|
|
|
26
31
|
---
|
|
27
32
|
|
|
@@ -49,29 +54,23 @@ import { ApiForgeModule } from 'nestjs-api-forge';
|
|
|
49
54
|
includePath: true,
|
|
50
55
|
includeTimestamp: true,
|
|
51
56
|
includeRequestId: true,
|
|
57
|
+
includeResponseTime: true,
|
|
58
|
+
correlationIdHeader: 'x-request-id',
|
|
52
59
|
}),
|
|
53
60
|
],
|
|
54
61
|
})
|
|
55
62
|
export class AppModule {}
|
|
56
63
|
```
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
### 2. Add `ValidationPipe` in `main.ts`
|
|
65
|
+
### 2. Use `ForgeValidationPipe` in `main.ts`
|
|
61
66
|
|
|
62
67
|
```typescript
|
|
63
|
-
import {
|
|
68
|
+
import { ForgeValidationPipe } from 'nestjs-api-forge';
|
|
64
69
|
|
|
65
|
-
app.useGlobalPipes(
|
|
66
|
-
new ValidationPipe({
|
|
67
|
-
whitelist: true,
|
|
68
|
-
forbidNonWhitelisted: true,
|
|
69
|
-
transform: true,
|
|
70
|
-
}),
|
|
71
|
-
);
|
|
70
|
+
app.useGlobalPipes(new ForgeValidationPipe());
|
|
72
71
|
```
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
`ForgeValidationPipe` extends NestJS `ValidationPipe` and throws a structured `ValidationException` with typed field-level details — no string parsing required.
|
|
75
74
|
|
|
76
75
|
---
|
|
77
76
|
|
|
@@ -89,7 +88,8 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
|
|
|
89
88
|
"timestamp": "2026-05-15T10:00:00.000Z",
|
|
90
89
|
"path": "/api/users/1",
|
|
91
90
|
"version": "1.0.0",
|
|
92
|
-
"requestId": "a3f2c1d0-84e5-4b6a-9123-abc123def456"
|
|
91
|
+
"requestId": "a3f2c1d0-84e5-4b6a-9123-abc123def456",
|
|
92
|
+
"responseTime": "4ms"
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
```
|
|
@@ -101,19 +101,18 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
|
|
|
101
101
|
"success": false,
|
|
102
102
|
"statusCode": 404,
|
|
103
103
|
"message": "User not found",
|
|
104
|
-
"error": {
|
|
105
|
-
"code": "NOT_FOUND"
|
|
106
|
-
},
|
|
104
|
+
"error": { "code": "NOT_FOUND" },
|
|
107
105
|
"meta": {
|
|
108
106
|
"timestamp": "2026-05-15T10:00:00.000Z",
|
|
109
107
|
"path": "/api/users/99",
|
|
110
108
|
"version": "1.0.0",
|
|
111
|
-
"requestId": "b1e2f3a4-0000-4b5c-8d9e-fedcba987654"
|
|
109
|
+
"requestId": "b1e2f3a4-0000-4b5c-8d9e-fedcba987654",
|
|
110
|
+
"responseTime": "2ms"
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
```
|
|
115
114
|
|
|
116
|
-
### Validation Error
|
|
115
|
+
### Validation Error
|
|
117
116
|
|
|
118
117
|
```json
|
|
119
118
|
{
|
|
@@ -124,24 +123,21 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
|
|
|
124
123
|
"code": "VALIDATION_ERROR",
|
|
125
124
|
"details": [
|
|
126
125
|
{ "field": "email", "message": "must be an email" },
|
|
127
|
-
{ "field": "
|
|
126
|
+
{ "field": "address.zip", "message": "must be a string" }
|
|
128
127
|
]
|
|
129
128
|
},
|
|
130
|
-
"meta": {
|
|
131
|
-
"timestamp": "2026-05-15T10:00:00.000Z",
|
|
132
|
-
"path": "/api/users"
|
|
133
|
-
}
|
|
129
|
+
"meta": { "timestamp": "2026-05-15T10:00:00.000Z", "path": "/api/users" }
|
|
134
130
|
}
|
|
135
131
|
```
|
|
136
132
|
|
|
137
|
-
### Paginated
|
|
133
|
+
### Paginated
|
|
138
134
|
|
|
139
135
|
```json
|
|
140
136
|
{
|
|
141
137
|
"success": true,
|
|
142
138
|
"statusCode": 200,
|
|
143
139
|
"message": "Users fetched successfully",
|
|
144
|
-
"data": [
|
|
140
|
+
"data": [{ "id": 1, "name": "Alice Johnson" }],
|
|
145
141
|
"pagination": {
|
|
146
142
|
"total": 42,
|
|
147
143
|
"page": 2,
|
|
@@ -152,7 +148,24 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
|
|
|
152
148
|
},
|
|
153
149
|
"meta": {
|
|
154
150
|
"timestamp": "2026-05-15T10:00:00.000Z",
|
|
155
|
-
"path": "/api/users?page=2&limit=10"
|
|
151
|
+
"path": "/api/users?page=2&limit=10",
|
|
152
|
+
"responseTime": "8ms"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Deprecated route
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"success": true,
|
|
162
|
+
"statusCode": 200,
|
|
163
|
+
"message": "OK",
|
|
164
|
+
"data": { },
|
|
165
|
+
"meta": {
|
|
166
|
+
"timestamp": "2026-05-15T10:00:00.000Z",
|
|
167
|
+
"deprecated": true,
|
|
168
|
+
"deprecationNotice": "Use /v2/users instead"
|
|
156
169
|
}
|
|
157
170
|
}
|
|
158
171
|
```
|
|
@@ -167,7 +180,9 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
|
|
|
167
180
|
|---|---|---|---|
|
|
168
181
|
| `includePath` | `boolean` | `true` | Include request path in `meta` |
|
|
169
182
|
| `includeTimestamp` | `boolean` | `true` | Include ISO timestamp in `meta` |
|
|
170
|
-
| `includeRequestId` | `boolean` | `false` |
|
|
183
|
+
| `includeRequestId` | `boolean` | `false` | Auto-generate UUID `requestId` in `meta` if no correlation header found |
|
|
184
|
+
| `correlationIdHeader` | `string \| string[]` | `['x-request-id', 'x-correlation-id']` | Header(s) to read request ID from; echoed back on the response |
|
|
185
|
+
| `includeResponseTime` | `boolean` | `false` | Include handler duration as `meta.responseTime` (e.g. `"12ms"`) |
|
|
171
186
|
| `version` | `string` | `undefined` | API version string added to `meta` |
|
|
172
187
|
| `defaultSuccessMessage` | `string` | `'Request successful'` | Fallback success message |
|
|
173
188
|
|
|
@@ -179,8 +194,9 @@ ApiForgeModule.forRootAsync({
|
|
|
179
194
|
inject: [ConfigService],
|
|
180
195
|
useFactory: (config: ConfigService) => ({
|
|
181
196
|
version: config.get('API_VERSION'),
|
|
182
|
-
defaultSuccessMessage: config.get('DEFAULT_SUCCESS_MSG'),
|
|
183
197
|
includeRequestId: true,
|
|
198
|
+
includeResponseTime: true,
|
|
199
|
+
correlationIdHeader: config.get('CORRELATION_HEADER'),
|
|
184
200
|
}),
|
|
185
201
|
})
|
|
186
202
|
```
|
|
@@ -192,31 +208,40 @@ ApiForgeModule.forRootAsync({
|
|
|
192
208
|
| `@ApiForge(options?)` | Controller / Method | Apply filter + interceptor without going global |
|
|
193
209
|
| `@ForgeMessage(msg)` | Controller / Method | Override the success message for that route |
|
|
194
210
|
| `@ForgeRawResponse()` | Controller / Method | Skip envelope wrapping; return raw handler value |
|
|
211
|
+
| `@ForgeMeta(extra)` | Controller / Method | Merge extra key-value pairs into `meta` |
|
|
212
|
+
| `@ForgeDeprecated(notice?)` | Controller / Method | Mark route deprecated — adds `meta.deprecated`, optional `meta.deprecationNotice`, and `Deprecation: true` header |
|
|
213
|
+
|
|
214
|
+
### `ForgeValidationPipe`
|
|
215
|
+
|
|
216
|
+
Drop-in replacement for NestJS `ValidationPipe`. Throws `ValidationException` with structured `details` instead of a string-message array. Supports nested objects (dot-notation fields).
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// main.ts
|
|
220
|
+
app.useGlobalPipes(new ForgeValidationPipe());
|
|
221
|
+
|
|
222
|
+
// Override defaults
|
|
223
|
+
app.useGlobalPipes(new ForgeValidationPipe({
|
|
224
|
+
whitelist: false,
|
|
225
|
+
forbidNonWhitelisted: false,
|
|
226
|
+
}));
|
|
227
|
+
```
|
|
195
228
|
|
|
196
229
|
### `ApiResponseDto` — manual usage
|
|
197
230
|
|
|
198
231
|
```typescript
|
|
199
232
|
import { ApiResponseDto } from 'nestjs-api-forge';
|
|
200
233
|
|
|
201
|
-
|
|
202
|
-
ApiResponseDto.success(data, 'User fetched', 200, { path: '/users/1' });
|
|
203
|
-
|
|
204
|
-
// Created (201)
|
|
234
|
+
ApiResponseDto.success(data, 'OK', 200, { path: '/users/1' });
|
|
205
235
|
ApiResponseDto.created(data, 'User created');
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
ApiResponseDto.noContent('Deleted');
|
|
209
|
-
|
|
210
|
-
// Paginated
|
|
236
|
+
ApiResponseDto.accepted(jobRef, 'Export queued'); // 202
|
|
237
|
+
ApiResponseDto.noContent('Deleted'); // 204
|
|
211
238
|
ApiResponseDto.paginated(data, total, page, limit, 'Users fetched');
|
|
212
|
-
|
|
213
|
-
// Error
|
|
214
239
|
ApiResponseDto.error('Not found', 404, { code: 'NOT_FOUND' });
|
|
215
240
|
```
|
|
216
241
|
|
|
217
242
|
### Built-in Exceptions
|
|
218
243
|
|
|
219
|
-
All
|
|
244
|
+
All extend `ApiException` → `HttpException`.
|
|
220
245
|
|
|
221
246
|
| Class | Status | Code |
|
|
222
247
|
|---|---|---|
|
|
@@ -242,12 +267,10 @@ All exceptions extend `ApiException` → `HttpException` and produce a structure
|
|
|
242
267
|
|
|
243
268
|
```typescript
|
|
244
269
|
import { Controller, Get, Post, Delete, Param, Body, HttpCode, HttpStatus } from '@nestjs/common';
|
|
245
|
-
import { ForgeMessage, ForgeRawResponse, NotFoundException } from 'nestjs-api-forge';
|
|
270
|
+
import { ForgeMessage, ForgeRawResponse, ForgeDeprecated, NotFoundException } from 'nestjs-api-forge';
|
|
246
271
|
|
|
247
272
|
@Controller('users')
|
|
248
273
|
export class UsersController {
|
|
249
|
-
constructor(private readonly usersService: UsersService) {}
|
|
250
|
-
|
|
251
274
|
@Get(':id')
|
|
252
275
|
@ForgeMessage('User fetched successfully')
|
|
253
276
|
findOne(@Param('id') id: number) {
|
|
@@ -264,11 +287,31 @@ export class UsersController {
|
|
|
264
287
|
}
|
|
265
288
|
```
|
|
266
289
|
|
|
267
|
-
###
|
|
290
|
+
### Deprecated route
|
|
268
291
|
|
|
269
292
|
```typescript
|
|
270
|
-
|
|
293
|
+
@Get('export')
|
|
294
|
+
@ForgeDeprecated('Use POST /v2/users/export instead')
|
|
295
|
+
@ForgeMessage('Export started')
|
|
296
|
+
startExport() {
|
|
297
|
+
const job = this.usersService.queueExport();
|
|
298
|
+
return ApiResponseDto.accepted({ jobId: job.id }, 'Export queued');
|
|
299
|
+
}
|
|
300
|
+
```
|
|
271
301
|
|
|
302
|
+
### Custom meta per route
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
@Get()
|
|
306
|
+
@ForgeMeta({ region: 'us-east-1', cache: 'miss' })
|
|
307
|
+
findAll() {
|
|
308
|
+
return this.usersService.findAll();
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Paginated list (raw response)
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
272
315
|
@Get()
|
|
273
316
|
@ForgeRawResponse()
|
|
274
317
|
findAll(@Query('page') page = '1', @Query('limit') limit = '10') {
|
|
@@ -282,17 +325,9 @@ findAll(@Query('page') page = '1', @Query('limit') limit = '10') {
|
|
|
282
325
|
### Per-controller scope (no global module)
|
|
283
326
|
|
|
284
327
|
```typescript
|
|
285
|
-
import { ApiForge, ForgeMessage } from 'nestjs-api-forge';
|
|
286
|
-
|
|
287
328
|
@Controller('products')
|
|
288
|
-
@ApiForge({ version: '2.0' })
|
|
289
|
-
export class ProductsController {
|
|
290
|
-
@Get()
|
|
291
|
-
@ForgeMessage('Products fetched successfully')
|
|
292
|
-
findAll() {
|
|
293
|
-
return this.productsService.findAll();
|
|
294
|
-
}
|
|
295
|
-
}
|
|
329
|
+
@ApiForge({ version: '2.0', includeResponseTime: true })
|
|
330
|
+
export class ProductsController { ... }
|
|
296
331
|
```
|
|
297
332
|
|
|
298
333
|
### Raw response (health check)
|
|
@@ -305,17 +340,25 @@ health() {
|
|
|
305
340
|
}
|
|
306
341
|
```
|
|
307
342
|
|
|
308
|
-
###
|
|
343
|
+
### Correlation ID tracing
|
|
344
|
+
|
|
345
|
+
Send `X-Request-ID: abc-123` in the request — the same ID appears in `meta.requestId` and is echoed in the `X-Request-ID` response header. Useful for distributed tracing across microservices.
|
|
309
346
|
|
|
310
347
|
```typescript
|
|
311
|
-
|
|
348
|
+
ApiForgeModule.forRoot({
|
|
349
|
+
correlationIdHeader: 'x-request-id', // or an array of headers
|
|
350
|
+
includeRequestId: true, // generate UUID if header is absent
|
|
351
|
+
})
|
|
352
|
+
```
|
|
312
353
|
|
|
313
|
-
|
|
354
|
+
### Custom exception with field details
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
314
357
|
throw new BadRequestException('Invalid input', [
|
|
315
358
|
{ field: 'price', message: 'Must be a positive number', value: -5 },
|
|
316
359
|
]);
|
|
317
360
|
|
|
318
|
-
// From class-validator
|
|
361
|
+
// From class-validator constraint map
|
|
319
362
|
throw ValidationException.fromConstraints({
|
|
320
363
|
email: { isEmail: 'must be an email' },
|
|
321
364
|
age: { min: 'must be at least 1' },
|
|
@@ -335,6 +378,20 @@ rxjs ^7
|
|
|
335
378
|
|
|
336
379
|
---
|
|
337
380
|
|
|
381
|
+
## Changelog
|
|
382
|
+
|
|
383
|
+
### 1.1.0
|
|
384
|
+
- `ForgeValidationPipe` — structured validation errors with nested field support
|
|
385
|
+
- `@ForgeMeta(extra)` decorator — merge custom fields into response meta
|
|
386
|
+
- `@ForgeDeprecated(notice?)` decorator — deprecation flag in meta + response header
|
|
387
|
+
- `ApiResponseDto.accepted()` — 202 helper
|
|
388
|
+
- Correlation ID passthrough (`correlationIdHeader` option)
|
|
389
|
+
- Response time measurement (`includeResponseTime` option)
|
|
390
|
+
- `forRootAsync` fix — options factory now runs once instead of twice
|
|
391
|
+
- 3 new exceptions: `MethodNotAllowedException`, `PaymentRequiredException`, `GatewayTimeoutException`
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
338
395
|
## License
|
|
339
396
|
|
|
340
397
|
MIT
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { ForgeOptions } from '../interfaces/api-response.interface';
|
|
2
2
|
export declare const FORGE_MESSAGE_KEY = "forge:response_message";
|
|
3
3
|
export declare const FORGE_RAW_RESPONSE_KEY = "forge:raw_response";
|
|
4
|
+
export declare const FORGE_META_KEY = "forge:extra_meta";
|
|
5
|
+
export declare const FORGE_DEPRECATED_KEY = "forge:deprecated";
|
|
4
6
|
export declare const ForgeMessage: (message: string) => import("@nestjs/common").CustomDecorator<string>;
|
|
5
7
|
export declare const ForgeRawResponse: () => import("@nestjs/common").CustomDecorator<string>;
|
|
8
|
+
export declare const ForgeMeta: (extra: Record<string, unknown>) => import("@nestjs/common").CustomDecorator<string>;
|
|
9
|
+
export declare const ForgeDeprecated: (notice?: string) => import("@nestjs/common").CustomDecorator<string>;
|
|
6
10
|
export declare function ApiForge(options?: ForgeOptions): <TFunction extends Function, Y>(target: TFunction | object, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ForgeRawResponse = exports.ForgeMessage = exports.FORGE_RAW_RESPONSE_KEY = exports.FORGE_MESSAGE_KEY = void 0;
|
|
3
|
+
exports.ForgeDeprecated = exports.ForgeMeta = exports.ForgeRawResponse = exports.ForgeMessage = exports.FORGE_DEPRECATED_KEY = exports.FORGE_META_KEY = exports.FORGE_RAW_RESPONSE_KEY = exports.FORGE_MESSAGE_KEY = void 0;
|
|
4
4
|
exports.ApiForge = ApiForge;
|
|
5
5
|
const common_1 = require("@nestjs/common");
|
|
6
6
|
const core_1 = require("@nestjs/core");
|
|
@@ -8,10 +8,16 @@ const response_interceptor_1 = require("../interceptors/response.interceptor");
|
|
|
8
8
|
const global_exception_filter_1 = require("../filters/global-exception.filter");
|
|
9
9
|
exports.FORGE_MESSAGE_KEY = 'forge:response_message';
|
|
10
10
|
exports.FORGE_RAW_RESPONSE_KEY = 'forge:raw_response';
|
|
11
|
+
exports.FORGE_META_KEY = 'forge:extra_meta';
|
|
12
|
+
exports.FORGE_DEPRECATED_KEY = 'forge:deprecated';
|
|
11
13
|
const ForgeMessage = (message) => (0, common_1.SetMetadata)(exports.FORGE_MESSAGE_KEY, message);
|
|
12
14
|
exports.ForgeMessage = ForgeMessage;
|
|
13
15
|
const ForgeRawResponse = () => (0, common_1.SetMetadata)(exports.FORGE_RAW_RESPONSE_KEY, true);
|
|
14
16
|
exports.ForgeRawResponse = ForgeRawResponse;
|
|
17
|
+
const ForgeMeta = (extra) => (0, common_1.SetMetadata)(exports.FORGE_META_KEY, extra);
|
|
18
|
+
exports.ForgeMeta = ForgeMeta;
|
|
19
|
+
const ForgeDeprecated = (notice) => (0, common_1.SetMetadata)(exports.FORGE_DEPRECATED_KEY, notice ?? true);
|
|
20
|
+
exports.ForgeDeprecated = ForgeDeprecated;
|
|
15
21
|
function ApiForge(options = {}) {
|
|
16
22
|
const reflector = new core_1.Reflector();
|
|
17
23
|
return (0, common_1.applyDecorators)((0, common_1.UseInterceptors)(new response_interceptor_1.ForgeResponseInterceptor(reflector, options)), (0, common_1.UseFilters)(new global_exception_filter_1.ForgeExceptionFilter(options)));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-response.decorator.js","sourceRoot":"","sources":["../../src/decorators/api-response.decorator.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"api-response.decorator.js","sourceRoot":"","sources":["../../src/decorators/api-response.decorator.ts"],"names":[],"mappings":";;;AA0CA,4BAMC;AAhDD,2CAA2F;AAC3F,uCAAyC;AACzC,+EAAgF;AAChF,gFAA0E;AAG7D,QAAA,iBAAiB,GAAG,wBAAwB,CAAC;AAC7C,QAAA,sBAAsB,GAAG,oBAAoB,CAAC;AAC9C,QAAA,cAAc,GAAG,kBAAkB,CAAC;AACpC,QAAA,oBAAoB,GAAG,kBAAkB,CAAC;AAGhD,MAAM,YAAY,GAAG,CAAC,OAAe,EAAE,EAAE,CAC9C,IAAA,oBAAW,EAAC,yBAAiB,EAAE,OAAO,CAAC,CAAC;AAD7B,QAAA,YAAY,gBACiB;AAGnC,MAAM,gBAAgB,GAAG,GAAG,EAAE,CAAC,IAAA,oBAAW,EAAC,8BAAsB,EAAE,IAAI,CAAC,CAAC;AAAnE,QAAA,gBAAgB,oBAAmD;AAQzE,MAAM,SAAS,GAAG,CAAC,KAA8B,EAAE,EAAE,CAC1D,IAAA,oBAAW,EAAC,sBAAc,EAAE,KAAK,CAAC,CAAC;AADxB,QAAA,SAAS,aACe;AAU9B,MAAM,eAAe,GAAG,CAAC,MAAe,EAAE,EAAE,CACjD,IAAA,oBAAW,EAAC,4BAAoB,EAAE,MAAM,IAAI,IAAI,CAAC,CAAC;AADvC,QAAA,eAAe,mBACwB;AAMpD,SAAgB,QAAQ,CAAC,UAAwB,EAAE;IACjD,MAAM,SAAS,GAAG,IAAI,gBAAS,EAAE,CAAC;IAClC,OAAO,IAAA,wBAAe,EACpB,IAAA,wBAAe,EAAC,IAAI,+CAAwB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,EACjE,IAAA,mBAAU,EAAC,IAAI,8CAAoB,CAAC,OAAO,CAAC,CAAC,CAC9C,CAAC;AACJ,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { ApiErrorPayload, ApiMeta, ApiSuccessResponse, ApiErrorResponse, ApiPagi
|
|
|
2
2
|
export declare class ApiResponseDto {
|
|
3
3
|
static success<T>(data: T, message?: string, statusCode?: number, meta?: Partial<ApiMeta>): ApiSuccessResponse<T>;
|
|
4
4
|
static created<T>(data: T, message?: string, meta?: Partial<ApiMeta>): ApiSuccessResponse<T>;
|
|
5
|
+
static accepted<T>(data: T, message?: string, meta?: Partial<ApiMeta>): ApiSuccessResponse<T>;
|
|
5
6
|
static noContent(message?: string, meta?: Partial<ApiMeta>): ApiSuccessResponse<null>;
|
|
6
7
|
static paginated<T>(data: T[], total: number, page: number, limit: number, message?: string, meta?: Partial<ApiMeta>): ApiPaginatedResponse<T>;
|
|
7
8
|
static error(message: string, statusCode: number, error: ApiErrorPayload, meta?: Partial<ApiMeta>): ApiErrorResponse;
|
|
@@ -17,6 +17,9 @@ class ApiResponseDto {
|
|
|
17
17
|
static created(data, message = 'Resource created successfully', meta = {}) {
|
|
18
18
|
return ApiResponseDto.success(data, message, 201, meta);
|
|
19
19
|
}
|
|
20
|
+
static accepted(data, message = 'Request accepted for processing', meta = {}) {
|
|
21
|
+
return ApiResponseDto.success(data, message, 202, meta);
|
|
22
|
+
}
|
|
20
23
|
static noContent(message = 'No content', meta = {}) {
|
|
21
24
|
return ApiResponseDto.success(null, message, 204, meta);
|
|
22
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-response.dto.js","sourceRoot":"","sources":["../../src/dto/api-response.dto.ts"],"names":[],"mappings":";;;AASA,MAAa,cAAc;IACzB,MAAM,CAAC,OAAO,CACZ,IAAO,EACP,OAAO,GAAG,oBAAoB,EAC9B,UAAU,GAAG,GAAG,EAChB,OAAyB,EAAE;QAE3B,OAAO;YACL,OAAO,EAAE,IAAI;YACb,UAAU;YACV,OAAO;YACP,IAAI;YACJ,IAAI,EAAE;gBACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,GAAG,IAAI;aACR;SACF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,OAAO,CACZ,IAAO,EACP,OAAO,GAAG,+BAA+B,EACzC,OAAyB,EAAE;QAE3B,OAAO,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,SAAS,CACd,OAAO,GAAG,YAAY,EACtB,OAAyB,EAAE;QAE3B,OAAO,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,SAAS,CACd,IAAS,EACT,KAAa,EACb,IAAY,EACZ,KAAa,EACb,OAAO,GAAG,2BAA2B,EACrC,OAAyB,EAAE;QAE3B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;QAC5C,MAAM,UAAU,GAAmB;YACjC,KAAK;YACL,IAAI;YACJ,KAAK;YACL,UAAU;YACV,WAAW,EAAE,IAAI,GAAG,UAAU;YAC9B,WAAW,EAAE,IAAI,GAAG,CAAC;SACtB,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,GAAG;YACf,OAAO;YACP,IAAI;YACJ,UAAU;YACV,IAAI,EAAE;gBACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,GAAG,IAAI;aACR;SACF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,CACV,OAAe,EACf,UAAkB,EAClB,KAAsB,EACtB,OAAyB,EAAE;QAE3B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,UAAU;YACV,OAAO;YACP,KAAK;YACL,IAAI,EAAE;gBACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,GAAG,IAAI;aACR;SACF,CAAC;IACJ,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"api-response.dto.js","sourceRoot":"","sources":["../../src/dto/api-response.dto.ts"],"names":[],"mappings":";;;AASA,MAAa,cAAc;IACzB,MAAM,CAAC,OAAO,CACZ,IAAO,EACP,OAAO,GAAG,oBAAoB,EAC9B,UAAU,GAAG,GAAG,EAChB,OAAyB,EAAE;QAE3B,OAAO;YACL,OAAO,EAAE,IAAI;YACb,UAAU;YACV,OAAO;YACP,IAAI;YACJ,IAAI,EAAE;gBACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,GAAG,IAAI;aACR;SACF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,OAAO,CACZ,IAAO,EACP,OAAO,GAAG,+BAA+B,EACzC,OAAyB,EAAE;QAE3B,OAAO,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,QAAQ,CACb,IAAO,EACP,OAAO,GAAG,iCAAiC,EAC3C,OAAyB,EAAE;QAE3B,OAAO,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,SAAS,CACd,OAAO,GAAG,YAAY,EACtB,OAAyB,EAAE;QAE3B,OAAO,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,SAAS,CACd,IAAS,EACT,KAAa,EACb,IAAY,EACZ,KAAa,EACb,OAAO,GAAG,2BAA2B,EACrC,OAAyB,EAAE;QAE3B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;QAC5C,MAAM,UAAU,GAAmB;YACjC,KAAK;YACL,IAAI;YACJ,KAAK;YACL,UAAU;YACV,WAAW,EAAE,IAAI,GAAG,UAAU;YAC9B,WAAW,EAAE,IAAI,GAAG,CAAC;SACtB,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,GAAG;YACf,OAAO;YACP,IAAI;YACJ,UAAU;YACV,IAAI,EAAE;gBACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,GAAG,IAAI;aACR;SACF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,CACV,OAAe,EACf,UAAkB,EAClB,KAAsB,EACtB,OAAyB,EAAE;QAE3B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,UAAU;YACV,OAAO;YACP,KAAK;YACL,IAAI,EAAE;gBACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,GAAG,IAAI;aACR;SACF,CAAC;IACJ,CAAC;CACF;AA1FD,wCA0FC"}
|
|
@@ -5,6 +5,7 @@ export declare class ForgeExceptionFilter implements ExceptionFilter {
|
|
|
5
5
|
private readonly logger;
|
|
6
6
|
constructor(options?: ForgeOptions);
|
|
7
7
|
catch(exception: unknown, host: ArgumentsHost): void;
|
|
8
|
+
private resolveRequestId;
|
|
8
9
|
private buildMeta;
|
|
9
10
|
private buildErrorResponse;
|
|
10
11
|
private extractHttpExceptionMessage;
|
|
@@ -25,7 +25,10 @@ let ForgeExceptionFilter = ForgeExceptionFilter_1 = class ForgeExceptionFilter {
|
|
|
25
25
|
const ctx = host.switchToHttp();
|
|
26
26
|
const response = ctx.getResponse();
|
|
27
27
|
const request = ctx.getRequest();
|
|
28
|
-
const
|
|
28
|
+
const requestId = this.resolveRequestId(request);
|
|
29
|
+
if (requestId)
|
|
30
|
+
response.setHeader('x-request-id', requestId);
|
|
31
|
+
const meta = this.buildMeta(request, requestId);
|
|
29
32
|
const errorResponse = this.buildErrorResponse(exception, meta);
|
|
30
33
|
if (errorResponse.statusCode >= 500) {
|
|
31
34
|
this.logger.error(`${request.method} ${request.url} → ${errorResponse.statusCode}`, exception instanceof Error ? exception.stack : String(exception));
|
|
@@ -35,13 +38,29 @@ let ForgeExceptionFilter = ForgeExceptionFilter_1 = class ForgeExceptionFilter {
|
|
|
35
38
|
}
|
|
36
39
|
response.status(errorResponse.statusCode).json(errorResponse);
|
|
37
40
|
}
|
|
38
|
-
|
|
39
|
-
const {
|
|
41
|
+
resolveRequestId(request) {
|
|
42
|
+
const { includeRequestId = false, correlationIdHeader } = this.options;
|
|
43
|
+
if (!includeRequestId && !correlationIdHeader)
|
|
44
|
+
return undefined;
|
|
45
|
+
const headers = typeof correlationIdHeader === 'string'
|
|
46
|
+
? [correlationIdHeader]
|
|
47
|
+
: Array.isArray(correlationIdHeader)
|
|
48
|
+
? correlationIdHeader
|
|
49
|
+
: ['x-request-id', 'x-correlation-id'];
|
|
50
|
+
for (const h of headers) {
|
|
51
|
+
const val = request.headers[h.toLowerCase()];
|
|
52
|
+
if (typeof val === 'string' && val)
|
|
53
|
+
return val;
|
|
54
|
+
}
|
|
55
|
+
return includeRequestId ? (0, crypto_1.randomUUID)() : undefined;
|
|
56
|
+
}
|
|
57
|
+
buildMeta(request, requestId) {
|
|
58
|
+
const { includePath = true, includeTimestamp = true, version } = this.options;
|
|
40
59
|
return {
|
|
41
60
|
...(includeTimestamp && { timestamp: new Date().toISOString() }),
|
|
42
61
|
...(includePath && { path: request.url }),
|
|
43
62
|
...(version && { version }),
|
|
44
|
-
...(
|
|
63
|
+
...(requestId && { requestId }),
|
|
45
64
|
};
|
|
46
65
|
}
|
|
47
66
|
buildErrorResponse(exception, meta) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global-exception.filter.js","sourceRoot":"","sources":["../../src/filters/global-exception.filter.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAOwB;AACxB,mCAAoC;AAEpC,+DAA2D;AAC3D,8DAAyD;AAEzD,8DAAiE;AAG1D,IAAM,oBAAoB,4BAA1B,MAAM,oBAAoB;IAG/B,YAA6B,UAAwB,EAAE;QAA1B,YAAO,GAAP,OAAO,CAAmB;QAFtC,WAAM,GAAG,IAAI,eAAM,CAAC,sBAAoB,CAAC,IAAI,CAAC,CAAC;IAEN,CAAC;IAE3D,KAAK,CAAC,SAAkB,EAAE,IAAmB;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAY,CAAC;QAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,EAAW,CAAC;QAE1C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"global-exception.filter.js","sourceRoot":"","sources":["../../src/filters/global-exception.filter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -21,5 +21,6 @@ __exportStar(require("./exceptions"), exports);
|
|
|
21
21
|
__exportStar(require("./filters"), exports);
|
|
22
22
|
__exportStar(require("./interceptors"), exports);
|
|
23
23
|
__exportStar(require("./interfaces"), exports);
|
|
24
|
+
__exportStar(require("./pipes"), exports);
|
|
24
25
|
__exportStar(require("./utils"), exports);
|
|
25
26
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,+CAA6B;AAC7B,wCAAsB;AACtB,+CAA6B;AAC7B,4CAA0B;AAC1B,iDAA+B;AAC/B,+CAA6B;AAC7B,0CAAwB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAmC;AACnC,+CAA6B;AAC7B,wCAAsB;AACtB,+CAA6B;AAC7B,4CAA0B;AAC1B,iDAA+B;AAC/B,+CAA6B;AAC7B,0CAAwB;AACxB,0CAAwB"}
|
|
@@ -7,5 +7,6 @@ export declare class ForgeResponseInterceptor implements NestInterceptor {
|
|
|
7
7
|
private readonly options;
|
|
8
8
|
constructor(reflector: Reflector, options?: ForgeOptions);
|
|
9
9
|
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
10
|
+
private resolveRequestId;
|
|
10
11
|
private buildMeta;
|
|
11
12
|
}
|
|
@@ -25,24 +25,64 @@ let ForgeResponseInterceptor = class ForgeResponseInterceptor {
|
|
|
25
25
|
const isRaw = this.reflector.getAllAndOverride(api_response_decorator_1.FORGE_RAW_RESPONSE_KEY, [context.getHandler(), context.getClass()]);
|
|
26
26
|
if (isRaw)
|
|
27
27
|
return next.handle();
|
|
28
|
+
const start = Date.now();
|
|
28
29
|
const customMessage = this.reflector.getAllAndOverride(api_response_decorator_1.FORGE_MESSAGE_KEY, [context.getHandler(), context.getClass()]);
|
|
30
|
+
const extraMeta = this.reflector.getAllAndOverride(api_response_decorator_1.FORGE_META_KEY, [context.getHandler(), context.getClass()]) ?? {};
|
|
31
|
+
const deprecatedValue = this.reflector.getAllAndOverride(api_response_decorator_1.FORGE_DEPRECATED_KEY, [context.getHandler(), context.getClass()]);
|
|
29
32
|
const request = context.switchToHttp().getRequest();
|
|
30
|
-
const
|
|
31
|
-
const
|
|
33
|
+
const response = context.switchToHttp().getResponse();
|
|
34
|
+
const statusCode = response.statusCode;
|
|
35
|
+
const requestId = this.resolveRequestId(request);
|
|
36
|
+
if (requestId)
|
|
37
|
+
response.setHeader('x-request-id', requestId);
|
|
38
|
+
if (deprecatedValue)
|
|
39
|
+
response.setHeader('Deprecation', 'true');
|
|
32
40
|
return next.handle().pipe((0, operators_1.map)((data) => {
|
|
33
41
|
const message = customMessage ??
|
|
34
42
|
this.options.defaultSuccessMessage ??
|
|
35
43
|
'Request successful';
|
|
44
|
+
const deprecationMeta = deprecatedValue
|
|
45
|
+
? {
|
|
46
|
+
deprecated: true,
|
|
47
|
+
...(typeof deprecatedValue === 'string' && {
|
|
48
|
+
deprecationNotice: deprecatedValue,
|
|
49
|
+
}),
|
|
50
|
+
}
|
|
51
|
+
: {};
|
|
52
|
+
const meta = {
|
|
53
|
+
...this.buildMeta(request, requestId),
|
|
54
|
+
...(this.options.includeResponseTime && {
|
|
55
|
+
responseTime: `${Date.now() - start}ms`,
|
|
56
|
+
}),
|
|
57
|
+
...deprecationMeta,
|
|
58
|
+
...extraMeta,
|
|
59
|
+
};
|
|
36
60
|
return api_response_dto_1.ApiResponseDto.success(data, message, statusCode, meta);
|
|
37
61
|
}));
|
|
38
62
|
}
|
|
39
|
-
|
|
40
|
-
const {
|
|
63
|
+
resolveRequestId(request) {
|
|
64
|
+
const { includeRequestId = false, correlationIdHeader } = this.options;
|
|
65
|
+
if (!includeRequestId && !correlationIdHeader)
|
|
66
|
+
return undefined;
|
|
67
|
+
const headers = typeof correlationIdHeader === 'string'
|
|
68
|
+
? [correlationIdHeader]
|
|
69
|
+
: Array.isArray(correlationIdHeader)
|
|
70
|
+
? correlationIdHeader
|
|
71
|
+
: ['x-request-id', 'x-correlation-id'];
|
|
72
|
+
for (const h of headers) {
|
|
73
|
+
const val = request.headers[h.toLowerCase()];
|
|
74
|
+
if (typeof val === 'string' && val)
|
|
75
|
+
return val;
|
|
76
|
+
}
|
|
77
|
+
return includeRequestId ? (0, crypto_1.randomUUID)() : undefined;
|
|
78
|
+
}
|
|
79
|
+
buildMeta(request, requestId) {
|
|
80
|
+
const { includePath = true, includeTimestamp = true, version } = this.options;
|
|
41
81
|
return {
|
|
42
82
|
...(includeTimestamp && { timestamp: new Date().toISOString() }),
|
|
43
83
|
...(includePath && { path: request.url }),
|
|
44
84
|
...(version && { version }),
|
|
45
|
-
...(
|
|
85
|
+
...(requestId && { requestId }),
|
|
46
86
|
};
|
|
47
87
|
}
|
|
48
88
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/response.interceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,uCAAyC;AACzC,mCAAoC;AAEpC,8CAAqC;AAErC,8DAAyD;AAEzD,
|
|
1
|
+
{"version":3,"file":"response.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/response.interceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,uCAAyC;AACzC,mCAAoC;AAEpC,8CAAqC;AAErC,8DAAyD;AAEzD,iFAK8C;AAGvC,IAAM,wBAAwB,GAA9B,MAAM,wBAAwB;IACnC,YACmB,SAAoB,EACpB,UAAwB,EAAE;QAD1B,cAAS,GAAT,SAAS,CAAW;QACpB,YAAO,GAAP,OAAO,CAAmB;IAC1C,CAAC;IAEJ,SAAS,CAAC,OAAyB,EAAE,IAAiB;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAC5C,+CAAsB,EACtB,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAC3C,CAAC;QAEF,IAAI,KAAK;YAAE,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QAEhC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEzB,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CACpD,0CAAiB,EACjB,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAC3C,CAAC;QAEF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAChD,uCAAc,EACd,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAC3C,IAAI,EAAE,CAAC;QAER,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CACtD,6CAAoB,EACpB,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAC3C,CAAC;QAEF,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAW,CAAC;QAC7D,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,WAAW,EAAY,CAAC;QAChE,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;QAEvC,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,SAAS;YAAE,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;QAC7D,IAAI,eAAe;YAAE,QAAQ,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAE/D,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CACvB,IAAA,eAAG,EAAC,CAAC,IAAI,EAAE,EAAE;YACX,MAAM,OAAO,GACX,aAAa;gBACb,IAAI,CAAC,OAAO,CAAC,qBAAqB;gBAClC,oBAAoB,CAAC;YAEvB,MAAM,eAAe,GAAG,eAAe;gBACrC,CAAC,CAAC;oBACE,UAAU,EAAE,IAAa;oBACzB,GAAG,CAAC,OAAO,eAAe,KAAK,QAAQ,IAAI;wBACzC,iBAAiB,EAAE,eAAe;qBACnC,CAAC;iBACH;gBACH,CAAC,CAAC,EAAE,CAAC;YAEP,MAAM,IAAI,GAAG;gBACX,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC;gBACrC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,IAAI;oBACtC,YAAY,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI;iBACxC,CAAC;gBACF,GAAG,eAAe;gBAClB,GAAG,SAAS;aACb,CAAC;YAEF,OAAO,iCAAc,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;QACjE,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAEO,gBAAgB,CAAC,OAAgB;QACvC,MAAM,EAAE,gBAAgB,GAAG,KAAK,EAAE,mBAAmB,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QACvE,IAAI,CAAC,gBAAgB,IAAI,CAAC,mBAAmB;YAAE,OAAO,SAAS,CAAC;QAEhE,MAAM,OAAO,GACX,OAAO,mBAAmB,KAAK,QAAQ;YACrC,CAAC,CAAC,CAAC,mBAAmB,CAAC;YACvB,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC;gBAClC,CAAC,CAAC,mBAAmB;gBACrB,CAAC,CAAC,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAE7C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YAC7C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;QACjD,CAAC;QAED,OAAO,gBAAgB,CAAC,CAAC,CAAC,IAAA,mBAAU,GAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACrD,CAAC;IAEO,SAAS,CAAC,OAAgB,EAAE,SAA6B;QAC/D,MAAM,EAAE,WAAW,GAAG,IAAI,EAAE,gBAAgB,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QAC9E,OAAO;YACL,GAAG,CAAC,gBAAgB,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;YAChE,GAAG,CAAC,WAAW,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;YACzC,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,CAAC;YAC3B,GAAG,CAAC,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;SAChC,CAAC;IACJ,CAAC;CACF,CAAA;AAjGY,4DAAwB;mCAAxB,wBAAwB;IADpC,IAAA,mBAAU,GAAE;qCAGmB,gBAAS;GAF5B,wBAAwB,CAiGpC"}
|
|
@@ -3,6 +3,10 @@ export interface ApiMeta {
|
|
|
3
3
|
path?: string;
|
|
4
4
|
version?: string;
|
|
5
5
|
requestId?: string;
|
|
6
|
+
responseTime?: string;
|
|
7
|
+
deprecated?: true;
|
|
8
|
+
deprecationNotice?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
6
10
|
}
|
|
7
11
|
export interface ApiSuccessResponse<T = unknown> {
|
|
8
12
|
success: true;
|
|
@@ -48,6 +52,8 @@ export interface ForgeOptions {
|
|
|
48
52
|
includePath?: boolean;
|
|
49
53
|
includeTimestamp?: boolean;
|
|
50
54
|
includeRequestId?: boolean;
|
|
55
|
+
correlationIdHeader?: string | string[];
|
|
56
|
+
includeResponseTime?: boolean;
|
|
51
57
|
version?: string;
|
|
52
58
|
defaultSuccessMessage?: string;
|
|
53
59
|
}
|