nestjs-api-forge 1.0.3 → 1.0.5

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,39 +1,74 @@
1
- # nestjs-api-forge
1
+ <div align="center">
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)
4
- [![npm version](https://img.shields.io/npm/v/nestjs-api-forge.svg)](https://www.npmjs.com/package/nestjs-api-forge)
5
- [![npm downloads](https://img.shields.io/npm/dm/nestjs-api-forge.svg)](https://www.npmjs.com/package/nestjs-api-forge)
6
- [![license](https://img.shields.io/npm/l/nestjs-api-forge.svg)](https://github.com/mirzasaikatahmmed/nestjs-api-forge/blob/main/LICENSE)
3
+ # ⚡ NestJS API Forge
7
4
 
8
- Plug-and-play response envelope, exception filter, and error formatting for [NestJS](https://nestjs.com/) REST APIs — zero boilerplate, fully typed.
5
+ **Plug-and-play response envelope, exception filter, and error formatting for NestJS REST APIs — zero boilerplate, fully typed.**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/nestjs-api-forge?color=blue&label=npm)](https://www.npmjs.com/package/nestjs-api-forge)
8
+ [![npm downloads](https://img.shields.io/npm/dm/nestjs-api-forge?color=green)](https://www.npmjs.com/package/nestjs-api-forge)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+ [![NestJS](https://img.shields.io/badge/NestJS-%3E%3D9-red)](https://nestjs.com)
11
+ [![Built with TypeScript](https://img.shields.io/badge/Built%20with-TypeScript-3178c6)](https://www.typescriptlang.org)
12
+ [![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)
13
+ [![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-ea4aaa?logo=github-sponsors)](https://github.com/sponsors/mirzasaikatahmmed)
14
+ [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-☕-yellow?logo=buy-me-a-coffee)](https://buymeacoffee.com/saikat)
15
+
16
+ <br/>
17
+
18
+ > One import to standardize every response, every error, and every validation in your NestJS API.
19
+
20
+ </div>
9
21
 
10
22
  ---
11
23
 
12
- ## Features
24
+ ## 🧩 What it does
25
+
26
+ Register `ApiForgeModule` once and every response from your API is automatically shaped into a consistent envelope. No more manual `{ success, data, message }` wrappers scattered across controllers.
27
+
28
+ | Layer | What Forge provides |
29
+ |-------|-------------------|
30
+ | 📦 | **Response envelope** — every handler wrapped with `success`, `statusCode`, `message`, `data`, and `meta` |
31
+ | 🛡️ | **Global exception filter** — handles `HttpException`, validation errors, and unexpected crashes uniformly |
32
+ | ✅ | **Structured validation** — `ForgeValidationPipe` replaces `ValidationPipe` with typed field-level error details |
33
+ | 📄 | **Paginated responses** — `ApiResponseDto.paginated()` with full pagination meta in one call |
34
+ | 🏷️ | **Per-route decorators** — override messages, skip wrapping, add custom meta, or mark routes deprecated |
35
+ | 🔍 | **Request tracing** — correlation ID passthrough, auto UUID generation, and response time measurement |
36
+ | 💥 | **Typed exceptions** — drop-in replacements for every NestJS `HttpException` with structured error codes |
37
+
38
+ ### Decorators at a glance
13
39
 
14
- - **Uniform success envelope** — every handler response wrapped with `success`, `statusCode`, `message`, `data`, and `meta`
15
- - **Structured error responses** — consistent `code`, `message`, and optional `details` array for all errors
16
- - **Global exception filter** — handles `HttpException`, `ValidationPipe` errors, and unexpected exceptions
17
- - **Paginated response helper** — `ApiResponseDto.paginated()` with full pagination meta
18
- - **Built-in typed exceptions** — drop-in replacements for NestJS built-ins with structured error codes
19
- - **`@ForgeMessage`** — override per-route success message
20
- - **`@ForgeRawResponse`** — opt a route out of envelope wrapping
21
- - **`@ApiForge`** — apply filter + interceptor to a single controller without going global
22
- - **`ApiForgeModule.forRoot()`** — one-line global registration
23
- - **`ApiForgeModule.forRootAsync()`** — config-service-driven async options
24
- - **Request ID tracing** — optional UUID `requestId` in every response `meta`
40
+ | Decorator | Scope | Description |
41
+ |-----------|-------|-------------|
42
+ | `@ApiForge(options?)` | Controller / Method | Apply filter + interceptor without going global |
43
+ | `@ForgeMessage(msg)` | Controller / Method | Override the success message for that route |
44
+ | `@ForgeRawResponse()` | Controller / Method | Skip envelope wrapping — return raw handler value |
45
+ | `@ForgeMeta(extra)` | Controller / Method | Merge extra key-value pairs into `meta` |
46
+ | `@ForgeDeprecated(notice?)` | Controller / Method | Mark route deprecated — adds `meta.deprecated` + `Deprecation` header |
25
47
 
26
48
  ---
27
49
 
28
- ## Installation
50
+ ## 📦 Installation
51
+
52
+ ### Requirements
53
+
54
+ - **Node.js** v18 or higher
55
+ - **NestJS** v9, v10, or v11
56
+
57
+ ### Install
29
58
 
30
59
  ```bash
31
60
  npm install nestjs-api-forge
32
61
  ```
33
62
 
63
+ Verify the install:
64
+
65
+ ```bash
66
+ node -e "require('nestjs-api-forge'); console.log('nestjs-api-forge installed')"
67
+ ```
68
+
34
69
  ---
35
70
 
36
- ## Quick Start
71
+ ## 🚀 Quick Start
37
72
 
38
73
  ### 1. Register globally in `app.module.ts`
39
74
 
@@ -49,33 +84,34 @@ import { ApiForgeModule } from 'nestjs-api-forge';
49
84
  includePath: true,
50
85
  includeTimestamp: true,
51
86
  includeRequestId: true,
87
+ includeResponseTime: true,
88
+ correlationIdHeader: 'x-request-id',
52
89
  }),
53
90
  ],
54
91
  })
55
92
  export class AppModule {}
56
93
  ```
57
94
 
58
- Every route in your application now returns a standardized response automatically.
59
-
60
- ### 2. Add `ValidationPipe` in `main.ts`
95
+ ### 2. Add `ForgeValidationPipe` in `main.ts`
61
96
 
62
97
  ```typescript
63
- import { ValidationPipe } from '@nestjs/common';
64
-
65
- app.useGlobalPipes(
66
- new ValidationPipe({
67
- whitelist: true,
68
- forbidNonWhitelisted: true,
69
- transform: true,
70
- }),
71
- );
98
+ import { NestFactory } from '@nestjs/core';
99
+ import { ForgeValidationPipe } from 'nestjs-api-forge';
100
+ import { AppModule } from './app.module';
101
+
102
+ async function bootstrap() {
103
+ const app = await NestFactory.create(AppModule);
104
+ app.useGlobalPipes(new ForgeValidationPipe());
105
+ await app.listen(3000);
106
+ }
107
+ bootstrap();
72
108
  ```
73
109
 
74
- The exception filter automatically parses `ValidationPipe` errors and formats them into `error.details`.
110
+ That's it. Every response is now wrapped, every error is structured, and every validation failure returns typed field details.
75
111
 
76
112
  ---
77
113
 
78
- ## Response Shapes
114
+ ## 📐 Response Shapes
79
115
 
80
116
  ### Success (`2xx`)
81
117
 
@@ -86,10 +122,11 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
86
122
  "message": "User fetched successfully",
87
123
  "data": { "id": 1, "name": "Alice Johnson", "email": "alice@example.com" },
88
124
  "meta": {
89
- "timestamp": "2026-05-15T10:00:00.000Z",
125
+ "timestamp": "2025-01-15T10:00:00.000Z",
90
126
  "path": "/api/users/1",
91
127
  "version": "1.0.0",
92
- "requestId": "a3f2c1d0-84e5-4b6a-9123-abc123def456"
128
+ "requestId": "a3f2c1d0-84e5-4b6a-9123-abc123def456",
129
+ "responseTime": "4ms"
93
130
  }
94
131
  }
95
132
  ```
@@ -101,19 +138,17 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
101
138
  "success": false,
102
139
  "statusCode": 404,
103
140
  "message": "User not found",
104
- "error": {
105
- "code": "NOT_FOUND"
106
- },
141
+ "error": { "code": "NOT_FOUND" },
107
142
  "meta": {
108
- "timestamp": "2026-05-15T10:00:00.000Z",
143
+ "timestamp": "2025-01-15T10:00:00.000Z",
109
144
  "path": "/api/users/99",
110
- "version": "1.0.0",
111
- "requestId": "b1e2f3a4-0000-4b5c-8d9e-fedcba987654"
145
+ "requestId": "b1e2f3a4-0000-4b5c-8d9e-fedcba987654",
146
+ "responseTime": "2ms"
112
147
  }
113
148
  }
114
149
  ```
115
150
 
116
- ### Validation Error (from `ValidationPipe`)
151
+ ### Validation Error
117
152
 
118
153
  ```json
119
154
  {
@@ -124,24 +159,21 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
124
159
  "code": "VALIDATION_ERROR",
125
160
  "details": [
126
161
  { "field": "email", "message": "must be an email" },
127
- { "field": "age", "message": "must be an integer number" }
162
+ { "field": "address.zip", "message": "must be a string" }
128
163
  ]
129
164
  },
130
- "meta": {
131
- "timestamp": "2026-05-15T10:00:00.000Z",
132
- "path": "/api/users"
133
- }
165
+ "meta": { "timestamp": "2025-01-15T10:00:00.000Z", "path": "/api/users" }
134
166
  }
135
167
  ```
136
168
 
137
- ### Paginated (`ApiResponseDto.paginated()`)
169
+ ### Paginated
138
170
 
139
171
  ```json
140
172
  {
141
173
  "success": true,
142
174
  "statusCode": 200,
143
175
  "message": "Users fetched successfully",
144
- "data": [ { "id": 1, "name": "Alice Johnson" } ],
176
+ "data": [{ "id": 1, "name": "Alice Johnson" }],
145
177
  "pagination": {
146
178
  "total": 42,
147
179
  "page": 2,
@@ -151,75 +183,96 @@ The exception filter automatically parses `ValidationPipe` errors and formats th
151
183
  "hasPrevPage": true
152
184
  },
153
185
  "meta": {
154
- "timestamp": "2026-05-15T10:00:00.000Z",
155
- "path": "/api/users?page=2&limit=10"
186
+ "timestamp": "2025-01-15T10:00:00.000Z",
187
+ "path": "/api/users?page=2&limit=10",
188
+ "responseTime": "8ms"
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Deprecated Route
194
+
195
+ ```json
196
+ {
197
+ "success": true,
198
+ "statusCode": 200,
199
+ "message": "OK",
200
+ "data": {},
201
+ "meta": {
202
+ "timestamp": "2025-01-15T10:00:00.000Z",
203
+ "deprecated": true,
204
+ "deprecationNotice": "Use /v2/users instead"
156
205
  }
157
206
  }
158
207
  ```
159
208
 
160
209
  ---
161
210
 
162
- ## API Reference
211
+ ## 📖 API Reference
163
212
 
164
213
  ### `ApiForgeModule.forRoot(options?)`
165
214
 
166
215
  | Option | Type | Default | Description |
167
- |---|---|---|---|
216
+ |--------|------|---------|-------------|
168
217
  | `includePath` | `boolean` | `true` | Include request path in `meta` |
169
218
  | `includeTimestamp` | `boolean` | `true` | Include ISO timestamp in `meta` |
170
- | `includeRequestId` | `boolean` | `false` | Attach a generated UUID to `meta.requestId` |
219
+ | `includeRequestId` | `boolean` | `false` | Auto-generate UUID `requestId` in `meta` if no correlation header found |
220
+ | `correlationIdHeader` | `string \| string[]` | `['x-request-id', 'x-correlation-id']` | Header(s) to read request ID from; echoed back on the response |
221
+ | `includeResponseTime` | `boolean` | `false` | Include handler duration as `meta.responseTime` (e.g. `"12ms"`) |
171
222
  | `version` | `string` | `undefined` | API version string added to `meta` |
172
223
  | `defaultSuccessMessage` | `string` | `'Request successful'` | Fallback success message |
173
224
 
174
225
  ### `ApiForgeModule.forRootAsync(asyncOptions)`
175
226
 
227
+ Use when options depend on a config service:
228
+
176
229
  ```typescript
177
230
  ApiForgeModule.forRootAsync({
178
231
  imports: [ConfigModule],
179
232
  inject: [ConfigService],
180
233
  useFactory: (config: ConfigService) => ({
181
234
  version: config.get('API_VERSION'),
182
- defaultSuccessMessage: config.get('DEFAULT_SUCCESS_MSG'),
183
235
  includeRequestId: true,
236
+ includeResponseTime: true,
237
+ correlationIdHeader: config.get('CORRELATION_HEADER'),
184
238
  }),
185
239
  })
186
240
  ```
187
241
 
188
- ### Decorators
242
+ ### `ForgeValidationPipe`
189
243
 
190
- | Decorator | Scope | Description |
191
- |---|---|---|
192
- | `@ApiForge(options?)` | Controller / Method | Apply filter + interceptor without going global |
193
- | `@ForgeMessage(msg)` | Controller / Method | Override the success message for that route |
194
- | `@ForgeRawResponse()` | Controller / Method | Skip envelope wrapping; return raw handler value |
244
+ Drop-in replacement for NestJS `ValidationPipe`. Throws `ValidationException` with structured `details` instead of a string-message array. Supports nested objects using dot-notation field paths.
195
245
 
196
- ### `ApiResponseDto` — manual usage
246
+ ```typescript
247
+ // Use defaults
248
+ app.useGlobalPipes(new ForgeValidationPipe());
249
+
250
+ // Override defaults
251
+ app.useGlobalPipes(new ForgeValidationPipe({
252
+ whitelist: false,
253
+ forbidNonWhitelisted: false,
254
+ }));
255
+ ```
256
+
257
+ ### `ApiResponseDto` — manual builders
197
258
 
198
259
  ```typescript
199
260
  import { ApiResponseDto } from 'nestjs-api-forge';
200
261
 
201
- // Success
202
- ApiResponseDto.success(data, 'User fetched', 200, { path: '/users/1' });
203
-
204
- // Created (201)
262
+ ApiResponseDto.success(data, 'OK', 200, { path: '/users/1' });
205
263
  ApiResponseDto.created(data, 'User created');
206
-
207
- // No content (204)
208
- ApiResponseDto.noContent('Deleted');
209
-
210
- // Paginated
264
+ ApiResponseDto.accepted(jobRef, 'Export queued'); // 202
265
+ ApiResponseDto.noContent('Deleted'); // 204
211
266
  ApiResponseDto.paginated(data, total, page, limit, 'Users fetched');
212
-
213
- // Error
214
267
  ApiResponseDto.error('Not found', 404, { code: 'NOT_FOUND' });
215
268
  ```
216
269
 
217
270
  ### Built-in Exceptions
218
271
 
219
- All exceptions extend `ApiException` → `HttpException` and produce a structured error body.
272
+ All extend `ApiException` → `HttpException`. Pass an optional message and `details` array to any of them.
220
273
 
221
274
  | Class | Status | Code |
222
- |---|---|---|
275
+ |-------|--------|------|
223
276
  | `BadRequestException` | 400 | `BAD_REQUEST` |
224
277
  | `UnauthorizedException` | 401 | `UNAUTHORIZED` |
225
278
  | `PaymentRequiredException` | 402 | `PAYMENT_REQUIRED` |
@@ -236,23 +289,21 @@ All exceptions extend `ApiException` → `HttpException` and produce a structure
236
289
 
237
290
  ---
238
291
 
239
- ## Usage Examples
292
+ ## 💡 Usage Examples
240
293
 
241
294
  ### Standard CRUD controller
242
295
 
243
296
  ```typescript
244
- import { Controller, Get, Post, Delete, Param, Body, HttpCode, HttpStatus } from '@nestjs/common';
245
- import { ForgeMessage, ForgeRawResponse, NotFoundException } from 'nestjs-api-forge';
297
+ import { Controller, Get, Delete, Param, HttpCode, HttpStatus } from '@nestjs/common';
298
+ import { ForgeMessage, NotFoundException } from 'nestjs-api-forge';
246
299
 
247
300
  @Controller('users')
248
301
  export class UsersController {
249
- constructor(private readonly usersService: UsersService) {}
250
-
251
302
  @Get(':id')
252
303
  @ForgeMessage('User fetched successfully')
253
304
  findOne(@Param('id') id: number) {
254
305
  const user = this.usersService.findById(id);
255
- if (!user) throw new NotFoundException('User');
306
+ if (!user) throw new NotFoundException('User not found');
256
307
  return user;
257
308
  }
258
309
 
@@ -264,11 +315,9 @@ export class UsersController {
264
315
  }
265
316
  ```
266
317
 
267
- ### Paginated list (skip envelope, build manually)
318
+ ### Paginated list
268
319
 
269
320
  ```typescript
270
- import { ApiResponseDto, ForgeRawResponse } from 'nestjs-api-forge';
271
-
272
321
  @Get()
273
322
  @ForgeRawResponse()
274
323
  findAll(@Query('page') page = '1', @Query('limit') limit = '10') {
@@ -279,22 +328,36 @@ findAll(@Query('page') page = '1', @Query('limit') limit = '10') {
279
328
  }
280
329
  ```
281
330
 
282
- ### Per-controller scope (no global module)
331
+ ### Deprecated route
283
332
 
284
333
  ```typescript
285
- import { ApiForge, ForgeMessage } from 'nestjs-api-forge';
334
+ @Get('export')
335
+ @ForgeDeprecated('Use POST /v2/users/export instead')
336
+ @ForgeMessage('Export started')
337
+ startExport() {
338
+ const job = this.usersService.queueExport();
339
+ return ApiResponseDto.accepted({ jobId: job.id }, 'Export queued');
340
+ }
341
+ ```
286
342
 
287
- @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
- }
343
+ ### Custom meta per route
344
+
345
+ ```typescript
346
+ @Get()
347
+ @ForgeMeta({ region: 'us-east-1', cache: 'miss' })
348
+ findAll() {
349
+ return this.usersService.findAll();
295
350
  }
296
351
  ```
297
352
 
353
+ ### Per-controller scope (no global module)
354
+
355
+ ```typescript
356
+ @Controller('products')
357
+ @ApiForge({ version: '2.0', includeResponseTime: true })
358
+ export class ProductsController { ... }
359
+ ```
360
+
298
361
  ### Raw response (health check)
299
362
 
300
363
  ```typescript
@@ -305,17 +368,25 @@ health() {
305
368
  }
306
369
  ```
307
370
 
308
- ### Custom exception with field details
371
+ ### Correlation ID tracing
372
+
373
+ Send `X-Request-ID: abc-123` in the request — the same ID appears in `meta.requestId` and is echoed in the response header. Useful for distributed tracing across microservices.
309
374
 
310
375
  ```typescript
311
- import { BadRequestException, ValidationException } from 'nestjs-api-forge';
376
+ ApiForgeModule.forRoot({
377
+ correlationIdHeader: 'x-request-id', // or an array of headers
378
+ includeRequestId: true, // generate UUID if header is absent
379
+ })
380
+ ```
312
381
 
313
- // With field-level details
382
+ ### Custom exception with field details
383
+
384
+ ```typescript
314
385
  throw new BadRequestException('Invalid input', [
315
386
  { field: 'price', message: 'Must be a positive number', value: -5 },
316
387
  ]);
317
388
 
318
- // From class-validator constraints map
389
+ // From class-validator constraint map
319
390
  throw ValidationException.fromConstraints({
320
391
  email: { isEmail: 'must be an email' },
321
392
  age: { min: 'must be at least 1' },
@@ -324,17 +395,131 @@ throw ValidationException.fromConstraints({
324
395
 
325
396
  ---
326
397
 
327
- ## Peer Dependencies
398
+ ## 📁 Project Structure
328
399
 
329
400
  ```
330
- @nestjs/common ^9 | ^10 | ^11
331
- @nestjs/core ^9 | ^10 | ^11
401
+ nestjs-api-forge/
402
+ ├── src/
403
+ │ ├── api-forge.module.ts # forRoot / forRootAsync registration
404
+ │ ├── index.ts # Public exports
405
+ │ ├── decorators/
406
+ │ │ └── api-response.decorator.ts # @ApiForge, @ForgeMessage, @ForgeRawResponse, @ForgeMeta, @ForgeDeprecated
407
+ │ ├── dto/
408
+ │ │ └── api-response.dto.ts # ApiResponseDto static builders
409
+ │ ├── exceptions/
410
+ │ │ ├── api.exception.ts # Base ApiException class
411
+ │ │ └── validation.exception.ts # ValidationException with field details
412
+ │ ├── filters/
413
+ │ │ └── global-exception.filter.ts # ForgeExceptionFilter
414
+ │ ├── interceptors/
415
+ │ │ └── response.interceptor.ts # ForgeResponseInterceptor
416
+ │ ├── interfaces/
417
+ │ │ └── api-response.interface.ts # TypeScript interfaces and ForgeOptions
418
+ │ ├── pipes/
419
+ │ │ └── forge-validation.pipe.ts # ForgeValidationPipe
420
+ │ └── utils/
421
+ │ └── error-code.util.ts
422
+ ├── app/ # Example NestJS app demonstrating the library
423
+ │ └── src/
424
+ │ ├── users/ # Full CRUD example with pagination
425
+ │ └── products/ # Per-controller @ApiForge example
426
+ ├── .github/
427
+ │ ├── ISSUE_TEMPLATE/ # Bug report & feature request templates
428
+ │ └── workflows/
429
+ │ ├── publish.yml # npm publish on tag push
430
+ │ └── malware-scan.yml # Security scan on every push
431
+ ├── CONTRIBUTING.md
432
+ ├── CODE_OF_CONDUCT.md
433
+ └── package.json
434
+ ```
435
+
436
+ ---
437
+
438
+ ## 🛠️ Development
439
+
440
+ ```bash
441
+ # Clone the repo
442
+ git clone https://github.com/mirzasaikatahmmed/nestjs-api-forge.git
443
+ cd nestjs-api-forge
444
+
445
+ # Install dependencies
446
+ npm install
447
+
448
+ # Build the library
449
+ npm run build
450
+
451
+ # Watch mode
452
+ npm run build:watch
453
+
454
+ # Format code
455
+ npm run format
456
+
457
+ # Run the example app
458
+ cd app && npm install && npm run start:dev
459
+ ```
460
+
461
+ ---
462
+
463
+ ## 📦 Peer Dependencies
464
+
465
+ ```
466
+ @nestjs/common ^9 | ^10 | ^11
467
+ @nestjs/core ^9 | ^10 | ^11
332
468
  reflect-metadata ^0.1 | ^0.2
333
- rxjs ^7
469
+ rxjs ^7
334
470
  ```
335
471
 
336
472
  ---
337
473
 
338
- ## License
474
+ ## 📋 Changelog
475
+
476
+ ### v1.1.0
477
+ - `ForgeValidationPipe` — structured validation errors with nested field support (dot-notation)
478
+ - `@ForgeMeta(extra)` — merge custom fields into response `meta` per route or controller
479
+ - `@ForgeDeprecated(notice?)` — deprecation flag in `meta` + `Deprecation: true` response header
480
+ - `ApiResponseDto.accepted()` — 202 Accepted helper
481
+ - Correlation ID passthrough (`correlationIdHeader` option)
482
+ - Response time measurement (`includeResponseTime` option)
483
+ - `forRootAsync` fix — options factory now runs once instead of twice
484
+ - 3 new exceptions: `MethodNotAllowedException`, `PaymentRequiredException`, `GatewayTimeoutException`
485
+
486
+ ### v1.0.4
487
+ - Decorators: `@ForgeMessage`, `@ForgeRawResponse`, `@ForgeMeta`, `@ForgeDeprecated`, `@ApiForge`
488
+ - `ForgeValidationPipe` with structured field-level errors
489
+ - Response metadata support (`includePath`, `includeTimestamp`, `includeRequestId`, `includeResponseTime`)
490
+
491
+ ### v1.0.3
492
+ - Bump version, update repository URL
493
+
494
+ ### v1.0.0
495
+ - Initial release — `ApiForgeModule.forRoot()`, `ForgeExceptionFilter`, `ForgeResponseInterceptor`, `ApiResponseDto`, and 10 typed exceptions
496
+
497
+ ---
498
+
499
+ ## 🤝 Contributing
500
+
501
+ Pull requests are welcome! For major changes, please open an issue first to discuss your approach.
502
+
503
+ 1. Fork the repository
504
+ 2. Create your branch: `git checkout -b feat/your-feature`
505
+ 3. Make your changes and run `npm run format`
506
+ 4. Push and open a PR against `main`
507
+
508
+ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for full guidelines and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before participating. All contributors are listed in [CONTRIBUTORS.md](CONTRIBUTORS.md).
509
+
510
+ ---
511
+
512
+ ## 💛 Support
513
+
514
+ If NestJS API Forge saves you boilerplate, consider supporting the project:
515
+
516
+ [![GitHub Sponsors](https://img.shields.io/badge/Sponsor%20on%20GitHub-%E2%9D%A4-ea4aaa?logo=github-sponsors&style=for-the-badge)](https://github.com/sponsors/mirzasaikatahmmed)
517
+ [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-☕-yellow?logo=buy-me-a-coffee&style=for-the-badge)](https://buymeacoffee.com/saikat)
518
+
519
+ ---
520
+
521
+ <div align="center">
522
+
523
+ Made with ❤️ by [Mirza Saikat Ahmmed](https://github.com/mirzasaikatahmmed)
339
524
 
340
- MIT
525
+ </div>
@@ -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"}