@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.
@@ -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;
@@ -1,3 +1,5 @@
1
1
  export * from './application.error';
2
- export * from './application-error.filter';
2
+ export * from './domain.error';
3
+ export * from './infrastructure.error';
4
+ export * from './unified-exception.filter';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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("./application-error.filter"), 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saihu/common",
3
- "version": "1.1.92",
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",