@saihu/common 1.1.92 → 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.
- package/dist/errors/domain.error.d.ts +11 -0
- package/dist/errors/domain.error.d.ts.map +1 -0
- package/dist/errors/domain.error.js +23 -0
- package/dist/errors/index.d.ts +3 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +3 -1
- package/dist/errors/infrastructure.error.d.ts +9 -0
- package/dist/errors/infrastructure.error.d.ts.map +1 -0
- package/dist/errors/infrastructure.error.js +23 -0
- package/dist/errors/unified-exception.filter.d.ts +24 -0
- package/dist/errors/unified-exception.filter.d.ts.map +1 -0
- package/dist/errors/unified-exception.filter.js +153 -0
- package/package.json +1 -1
|
@@ -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;
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC"}
|
|
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"}
|
package/dist/errors/index.js
CHANGED
|
@@ -15,4 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./application.error"), exports);
|
|
18
|
-
__exportStar(require("./
|
|
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);
|