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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # nestjs-api-forge
2
2
 
3
- [![Build & Publish](https://github.com/mirzasaikatahmmed/nestjs-api-forge/actions/workflows/publish.yml/badge.svg)](https://github.com/mirzasaikatahmmed/nestjs-api-forge/actions/workflows/publish.yml)
3
+ [![CI](https://img.shields.io/github/actions/workflow/status/mirzasaikatahmmed/nestjs-api-forge/publish.yml?label=build)](https://github.com/mirzasaikatahmmed/nestjs-api-forge/actions/workflows/publish.yml)
4
4
  [![npm version](https://img.shields.io/npm/v/nestjs-api-forge.svg)](https://www.npmjs.com/package/nestjs-api-forge)
5
5
  [![npm downloads](https://img.shields.io/npm/dm/nestjs-api-forge.svg)](https://www.npmjs.com/package/nestjs-api-forge)
6
6
  [![license](https://img.shields.io/npm/l/nestjs-api-forge.svg)](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
- - **Request ID tracing** — optional UUID `requestId` in every response `meta`
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
- Every route in your application now returns a standardized response automatically.
59
-
60
- ### 2. Add `ValidationPipe` in `main.ts`
65
+ ### 2. Use `ForgeValidationPipe` in `main.ts`
61
66
 
62
67
  ```typescript
63
- import { ValidationPipe } from '@nestjs/common';
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
- The exception filter automatically parses `ValidationPipe` errors and formats them into `error.details`.
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 (from `ValidationPipe`)
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": "age", "message": "must be an integer number" }
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 (`ApiResponseDto.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": [ { "id": 1, "name": "Alice Johnson" } ],
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` | Attach a generated UUID to `meta.requestId` |
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
- // Success
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
- // No content (204)
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 exceptions extend `ApiException` → `HttpException` and produce a structured error body.
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
- ### Paginated list (skip envelope, build manually)
290
+ ### Deprecated route
268
291
 
269
292
  ```typescript
270
- import { ApiResponseDto, ForgeRawResponse } from 'nestjs-api-forge';
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
- ### Custom exception with field details
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
- import { BadRequestException, ValidationException } from 'nestjs-api-forge';
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
- // With field-level details
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 constraints map
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":";;;AAoBA,4BAMC;AA1BD,2CAA2F;AAC3F,uCAAyC;AACzC,+EAAgF;AAChF,gFAA0E;AAG7D,QAAA,iBAAiB,GAAG,wBAAwB,CAAC;AAC7C,QAAA,sBAAsB,GAAG,oBAAoB,CAAC;AAGpD,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;AAMhF,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"}
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;AAlFD,wCAkFC"}
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 meta = this.buildMeta(request);
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
- buildMeta(request) {
39
- const { includePath = true, includeTimestamp = true, includeRequestId = false, version, } = this.options;
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
- ...(includeRequestId && { requestId: (0, crypto_1.randomUUID)() }),
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":"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"}
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
@@ -5,4 +5,5 @@ export * from './exceptions';
5
5
  export * from './filters';
6
6
  export * from './interceptors';
7
7
  export * from './interfaces';
8
+ export * from './pipes';
8
9
  export * from './utils';
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 statusCode = context.switchToHttp().getResponse().statusCode;
31
- const meta = this.buildMeta(request);
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
- buildMeta(request) {
40
- const { includePath = true, includeTimestamp = true, includeRequestId = false, version, } = this.options;
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
- ...(includeRequestId && { requestId: (0, crypto_1.randomUUID)() }),
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,iFAG8C;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,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,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAW,CAAC;QAC7D,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC;QAEnE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAErC,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,OAAO,iCAAc,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;QACjE,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAEO,SAAS,CAAC,OAAgB;QAChC,MAAM,EACJ,WAAW,GAAG,IAAI,EAClB,gBAAgB,GAAG,IAAI,EACvB,gBAAgB,GAAG,KAAK,EACxB,OAAO,GACR,GAAG,IAAI,CAAC,OAAO,CAAC;QACjB,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,gBAAgB,IAAI,EAAE,SAAS,EAAE,IAAA,mBAAU,GAAE,EAAE,CAAC;SACrD,CAAC;IACJ,CAAC;CACF,CAAA;AAlDY,4DAAwB;mCAAxB,wBAAwB;IADpC,IAAA,mBAAU,GAAE;qCAGmB,gBAAS;GAF5B,wBAAwB,CAkDpC"}
1
+ {"version":3,"file":"response.interceptor.js","sourceRoot":"","sources":["../../src/interceptors/response.interceptor.ts"],"names":[],"mappings":"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"}
@@ -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
  }
@@ -0,0 +1,4 @@
1
+ import { ValidationPipe, ValidationPipeOptions } from '@nestjs/common';
2
+ export declare class ForgeValidationPipe extends ValidationPipe {
3
+ constructor(options?: ValidationPipeOptions);
4
+ }