navis.js 1.0.0 → 3.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
@@ -2,6 +2,8 @@
2
2
 
3
3
  A lightweight, serverless-first, microservice API framework designed for AWS Lambda and Node.js.
4
4
 
5
+ **Author:** Syed Iman Ali
6
+
5
7
  ## Philosophy
6
8
 
7
9
  Navis.js is "Express for serverless microservices — but simpler."
@@ -71,11 +73,20 @@ exports.handler = async (event) => {
71
73
  ```bash
72
74
  # Start example server
73
75
  navis start
76
+
77
+ # Generate a new microservice (v2)
78
+ navis generate service my-service
79
+
80
+ # Run verification tests (v3)
81
+ navis test
82
+
83
+ # Show metrics endpoint info (v3)
84
+ navis metrics
74
85
  ```
75
86
 
76
87
  ## Features
77
88
 
78
- ### v1 (Current)
89
+ ### v1
79
90
 
80
91
  - ✅ HTTP routing (GET, POST, PUT, DELETE)
81
92
  - ✅ Middleware support (`app.use()`)
@@ -84,14 +95,22 @@ navis start
84
95
  - ✅ ServiceClient for service-to-service calls
85
96
  - ✅ Timeout support
86
97
 
87
- ### v2 (Planned)
98
+ ### v2
99
+
100
+ - ✅ **Retry logic** - Automatic retry with exponential backoff
101
+ - ✅ **Circuit breaker** - Prevents cascading failures
102
+ - ✅ **Config-based services** - Centralized service configuration
103
+ - ✅ **Service discovery** - Health checks and load balancing
104
+ - ✅ **Additional HTTP methods** - PUT, DELETE, PATCH support
105
+ - ✅ **CLI generators** - `navis generate service` command
88
106
 
89
- - 🔄 Retry logic
90
- - 🔄 Circuit breaker
91
- - 🔄 Config-based services
92
- - 🔄 Service discovery
93
- - 🔄 Async messaging (SQS / Kafka / NATS)
94
- - 🔄 CLI generators (`navis generate service`)
107
+ ### v3 (Current)
108
+
109
+ - **Async messaging** - SQS, Kafka, and NATS adapters
110
+ - **Structured logging** - Multi-level logging with context
111
+ - **Metrics collection** - Counters, gauges, histograms with Prometheus export
112
+ - **Distributed tracing** - Trace and span management
113
+ - ✅ **Enhanced CLI** - Test and metrics commands
95
114
 
96
115
  ## API Reference
97
116
 
@@ -107,19 +126,66 @@ navis start
107
126
  - `app.listen(port, callback)` - Start HTTP server (Node.js)
108
127
  - `app.handleLambda(event)` - Handle AWS Lambda event
109
128
 
110
- ### ServiceClient
129
+ ### ServiceClient (v2 Enhanced)
111
130
 
131
+ ```javascript
112
132
  const { ServiceClient } = require('navis.js');
113
133
 
134
+ // Basic usage
114
135
  const client = new ServiceClient('http://api.example.com', {
115
- timeout: 5000, // milliseconds
136
+ timeout: 5000,
116
137
  });
117
138
 
118
- // GET request
119
- const response = await client.get('/users');
139
+ // With retry and circuit breaker (v2)
140
+ const resilientClient = new ServiceClient('http://api.example.com', {
141
+ timeout: 5000,
142
+ maxRetries: 3,
143
+ retryBaseDelay: 1000,
144
+ circuitBreaker: {
145
+ failureThreshold: 5,
146
+ resetTimeout: 60000,
147
+ },
148
+ });
149
+
150
+ // All HTTP methods (v2)
151
+ await client.get('/users');
152
+ await client.post('/users', { name: 'John' });
153
+ await client.put('/users/1', { name: 'Jane' }); // v2
154
+ await client.patch('/users/1', { name: 'Bob' }); // v2
155
+ await client.delete('/users/1'); // v2
156
+ ```
157
+
158
+ ### Service Configuration (v2)
159
+
160
+ ```javascript
161
+ const { ServiceConfig, ServiceClient } = require('navis.js');
162
+
163
+ const config = new ServiceConfig({
164
+ defaultOptions: {
165
+ timeout: 5000,
166
+ retry: { maxRetries: 3 },
167
+ circuitBreaker: { failureThreshold: 5 },
168
+ },
169
+ });
170
+
171
+ config.register('userService', 'http://localhost:3001');
172
+ const userConfig = config.get('userService');
173
+ const client = new ServiceClient(userConfig.baseUrl, userConfig);
174
+ ```
175
+
176
+ ### Service Discovery (v2)
177
+
178
+ ```javascript
179
+ const { ServiceDiscovery, ServiceClient } = require('navis.js');
180
+
181
+ const discovery = new ServiceDiscovery();
182
+ discovery.register('api', [
183
+ 'http://api1.example.com',
184
+ 'http://api2.example.com',
185
+ ]);
120
186
 
121
- // POST request
122
- const result = await client.post('/users', { name: 'John' });
187
+ const url = discovery.getNext('api'); // Round-robin
188
+ const client = new ServiceClient(url);
123
189
  ```
124
190
 
125
191
  ### Response Helpers
@@ -134,6 +200,54 @@ response.success(res, { data: 'value' }, 200);
134
200
  response.error(res, 'Error message', 500);
135
201
  ```
136
202
 
203
+ ### Observability (v3)
204
+
205
+ ```javascript
206
+ const { Logger, Metrics, Tracer } = require('navis.js');
207
+
208
+ // Structured logging
209
+ const logger = new Logger({ level: 'INFO', context: { service: 'api' } });
210
+ logger.info('User logged in', { userId: 123 });
211
+
212
+ // Metrics collection
213
+ const metrics = new Metrics();
214
+ metrics.increment('api_calls', 1, { endpoint: '/users' });
215
+ metrics.recordRequest('GET', '/users', 150, 200);
216
+
217
+ // Expose Prometheus metrics
218
+ app.get('/metrics', (req, res) => {
219
+ res.setHeader('Content-Type', 'text/plain');
220
+ res.end(metrics.toPrometheus());
221
+ });
222
+
223
+ // Distributed tracing
224
+ const tracer = new Tracer({ serviceName: 'api' });
225
+ const traceId = tracer.startTrace('user-operation');
226
+ const spanId = tracer.startSpan('db-query', { traceId });
227
+ tracer.finishSpan(spanId, { status: 'ok' });
228
+ ```
229
+
230
+ ### Async Messaging (v3)
231
+
232
+ ```javascript
233
+ const { SQSMessaging, KafkaMessaging, NATSMessaging } = require('navis.js');
234
+
235
+ // AWS SQS (requires @aws-sdk/client-sqs)
236
+ const sqs = new SQSMessaging({ region: 'us-east-1' });
237
+ await sqs.connect();
238
+ await sqs.publish(queueUrl, { userId: 123, action: 'user.created' });
239
+
240
+ // Kafka (requires kafkajs)
241
+ const kafka = new KafkaMessaging({ brokers: ['localhost:9092'] });
242
+ await kafka.connect();
243
+ await kafka.publish('user-events', { userId: 123, event: 'created' });
244
+
245
+ // NATS (requires nats)
246
+ const nats = new NATSMessaging({ servers: ['nats://localhost:4222'] });
247
+ await nats.connect();
248
+ await nats.publish('user.created', { userId: 123 });
249
+ ```
250
+
137
251
  ## Examples
138
252
 
139
253
  See the `examples/` directory:
@@ -141,18 +255,33 @@ See the `examples/` directory:
141
255
  - `server.js` - Node.js HTTP server example
142
256
  - `lambda.js` - AWS Lambda handler example
143
257
  - `service-client-demo.js` - ServiceClient usage example
258
+ - `v2-features-demo.js` - v2 features demonstration (retry, circuit breaker, etc.)
259
+ - `v3-features-demo.js` - v3 features demonstration (messaging, observability, etc.)
144
260
 
145
261
  ## Roadmap
146
262
 
147
- ### v1 (Current)
263
+ ### v1
148
264
  Core functionality: routing, middleware, Lambda support, ServiceClient
149
265
 
150
- ### v2 (Next)
151
- Resilience patterns: retry, circuit breaker, service discovery
266
+ ### v2
267
+ Resilience patterns: retry, circuit breaker, service discovery, CLI generators
268
+
269
+ ### v3 ✅ (Current)
270
+ Advanced features: async messaging (SQS/Kafka/NATS), observability, enhanced CLI
152
271
 
153
- ### v3 (Future)
154
- Advanced features: async messaging, observability, advanced CLI
272
+ ## Documentation
273
+
274
+ - [V2 Features Guide](./V2_FEATURES.md) - Complete v2 features documentation
275
+ - [V3 Features Guide](./V3_FEATURES.md) - Complete v3 features documentation
276
+ - [Verification Guide v2](./VERIFY_V2.md) - How to verify v2 features
277
+ - [Verification Guide v3](./VERIFY_V3.md) - How to verify v3 features
155
278
 
156
279
  ## License
157
280
 
158
- MIT
281
+ MIT
282
+
283
+ ## Author
284
+
285
+ **Syed Iman Ali**
286
+
287
+ Created with ❤️ for the serverless microservices community.
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Service Generator
3
+ * v2: Generate service boilerplate
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Generate service boilerplate
11
+ * @param {string} serviceName - Name of the service
12
+ * @param {string} targetDir - Target directory (default: current directory)
13
+ */
14
+ function generateService(serviceName, targetDir = process.cwd()) {
15
+ const serviceDir = path.join(targetDir, serviceName);
16
+
17
+ // Create service directory
18
+ if (!fs.existsSync(serviceDir)) {
19
+ fs.mkdirSync(serviceDir, { recursive: true });
20
+ }
21
+
22
+ // Generate service.js
23
+ const serviceTemplate = `const { NavisApp, response } = require('navis.js');
24
+
25
+ const app = new NavisApp();
26
+
27
+ // Middleware
28
+ app.use((req, res, next) => {
29
+ console.log(\`\${req.method} \${req.url}\`);
30
+ next();
31
+ });
32
+
33
+ // Routes
34
+ app.get('/', (req, res) => {
35
+ response.success(res, {
36
+ service: '${serviceName}',
37
+ message: 'Welcome to ${serviceName} service',
38
+ version: '1.0.0'
39
+ });
40
+ });
41
+
42
+ app.get('/health', (req, res) => {
43
+ response.success(res, { status: 'ok', service: '${serviceName}' });
44
+ });
45
+
46
+ // Add your routes here
47
+ // app.get('/api/users', (req, res) => { ... });
48
+ // app.post('/api/users', (req, res) => { ... });
49
+
50
+ // Start server
51
+ const PORT = process.env.PORT || 3000;
52
+ app.listen(PORT, () => {
53
+ console.log(\`${serviceName} service running on http://localhost:\${PORT}\`);
54
+ });
55
+
56
+ module.exports = app;
57
+ `;
58
+
59
+ fs.writeFileSync(path.join(serviceDir, 'service.js'), serviceTemplate);
60
+
61
+ // Generate lambda.js
62
+ const lambdaTemplate = `const { NavisApp } = require('navis.js');
63
+
64
+ const app = new NavisApp();
65
+
66
+ // Middleware
67
+ app.use((req, res, next) => {
68
+ console.log(\`Lambda: \${req.method} \${req.path}\`);
69
+ next();
70
+ });
71
+
72
+ // Routes
73
+ app.get('/', (req, res) => {
74
+ res.statusCode = 200;
75
+ res.body = {
76
+ service: '${serviceName}',
77
+ message: 'Welcome to ${serviceName} service (Lambda)',
78
+ version: '1.0.0'
79
+ };
80
+ });
81
+
82
+ app.get('/health', (req, res) => {
83
+ res.statusCode = 200;
84
+ res.body = { status: 'ok', service: '${serviceName}' };
85
+ });
86
+
87
+ // Lambda handler
88
+ exports.handler = async (event) => {
89
+ return await app.handleLambda(event);
90
+ };
91
+ `;
92
+
93
+ fs.writeFileSync(path.join(serviceDir, 'lambda.js'), lambdaTemplate);
94
+
95
+ // Generate package.json
96
+ const packageJson = {
97
+ name: serviceName,
98
+ version: '1.0.0',
99
+ description: `${serviceName} microservice`,
100
+ main: 'service.js',
101
+ scripts: {
102
+ start: 'node service.js',
103
+ 'start:lambda': 'node lambda.js',
104
+ },
105
+ dependencies: {
106
+ 'navis.js': '^1.0.0',
107
+ },
108
+ };
109
+
110
+ fs.writeFileSync(
111
+ path.join(serviceDir, 'package.json'),
112
+ JSON.stringify(packageJson, null, 2)
113
+ );
114
+
115
+ // Generate README.md
116
+ const readmeTemplate = `# ${serviceName}
117
+
118
+ Microservice generated with Navis.js
119
+
120
+ ## Installation
121
+
122
+ \`\`\`bash
123
+ npm install
124
+ \`\`\`
125
+
126
+ ## Running
127
+
128
+ ### Local Development
129
+
130
+ \`\`\`bash
131
+ npm start
132
+ # or
133
+ node service.js
134
+ \`\`\`
135
+
136
+ ### AWS Lambda
137
+
138
+ Deploy \`lambda.js\` to AWS Lambda and configure API Gateway.
139
+
140
+ ## API Endpoints
141
+
142
+ - \`GET /\` - Service information
143
+ - \`GET /health\` - Health check
144
+
145
+ ## Development
146
+
147
+ Add your routes in \`service.js\`:
148
+
149
+ \`\`\`javascript
150
+ app.get('/api/users', (req, res) => {
151
+ // Your route handler
152
+ });
153
+ \`\`\`
154
+ `;
155
+
156
+ fs.writeFileSync(path.join(serviceDir, 'README.md'), readmeTemplate);
157
+
158
+ console.log(`✅ Service "${serviceName}" generated successfully!`);
159
+ console.log(`📁 Location: ${serviceDir}`);
160
+ console.log(`\nNext steps:`);
161
+ console.log(` cd ${serviceName}`);
162
+ console.log(` npm install`);
163
+ console.log(` npm start`);
164
+ }
165
+
166
+ module.exports = { generateService };
167
+
package/bin/navis.js CHANGED
@@ -33,8 +33,57 @@ if (command === 'start') {
33
33
  process.exit(code);
34
34
  });
35
35
  } else if (command === 'generate') {
36
- // TODO v2: Implement navis generate service
37
- console.log('Generator commands coming in v2');
36
+ const subcommand = process.argv[3];
37
+
38
+ if (subcommand === 'service') {
39
+ const serviceName = process.argv[4];
40
+
41
+ if (!serviceName) {
42
+ console.error('Error: Service name is required');
43
+ console.log('Usage: navis generate service <service-name>');
44
+ process.exit(1);
45
+ }
46
+
47
+ const { generateService } = require('./generators/service');
48
+ generateService(serviceName);
49
+ } else {
50
+ console.log('Generator commands:');
51
+ console.log(' navis generate service <name> Generate a new microservice');
52
+ console.log('');
53
+ console.log('Example:');
54
+ console.log(' navis generate service user-service');
55
+ }
56
+ } else if (command === 'deploy') {
57
+ // v3: Deploy command (placeholder for future deployment features)
58
+ console.log('Deploy command - Coming soon');
59
+ console.log('This will support deployment to AWS Lambda, Docker, etc.');
60
+ } else if (command === 'test') {
61
+ // v3: Test command
62
+ const { spawn } = require('child_process');
63
+ const testPath = path.join(__dirname, '..', 'verify-v2.js');
64
+
65
+ if (fs.existsSync(testPath)) {
66
+ const test = spawn('node', [testPath], {
67
+ stdio: 'inherit',
68
+ cwd: path.join(__dirname, '..'),
69
+ });
70
+
71
+ test.on('exit', (code) => {
72
+ process.exit(code);
73
+ });
74
+ } else {
75
+ console.log('No test file found');
76
+ }
77
+ } else if (command === 'metrics') {
78
+ // v3: Show metrics endpoint
79
+ console.log('Metrics endpoint:');
80
+ console.log(' Add /metrics route to your app to expose Prometheus metrics');
81
+ console.log('');
82
+ console.log('Example:');
83
+ console.log(' app.get("/metrics", (req, res) => {');
84
+ console.log(' res.setHeader("Content-Type", "text/plain");');
85
+ console.log(' res.end(metrics.toPrometheus());');
86
+ console.log(' });');
38
87
  } else {
39
88
  console.log('Navis.js CLI');
40
89
  console.log('');
@@ -43,4 +92,9 @@ if (command === 'start') {
43
92
  console.log('');
44
93
  console.log('v2 commands:');
45
94
  console.log(' navis generate Generate service boilerplate');
95
+ console.log('');
96
+ console.log('v3 commands:');
97
+ console.log(' navis test Run verification tests');
98
+ console.log(' navis metrics Show metrics endpoint info');
99
+ console.log(' navis deploy Deploy service (coming soon)');
46
100
  }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Navis.js v2 Features Demo
3
+ * Demonstrates retry logic, circuit breaker, service config, and service discovery
4
+ */
5
+
6
+ const {
7
+ ServiceClient,
8
+ ServiceConfig,
9
+ ServiceDiscovery,
10
+ retry,
11
+ } = require('../src/index');
12
+
13
+ // Example 1: ServiceClient with retry and circuit breaker
14
+ async function demoServiceClient() {
15
+ console.log('\n=== ServiceClient with Retry & Circuit Breaker ===\n');
16
+
17
+ const client = new ServiceClient('http://localhost:3000', {
18
+ timeout: 3000,
19
+ maxRetries: 3,
20
+ retryBaseDelay: 1000,
21
+ circuitBreaker: {
22
+ failureThreshold: 3,
23
+ resetTimeout: 10000,
24
+ },
25
+ });
26
+
27
+ try {
28
+ const response = await client.get('/health');
29
+ console.log('✅ Request successful:', response.data);
30
+ } catch (error) {
31
+ if (error.circuitBreakerOpen) {
32
+ console.log('❌ Circuit breaker is OPEN:', error.message);
33
+ console.log('Circuit state:', error.circuitState);
34
+ } else {
35
+ console.log('❌ Request failed:', error.message);
36
+ }
37
+ }
38
+
39
+ // Check circuit breaker state
40
+ const state = client.getCircuitBreakerState();
41
+ if (state) {
42
+ console.log('Circuit breaker state:', state);
43
+ }
44
+ }
45
+
46
+ // Example 2: Service Configuration
47
+ async function demoServiceConfig() {
48
+ console.log('\n=== Service Configuration ===\n');
49
+
50
+ const config = new ServiceConfig({
51
+ defaultOptions: {
52
+ timeout: 5000,
53
+ retry: {
54
+ maxRetries: 3,
55
+ baseDelay: 1000,
56
+ },
57
+ circuitBreaker: {
58
+ failureThreshold: 5,
59
+ resetTimeout: 60000,
60
+ },
61
+ },
62
+ });
63
+
64
+ // Register services
65
+ config.register('userService', 'http://localhost:3001', {
66
+ timeout: 3000,
67
+ });
68
+
69
+ config.register('orderService', 'http://localhost:3002', {
70
+ retry: {
71
+ maxRetries: 5,
72
+ },
73
+ });
74
+
75
+ console.log('Registered services:', Object.keys(config.getAll()));
76
+
77
+ // Get service config
78
+ const userServiceConfig = config.get('userService');
79
+ console.log('User service config:', userServiceConfig);
80
+ }
81
+
82
+ // Example 3: Service Discovery
83
+ async function demoServiceDiscovery() {
84
+ console.log('\n=== Service Discovery ===\n');
85
+
86
+ const discovery = new ServiceDiscovery({
87
+ healthCheckInterval: 10000,
88
+ healthCheckPath: '/health',
89
+ });
90
+
91
+ // Register service with multiple endpoints
92
+ discovery.register('api', [
93
+ 'http://localhost:3000',
94
+ 'http://localhost:3001',
95
+ 'http://localhost:3002',
96
+ ]);
97
+
98
+ // Get next available service (round-robin)
99
+ const url1 = discovery.getNext('api');
100
+ console.log('Next service URL:', url1);
101
+
102
+ const url2 = discovery.getNext('api');
103
+ console.log('Next service URL:', url2);
104
+
105
+ // Get all healthy services
106
+ const healthy = discovery.getHealthy('api');
107
+ console.log('Healthy services:', healthy);
108
+ }
109
+
110
+ // Example 4: Using retry utility directly
111
+ async function demoRetryUtility() {
112
+ console.log('\n=== Retry Utility ===\n');
113
+
114
+ let attemptCount = 0;
115
+
116
+ const flakyFunction = async () => {
117
+ attemptCount++;
118
+ console.log(`Attempt ${attemptCount}...`);
119
+
120
+ if (attemptCount < 3) {
121
+ throw new Error('Temporary failure');
122
+ }
123
+
124
+ return { success: true, attempt: attemptCount };
125
+ };
126
+
127
+ try {
128
+ const result = await retry(flakyFunction, {
129
+ maxRetries: 3,
130
+ baseDelay: 500,
131
+ });
132
+ console.log('✅ Success after retries:', result);
133
+ } catch (error) {
134
+ console.log('❌ Failed after all retries:', error.message);
135
+ }
136
+ }
137
+
138
+ // Example 5: ServiceClient with additional HTTP methods
139
+ async function demoHttpMethods() {
140
+ console.log('\n=== Additional HTTP Methods ===\n');
141
+
142
+ const client = new ServiceClient('http://localhost:3000', {
143
+ retry: false, // Disable retry for demo
144
+ });
145
+
146
+ try {
147
+ // PUT request
148
+ const putResponse = await client.put('/api/users/1', { name: 'John' });
149
+ console.log('PUT response:', putResponse.data);
150
+
151
+ // DELETE request
152
+ const deleteResponse = await client.delete('/api/users/1');
153
+ console.log('DELETE response:', deleteResponse.data);
154
+
155
+ // PATCH request
156
+ const patchResponse = await client.patch('/api/users/1', { name: 'Jane' });
157
+ console.log('PATCH response:', patchResponse.data);
158
+ } catch (error) {
159
+ console.log('Request failed (expected if server not running):', error.message);
160
+ }
161
+ }
162
+
163
+ // Run all demos
164
+ async function runDemos() {
165
+ console.log('🚀 Navis.js v2 Features Demo\n');
166
+ console.log('='.repeat(50));
167
+
168
+ await demoServiceConfig();
169
+ await demoServiceDiscovery();
170
+ await demoRetryUtility();
171
+ await demoHttpMethods();
172
+
173
+ // ServiceClient demo requires a running server
174
+ console.log('\n💡 Note: ServiceClient demo requires a running server on port 3000');
175
+ console.log(' Run: node examples/server.js');
176
+ }
177
+
178
+ // Run if called directly
179
+ if (require.main === module) {
180
+ runDemos().catch(console.error);
181
+ }
182
+
183
+ module.exports = {
184
+ demoServiceClient,
185
+ demoServiceConfig,
186
+ demoServiceDiscovery,
187
+ demoRetryUtility,
188
+ demoHttpMethods,
189
+ };
190
+