@saihu/common 1.1.91 → 1.1.92

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,3 @@
1
+ export * from './application.error';
2
+ export * from './application-error.filter';
3
+ //# 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,4BAA4B,CAAC"}
@@ -0,0 +1,18 @@
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("./application-error.filter"), exports);
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.92",
4
4
  "description": "Common utilities for NestJS applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",