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.
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Service Discovery - Basic service discovery mechanism
3
+ * v2: Service discovery for microservice architectures
4
+ */
5
+
6
+ class ServiceDiscovery {
7
+ constructor(options = {}) {
8
+ this.services = new Map(); // name -> { urls: [], currentIndex: 0, health: {} }
9
+ this.healthCheckInterval = options.healthCheckInterval || 30000; // 30 seconds
10
+ this.healthCheckTimeout = options.healthCheckTimeout || 5000; // 5 seconds
11
+ this.healthCheckPath = options.healthCheckPath || '/health';
12
+ this.enabled = options.enabled !== false; // Enabled by default
13
+ }
14
+
15
+ /**
16
+ * Register a service with multiple endpoints (for load balancing)
17
+ * @param {string} name - Service name
18
+ * @param {string|Array} urls - Service URL(s)
19
+ */
20
+ register(name, urls) {
21
+ const urlArray = Array.isArray(urls) ? urls : [urls];
22
+ this.services.set(name, {
23
+ urls: urlArray,
24
+ currentIndex: 0,
25
+ health: {},
26
+ });
27
+
28
+ if (this.enabled) {
29
+ this._startHealthCheck(name);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Get next available service URL (round-robin)
35
+ * @param {string} name - Service name
36
+ * @returns {string|null} - Service URL or null if not found
37
+ */
38
+ getNext(name) {
39
+ const service = this.services.get(name);
40
+ if (!service || service.urls.length === 0) {
41
+ return null;
42
+ }
43
+
44
+ // Simple round-robin
45
+ const url = service.urls[service.currentIndex];
46
+ service.currentIndex = (service.currentIndex + 1) % service.urls.length;
47
+
48
+ return url;
49
+ }
50
+
51
+ /**
52
+ * Get all healthy URLs for a service
53
+ * @param {string} name - Service name
54
+ * @returns {Array} - Array of healthy URLs
55
+ */
56
+ getHealthy(name) {
57
+ const service = this.services.get(name);
58
+ if (!service) {
59
+ return [];
60
+ }
61
+
62
+ return service.urls.filter(url => {
63
+ const health = service.health[url];
64
+ return !health || health.status === 'healthy' || Date.now() - health.lastCheck > this.healthCheckInterval * 2;
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Mark service URL as unhealthy
70
+ * @param {string} name - Service name
71
+ * @param {string} url - Service URL
72
+ */
73
+ markUnhealthy(name, url) {
74
+ const service = this.services.get(name);
75
+ if (service) {
76
+ service.health[url] = {
77
+ status: 'unhealthy',
78
+ lastCheck: Date.now(),
79
+ };
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Mark service URL as healthy
85
+ * @param {string} name - Service name
86
+ * @param {string} url - Service URL
87
+ */
88
+ markHealthy(name, url) {
89
+ const service = this.services.get(name);
90
+ if (service) {
91
+ service.health[url] = {
92
+ status: 'healthy',
93
+ lastCheck: Date.now(),
94
+ };
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Start health check for a service
100
+ * @private
101
+ */
102
+ _startHealthCheck(name) {
103
+ const service = this.services.get(name);
104
+ if (!service) {
105
+ return;
106
+ }
107
+
108
+ // Check health for each URL
109
+ service.urls.forEach(url => {
110
+ this._checkHealth(name, url);
111
+ });
112
+
113
+ // Schedule next health check
114
+ setTimeout(() => {
115
+ this._startHealthCheck(name);
116
+ }, this.healthCheckInterval);
117
+ }
118
+
119
+ /**
120
+ * Check health of a service URL
121
+ * @private
122
+ */
123
+ async _checkHealth(name, url) {
124
+ try {
125
+ const http = require('http');
126
+ const https = require('https');
127
+ const parsedUrl = new URL(url);
128
+ const client = parsedUrl.protocol === 'https:' ? https : http;
129
+
130
+ const healthUrl = new URL(this.healthCheckPath, url);
131
+
132
+ const result = await new Promise((resolve, reject) => {
133
+ const req = client.request({
134
+ hostname: parsedUrl.hostname,
135
+ port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
136
+ path: healthUrl.pathname,
137
+ method: 'GET',
138
+ timeout: this.healthCheckTimeout,
139
+ }, (res) => {
140
+ let body = '';
141
+ res.on('data', chunk => { body += chunk; });
142
+ res.on('end', () => {
143
+ resolve({ statusCode: res.statusCode, body });
144
+ });
145
+ });
146
+
147
+ req.on('error', reject);
148
+ req.on('timeout', () => {
149
+ req.destroy();
150
+ reject(new Error('Health check timeout'));
151
+ });
152
+
153
+ req.end();
154
+ });
155
+
156
+ if (result.statusCode === 200) {
157
+ this.markHealthy(name, url);
158
+ } else {
159
+ this.markUnhealthy(name, url);
160
+ }
161
+ } catch (error) {
162
+ this.markUnhealthy(name, url);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Unregister a service
168
+ * @param {string} name - Service name
169
+ */
170
+ unregister(name) {
171
+ this.services.delete(name);
172
+ }
173
+
174
+ /**
175
+ * Get all registered services
176
+ * @returns {Array} - Array of service names
177
+ */
178
+ list() {
179
+ return Array.from(this.services.keys());
180
+ }
181
+ }
182
+
183
+ module.exports = ServiceDiscovery;
184
+