@noony-serverless/core 0.5.1 → 0.5.2
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.
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import { BaseMiddleware, Context } from '../core';
|
|
2
|
+
export interface ErrorCategory {
|
|
3
|
+
type: string;
|
|
4
|
+
userMessage: string;
|
|
5
|
+
httpStatus: number;
|
|
6
|
+
retryable: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ErrorMatcher {
|
|
9
|
+
/**
|
|
10
|
+
* Function to determine if this category applies to the error.
|
|
11
|
+
* Return true if the error matches this category.
|
|
12
|
+
*/
|
|
13
|
+
matches: (error: Error) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The category to return if the error matches.
|
|
16
|
+
*/
|
|
17
|
+
category: ErrorCategory;
|
|
18
|
+
}
|
|
19
|
+
export interface ErrorHandlerOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Custom error categories to check before default ones.
|
|
22
|
+
* These are evaluated in order.
|
|
23
|
+
*/
|
|
24
|
+
customCategories?: ErrorMatcher[];
|
|
25
|
+
}
|
|
2
26
|
/**
|
|
3
27
|
* Middleware class for handling errors in the application.
|
|
4
28
|
* Implements the `BaseMiddleware` interface and provides an asynchronous
|
|
@@ -9,12 +33,7 @@ import { BaseMiddleware, Context } from '../core';
|
|
|
9
33
|
*
|
|
10
34
|
* @remarks
|
|
11
35
|
* This middleware should be registered to catch and process errors that occur
|
|
12
|
-
* during request handling.
|
|
13
|
-
*
|
|
14
|
-
* @method onError
|
|
15
|
-
* @param error - The error object that was thrown.
|
|
16
|
-
* @param context - The context in which the error occurred.
|
|
17
|
-
* @returns A promise that resolves when error handling is complete.
|
|
36
|
+
* during request handling. It supports custom error categorization via options.
|
|
18
37
|
*
|
|
19
38
|
* @example
|
|
20
39
|
* Basic handler with error handling:
|
|
@@ -36,24 +55,32 @@ import { BaseMiddleware, Context } from '../core';
|
|
|
36
55
|
* ```
|
|
37
56
|
*
|
|
38
57
|
* @example
|
|
39
|
-
*
|
|
58
|
+
* Usage with custom categories:
|
|
40
59
|
* ```typescript
|
|
41
|
-
* import { http } from '@google-cloud/functions-framework';
|
|
42
60
|
* import { Handler, ErrorHandlerMiddleware } from '@noony-serverless/core';
|
|
43
61
|
*
|
|
44
|
-
* const
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
62
|
+
* const customCategories = [{
|
|
63
|
+
* matches: (err) => err.message.includes('payment_failed'),
|
|
64
|
+
* category: {
|
|
65
|
+
* type: 'PAYMENT_ERROR',
|
|
66
|
+
* userMessage: 'Payment processing failed',
|
|
67
|
+
* httpStatus: 402,
|
|
68
|
+
* retryable: false
|
|
69
|
+
* }
|
|
70
|
+
* }];
|
|
71
|
+
*
|
|
72
|
+
* const handler = new Handler()
|
|
73
|
+
* .use(new ErrorHandlerMiddleware({ customCategories }))
|
|
74
|
+
* .handle(...);
|
|
54
75
|
* ```
|
|
55
76
|
*/
|
|
56
77
|
export declare class ErrorHandlerMiddleware<TBody = unknown, TUser = unknown> implements BaseMiddleware<TBody, TUser> {
|
|
78
|
+
private options?;
|
|
79
|
+
/**
|
|
80
|
+
* Creates an instance of ErrorHandlerMiddleware.
|
|
81
|
+
* @param {ErrorHandlerOptions} [options] - Configuration options for the middleware, including custom error categories.
|
|
82
|
+
*/
|
|
83
|
+
constructor(options?: ErrorHandlerOptions | undefined);
|
|
57
84
|
onError(error: Error, context: Context<TBody, TUser>): Promise<void>;
|
|
58
85
|
}
|
|
59
86
|
/**
|
|
@@ -61,6 +88,7 @@ export declare class ErrorHandlerMiddleware<TBody = unknown, TUser = unknown> im
|
|
|
61
88
|
*
|
|
62
89
|
* @template TBody - The type of the request body payload (preserves type chain)
|
|
63
90
|
* @template TUser - The type of the authenticated user (preserves type chain)
|
|
91
|
+
* @param {ErrorHandlerOptions} [options] - Configuration options for the middleware.
|
|
64
92
|
* @returns {BaseMiddleware} An object implementing the `onError` method to handle errors.
|
|
65
93
|
*
|
|
66
94
|
* @remarks
|
|
@@ -75,18 +103,31 @@ export declare class ErrorHandlerMiddleware<TBody = unknown, TUser = unknown> im
|
|
|
75
103
|
* const loginHandler = new Handler()
|
|
76
104
|
* .use(errorHandler())
|
|
77
105
|
* .handle(async (request, context) => {
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* if (!username || !password) {
|
|
81
|
-
* throw new HttpError(400, 'Credentials required', 'MISSING_CREDENTIALS');
|
|
82
|
-
* }
|
|
83
|
-
*
|
|
84
|
-
* const user = await authenticateUser(username, password);
|
|
85
|
-
* return { success: true, data: { token: generateToken(user) } };
|
|
106
|
+
* // ...
|
|
86
107
|
* });
|
|
87
108
|
* ```
|
|
88
109
|
*
|
|
89
110
|
* @example
|
|
111
|
+
* Usage with custom categories:
|
|
112
|
+
* ```typescript
|
|
113
|
+
* import { Handler, errorHandler } from '@noony-serverless/core';
|
|
114
|
+
*
|
|
115
|
+
* const customCategories = [{
|
|
116
|
+
* matches: (err) => err.message.includes('custom'),
|
|
117
|
+
* category: {
|
|
118
|
+
* type: 'CUSTOM_ERROR',
|
|
119
|
+
* userMessage: 'A custom error occurred',
|
|
120
|
+
* httpStatus: 400,
|
|
121
|
+
* retryable: false
|
|
122
|
+
* }
|
|
123
|
+
* }];
|
|
124
|
+
*
|
|
125
|
+
* const handler = new Handler()
|
|
126
|
+
* .use(errorHandler({ customCategories }))
|
|
127
|
+
* .handle(...)
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
90
131
|
* Multiple middleware chain:
|
|
91
132
|
* ```typescript
|
|
92
133
|
* import { Handler, errorHandler, BodyParserMiddleware } from '@noony-serverless/core';
|
|
@@ -101,5 +142,5 @@ export declare class ErrorHandlerMiddleware<TBody = unknown, TUser = unknown> im
|
|
|
101
142
|
* });
|
|
102
143
|
* ```
|
|
103
144
|
*/
|
|
104
|
-
export declare const errorHandler: <TBody = unknown, TUser = unknown>() => BaseMiddleware<TBody, TUser>;
|
|
145
|
+
export declare const errorHandler: <TBody = unknown, TUser = unknown>(options?: ErrorHandlerOptions) => BaseMiddleware<TBody, TUser>;
|
|
105
146
|
//# sourceMappingURL=errorHandlerMiddleware.d.ts.map
|
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.errorHandler = exports.ErrorHandlerMiddleware = void 0;
|
|
4
4
|
const core_1 = require("../core");
|
|
5
|
+
// Optimization: Cache environment variables to avoid process.env access on every request
|
|
6
|
+
const IS_DEVELOPMENT = process.env.NODE_ENV === 'development' || process.env.DEBUG === 'true';
|
|
7
|
+
const DEBUG_API_RESPONSE = process.env.DEBUG_API_RESPONSE === 'true';
|
|
8
|
+
// Optimization: Pre-allocate static error categories to reduce garbage collection pressure
|
|
9
|
+
const DATABASE_ERROR = {
|
|
10
|
+
type: 'DATABASE_ERROR',
|
|
11
|
+
userMessage: 'Database temporarily unavailable. Please try again.',
|
|
12
|
+
httpStatus: 503,
|
|
13
|
+
retryable: true,
|
|
14
|
+
};
|
|
15
|
+
const TIMEOUT_ERROR = {
|
|
16
|
+
type: 'TIMEOUT_ERROR',
|
|
17
|
+
userMessage: 'Request took too long. Please try again.',
|
|
18
|
+
httpStatus: 504,
|
|
19
|
+
retryable: true,
|
|
20
|
+
};
|
|
21
|
+
const EXTERNAL_SERVICE_ERROR = {
|
|
22
|
+
type: 'EXTERNAL_SERVICE_ERROR',
|
|
23
|
+
userMessage: 'External service unavailable. Please try again later.',
|
|
24
|
+
httpStatus: 502,
|
|
25
|
+
retryable: true,
|
|
26
|
+
};
|
|
27
|
+
const INTERNAL_ERROR = {
|
|
28
|
+
type: 'INTERNAL_ERROR',
|
|
29
|
+
userMessage: 'An unexpected error occurred.',
|
|
30
|
+
httpStatus: 500,
|
|
31
|
+
retryable: false,
|
|
32
|
+
};
|
|
33
|
+
const categorizeError = (error, customMatchers) => {
|
|
34
|
+
// Check custom matchers first
|
|
35
|
+
if (customMatchers) {
|
|
36
|
+
for (const matcher of customMatchers) {
|
|
37
|
+
if (matcher.matches(error)) {
|
|
38
|
+
return matcher.category;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const message = error.message.toLowerCase();
|
|
43
|
+
// Database errors (MongoDB, Mongoose)
|
|
44
|
+
if (message.includes('mongodb') ||
|
|
45
|
+
message.includes('mongoose') ||
|
|
46
|
+
message.includes('buffering timed out')) {
|
|
47
|
+
return DATABASE_ERROR;
|
|
48
|
+
}
|
|
49
|
+
// Timeout errors
|
|
50
|
+
if (message.includes('timeout') || message.includes('timed out')) {
|
|
51
|
+
return TIMEOUT_ERROR;
|
|
52
|
+
}
|
|
53
|
+
// Network/external service errors
|
|
54
|
+
if (message.includes('econnrefused') ||
|
|
55
|
+
message.includes('network') ||
|
|
56
|
+
message.includes('fetch failed')) {
|
|
57
|
+
return EXTERNAL_SERVICE_ERROR;
|
|
58
|
+
}
|
|
59
|
+
// Default: generic server error
|
|
60
|
+
return INTERNAL_ERROR;
|
|
61
|
+
};
|
|
5
62
|
/**
|
|
6
63
|
* Handles errors thrown during request processing and sends an appropriate JSON response.
|
|
7
64
|
*
|
|
@@ -15,25 +72,33 @@ const core_1 = require("../core");
|
|
|
15
72
|
* @param context - The request context containing request and response objects.
|
|
16
73
|
* @returns A promise that resolves when the error response has been sent.
|
|
17
74
|
*/
|
|
18
|
-
const handleError = async (error, context) => {
|
|
19
|
-
|
|
75
|
+
const handleError = async (error, context, options) => {
|
|
76
|
+
// Safe property collection for logging
|
|
77
|
+
const errorType = error?.constructor?.name;
|
|
78
|
+
const errorMessage = error?.message;
|
|
20
79
|
core_1.logger.error('Error processing request', {
|
|
21
|
-
|
|
80
|
+
errorType,
|
|
81
|
+
errorMessage,
|
|
22
82
|
errorStack: error?.stack,
|
|
83
|
+
errorCode: error?.code,
|
|
84
|
+
httpStatus: error?.status,
|
|
23
85
|
requestId: context.requestId,
|
|
86
|
+
url: context.req.url,
|
|
87
|
+
method: context.req.method,
|
|
24
88
|
userAgent: context.req.headers?.['user-agent'],
|
|
25
89
|
ip: context.req.ip || 'unknown',
|
|
26
90
|
});
|
|
91
|
+
const timestamp = new Date().toISOString();
|
|
27
92
|
if (error instanceof core_1.HttpError) {
|
|
28
93
|
const responsePayload = {
|
|
29
94
|
success: false,
|
|
30
95
|
payload: {
|
|
31
96
|
error: error.message,
|
|
32
97
|
},
|
|
33
|
-
timestamp
|
|
98
|
+
timestamp,
|
|
34
99
|
};
|
|
35
100
|
// Only include sensitive details in development
|
|
36
|
-
if (
|
|
101
|
+
if (IS_DEVELOPMENT && error.details) {
|
|
37
102
|
responsePayload.payload.details = error.details;
|
|
38
103
|
}
|
|
39
104
|
// Only include error codes for client errors (4xx), not server errors
|
|
@@ -41,26 +106,33 @@ const handleError = async (error, context) => {
|
|
|
41
106
|
responsePayload.payload.code = error.code;
|
|
42
107
|
}
|
|
43
108
|
context.res.status(error.status).json(responsePayload);
|
|
109
|
+
return;
|
|
44
110
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
error:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
111
|
+
// Categorize the error for appropriate response
|
|
112
|
+
const category = categorizeError(error, options?.customCategories);
|
|
113
|
+
const responsePayload = {
|
|
114
|
+
error: category.userMessage,
|
|
115
|
+
success: false,
|
|
116
|
+
payload: {
|
|
117
|
+
error: category.userMessage,
|
|
118
|
+
code: category.type,
|
|
119
|
+
},
|
|
120
|
+
timestamp,
|
|
121
|
+
};
|
|
122
|
+
// Add retryable hint for client
|
|
123
|
+
if (category.retryable) {
|
|
124
|
+
responsePayload.payload.retryable = true;
|
|
125
|
+
context.res.header('Retry-After', '5');
|
|
126
|
+
}
|
|
127
|
+
// Include full error details when DEBUG_API_RESPONSE is enabled (dev/local)
|
|
128
|
+
if (DEBUG_API_RESPONSE) {
|
|
129
|
+
responsePayload.payload.debug = {
|
|
130
|
+
originalError: error.message,
|
|
131
|
+
stack: error.stack,
|
|
132
|
+
errorType: error.constructor?.name,
|
|
57
133
|
};
|
|
58
|
-
// Add stack trace in development for non-HTTP errors
|
|
59
|
-
if (isDevelopment && error.stack) {
|
|
60
|
-
responsePayload.payload.stack = error.stack;
|
|
61
|
-
}
|
|
62
|
-
context.res.status(500).json(responsePayload);
|
|
63
134
|
}
|
|
135
|
+
context.res.status(category.httpStatus).json(responsePayload);
|
|
64
136
|
};
|
|
65
137
|
/**
|
|
66
138
|
* Middleware class for handling errors in the application.
|
|
@@ -72,12 +144,7 @@ const handleError = async (error, context) => {
|
|
|
72
144
|
*
|
|
73
145
|
* @remarks
|
|
74
146
|
* This middleware should be registered to catch and process errors that occur
|
|
75
|
-
* during request handling.
|
|
76
|
-
*
|
|
77
|
-
* @method onError
|
|
78
|
-
* @param error - The error object that was thrown.
|
|
79
|
-
* @param context - The context in which the error occurred.
|
|
80
|
-
* @returns A promise that resolves when error handling is complete.
|
|
147
|
+
* during request handling. It supports custom error categorization via options.
|
|
81
148
|
*
|
|
82
149
|
* @example
|
|
83
150
|
* Basic handler with error handling:
|
|
@@ -99,26 +166,36 @@ const handleError = async (error, context) => {
|
|
|
99
166
|
* ```
|
|
100
167
|
*
|
|
101
168
|
* @example
|
|
102
|
-
*
|
|
169
|
+
* Usage with custom categories:
|
|
103
170
|
* ```typescript
|
|
104
|
-
* import { http } from '@google-cloud/functions-framework';
|
|
105
171
|
* import { Handler, ErrorHandlerMiddleware } from '@noony-serverless/core';
|
|
106
172
|
*
|
|
107
|
-
* const
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
173
|
+
* const customCategories = [{
|
|
174
|
+
* matches: (err) => err.message.includes('payment_failed'),
|
|
175
|
+
* category: {
|
|
176
|
+
* type: 'PAYMENT_ERROR',
|
|
177
|
+
* userMessage: 'Payment processing failed',
|
|
178
|
+
* httpStatus: 402,
|
|
179
|
+
* retryable: false
|
|
180
|
+
* }
|
|
181
|
+
* }];
|
|
113
182
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
183
|
+
* const handler = new Handler()
|
|
184
|
+
* .use(new ErrorHandlerMiddleware({ customCategories }))
|
|
185
|
+
* .handle(...);
|
|
117
186
|
* ```
|
|
118
187
|
*/
|
|
119
188
|
class ErrorHandlerMiddleware {
|
|
189
|
+
options;
|
|
190
|
+
/**
|
|
191
|
+
* Creates an instance of ErrorHandlerMiddleware.
|
|
192
|
+
* @param {ErrorHandlerOptions} [options] - Configuration options for the middleware, including custom error categories.
|
|
193
|
+
*/
|
|
194
|
+
constructor(options) {
|
|
195
|
+
this.options = options;
|
|
196
|
+
}
|
|
120
197
|
async onError(error, context) {
|
|
121
|
-
await handleError(error, context);
|
|
198
|
+
await handleError(error, context, this.options);
|
|
122
199
|
}
|
|
123
200
|
}
|
|
124
201
|
exports.ErrorHandlerMiddleware = ErrorHandlerMiddleware;
|
|
@@ -127,6 +204,7 @@ exports.ErrorHandlerMiddleware = ErrorHandlerMiddleware;
|
|
|
127
204
|
*
|
|
128
205
|
* @template TBody - The type of the request body payload (preserves type chain)
|
|
129
206
|
* @template TUser - The type of the authenticated user (preserves type chain)
|
|
207
|
+
* @param {ErrorHandlerOptions} [options] - Configuration options for the middleware.
|
|
130
208
|
* @returns {BaseMiddleware} An object implementing the `onError` method to handle errors.
|
|
131
209
|
*
|
|
132
210
|
* @remarks
|
|
@@ -141,15 +219,28 @@ exports.ErrorHandlerMiddleware = ErrorHandlerMiddleware;
|
|
|
141
219
|
* const loginHandler = new Handler()
|
|
142
220
|
* .use(errorHandler())
|
|
143
221
|
* .handle(async (request, context) => {
|
|
144
|
-
*
|
|
222
|
+
* // ...
|
|
223
|
+
* });
|
|
224
|
+
* ```
|
|
145
225
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
226
|
+
* @example
|
|
227
|
+
* Usage with custom categories:
|
|
228
|
+
* ```typescript
|
|
229
|
+
* import { Handler, errorHandler } from '@noony-serverless/core';
|
|
149
230
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
231
|
+
* const customCategories = [{
|
|
232
|
+
* matches: (err) => err.message.includes('custom'),
|
|
233
|
+
* category: {
|
|
234
|
+
* type: 'CUSTOM_ERROR',
|
|
235
|
+
* userMessage: 'A custom error occurred',
|
|
236
|
+
* httpStatus: 400,
|
|
237
|
+
* retryable: false
|
|
238
|
+
* }
|
|
239
|
+
* }];
|
|
240
|
+
*
|
|
241
|
+
* const handler = new Handler()
|
|
242
|
+
* .use(errorHandler({ customCategories }))
|
|
243
|
+
* .handle(...)
|
|
153
244
|
* ```
|
|
154
245
|
*
|
|
155
246
|
* @example
|
|
@@ -167,9 +258,9 @@ exports.ErrorHandlerMiddleware = ErrorHandlerMiddleware;
|
|
|
167
258
|
* });
|
|
168
259
|
* ```
|
|
169
260
|
*/
|
|
170
|
-
const errorHandler = () => ({
|
|
261
|
+
const errorHandler = (options) => ({
|
|
171
262
|
onError: async (error, context) => {
|
|
172
|
-
await handleError(error, context);
|
|
263
|
+
await handleError(error, context, options);
|
|
173
264
|
},
|
|
174
265
|
});
|
|
175
266
|
exports.errorHandler = errorHandler;
|
package/package.json
CHANGED