@hazeljs/discovery 0.2.0-beta.1
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 +281 -0
- package/dist/__tests__/decorators.test.d.ts +5 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +72 -0
- package/dist/__tests__/discovery-client.test.d.ts +5 -0
- package/dist/__tests__/discovery-client.test.d.ts.map +1 -0
- package/dist/__tests__/discovery-client.test.js +142 -0
- package/dist/__tests__/load-balancer-strategies.test.d.ts +5 -0
- package/dist/__tests__/load-balancer-strategies.test.d.ts.map +1 -0
- package/dist/__tests__/load-balancer-strategies.test.js +234 -0
- package/dist/__tests__/memory-backend.test.d.ts +5 -0
- package/dist/__tests__/memory-backend.test.d.ts.map +1 -0
- package/dist/__tests__/memory-backend.test.js +246 -0
- package/dist/__tests__/service-client.test.d.ts +5 -0
- package/dist/__tests__/service-client.test.d.ts.map +1 -0
- package/dist/__tests__/service-client.test.js +215 -0
- package/dist/__tests__/service-registry.test.d.ts +5 -0
- package/dist/__tests__/service-registry.test.d.ts.map +1 -0
- package/dist/__tests__/service-registry.test.js +65 -0
- package/dist/backends/consul-backend.d.ts +76 -0
- package/dist/backends/consul-backend.d.ts.map +1 -0
- package/dist/backends/consul-backend.js +275 -0
- package/dist/backends/kubernetes-backend.d.ts +65 -0
- package/dist/backends/kubernetes-backend.d.ts.map +1 -0
- package/dist/backends/kubernetes-backend.js +174 -0
- package/dist/backends/memory-backend.d.ts +22 -0
- package/dist/backends/memory-backend.d.ts.map +1 -0
- package/dist/backends/memory-backend.js +115 -0
- package/dist/backends/redis-backend.d.ts +71 -0
- package/dist/backends/redis-backend.d.ts.map +1 -0
- package/dist/backends/redis-backend.js +200 -0
- package/dist/backends/registry-backend.d.ts +39 -0
- package/dist/backends/registry-backend.d.ts.map +1 -0
- package/dist/backends/registry-backend.js +5 -0
- package/dist/client/discovery-client.d.ts +47 -0
- package/dist/client/discovery-client.d.ts.map +1 -0
- package/dist/client/discovery-client.js +123 -0
- package/dist/client/service-client.d.ts +52 -0
- package/dist/client/service-client.d.ts.map +1 -0
- package/dist/client/service-client.js +95 -0
- package/dist/decorators/inject-service-client.decorator.d.ts +16 -0
- package/dist/decorators/inject-service-client.decorator.d.ts.map +1 -0
- package/dist/decorators/inject-service-client.decorator.js +24 -0
- package/dist/decorators/service-registry.decorator.d.ts +11 -0
- package/dist/decorators/service-registry.decorator.d.ts.map +1 -0
- package/dist/decorators/service-registry.decorator.js +20 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/load-balancer/strategies.d.ts +82 -0
- package/dist/load-balancer/strategies.d.ts.map +1 -0
- package/dist/load-balancer/strategies.js +209 -0
- package/dist/registry/service-registry.d.ts +51 -0
- package/dist/registry/service-registry.d.ts.map +1 -0
- package/dist/registry/service-registry.js +148 -0
- package/dist/types/index.d.ts +61 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/package.json +78 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Load Balancing Strategies
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LoadBalancerFactory = exports.ZoneAwareStrategy = exports.IPHashStrategy = exports.WeightedRoundRobinStrategy = exports.LeastConnectionsStrategy = exports.RandomStrategy = exports.RoundRobinStrategy = void 0;
|
|
7
|
+
const types_1 = require("../types");
|
|
8
|
+
/**
|
|
9
|
+
* Base strategy that filters healthy instances
|
|
10
|
+
*/
|
|
11
|
+
class BaseStrategy {
|
|
12
|
+
filterHealthy(instances) {
|
|
13
|
+
return instances.filter((instance) => instance.status === types_1.ServiceStatus.UP);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Round Robin Strategy
|
|
18
|
+
*/
|
|
19
|
+
class RoundRobinStrategy extends BaseStrategy {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
this.name = 'round-robin';
|
|
23
|
+
this.currentIndex = 0;
|
|
24
|
+
}
|
|
25
|
+
choose(instances) {
|
|
26
|
+
const healthy = this.filterHealthy(instances);
|
|
27
|
+
if (healthy.length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
const instance = healthy[this.currentIndex % healthy.length];
|
|
30
|
+
this.currentIndex = (this.currentIndex + 1) % healthy.length;
|
|
31
|
+
return instance;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.RoundRobinStrategy = RoundRobinStrategy;
|
|
35
|
+
/**
|
|
36
|
+
* Random Strategy
|
|
37
|
+
*/
|
|
38
|
+
class RandomStrategy extends BaseStrategy {
|
|
39
|
+
constructor() {
|
|
40
|
+
super(...arguments);
|
|
41
|
+
this.name = 'random';
|
|
42
|
+
}
|
|
43
|
+
choose(instances) {
|
|
44
|
+
const healthy = this.filterHealthy(instances);
|
|
45
|
+
if (healthy.length === 0)
|
|
46
|
+
return null;
|
|
47
|
+
const randomIndex = Math.floor(Math.random() * healthy.length);
|
|
48
|
+
return healthy[randomIndex];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.RandomStrategy = RandomStrategy;
|
|
52
|
+
/**
|
|
53
|
+
* Least Connections Strategy
|
|
54
|
+
* Tracks active connections per instance
|
|
55
|
+
*/
|
|
56
|
+
class LeastConnectionsStrategy extends BaseStrategy {
|
|
57
|
+
constructor() {
|
|
58
|
+
super(...arguments);
|
|
59
|
+
this.name = 'least-connections';
|
|
60
|
+
this.connections = new Map();
|
|
61
|
+
}
|
|
62
|
+
choose(instances) {
|
|
63
|
+
const healthy = this.filterHealthy(instances);
|
|
64
|
+
if (healthy.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
// Find instance with least connections
|
|
67
|
+
let minConnections = Infinity;
|
|
68
|
+
let selectedInstance = null;
|
|
69
|
+
for (const instance of healthy) {
|
|
70
|
+
const connections = this.connections.get(instance.id) || 0;
|
|
71
|
+
if (connections < minConnections) {
|
|
72
|
+
minConnections = connections;
|
|
73
|
+
selectedInstance = instance;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return selectedInstance;
|
|
77
|
+
}
|
|
78
|
+
incrementConnections(instanceId) {
|
|
79
|
+
const current = this.connections.get(instanceId) || 0;
|
|
80
|
+
this.connections.set(instanceId, current + 1);
|
|
81
|
+
}
|
|
82
|
+
decrementConnections(instanceId) {
|
|
83
|
+
const current = this.connections.get(instanceId) || 0;
|
|
84
|
+
this.connections.set(instanceId, Math.max(0, current - 1));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.LeastConnectionsStrategy = LeastConnectionsStrategy;
|
|
88
|
+
/**
|
|
89
|
+
* Weighted Round Robin Strategy
|
|
90
|
+
* Uses metadata.weight for weighted selection
|
|
91
|
+
*/
|
|
92
|
+
class WeightedRoundRobinStrategy extends BaseStrategy {
|
|
93
|
+
constructor() {
|
|
94
|
+
super(...arguments);
|
|
95
|
+
this.name = 'weighted-round-robin';
|
|
96
|
+
this.currentIndex = 0;
|
|
97
|
+
this.currentWeight = 0;
|
|
98
|
+
}
|
|
99
|
+
choose(instances) {
|
|
100
|
+
const healthy = this.filterHealthy(instances);
|
|
101
|
+
if (healthy.length === 0)
|
|
102
|
+
return null;
|
|
103
|
+
// Build weighted list
|
|
104
|
+
const weighted = [];
|
|
105
|
+
for (const instance of healthy) {
|
|
106
|
+
const weightValue = instance.metadata?.weight;
|
|
107
|
+
const weight = typeof weightValue === 'number' && weightValue > 0 ? weightValue : 1;
|
|
108
|
+
for (let i = 0; i < weight; i++) {
|
|
109
|
+
weighted.push(instance);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (weighted.length === 0)
|
|
113
|
+
return null;
|
|
114
|
+
const instance = weighted[this.currentIndex % weighted.length];
|
|
115
|
+
this.currentIndex = (this.currentIndex + 1) % weighted.length;
|
|
116
|
+
return instance;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.WeightedRoundRobinStrategy = WeightedRoundRobinStrategy;
|
|
120
|
+
/**
|
|
121
|
+
* IP Hash Strategy
|
|
122
|
+
* Consistent hashing based on client IP
|
|
123
|
+
*/
|
|
124
|
+
class IPHashStrategy extends BaseStrategy {
|
|
125
|
+
constructor() {
|
|
126
|
+
super(...arguments);
|
|
127
|
+
this.name = 'ip-hash';
|
|
128
|
+
}
|
|
129
|
+
choose(instances, clientIP) {
|
|
130
|
+
const healthy = this.filterHealthy(instances);
|
|
131
|
+
if (healthy.length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
if (!clientIP)
|
|
134
|
+
return healthy[0];
|
|
135
|
+
// Simple hash function
|
|
136
|
+
const hash = this.hashCode(clientIP);
|
|
137
|
+
const index = Math.abs(hash) % healthy.length;
|
|
138
|
+
return healthy[index];
|
|
139
|
+
}
|
|
140
|
+
hashCode(str) {
|
|
141
|
+
let hash = 0;
|
|
142
|
+
for (let i = 0; i < str.length; i++) {
|
|
143
|
+
const char = str.charCodeAt(i);
|
|
144
|
+
hash = (hash << 5) - hash + char;
|
|
145
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
146
|
+
}
|
|
147
|
+
return hash;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.IPHashStrategy = IPHashStrategy;
|
|
151
|
+
/**
|
|
152
|
+
* Zone Aware Strategy
|
|
153
|
+
* Prefers instances in the same zone
|
|
154
|
+
*/
|
|
155
|
+
class ZoneAwareStrategy extends BaseStrategy {
|
|
156
|
+
constructor(preferredZone) {
|
|
157
|
+
super();
|
|
158
|
+
this.preferredZone = preferredZone;
|
|
159
|
+
this.name = 'zone-aware';
|
|
160
|
+
}
|
|
161
|
+
choose(instances) {
|
|
162
|
+
const healthy = this.filterHealthy(instances);
|
|
163
|
+
if (healthy.length === 0)
|
|
164
|
+
return null;
|
|
165
|
+
// Try to find instance in preferred zone
|
|
166
|
+
if (this.preferredZone) {
|
|
167
|
+
const sameZone = healthy.filter((i) => i.zone === this.preferredZone);
|
|
168
|
+
if (sameZone.length > 0) {
|
|
169
|
+
return sameZone[Math.floor(Math.random() * sameZone.length)];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Fallback to random
|
|
173
|
+
return healthy[Math.floor(Math.random() * healthy.length)];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
exports.ZoneAwareStrategy = ZoneAwareStrategy;
|
|
177
|
+
/**
|
|
178
|
+
* Load Balancer Factory
|
|
179
|
+
*/
|
|
180
|
+
class LoadBalancerFactory {
|
|
181
|
+
constructor() {
|
|
182
|
+
this.strategies = new Map();
|
|
183
|
+
this.registerDefaultStrategies();
|
|
184
|
+
}
|
|
185
|
+
registerDefaultStrategies() {
|
|
186
|
+
this.register(new RoundRobinStrategy());
|
|
187
|
+
this.register(new RandomStrategy());
|
|
188
|
+
this.register(new LeastConnectionsStrategy());
|
|
189
|
+
this.register(new WeightedRoundRobinStrategy());
|
|
190
|
+
this.register(new IPHashStrategy());
|
|
191
|
+
}
|
|
192
|
+
register(strategy) {
|
|
193
|
+
this.strategies.set(strategy.name, strategy);
|
|
194
|
+
}
|
|
195
|
+
get(name) {
|
|
196
|
+
return this.strategies.get(name);
|
|
197
|
+
}
|
|
198
|
+
create(name, options) {
|
|
199
|
+
if (name === 'zone-aware') {
|
|
200
|
+
return new ZoneAwareStrategy(options?.zone);
|
|
201
|
+
}
|
|
202
|
+
const strategy = this.get(name);
|
|
203
|
+
if (!strategy) {
|
|
204
|
+
throw new Error(`Load balancing strategy '${name}' not found`);
|
|
205
|
+
}
|
|
206
|
+
return strategy;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
exports.LoadBalancerFactory = LoadBalancerFactory;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Registry
|
|
3
|
+
* Manages service registration and health checks
|
|
4
|
+
*/
|
|
5
|
+
import { ServiceInstance, ServiceRegistryConfig } from '../types';
|
|
6
|
+
import { RegistryBackend } from '../backends/registry-backend';
|
|
7
|
+
export declare class ServiceRegistry {
|
|
8
|
+
private config;
|
|
9
|
+
private backend;
|
|
10
|
+
private instance;
|
|
11
|
+
private heartbeatInterval;
|
|
12
|
+
private cleanupInterval;
|
|
13
|
+
constructor(config: ServiceRegistryConfig, backend?: RegistryBackend);
|
|
14
|
+
/**
|
|
15
|
+
* Register this service instance
|
|
16
|
+
*/
|
|
17
|
+
register(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Deregister this service instance
|
|
20
|
+
*/
|
|
21
|
+
deregister(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Get the current service instance
|
|
24
|
+
*/
|
|
25
|
+
getInstance(): ServiceInstance | null;
|
|
26
|
+
/**
|
|
27
|
+
* Get the backend
|
|
28
|
+
*/
|
|
29
|
+
getBackend(): RegistryBackend;
|
|
30
|
+
/**
|
|
31
|
+
* Start heartbeat interval
|
|
32
|
+
*/
|
|
33
|
+
private startHeartbeat;
|
|
34
|
+
/**
|
|
35
|
+
* Start cleanup interval
|
|
36
|
+
*/
|
|
37
|
+
private startCleanup;
|
|
38
|
+
/**
|
|
39
|
+
* Perform health check
|
|
40
|
+
*/
|
|
41
|
+
private performHealthCheck;
|
|
42
|
+
/**
|
|
43
|
+
* Generate unique instance ID
|
|
44
|
+
*/
|
|
45
|
+
private generateInstanceId;
|
|
46
|
+
/**
|
|
47
|
+
* Get local IP address
|
|
48
|
+
*/
|
|
49
|
+
private getLocalIP;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=service-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../../src/registry/service-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAiB,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAI/D,qBAAa,eAAe;IAOxB,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,eAAe,CAA+B;gBAG5C,MAAM,EAAE,qBAAqB,EACrC,OAAO,CAAC,EAAE,eAAe;IAK3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBjC;;OAEG;IACH,WAAW,IAAI,eAAe,GAAG,IAAI;IAIrC;;OAEG;IACH,UAAU,IAAI,eAAe;IAI7B;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;YACW,kBAAkB;IAoBhC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;OAEG;IACH,OAAO,CAAC,UAAU;CAgBnB"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Service Registry
|
|
4
|
+
* Manages service registration and health checks
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ServiceRegistry = void 0;
|
|
11
|
+
const types_1 = require("../types");
|
|
12
|
+
const memory_backend_1 = require("../backends/memory-backend");
|
|
13
|
+
const axios_1 = __importDefault(require("axios"));
|
|
14
|
+
class ServiceRegistry {
|
|
15
|
+
constructor(config, backend) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.instance = null;
|
|
18
|
+
this.heartbeatInterval = null;
|
|
19
|
+
this.cleanupInterval = null;
|
|
20
|
+
this.backend = backend || new memory_backend_1.MemoryRegistryBackend();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Register this service instance
|
|
24
|
+
*/
|
|
25
|
+
async register() {
|
|
26
|
+
const host = this.config.host || this.getLocalIP();
|
|
27
|
+
const instanceId = this.generateInstanceId(this.config.name, host, this.config.port);
|
|
28
|
+
this.instance = {
|
|
29
|
+
id: instanceId,
|
|
30
|
+
name: this.config.name,
|
|
31
|
+
host,
|
|
32
|
+
port: this.config.port,
|
|
33
|
+
protocol: this.config.protocol || 'http',
|
|
34
|
+
metadata: this.config.metadata || {},
|
|
35
|
+
healthCheckPath: this.config.healthCheckPath || '/health',
|
|
36
|
+
healthCheckInterval: this.config.healthCheckInterval || 30000,
|
|
37
|
+
zone: this.config.zone,
|
|
38
|
+
tags: this.config.tags || [],
|
|
39
|
+
status: types_1.ServiceStatus.STARTING,
|
|
40
|
+
lastHeartbeat: new Date(),
|
|
41
|
+
registeredAt: new Date(),
|
|
42
|
+
};
|
|
43
|
+
await this.backend.register(this.instance);
|
|
44
|
+
// Start heartbeat
|
|
45
|
+
this.startHeartbeat();
|
|
46
|
+
// Start cleanup task
|
|
47
|
+
this.startCleanup();
|
|
48
|
+
// Perform initial health check
|
|
49
|
+
await this.performHealthCheck();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Deregister this service instance
|
|
53
|
+
*/
|
|
54
|
+
async deregister() {
|
|
55
|
+
if (this.heartbeatInterval) {
|
|
56
|
+
clearInterval(this.heartbeatInterval);
|
|
57
|
+
this.heartbeatInterval = null;
|
|
58
|
+
}
|
|
59
|
+
if (this.cleanupInterval) {
|
|
60
|
+
clearInterval(this.cleanupInterval);
|
|
61
|
+
this.cleanupInterval = null;
|
|
62
|
+
}
|
|
63
|
+
if (this.instance) {
|
|
64
|
+
await this.backend.deregister(this.instance.id);
|
|
65
|
+
this.instance = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get the current service instance
|
|
70
|
+
*/
|
|
71
|
+
getInstance() {
|
|
72
|
+
return this.instance;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get the backend
|
|
76
|
+
*/
|
|
77
|
+
getBackend() {
|
|
78
|
+
return this.backend;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Start heartbeat interval
|
|
82
|
+
*/
|
|
83
|
+
startHeartbeat() {
|
|
84
|
+
if (!this.instance)
|
|
85
|
+
return;
|
|
86
|
+
const interval = this.instance.healthCheckInterval || 30000;
|
|
87
|
+
this.heartbeatInterval = setInterval(async () => {
|
|
88
|
+
if (this.instance) {
|
|
89
|
+
await this.performHealthCheck();
|
|
90
|
+
}
|
|
91
|
+
}, interval);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Start cleanup interval
|
|
95
|
+
*/
|
|
96
|
+
startCleanup() {
|
|
97
|
+
this.cleanupInterval = setInterval(async () => {
|
|
98
|
+
await this.backend.cleanup();
|
|
99
|
+
}, 60000); // Run every minute
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Perform health check
|
|
103
|
+
*/
|
|
104
|
+
async performHealthCheck() {
|
|
105
|
+
if (!this.instance)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
const url = `${this.instance.protocol}://${this.instance.host}:${this.instance.port}${this.instance.healthCheckPath}`;
|
|
109
|
+
const response = await axios_1.default.get(url, { timeout: 5000 });
|
|
110
|
+
if (response.status === 200) {
|
|
111
|
+
this.instance.status = types_1.ServiceStatus.UP;
|
|
112
|
+
await this.backend.heartbeat(this.instance.id);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
this.instance.status = types_1.ServiceStatus.DOWN;
|
|
116
|
+
await this.backend.updateStatus(this.instance.id, types_1.ServiceStatus.DOWN);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
this.instance.status = types_1.ServiceStatus.DOWN;
|
|
121
|
+
await this.backend.updateStatus(this.instance.id, types_1.ServiceStatus.DOWN);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Generate unique instance ID
|
|
126
|
+
*/
|
|
127
|
+
generateInstanceId(name, host, port) {
|
|
128
|
+
return `${name}:${host}:${port}:${Date.now()}`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get local IP address
|
|
132
|
+
*/
|
|
133
|
+
getLocalIP() {
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
135
|
+
const { networkInterfaces } = require('os');
|
|
136
|
+
const nets = networkInterfaces();
|
|
137
|
+
for (const name of Object.keys(nets)) {
|
|
138
|
+
for (const net of nets[name]) {
|
|
139
|
+
// Skip internal and non-IPv4 addresses
|
|
140
|
+
if (net.family === 'IPv4' && !net.internal) {
|
|
141
|
+
return net.address;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return 'localhost';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
exports.ServiceRegistry = ServiceRegistry;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Discovery Types
|
|
3
|
+
*/
|
|
4
|
+
export interface ServiceInstance {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
host: string;
|
|
8
|
+
port: number;
|
|
9
|
+
protocol?: 'http' | 'https' | 'grpc';
|
|
10
|
+
metadata?: Record<string, unknown>;
|
|
11
|
+
healthCheckPath?: string;
|
|
12
|
+
healthCheckInterval?: number;
|
|
13
|
+
zone?: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
status: ServiceStatus;
|
|
16
|
+
lastHeartbeat: Date;
|
|
17
|
+
registeredAt: Date;
|
|
18
|
+
}
|
|
19
|
+
export declare enum ServiceStatus {
|
|
20
|
+
UP = "UP",
|
|
21
|
+
DOWN = "DOWN",
|
|
22
|
+
STARTING = "STARTING",
|
|
23
|
+
OUT_OF_SERVICE = "OUT_OF_SERVICE",
|
|
24
|
+
UNKNOWN = "UNKNOWN"
|
|
25
|
+
}
|
|
26
|
+
export interface ServiceRegistryConfig {
|
|
27
|
+
name: string;
|
|
28
|
+
host?: string;
|
|
29
|
+
port: number;
|
|
30
|
+
protocol?: 'http' | 'https' | 'grpc';
|
|
31
|
+
healthCheckPath?: string;
|
|
32
|
+
healthCheckInterval?: number;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
zone?: string;
|
|
35
|
+
tags?: string[];
|
|
36
|
+
backend?: 'memory' | 'redis' | 'consul' | 'etcd' | 'kubernetes';
|
|
37
|
+
backendConfig?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
export interface DiscoveryClientConfig {
|
|
40
|
+
backend?: 'memory' | 'redis' | 'consul' | 'etcd' | 'kubernetes';
|
|
41
|
+
backendConfig?: Record<string, unknown>;
|
|
42
|
+
cacheEnabled?: boolean;
|
|
43
|
+
cacheTTL?: number;
|
|
44
|
+
refreshInterval?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface LoadBalancerStrategy {
|
|
47
|
+
name: string;
|
|
48
|
+
choose(instances: ServiceInstance[]): ServiceInstance | null;
|
|
49
|
+
}
|
|
50
|
+
export interface HealthCheckResult {
|
|
51
|
+
status: ServiceStatus;
|
|
52
|
+
message?: string;
|
|
53
|
+
timestamp: Date;
|
|
54
|
+
}
|
|
55
|
+
export interface ServiceFilter {
|
|
56
|
+
zone?: string;
|
|
57
|
+
tags?: string[];
|
|
58
|
+
metadata?: Record<string, unknown>;
|
|
59
|
+
status?: ServiceStatus;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,IAAI,CAAC;IACpB,YAAY,EAAE,IAAI,CAAC;CACpB;AAED,oBAAY,aAAa;IACvB,EAAE,OAAO;IACT,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,cAAc,mBAAmB;IACjC,OAAO,YAAY;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAC;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAC;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,GAAG,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Service Discovery Types
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ServiceStatus = void 0;
|
|
7
|
+
var ServiceStatus;
|
|
8
|
+
(function (ServiceStatus) {
|
|
9
|
+
ServiceStatus["UP"] = "UP";
|
|
10
|
+
ServiceStatus["DOWN"] = "DOWN";
|
|
11
|
+
ServiceStatus["STARTING"] = "STARTING";
|
|
12
|
+
ServiceStatus["OUT_OF_SERVICE"] = "OUT_OF_SERVICE";
|
|
13
|
+
ServiceStatus["UNKNOWN"] = "UNKNOWN";
|
|
14
|
+
})(ServiceStatus || (exports.ServiceStatus = ServiceStatus = {}));
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hazeljs/discovery",
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
|
+
"description": "Service discovery and registry for HazelJS microservices - Eureka-inspired with multiple backend support",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest --coverage --passWithNoTests",
|
|
13
|
+
"test:ci": "jest --coverage --coverageReporters=text --coverageReporters=lcov --coverageReporters=clover --no-coverage-threshold",
|
|
14
|
+
"test:watch": "jest --watch",
|
|
15
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
16
|
+
"lint:fix": "eslint \"src/**/*.ts\" --fix",
|
|
17
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
18
|
+
"clean": "rm -rf dist"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@hazeljs/core": "file:../core",
|
|
22
|
+
"reflect-metadata": "^0.2.2",
|
|
23
|
+
"axios": "^1.6.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/jest": "^29.5.14",
|
|
27
|
+
"@types/node": "^20.17.50",
|
|
28
|
+
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
|
29
|
+
"@typescript-eslint/parser": "^8.18.2",
|
|
30
|
+
"eslint": "^8.56.0",
|
|
31
|
+
"eslint-config-prettier": "^9.1.0",
|
|
32
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
33
|
+
"jest": "^29.7.0",
|
|
34
|
+
"prettier": "^3.2.5",
|
|
35
|
+
"ts-jest": "^29.1.2",
|
|
36
|
+
"typescript": "^5.3.3"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"ioredis": "^5.3.0",
|
|
40
|
+
"consul": "^1.2.0",
|
|
41
|
+
"@kubernetes/client-node": "^0.20.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"ioredis": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"consul": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"@kubernetes/client-node": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "git+https://github.com/hazel-js/hazeljs.git",
|
|
60
|
+
"directory": "packages/discovery"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"hazeljs",
|
|
64
|
+
"microservices",
|
|
65
|
+
"service-discovery",
|
|
66
|
+
"service-registry",
|
|
67
|
+
"load-balancing",
|
|
68
|
+
"eureka",
|
|
69
|
+
"consul",
|
|
70
|
+
"kubernetes"
|
|
71
|
+
],
|
|
72
|
+
"author": "Muhammad Arslan <marslan@hazeljs.com>",
|
|
73
|
+
"license": "MIT",
|
|
74
|
+
"bugs": {
|
|
75
|
+
"url": "https://github.com/hazeljs/hazel-js/issues"
|
|
76
|
+
},
|
|
77
|
+
"homepage": "https://hazeljs.com"
|
|
78
|
+
}
|