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.
package/README.md CHANGED
@@ -112,6 +112,23 @@ navis metrics
112
112
  - ✅ **Distributed tracing** - Trace and span management
113
113
  - ✅ **Enhanced CLI** - Test and metrics commands
114
114
 
115
+ ### v3.1
116
+
117
+ - ✅ **Lambda cold start optimization** - Connection pooling, lazy initialization
118
+ - ✅ **ServiceClientPool** - Reuse HTTP connections across invocations
119
+ - ✅ **LazyInit utility** - Defer heavy operations until needed
120
+ - ✅ **LambdaHandler** - Optimized handler with warm-up support
121
+ - ✅ **Cold start tracking** - Monitor and log cold start metrics
122
+
123
+ ### v4 (Current)
124
+
125
+ - ✅ **Advanced routing** - Route parameters (`:id`), nested routes, PATCH method
126
+ - ✅ **Request validation** - Schema-based validation with comprehensive rules
127
+ - ✅ **Authentication** - JWT and API Key authentication
128
+ - ✅ **Authorization** - Role-based access control
129
+ - ✅ **Rate limiting** - In-memory rate limiting with configurable windows
130
+ - ✅ **Enhanced error handling** - Custom error classes and error handler middleware
131
+
115
132
  ## API Reference
116
133
 
117
134
  ### NavisApp
@@ -248,12 +265,55 @@ await nats.connect();
248
265
  await nats.publish('user.created', { userId: 123 });
