@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
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# @hazeljs/discovery
|
|
2
|
+
|
|
3
|
+
Service Discovery and Registry for HazelJS microservices - inspired by Netflix Eureka and Consul.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@hazeljs/discovery)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🔍 **Service Registration & Discovery** - Automatic service registration with health checks
|
|
11
|
+
- ⚖️ **Load Balancing** - 6 built-in strategies (Round Robin, Random, Least Connections, etc.)
|
|
12
|
+
- 🏥 **Health Checks** - Automatic health monitoring with heartbeat
|
|
13
|
+
- 🎯 **Service Filtering** - Filter by zone, tags, metadata, and status
|
|
14
|
+
- 💾 **Multiple Backends** - Memory (dev), Redis, Consul, etcd, Kubernetes
|
|
15
|
+
- 🎨 **Decorator Support** - Clean integration with HazelJS apps
|
|
16
|
+
- 📊 **Caching** - Built-in service discovery caching
|
|
17
|
+
- 🔄 **Auto-Cleanup** - Automatic removal of expired instances
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @hazeljs/discovery
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Register a Service
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { ServiceRegistry } from '@hazeljs/discovery';
|
|
31
|
+
|
|
32
|
+
const registry = new ServiceRegistry({
|
|
33
|
+
name: 'user-service',
|
|
34
|
+
port: 3000,
|
|
35
|
+
host: 'localhost',
|
|
36
|
+
healthCheckPath: '/health',
|
|
37
|
+
healthCheckInterval: 30000,
|
|
38
|
+
metadata: { version: '1.0.0' },
|
|
39
|
+
zone: 'us-east-1',
|
|
40
|
+
tags: ['api', 'users'],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await registry.register();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Discover Services
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { DiscoveryClient } from '@hazeljs/discovery';
|
|
50
|
+
|
|
51
|
+
const client = new DiscoveryClient({
|
|
52
|
+
cacheEnabled: true,
|
|
53
|
+
cacheTTL: 30000,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Get all instances
|
|
57
|
+
const instances = await client.getInstances('user-service');
|
|
58
|
+
|
|
59
|
+
// Get one instance with load balancing
|
|
60
|
+
const instance = await client.getInstance('user-service', 'round-robin');
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Call Services
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { ServiceClient } from '@hazeljs/discovery';
|
|
67
|
+
|
|
68
|
+
const serviceClient = new ServiceClient(discoveryClient, {
|
|
69
|
+
serviceName: 'user-service',
|
|
70
|
+
loadBalancingStrategy: 'round-robin',
|
|
71
|
+
timeout: 5000,
|
|
72
|
+
retries: 3,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Automatic service discovery + load balancing
|
|
76
|
+
const user = await serviceClient.get('/users/123');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 4. With HazelJS Decorators
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { ServiceRegistryDecorator, InjectServiceClient } from '@hazeljs/discovery';
|
|
83
|
+
|
|
84
|
+
@ServiceRegistryDecorator({
|
|
85
|
+
name: 'order-service',
|
|
86
|
+
port: 3001,
|
|
87
|
+
healthCheckPath: '/health',
|
|
88
|
+
})
|
|
89
|
+
export class AppModule {}
|
|
90
|
+
|
|
91
|
+
@Injectable()
|
|
92
|
+
export class OrderService {
|
|
93
|
+
constructor(
|
|
94
|
+
@InjectServiceClient('user-service')
|
|
95
|
+
private userClient: ServiceClient
|
|
96
|
+
) {}
|
|
97
|
+
|
|
98
|
+
async createOrder(userId: string) {
|
|
99
|
+
const user = await this.userClient.get(`/users/${userId}`);
|
|
100
|
+
// ... create order
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Load Balancing Strategies
|
|
106
|
+
|
|
107
|
+
### Round Robin
|
|
108
|
+
```typescript
|
|
109
|
+
const instance = await client.getInstance('service-name', 'round-robin');
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Random
|
|
113
|
+
```typescript
|
|
114
|
+
const instance = await client.getInstance('service-name', 'random');
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Least Connections
|
|
118
|
+
```typescript
|
|
119
|
+
const instance = await client.getInstance('service-name', 'least-connections');
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Weighted Round Robin
|
|
123
|
+
```typescript
|
|
124
|
+
// Set weight in service metadata
|
|
125
|
+
const registry = new ServiceRegistry({
|
|
126
|
+
name: 'api-service',
|
|
127
|
+
metadata: { weight: 5 }, // Higher weight = more traffic
|
|
128
|
+
// ...
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### IP Hash (Sticky Sessions)
|
|
133
|
+
```typescript
|
|
134
|
+
const instance = await client.getInstance('service-name', 'ip-hash');
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Zone Aware
|
|
138
|
+
```typescript
|
|
139
|
+
const factory = client.getLoadBalancerFactory();
|
|
140
|
+
const strategy = factory.create('zone-aware', { zone: 'us-east-1' });
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Service Filtering
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
const instances = await client.getInstances('user-service', {
|
|
147
|
+
zone: 'us-east-1',
|
|
148
|
+
status: ServiceStatus.UP,
|
|
149
|
+
tags: ['api', 'production'],
|
|
150
|
+
metadata: { version: '2.0.0' },
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Registry Backends
|
|
155
|
+
|
|
156
|
+
### Memory (Development)
|
|
157
|
+
```typescript
|
|
158
|
+
import { MemoryRegistryBackend } from '@hazeljs/discovery';
|
|
159
|
+
|
|
160
|
+
const backend = new MemoryRegistryBackend();
|
|
161
|
+
const registry = new ServiceRegistry(config, backend);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Redis (Production)
|
|
165
|
+
```typescript
|
|
166
|
+
import Redis from 'ioredis';
|
|
167
|
+
import { RedisRegistryBackend } from '@hazeljs/discovery';
|
|
168
|
+
|
|
169
|
+
const redis = new Redis({
|
|
170
|
+
host: 'localhost',
|
|
171
|
+
port: 6379,
|
|
172
|
+
password: 'your-password',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const backend = new RedisRegistryBackend(redis, {
|
|
176
|
+
keyPrefix: 'myapp:discovery:',
|
|
177
|
+
ttl: 90, // seconds
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const registry = new ServiceRegistry(config, backend);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Consul
|
|
184
|
+
```typescript
|
|
185
|
+
import Consul from 'consul';
|
|
186
|
+
import { ConsulRegistryBackend } from '@hazeljs/discovery';
|
|
187
|
+
|
|
188
|
+
const consul = new Consul({
|
|
189
|
+
host: 'localhost',
|
|
190
|
+
port: 8500,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const backend = new ConsulRegistryBackend(consul, {
|
|
194
|
+
ttl: '30s',
|
|
195
|
+
datacenter: 'dc1',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const registry = new ServiceRegistry(config, backend);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Kubernetes
|
|
202
|
+
```typescript
|
|
203
|
+
import { KubeConfig } from '@kubernetes/client-node';
|
|
204
|
+
import { KubernetesRegistryBackend } from '@hazeljs/discovery';
|
|
205
|
+
|
|
206
|
+
const kubeConfig = new KubeConfig();
|
|
207
|
+
kubeConfig.loadFromDefault();
|
|
208
|
+
|
|
209
|
+
const backend = new KubernetesRegistryBackend(kubeConfig, {
|
|
210
|
+
namespace: 'default',
|
|
211
|
+
labelSelector: 'app.kubernetes.io/managed-by=hazeljs',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// In Kubernetes, service registration is handled by the platform
|
|
215
|
+
// Use the backend for service discovery only
|
|
216
|
+
const client = new DiscoveryClient({}, backend);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## API Reference
|
|
220
|
+
|
|
221
|
+
### ServiceRegistry
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
class ServiceRegistry {
|
|
225
|
+
constructor(config: ServiceRegistryConfig, backend?: RegistryBackend);
|
|
226
|
+
register(): Promise<void>;
|
|
227
|
+
deregister(): Promise<void>;
|
|
228
|
+
getInstance(): ServiceInstance | null;
|
|
229
|
+
getBackend(): RegistryBackend;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### DiscoveryClient
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
class DiscoveryClient {
|
|
237
|
+
constructor(config?: DiscoveryClientConfig, backend?: RegistryBackend);
|
|
238
|
+
getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
|
|
239
|
+
getInstance(serviceName: string, strategy?: string, filter?: ServiceFilter): Promise<ServiceInstance | null>;
|
|
240
|
+
getAllServices(): Promise<string[]>;
|
|
241
|
+
clearCache(serviceName?: string): void;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### ServiceClient
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
class ServiceClient {
|
|
249
|
+
constructor(discoveryClient: DiscoveryClient, config: ServiceClientConfig);
|
|
250
|
+
get<T>(path: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
251
|
+
post<T>(path: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
252
|
+
put<T>(path: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
253
|
+
delete<T>(path: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
254
|
+
patch<T>(path: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Examples
|
|
259
|
+
|
|
260
|
+
See the [examples](./examples) directory for complete working examples.
|
|
261
|
+
|
|
262
|
+
## Testing
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
npm test
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Contributing
|
|
269
|
+
|
|
270
|
+
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT © [HazelJS](https://hazeljs.com)
|
|
275
|
+
|
|
276
|
+
## Links
|
|
277
|
+
|
|
278
|
+
- [Documentation](https://hazeljs.com/docs)
|
|
279
|
+
- [GitHub](https://github.com/hazel-js/hazeljs)
|
|
280
|
+
- [Issues](https://github.com/hazel-js/hazeljs/issues)
|
|
281
|
+
- [Roadmap](../../ROADMAP_2.0.md)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Decorators Tests
|
|
4
|
+
*/
|
|
5
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
6
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
7
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
8
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
9
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
10
|
+
};
|
|
11
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
12
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
13
|
+
};
|
|
14
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
15
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
require("reflect-metadata");
|
|
19
|
+
const service_registry_decorator_1 = require("../decorators/service-registry.decorator");
|
|
20
|
+
const inject_service_client_decorator_1 = require("../decorators/inject-service-client.decorator");
|
|
21
|
+
describe('Decorators', () => {
|
|
22
|
+
describe('ServiceRegistry', () => {
|
|
23
|
+
it('should attach metadata to class', () => {
|
|
24
|
+
const config = {
|
|
25
|
+
name: 'test-service',
|
|
26
|
+
port: 3000,
|
|
27
|
+
zone: 'us-east-1',
|
|
28
|
+
};
|
|
29
|
+
let TestService = class TestService {
|
|
30
|
+
};
|
|
31
|
+
TestService = __decorate([
|
|
32
|
+
(0, service_registry_decorator_1.ServiceRegistry)(config)
|
|
33
|
+
], TestService);
|
|
34
|
+
const metadata = (0, service_registry_decorator_1.getServiceRegistryMetadata)(TestService);
|
|
35
|
+
expect(metadata).toEqual(config);
|
|
36
|
+
});
|
|
37
|
+
it('should return undefined for class without decorator', () => {
|
|
38
|
+
class TestService {
|
|
39
|
+
}
|
|
40
|
+
const metadata = (0, service_registry_decorator_1.getServiceRegistryMetadata)(TestService);
|
|
41
|
+
expect(metadata).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('InjectServiceClient', () => {
|
|
45
|
+
it('should attach metadata to parameter', () => {
|
|
46
|
+
let TestService = class TestService {
|
|
47
|
+
constructor(_userClient, _orderClient) { }
|
|
48
|
+
};
|
|
49
|
+
TestService = __decorate([
|
|
50
|
+
__param(0, (0, inject_service_client_decorator_1.InjectServiceClient)('user-service')),
|
|
51
|
+
__param(1, (0, inject_service_client_decorator_1.InjectServiceClient)('order-service', { timeout: 5000 })),
|
|
52
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
53
|
+
], TestService);
|
|
54
|
+
const metadata = (0, inject_service_client_decorator_1.getInjectServiceClientMetadata)(TestService);
|
|
55
|
+
expect(metadata).toBeDefined();
|
|
56
|
+
expect(metadata?.[0]).toEqual({
|
|
57
|
+
serviceName: 'user-service',
|
|
58
|
+
});
|
|
59
|
+
expect(metadata?.[1]).toEqual({
|
|
60
|
+
serviceName: 'order-service',
|
|
61
|
+
timeout: 5000,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('should return undefined for class without decorator', () => {
|
|
65
|
+
class TestService {
|
|
66
|
+
constructor(_client) { }
|
|
67
|
+
}
|
|
68
|
+
const metadata = (0, inject_service_client_decorator_1.getInjectServiceClientMetadata)(TestService);
|
|
69
|
+
expect(metadata).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/discovery-client.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Discovery Client Tests
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const discovery_client_1 = require("../client/discovery-client");
|
|
7
|
+
const memory_backend_1 = require("../backends/memory-backend");
|
|
8
|
+
const types_1 = require("../types");
|
|
9
|
+
describe('DiscoveryClient', () => {
|
|
10
|
+
let client;
|
|
11
|
+
let backend;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
backend = new memory_backend_1.MemoryRegistryBackend();
|
|
14
|
+
client = new discovery_client_1.DiscoveryClient({}, backend);
|
|
15
|
+
});
|
|
16
|
+
const createInstance = (id, name = 'test-service') => ({
|
|
17
|
+
id,
|
|
18
|
+
name,
|
|
19
|
+
host: 'localhost',
|
|
20
|
+
port: 3000,
|
|
21
|
+
status: types_1.ServiceStatus.UP,
|
|
22
|
+
lastHeartbeat: new Date(),
|
|
23
|
+
registeredAt: new Date(),
|
|
24
|
+
});
|
|
25
|
+
describe('getInstances', () => {
|
|
26
|
+
it('should return instances from backend', async () => {
|
|
27
|
+
const instance1 = createInstance('1', 'service-a');
|
|
28
|
+
const instance2 = createInstance('2', 'service-a');
|
|
29
|
+
await backend.register(instance1);
|
|
30
|
+
await backend.register(instance2);
|
|
31
|
+
const instances = await client.getInstances('service-a');
|
|
32
|
+
expect(instances).toHaveLength(2);
|
|
33
|
+
});
|
|
34
|
+
it('should apply filters', async () => {
|
|
35
|
+
const instance1 = { ...createInstance('1'), zone: 'us-east-1' };
|
|
36
|
+
const instance2 = { ...createInstance('2'), zone: 'us-west-1' };
|
|
37
|
+
await backend.register(instance1);
|
|
38
|
+
await backend.register(instance2);
|
|
39
|
+
const instances = await client.getInstances('test-service', {
|
|
40
|
+
zone: 'us-east-1',
|
|
41
|
+
});
|
|
42
|
+
expect(instances).toHaveLength(1);
|
|
43
|
+
expect(instances[0].id).toBe('1');
|
|
44
|
+
});
|
|
45
|
+
it('should use cache when enabled', async () => {
|
|
46
|
+
const clientWithCache = new discovery_client_1.DiscoveryClient({ cacheEnabled: true, cacheTTL: 1000 }, backend);
|
|
47
|
+
const instance = createInstance('1');
|
|
48
|
+
await backend.register(instance);
|
|
49
|
+
const first = await clientWithCache.getInstances('test-service');
|
|
50
|
+
expect(first).toHaveLength(1);
|
|
51
|
+
// Remove from backend
|
|
52
|
+
await backend.deregister('1');
|
|
53
|
+
// Should still return cached result
|
|
54
|
+
const second = await clientWithCache.getInstances('test-service');
|
|
55
|
+
expect(second).toHaveLength(1);
|
|
56
|
+
});
|
|
57
|
+
it('should refresh cache after TTL expires', async () => {
|
|
58
|
+
const clientWithCache = new discovery_client_1.DiscoveryClient({ cacheEnabled: true, cacheTTL: 50 }, backend);
|
|
59
|
+
const instance = createInstance('1');
|
|
60
|
+
await backend.register(instance);
|
|
61
|
+
await clientWithCache.getInstances('test-service');
|
|
62
|
+
await backend.deregister('1');
|
|
63
|
+
// Wait for cache to expire
|
|
64
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
65
|
+
const instances = await clientWithCache.getInstances('test-service');
|
|
66
|
+
expect(instances).toHaveLength(0);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('getInstance', () => {
|
|
70
|
+
it('should return a single instance using load balancing', async () => {
|
|
71
|
+
const instance1 = createInstance('1');
|
|
72
|
+
const instance2 = createInstance('2');
|
|
73
|
+
await backend.register(instance1);
|
|
74
|
+
await backend.register(instance2);
|
|
75
|
+
const instance = await client.getInstance('test-service');
|
|
76
|
+
expect(instance).toBeDefined();
|
|
77
|
+
expect(['1', '2']).toContain(instance?.id);
|
|
78
|
+
});
|
|
79
|
+
it('should return null when no instances available', async () => {
|
|
80
|
+
const instance = await client.getInstance('non-existent');
|
|
81
|
+
expect(instance).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
it('should use specified load balancing strategy', async () => {
|
|
84
|
+
const instance1 = createInstance('1');
|
|
85
|
+
const instance2 = createInstance('2');
|
|
86
|
+
await backend.register(instance1);
|
|
87
|
+
await backend.register(instance2);
|
|
88
|
+
// Round robin should cycle
|
|
89
|
+
const first = await client.getInstance('test-service', 'round-robin');
|
|
90
|
+
const second = await client.getInstance('test-service', 'round-robin');
|
|
91
|
+
const third = await client.getInstance('test-service', 'round-robin');
|
|
92
|
+
expect(first?.id).toBe('1');
|
|
93
|
+
expect(second?.id).toBe('2');
|
|
94
|
+
expect(third?.id).toBe('1');
|
|
95
|
+
});
|
|
96
|
+
it('should apply filters', async () => {
|
|
97
|
+
const instance1 = { ...createInstance('1'), zone: 'us-east-1' };
|
|
98
|
+
const instance2 = { ...createInstance('2'), zone: 'us-west-1' };
|
|
99
|
+
await backend.register(instance1);
|
|
100
|
+
await backend.register(instance2);
|
|
101
|
+
const instance = await client.getInstance('test-service', 'round-robin', {
|
|
102
|
+
zone: 'us-east-1',
|
|
103
|
+
});
|
|
104
|
+
expect(instance?.id).toBe('1');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe('getAllServices', () => {
|
|
108
|
+
it('should return all service names', async () => {
|
|
109
|
+
await backend.register(createInstance('1', 'service-a'));
|
|
110
|
+
await backend.register(createInstance('2', 'service-b'));
|
|
111
|
+
const services = await client.getAllServices();
|
|
112
|
+
expect(services.sort()).toEqual(['service-a', 'service-b']);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('clearCache', () => {
|
|
116
|
+
it('should clear cache for specific service', () => {
|
|
117
|
+
const clientWithCache = new discovery_client_1.DiscoveryClient({ cacheEnabled: true }, backend);
|
|
118
|
+
clientWithCache.clearCache('service-a');
|
|
119
|
+
// Should not throw
|
|
120
|
+
expect(true).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
it('should clear all cache when no service specified', () => {
|
|
123
|
+
const clientWithCache = new discovery_client_1.DiscoveryClient({ cacheEnabled: true }, backend);
|
|
124
|
+
clientWithCache.clearCache();
|
|
125
|
+
// Should not throw
|
|
126
|
+
expect(true).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('getBackend', () => {
|
|
130
|
+
it('should return the backend instance', () => {
|
|
131
|
+
const backend = client.getBackend();
|
|
132
|
+
expect(backend).toBeInstanceOf(memory_backend_1.MemoryRegistryBackend);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('getLoadBalancerFactory', () => {
|
|
136
|
+
it('should return the load balancer factory', () => {
|
|
137
|
+
const factory = client.getLoadBalancerFactory();
|
|
138
|
+
expect(factory).toBeDefined();
|
|
139
|
+
expect(factory.get('round-robin')).toBeDefined();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-balancer-strategies.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/load-balancer-strategies.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|