navis.js 3.1.0 → 5.0.0
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/README.md +30 -2
- package/examples/v4-features-demo.js +171 -0
- package/examples/v5-features-demo.js +167 -0
- package/package.json +1 -1
- package/src/auth/authenticator.js +223 -0
- package/src/cache/cache.js +157 -0
- package/src/cache/redis-cache.js +174 -0
- package/src/core/advanced-router.js +186 -0
- package/src/core/app.js +264 -176
- package/src/core/graceful-shutdown.js +77 -0
- package/src/errors/error-handler.js +157 -0
- package/src/health/health-checker.js +120 -0
- package/src/index.js +69 -0
- package/src/middleware/cache-middleware.js +105 -0
- package/src/middleware/compression.js +97 -0
- package/src/middleware/cors.js +86 -0
- package/src/middleware/rate-limiter.js +159 -0
- package/src/middleware/security.js +107 -0
- package/src/validation/validator.js +301 -0
package/src/core/app.js
CHANGED
|
@@ -1,177 +1,265 @@
|
|
|
1
|
-
const http = require('http');
|
|
2
|
-
const Router = require('./router');
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const Router = require('./router');
|
|
3
|
+
const AdvancedRouter = require('./advanced-router');
|
|
4
|
+
const { executeMiddleware } = require('./middleware');
|
|
5
|
+
const { error: errorResponse } = require('../utils/response');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* NavisApp - Main application class
|
|
9
|
+
*/
|
|
10
|
+
class NavisApp {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
// Use advanced router if enabled (v4)
|
|
13
|
+
this.useAdvancedRouter = options.useAdvancedRouter !== false; // Default true in v4
|
|
14
|
+
this.router = this.useAdvancedRouter ? new AdvancedRouter() : new Router();
|
|
15
|
+
this.middlewares = [];
|
|
16
|
+
this.server = null;
|
|
17
|
+
this.errorHandler = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register middleware
|
|
22
|
+
* @param {Function} fn - Middleware function (req, res, next)
|
|
23
|
+
*/
|
|
24
|
+
use(fn) {
|
|
25
|
+
this.middlewares.push(fn);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Register GET route
|
|
30
|
+
*/
|
|
31
|
+
get(path, handler) {
|
|
32
|
+
this.router.get(path, handler);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Register POST route
|
|
37
|
+
*/
|
|
38
|
+
post(path, handler) {
|
|
39
|
+
this.router.post(path, handler);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Register PUT route
|
|
44
|
+
*/
|
|
45
|
+
put(path, handler) {
|
|
46
|
+
this.router.put(path, handler);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register DELETE route
|
|
51
|
+
*/
|
|
52
|
+
delete(path, handler) {
|
|
53
|
+
this.router.delete(path, handler);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Register PATCH route (v4)
|
|
58
|
+
*/
|
|
59
|
+
patch(path, handler) {
|
|
60
|
+
if (this.router.patch) {
|
|
61
|
+
this.router.patch(path, handler);
|
|
62
|
+
} else {
|
|
63
|
+
throw new Error('PATCH method requires advanced router');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set error handler (v4)
|
|
69
|
+
* @param {Function} handler - Error handler function
|
|
70
|
+
*/
|
|
71
|
+
setErrorHandler(handler) {
|
|
72
|
+
this.errorHandler = handler;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handle HTTP request (Node.js)
|
|
77
|
+
* @param {Object} req - Node.js HTTP request
|
|
78
|
+
* @param {Object} res - Node.js HTTP response
|
|
79
|
+
*/
|
|
80
|
+
async handleRequest(req, res) {
|
|
81
|
+
const method = req.method;
|
|
82
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
83
|
+
const path = url.pathname;
|
|
84
|
+
|
|
85
|
+
// Parse query string
|
|
86
|
+
req.query = {};
|
|
87
|
+
url.searchParams.forEach((value, key) => {
|
|
88
|
+
req.query[key] = value;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Find route handler
|
|
92
|
+
let routeResult;
|
|
93
|
+
if (this.useAdvancedRouter) {
|
|
94
|
+
routeResult = this.router.find(method, path);
|
|
95
|
+
if (routeResult) {
|
|
96
|
+
req.params = routeResult.params || {};
|
|
97
|
+
req.handler = routeResult.handler;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
const handler = this.router.find(method, path);
|
|
101
|
+
if (handler) {
|
|
102
|
+
routeResult = { handler };
|
|
103
|
+
req.params = {};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!routeResult) {
|
|
108
|
+
if (this.errorHandler) {
|
|
109
|
+
const notFoundError = new Error('Not Found');
|
|
110
|
+
notFoundError.statusCode = 404;
|
|
111
|
+
await this.errorHandler(notFoundError, req, res, () => {});
|
|
112
|
+
} else {
|
|
113
|
+
errorResponse(res, 'Not Found', 404);
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Execute middleware chain, then route handler
|
|
119
|
+
try {
|
|
120
|
+
await executeMiddleware(
|
|
121
|
+
this.middlewares,
|
|
122
|
+
req,
|
|
123
|
+
res,
|
|
124
|
+
routeResult.handler,
|
|
125
|
+
false
|
|
126
|
+
);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (this.errorHandler) {
|
|
129
|
+
await this.errorHandler(err, req, res, () => {});
|
|
130
|
+
} else {
|
|
131
|
+
errorResponse(res, err.message || 'Internal Server Error', err.statusCode || 500);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Handle AWS Lambda event
|
|
138
|
+
* @param {Object} event - Lambda event
|
|
139
|
+
* @returns {Object} - Lambda response
|
|
140
|
+
*/
|
|
141
|
+
async handleLambda(event) {
|
|
142
|
+
const method = event.httpMethod || event.requestContext?.http?.method || 'GET';
|
|
143
|
+
const path = event.path || event.rawPath || '/';
|
|
144
|
+
|
|
145
|
+
// Find route handler
|
|
146
|
+
let routeResult;
|
|
147
|
+
if (this.useAdvancedRouter) {
|
|
148
|
+
routeResult = this.router.find(method, path);
|
|
149
|
+
} else {
|
|
150
|
+
const handler = this.router.find(method, path);
|
|
151
|
+
if (handler) {
|
|
152
|
+
routeResult = { handler, params: {} };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!routeResult) {
|
|
157
|
+
return {
|
|
158
|
+
statusCode: 404,
|
|
159
|
+
headers: {
|
|
160
|
+
'Content-Type': 'application/json',
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify({ error: 'Not Found' }),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Create Lambda-compatible req/res objects
|
|
167
|
+
const req = {
|
|
168
|
+
method,
|
|
169
|
+
path,
|
|
170
|
+
headers: event.headers || {},
|
|
171
|
+
body: event.body ? (typeof event.body === 'string' ? JSON.parse(event.body) : event.body) : {},
|
|
172
|
+
query: event.queryStringParameters || {},
|
|
173
|
+
params: routeResult.params || {},
|
|
174
|
+
// Store original event for advanced use cases
|
|
175
|
+
event,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const res = {
|
|
179
|
+
statusCode: 200,
|
|
180
|
+
headers: {},
|
|
181
|
+
body: null,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Execute middleware chain, then route handler
|
|
185
|
+
try {
|
|
186
|
+
const result = await executeMiddleware(
|
|
187
|
+
this.middlewares,
|
|
188
|
+
req,
|
|
189
|
+
res,
|
|
190
|
+
routeResult.handler,
|
|
191
|
+
true
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// If handler returned a Lambda response directly, use it
|
|
195
|
+
if (result && result.statusCode) {
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Otherwise, construct response from res object
|
|
200
|
+
return {
|
|
201
|
+
statusCode: res.statusCode || 200,
|
|
202
|
+
headers: {
|
|
203
|
+
'Content-Type': 'application/json',
|
|
204
|
+
...res.headers,
|
|
205
|
+
},
|
|
206
|
+
body: res.body ? JSON.stringify(res.body) : JSON.stringify(result || {}),
|
|
207
|
+
};
|
|
208
|
+
} catch (err) {
|
|
209
|
+
// Use error handler if set
|
|
210
|
+
if (this.errorHandler) {
|
|
211
|
+
// Create a mock res object for error handler
|
|
212
|
+
const errorRes = {
|
|
213
|
+
statusCode: err.statusCode || 500,
|
|
214
|
+
headers: { 'Content-Type': 'application/json' },
|
|
215
|
+
body: null,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
await this.errorHandler(err, req, errorRes, () => {});
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
statusCode: errorRes.statusCode,
|
|
222
|
+
headers: errorRes.headers,
|
|
223
|
+
body: typeof errorRes.body === 'string' ? errorRes.body : JSON.stringify(errorRes.body || { error: err.message }),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
statusCode: err.statusCode || 500,
|
|
229
|
+
headers: {
|
|
230
|
+
'Content-Type': 'application/json',
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify({ error: err.message || 'Internal Server Error' }),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Start HTTP server (Node.js)
|
|
239
|
+
* @param {number} port - Port number
|
|
240
|
+
* @param {Function} callback - Optional callback
|
|
241
|
+
* @returns {Object} - HTTP server instance
|
|
242
|
+
*/
|
|
243
|
+
listen(port = 3000, callback) {
|
|
244
|
+
this.server = http.createServer((req, res) => {
|
|
245
|
+
this.handleRequest(req, res);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
this.server.listen(port, () => {
|
|
249
|
+
if (callback) callback();
|
|
250
|
+
console.log(`Navis.js server listening on port ${port}`);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return this.server;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get server instance
|
|
258
|
+
* @returns {Object|null} - HTTP server instance
|
|
259
|
+
*/
|
|
260
|
+
getServer() {
|
|
261
|
+
return this.server;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
177
265
|
module.exports = NavisApp;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful Shutdown Handler
|
|
3
|
+
* v5: Clean shutdown handling for Node.js servers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Graceful shutdown handler
|
|
8
|
+
* @param {Object} server - HTTP server instance
|
|
9
|
+
* @param {Object} options - Shutdown options
|
|
10
|
+
*/
|
|
11
|
+
function gracefulShutdown(server, options = {}) {
|
|
12
|
+
const {
|
|
13
|
+
timeout = 10000, // 10 seconds default
|
|
14
|
+
onShutdown = async () => {}, // Cleanup function
|
|
15
|
+
signals = ['SIGTERM', 'SIGINT'],
|
|
16
|
+
log = console.log,
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
let isShuttingDown = false;
|
|
20
|
+
|
|
21
|
+
const shutdown = async (signal) => {
|
|
22
|
+
if (isShuttingDown) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
isShuttingDown = true;
|
|
27
|
+
log(`Received ${signal}, starting graceful shutdown...`);
|
|
28
|
+
|
|
29
|
+
// Stop accepting new connections
|
|
30
|
+
server.close(() => {
|
|
31
|
+
log('HTTP server closed');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Set timeout for forced shutdown
|
|
35
|
+
const shutdownTimer = setTimeout(() => {
|
|
36
|
+
log('Forced shutdown after timeout');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}, timeout);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Run cleanup
|
|
42
|
+
await onShutdown();
|
|
43
|
+
clearTimeout(shutdownTimer);
|
|
44
|
+
log('Graceful shutdown completed');
|
|
45
|
+
process.exit(0);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
clearTimeout(shutdownTimer);
|
|
48
|
+
log('Error during shutdown:', error);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Register signal handlers
|
|
54
|
+
for (const signal of signals) {
|
|
55
|
+
process.on(signal, () => shutdown(signal));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle uncaught exceptions
|
|
59
|
+
process.on('uncaughtException', async (error) => {
|
|
60
|
+
log('Uncaught exception:', error);
|
|
61
|
+
await shutdown('uncaughtException');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Handle unhandled promise rejections
|
|
65
|
+
process.on('unhandledRejection', async (reason, promise) => {
|
|
66
|
+
log('Unhandled rejection:', reason);
|
|
67
|
+
await shutdown('unhandledRejection');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
shutdown: () => shutdown('manual'),
|
|
72
|
+
isShuttingDown: () => isShuttingDown,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = gracefulShutdown;
|
|
77
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Error Handling
|
|
3
|
+
* v4: Custom error classes and error handling middleware
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class AppError extends Error {
|
|
7
|
+
constructor(message, statusCode = 500, code = null) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = this.constructor.name;
|
|
10
|
+
this.statusCode = statusCode;
|
|
11
|
+
this.code = code || this.constructor.name.toUpperCase();
|
|
12
|
+
this.isOperational = true;
|
|
13
|
+
Error.captureStackTrace(this, this.constructor);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class NotFoundError extends AppError {
|
|
18
|
+
constructor(message = 'Resource not found') {
|
|
19
|
+
super(message, 404, 'NOT_FOUND');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class BadRequestError extends AppError {
|
|
24
|
+
constructor(message = 'Bad request') {
|
|
25
|
+
super(message, 400, 'BAD_REQUEST');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class UnauthorizedError extends AppError {
|
|
30
|
+
constructor(message = 'Unauthorized') {
|
|
31
|
+
super(message, 401, 'UNAUTHORIZED');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class ForbiddenError extends AppError {
|
|
36
|
+
constructor(message = 'Forbidden') {
|
|
37
|
+
super(message, 403, 'FORBIDDEN');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class ConflictError extends AppError {
|
|
42
|
+
constructor(message = 'Conflict') {
|
|
43
|
+
super(message, 409, 'CONFLICT');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class InternalServerError extends AppError {
|
|
48
|
+
constructor(message = 'Internal server error') {
|
|
49
|
+
super(message, 500, 'INTERNAL_SERVER_ERROR');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Error handler middleware
|
|
55
|
+
* @param {Object} options - Error handler options
|
|
56
|
+
* @returns {Function} - Error handling middleware
|
|
57
|
+
*/
|
|
58
|
+
function errorHandler(options = {}) {
|
|
59
|
+
const {
|
|
60
|
+
format = 'json',
|
|
61
|
+
includeStack = process.env.NODE_ENV === 'development',
|
|
62
|
+
logErrors = true,
|
|
63
|
+
logger = console.error,
|
|
64
|
+
} = options;
|
|
65
|
+
|
|
66
|
+
return async (err, req, res, next) => {
|
|
67
|
+
// Determine status code
|
|
68
|
+
const statusCode = err.statusCode || err.status || 500;
|
|
69
|
+
const code = err.code || 'INTERNAL_SERVER_ERROR';
|
|
70
|
+
const message = err.message || 'Internal server error';
|
|
71
|
+
|
|
72
|
+
// Log error
|
|
73
|
+
if (logErrors) {
|
|
74
|
+
logger('Error:', {
|
|
75
|
+
message,
|
|
76
|
+
statusCode,
|
|
77
|
+
code,
|
|
78
|
+
stack: includeStack ? err.stack : undefined,
|
|
79
|
+
path: req.path,
|
|
80
|
+
method: req.method,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Format error response
|
|
85
|
+
const errorResponse = {
|
|
86
|
+
error: {
|
|
87
|
+
message,
|
|
88
|
+
code,
|
|
89
|
+
statusCode,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Include stack trace in development
|
|
94
|
+
if (includeStack && err.stack) {
|
|
95
|
+
errorResponse.error.stack = err.stack.split('\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Include validation errors if present
|
|
99
|
+
if (err.errors && Array.isArray(err.errors)) {
|
|
100
|
+
errorResponse.error.errors = err.errors;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Set response
|
|
104
|
+
res.statusCode = statusCode;
|
|
105
|
+
|
|
106
|
+
if (format === 'json') {
|
|
107
|
+
res.headers = res.headers || {};
|
|
108
|
+
res.headers['Content-Type'] = 'application/json';
|
|
109
|
+
res.body = errorResponse;
|
|
110
|
+
} else {
|
|
111
|
+
res.body = `${statusCode} ${message}`;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Async error wrapper
|
|
118
|
+
* Wraps async route handlers to catch errors
|
|
119
|
+
* @param {Function} fn - Async function
|
|
120
|
+
* @returns {Function} - Wrapped function
|
|
121
|
+
*/
|
|
122
|
+
function asyncHandler(fn) {
|
|
123
|
+
return (req, res, next) => {
|
|
124
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Not found handler
|
|
130
|
+
* @returns {Function} - Middleware function
|
|
131
|
+
*/
|
|
132
|
+
function notFoundHandler() {
|
|
133
|
+
return (req, res) => {
|
|
134
|
+
res.statusCode = 404;
|
|
135
|
+
res.body = {
|
|
136
|
+
error: {
|
|
137
|
+
message: `Route ${req.method} ${req.path} not found`,
|
|
138
|
+
code: 'NOT_FOUND',
|
|
139
|
+
statusCode: 404,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
AppError,
|
|
147
|
+
NotFoundError,
|
|
148
|
+
BadRequestError,
|
|
149
|
+
UnauthorizedError,
|
|
150
|
+
ForbiddenError,
|
|
151
|
+
ConflictError,
|
|
152
|
+
InternalServerError,
|
|
153
|
+
errorHandler,
|
|
154
|
+
asyncHandler,
|
|
155
|
+
notFoundHandler,
|
|
156
|
+
};
|
|
157
|
+
|