navis.js 1.0.0 → 2.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 +88 -18
- package/bin/generators/service.js +167 -0
- package/bin/navis.js +20 -2
- package/examples/v2-features-demo.js +190 -0
- package/package.json +1 -1
- package/src/index.js +30 -12
- package/src/utils/circuit-breaker.js +107 -0
- package/src/utils/retry.js +88 -0
- package/src/utils/service-client.js +234 -104
- package/src/utils/service-config.js +88 -0
- package/src/utils/service-discovery.js +184 -0
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,14 @@ 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
|
|
74
79
|
```
|
|
75
80
|
|
|
76
81
|
## Features
|
|
77
82
|
|
|
78
|
-
### v1
|
|
83
|
+
### v1
|
|
79
84
|
|
|
80
85
|
- ✅ HTTP routing (GET, POST, PUT, DELETE)
|
|
81
86
|
- ✅ Middleware support (`app.use()`)
|
|
@@ -84,14 +89,20 @@ navis start
|
|
|
84
89
|
- ✅ ServiceClient for service-to-service calls
|
|
85
90
|
- ✅ Timeout support
|
|
86
91
|
|
|
87
|
-
### v2 (
|
|
92
|
+
### v2 (Current)
|
|
93
|
+
|
|
94
|
+
- ✅ **Retry logic** - Automatic retry with exponential backoff
|
|
95
|
+
- ✅ **Circuit breaker** - Prevents cascading failures
|
|
96
|
+
- ✅ **Config-based services** - Centralized service configuration
|
|
97
|
+
- ✅ **Service discovery** - Health checks and load balancing
|
|
98
|
+
- ✅ **Additional HTTP methods** - PUT, DELETE, PATCH support
|
|
99
|
+
- ✅ **CLI generators** - `navis generate service` command
|
|
100
|
+
|
|
101
|
+
### v3 (Planned)
|
|
88
102
|
|
|
89
|
-
- 🔄 Retry logic
|
|
90
|
-
- 🔄 Circuit breaker
|
|
91
|
-
- 🔄 Config-based services
|
|
92
|
-
- 🔄 Service discovery
|
|
93
103
|
- 🔄 Async messaging (SQS / Kafka / NATS)
|
|
94
|
-
- 🔄
|
|
104
|
+
- 🔄 Advanced observability
|
|
105
|
+
- 🔄 Enhanced CLI features
|
|
95
106
|
|
|
96
107
|
## API Reference
|
|
97
108
|
|
|
@@ -107,19 +118,66 @@ navis start
|
|
|
107
118
|
- `app.listen(port, callback)` - Start HTTP server (Node.js)
|
|
108
119
|
- `app.handleLambda(event)` - Handle AWS Lambda event
|
|
109
120
|
|
|
110
|
-
### ServiceClient
|
|
121
|
+
### ServiceClient (v2 Enhanced)
|
|
111
122
|
|
|
123
|
+
```javascript
|
|
112
124
|
const { ServiceClient } = require('navis.js');
|
|
113
125
|
|
|
126
|
+
// Basic usage
|
|
114
127
|
const client = new ServiceClient('http://api.example.com', {
|
|
115
|
-
timeout: 5000,
|
|
128
|
+
timeout: 5000,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// With retry and circuit breaker (v2)
|
|
132
|
+
const resilientClient = new ServiceClient('http://api.example.com', {
|
|
133
|
+
timeout: 5000,
|
|
134
|
+
maxRetries: 3,
|
|
135
|
+
retryBaseDelay: 1000,
|
|
136
|
+
circuitBreaker: {
|
|
137
|
+
failureThreshold: 5,
|
|
138
|
+
resetTimeout: 60000,
|
|
139
|
+
},
|
|
116
140
|
});
|
|
117
141
|
|
|
118
|
-
//
|
|
119
|
-
|
|
142
|
+
// All HTTP methods (v2)
|
|
143
|
+
await client.get('/users');
|
|
144
|
+
await client.post('/users', { name: 'John' });
|
|
145
|
+
await client.put('/users/1', { name: 'Jane' }); // v2
|
|
146
|
+
await client.patch('/users/1', { name: 'Bob' }); // v2
|
|
147
|
+
await client.delete('/users/1'); // v2
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Service Configuration (v2)
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
const { ServiceConfig, ServiceClient } = require('navis.js');
|
|
154
|
+
|
|
155
|
+
const config = new ServiceConfig({
|
|
156
|
+
defaultOptions: {
|
|
157
|
+
timeout: 5000,
|
|
158
|
+
retry: { maxRetries: 3 },
|
|
159
|
+
circuitBreaker: { failureThreshold: 5 },
|
|
160
|
+
},
|
|
161
|
+
});
|
|
120
162
|
|
|
121
|
-
|
|
122
|
-
const
|
|
163
|
+
config.register('userService', 'http://localhost:3001');
|
|
164
|
+
const userConfig = config.get('userService');
|
|
165
|
+
const client = new ServiceClient(userConfig.baseUrl, userConfig);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Service Discovery (v2)
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
const { ServiceDiscovery, ServiceClient } = require('navis.js');
|
|
172
|
+
|
|
173
|
+
const discovery = new ServiceDiscovery();
|
|
174
|
+
discovery.register('api', [
|
|
175
|
+
'http://api1.example.com',
|
|
176
|
+
'http://api2.example.com',
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
const url = discovery.getNext('api'); // Round-robin
|
|
180
|
+
const client = new ServiceClient(url);
|
|
123
181
|
```
|
|
124
182
|
|
|
125
183
|
### Response Helpers
|
|
@@ -141,18 +199,30 @@ See the `examples/` directory:
|
|
|
141
199
|
- `server.js` - Node.js HTTP server example
|
|
142
200
|
- `lambda.js` - AWS Lambda handler example
|
|
143
201
|
- `service-client-demo.js` - ServiceClient usage example
|
|
202
|
+
- `v2-features-demo.js` - v2 features demonstration (retry, circuit breaker, etc.)
|
|
144
203
|
|
|
145
204
|
## Roadmap
|
|
146
205
|
|
|
147
|
-
### v1
|
|
206
|
+
### v1 ✅
|
|
148
207
|
Core functionality: routing, middleware, Lambda support, ServiceClient
|
|
149
208
|
|
|
150
|
-
### v2 (
|
|
151
|
-
Resilience patterns: retry, circuit breaker, service discovery
|
|
209
|
+
### v2 ✅ (Current)
|
|
210
|
+
Resilience patterns: retry, circuit breaker, service discovery, CLI generators
|
|
152
211
|
|
|
153
212
|
### v3 (Future)
|
|
154
|
-
Advanced features: async messaging, observability,
|
|
213
|
+
Advanced features: async messaging (SQS/Kafka/NATS), observability, enhanced CLI
|
|
214
|
+
|
|
215
|
+
## Documentation
|
|
216
|
+
|
|
217
|
+
- [V2 Features Guide](./V2_FEATURES.md) - Complete v2 features documentation
|
|
218
|
+
- [Verification Guide](./VERIFY_V2.md) - How to verify all features
|
|
155
219
|
|
|
156
220
|
## License
|
|
157
221
|
|
|
158
|
-
MIT
|
|
222
|
+
MIT
|
|
223
|
+
|
|
224
|
+
## Author
|
|
225
|
+
|
|
226
|
+
**Syed Iman Ali**
|
|
227
|
+
|
|
228
|
+
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,26 @@ if (command === 'start') {
|
|
|
33
33
|
process.exit(code);
|
|
34
34
|
});
|
|
35
35
|
} else if (command === 'generate') {
|
|
36
|
-
|
|
37
|
-
|
|
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
|
+
}
|
|
38
56
|
} else {
|
|
39
57
|
console.log('Navis.js CLI');
|
|
40
58
|
console.log('');
|
|
@@ -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
|
+
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
|
-
const NavisApp = require('./core/app');
|
|
2
|
-
const ServiceClient = require('./utils/service-client');
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
const NavisApp = require('./core/app');
|
|
2
|
+
const ServiceClient = require('./utils/service-client');
|
|
3
|
+
const ServiceConfig = require('./utils/service-config');
|
|
4
|
+
const ServiceDiscovery = require('./utils/service-discovery');
|
|
5
|
+
const CircuitBreaker = require('./utils/circuit-breaker');
|
|
6
|
+
const { success, error } = require('./utils/response');
|
|
7
|
+
const { retry, shouldRetryHttpStatus } = require('./utils/retry');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
// Core
|
|
11
|
+
NavisApp,
|
|
12
|
+
|
|
13
|
+
// Service Client (v2 enhanced)
|
|
14
|
+
ServiceClient,
|
|
15
|
+
|
|
16
|
+
// v2 Features
|
|
17
|
+
ServiceConfig,
|
|
18
|
+
ServiceDiscovery,
|
|
19
|
+
CircuitBreaker,
|
|
20
|
+
|
|
21
|
+
// Utilities
|
|
22
|
+
response: {
|
|
23
|
+
success,
|
|
24
|
+
error,
|
|
25
|
+
},
|
|
26
|
+
retry: {
|
|
27
|
+
retry,
|
|
28
|
+
shouldRetryHttpStatus,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit Breaker - Prevents cascading failures in microservices
|
|
3
|
+
* v2: Circuit breaker pattern implementation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class CircuitBreaker {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.failureThreshold = options.failureThreshold || 5; // Open circuit after 5 failures
|
|
9
|
+
this.resetTimeout = options.resetTimeout || 60000; // 60 seconds
|
|
10
|
+
this.monitoringWindow = options.monitoringWindow || 10000; // 10 seconds
|
|
11
|
+
|
|
12
|
+
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
|
|
13
|
+
this.failureCount = 0;
|
|
14
|
+
this.successCount = 0;
|
|
15
|
+
this.lastFailureTime = null;
|
|
16
|
+
this.nextAttemptTime = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Record a successful request
|
|
21
|
+
*/
|
|
22
|
+
recordSuccess() {
|
|
23
|
+
if (this.state === 'HALF_OPEN') {
|
|
24
|
+
this.successCount++;
|
|
25
|
+
if (this.successCount >= 2) {
|
|
26
|
+
// Reset to CLOSED after 2 successes in HALF_OPEN
|
|
27
|
+
this.state = 'CLOSED';
|
|
28
|
+
this.failureCount = 0;
|
|
29
|
+
this.successCount = 0;
|
|
30
|
+
}
|
|
31
|
+
} else if (this.state === 'CLOSED') {
|
|
32
|
+
this.failureCount = 0; // Reset failure count on success
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Record a failed request
|
|
38
|
+
*/
|
|
39
|
+
recordFailure() {
|
|
40
|
+
this.failureCount++;
|
|
41
|
+
this.lastFailureTime = Date.now();
|
|
42
|
+
|
|
43
|
+
if (this.state === 'CLOSED' && this.failureCount >= this.failureThreshold) {
|
|
44
|
+
// Open the circuit
|
|
45
|
+
this.state = 'OPEN';
|
|
46
|
+
this.nextAttemptTime = Date.now() + this.resetTimeout;
|
|
47
|
+
} else if (this.state === 'HALF_OPEN') {
|
|
48
|
+
// Failed in half-open, go back to open
|
|
49
|
+
this.state = 'OPEN';
|
|
50
|
+
this.nextAttemptTime = Date.now() + this.resetTimeout;
|
|
51
|
+
this.successCount = 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if request should be allowed
|
|
57
|
+
* @returns {boolean} - true if request should proceed
|
|
58
|
+
*/
|
|
59
|
+
canAttempt() {
|
|
60
|
+
if (this.state === 'CLOSED') {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.state === 'OPEN') {
|
|
65
|
+
if (Date.now() >= this.nextAttemptTime) {
|
|
66
|
+
// Transition to HALF_OPEN
|
|
67
|
+
this.state = 'HALF_OPEN';
|
|
68
|
+
this.successCount = 0;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false; // Circuit is open, reject request
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.state === 'HALF_OPEN') {
|
|
75
|
+
return true; // Allow limited requests in half-open state
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get current circuit state
|
|
83
|
+
*/
|
|
84
|
+
getState() {
|
|
85
|
+
return {
|
|
86
|
+
state: this.state,
|
|
87
|
+
failureCount: this.failureCount,
|
|
88
|
+
successCount: this.successCount,
|
|
89
|
+
lastFailureTime: this.lastFailureTime,
|
|
90
|
+
nextAttemptTime: this.nextAttemptTime,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Reset circuit breaker
|
|
96
|
+
*/
|
|
97
|
+
reset() {
|
|
98
|
+
this.state = 'CLOSED';
|
|
99
|
+
this.failureCount = 0;
|
|
100
|
+
this.successCount = 0;
|
|
101
|
+
this.lastFailureTime = null;
|
|
102
|
+
this.nextAttemptTime = null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = CircuitBreaker;
|
|
107
|
+
|