mastercontroller 1.3.6 → 1.3.8
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/.claude/settings.local.json +3 -1
- package/MasterRequest.js +6 -0
- package/error/ErrorBoundary.js +353 -0
- package/error/HydrationMismatch.js +265 -0
- package/error/MasterBackendErrorHandler.js +769 -0
- package/error/MasterError.js +240 -0
- package/error/MasterError.js.tmp +0 -0
- package/error/MasterErrorHandler.js +487 -0
- package/error/MasterErrorLogger.js +360 -0
- package/error/MasterErrorMiddleware.js +407 -0
- package/error/MasterErrorRenderer.js +536 -0
- package/error/MasterErrorRenderer.js.tmp +0 -0
- package/error/SSRErrorHandler.js +273 -0
- package/log/mastercontroller.log +2 -0
- package/package.json +7 -6
- package/test-json-empty-body.js +76 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MasterErrorMiddleware - Request/Response error handling middleware
|
|
3
|
+
* Version: 1.0.1
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { handleControllerError, handleRoutingError, sendErrorResponse } = require('./MasterBackendErrorHandler');
|
|
7
|
+
const { logger } = require('./MasterErrorLogger');
|
|
8
|
+
|
|
9
|
+
const isDevelopment = process.env.NODE_ENV !== 'production' && process.env.master === 'development';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Global error handler middleware
|
|
13
|
+
* Wrap all controller actions with this
|
|
14
|
+
*/
|
|
15
|
+
function errorHandlerMiddleware(handler, controllerName, actionName) {
|
|
16
|
+
return async function wrappedHandler(requestObject) {
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Execute the actual handler
|
|
21
|
+
const result = await Promise.resolve(handler.call(this, requestObject));
|
|
22
|
+
|
|
23
|
+
// Log successful request in development
|
|
24
|
+
if (isDevelopment) {
|
|
25
|
+
const duration = Date.now() - startTime;
|
|
26
|
+
logger.info({
|
|
27
|
+
code: 'MC_INFO_REQUEST_SUCCESS',
|
|
28
|
+
message: `${controllerName}#${actionName} completed`,
|
|
29
|
+
context: {
|
|
30
|
+
duration,
|
|
31
|
+
path: requestObject.pathName,
|
|
32
|
+
method: requestObject.type
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const duration = Date.now() - startTime;
|
|
41
|
+
|
|
42
|
+
// Handle the error
|
|
43
|
+
const mcError = handleControllerError(
|
|
44
|
+
error,
|
|
45
|
+
controllerName,
|
|
46
|
+
actionName,
|
|
47
|
+
requestObject.pathName
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Send error response
|
|
51
|
+
sendErrorResponse(
|
|
52
|
+
requestObject.response,
|
|
53
|
+
mcError,
|
|
54
|
+
requestObject.pathName
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Log to monitoring
|
|
58
|
+
logger.error({
|
|
59
|
+
code: mcError.code,
|
|
60
|
+
message: mcError.message,
|
|
61
|
+
controller: controllerName,
|
|
62
|
+
action: actionName,
|
|
63
|
+
route: requestObject.pathName,
|
|
64
|
+
method: requestObject.type,
|
|
65
|
+
duration,
|
|
66
|
+
originalError: error,
|
|
67
|
+
stack: error.stack
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Don't re-throw - error has been handled
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Request logging middleware
|
|
78
|
+
*/
|
|
79
|
+
function requestLoggerMiddleware() {
|
|
80
|
+
return function(requestObject, next) {
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
|
|
83
|
+
logger.info({
|
|
84
|
+
code: 'MC_INFO_REQUEST_START',
|
|
85
|
+
message: `${requestObject.type} ${requestObject.pathName}`,
|
|
86
|
+
context: {
|
|
87
|
+
method: requestObject.type,
|
|
88
|
+
path: requestObject.pathName,
|
|
89
|
+
params: requestObject.params,
|
|
90
|
+
query: requestObject.query,
|
|
91
|
+
ip: requestObject.request.connection?.remoteAddress
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Continue to next middleware
|
|
96
|
+
if (typeof next === 'function') {
|
|
97
|
+
next();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 404 handler middleware
|
|
104
|
+
*/
|
|
105
|
+
function notFoundMiddleware(requestObject) {
|
|
106
|
+
const mcError = handleRoutingError(
|
|
107
|
+
requestObject.pathName,
|
|
108
|
+
[] // Would need to pass available routes here
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
sendErrorResponse(
|
|
112
|
+
requestObject.response,
|
|
113
|
+
mcError,
|
|
114
|
+
requestObject.pathName
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Extract user code context from stack trace
|
|
120
|
+
*/
|
|
121
|
+
function extractUserCodeContext(stack) {
|
|
122
|
+
if (!stack) return null;
|
|
123
|
+
|
|
124
|
+
const lines = stack.split('\n');
|
|
125
|
+
const userFiles = [];
|
|
126
|
+
const frameworkFiles = [];
|
|
127
|
+
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
// Skip the error message line
|
|
130
|
+
if (!line.trim().startsWith('at ')) continue;
|
|
131
|
+
|
|
132
|
+
// Extract file path from stack line
|
|
133
|
+
const match = line.match(/\((.+?):(\d+):(\d+)\)|at (.+?):(\d+):(\d+)/);
|
|
134
|
+
if (!match) continue;
|
|
135
|
+
|
|
136
|
+
const filePath = match[1] || match[4];
|
|
137
|
+
const lineNum = match[2] || match[5];
|
|
138
|
+
const colNum = match[3] || match[6];
|
|
139
|
+
|
|
140
|
+
if (!filePath) continue;
|
|
141
|
+
|
|
142
|
+
// Categorize as user code or framework code
|
|
143
|
+
const isFramework = filePath.includes('node_modules/mastercontroller');
|
|
144
|
+
const isNodeInternal = filePath.includes('node:internal') || filePath.includes('/lib/internal/');
|
|
145
|
+
|
|
146
|
+
if (isNodeInternal) continue;
|
|
147
|
+
|
|
148
|
+
const fileInfo = {
|
|
149
|
+
file: filePath,
|
|
150
|
+
line: lineNum,
|
|
151
|
+
column: colNum,
|
|
152
|
+
location: `${filePath}:${lineNum}:${colNum}`
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if (isFramework) {
|
|
156
|
+
frameworkFiles.push(fileInfo);
|
|
157
|
+
} else {
|
|
158
|
+
userFiles.push(fileInfo);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
userFiles,
|
|
164
|
+
frameworkFiles,
|
|
165
|
+
triggeringFile: userFiles[0] || frameworkFiles[0] || null
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Uncaught exception handler
|
|
171
|
+
*/
|
|
172
|
+
function setupGlobalErrorHandlers() {
|
|
173
|
+
// Handle uncaught exceptions
|
|
174
|
+
process.on('uncaughtException', (error) => {
|
|
175
|
+
console.error('[MasterController] Uncaught Exception:', error);
|
|
176
|
+
|
|
177
|
+
// Extract context from stack trace
|
|
178
|
+
const context = extractUserCodeContext(error.stack);
|
|
179
|
+
|
|
180
|
+
// Build enhanced error message
|
|
181
|
+
let enhancedMessage = `Uncaught exception: ${error.message}`;
|
|
182
|
+
|
|
183
|
+
if (context && context.triggeringFile) {
|
|
184
|
+
enhancedMessage += `\n\n🔍 Error Location: ${context.triggeringFile.location}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (context && context.userFiles.length > 0) {
|
|
188
|
+
enhancedMessage += `\n\n📂 Your Code Involved:`;
|
|
189
|
+
context.userFiles.forEach((file, i) => {
|
|
190
|
+
if (i < 3) { // Show first 3 user files
|
|
191
|
+
enhancedMessage += `\n ${i + 1}. ${file.location}`;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (context && context.frameworkFiles.length > 0) {
|
|
197
|
+
enhancedMessage += `\n\n🔧 Framework Files Involved:`;
|
|
198
|
+
context.frameworkFiles.forEach((file, i) => {
|
|
199
|
+
if (i < 2) { // Show first 2 framework files
|
|
200
|
+
enhancedMessage += `\n ${i + 1}. ${file.location}`;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.error(enhancedMessage);
|
|
206
|
+
|
|
207
|
+
logger.fatal({
|
|
208
|
+
code: 'MC_ERR_UNCAUGHT_EXCEPTION',
|
|
209
|
+
message: enhancedMessage,
|
|
210
|
+
originalError: error,
|
|
211
|
+
stack: error.stack,
|
|
212
|
+
context: context
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Give logger time to write, then exit
|
|
216
|
+
setTimeout(() => {
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}, 1000);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Handle unhandled promise rejections
|
|
222
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
223
|
+
console.error('[MasterController] Unhandled Rejection:', reason);
|
|
224
|
+
|
|
225
|
+
// Extract context from stack trace if available
|
|
226
|
+
const context = reason?.stack ? extractUserCodeContext(reason.stack) : null;
|
|
227
|
+
|
|
228
|
+
// Build enhanced error message
|
|
229
|
+
let enhancedMessage = `Unhandled promise rejection: ${reason}`;
|
|
230
|
+
|
|
231
|
+
if (context && context.triggeringFile) {
|
|
232
|
+
enhancedMessage += `\n\n🔍 Error Location: ${context.triggeringFile.location}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (context && context.userFiles.length > 0) {
|
|
236
|
+
enhancedMessage += `\n\n📂 Your Code Involved:`;
|
|
237
|
+
context.userFiles.forEach((file, i) => {
|
|
238
|
+
if (i < 3) { // Show first 3 user files
|
|
239
|
+
enhancedMessage += `\n ${i + 1}. ${file.location}`;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (enhancedMessage !== `Unhandled promise rejection: ${reason}`) {
|
|
245
|
+
console.error(enhancedMessage);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
logger.error({
|
|
249
|
+
code: 'MC_ERR_UNHANDLED_REJECTION',
|
|
250
|
+
message: enhancedMessage,
|
|
251
|
+
originalError: reason,
|
|
252
|
+
stack: reason?.stack,
|
|
253
|
+
context: context
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Handle warnings
|
|
258
|
+
process.on('warning', (warning) => {
|
|
259
|
+
if (isDevelopment) {
|
|
260
|
+
console.warn('[MasterController] Warning:', warning);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
logger.warn({
|
|
264
|
+
code: 'MC_WARN_PROCESS_WARNING',
|
|
265
|
+
message: warning.message,
|
|
266
|
+
context: {
|
|
267
|
+
name: warning.name,
|
|
268
|
+
stack: warning.stack
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Safe file reader with error handling
|
|
276
|
+
*/
|
|
277
|
+
function safeReadFile(fs, filePath, encoding = 'utf8') {
|
|
278
|
+
try {
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
content: fs.readFileSync(filePath, encoding),
|
|
282
|
+
error: null
|
|
283
|
+
};
|
|
284
|
+
} catch (error) {
|
|
285
|
+
const { handleFileReadError } = require('./MasterBackendErrorHandler');
|
|
286
|
+
const mcError = handleFileReadError(error, filePath);
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
content: null,
|
|
291
|
+
error: mcError
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Safe file existence check
|
|
298
|
+
*/
|
|
299
|
+
function safeFileExists(fs, filePath) {
|
|
300
|
+
try {
|
|
301
|
+
return fs.existsSync(filePath);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
logger.warn({
|
|
304
|
+
code: 'MC_WARN_FILE_CHECK',
|
|
305
|
+
message: `Could not check if file exists: ${filePath}`,
|
|
306
|
+
originalError: error
|
|
307
|
+
});
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Wrap controller class with error handling
|
|
314
|
+
*/
|
|
315
|
+
function wrapController(ControllerClass, controllerName) {
|
|
316
|
+
const wrappedMethods = {};
|
|
317
|
+
|
|
318
|
+
// Get all methods from the controller
|
|
319
|
+
const methodNames = Object.getOwnPropertyNames(ControllerClass.prototype);
|
|
320
|
+
|
|
321
|
+
methodNames.forEach(methodName => {
|
|
322
|
+
if (methodName === 'constructor') return;
|
|
323
|
+
|
|
324
|
+
const originalMethod = ControllerClass.prototype[methodName];
|
|
325
|
+
|
|
326
|
+
if (typeof originalMethod === 'function') {
|
|
327
|
+
// Wrap each method with error handling
|
|
328
|
+
wrappedMethods[methodName] = errorHandlerMiddleware(
|
|
329
|
+
originalMethod,
|
|
330
|
+
controllerName,
|
|
331
|
+
methodName
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Create new class with wrapped methods
|
|
337
|
+
const WrappedController = class extends ControllerClass {
|
|
338
|
+
constructor(...args) {
|
|
339
|
+
super(...args);
|
|
340
|
+
|
|
341
|
+
// Apply wrapped methods
|
|
342
|
+
Object.keys(wrappedMethods).forEach(methodName => {
|
|
343
|
+
this[methodName] = wrappedMethods[methodName].bind(this);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
return WrappedController;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Performance tracking middleware
|
|
353
|
+
*/
|
|
354
|
+
function performanceMiddleware() {
|
|
355
|
+
const requests = new Map();
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
start(requestId, requestObject) {
|
|
359
|
+
requests.set(requestId, {
|
|
360
|
+
startTime: Date.now(),
|
|
361
|
+
path: requestObject.pathName,
|
|
362
|
+
method: requestObject.type
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
end(requestId) {
|
|
367
|
+
const req = requests.get(requestId);
|
|
368
|
+
if (!req) return;
|
|
369
|
+
|
|
370
|
+
const duration = Date.now() - req.startTime;
|
|
371
|
+
|
|
372
|
+
if (duration > 1000) {
|
|
373
|
+
logger.warn({
|
|
374
|
+
code: 'MC_WARN_SLOW_REQUEST',
|
|
375
|
+
message: `Slow request detected (${duration}ms)`,
|
|
376
|
+
context: {
|
|
377
|
+
duration,
|
|
378
|
+
path: req.path,
|
|
379
|
+
method: req.method
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
requests.delete(requestId);
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
getStats() {
|
|
388
|
+
return {
|
|
389
|
+
activeRequests: requests.size,
|
|
390
|
+
requests: Array.from(requests.values())
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const performanceTracker = performanceMiddleware();
|
|
397
|
+
|
|
398
|
+
module.exports = {
|
|
399
|
+
errorHandlerMiddleware,
|
|
400
|
+
requestLoggerMiddleware,
|
|
401
|
+
notFoundMiddleware,
|
|
402
|
+
setupGlobalErrorHandlers,
|
|
403
|
+
safeReadFile,
|
|
404
|
+
safeFileExists,
|
|
405
|
+
wrapController,
|
|
406
|
+
performanceTracker
|
|
407
|
+
};
|