navis.js 3.0.2 → 4.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.
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Advanced Router with Parameters and Path Matching
3
+ * v4: Support for route parameters, wildcards, and path matching
4
+ */
5
+
6
+ class AdvancedRouter {
7
+ constructor() {
8
+ this.routes = {
9
+ GET: [],
10
+ POST: [],
11
+ PUT: [],
12
+ DELETE: [],
13
+ PATCH: [],
14
+ };
15
+ }
16
+
17
+ /**
18
+ * Register a route handler
19
+ * @param {string} method - HTTP method
20
+ * @param {string} path - Route path (supports :param and * wildcards)
21
+ * @param {Function} handler - Route handler function
22
+ */
23
+ register(method, path, handler) {
24
+ const normalizedMethod = method.toUpperCase();
25
+ if (!this.routes[normalizedMethod]) {
26
+ throw new Error(`Unsupported HTTP method: ${method}`);
27
+ }
28
+
29
+ // Parse route pattern
30
+ const pattern = this._parsePattern(path);
31
+
32
+ this.routes[normalizedMethod].push({
33
+ pattern: path,
34
+ regex: pattern.regex,
35
+ params: pattern.params,
36
+ handler,
37
+ });
38
+
39
+ // Sort routes by specificity (more specific first)
40
+ this.routes[normalizedMethod].sort((a, b) => {
41
+ const aSpecificity = this._calculateSpecificity(a.pattern);
42
+ const bSpecificity = this._calculateSpecificity(b.pattern);
43
+ return bSpecificity - aSpecificity;
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Find route handler for a method and path
49
+ * @param {string} method - HTTP method
50
+ * @param {string} path - Request path
51
+ * @returns {Object|null} - { handler, params } or null if not found
52
+ */
53
+ find(method, path) {
54
+ const normalizedMethod = method.toUpperCase();
55
+ const methodRoutes = this.routes[normalizedMethod] || [];
56
+
57
+ for (const route of methodRoutes) {
58
+ const match = path.match(route.regex);
59
+ if (match) {
60
+ // Extract parameters
61
+ const params = {};
62
+ route.params.forEach((param, index) => {
63
+ params[param] = match[index + 1];
64
+ });
65
+
66
+ return {
67
+ handler: route.handler,
68
+ params,
69
+ };
70
+ }
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ /**
77
+ * Parse route pattern into regex and parameter names
78
+ * @private
79
+ */
80
+ _parsePattern(pattern) {
81
+ const params = [];
82
+ const parts = pattern.split('/').filter(p => p !== ''); // Remove empty parts
83
+ const regexParts = [];
84
+
85
+ for (const part of parts) {
86
+ if (part.startsWith(':')) {
87
+ // Parameter
88
+ const paramName = part.substring(1);
89
+ params.push(paramName);
90
+ regexParts.push('([^/]+)');
91
+ } else if (part === '*') {
92
+ // Wildcard
93
+ regexParts.push('.*');
94
+ } else {
95
+ // Literal - escape regex special chars
96
+ regexParts.push(part.replace(/[.+?^${}()|[\]\\]/g, '\\$&'));
97
+ }
98
+ }
99
+
100
+ // Build regex pattern
101
+ const regexPattern = '^/' + regexParts.join('/') + '/?$';
102
+
103
+ return {
104
+ regex: new RegExp(regexPattern),
105
+ params,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Calculate route specificity (higher = more specific)
111
+ * @private
112
+ */
113
+ _calculateSpecificity(pattern) {
114
+ let specificity = 0;
115
+
116
+ // Count static segments
117
+ const staticSegments = pattern.split('/').filter(seg =>
118
+ seg && !seg.startsWith(':') && seg !== '*'
119
+ );
120
+ specificity += staticSegments.length * 10;
121
+
122
+ // Count parameters (less specific)
123
+ const params = (pattern.match(/:[^/]+/g) || []).length;
124
+ specificity += params * 5;
125
+
126
+ // Wildcards are least specific
127
+ if (pattern.includes('*')) {
128
+ specificity -= 10;
129
+ }
130
+
131
+ return specificity;
132
+ }
133
+
134
+ /**
135
+ * Get all registered routes
136
+ * @returns {Object} - All routes grouped by method
137
+ */
138
+ getAllRoutes() {
139
+ const allRoutes = {};
140
+ for (const method in this.routes) {
141
+ allRoutes[method] = this.routes[method].map(route => ({
142
+ pattern: route.pattern,
143
+ params: route.params,
144
+ }));
145
+ }
146
+ return allRoutes;
147
+ }
148
+
149
+ /**
150
+ * Register GET route
151
+ */
152
+ get(path, handler) {
153
+ this.register('GET', path, handler);
154
+ }
155
+
156
+ /**
157
+ * Register POST route
158
+ */
159
+ post(path, handler) {
160
+ this.register('POST', path, handler);
161
+ }
162
+
163
+ /**
164
+ * Register PUT route
165
+ */
166
+ put(path, handler) {
167
+ this.register('PUT', path, handler);
168
+ }
169
+
170
+ /**
171
+ * Register DELETE route
172
+ */
173
+ delete(path, handler) {
174
+ this.register('DELETE', path, handler);
175
+ }
176
+
177
+ /**
178
+ * Register PATCH route
179
+ */
180
+ patch(path, handler) {
181
+ this.register('PATCH', path, handler);
182
+ }
183
+ }
184
+
185
+ module.exports = AdvancedRouter;
186
+
package/src/core/app.js CHANGED
@@ -1,177 +1,256 @@
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
+ */
242
+ listen(port = 3000, callback) {
243
+ this.server = http.createServer((req, res) => {
244
+ this.handleRequest(req, res);
245
+ });
246
+
247
+ this.server.listen(port, () => {
248
+ if (callback) callback();
249
+ console.log(`Navis.js server listening on port ${port}`);
250
+ });
251
+
252
+ return this.server;
253
+ }
254
+ }
255
+
177
256
  module.exports = NavisApp;