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

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
@@ -6,8 +6,12 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.KubernetesRegistryBackend = 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 KubernetesRegistryBackend {
10
13
  constructor(kubeConfig, config = {}) {
14
+ (0, validation_1.validateKubernetesBackendConfig)(config);
11
15
  // Import CoreV1Api dynamically to avoid build errors when @kubernetes/client-node is not installed
12
16
  // eslint-disable-next-line @typescript-eslint/no-require-imports
13
17
  const { CoreV1Api: CoreV1ApiClass } = require('@kubernetes/client-node');
@@ -44,6 +48,7 @@ class KubernetesRegistryBackend {
44
48
  * Get all instances of a service from Kubernetes Endpoints
45
49
  */
46
50
  async getInstances(serviceName, filter) {
51
+ const logger = logger_1.DiscoveryLogger.getLogger();
47
52
  try {
48
53
  // Get service endpoints
49
54
  const endpointsResponse = await this.k8sApi.readNamespacedEndpoints(serviceName, this.namespace);
@@ -73,12 +78,10 @@ class KubernetesRegistryBackend {
73
78
  }
74
79
  }
75
80
  // Apply filters
76
- if (filter) {
77
- return this.applyFilter(instances, filter);
78
- }
79
- return instances;
81
+ return (0, filter_1.applyServiceFilter)(instances, filter);
80
82
  }
81
- catch {
83
+ catch (error) {
84
+ logger.error(`Failed to get instances for service "${serviceName}" from Kubernetes`, error);
82
85
  return [];
83
86
  }
84
87
  }
@@ -95,11 +98,13 @@ class KubernetesRegistryBackend {
95
98
  * Get all registered services in the namespace
96
99
  */
97
100
  async getAllServices() {
101
+ const logger = logger_1.DiscoveryLogger.getLogger();
98
102
  try {
99
103
  const servicesResponse = await this.k8sApi.listNamespacedService(this.namespace, undefined, undefined, undefined, undefined, this.labelSelector);
100
104
  return servicesResponse.body.items.map((service) => service.metadata?.name || '');
101
105
  }
102
- catch {
106
+ catch (error) {
107
+ logger.error('Failed to list services from Kubernetes', error);
103
108
  return [];
104
109
  }
105
110
  }
@@ -144,31 +149,5 @@ class KubernetesRegistryBackend {
144
149
  registeredAt: new Date(metadata?.creationTimestamp || Date.now()),
145
150
  };
146
151
  }
147
- /**
148
- * Apply filter to instances
149
- */
150
- applyFilter(instances, filter) {
151
- return instances.filter((instance) => {
152
- if (filter.zone && instance.zone !== filter.zone) {
153
- return false;
154
- }
155
- if (filter.status && instance.status !== filter.status) {
156
- return false;
157
- }
158
- if (filter.tags && filter.tags.length > 0) {
159
- if (!instance.tags || !filter.tags.every((tag) => instance.tags.includes(tag))) {
160
- return false;
161
- }
162
- }
163
- if (filter.metadata) {
164
- for (const [key, value] of Object.entries(filter.metadata)) {
165
- if (!instance.metadata || instance.metadata[key] !== value) {
166
- return false;
167
- }
168
- }
169
- }
170
- return true;
171
- });
172
- }
173
152
  }
174
153
  exports.KubernetesRegistryBackend = KubernetesRegistryBackend;
@@ -17,6 +17,5 @@ export declare class MemoryRegistryBackend implements RegistryBackend {
17
17
  getAllServices(): Promise<string[]>;
18
18
  updateStatus(instanceId: string, status: string): Promise<void>;
19
19
  cleanup(): Promise<void>;
20
- private applyFilter;
21
20
  }
22
21
  //# sourceMappingURL=memory-backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-backend.d.ts","sourceRoot":"","sources":["../../src/backends/memory-backend.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAEzE,qBAAa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,cAAc,SAAQ;IAK5B,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlD,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7C,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5C,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAoBrF,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAIhE,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAInC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB9B,OAAO,CAAC,WAAW;CA+BpB"}
1
+ {"version":3,"file":"memory-backend.d.ts","sourceRoot":"","sources":["../../src/backends/memory-backend.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAGzE,qBAAa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,cAAc,SAAQ;IAK5B,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlD,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7C,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5C,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAgBrF,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAIhE,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAInC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAe/B"}
@@ -6,6 +6,7 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.MemoryRegistryBackend = void 0;
8
8
  const types_1 = require("../types");
9
+ const filter_1 = require("../utils/filter");
9
10
  class MemoryRegistryBackend {
10
11
  constructor(expirationTime = 90000) {
11
12
  this.instances = new Map();
@@ -46,7 +47,7 @@ class MemoryRegistryBackend {
46
47
  const instanceIds = this.serviceIndex.get(serviceName);
47
48
  if (!instanceIds)
48
49
  return [];
49
- let instances = [];
50
+ const instances = [];
50
51
  for (const id of instanceIds) {
51
52
  const instance = this.instances.get(id);
52
53
  if (instance) {
@@ -54,10 +55,7 @@ class MemoryRegistryBackend {
54
55
  }
55
56
  }
56
57
  // Apply filters
57
- if (filter) {
58
- instances = this.applyFilter(instances, filter);
59
- }
60
- return instances;
58
+ return (0, filter_1.applyServiceFilter)(instances, filter);
61
59
  }
62
60
  async getInstance(instanceId) {
63
61
  return this.instances.get(instanceId) || null;
@@ -84,32 +82,5 @@ class MemoryRegistryBackend {
84
82
  await this.deregister(id);
85
83
  }
86
84
  }
87
- applyFilter(instances, filter) {
88
- return instances.filter((instance) => {
89
- // Filter by zone
90
- if (filter.zone && instance.zone !== filter.zone) {
91
- return false;
92
- }
93
- // Filter by status
94
- if (filter.status && instance.status !== filter.status) {
95
- return false;
96
- }
97
- // Filter by tags
98
- if (filter.tags && filter.tags.length > 0) {
99
- if (!instance.tags || !filter.tags.every((tag) => instance.tags.includes(tag))) {
100
- return false;
101
- }
102
- }
103
- // Filter by metadata
104
- if (filter.metadata) {
105
- for (const [key, value] of Object.entries(filter.metadata)) {
106
- if (!instance.metadata || instance.metadata[key] !== value) {
107
- return false;
108
- }
109
- }
110
- }
111
- return true;
112
- });
113
- }
114
85
  }
115
86
  exports.MemoryRegistryBackend = MemoryRegistryBackend;
@@ -17,7 +17,16 @@ export declare class RedisRegistryBackend implements RegistryBackend {
17
17
  private redis;
18
18
  private readonly keyPrefix;
19
19
  private readonly ttl;
20
+ private connected;
20
21
  constructor(redis: Redis, config?: RedisBackendConfig);
22
+ /**
23
+ * Set up Redis connection event handlers for resilience
24
+ */
25
+ private setupConnectionHandlers;
26
+ /**
27
+ * Check Redis connectivity before operations
28
+ */
29
+ private ensureConnected;
21
30
  /**
22
31
  * Register a service instance
23
32
  */
@@ -31,7 +40,7 @@ export declare class RedisRegistryBackend implements RegistryBackend {
31
40
  */
32
41
  heartbeat(instanceId: string): Promise<void>;
33
42
  /**
34
- * Get all instances of a service
43
+ * Get all instances of a service (uses MGET for efficiency)
35
44
  */
36
45
  getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
37
46
  /**
@@ -39,7 +48,7 @@ export declare class RedisRegistryBackend implements RegistryBackend {
39
48
  */
40
49
  getInstance(instanceId: string): Promise<ServiceInstance | null>;
41
50
  /**
42
- * Get all registered services
51
+ * Get all registered services using SCAN (safe for production)
43
52
  */
44
53
  getAllServices(): Promise<string[]>;
45
54
  /**
@@ -63,9 +72,5 @@ export declare class RedisRegistryBackend implements RegistryBackend {
63
72
  * Get Redis key for service set
64
73
  */
65
74
  private getServiceSetKey;
66
- /**
67
- * Apply filter to instances
68
- */
69
- private applyFilter;
70
75
  }
71
76
  //# sourceMappingURL=redis-backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis-backend.d.ts","sourceRoot":"","sources":["../../src/backends/redis-backend.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AACzE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,kBAAuB;IAMzD;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxD;;OAEG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnD;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBlD;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA2B3F;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAiBtE;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAUzC;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYrE;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B9B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACH,OAAO,CAAC,WAAW;CA+BpB"}
1
+ {"version":3,"file":"redis-backend.d.ts","sourceRoot":"","sources":["../../src/backends/redis-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,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAQ;gBAEb,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,kBAAuB;IAUzD;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAuB/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxD;;OAEG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBnD;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBlD;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA8B3F;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAmBtE;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAoBzC;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrE;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B9B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAGzB"}
@@ -6,16 +6,52 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.RedisRegistryBackend = 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 RedisRegistryBackend {
10
13
  constructor(redis, config = {}) {
14
+ this.connected = true;
15
+ (0, validation_1.validateRedisBackendConfig)(config);
11
16
  this.redis = redis;
12
17
  this.keyPrefix = config.keyPrefix || 'hazeljs:discovery:';
13
18
  this.ttl = config.ttl || 90; // 90 seconds default
19
+ this.setupConnectionHandlers();
20
+ }
21
+ /**
22
+ * Set up Redis connection event handlers for resilience
23
+ */
24
+ setupConnectionHandlers() {
25
+ const logger = logger_1.DiscoveryLogger.getLogger();
26
+ this.redis.on('error', (err) => {
27
+ this.connected = false;
28
+ logger.error('Redis connection error', err);
29
+ });
30
+ this.redis.on('connect', () => {
31
+ this.connected = true;
32
+ logger.info('Redis connected');
33
+ });
34
+ this.redis.on('reconnecting', () => {
35
+ logger.warn('Redis reconnecting...');
36
+ });
37
+ this.redis.on('close', () => {
38
+ this.connected = false;
39
+ logger.warn('Redis connection closed');
40
+ });
41
+ }
42
+ /**
43
+ * Check Redis connectivity before operations
44
+ */
45
+ ensureConnected() {
46
+ if (!this.connected) {
47
+ throw new Error('Redis backend is not connected');
48
+ }
14
49
  }
15
50
  /**
16
51
  * Register a service instance
17
52
  */
18
53
  async register(instance) {
54
+ this.ensureConnected();
19
55
  const key = this.getInstanceKey(instance.id);
20
56
  const serviceSetKey = this.getServiceSetKey(instance.name);
21
57
  // Store instance data
@@ -29,6 +65,7 @@ class RedisRegistryBackend {
29
65
  * Deregister a service instance
30
66
  */
31
67
  async deregister(instanceId) {
68
+ this.ensureConnected();
32
69
  const key = this.getInstanceKey(instanceId);
33
70
  // Get instance to find service name
34
71
  const data = await this.redis.get(key);
@@ -45,6 +82,7 @@ class RedisRegistryBackend {
45
82
  * Update service instance heartbeat
46
83
  */
47
84
  async heartbeat(instanceId) {
85
+ this.ensureConnected();
48
86
  const key = this.getInstanceKey(instanceId);
49
87
  // Get current instance
50
88
  const data = await this.redis.get(key);
@@ -61,33 +99,36 @@ class RedisRegistryBackend {
61
99
  }
62
100
  }
63
101
  /**
64
- * Get all instances of a service
102
+ * Get all instances of a service (uses MGET for efficiency)
65
103
  */
66
104
  async getInstances(serviceName, filter) {
105
+ this.ensureConnected();
67
106
  const serviceSetKey = this.getServiceSetKey(serviceName);
68
107
  // Get all instance IDs for this service
69
108
  const instanceIds = await this.redis.smembers(serviceSetKey);
70
109
  if (instanceIds.length === 0) {
71
110
  return [];
72
111
  }
73
- // Get all instance data
112
+ // Batch-fetch all instances with MGET
113
+ const keys = instanceIds.map((id) => this.getInstanceKey(id));
114
+ const results = await this.redis.mget(...keys);
74
115
  const instances = [];
75
- for (const id of instanceIds) {
76
- const instance = await this.getInstance(id);
77
- if (instance) {
116
+ for (const data of results) {
117
+ if (data) {
118
+ const instance = JSON.parse(data);
119
+ instance.lastHeartbeat = new Date(instance.lastHeartbeat);
120
+ instance.registeredAt = new Date(instance.registeredAt);
78
121
  instances.push(instance);
79
122
  }
80
123
  }
81
124
  // Apply filters
82
- if (filter) {
83
- return this.applyFilter(instances, filter);
84
- }
85
- return instances;
125
+ return (0, filter_1.applyServiceFilter)(instances, filter);
86
126
  }
87
127
  /**
88
128
  * Get a specific service instance
89
129
  */
90
130
  async getInstance(instanceId) {
131
+ this.ensureConnected();
91
132
  const key = this.getInstanceKey(instanceId);
92
133
  const data = await this.redis.get(key);
93
134
  if (!data) {
@@ -100,20 +141,28 @@ class RedisRegistryBackend {
100
141
  return instance;
101
142
  }
102
143
  /**
103
- * Get all registered services
144
+ * Get all registered services using SCAN (safe for production)
104
145
  */
105
146
  async getAllServices() {
147
+ this.ensureConnected();
106
148
  const pattern = `${this.keyPrefix}service:*`;
107
- const keys = await this.redis.keys(pattern);
108
- return keys.map((key) => {
109
- const serviceName = key.replace(`${this.keyPrefix}service:`, '');
110
- return serviceName;
111
- });
149
+ const prefixLen = `${this.keyPrefix}service:`.length;
150
+ const services = [];
151
+ let cursor = '0';
152
+ do {
153
+ const [nextCursor, keys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
154
+ cursor = nextCursor;
155
+ for (const key of keys) {
156
+ services.push(key.substring(prefixLen));
157
+ }
158
+ } while (cursor !== '0');
159
+ return services;
112
160
  }
113
161
  /**
114
162
  * Update service instance status
115
163
  */
116
164
  async updateStatus(instanceId, status) {
165
+ this.ensureConnected();
117
166
  const key = this.getInstanceKey(instanceId);
118
167
  const data = await this.redis.get(key);
119
168
  if (data) {
@@ -127,8 +176,9 @@ class RedisRegistryBackend {
127
176
  * Note: Redis handles expiration automatically via TTL
128
177
  */
129
178
  async cleanup() {
179
+ this.ensureConnected();
130
180
  // Redis automatically removes expired keys
131
- // This method can be used for additional cleanup if needed
181
+ // This method cleans up stale entries in service sets
132
182
  const services = await this.getAllServices();
133
183
  for (const serviceName of services) {
134
184
  const serviceSetKey = this.getServiceSetKey(serviceName);
@@ -166,35 +216,5 @@ class RedisRegistryBackend {
166
216
  getServiceSetKey(serviceName) {
167
217
  return `${this.keyPrefix}service:${serviceName}`;
168
218
  }
169
- /**
170
- * Apply filter to instances
171
- */
172
- applyFilter(instances, filter) {
173
- return instances.filter((instance) => {
174
- // Filter by zone
175
- if (filter.zone && instance.zone !== filter.zone) {
176
- return false;
177
- }
178
- // Filter by status
179
- if (filter.status && instance.status !== filter.status) {
180
- return false;
181
- }
182
- // Filter by tags
183
- if (filter.tags && filter.tags.length > 0) {
184
- if (!instance.tags || !filter.tags.every((tag) => instance.tags.includes(tag))) {
185
- return false;
186
- }
187
- }
188
- // Filter by metadata
189
- if (filter.metadata) {
190
- for (const [key, value] of Object.entries(filter.metadata)) {
191
- if (!instance.metadata || instance.metadata[key] !== value) {
192
- return false;
193
- }
194
- }
195
- }
196
- return true;
197
- });
198
- }
199
219
  }
200
220
  exports.RedisRegistryBackend = RedisRegistryBackend;
@@ -10,6 +10,7 @@ export declare class DiscoveryClient {
10
10
  private backend;
11
11
  private cache;
12
12
  private loadBalancerFactory;
13
+ private refreshIntervalHandle;
13
14
  constructor(config?: DiscoveryClientConfig, backend?: RegistryBackend);
14
15
  /**
15
16
  * Get all instances of a service
@@ -36,12 +37,13 @@ export declare class DiscoveryClient {
36
37
  */
37
38
  getLoadBalancerFactory(): LoadBalancerFactory;
38
39
  /**
39
- * Start cache refresh interval
40
+ * Close the discovery client and release all resources.
41
+ * Stops the cache refresh interval and clears the cache.
40
42
  */
41
- private startRefreshInterval;
43
+ close(): void;
42
44
  /**
43
- * Apply filter to instances
45
+ * Start cache refresh interval
44
46
  */
45
- private applyFilter;
47
+ private startRefreshInterval;
46
48
  }
47
49
  //# sourceMappingURL=discovery-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"discovery-client.d.ts","sourceRoot":"","sources":["../../src/client/discovery-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,qBAAa,eAAe;IAMxB,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,KAAK,CAA0E;IACvF,OAAO,CAAC,mBAAmB,CAAsB;gBAGvC,MAAM,GAAE,qBAA0B,EAC1C,OAAO,CAAC,EAAE,eAAe;IAU3B;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA2B3F;;OAEG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,QAAQ,GAAE,MAAsB,EAChC,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQlC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIzC;;OAEG;IACH,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAQtC;;OAEG;IACH,UAAU,IAAI,eAAe;IAI7B;;OAEG;IACH,sBAAsB,IAAI,mBAAmB;IAI7C;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;OAEG;IACH,OAAO,CAAC,WAAW;CAepB"}
1
+ {"version":3,"file":"discovery-client.d.ts","sourceRoot":"","sources":["../../src/client/discovery-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAKlE,qBAAa,eAAe;IAOxB,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,KAAK,CAA0E;IACvF,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,qBAAqB,CAA+B;gBAGlD,MAAM,GAAE,qBAA0B,EAC1C,OAAO,CAAC,EAAE,eAAe;IAY3B;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA2B3F;;OAEG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,QAAQ,GAAE,MAAsB,EAChC,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQlC;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIzC;;OAEG;IACH,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI;IAQtC;;OAEG;IACH,UAAU,IAAI,eAAe;IAI7B;;OAEG;IACH,sBAAsB,IAAI,mBAAmB;IAI7C;;;OAGG;IACH,KAAK,IAAI,IAAI;IAQb;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAkB7B"}
@@ -7,10 +7,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.DiscoveryClient = void 0;
8
8
  const memory_backend_1 = require("../backends/memory-backend");
9
9
  const strategies_1 = require("../load-balancer/strategies");
10
+ const filter_1 = require("../utils/filter");
11
+ const logger_1 = require("../utils/logger");
12
+ const validation_1 = require("../utils/validation");
10
13
  class DiscoveryClient {
11
14
  constructor(config = {}, backend) {
12
15
  this.config = config;
13
16
  this.cache = new Map();
17
+ this.refreshIntervalHandle = null;
18
+ (0, validation_1.validateDiscoveryClientConfig)(config);
14
19
  this.backend = backend || new memory_backend_1.MemoryRegistryBackend();
15
20
  this.loadBalancerFactory = new strategies_1.LoadBalancerFactory();
16
21
  if (config.refreshInterval) {
@@ -28,7 +33,7 @@ class DiscoveryClient {
28
33
  const age = Date.now() - cached.timestamp;
29
34
  const ttl = this.config.cacheTTL || 30000;
30
35
  if (age < ttl) {
31
- return this.applyFilter(cached.instances, filter);
36
+ return (0, filter_1.applyServiceFilter)(cached.instances, filter);
32
37
  }
33
38
  }
34
39
  }
@@ -83,41 +88,36 @@ class DiscoveryClient {
83
88
  return this.loadBalancerFactory;
84
89
  }
85
90
  /**
86
- * Start cache refresh interval
91
+ * Close the discovery client and release all resources.
92
+ * Stops the cache refresh interval and clears the cache.
87
93
  */
88
- startRefreshInterval() {
89
- setInterval(async () => {
90
- const services = await this.getAllServices();
91
- for (const service of services) {
92
- const instances = await this.backend.getInstances(service);
93
- this.cache.set(service, {
94
- instances,
95
- timestamp: Date.now(),
96
- });
97
- }
98
- }, this.config.refreshInterval);
94
+ close() {
95
+ if (this.refreshIntervalHandle) {
96
+ clearInterval(this.refreshIntervalHandle);
97
+ this.refreshIntervalHandle = null;
98
+ }
99
+ this.cache.clear();
99
100
  }
100
101
  /**
101
- * Apply filter to instances
102
+ * Start cache refresh interval
102
103
  */
103
- applyFilter(instances, filter) {
104
- if (!filter)
105
- return instances;
106
- return instances.filter((instance) => {
107
- if (filter.zone && instance.zone !== filter.zone)
108
- return false;
109
- if (filter.status && instance.status !== filter.status)
110
- return false;
111
- if (filter.tags && !filter.tags.every((tag) => instance.tags?.includes(tag)))
112
- return false;
113
- if (filter.metadata) {
114
- for (const [key, value] of Object.entries(filter.metadata)) {
115
- if (instance.metadata?.[key] !== value)
116
- return false;
104
+ startRefreshInterval() {
105
+ const logger = logger_1.DiscoveryLogger.getLogger();
106
+ this.refreshIntervalHandle = setInterval(async () => {
107
+ try {
108
+ const services = await this.getAllServices();
109
+ for (const service of services) {
110
+ const instances = await this.backend.getInstances(service);
111
+ this.cache.set(service, {
112
+ instances,
113
+ timestamp: Date.now(),
114
+ });
117
115
  }
118
116
  }
119
- return true;
120
- });
117
+ catch (error) {
118
+ logger.error('Failed to refresh service cache', error);
119
+ }
120
+ }, this.config.refreshInterval);
121
121
  }
122
122
  }
123
123
  exports.DiscoveryClient = DiscoveryClient;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Service Client
3
- * HTTP client with automatic service discovery and load balancing
3
+ * HTTP client with automatic service discovery and load balancing.
4
+ * Uses @hazeljs/resilience RetryPolicy for retry logic.
4
5
  */
5
6
  import { DiscoveryClient } from './discovery-client';
6
7
  import { ServiceFilter } from '../types';
@@ -17,8 +18,7 @@ export declare class ServiceClient {
17
18
  private discoveryClient;
18
19
  private config;
19
20
  private axiosInstance;
20
- private retries;
21
- private retryDelay;
21
+ private retryPolicy;
22
22
  constructor(discoveryClient: DiscoveryClient, config: ServiceClientConfig);
23
23
  /**
24
24
  * GET request
@@ -41,12 +41,8 @@ export declare class ServiceClient {
41
41
  */
42
42
  patch<T = unknown>(path: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
43
43
  /**
44
- * Generic request with service discovery
44
+ * Generic request with service discovery and resilience-backed retry
45
45
  */
46
46
  private request;
47
- /**
48
- * Sleep utility
49
- */
50
- private sleep;
51
47
  }
52
48
  //# sourceMappingURL=service-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service-client.d.ts","sourceRoot":"","sources":["../../src/client/service-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAc,EAAiB,kBAAkB,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEhF,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,aAAa;IAMtB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;gBAGjB,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,mBAAmB;IAUrC;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5F;;OAEG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI/F;;OAEG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;YACW,OAAO;IA2CrB;;OAEG;IACH,OAAO,CAAC,KAAK;CAGd"}
1
+ {"version":3,"file":"service-client.d.ts","sourceRoot":"","sources":["../../src/client/service-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKzC,OAAc,EAAiB,kBAAkB,EAAE,aAAa,EAAc,MAAM,OAAO,CAAC;AAE5F,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyBD,qBAAa,aAAa;IAKtB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,WAAW,CAAc;gBAGvB,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,mBAAmB;IAyBrC;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5F;;OAEG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI/F;;OAEG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;YACW,OAAO;CAyCtB"}