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/src/core/app.js CHANGED
@@ -1,177 +1,265 @@
1
- const http = require('http');
2
- const Router = require('./router');
3
- const { executeMiddleware } = require('./middleware');
4
- const { error: errorResponse } = require('../utils/response');
5
-
6
- /**
7
- * NavisApp - Main application class
8
- */
9
- class NavisApp {
10
- constructor() {
11
- this.router = new Router();
12
- this.middlewares = [];
13
- this.server = null;
14
- }
15
-
16
- /**
17
- * Register middleware
18
- * @param {Function} fn - Middleware function (req, res, next)
19
- */
20
- use(fn) {
21
- this.middlewares.push(fn);
22
- }
23
-
24
- /**
25
- * Register GET route
26
- */
27
- get(path, handler) {
28
- this.router.get(path, handler);
29
- }
30
-
31
- /**
32
- * Register POST route
33
- */
34
- post(path, handler) {
35
- this.router.post(path, handler);
36
- }
37
-
38
- /**
39
- * Register PUT route
40
- */
41
- put(path, handler) {
42
- this.router.put(path, handler);
43
- }
44
-
45
- /**
46
- * Register DELETE route
47
- */
48
- delete(path, handler) {
49
- this.router.delete(path, handler);
50
- }
51
-
52
- /**
53
- * Handle HTTP request (Node.js)
54
- * @param {Object} req - Node.js HTTP request
55
- * @param {Object} res - Node.js HTTP response
56
- */
57
- async handleRequest(req, res) {
58
- const method = req.method;
59
- const url = new URL(req.url, `http://${req.headers.host}`);
60
- const path = url.pathname;
61
-
62
- // Find route handler
63
- const handler = this.router.find(method, path);
64
-
65
- if (!handler) {
66
- errorResponse(res, 'Not Found', 404);
67
- return;
68
- }
69
-
70
- // Execute middleware chain, then route handler
71
- try {
72
- await executeMiddleware(
73
- this.middlewares,
74
- req,
75
- res,
76
- handler,
77
- false
78
- );
79
- } catch (err) {
80
- errorResponse(res, err.message || 'Internal Server Error', 500);
81
- }
82
- }
83
-
84
- /**
85
- * Handle AWS Lambda event
86
- * @param {Object} event - Lambda event
87
- * @returns {Object} - Lambda response
88
- */
89
- async handleLambda(event) {
90
- const method = event.httpMethod || event.requestContext?.http?.method || 'GET';
91
- const path = event.path || event.rawPath || '/';
92
-
93
- // Find route handler
94
- const handler = this.router.find(method, path);
95
-
96
- if (!handler) {
97
- return {
98
- statusCode: 404,
99
- headers: {
100
- 'Content-Type': 'application/json',
101
- },
102
- body: JSON.stringify({ error: 'Not Found' }),
103
- };
104
- }
105
-
106
- // Create Lambda-compatible req/res objects
107
- const req = {
108
- method,
109
- path,
110
- headers: event.headers || {},
111
- body: event.body ? JSON.parse(event.body) : {},
112
- query: event.queryStringParameters || {},
113
- // Store original event for advanced use cases
114
- event,
115
- };
116
-
117
- const res = {
118
- statusCode: 200,
119
- headers: {},
120
- body: null,
121
- };
122
-
123
- // Execute middleware chain, then route handler
124
- try {
125
- const result = await executeMiddleware(
126
- this.middlewares,
127
- req,
128
- res,
129
- handler,
130
- true
131
- );
132
-
133
- // If handler returned a Lambda response directly, use it
134
- if (result && result.statusCode) {
135
- return result;
136
- }
137
-
138
- // Otherwise, construct response from res object
139
- return {
140
- statusCode: res.statusCode || 200,
141
- headers: {
142
- 'Content-Type': 'application/json',
143
- ...res.headers,
144
- },
145
- body: res.body ? JSON.stringify(res.body) : JSON.stringify(result || {}),
146
- };
147
- } catch (err) {
148
- return {
149
- statusCode: 500,
150
- headers: {
151
- 'Content-Type': 'application/json',
152
- },
153
- body: JSON.stringify({ error: err.message || 'Internal Server Error' }),
154
- };
155
- }
156
- }
157
-
158
- /**
159
- * Start HTTP server (Node.js)
160
- * @param {number} port - Port number
161
- * @param {Function} callback - Optional callback
162
- */
163
- listen(port = 3000, callback) {
164
- this.server = http.createServer((req, res) => {
165
- this.handleRequest(req, res);
166
- });
167
-
168
- this.server.listen(port, () => {
169
- if (callback) callback();
170
- console.log(`Navis.js server listening on port ${port}`);
171
- });
172
-
173
- return this.server;
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
+