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