@saihu/common 1.1.91 → 1.1.93

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.
@@ -0,0 +1,24 @@
1
+ import { ExceptionFilter, ArgumentsHost } from '@nestjs/common';
2
+ import { ApplicationError } from '../errors/application.error';
3
+ export declare class ApplicationErrorFilter implements ExceptionFilter {
4
+ private readonly logger;
5
+ private readonly SENSITIVE_KEYS;
6
+ private readonly MAX_DETAILS_LENGTH;
7
+ catch(exception: ApplicationError, host: ArgumentsHost): void;
8
+ private mapErrorCodeToHttpStatus;
9
+ /**
10
+ * Recursively sanitize sensitive information from details object
11
+ * Handles nested objects like { headers: { authorization: "..." } }
12
+ */
13
+ private sanitizeDetails;
14
+ /**
15
+ * Recursively sanitize an object
16
+ */
17
+ private sanitizeObject;
18
+ /**
19
+ * Truncate a string to a maximum length
20
+ * Adds "... (truncated)" suffix if truncated
21
+ */
22
+ private truncateString;
23
+ }
24
+ //# sourceMappingURL=application-error.filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-error.filter.d.ts","sourceRoot":"","sources":["../../src/errors/application-error.filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAS,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EAAE,gBAAgB,EAAc,MAAM,6BAA6B,CAAC;AAG3E,qBACa,sBAAuB,YAAW,eAAe;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2C;IAGlE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAS7B;IAGF,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAQ;IAE3C,KAAK,CAAC,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa;IA8DtD,OAAO,CAAC,wBAAwB;IA0BhC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAUvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAmCtB;;;OAGG;IACH,OAAO,CAAC,cAAc;CAQvB"}
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var ApplicationErrorFilter_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.ApplicationErrorFilter = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const application_error_1 = require("../errors/application.error");
13
+ const dto_1 = require("../dto");
14
+ let ApplicationErrorFilter = ApplicationErrorFilter_1 = class ApplicationErrorFilter {
15
+ constructor() {
16
+ this.logger = new common_1.Logger(ApplicationErrorFilter_1.name);
17
+ // Sensitive keys to redact from logs
18
+ this.SENSITIVE_KEYS = [
19
+ 'password',
20
+ 'token',
21
+ 'accessToken',
22
+ 'refreshToken',
23
+ 'authorization',
24
+ 'secret',
25
+ 'apiKey',
26
+ 'creditCard',
27
+ ];
28
+ // Maximum length for logged details to prevent log explosion
29
+ this.MAX_DETAILS_LENGTH = 1000;
30
+ }
31
+ catch(exception, host) {
32
+ // Guard: ensure we're in HTTP context
33
+ if (host.getType() !== 'http') {
34
+ this.logger.warn(`ApplicationErrorFilter called in non-HTTP context: ${host.getType()}`);
35
+ return;
36
+ }
37
+ const ctx = host.switchToHttp();
38
+ const response = ctx.getResponse();
39
+ // Additional guard: ensure response exists
40
+ if (!response) {
41
+ this.logger.warn('Response object not available in HTTP context');
42
+ return;
43
+ }
44
+ // Map application error codes to HTTP status codes
45
+ const statusCode = this.mapErrorCodeToHttpStatus(exception.code);
46
+ // Get the cause as Error type for proper access to properties
47
+ const cause = exception.cause;
48
+ // Redact sensitive information from details before logging
49
+ const sanitizedDetails = this.sanitizeDetails(exception.details);
50
+ // Stringify and truncate details to prevent log explosion
51
+ const detailsString = sanitizedDetails
52
+ ? this.truncateString(JSON.stringify(sanitizedDetails), this.MAX_DETAILS_LENGTH)
53
+ : null;
54
+ // Log the error with full context (sanitized and truncated)
55
+ const logMessage = [
56
+ `ApplicationError: ${exception.code}`,
57
+ `Message: ${exception.message}`,
58
+ detailsString ? `Details: ${detailsString}` : null,
59
+ cause ? `Cause: ${cause.message}` : null,
60
+ ]
61
+ .filter(Boolean)
62
+ .join(' | ');
63
+ this.logger.error(logMessage, exception.stack);
64
+ // Log cause stack separately if it exists
65
+ if (cause === null || cause === void 0 ? void 0 : cause.stack) {
66
+ this.logger.error(`Caused by: ${cause.stack}`);
67
+ }
68
+ // Create standardized response using BaseResponseDto for backward compatibility
69
+ const errorResponse = dto_1.BaseResponseDto.error(statusCode, exception.message);
70
+ // Extend with application error code for new clients
71
+ // Existing clients ignore this field, new clients can use it for specific handling
72
+ errorResponse.errorCode = exception.code;
73
+ response.status(statusCode).json(errorResponse);
74
+ }
75
+ mapErrorCodeToHttpStatus(code) {
76
+ // Explicit mappings for known error codes
77
+ const mapping = {
78
+ [application_error_1.ErrorCodes.DATA_SOURCE_UNAVAILABLE]: 503, // Service Unavailable
79
+ [application_error_1.ErrorCodes.DATA_SOURCE_AUTH_FAILED]: 502, // Bad Gateway (upstream auth failed)
80
+ [application_error_1.ErrorCodes.RESOURCE_NOT_FOUND]: 404, // Not Found
81
+ [application_error_1.ErrorCodes.INVALID_INPUT]: 400, // Bad Request
82
+ [application_error_1.ErrorCodes.EXTERNAL_SERVICE_ERROR]: 502, // Bad Gateway
83
+ [application_error_1.ErrorCodes.INTERNAL_ERROR]: 500, // Internal Server Error
84
+ };
85
+ // Return explicit mapping if it exists
86
+ if (code in mapping) {
87
+ return mapping[code];
88
+ }
89
+ // Smart defaults for unknown codes:
90
+ // - Codes starting with "INTERNAL" -> 500 (internal server errors)
91
+ // - Everything else -> 400 (safer default for business/validation errors)
92
+ if (code.startsWith('INTERNAL')) {
93
+ return 500;
94
+ }
95
+ return 400; // Default to 400 for unknown business/validation errors
96
+ }
97
+ /**
98
+ * Recursively sanitize sensitive information from details object
99
+ * Handles nested objects like { headers: { authorization: "..." } }
100
+ */
101
+ sanitizeDetails(details) {
102
+ if (!details) {
103
+ return undefined;
104
+ }
105
+ return this.sanitizeObject(details);
106
+ }
107
+ /**
108
+ * Recursively sanitize an object
109
+ */
110
+ sanitizeObject(obj) {
111
+ if (obj === null || obj === undefined) {
112
+ return obj;
113
+ }
114
+ // Handle arrays
115
+ if (Array.isArray(obj)) {
116
+ return obj.map((item) => this.sanitizeObject(item));
117
+ }
118
+ // Handle objects
119
+ if (typeof obj === 'object') {
120
+ const sanitized = {};
121
+ for (const key of Object.keys(obj)) {
122
+ const lowerKey = key.toLowerCase();
123
+ // Check if key contains sensitive information
124
+ if (this.SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive))) {
125
+ sanitized[key] = '[REDACTED]';
126
+ }
127
+ else {
128
+ // Recursively sanitize nested objects
129
+ sanitized[key] = this.sanitizeObject(obj[key]);
130
+ }
131
+ }
132
+ return sanitized;
133
+ }
134
+ // Return primitives as-is
135
+ return obj;
136
+ }
137
+ /**
138
+ * Truncate a string to a maximum length
139
+ * Adds "... (truncated)" suffix if truncated
140
+ */
141
+ truncateString(str, maxLength) {
142
+ if (str.length <= maxLength) {
143
+ return str;
144
+ }
145
+ const suffix = '... (truncated)';
146
+ return str.substring(0, maxLength - suffix.length) + suffix;
147
+ }
148
+ };
149
+ exports.ApplicationErrorFilter = ApplicationErrorFilter;
150
+ exports.ApplicationErrorFilter = ApplicationErrorFilter = ApplicationErrorFilter_1 = __decorate([
151
+ (0, common_1.Catch)(application_error_1.ApplicationError)
152
+ ], ApplicationErrorFilter);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Common error codes for convenience.
3
+ * You can use these or define your own domain-specific codes.
4
+ */
5
+ export declare const ErrorCodes: {
6
+ readonly DATA_SOURCE_UNAVAILABLE: "DATA_SOURCE_UNAVAILABLE";
7
+ readonly DATA_SOURCE_AUTH_FAILED: "DATA_SOURCE_AUTH_FAILED";
8
+ readonly RESOURCE_NOT_FOUND: "RESOURCE_NOT_FOUND";
9
+ readonly INVALID_INPUT: "INVALID_INPUT";
10
+ readonly EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR";
11
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
12
+ };
13
+ export declare class ApplicationError extends Error {
14
+ readonly code: string;
15
+ readonly details?: Record<string, any>;
16
+ readonly cause?: Error;
17
+ constructor(code: string, message: string, details?: Record<string, any>, cause?: Error);
18
+ static withCode(code: string, message: string, details?: Record<string, any>): ApplicationError;
19
+ static dataSourceUnavailable(message: string, cause?: Error): ApplicationError;
20
+ static dataSourceAuthFailed(message: string, details?: Record<string, any>): ApplicationError;
21
+ static notFound(resource: string, id: string | number): ApplicationError;
22
+ static invalidInput(message: string, details?: Record<string, any>): ApplicationError;
23
+ static externalServiceError(service: string, message: string, cause?: Error): ApplicationError;
24
+ static internalError(message: string, cause?: Error): ApplicationError;
25
+ }
26
+ //# sourceMappingURL=application.error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application.error.d.ts","sourceRoot":"","sources":["../../src/errors/application.error.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,UAAU;;;;;;;CAcb,CAAC;AAEX,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9C,SAAgB,KAAK,CAAC,EAAE,KAAK,CAAC;gBAG5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,KAAK,CAAC,EAAE,KAAK;IAYf,MAAM,CAAC,QAAQ,CACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,gBAAgB;IAKnB,MAAM,CAAC,qBAAqB,CAC1B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,KAAK,GACZ,gBAAgB;IASnB,MAAM,CAAC,oBAAoB,CACzB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,gBAAgB;IAQnB,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,gBAAgB;IAQxE,MAAM,CAAC,YAAY,CACjB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,gBAAgB;IAInB,MAAM,CAAC,oBAAoB,CACzB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,KAAK,GACZ,gBAAgB;IASnB,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,gBAAgB;CAQvE"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApplicationError = exports.ErrorCodes = void 0;
4
+ /**
5
+ * Common error codes for convenience.
6
+ * You can use these or define your own domain-specific codes.
7
+ */
8
+ exports.ErrorCodes = {
9
+ // Data source errors
10
+ DATA_SOURCE_UNAVAILABLE: 'DATA_SOURCE_UNAVAILABLE',
11
+ DATA_SOURCE_AUTH_FAILED: 'DATA_SOURCE_AUTH_FAILED',
12
+ // Business logic errors
13
+ RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND',
14
+ INVALID_INPUT: 'INVALID_INPUT',
15
+ // External service errors
16
+ EXTERNAL_SERVICE_ERROR: 'EXTERNAL_SERVICE_ERROR',
17
+ // Generic
18
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
19
+ };
20
+ class ApplicationError extends Error {
21
+ constructor(code, message, details, cause) {
22
+ // Store cause manually for ES2018 compatibility
23
+ super(message);
24
+ this.name = 'ApplicationError';
25
+ this.code = code;
26
+ this.details = details;
27
+ this.cause = cause;
28
+ Error.captureStackTrace(this, this.constructor);
29
+ }
30
+ // Generic factory - use this for any custom error code
31
+ static withCode(code, message, details) {
32
+ return new ApplicationError(code, message, details);
33
+ }
34
+ // Convenience factory methods for common error types
35
+ static dataSourceUnavailable(message, cause) {
36
+ return new ApplicationError(exports.ErrorCodes.DATA_SOURCE_UNAVAILABLE, message, undefined, cause);
37
+ }
38
+ static dataSourceAuthFailed(message, details) {
39
+ return new ApplicationError(exports.ErrorCodes.DATA_SOURCE_AUTH_FAILED, message, details);
40
+ }
41
+ static notFound(resource, id) {
42
+ return new ApplicationError(exports.ErrorCodes.RESOURCE_NOT_FOUND, `${resource} with id ${id} not found`, { resource, id });
43
+ }
44
+ static invalidInput(message, details) {
45
+ return new ApplicationError(exports.ErrorCodes.INVALID_INPUT, message, details);
46
+ }
47
+ static externalServiceError(service, message, cause) {
48
+ return new ApplicationError(exports.ErrorCodes.EXTERNAL_SERVICE_ERROR, `${service}: ${message}`, { service }, cause);
49
+ }
50
+ static internalError(message, cause) {
51
+ return new ApplicationError(exports.ErrorCodes.INTERNAL_ERROR, message, undefined, cause);
52
+ }
53
+ }
54
+ exports.ApplicationError = ApplicationError;
@@ -0,0 +1,11 @@
1
+ export declare abstract class DomainError extends Error {
2
+ readonly details?: Record<string, any>;
3
+ constructor(message: string, details?: Record<string, any>);
4
+ }
5
+ /**
6
+ * Example of a specific Domain Error
7
+ */
8
+ export declare class BusinessRuleViolationError extends DomainError {
9
+ constructor(message: string, details?: Record<string, any>);
10
+ }
11
+ //# sourceMappingURL=domain.error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain.error.d.ts","sourceRoot":"","sources":["../../src/errors/domain.error.ts"],"names":[],"mappings":"AAAA,8BAAsB,WAAY,SAAQ,KAAK;IAC7C,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;gBAElC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAQ3D;AAED;;GAEG;AACH,qBAAa,0BAA2B,SAAQ,WAAW;gBAC7C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAG3D"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BusinessRuleViolationError = exports.DomainError = void 0;
4
+ class DomainError extends Error {
5
+ constructor(message, details) {
6
+ super(message);
7
+ this.name = 'DomainError';
8
+ this.details = details;
9
+ // Standard practice for custom errors in TS
10
+ Object.setPrototypeOf(this, new.target.prototype);
11
+ Error.captureStackTrace(this, this.constructor);
12
+ }
13
+ }
14
+ exports.DomainError = DomainError;
15
+ /**
16
+ * Example of a specific Domain Error
17
+ */
18
+ class BusinessRuleViolationError extends DomainError {
19
+ constructor(message, details) {
20
+ super(message, details);
21
+ }
22
+ }
23
+ exports.BusinessRuleViolationError = BusinessRuleViolationError;
@@ -0,0 +1,5 @@
1
+ export * from './application.error';
2
+ export * from './domain.error';
3
+ export * from './infrastructure.error';
4
+ export * from './unified-exception.filter';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./application.error"), exports);
18
+ __exportStar(require("./domain.error"), exports);
19
+ __exportStar(require("./infrastructure.error"), exports);
20
+ __exportStar(require("./unified-exception.filter"), exports);
@@ -0,0 +1,9 @@
1
+ export declare class InfrastructureError extends Error {
2
+ readonly code: string;
3
+ readonly cause?: Error;
4
+ readonly details?: Record<string, any>;
5
+ constructor(code: string, message: string, cause?: Error, details?: Record<string, any>);
6
+ static databaseError(message: string, cause?: Error): InfrastructureError;
7
+ static networkError(message: string, cause?: Error): InfrastructureError;
8
+ }
9
+ //# sourceMappingURL=infrastructure.error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infrastructure.error.d.ts","sourceRoot":"","sources":["../../src/errors/infrastructure.error.ts"],"names":[],"mappings":"AAAA,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,KAAK,CAAC,EAAE,KAAK,CAAC;IAC9B,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;gBAG5C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,KAAK,EACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAY/B,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,mBAAmB;IAKzE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,mBAAmB;CAGzE"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InfrastructureError = void 0;
4
+ class InfrastructureError extends Error {
5
+ constructor(code, message, cause, details) {
6
+ super(message);
7
+ this.name = 'InfrastructureError';
8
+ this.code = code;
9
+ this.cause = cause;
10
+ this.details = details;
11
+ Object.setPrototypeOf(this, new.target.prototype);
12
+ Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ // Factory for DB Errors
15
+ static databaseError(message, cause) {
16
+ return new InfrastructureError('DATABASE_ERROR', message, cause);
17
+ }
18
+ // Factory for Network/External Service Errors (at infra level)
19
+ static networkError(message, cause) {
20
+ return new InfrastructureError('NETWORK_ERROR', message, cause);
21
+ }
22
+ }
23
+ exports.InfrastructureError = InfrastructureError;
@@ -0,0 +1,24 @@
1
+ import { ExceptionFilter, ArgumentsHost } from '@nestjs/common';
2
+ export declare class UnifiedExceptionFilter implements ExceptionFilter {
3
+ private readonly logger;
4
+ private readonly SENSITIVE_KEYS;
5
+ private readonly MAX_DETAILS_LENGTH;
6
+ catch(exception: any, host: ArgumentsHost): void;
7
+ private mapAppErrorCodeToHttpStatus;
8
+ private logError;
9
+ /**
10
+ * Recursively sanitize sensitive information from details object
11
+ * Handles nested objects like { headers: { authorization: "..." } }
12
+ */
13
+ private sanitizeDetails;
14
+ /**
15
+ * Recursively sanitize an object
16
+ */
17
+ private sanitizeObject;
18
+ /**
19
+ * Truncate a string to a maximum length
20
+ * Adds "... (truncated)" suffix if truncated
21
+ */
22
+ private truncateString;
23
+ }
24
+ //# sourceMappingURL=unified-exception.filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unified-exception.filter.d.ts","sourceRoot":"","sources":["../../src/errors/unified-exception.filter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEf,aAAa,EAGd,MAAM,gBAAgB,CAAC;AAOxB,qBACa,sBAAuB,YAAW,eAAe;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2C;IAElE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAM7B;IACF,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAQ;IAE3C,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa;IA+CzC,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,QAAQ;IA2BhB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAUvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAmCtB;;;OAGG;IACH,OAAO,CAAC,cAAc;CAQvB"}
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var UnifiedExceptionFilter_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.UnifiedExceptionFilter = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const domain_error_1 = require("./domain.error");
13
+ const application_error_1 = require("./application.error");
14
+ const infrastructure_error_1 = require("./infrastructure.error");
15
+ const dto_1 = require("../dto");
16
+ let UnifiedExceptionFilter = UnifiedExceptionFilter_1 = class UnifiedExceptionFilter {
17
+ constructor() {
18
+ this.logger = new common_1.Logger(UnifiedExceptionFilter_1.name);
19
+ this.SENSITIVE_KEYS = [
20
+ 'password',
21
+ 'token',
22
+ 'authorization',
23
+ 'secret',
24
+ 'apiKey',
25
+ ];
26
+ this.MAX_DETAILS_LENGTH = 1000;
27
+ }
28
+ catch(exception, host) {
29
+ if (host.getType() !== 'http')
30
+ return;
31
+ const ctx = host.switchToHttp();
32
+ const response = ctx.getResponse();
33
+ let statusCode = common_1.HttpStatus.INTERNAL_SERVER_ERROR;
34
+ let message = 'An unexpected error occurred';
35
+ let errorCode = 'INTERNAL_SERVER_ERROR';
36
+ let details = null;
37
+ // 1. Identify Error Type and Map Status/Message
38
+ if (exception instanceof domain_error_1.DomainError) {
39
+ statusCode = common_1.HttpStatus.BAD_REQUEST; // 400: Business Rule Violation
40
+ message = exception.message;
41
+ errorCode = 'DOMAIN_RULE_VIOLATION';
42
+ details = exception.details;
43
+ }
44
+ else if (exception instanceof application_error_1.ApplicationError) {
45
+ statusCode = this.mapAppErrorCodeToHttpStatus(exception.code);
46
+ message = exception.message;
47
+ errorCode = exception.code;
48
+ details = exception.details;
49
+ }
50
+ else if (exception instanceof infrastructure_error_1.InfrastructureError) {
51
+ statusCode = common_1.HttpStatus.INTERNAL_SERVER_ERROR; // 500: Technical failure
52
+ message = 'A technical error occurred in the subsystem'; // Don't leak DB details
53
+ errorCode = exception.code;
54
+ details = null; // Hide infra details from user
55
+ }
56
+ else if (exception.status) {
57
+ // Handle standard NestJS HttpExceptions (like 401 Unauthorized)
58
+ statusCode = exception.status;
59
+ message = exception.message;
60
+ }
61
+ // 2. Logging & Sanitization (Internal)
62
+ this.logError(exception, errorCode, message, details);
63
+ // 3. Construct Standardized Response using BaseResponseDto
64
+ const errorResponse = dto_1.BaseResponseDto.error(statusCode, message);
65
+ // Attach extra metadata for the frontend
66
+ errorResponse.errorCode = errorCode;
67
+ if (details)
68
+ errorResponse.details = this.sanitizeDetails(details);
69
+ errorResponse.timestamp = new Date().toISOString();
70
+ response.status(statusCode).json(errorResponse);
71
+ }
72
+ mapAppErrorCodeToHttpStatus(code) {
73
+ const mapping = {
74
+ [application_error_1.ErrorCodes.RESOURCE_NOT_FOUND]: 404,
75
+ [application_error_1.ErrorCodes.INVALID_INPUT]: 400,
76
+ [application_error_1.ErrorCodes.DATA_SOURCE_UNAVAILABLE]: 503,
77
+ [application_error_1.ErrorCodes.EXTERNAL_SERVICE_ERROR]: 502,
78
+ };
79
+ return mapping[code] || 400;
80
+ }
81
+ logError(exception, code, message, details) {
82
+ const cause = exception.cause;
83
+ const sanitizedDetails = details ? this.sanitizeDetails(details) : null;
84
+ const detailsString = sanitizedDetails
85
+ ? this.truncateString(JSON.stringify(sanitizedDetails), this.MAX_DETAILS_LENGTH)
86
+ : null;
87
+ const logMessage = [
88
+ `[${code}] ${message}`,
89
+ detailsString ? `Details: ${detailsString}` : null,
90
+ cause ? `Cause: ${cause.message}` : null,
91
+ ]
92
+ .filter(Boolean)
93
+ .join(' | ');
94
+ this.logger.error(logMessage, exception.stack);
95
+ if (cause === null || cause === void 0 ? void 0 : cause.stack)
96
+ this.logger.error(`Caused by: ${cause.stack}`);
97
+ }
98
+ /**
99
+ * Recursively sanitize sensitive information from details object
100
+ * Handles nested objects like { headers: { authorization: "..." } }
101
+ */
102
+ sanitizeDetails(details) {
103
+ if (!details) {
104
+ return undefined;
105
+ }
106
+ return this.sanitizeObject(details);
107
+ }
108
+ /**
109
+ * Recursively sanitize an object
110
+ */
111
+ sanitizeObject(obj) {
112
+ if (obj === null || obj === undefined) {
113
+ return obj;
114
+ }
115
+ // Handle arrays
116
+ if (Array.isArray(obj)) {
117
+ return obj.map((item) => this.sanitizeObject(item));
118
+ }
119
+ // Handle objects
120
+ if (typeof obj === 'object') {
121
+ const sanitized = {};
122
+ for (const key of Object.keys(obj)) {
123
+ const lowerKey = key.toLowerCase();
124
+ // Check if key contains sensitive information
125
+ if (this.SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive))) {
126
+ sanitized[key] = '[REDACTED]';
127
+ }
128
+ else {
129
+ // Recursively sanitize nested objects
130
+ sanitized[key] = this.sanitizeObject(obj[key]);
131
+ }
132
+ }
133
+ return sanitized;
134
+ }
135
+ // Return primitives as-is
136
+ return obj;
137
+ }
138
+ /**
139
+ * Truncate a string to a maximum length
140
+ * Adds "... (truncated)" suffix if truncated
141
+ */
142
+ truncateString(str, maxLength) {
143
+ if (str.length <= maxLength) {
144
+ return str;
145
+ }
146
+ const suffix = '... (truncated)';
147
+ return str.substring(0, maxLength - suffix.length) + suffix;
148
+ }
149
+ };
150
+ exports.UnifiedExceptionFilter = UnifiedExceptionFilter;
151
+ exports.UnifiedExceptionFilter = UnifiedExceptionFilter = UnifiedExceptionFilter_1 = __decorate([
152
+ (0, common_1.Catch)() // Catch everything to ensure NO raw errors leak out
153
+ ], UnifiedExceptionFilter);
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './util';
7
7
  export * from './logger';
8
8
  export * from './messaging';
9
9
  export * from './decorators';
10
+ export * from './errors';
10
11
  export { SaihuCommonModule } from './saihu-common.module';
11
12
  export { BaseService } from './services/base.service';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ __exportStar(require("./util"), exports);
24
24
  __exportStar(require("./logger"), exports);
25
25
  __exportStar(require("./messaging"), exports);
26
26
  __exportStar(require("./decorators"), exports);
27
+ __exportStar(require("./errors"), exports);
27
28
  var saihu_common_module_1 = require("./saihu-common.module");
28
29
  Object.defineProperty(exports, "SaihuCommonModule", { enumerable: true, get: function () { return saihu_common_module_1.SaihuCommonModule; } });
29
30
  var base_service_1 = require("./services/base.service");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saihu/common",
3
- "version": "1.1.91",
3
+ "version": "1.1.93",
4
4
  "description": "Common utilities for NestJS applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",