@hazeljs/discovery 0.2.0-beta.8 → 0.2.0-beta.81

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.
Files changed (45) hide show
  1. package/LICENSE +192 -21
  2. package/README.md +190 -21
  3. package/dist/__tests__/consul-backend.test.d.ts +6 -0
  4. package/dist/__tests__/consul-backend.test.d.ts.map +1 -0
  5. package/dist/__tests__/consul-backend.test.js +300 -0
  6. package/dist/__tests__/kubernetes-backend.test.d.ts +6 -0
  7. package/dist/__tests__/kubernetes-backend.test.d.ts.map +1 -0
  8. package/dist/__tests__/kubernetes-backend.test.js +261 -0
  9. package/dist/__tests__/redis-backend.test.d.ts +6 -0
  10. package/dist/__tests__/redis-backend.test.d.ts.map +1 -0
  11. package/dist/__tests__/redis-backend.test.js +280 -0
  12. package/dist/__tests__/service-client.test.js +2 -1
  13. package/dist/backends/consul-backend.d.ts +46 -7
  14. package/dist/backends/consul-backend.d.ts.map +1 -1
  15. package/dist/backends/consul-backend.js +23 -39
  16. package/dist/backends/kubernetes-backend.d.ts +44 -6
  17. package/dist/backends/kubernetes-backend.d.ts.map +1 -1
  18. package/dist/backends/kubernetes-backend.js +11 -32
  19. package/dist/backends/memory-backend.d.ts +0 -1
  20. package/dist/backends/memory-backend.d.ts.map +1 -1
  21. package/dist/backends/memory-backend.js +3 -32
  22. package/dist/backends/redis-backend.d.ts +11 -6
  23. package/dist/backends/redis-backend.d.ts.map +1 -1
  24. package/dist/backends/redis-backend.js +66 -46
  25. package/dist/client/discovery-client.d.ts +6 -4
  26. package/dist/client/discovery-client.d.ts.map +1 -1
  27. package/dist/client/discovery-client.js +30 -30
  28. package/dist/client/service-client.d.ts +4 -8
  29. package/dist/client/service-client.d.ts.map +1 -1
  30. package/dist/client/service-client.js +94 -34
  31. package/dist/index.d.ts +4 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +8 -1
  34. package/dist/registry/service-registry.d.ts.map +1 -1
  35. package/dist/registry/service-registry.js +13 -2
  36. package/dist/utils/filter.d.ts +10 -0
  37. package/dist/utils/filter.d.ts.map +1 -0
  38. package/dist/utils/filter.js +39 -0
  39. package/dist/utils/logger.d.ts +21 -0
  40. package/dist/utils/logger.d.ts.map +1 -0
  41. package/dist/utils/logger.js +34 -0
  42. package/dist/utils/validation.d.ts +36 -0
  43. package/dist/utils/validation.d.ts.map +1 -0
  44. package/dist/utils/validation.js +109 -0
  45. package/package.json +7 -5
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ /**
3
+ * Redis Backend Tests
4
+ * Uses mocked ioredis client
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const redis_backend_1 = require("../backends/redis-backend");
8
+ const types_1 = require("../types");
9
+ const logger_1 = require("../utils/logger");
10
+ // Suppress console logs during tests
11
+ beforeAll(() => {
12
+ logger_1.DiscoveryLogger.setLogger({
13
+ debug: jest.fn(),
14
+ info: jest.fn(),
15
+ warn: jest.fn(),
16
+ error: jest.fn(),
17
+ });
18
+ });
19
+ afterAll(() => {
20
+ logger_1.DiscoveryLogger.resetLogger();
21
+ });
22
+ /** Helper to create a mock Redis instance */
23
+ function createMockRedis() {
24
+ const store = new Map();
25
+ const sets = new Map();
26
+ const ttls = new Map();
27
+ const listeners = new Map();
28
+ const mock = {
29
+ on: jest.fn((event, handler) => {
30
+ if (!listeners.has(event))
31
+ listeners.set(event, []);
32
+ listeners.get(event).push(handler);
33
+ }),
34
+ setex: jest.fn(async (key, ttl, value) => {
35
+ store.set(key, value);
36
+ ttls.set(key, ttl);
37
+ }),
38
+ get: jest.fn(async (key) => store.get(key) || null),
39
+ del: jest.fn(async (key) => {
40
+ store.delete(key);
41
+ ttls.delete(key);
42
+ return 1;
43
+ }),
44
+ mget: jest.fn(async (...keys) => {
45
+ // mget can receive keys as individual args or a single array
46
+ const flatKeys = keys.flat();
47
+ return flatKeys.map((k) => store.get(k) || null);
48
+ }),
49
+ sadd: jest.fn(async (key, member) => {
50
+ if (!sets.has(key))
51
+ sets.set(key, new Set());
52
+ sets.get(key).add(member);
53
+ return 1;
54
+ }),
55
+ srem: jest.fn(async (key, member) => {
56
+ sets.get(key)?.delete(member);
57
+ return 1;
58
+ }),
59
+ smembers: jest.fn(async (key) => {
60
+ return Array.from(sets.get(key) || []);
61
+ }),
62
+ scard: jest.fn(async (key) => {
63
+ return sets.get(key)?.size || 0;
64
+ }),
65
+ exists: jest.fn(async (key) => {
66
+ return store.has(key) ? 1 : 0;
67
+ }),
68
+ expire: jest.fn(async () => 1),
69
+ scan: jest.fn(async (_cursor, _match, pattern) => {
70
+ const keys = Array.from(sets.keys()).filter((k) => {
71
+ // Simple glob match: convert "prefix*" to regex
72
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
73
+ return regex.test(k);
74
+ });
75
+ return ['0', keys];
76
+ }),
77
+ quit: jest.fn(async () => 'OK'),
78
+ // Helper to emit events during tests
79
+ _emit: (event, ...args) => {
80
+ const handlers = listeners.get(event) || [];
81
+ handlers.forEach((h) => h(...args));
82
+ },
83
+ };
84
+ return mock;
85
+ }
86
+ function createInstance(id, name = 'test-service', overrides = {}) {
87
+ return {
88
+ id,
89
+ name,
90
+ host: 'localhost',
91
+ port: 3000,
92
+ status: types_1.ServiceStatus.UP,
93
+ lastHeartbeat: new Date('2025-01-01T00:00:00Z'),
94
+ registeredAt: new Date('2025-01-01T00:00:00Z'),
95
+ ...overrides,
96
+ };
97
+ }
98
+ describe('RedisRegistryBackend', () => {
99
+ let redis;
100
+ let backend;
101
+ beforeEach(() => {
102
+ redis = createMockRedis();
103
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
+ backend = new redis_backend_1.RedisRegistryBackend(redis);
105
+ });
106
+ describe('constructor', () => {
107
+ it('should use default config values', () => {
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
+ const b = new redis_backend_1.RedisRegistryBackend(redis);
110
+ expect(b).toBeDefined();
111
+ });
112
+ it('should accept custom config', () => {
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ const b = new redis_backend_1.RedisRegistryBackend(redis, {
115
+ keyPrefix: 'custom:',
116
+ ttl: 120,
117
+ });
118
+ expect(b).toBeDefined();
119
+ });
120
+ it('should throw on invalid TTL', () => {
121
+ expect(() => {
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ new redis_backend_1.RedisRegistryBackend(redis, { ttl: -5 });
124
+ }).toThrow();
125
+ });
126
+ it('should register connection event handlers', () => {
127
+ expect(redis.on).toHaveBeenCalledWith('error', expect.any(Function));
128
+ expect(redis.on).toHaveBeenCalledWith('connect', expect.any(Function));
129
+ expect(redis.on).toHaveBeenCalledWith('reconnecting', expect.any(Function));
130
+ expect(redis.on).toHaveBeenCalledWith('close', expect.any(Function));
131
+ });
132
+ });
133
+ describe('register', () => {
134
+ it('should store the instance with TTL', async () => {
135
+ const instance = createInstance('1');
136
+ await backend.register(instance);
137
+ expect(redis.setex).toHaveBeenCalledWith('hazeljs:discovery:instance:1', 90, JSON.stringify(instance));
138
+ });
139
+ it('should add instance to service set', async () => {
140
+ const instance = createInstance('1', 'my-service');
141
+ await backend.register(instance);
142
+ expect(redis.sadd).toHaveBeenCalledWith('hazeljs:discovery:service:my-service', '1');
143
+ });
144
+ it('should set expiration on service set', async () => {
145
+ const instance = createInstance('1');
146
+ await backend.register(instance);
147
+ expect(redis.expire).toHaveBeenCalledWith('hazeljs:discovery:service:test-service', 180);
148
+ });
149
+ });
150
+ describe('deregister', () => {
151
+ it('should remove instance from Redis', async () => {
152
+ const instance = createInstance('1');
153
+ await backend.register(instance);
154
+ await backend.deregister('1');
155
+ expect(redis.srem).toHaveBeenCalledWith('hazeljs:discovery:service:test-service', '1');
156
+ expect(redis.del).toHaveBeenCalledWith('hazeljs:discovery:instance:1');
157
+ });
158
+ it('should do nothing if instance does not exist', async () => {
159
+ await backend.deregister('non-existent');
160
+ expect(redis.del).not.toHaveBeenCalled();
161
+ });
162
+ });
163
+ describe('heartbeat', () => {
164
+ it('should update heartbeat timestamp and status', async () => {
165
+ const instance = createInstance('1');
166
+ instance.status = types_1.ServiceStatus.DOWN;
167
+ await backend.register(instance);
168
+ await backend.heartbeat('1');
169
+ // Should have been called again with updated data
170
+ expect(redis.setex).toHaveBeenCalledTimes(2);
171
+ const lastCall = redis.setex.mock.calls[1];
172
+ const storedInstance = JSON.parse(lastCall[2]);
173
+ expect(storedInstance.status).toBe(types_1.ServiceStatus.UP);
174
+ });
175
+ it('should do nothing if instance does not exist', async () => {
176
+ await backend.heartbeat('non-existent');
177
+ // setex should not be called
178
+ expect(redis.setex).not.toHaveBeenCalled();
179
+ });
180
+ });
181
+ describe('getInstances', () => {
182
+ it('should return all instances for a service using MGET', async () => {
183
+ const i1 = createInstance('1', 'svc');
184
+ const i2 = createInstance('2', 'svc');
185
+ await backend.register(i1);
186
+ await backend.register(i2);
187
+ const instances = await backend.getInstances('svc');
188
+ expect(instances).toHaveLength(2);
189
+ expect(redis.mget).toHaveBeenCalled();
190
+ });
191
+ it('should return empty array for non-existent service', async () => {
192
+ const instances = await backend.getInstances('non-existent');
193
+ expect(instances).toEqual([]);
194
+ });
195
+ it('should apply filter', async () => {
196
+ const i1 = createInstance('1', 'svc', { zone: 'us-east-1' });
197
+ const i2 = createInstance('2', 'svc', { zone: 'us-west-1' });
198
+ await backend.register(i1);
199
+ await backend.register(i2);
200
+ const instances = await backend.getInstances('svc', { zone: 'us-east-1' });
201
+ expect(instances).toHaveLength(1);
202
+ expect(instances[0].zone).toBe('us-east-1');
203
+ });
204
+ });
205
+ describe('getInstance', () => {
206
+ it('should return a specific instance', async () => {
207
+ const instance = createInstance('1');
208
+ await backend.register(instance);
209
+ const retrieved = await backend.getInstance('1');
210
+ expect(retrieved).toBeDefined();
211
+ expect(retrieved.id).toBe('1');
212
+ });
213
+ it('should return null for non-existent instance', async () => {
214
+ const result = await backend.getInstance('non-existent');
215
+ expect(result).toBeNull();
216
+ });
217
+ it('should convert dates back from strings', async () => {
218
+ const instance = createInstance('1');
219
+ await backend.register(instance);
220
+ const retrieved = await backend.getInstance('1');
221
+ expect(retrieved.lastHeartbeat).toBeInstanceOf(Date);
222
+ expect(retrieved.registeredAt).toBeInstanceOf(Date);
223
+ });
224
+ });
225
+ describe('getAllServices', () => {
226
+ it('should use SCAN instead of KEYS', async () => {
227
+ const i1 = createInstance('1', 'svc-a');
228
+ const i2 = createInstance('2', 'svc-b');
229
+ await backend.register(i1);
230
+ await backend.register(i2);
231
+ const services = await backend.getAllServices();
232
+ expect(redis.scan).toHaveBeenCalled();
233
+ expect(services).toHaveLength(2);
234
+ });
235
+ });
236
+ describe('updateStatus', () => {
237
+ it('should update the status of an instance', async () => {
238
+ const instance = createInstance('1');
239
+ await backend.register(instance);
240
+ await backend.updateStatus('1', types_1.ServiceStatus.DOWN);
241
+ const retrieved = await backend.getInstance('1');
242
+ expect(retrieved.status).toBe(types_1.ServiceStatus.DOWN);
243
+ });
244
+ it('should do nothing for non-existent instance', async () => {
245
+ await backend.updateStatus('non-existent', types_1.ServiceStatus.DOWN);
246
+ expect(redis.setex).not.toHaveBeenCalled();
247
+ });
248
+ });
249
+ describe('cleanup', () => {
250
+ it('should remove stale entries from service sets', async () => {
251
+ const instance = createInstance('1', 'svc');
252
+ await backend.register(instance);
253
+ // Simulate the instance key expiring in Redis
254
+ redis.exists.mockResolvedValueOnce(0);
255
+ await backend.cleanup();
256
+ expect(redis.srem).toHaveBeenCalledWith('hazeljs:discovery:service:svc', '1');
257
+ });
258
+ });
259
+ describe('connection error handling', () => {
260
+ it('should track connection state via events', () => {
261
+ redis._emit('close');
262
+ // Backend should mark as disconnected
263
+ // Next operation should throw
264
+ expect(backend.getInstances('svc')).rejects.toThrow('Redis backend is not connected');
265
+ });
266
+ it('should recover on connect event', async () => {
267
+ redis._emit('close');
268
+ redis._emit('connect');
269
+ // Should work again
270
+ const result = await backend.getInstances('svc');
271
+ expect(result).toEqual([]);
272
+ });
273
+ });
274
+ describe('close', () => {
275
+ it('should call redis.quit()', async () => {
276
+ await backend.close();
277
+ expect(redis.quit).toHaveBeenCalled();
278
+ });
279
+ });
280
+ });
@@ -139,7 +139,8 @@ describe('ServiceClient', () => {
139
139
  const mockRequest = jest.fn().mockRejectedValue(new Error('Network error'));
140
140
  serviceClient.axiosInstance = { request: mockRequest };
141
141
  await expect(serviceClient.get('/api/users')).rejects.toThrow();
142
- expect(mockRequest).toHaveBeenCalledTimes(3); // Default retries
142
+ // retries: 3 RetryPolicy maxAttempts: 3 → 1 initial + 3 retries = 4 total
143
+ expect(mockRequest).toHaveBeenCalledTimes(4);
143
144
  });