249
266
  ```
250
267
 
268
+ ### Lambda Optimization (v3.1)
269
+
270
+ ```javascript
271
+ const {
272
+ NavisApp,
273
+ getPool,
274
+ LambdaHandler,
275
+ coldStartTracker,
276
+ LazyInit,
277
+ } = require('navis.js');
278
+
279
+ // Initialize app OUTSIDE handler (reused across invocations)
280
+ const app = new NavisApp();
281
+ app.use(coldStartTracker);
282
+
283
+ // Connection pooling - reuse HTTP connections
284
+ const client = getPool().get('http://api.example.com', {
285
+ timeout: 3000,
286
+ maxRetries: 2,
287
+ });
288
+
289
+ // Lazy initialization - defer heavy operations
290
+ const dbConnection = new LazyInit();
291
+ app.get('/users', async (req, res) => {
292
+ const db = await dbConnection.init(async () => {
293
+ return await connectToDatabase(); // Only runs once
294
+ });
295
+ res.body = await db.query('SELECT * FROM users');
296
+ });
297
+
298
+ // Optimized Lambda handler
299
+ const handler = new LambdaHandler(app, {
300
+ enableMetrics: true,
301
+ warmupPath: '/warmup',
302
+ });
303
+
304
+ exports.handler = async (event, context) => {
305
+ return await handler.handle(event, context);
306
+ };
307
+ ```
308
+
251
309
  ## Examples
252
310
 
253
311
  See the `examples/` directory:
254
312
 
255
313
  - `server.js` - Node.js HTTP server example
256
314
  - `lambda.js` - AWS Lambda handler example
315
+ - `lambda-optimized.js` - Optimized Lambda handler with cold start optimizations (v3.1)
316
+ - `v4-features-demo.js` - v4 features demonstration (routing, validation, auth, rate limiting, etc.)
257
317
  - `service-client-demo.js` - ServiceClient usage example
258
318
  - `v2-features-demo.js` - v2 features demonstration (retry, circuit breaker, etc.)
259
319
  - `v3-features-demo.js` - v3 features demonstration (messaging, observability, etc.)
@@ -266,13 +326,18 @@ Core functionality: routing, middleware, Lambda support, ServiceClient
266
326
  ### v2 ✅
267
327
  Resilience patterns: retry, circuit breaker, service discovery, CLI generators
268
328
 
269
- ### v3 ✅ (Current)
329
+ ### v3 ✅
270
330
  Advanced features: async messaging (SQS/Kafka/NATS), observability, enhanced CLI
271
331
 
332
+ ### v4 ✅ (Current)
333
+ Production-ready: advanced routing, validation, authentication, rate limiting, error handling
334
+
272
335
  ## Documentation
273
336
 
274
337
  - [V2 Features Guide](./V2_FEATURES.md) - Complete v2 features documentation
275
338
  - [V3 Features Guide](./V3_FEATURES.md) - Complete v3 features documentation
339
+ - [V4 Features Guide](./V4_FEATURES.md) - Complete v4 features documentation
340
+ - [Lambda Optimization Guide](./LAMBDA_OPTIMIZATION.md) - Lambda cold start optimization guide (v3.1)
276
341
  - [Verification Guide v2](./VERIFY_V2.md) - How to verify v2 features
277
342
  - [Verification Guide v3](./VERIFY_V3.md) - How to verify v3 features
278
343
 
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Optimized Lambda Handler Example
3
+ * v3.1: Best practices for reducing cold start time
4
+ */
5
+
6
+ const { NavisApp, response, getPool } = require('../src/index');
7
+ const LambdaHandler = require('../src/core/lambda-handler');
8
+ const { coldStartTracker } = require('../src/middleware/cold-start-tracker');
9
+
10
+ // ============================================
11
+ // CRITICAL: Initialize app OUTSIDE handler
12
+ // This ensures the app is reused across invocations
13
+ // ============================================
14
+ const app = new NavisApp();
15
+
16
+ // Add cold start tracking middleware
17
+ app.use(coldStartTracker);
18
+
19
+ // ============================================
20
+ // Register routes at MODULE LEVEL (not in handler)
21
+ // This runs once per container, not per invocation
22
+ // ============================================
23
+ app.get('/', (req, res) => {
24
+ res.statusCode = 200;
25
+ res.body = {
26
+ message: 'Welcome to Navis.js Lambda (Optimized)!',
27
+ optimized: true,
28
+ };
29
+ });
30
+
31
+ app.get('/health', (req, res) => {
32
+ res.statusCode = 200;
33
+ res.body = { status: 'ok' };
34
+ });
35
+
36
+ app.get('/warmup', (req, res) => {
37
+ res.statusCode = 200;
38
+ res.body = { status: 'warmed' };
39
+ });
40
+
41
+ // Example: Using ServiceClient with connection pooling
42
+ app.get('/external', async (req, res) => {
43
+ try {
44
+ // Get client from pool (reuses connections)
45
+ const client = getPool().get('http://api.example.com', {
46
+ timeout: 3000,
47
+ maxRetries: 2,
48
+ });
49
+
50
+ const result = await client.get('/data');
51
+ res.statusCode = 200;
52
+ res.body = { data: result.data };
53
+ } catch (error) {
54
+ res.statusCode = 500;
55
+ res.body = { error: error.message };
56
+ }
57
+ });
58
+
59
+ // ============================================
60
+ // OPTIMIZED HANDLER
61
+ // ============================================
62
+
63
+ // Create handler instance (reused across invocations)
64
+ const handler = new LambdaHandler(app, {
65
+ enableMetrics: true,
66
+ warmupPath: '/warmup',
67
+ });
68
+
69
+ // Cache the handler function (V8 optimization)
70
+ let cachedHandler = null;
71
+
72
+ /**
73
+ * Lambda handler - optimized for cold starts
74
+ * @param {Object} event - Lambda event
75
+ * @param {Object} context - Lambda context
76
+ */
77
+ exports.handler = async (event, context) => {
78
+ // Reuse handler function (V8 JIT optimization)
79
+ if (!cachedHandler) {
80
+ cachedHandler = async (event, context) => {
81
+ return await handler.handle(event, context);
82
+ };
83
+ }
84
+
85
+ return await cachedHandler(event, context);
86
+ };
87
+
88
+ /**
89
+ * Optional: Pre-warm function
90
+ * Can be called during container initialization
91
+ */
92
+ exports.preWarm = async () => {
93
+ // Pre-initialize any heavy operations
94
+ // This runs once per container
95
+ console.log('Pre-warming Lambda container...');
96
+
97
+ // Example: Pre-initialize service clients
98
+ const pool = getPool();
99
+ pool.get('http://api.example.com', { timeout: 3000 });
100
+
101
+ console.log('Pre-warming complete');
102
+ };
103
+
@@ -1,30 +1,31 @@
1
- const { NavisApp } = require('../src/index');
2
-
3
- const app = new NavisApp();
4
-
5
- // Middleware example
6
- app.use((req, res, next) => {
7
- console.log(`Lambda: ${req.method} ${req.path}`);
8
- next();
9
- });
10
-
11
- // Routes
12
- app.get('/', (req, res) => {
13
- res.statusCode = 200;
14
- res.body = { message: 'Welcome to Navis.js Lambda!' };
15
- });
16
-
17
- app.get('/health', (req, res) => {
18
- res.statusCode = 200;
19
- res.body = { status: 'ok' };
20
- });
21
-
22
- app.post('/echo', (req, res) => {
23
- res.statusCode = 200;
24
- res.body = { echo: req.body };
25
- });
26
-
27
- // Lambda handler
28
- exports.handler = async (event) => {
29
- return await app.handleLambda(event);
1
+ const { NavisApp } = require('../src/index');
2
+
3
+ // Initialize app at module level (reused across invocations)
4
+ const app = new NavisApp();
5
+
6
+ // Middleware example
7
+ app.use((req, res, next) => {
8
+ console.log(`Lambda: ${req.method} ${req.path}`);
9
+ next();
10
+ });
11
+
12
+ // Routes registered at module level (not in handler)
13
+ app.get('/', (req, res) => {
14
+ res.statusCode = 200;
15
+ res.body = { message: 'Welcome to Navis.js Lambda!' };
16
+ });
17
+
18
+ app.get('/health', (req, res) => {
19
+ res.statusCode = 200;
20
+ res.body = { status: 'ok' };
21
+ });
22
+
23
+ app.post('/echo', (req, res) => {
24
+ res.statusCode = 200;
25
+ res.body = { echo: req.body };
26
+ });
27
+
28
+ // Lambda handler - optimized for reuse
29
+ exports.handler = async (event) => {
30
+ return await app.handleLambda(event);
30
31
  };
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Navis.js v4 Features Demo
3
+ * Demonstrates advanced routing, validation, auth, rate limiting, and error handling
4
+ */
5
+
6
+ const {
7
+ NavisApp,
8
+ response,
9
+ validate,
10
+ authenticateJWT,
11
+ authorize,
12
+ rateLimit,
13
+ errorHandler,
14
+ asyncHandler,
15
+ NotFoundError,
16
+ BadRequestError,
17
+ } = require('../src/index');
18
+
19
+ const app = new NavisApp();
20
+
21
+ // Set error handler
22
+ app.setErrorHandler(errorHandler({
23
+ includeStack: true,
24
+ logErrors: true,
25
+ }));
26
+
27
+ // Global rate limiting
28
+ app.use(rateLimit({
29
+ windowMs: 60000, // 1 minute
30
+ max: 100, // 100 requests per minute
31
+ }));
32
+
33
+ // Example 1: Route Parameters
34
+ console.log('\n=== Route Parameters (v4) ===\n');
35
+
36
+ app.get('/users/:id', (req, res) => {
37
+ console.log('User ID:', req.params.id);
38
+ response.success(res, {
39
+ message: `Fetching user ${req.params.id}`,
40
+ userId: req.params.id,
41
+ });
42
+ });
43
+
44
+ app.get('/users/:id/posts/:postId', (req, res) => {
45
+ console.log('User ID:', req.params.id);
46
+ console.log('Post ID:', req.params.postId);
47
+ response.success(res, {
48
+ userId: req.params.id,
49
+ postId: req.params.postId,
50
+ });
51
+ });
52
+
53
+ // Example 2: Request Validation
54
+ console.log('\n=== Request Validation (v4) ===\n');
55
+
56
+ const createUserSchema = {
57
+ body: {
58
+ name: {
59
+ type: 'string',
60
+ required: true,
61
+ minLength: 3,
62
+ maxLength: 50,
63
+ },
64
+ email: {
65
+ type: 'string',
66
+ required: true,
67
+ format: 'email',
68
+ },
69
+ age: {
70
+ type: 'number',
71
+ min: 18,
72
+ max: 100,
73
+ },
74
+ },
75
+ };
76
+
77
+ app.post('/users', validate(createUserSchema), (req, res) => {
78
+ console.log('Validated body:', req.body);
79
+ response.success(res, {
80
+ message: 'User created',
81
+ user: req.body,
82
+ }, 201);
83
+ });
84
+
85
+ // Example 3: Authentication (Mock JWT)
86
+ console.log('\n=== Authentication (v4) ===\n');
87
+
88
+ // Mock JWT secret (in production, use environment variable)
89
+ process.env.JWT_SECRET = 'your-secret-key';
90
+
91
+ // Protected route
92
+ app.get('/profile', authenticateJWT(), (req, res) => {
93
+ response.success(res, {
94
+ message: 'Protected route',
95
+ user: req.user,
96
+ });
97
+ });
98
+
99
+ // Role-based authorization
100
+ app.get('/admin', authenticateJWT(), authorize(['admin']), (req, res) => {
101
+ response.success(res, {
102
+ message: 'Admin area',
103
+ user: req.user,
104
+ });
105
+ });
106
+
107
+ // Example 4: Error Handling
108
+ console.log('\n=== Error Handling (v4) ===\n');
109
+
110
+ app.get('/error-test', asyncHandler(async (req, res) => {
111
+ throw new BadRequestError('This is a bad request');
112
+ }));
113
+
114
+ app.get('/not-found-test', asyncHandler(async (req, res) => {
115
+ throw new NotFoundError('Resource not found');
116
+ }));
117
+
118
+ // Example 5: Rate Limiting per Route
119
+ console.log('\n=== Rate Limiting (v4) ===\n');
120
+
121
+ app.post('/login', rateLimit({ max: 5, windowMs: 60000 }), (req, res) => {
122
+ response.success(res, {
123
+ message: 'Login endpoint (5 requests per minute)',
124
+ });
125
+ });
126
+
127
+ // Example 6: Query Parameters
128
+ app.get('/search', (req, res) => {
129
+ console.log('Query params:', req.query);
130
+ response.success(res, {
131
+ query: req.query.q,
132
+ filters: req.query,
133
+ });
134
+ });
135
+
136
+ // Example 7: PATCH Method (v4)
137
+ app.patch('/users/:id', validate({
138
+ body: {
139
+ name: { type: 'string', required: false },
140
+ email: { type: 'string', required: false, format: 'email' },
141
+ },
142
+ }), (req, res) => {
143
+ response.success(res, {
144
+ message: `Updating user ${req.params.id}`,
145
+ updates: req.body,
146
+ });
147
+ });
148
+
149
+ // Start server
150
+ const PORT = 3000;
151
+ app.listen(PORT, () => {
152
+ console.log(`\n🚀 Navis.js v4 Features Demo Server`);
153
+ console.log(`📡 Listening on http://localhost:${PORT}\n`);
154
+ console.log('Available endpoints:');
155
+ console.log(' GET /users/:id');
156
+ console.log(' GET /users/:id/posts/:postId');
157
+ console.log(' POST /users (with validation)');
158
+ console.log(' GET /profile (requires JWT)');
159
+ console.log(' GET /admin (requires admin role)');
160
+ console.log(' GET /error-test');
161
+ console.log(' GET /not-found-test');
162
+ console.log(' POST /login (rate limited)');
163
+ console.log(' GET /search?q=test');
164
+ console.log(' PATCH /users/:id');
165
+ console.log('\n💡 Test with:');
166
+ console.log(' curl http://localhost:3000/users/123');
167
+ console.log(' curl -X POST http://localhost:3000/users -H "Content-Type: application/json" -d \'{"name":"John","email":"john@example.com","age":25}\'');
168
+ });
169
+
170
+ module.exports = app;
171
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navis.js",
3
- "version": "3.0.2",
3
+ "version": "4.0.0",
4
4
  "description": "A lightweight, serverless-first, microservice API framework designed for AWS Lambda and Node.js",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Authentication and Authorization Middleware
