@myko.pk/response 1.0.0

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/dist/index.js ADDED
@@ -0,0 +1,883 @@
1
+ import { Catch, BadRequestException, HttpException, UnauthorizedException, ForbiddenException, Injectable, Module, HttpStatus, SetMetadata, applyDecorators } from '@nestjs/common';
2
+ import { Logger } from '@myko.pk/logger';
3
+ import { map } from 'rxjs/operators';
4
+ import { AsyncLocalStorage } from 'async_hooks';
5
+ import { randomUUID } from 'crypto';
6
+ import { IsOptional, IsInt, Min, Max, IsString } from 'class-validator';
7
+ import { Type } from 'class-transformer';
8
+ import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+ import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
12
+
13
+ var __defProp = Object.defineProperty;
14
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
15
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
16
+ var __decorateClass = (decorators, target, key, kind) => {
17
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
18
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
19
+ if (decorator = decorators[i])
20
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
21
+ if (kind && result) __defProp(target, key, result);
22
+ return result;
23
+ };
24
+
25
+ // src/response.constants.ts
26
+ var RESPONSE_FILTERS = {
27
+ GLOBAL: "global",
28
+ VALIDATION: "validation",
29
+ HTTP: "http",
30
+ DATABASE: "database",
31
+ AUTH: "auth"
32
+ };
33
+ var HTTP_STATUS = {
34
+ OK: 200,
35
+ CREATED: 201,
36
+ ACCEPTED: 202,
37
+ NO_CONTENT: 204,
38
+ MULTI_STATUS: 207,
39
+ FOUND: 302,
40
+ BAD_REQUEST: 400
41
+ };
42
+ var RESPONSE_MESSAGES = {
43
+ SUCCESS: "Success",
44
+ CREATED: "Created successfully",
45
+ ACCEPTED: "Request accepted",
46
+ NO_CONTENT: "No content",
47
+ UPDATED: "Updated successfully",
48
+ DELETED: "Deleted successfully",
49
+ PARTIAL_SUCCESS: "Partially successful",
50
+ REDIRECTING: "Redirecting",
51
+ FILE_READY: "File ready for download"
52
+ };
53
+
54
+ // src/builders/response.builder.ts
55
+ var ResponseBuilder = class {
56
+ static {
57
+ __name(this, "ResponseBuilder");
58
+ }
59
+ /**
60
+ * Build a success response
61
+ * @template T - Type of response data
62
+ * @param data - Response data
63
+ * @param message - Success message
64
+ * @param statusCode - HTTP status code
65
+ * @param requestId - Request tracking ID for correlation
66
+ * @returns ApiResponse with success data
67
+ */
68
+ static success(data, message = RESPONSE_MESSAGES.SUCCESS, statusCode = HTTP_STATUS.OK, requestId) {
69
+ return {
70
+ success: true,
71
+ statusCode,
72
+ message,
73
+ data,
74
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
75
+ requestId
76
+ };
77
+ }
78
+ /**
79
+ * Shorthand success response with no data payload (e.g. status-only endpoints)
80
+ * @param message - Success message
81
+ * @param statusCode - HTTP status code
82
+ * @param requestId - Request tracking ID for correlation
83
+ * @returns ApiResponse with no data field
84
+ */
85
+ static ok(message = RESPONSE_MESSAGES.SUCCESS, statusCode = HTTP_STATUS.OK, requestId) {
86
+ return {
87
+ success: true,
88
+ statusCode,
89
+ message,
90
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
91
+ requestId
92
+ };
93
+ }
94
+ /**
95
+ * Build an error response
96
+ * @param message - Error message
97
+ * @param code - Error code for categorization
98
+ * @param statusCode - HTTP status code
99
+ * @param details - Additional error details
100
+ * @param requestId - Request tracking ID for correlation
101
+ * @returns ApiResponse with error information
102
+ */
103
+ static error(message, code, statusCode = HTTP_STATUS.BAD_REQUEST, details, requestId) {
104
+ return {
105
+ success: false,
106
+ statusCode,
107
+ message,
108
+ error: {
109
+ code,
110
+ details
111
+ },
112
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
113
+ requestId
114
+ };
115
+ }
116
+ /**
117
+ * Build a paginated response
118
+ * @template T - Type of array items
119
+ * @param data - Array of items
120
+ * @param total - Total number of items available
121
+ * @param page - Current page number
122
+ * @param limit - Items per page
123
+ * @param message - Success message
124
+ * @param statusCode - HTTP status code
125
+ * @param requestId - Request tracking ID for correlation
126
+ * @returns PaginatedResponse with pagination metadata
127
+ */
128
+ static paginated(data, total, page, limit, message = RESPONSE_MESSAGES.SUCCESS, statusCode = HTTP_STATUS.OK, requestId) {
129
+ const pages = Math.ceil(total / limit);
130
+ return {
131
+ success: true,
132
+ statusCode,
133
+ message,
134
+ data,
135
+ pagination: {
136
+ total,
137
+ page,
138
+ limit,
139
+ pages
140
+ },
141
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
142
+ requestId
143
+ };
144
+ }
145
+ /**
146
+ * Build a cursor-based paginated response for infinite-scroll / "load more" patterns
147
+ * @template T - Type of array items
148
+ * @param data - Array of items for the current page
149
+ * @param total - Total number of items available
150
+ * @param nextCursor - Cursor string for the next page
151
+ * @param hasNextPage - Whether more items exist after this page
152
+ * @param limit - Items per page
153
+ * @param message - Success message
154
+ * @param statusCode - HTTP status code
155
+ * @param requestId - Request tracking ID for correlation
156
+ * @returns CursorPaginatedResponse with cursor metadata
157
+ */
158
+ static cursorPaginated(data, total, nextCursor, hasNextPage, limit, previousCursor, hasPreviousPage, message = RESPONSE_MESSAGES.SUCCESS, statusCode = HTTP_STATUS.OK, requestId) {
159
+ return {
160
+ success: true,
161
+ statusCode,
162
+ message,
163
+ data,
164
+ pagination: {
165
+ total,
166
+ nextCursor,
167
+ previousCursor,
168
+ hasNextPage,
169
+ hasPreviousPage: hasPreviousPage ?? false,
170
+ limit
171
+ },
172
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
173
+ requestId
174
+ };
175
+ }
176
+ /**
177
+ * Build a created (201) response
178
+ * @template T - Type of response data
179
+ * @param data - Created resource data
180
+ * @param message - Success message
181
+ * @param requestId - Request tracking ID for correlation
182
+ * @returns ApiResponse with 201 status code
183
+ */
184
+ static created(data, message = RESPONSE_MESSAGES.CREATED, requestId) {
185
+ return this.success(data, message, HTTP_STATUS.CREATED, requestId);
186
+ }
187
+ /**
188
+ * Build an accepted (202) response for async operations
189
+ * @template T - Type of response data
190
+ * @param data - Response data (optional)
191
+ * @param message - Success message
192
+ * @param requestId - Request tracking ID for correlation
193
+ * @returns ApiResponse with 202 status code
194
+ */
195
+ static accepted(data, message = RESPONSE_MESSAGES.ACCEPTED, requestId) {
196
+ return this.success(data, message, HTTP_STATUS.ACCEPTED, requestId);
197
+ }
198
+ /**
199
+ * Build a no content (204) response
200
+ * @param message - Success message
201
+ * @param requestId - Request tracking ID for correlation
202
+ * @returns ApiResponse with 204 status code
203
+ */
204
+ static noContent(message = RESPONSE_MESSAGES.NO_CONTENT, requestId) {
205
+ return {
206
+ success: true,
207
+ statusCode: HTTP_STATUS.NO_CONTENT,
208
+ message,
209
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
210
+ requestId
211
+ };
212
+ }
213
+ /**
214
+ * Response for successful update operations
215
+ * @param data - Updated resource data
216
+ * @param message - Success message
217
+ * @param requestId - Request tracking ID
218
+ */
219
+ static updated(data, message = RESPONSE_MESSAGES.UPDATED, requestId) {
220
+ return this.success(data, message, HTTP_STATUS.OK, requestId);
221
+ }
222
+ /**
223
+ * Response for successful delete operations
224
+ * @param message - Success message
225
+ * @param requestId - Request tracking ID
226
+ */
227
+ static deleted(message = RESPONSE_MESSAGES.DELETED, requestId) {
228
+ return {
229
+ success: true,
230
+ statusCode: HTTP_STATUS.OK,
231
+ message,
232
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
233
+ requestId
234
+ };
235
+ }
236
+ /**
237
+ * Response for bulk operations (create, update, delete multiple)
238
+ * @param succeeded - Number of successful operations
239
+ * @param failed - Number of failed operations
240
+ * @param total - Total operations attempted
241
+ * @param requestId - Request tracking ID
242
+ */
243
+ static bulkOperation(succeeded, failed, total, requestId) {
244
+ const message = failed === 0 ? `All ${total} operations completed successfully` : `${succeeded} succeeded, ${failed} failed out of ${total} operations`;
245
+ return {
246
+ success: failed === 0,
247
+ statusCode: failed === 0 ? HTTP_STATUS.OK : HTTP_STATUS.MULTI_STATUS,
248
+ message,
249
+ data: {
250
+ succeeded,
251
+ failed,
252
+ total
253
+ },
254
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
255
+ requestId
256
+ };
257
+ }
258
+ /**
259
+ * Response for partial success scenarios
260
+ * @param data - Partial data that was processed
261
+ * @param message - Message describing the partial success
262
+ * @param requestId - Request tracking ID
263
+ */
264
+ static partialSuccess(data, message = RESPONSE_MESSAGES.PARTIAL_SUCCESS, requestId) {
265
+ return {
266
+ success: true,
267
+ statusCode: HTTP_STATUS.MULTI_STATUS,
268
+ message,
269
+ data,
270
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
271
+ requestId
272
+ };
273
+ }
274
+ /**
275
+ * Response for redirect operations
276
+ * @param url - Redirect URL
277
+ * @param statusCode - HTTP status code (301, 302, 307, 308)
278
+ * @param message - Redirect message
279
+ * @param requestId - Request tracking ID
280
+ */
281
+ static redirect(url, statusCode = HTTP_STATUS.FOUND, message = RESPONSE_MESSAGES.REDIRECTING, requestId) {
282
+ return {
283
+ success: true,
284
+ statusCode,
285
+ message,
286
+ data: { url },
287
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
288
+ requestId
289
+ };
290
+ }
291
+ /**
292
+ * Response for file download operations
293
+ * @param filename - Name of the file being downloaded
294
+ * @param size - File size in bytes
295
+ * @param mimeType - MIME type of the file
296
+ * @param requestId - Request tracking ID
297
+ */
298
+ static fileDownload(filename, size, mimeType, requestId) {
299
+ return {
300
+ success: true,
301
+ statusCode: HTTP_STATUS.OK,
302
+ message: RESPONSE_MESSAGES.FILE_READY,
303
+ data: {
304
+ filename,
305
+ size,
306
+ mimeType
307
+ },
308
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
309
+ requestId
310
+ };
311
+ }
312
+ /**
313
+ * Response for import/export operations
314
+ * @param processed - Number of records processed
315
+ * @param imported - Number of records imported
316
+ * @param skipped - Number of records skipped
317
+ * @param errors - Array of error messages
318
+ * @param requestId - Request tracking ID
319
+ */
320
+ static importExport(processed, imported, skipped, errors = [], requestId) {
321
+ const hasErrors = errors.length > 0;
322
+ const message = hasErrors ? `Imported ${imported} records with ${errors.length} errors` : `Successfully imported ${imported} records`;
323
+ return {
324
+ success: !hasErrors,
325
+ statusCode: hasErrors ? HTTP_STATUS.MULTI_STATUS : HTTP_STATUS.OK,
326
+ message,
327
+ data: {
328
+ processed,
329
+ imported,
330
+ skipped,
331
+ errors: hasErrors ? errors : void 0
332
+ },
333
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
334
+ requestId
335
+ };
336
+ }
337
+ };
338
+ var ERROR_TITLES = {
339
+ 400: "Bad Request",
340
+ 401: "Unauthorized",
341
+ 403: "Forbidden",
342
+ 404: "Page Not Found",
343
+ 405: "Method Not Allowed",
344
+ 408: "Request Timeout",
345
+ 409: "Conflict",
346
+ 410: "Gone",
347
+ 422: "Unprocessable Entity",
348
+ 429: "Too Many Requests",
349
+ 500: "Internal Server Error",
350
+ 502: "Bad Gateway",
351
+ 503: "Service Unavailable",
352
+ 504: "Gateway Timeout"
353
+ };
354
+ var ERROR_DESCRIPTIONS = {
355
+ 400: "The request could not be processed due to invalid data. Please check your input and try again.",
356
+ 401: "You need to be signed in to access this page. Please sign in and try again.",
357
+ 403: "You don't have permission to access this page. If you believe this is a mistake, contact support.",
358
+ 404: "The page you're looking for doesn't exist or has been moved. Check the URL and try again.",
359
+ 405: "This page does not support the request method used.",
360
+ 408: "The server timed out waiting for your request. Please try again.",
361
+ 409: "The request could not be completed due to a conflict with the current state of the resource.",
362
+ 422: "The submitted data could not be processed. Please review your input and try again.",
363
+ 429: "Too many requests. Please wait a moment before trying again.",
364
+ 500: "Something went wrong on our end. Please try again later.",
365
+ 502: "The server received an invalid response from an upstream server. Please try again later.",
366
+ 503: "The server is temporarily unavailable. Please try again later.",
367
+ 504: "The server timed out waiting for an upstream server. Please try again later."
368
+ };
369
+ function getErrorTitle(statusCode) {
370
+ return ERROR_TITLES[statusCode] || "Something Went Wrong";
371
+ }
372
+ __name(getErrorTitle, "getErrorTitle");
373
+ function getErrorDescription(statusCode) {
374
+ return ERROR_DESCRIPTIONS[statusCode] || "An unexpected error occurred. Please try again later.";
375
+ }
376
+ __name(getErrorDescription, "getErrorDescription");
377
+ function isBrowserRequest(request) {
378
+ const accept = request.headers?.accept;
379
+ return typeof accept === "string" && accept.includes("text/html");
380
+ }
381
+ __name(isBrowserRequest, "isBrowserRequest");
382
+ var GlobalExceptionFilter = class {
383
+ logger = new Logger(GlobalExceptionFilter.name);
384
+ catch(exception, host) {
385
+ const ctx = host.switchToHttp();
386
+ const response = ctx.getResponse();
387
+ const request = ctx.getRequest();
388
+ const requestId = request.headers["x-request-id"];
389
+ let statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
390
+ let message = "Internal server error";
391
+ let errorCode = "INTERNAL_ERROR";
392
+ let details = void 0;
393
+ if (exception instanceof HttpException) {
394
+ statusCode = exception.getStatus();
395
+ const exceptionResponse = exception.getResponse();
396
+ if (typeof exceptionResponse === "object") {
397
+ const responseObj = exceptionResponse;
398
+ message = responseObj.message || exception.message;
399
+ details = responseObj.error || responseObj.errors;
400
+ errorCode = responseObj.code || exception.constructor.name.replace("Exception", "").toUpperCase();
401
+ if (exception instanceof BadRequestException && !responseObj.code) {
402
+ errorCode = "VALIDATION_ERROR";
403
+ message = "Validation failed";
404
+ }
405
+ } else {
406
+ message = exceptionResponse;
407
+ errorCode = exception.constructor.name.replace("Exception", "").toUpperCase();
408
+ }
409
+ } else if (exception instanceof Error) {
410
+ message = exception.message;
411
+ errorCode = exception.name || "ERROR";
412
+ }
413
+ const errorResponse = {
414
+ success: false,
415
+ statusCode,
416
+ message,
417
+ error: {
418
+ code: errorCode,
419
+ details
420
+ },
421
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
422
+ requestId
423
+ };
424
+ const logContext = {
425
+ statusCode,
426
+ message,
427
+ method: request.method,
428
+ url: request.url,
429
+ ip: request.ip,
430
+ userAgent: request.headers["user-agent"],
431
+ errorCode
432
+ };
433
+ if (statusCode >= 500) {
434
+ this.logger.error(`[${requestId}] ${errorCode}:`, logContext);
435
+ } else if (statusCode >= 400) {
436
+ this.logger.warn(`[${requestId}] ${errorCode}:`, logContext);
437
+ } else {
438
+ this.logger.debug(`[${requestId}] ${errorCode}:`, logContext);
439
+ }
440
+ if (response.headersSent) return;
441
+ if (isBrowserRequest(request)) {
442
+ const showMessage = statusCode < 500 || process.env.NODE_ENV !== "production";
443
+ const renderOptions = {
444
+ statusCode,
445
+ title: getErrorTitle(statusCode),
446
+ description: getErrorDescription(statusCode),
447
+ message: showMessage ? message : "Something went wrong. Please try again later.",
448
+ errorCode,
449
+ showMessage
450
+ };
451
+ response.setHeader(
452
+ "Content-Security-Policy",
453
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'"
454
+ );
455
+ response.status(statusCode);
456
+ response.render("error", renderOptions, (renderErr, html) => {
457
+ if (renderErr) {
458
+ this.logger.error(`[${requestId}] Failed to render error page:`, renderErr);
459
+ return response.json(errorResponse);
460
+ }
461
+ response.send(html);
462
+ });
463
+ } else {
464
+ response.status(statusCode).json(errorResponse);
465
+ }
466
+ }
467
+ };
468
+ __name(GlobalExceptionFilter, "GlobalExceptionFilter");
469
+ GlobalExceptionFilter = __decorateClass([
470
+ Catch()
471
+ ], GlobalExceptionFilter);
472
+ var ValidationExceptionFilter = class {
473
+ logger = new Logger(ValidationExceptionFilter.name);
474
+ catch(exception, host) {
475
+ const ctx = host.switchToHttp();
476
+ const response = ctx.getResponse();
477
+ const request = ctx.getRequest();
478
+ const requestId = request.headers["x-request-id"];
479
+ const exceptionResponse = exception.getResponse();
480
+ const rawErrors = exceptionResponse.message;
481
+ const errors = Array.isArray(rawErrors) ? rawErrors : typeof rawErrors === "string" ? [rawErrors] : [];
482
+ const formattedErrors = this.formatValidationErrors(errors);
483
+ const errorResponse = {
484
+ success: false,
485
+ statusCode: 400,
486
+ message: "Validation failed",
487
+ error: {
488
+ code: "VALIDATION_ERROR",
489
+ details: formattedErrors
490
+ },
491
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
492
+ requestId
493
+ };
494
+ this.logger.warn(`[${requestId}] Validation error:`, {
495
+ method: request.method,
496
+ url: request.url,
497
+ errors: formattedErrors
498
+ });
499
+ response.status(400).json(errorResponse);
500
+ }
501
+ /**
502
+ * Format validation errors from class-validator
503
+ */
504
+ formatValidationErrors(errors) {
505
+ const messages = [];
506
+ if (Array.isArray(errors)) {
507
+ errors.forEach((error) => {
508
+ if (typeof error === "string") {
509
+ messages.push(error);
510
+ } else if (error?.constraints) {
511
+ messages.push(...Object.values(error.constraints));
512
+ }
513
+ });
514
+ }
515
+ return messages;
516
+ }
517
+ };
518
+ __name(ValidationExceptionFilter, "ValidationExceptionFilter");
519
+ ValidationExceptionFilter = __decorateClass([
520
+ Catch(BadRequestException)
521
+ ], ValidationExceptionFilter);
522
+ var HttpExceptionFilter = class {
523
+ logger = new Logger(HttpExceptionFilter.name);
524
+ catch(exception, host) {
525
+ const ctx = host.switchToHttp();
526
+ const response = ctx.getResponse();
527
+ const request = ctx.getRequest();
528
+ const requestId = request.headers["x-request-id"];
529
+ const statusCode = exception.getStatus();
530
+ const exceptionResponse = exception.getResponse();
531
+ const message = typeof exceptionResponse === "string" ? exceptionResponse : exceptionResponse.message || exception.message;
532
+ const errorCode = this.getErrorCode(exception, statusCode);
533
+ const errorResponse = {
534
+ success: false,
535
+ statusCode,
536
+ message,
537
+ error: {
538
+ code: errorCode,
539
+ details: exceptionResponse.error || exceptionResponse.errors
540
+ },
541
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
542
+ requestId
543
+ };
544
+ const logContext = {
545
+ statusCode,
546
+ message,
547
+ method: request.method,
548
+ url: request.url,
549
+ ip: request.ip
550
+ };
551
+ if (statusCode >= 500) {
552
+ this.logger.error(`[${requestId}] ${errorCode}:`, logContext);
553
+ } else if (statusCode >= 400) {
554
+ this.logger.warn(`[${requestId}] ${errorCode}:`, logContext);
555
+ }
556
+ response.status(statusCode).json(errorResponse);
557
+ }
558
+ /**
559
+ * Get error code from exception type
560
+ */
561
+ getErrorCode(exception, statusCode) {
562
+ const exceptionName = exception.constructor.name;
563
+ const errorCodeMap = {
564
+ 400: "BAD_REQUEST",
565
+ 401: "UNAUTHORIZED",
566
+ 403: "FORBIDDEN",
567
+ 404: "NOT_FOUND",
568
+ 409: "CONFLICT",
569
+ 422: "UNPROCESSABLE_ENTITY",
570
+ 429: "TOO_MANY_REQUESTS",
571
+ 500: "INTERNAL_SERVER_ERROR",
572
+ 502: "BAD_GATEWAY",
573
+ 503: "SERVICE_UNAVAILABLE"
574
+ };
575
+ return errorCodeMap[statusCode] || exceptionName.replace("Exception", "").toUpperCase();
576
+ }
577
+ };
578
+ __name(HttpExceptionFilter, "HttpExceptionFilter");
579
+ HttpExceptionFilter = __decorateClass([
580
+ Catch(HttpException)
581
+ ], HttpExceptionFilter);
582
+ var DatabaseExceptionFilter = class {
583
+ logger = new Logger(DatabaseExceptionFilter.name);
584
+ catch(exception, host) {
585
+ const ctx = host.switchToHttp();
586
+ const response = ctx.getResponse();
587
+ const request = ctx.getRequest();
588
+ const requestId = request.headers["x-request-id"];
589
+ if (!this.isDatabaseError(exception)) {
590
+ throw exception;
591
+ }
592
+ const { statusCode, message, errorCode } = this.parseError(exception);
593
+ const errorResponse = {
594
+ success: false,
595
+ statusCode,
596
+ message,
597
+ error: {
598
+ code: errorCode
599
+ },
600
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
601
+ requestId
602
+ };
603
+ this.logger.error(`[${requestId}] Database error:`, {
604
+ statusCode,
605
+ message,
606
+ errorCode,
607
+ method: request.method,
608
+ url: request.url,
609
+ // Log full error details in development only
610
+ ...process.env.NODE_ENV !== "production" && { details: exception.message }
611
+ });
612
+ response.status(statusCode).json(errorResponse);
613
+ }
614
+ /**
615
+ * Check if exception is a database error
616
+ */
617
+ isDatabaseError(exception) {
618
+ const errorName = exception.constructor.name;
619
+ const errorMessage = exception.message || "";
620
+ if (errorName.includes("QueryFailedError") || errorName.includes("EntityNotFound")) {
621
+ return true;
622
+ }
623
+ if (errorName.includes("PrismaClientKnownRequestError") || errorName.includes("PrismaClientValidationError")) {
624
+ return true;
625
+ }
626
+ if (errorMessage.includes("UNIQUE constraint failed") || errorMessage.includes("Foreign key constraint") || errorMessage.includes("Duplicate entry") || errorMessage.includes("violates unique constraint")) {
627
+ return true;
628
+ }
629
+ return false;
630
+ }
631
+ /**
632
+ * Parse database error and return user-friendly message
633
+ */
634
+ parseError(exception) {
635
+ const errorMessage = exception.message || "";
636
+ const errorCode = exception.code || "DATABASE_ERROR";
637
+ if (errorMessage.includes("UNIQUE constraint failed") || errorMessage.includes("Duplicate entry") || errorMessage.includes("violates unique constraint") || errorCode === "P2002") {
638
+ return {
639
+ statusCode: 409,
640
+ message: "This record already exists",
641
+ errorCode: "DUPLICATE_ENTRY"
642
+ };
643
+ }
644
+ if (errorMessage.includes("Foreign key constraint") || errorMessage.includes("FOREIGN KEY constraint failed") || errorCode === "P2003") {
645
+ return {
646
+ statusCode: 400,
647
+ message: "Invalid reference to related record",
648
+ errorCode: "INVALID_REFERENCE"
649
+ };
650
+ }
651
+ if (errorMessage.includes("No entity found") || errorCode === "P2025") {
652
+ return {
653
+ statusCode: 404,
654
+ message: "Record not found",
655
+ errorCode: "NOT_FOUND"
656
+ };
657
+ }
658
+ if (errorMessage.includes("Validation failed") || errorCode === "P2007") {
659
+ return {
660
+ statusCode: 400,
661
+ message: "Invalid data provided",
662
+ errorCode: "VALIDATION_ERROR"
663
+ };
664
+ }
665
+ return {
666
+ statusCode: 500,
667
+ message: "Database operation failed",
668
+ errorCode: "DATABASE_ERROR"
669
+ };
670
+ }
671
+ };
672
+ __name(DatabaseExceptionFilter, "DatabaseExceptionFilter");
673
+ DatabaseExceptionFilter = __decorateClass([
674
+ Catch()
675
+ ], DatabaseExceptionFilter);
676
+ var AuthExceptionFilter = class {
677
+ logger = new Logger(AuthExceptionFilter.name);
678
+ catch(exception, host) {
679
+ const ctx = host.switchToHttp();
680
+ const response = ctx.getResponse();
681
+ const request = ctx.getRequest();
682
+ const requestId = request.headers["x-request-id"];
683
+ const statusCode = exception.getStatus();
684
+ const exceptionResponse = exception.getResponse();
685
+ const message = typeof exceptionResponse === "string" ? exceptionResponse : exceptionResponse.message || exception.message;
686
+ const errorCode = statusCode === 401 ? "UNAUTHORIZED" : "FORBIDDEN";
687
+ const errorResponse = {
688
+ success: false,
689
+ statusCode,
690
+ message,
691
+ error: {
692
+ code: errorCode
693
+ },
694
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
695
+ requestId
696
+ };
697
+ this.logger.warn(`[${requestId}] Auth error:`, {
698
+ statusCode,
699
+ message,
700
+ errorCode,
701
+ method: request.method,
702
+ url: request.url,
703
+ ip: request.ip
704
+ });
705
+ response.status(statusCode).json(errorResponse);
706
+ }
707
+ };
708
+ __name(AuthExceptionFilter, "AuthExceptionFilter");
709
+ AuthExceptionFilter = __decorateClass([
710
+ Catch(UnauthorizedException, ForbiddenException)
711
+ ], AuthExceptionFilter);
712
+ var SKIP_RESPONSE_WRAPPER = "SKIP_RESPONSE_WRAPPER";
713
+ var SkipResponseWrapper = /* @__PURE__ */ __name(() => SetMetadata(SKIP_RESPONSE_WRAPPER, true), "SkipResponseWrapper");
714
+ var RESPONSE_MESSAGE = "RESPONSE_MESSAGE";
715
+ var ResponseMessage = /* @__PURE__ */ __name((message) => SetMetadata(RESPONSE_MESSAGE, message), "ResponseMessage");
716
+ var responseContext = new AsyncLocalStorage();
717
+
718
+ // src/interceptors/response.interceptor.ts
719
+ var ResponseInterceptor = class {
720
+ constructor(reflector) {
721
+ this.reflector = reflector;
722
+ }
723
+ reflector;
724
+ intercept(context, next) {
725
+ const skip = this.reflector.getAllAndOverride(SKIP_RESPONSE_WRAPPER, [
726
+ context.getHandler(),
727
+ context.getClass()
728
+ ]);
729
+ if (skip) return next.handle();
730
+ const customMessage = this.reflector.getAllAndOverride(RESPONSE_MESSAGE, [
731
+ context.getHandler(),
732
+ context.getClass()
733
+ ]);
734
+ return next.handle().pipe(
735
+ map((data) => {
736
+ const response = context.switchToHttp().getResponse();
737
+ if (response.headersSent) return data;
738
+ if (data && typeof data === "object" && "success" in data && "statusCode" in data) {
739
+ return data;
740
+ }
741
+ const requestId = context.switchToHttp().getRequest().headers["x-request-id"] ?? responseContext.getStore()?.requestId;
742
+ return ResponseBuilder.success(data, customMessage ?? "Success", response.statusCode, requestId);
743
+ })
744
+ );
745
+ }
746
+ };
747
+ __name(ResponseInterceptor, "ResponseInterceptor");
748
+ ResponseInterceptor = __decorateClass([
749
+ Injectable()
750
+ ], ResponseInterceptor);
751
+ var RequestContextInterceptor = class {
752
+ intercept(context, next) {
753
+ const request = context.switchToHttp().getRequest();
754
+ const requestId = request.headers["x-request-id"] ?? randomUUID();
755
+ return responseContext.run(
756
+ { requestId, path: request.path, method: request.method },
757
+ () => next.handle()
758
+ );
759
+ }
760
+ };
761
+ __name(RequestContextInterceptor, "RequestContextInterceptor");
762
+ RequestContextInterceptor = __decorateClass([
763
+ Injectable()
764
+ ], RequestContextInterceptor);
765
+ var PaginatedQueryDto = class {
766
+ static {
767
+ __name(this, "PaginatedQueryDto");
768
+ }
769
+ page = 1;
770
+ limit = 20;
771
+ };
772
+ __decorateClass([
773
+ IsOptional(),
774
+ Type(() => Number),
775
+ IsInt(),
776
+ Min(1)
777
+ ], PaginatedQueryDto.prototype, "page", 2);
778
+ __decorateClass([
779
+ IsOptional(),
780
+ Type(() => Number),
781
+ IsInt(),
782
+ Min(1),
783
+ Max(100)
784
+ ], PaginatedQueryDto.prototype, "limit", 2);
785
+ var CursorPaginatedQueryDto = class {
786
+ static {
787
+ __name(this, "CursorPaginatedQueryDto");
788
+ }
789
+ cursor;
790
+ limit = 20;
791
+ };
792
+ __decorateClass([
793
+ IsOptional(),
794
+ IsString()
795
+ ], CursorPaginatedQueryDto.prototype, "cursor", 2);
796
+ __decorateClass([
797
+ IsOptional(),
798
+ Type(() => Number),
799
+ IsInt(),
800
+ Min(1),
801
+ Max(100)
802
+ ], CursorPaginatedQueryDto.prototype, "limit", 2);
803
+ var ApiResponseEnvelope = /* @__PURE__ */ __name((model) => applyDecorators(
804
+ ApiExtraModels(model),
805
+ ApiOkResponse({
806
+ schema: {
807
+ type: "object",
808
+ properties: {
809
+ success: { type: "boolean" },
810
+ statusCode: { type: "number" },
811
+ message: { type: "string" },
812
+ data: { $ref: getSchemaPath(model) },
813
+ error: {
814
+ type: "object",
815
+ properties: {
816
+ code: { type: "string" },
817
+ details: { type: "string" }
818
+ }
819
+ },
820
+ timestamp: { type: "string", format: "date-time" },
821
+ requestId: { type: "string" }
822
+ }
823
+ }
824
+ })
825
+ ), "ApiResponseEnvelope");
826
+
827
+ // src/validation/validation-formatter.ts
828
+ function formatValidationErrors(errors) {
829
+ return errors.map((error) => {
830
+ const formatted = {
831
+ field: error.property,
832
+ constraints: error.constraints ? Object.values(error.constraints) : []
833
+ };
834
+ if (error.children?.length) {
835
+ formatted.children = formatValidationErrors(error.children);
836
+ }
837
+ return formatted;
838
+ });
839
+ }
840
+ __name(formatValidationErrors, "formatValidationErrors");
841
+ var __filename$1 = fileURLToPath(import.meta.url);
842
+ var __dirname$1 = dirname(__filename$1);
843
+ var ERROR_VIEWS_DIR = join(__dirname$1, "views");
844
+ var FILTER_MAP = {
845
+ GLOBAL: { provide: APP_FILTER, useClass: GlobalExceptionFilter },
846
+ VALIDATION: { provide: APP_FILTER, useClass: ValidationExceptionFilter },
847
+ HTTP: { provide: APP_FILTER, useClass: HttpExceptionFilter },
848
+ DATABASE: { provide: APP_FILTER, useClass: DatabaseExceptionFilter },
849
+ AUTH: { provide: APP_FILTER, useClass: AuthExceptionFilter }
850
+ };
851
+ var ResponseModule = class {
852
+ static forRoot(options) {
853
+ const providers = [];
854
+ const filterKeys = options?.filters ?? Object.keys(FILTER_MAP);
855
+ for (const key of filterKeys) {
856
+ providers.push(FILTER_MAP[key]);
857
+ }
858
+ if (options?.enableRequestContext) {
859
+ providers.push({
860
+ provide: APP_INTERCEPTOR,
861
+ useClass: RequestContextInterceptor
862
+ });
863
+ }
864
+ if (options?.enableResponseWrapper) {
865
+ providers.push({
866
+ provide: APP_INTERCEPTOR,
867
+ useClass: ResponseInterceptor
868
+ });
869
+ }
870
+ return {
871
+ module: ResponseModule,
872
+ providers
873
+ };
874
+ }
875
+ };
876
+ __name(ResponseModule, "ResponseModule");
877
+ ResponseModule = __decorateClass([
878
+ Module({})
879
+ ], ResponseModule);
880
+
881
+ export { ApiResponseEnvelope, AuthExceptionFilter, CursorPaginatedQueryDto, DatabaseExceptionFilter, ERROR_VIEWS_DIR, GlobalExceptionFilter, HTTP_STATUS, HttpExceptionFilter, PaginatedQueryDto, RESPONSE_FILTERS, RESPONSE_MESSAGE, RESPONSE_MESSAGES, RequestContextInterceptor, ResponseBuilder, ResponseInterceptor, ResponseMessage, ResponseModule, SKIP_RESPONSE_WRAPPER, SkipResponseWrapper, ValidationExceptionFilter, formatValidationErrors, responseContext };
882
+ //# sourceMappingURL=index.js.map
883
+ //# sourceMappingURL=index.js.map