144
145
  it('should respect retry delay', async () => {
145
146
  const client = new service_client_1.ServiceClient(discoveryClient, {
@@ -4,7 +4,51 @@
4
4
  */
5
5
  import { RegistryBackend } from './registry-backend';
6
6
  import { ServiceInstance, ServiceFilter } from '../types';
7
- type Consul = any;
7
+ /**
8
+ * Minimal type definitions for the Consul client API surface we use.
9
+ * These mirror the shapes exposed by the `consul` npm package.
10
+ */
11
+ export interface ConsulClient {
12
+ agent: {
13
+ service: {
14
+ register(opts: Record<string, unknown>): Promise<void>;
15
+ deregister(serviceId: string): Promise<void>;
16
+ list(): Promise<Record<string, ConsulServiceEntry>>;
17
+ };
18
+ check: {
19
+ pass(checkId: string): Promise<void>;
20
+ fail(checkId: string): Promise<void>;
21
+ warn(checkId: string): Promise<void>;
22
+ list(): Promise<Record<string, ConsulCheckEntry>>;
23
+ };
24
+ };
25
+ health: {
26
+ service(opts: {
27
+ service: string;
28
+ passing?: boolean;
29
+ }): Promise<ConsulHealthEntry[]>;
30
+ };
31
+ catalog: {
32
+ service: {
33
+ list(): Promise<Record<string, string[]>>;
34
+ };
35
+ };
36
+ }
37
+ export interface ConsulServiceEntry {
38
+ ID: string;
39
+ Service: string;
40
+ Address: string;
41
+ Port: number;
42
+ Meta?: Record<string, string>;
43
+ Tags?: string[];
44
+ }
45
+ export interface ConsulCheckEntry {
46
+ Status: string;
47
+ }
48
+ export interface ConsulHealthEntry {
49
+ Service: ConsulServiceEntry;
50
+ Checks?: ConsulCheckEntry[];
51
+ }
8
52
  export interface ConsulBackendConfig {
9
53
  host?: string;
10
54
  port?: number;
@@ -17,7 +61,7 @@ export declare class ConsulRegistryBackend implements RegistryBackend {
17
61
  private consul;
18
62
  private readonly ttl;
19
63
  private checkIntervals;
20
- constructor(consul: Consul, config?: ConsulBackendConfig);
64
+ constructor(consul: ConsulClient, config?: ConsulBackendConfig);
21
65
  /**
22
66
  * Register a service instance with Consul
23
67
  */
@@ -67,10 +111,5 @@ export declare class ConsulRegistryBackend implements RegistryBackend {
67
111
  * Parse TTL string to seconds
68
112
  */
69
113
  private parseTTL;
70
- /**
71
- * Apply filter to instances
72
- */
73
- private applyFilter;
74
114
  }
75
- export {};
76
115
  //# sourceMappingURL=consul-backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consul-backend.d.ts","sourceRoot":"","sources":["../../src/backends/consul-backend.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAIzE,KAAK,MAAM,GAAG,GAAG,CAAC;AAElB,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,cAAc,CAA0C;gBAEpD,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,mBAAwB;IAK5D;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BxD;;OAEG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQnD;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA6D3F;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAwCtE;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IASzC;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBrE;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;OAEG;IACH,OAAO,CAAC,aAAa;IAgBrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAQpB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAqBhB;;OAEG;IACH,OAAO,CAAC,WAAW;CA2BpB"}
1
+ {"version":3,"file":"consul-backend.d.ts","sourceRoot":"","sources":["../../src/backends/consul-backend.ts"],"names":[],"mappings":"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"}
@@ -6,9 +6,13 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.ConsulRegistryBackend = void 0;
8
8
  const types_1 = require("../types");
9
+ const filter_1 = require("../utils/filter");
10
+ const logger_1 = require("../utils/logger");
11
+ const validation_1 = require("../utils/validation");
9
12
  class ConsulRegistryBackend {
10
13
  constructor(consul, config = {}) {
11
14
  this.checkIntervals = new Map();
15
+ (0, validation_1.validateConsulBackendConfig)(config);
12
16
  this.consul = consul;
13
17
  this.ttl = config.ttl || '30s';
14
18
  }
@@ -51,19 +55,21 @@ class ConsulRegistryBackend {
51
55
  * Update service instance heartbeat
52
56
  */
53
57
  async heartbeat(instanceId) {
58
+ const logger = logger_1.DiscoveryLogger.getLogger();
54
59
  const checkId = `service:${instanceId}`;
55
60
  try {
56
61
  // Pass TTL check
57
62
  await this.consul.agent.check.pass(checkId);
58
63
  }
59
- catch {
60
- // Silently fail - will be retried on next heartbeat
64
+ catch (error) {
65
+ logger.warn(`Consul heartbeat failed for ${instanceId}, will retry on next heartbeat`, error);
61
66
  }
62
67
  }
63
68
  /**
64
69
  * Get all instances of a service
65
70
  */
66
71
  async getInstances(serviceName, filter) {
72
+ const logger = logger_1.DiscoveryLogger.getLogger();
67
73
  try {
68
74
  const result = await this.consul.health.service({
69
75
  service: serviceName,
@@ -99,12 +105,10 @@ class ConsulRegistryBackend {
99
105
  };
100
106
  });
101
107
  // Apply additional filters
102
- if (filter) {
103
- return this.applyFilter(instances, filter);
104
- }
105
- return instances;
108
+ return (0, filter_1.applyServiceFilter)(instances, filter);
106
109
  }
107
- catch {
110
+ catch (error) {
111
+ logger.error(`Failed to get instances for service "${serviceName}" from Consul`, error);
108
112
  return [];
109
113
  }
110
114
  }
@@ -112,6 +116,7 @@ class ConsulRegistryBackend {
112
116
  * Get a specific service instance
113
117
  */
114
118
  async getInstance(instanceId) {
119
+ const logger = logger_1.DiscoveryLogger.getLogger();
115
120
  try {
116
121
  const services = await this.consul.agent.service.list();
117
122
  const service = services[instanceId];
@@ -144,7 +149,8 @@ class ConsulRegistryBackend {
144
149
  registeredAt: service.Meta?.registeredAt ? new Date(service.Meta.registeredAt) : new Date(),
145
150
  };
146
151
  }
147
- catch {
152
+ catch (error) {
153
+ logger.error(`Failed to get instance "${instanceId}" from Consul`, error);
148
154
  return null;
149
155
  }
150
156
  }
@@ -152,11 +158,13 @@ class ConsulRegistryBackend {
152
158
  * Get all registered services
153
159
  */
154
160
  async getAllServices() {
161
+ const logger = logger_1.DiscoveryLogger.getLogger();
155
162
  try {
156
163
  const services = await this.consul.catalog.service.list();
157
164
  return Object.keys(services);
158
165
  }
159
- catch {
166
+ catch (error) {
167
+ logger.error('Failed to list services from Consul', error);
160
168
  return [];
161
169
  }
162
170
  }
@@ -164,6 +172,7 @@ class ConsulRegistryBackend {
164
172
  * Update service instance status
165
173
  */
166
174
  async updateStatus(instanceId, status) {
175
+ const logger = logger_1.DiscoveryLogger.getLogger();
167
176
  const checkId = `service:${instanceId}`;
168
177
  try {
169
178
  if (status === types_1.ServiceStatus.UP) {
@@ -176,8 +185,8 @@ class ConsulRegistryBackend {
176
185
  await this.consul.agent.check.warn(checkId);
177
186
  }
178
187
  }
179
- catch {
180
- // Silently fail - status will be updated on next heartbeat
188
+ catch (error) {
189
+ logger.warn(`Failed to update status for ${instanceId} in Consul`, error);
181
190
  }
182
191
  }
183
192
  /**
@@ -201,6 +210,7 @@ class ConsulRegistryBackend {
201
210
  * Start TTL check updates for a service
202
211
  */
203
212
  startTTLCheck(instanceId, checkId) {
213
+ const logger = logger_1.DiscoveryLogger.getLogger();
204
214
  // Parse TTL to get interval (update at 2/3 of TTL)
205
215
  const ttlSeconds = this.parseTTL(this.ttl);
206
216
  const intervalMs = (ttlSeconds * 1000 * 2) / 3;
@@ -208,8 +218,8 @@ class ConsulRegistryBackend {
208
218
  try {
209
219
  await this.consul.agent.check.pass(checkId);
210
220
  }
211
- catch {
212
- // Silently fail - will be retried on next interval
221
+ catch (error) {
222
+ logger.warn(`TTL check pass failed for ${instanceId}`, error);
213
223
  }
214
224
  }, intervalMs);
215
225
  this.checkIntervals.set(instanceId, interval);
@@ -245,31 +255,5 @@ class ConsulRegistryBackend {
245
255
  return 30;
246
256
  }
247
257
  }
248
- /**
249
- * Apply filter to instances
250
- */
251
- applyFilter(instances, filter) {
252
- return instances.filter((instance) => {
253
- if (filter.zone && instance.zone !== filter.zone) {
254
- return false;
255
- }
256
- if (filter.status && instance.status !== filter.status) {
257
- return false;
258
- }
259
- if (filter.tags && filter.tags.length > 0) {
260
- if (!instance.tags || !filter.tags.every((tag) => instance.tags.includes(tag))) {
261
- return false;
262
- }
263
- }
264
- if (filter.metadata) {
265
- for (const [key, value] of Object.entries(filter.metadata)) {
266
- if (!instance.metadata || instance.metadata[key] !== value) {
267
- return false;
268
- }
269
- }
270
- }
271
- return true;
272
- });
273
- }
274
258
  }
275
259
  exports.ConsulRegistryBackend = ConsulRegistryBackend;
@@ -4,7 +4,50 @@
4
4
  */
5
5
  import { RegistryBackend } from './registry-backend';
6
6
  import { ServiceInstance, ServiceFilter } from '../types';
7
- type KubeConfig = any;
7
+ /**
8
+ * Minimal type definitions for the Kubernetes client API surface we use.
9
+ * These mirror the shapes exposed by `@kubernetes/client-node`.
10
+ */
11
+ export interface KubeConfig {
12
+ makeApiClient(apiClass: new () => any): any;
13
+ }
14
+ export interface K8sEndpointAddress {
15
+ ip: string;
16
+ targetRef?: {
17
+ name?: string;
18
+ };
19
+ nodeName?: string;
20
+ }
21
+ export interface K8sEndpointPort {
22
+ port: number;
23
+ }
24
+ export interface K8sEndpointSubset {
25
+ ports?: K8sEndpointPort[];
26
+ addresses?: K8sEndpointAddress[];
27
+ notReadyAddresses?: K8sEndpointAddress[];
28
+ }
29
+ export interface K8sObjectMeta {
30
+ name?: string;
31
+ annotations?: Record<string, string>;
32
+ labels?: Record<string, string>;
33
+ creationTimestamp?: string;
34
+ }
35
+ export interface K8sEndpoints {
36
+ metadata?: K8sObjectMeta;
37
+ subsets?: K8sEndpointSubset[];
38
+ }
39
+ export interface K8sService {
40
+ metadata?: K8sObjectMeta;
41
+ }
42
+ export interface K8sApiResponse<T> {
43
+ body: T;
44
+ }
45
+ export interface CoreV1ApiLike {
46
+ readNamespacedEndpoints(name: string, namespace: string): Promise<K8sApiResponse<K8sEndpoints>>;
47
+ listNamespacedService(namespace: string, pretty?: string, allowWatchBookmarks?: boolean, _continue?: string, fieldSelector?: string, labelSelector?: string): Promise<K8sApiResponse<{
48
+ items: K8sService[];
49
+ }>>;
50
+ }
8
51
  export interface KubernetesBackendConfig {
9
52
  namespace?: string;
10
53
  labelSelector?: string;
@@ -56,10 +99,5 @@ export declare class KubernetesRegistryBackend implements RegistryBackend {
56
99
  * Create a ServiceInstance from Kubernetes endpoint data
57
100
  */
58
101
  private createServiceInstance;
59
- /**
60
- * Apply filter to instances
61
- */
62
- private applyFilter;
63
102
  }
64
- export {};
65
103
  //# sourceMappingURL=kubernetes-backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"kubernetes-backend.d.ts","sourceRoot":"","sources":["../../src/backends/kubernetes-backend.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAIzE,KAAK,UAAU,GAAG,GAAG,CAAC;AAItB,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,yBAA0B,YAAW,eAAe;IAC/D,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,UAAU,EAAE,UAAU,EAAE,MAAM,GAAE,uBAA4B;IASxE;;;;OAIG;IACG,QAAQ,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzD;;;OAGG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD;;;OAGG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA6D3F;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQtE;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAmBzC;;;OAGG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAsC7B;;OAEG;IACH,OAAO,CAAC,WAAW;CA2BpB"}
1
+ {"version":3,"file":"kubernetes-backend.d.ts","sourceRoot":"","sources":["../../src/backends/kubernetes-backend.ts"],"names":[],"mappings":"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"}