3
+ * v4: JWT, API Key, and role-based access control
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+
8
+ class AuthenticationError extends Error {
9
+ constructor(message, statusCode = 401) {
10
+ super(message);
11
+ this.name = 'AuthenticationError';
12
+ this.statusCode = statusCode;
13
+ }
14
+ }
15
+
16
+ class AuthorizationError extends Error {
17
+ constructor(message, statusCode = 403) {
18
+ super(message);
19
+ this.name = 'AuthorizationError';
20
+ this.statusCode = statusCode;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * JWT Authentication Middleware
26
+ * @param {Object} options - JWT options
27
+ * @returns {Function} - Middleware function
28
+ */
29
+ function authenticateJWT(options = {}) {
30
+ const {
31
+ secret = process.env.JWT_SECRET,
32
+ algorithms = ['HS256'],
33
+ header = 'authorization',
34
+ extractToken = (req) => {
35
+ const authHeader = req.headers[header] || req.headers[header.toLowerCase()];
36
+ if (!authHeader) return null;
37
+
38
+ // Support "Bearer <token>" format
39
+ const parts = authHeader.split(' ');
40
+ return parts.length === 2 && parts[0].toLowerCase() === 'bearer'
41
+ ? parts[1]
42
+ : authHeader;
43
+ },
44
+ } = options;
45
+
46
+ if (!secret) {
47
+ throw new Error('JWT secret is required');
48
+ }
49
+
50
+ return async (req, res, next) => {
51
+ try {
52
+ const token = extractToken(req);
53
+
54
+ if (!token) {
55
+ throw new AuthenticationError('No authentication token provided');
56
+ }
57
+
58
+ // Simple JWT decode and verify (for HS256)
59
+ // In production, use a proper JWT library like jsonwebtoken
60
+ const decoded = verifyJWT(token, secret);
61
+
62
+ if (!decoded) {
63
+ throw new AuthenticationError('Invalid or expired token');
64
+ }
65
+
66
+ // Attach user info to request
67
+ req.user = decoded;
68
+ req.token = token;
69
+
70
+ next();
71
+ } catch (error) {
72
+ if (error instanceof AuthenticationError) {
73
+ res.statusCode = error.statusCode;
74
+ res.body = { error: error.message };
75
+ return;
76
+ }
77
+ throw error;
78
+ }
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Simple JWT verification (HS256 only)
84
+ * For production, use jsonwebtoken library
85
+ * @private
86
+ */
87
+ function verifyJWT(token, secret) {
88
+ try {
89
+ const parts = token.split('.');
90
+ if (parts.length !== 3) {
91
+ return null;
92
+ }
93
+
94
+ const [headerB64, payloadB64, signatureB64] = parts;
95
+
96
+ // Verify signature
97
+ const signature = Buffer.from(signatureB64, 'base64url').toString('hex');
98
+ const expectedSignature = crypto
99
+ .createHmac('sha256', secret)
100
+ .update(`${headerB64}.${payloadB64}`)
101
+ .digest('hex');
102
+
103
+ if (signature !== expectedSignature) {
104
+ return null;
105
+ }
106
+
107
+ // Decode payload
108
+ const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
109
+
110
+ // Check expiration
111
+ if (payload.exp && Date.now() >= payload.exp * 1000) {
112
+ return null;
113
+ }
114
+
115
+ return payload;
116
+ } catch (error) {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * API Key Authentication Middleware
123
+ * @param {Object} options - API Key options
124
+ * @returns {Function} - Middleware function
125
+ */
126
+ function authenticateAPIKey(options = {}) {
127
+ const {
128
+ header = 'x-api-key',
129
+ keys = process.env.API_KEYS ? process.env.API_KEYS.split(',') : [],
130
+ validateKey = (key) => keys.includes(key),
131
+ } = options;
132
+
133
+ return async (req, res, next) => {
134
+ try {
135
+ const apiKey = req.headers[header] || req.headers[header.toLowerCase()];
136
+
137
+ if (!apiKey) {
138
+ throw new AuthenticationError('API key is required');
139
+ }
140
+
141
+ const isValid = await validateKey(apiKey);
142
+
143
+ if (!isValid) {
144
+ throw new AuthenticationError('Invalid API key');
145
+ }
146
+
147
+ req.apiKey = apiKey;
148
+ next();
149
+ } catch (error) {
150
+ if (error instanceof AuthenticationError) {
151
+ res.statusCode = error.statusCode;
152
+ res.body = { error: error.message };
153
+ return;
154
+ }
155
+ throw error;
156
+ }
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Role-based Authorization Middleware
162
+ * @param {string|Array} allowedRoles - Allowed roles
163
+ * @returns {Function} - Middleware function
164
+ */
165
+ function authorize(allowedRoles) {
166
+ const roles = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles];
167
+
168
+ return async (req, res, next) => {
169
+ try {
170
+ if (!req.user) {
171
+ throw new AuthenticationError('Authentication required');
172
+ }
173
+
174
+ const userRoles = req.user.roles || req.user.role ? [req.user.role] : [];
175
+
176
+ const hasRole = roles.some(role => userRoles.includes(role));
177
+
178
+ if (!hasRole) {
179
+ throw new AuthorizationError('Insufficient permissions');
180
+ }
181
+
182
+ next();
183
+ } catch (error) {
184
+ if (error instanceof AuthenticationError || error instanceof AuthorizationError) {
185
+ res.statusCode = error.statusCode;
186
+ res.body = { error: error.message };
187
+ return;
188
+ }
189
+ throw error;
190
+ }
191
+ };
192
+ }
193
+
194
+ /**
195
+ * Optional authentication (doesn't fail if no token)
196
+ * @param {Object} options - JWT options
197
+ * @returns {Function} - Middleware function
198
+ */
199
+ function optionalAuth(options = {}) {
200
+ const jwtAuth = authenticateJWT(options);
201
+
202
+ return async (req, res, next) => {
203
+ try {
204
+ await jwtAuth(req, res, () => {
205
+ // Continue even if auth fails
206
+ next();
207
+ });
208
+ } catch (error) {
209
+ // If auth fails, continue without user
210
+ next();
211
+ }
212
+ };
213
+ }
214
+
215
+ module.exports = {
216
+ authenticateJWT,
217
+ authenticateAPIKey,
218
+ authorize,
219
+ optionalAuth,
220
+ AuthenticationError,
221
+ AuthorizationError,
222
+ };
223
+