@hazeljs/discovery 0.2.0-alpha.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.
Files changed (78) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +450 -0
  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__/decorators.test.d.ts +5 -0
  7. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  8. package/dist/__tests__/decorators.test.js +72 -0
  9. package/dist/__tests__/discovery-client.test.d.ts +5 -0
  10. package/dist/__tests__/discovery-client.test.d.ts.map +1 -0
  11. package/dist/__tests__/discovery-client.test.js +142 -0
  12. package/dist/__tests__/kubernetes-backend.test.d.ts +6 -0
  13. package/dist/__tests__/kubernetes-backend.test.d.ts.map +1 -0
  14. package/dist/__tests__/kubernetes-backend.test.js +261 -0
  15. package/dist/__tests__/load-balancer-strategies.test.d.ts +5 -0
  16. package/dist/__tests__/load-balancer-strategies.test.d.ts.map +1 -0
  17. package/dist/__tests__/load-balancer-strategies.test.js +234 -0
  18. package/dist/__tests__/memory-backend.test.d.ts +5 -0
  19. package/dist/__tests__/memory-backend.test.d.ts.map +1 -0
  20. package/dist/__tests__/memory-backend.test.js +246 -0
  21. package/dist/__tests__/redis-backend.test.d.ts +6 -0
  22. package/dist/__tests__/redis-backend.test.d.ts.map +1 -0
  23. package/dist/__tests__/redis-backend.test.js +280 -0
  24. package/dist/__tests__/service-client.test.d.ts +5 -0
  25. package/dist/__tests__/service-client.test.d.ts.map +1 -0
  26. package/dist/__tests__/service-client.test.js +216 -0
  27. package/dist/__tests__/service-registry.test.d.ts +5 -0
  28. package/dist/__tests__/service-registry.test.d.ts.map +1 -0
  29. package/dist/__tests__/service-registry.test.js +65 -0
  30. package/dist/backends/consul-backend.d.ts +115 -0
  31. package/dist/backends/consul-backend.d.ts.map +1 -0
  32. package/dist/backends/consul-backend.js +259 -0
  33. package/dist/backends/kubernetes-backend.d.ts +103 -0
  34. package/dist/backends/kubernetes-backend.d.ts.map +1 -0
  35. package/dist/backends/kubernetes-backend.js +153 -0
  36. package/dist/backends/memory-backend.d.ts +21 -0
  37. package/dist/backends/memory-backend.d.ts.map +1 -0
  38. package/dist/backends/memory-backend.js +86 -0
  39. package/dist/backends/redis-backend.d.ts +76 -0
  40. package/dist/backends/redis-backend.d.ts.map +1 -0
  41. package/dist/backends/redis-backend.js +220 -0
  42. package/dist/backends/registry-backend.d.ts +39 -0
  43. package/dist/backends/registry-backend.d.ts.map +1 -0
  44. package/dist/backends/registry-backend.js +5 -0
  45. package/dist/client/discovery-client.d.ts +49 -0
  46. package/dist/client/discovery-client.d.ts.map +1 -0
  47. package/dist/client/discovery-client.js +123 -0
  48. package/dist/client/service-client.d.ts +48 -0
  49. package/dist/client/service-client.d.ts.map +1 -0
  50. package/dist/client/service-client.js +155 -0
  51. package/dist/decorators/inject-service-client.decorator.d.ts +16 -0
  52. package/dist/decorators/inject-service-client.decorator.d.ts.map +1 -0
  53. package/dist/decorators/inject-service-client.decorator.js +24 -0
  54. package/dist/decorators/service-registry.decorator.d.ts +11 -0
  55. package/dist/decorators/service-registry.decorator.d.ts.map +1 -0
  56. package/dist/decorators/service-registry.decorator.js +20 -0
  57. package/dist/index.d.ts +21 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +51 -0
  60. package/dist/load-balancer/strategies.d.ts +82 -0
  61. package/dist/load-balancer/strategies.d.ts.map +1 -0
  62. package/dist/load-balancer/strategies.js +209 -0
  63. package/dist/registry/service-registry.d.ts +51 -0
  64. package/dist/registry/service-registry.d.ts.map +1 -0
  65. package/dist/registry/service-registry.js +159 -0
  66. package/dist/types/index.d.ts +61 -0
  67. package/dist/types/index.d.ts.map +1 -0
  68. package/dist/types/index.js +14 -0
  69. package/dist/utils/filter.d.ts +10 -0
  70. package/dist/utils/filter.d.ts.map +1 -0
  71. package/dist/utils/filter.js +39 -0
  72. package/dist/utils/logger.d.ts +21 -0
  73. package/dist/utils/logger.d.ts.map +1 -0
  74. package/dist/utils/logger.js +34 -0
  75. package/dist/utils/validation.d.ts +36 -0
  76. package/dist/utils/validation.d.ts.map +1 -0
  77. package/dist/utils/validation.js +109 -0
  78. package/package.json +81 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Load Balancing Strategies
3
+ */
4
+ import { ServiceInstance, LoadBalancerStrategy } from '../types';
5
+ /**
6
+ * Base strategy that filters healthy instances
7
+ */
8
+ declare abstract class BaseStrategy implements LoadBalancerStrategy {
9
+ abstract name: string;
10
+ protected filterHealthy(instances: ServiceInstance[]): ServiceInstance[];
11
+ abstract choose(instances: ServiceInstance[]): ServiceInstance | null;
12
+ }
13
+ /**
14
+ * Round Robin Strategy
15
+ */
16
+ export declare class RoundRobinStrategy extends BaseStrategy {
17
+ name: string;
18
+ private currentIndex;
19
+ choose(instances: ServiceInstance[]): ServiceInstance | null;
20
+ }
21
+ /**
22
+ * Random Strategy
23
+ */
24
+ export declare class RandomStrategy extends BaseStrategy {
25
+ name: string;
26
+ choose(instances: ServiceInstance[]): ServiceInstance | null;
27
+ }
28
+ /**
29
+ * Least Connections Strategy
30
+ * Tracks active connections per instance
31
+ */
32
+ export declare class LeastConnectionsStrategy extends BaseStrategy {
33
+ name: string;
34
+ private connections;
35
+ choose(instances: ServiceInstance[]): ServiceInstance | null;
36
+ incrementConnections(instanceId: string): void;
37
+ decrementConnections(instanceId: string): void;
38
+ }
39
+ /**
40
+ * Weighted Round Robin Strategy
41
+ * Uses metadata.weight for weighted selection
42
+ */
43
+ export declare class WeightedRoundRobinStrategy extends BaseStrategy {
44
+ name: string;
45
+ private currentIndex;
46
+ private currentWeight;
47
+ choose(instances: ServiceInstance[]): ServiceInstance | null;
48
+ }
49
+ /**
50
+ * IP Hash Strategy
51
+ * Consistent hashing based on client IP
52
+ */
53
+ export declare class IPHashStrategy extends BaseStrategy {
54
+ name: string;
55
+ choose(instances: ServiceInstance[], clientIP?: string): ServiceInstance | null;
56
+ private hashCode;
57
+ }
58
+ /**
59
+ * Zone Aware Strategy
60
+ * Prefers instances in the same zone
61
+ */
62
+ export declare class ZoneAwareStrategy extends BaseStrategy {
63
+ private preferredZone?;
64
+ name: string;
65
+ constructor(preferredZone?: string | undefined);
66
+ choose(instances: ServiceInstance[]): ServiceInstance | null;
67
+ }
68
+ /**
69
+ * Load Balancer Factory
70
+ */
71
+ export declare class LoadBalancerFactory {
72
+ private strategies;
73
+ constructor();
74
+ private registerDefaultStrategies;
75
+ register(strategy: LoadBalancerStrategy): void;
76
+ get(name: string): LoadBalancerStrategy | undefined;
77
+ create(name: string, options?: {
78
+ zone?: string;
79
+ }): LoadBalancerStrategy;
80
+ }
81
+ export {};
82
+ //# sourceMappingURL=strategies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategies.d.ts","sourceRoot":"","sources":["../../src/load-balancer/strategies.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAiB,MAAM,UAAU,CAAC;AAEhF;;GAEG;AACH,uBAAe,YAAa,YAAW,oBAAoB;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE;IAIxE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,GAAG,IAAI;CACtE;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,IAAI,SAAiB;IACrB,OAAO,CAAC,YAAY,CAAK;IAEzB,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,GAAG,IAAI;CAQ7D;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,IAAI,SAAY;IAEhB,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,GAAG,IAAI;CAO7D;AAED;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,YAAY;IACxD,IAAI,SAAuB;IAC3B,OAAO,CAAC,WAAW,CAA6B;IAEhD,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,GAAG,IAAI;IAmB5D,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAK9C,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAI/C;AAED;;;GAGG;AACH,qBAAa,0BAA2B,SAAQ,YAAY;IAC1D,IAAI,SAA0B;IAC9B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,aAAa,CAAK;IAE1B,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,GAAG,IAAI;CAoB7D;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,IAAI,SAAa;IAEjB,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAW/E,OAAO,CAAC,QAAQ;CASjB;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IAGrC,OAAO,CAAC,aAAa,CAAC;IAFlC,IAAI,SAAgB;gBAEA,aAAa,CAAC,EAAE,MAAM,YAAA;IAI1C,MAAM,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,eAAe,GAAG,IAAI;CAe7D;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,UAAU,CAA2C;;IAM7D,OAAO,CAAC,yBAAyB;IAQjC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI;IAI9C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS;IAInD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,oBAAoB;CAWxE"}
@@ -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;AAM/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;IAO3B;;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;IAWpB;;OAEG;YACW,kBAAkB;IA0BhC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;OAEG;IACH,OAAO,CAAC,UAAU;CAgBnB"}
@@ -0,0 +1,159 @@
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 logger_1 = require("../utils/logger");
14
+ const validation_1 = require("../utils/validation");
15
+ const axios_1 = __importDefault(require("axios"));
16
+ class ServiceRegistry {
17
+ constructor(config, backend) {
18
+ this.config = config;
19
+ this.instance = null;
20
+ this.heartbeatInterval = null;
21
+ this.cleanupInterval = null;
22
+ (0, validation_1.validateServiceRegistryConfig)(config);
23
+ this.backend = backend || new memory_backend_1.MemoryRegistryBackend();
24
+ }
25
+ /**
26
+ * Register this service instance
27
+ */
28
+ async register() {
29
+ const host = this.config.host || this.getLocalIP();
30
+ const instanceId = this.generateInstanceId(this.config.name, host, this.config.port);
31
+ this.instance = {
32
+ id: instanceId,
33
+ name: this.config.name,
34
+ host,
35
+ port: this.config.port,
36
+ protocol: this.config.protocol || 'http',
37
+ metadata: this.config.metadata || {},
38
+ healthCheckPath: this.config.healthCheckPath || '/health',
39
+ healthCheckInterval: this.config.healthCheckInterval || 30000,
40
+ zone: this.config.zone,
41
+ tags: this.config.tags || [],
42
+ status: types_1.ServiceStatus.STARTING,
43
+ lastHeartbeat: new Date(),
44
+ registeredAt: new Date(),
45
+ };
46
+ await this.backend.register(this.instance);
47
+ // Start heartbeat
48
+ this.startHeartbeat();
49
+ // Start cleanup task
50
+ this.startCleanup();
51
+ // Perform initial health check
52
+ await this.performHealthCheck();
53
+ }
54
+ /**
55
+ * Deregister this service instance
56
+ */
57
+ async deregister() {
58
+ if (this.heartbeatInterval) {
59
+ clearInterval(this.heartbeatInterval);
60
+ this.heartbeatInterval = null;
61
+ }
62
+ if (this.cleanupInterval) {
63
+ clearInterval(this.cleanupInterval);
64
+ this.cleanupInterval = null;
65
+ }
66
+ if (this.instance) {
67
+ await this.backend.deregister(this.instance.id);
68
+ this.instance = null;
69
+ }
70
+ }
71
+ /**
72
+ * Get the current service instance
73
+ */
74
+ getInstance() {
75
+ return this.instance;
76
+ }
77
+ /**
78
+ * Get the backend
79
+ */
80
+ getBackend() {
81
+ return this.backend;
82
+ }
83
+ /**
84
+ * Start heartbeat interval
85
+ */
86
+ startHeartbeat() {
87
+ if (!this.instance)
88
+ return;
89
+ const interval = this.instance.healthCheckInterval || 30000;
90
+ this.heartbeatInterval = setInterval(async () => {
91
+ if (this.instance) {
92
+ await this.performHealthCheck();
93
+ }
94
+ }, interval);
95
+ }
96
+ /**
97
+ * Start cleanup interval
98
+ */
99
+ startCleanup() {
100
+ this.cleanupInterval = setInterval(async () => {
101
+ try {
102
+ await this.backend.cleanup();
103
+ }
104
+ catch (error) {
105
+ const logger = logger_1.DiscoveryLogger.getLogger();
106
+ logger.error('Cleanup task failed', error);
107
+ }
108
+ }, 60000); // Run every minute
109
+ }
110
+ /**
111
+ * Perform health check
112
+ */
113
+ async performHealthCheck() {
114
+ if (!this.instance)
115
+ return;
116
+ const logger = logger_1.DiscoveryLogger.getLogger();
117
+ try {
118
+ const url = `${this.instance.protocol}://${this.instance.host}:${this.instance.port}${this.instance.healthCheckPath}`;
119
+ const response = await axios_1.default.get(url, { timeout: 5000 });
120
+ if (response.status === 200) {
121
+ this.instance.status = types_1.ServiceStatus.UP;
122
+ await this.backend.heartbeat(this.instance.id);
123
+ }
124
+ else {
125
+ this.instance.status = types_1.ServiceStatus.DOWN;
126
+ await this.backend.updateStatus(this.instance.id, types_1.ServiceStatus.DOWN);
127
+ }
128
+ }
129
+ catch (error) {
130
+ this.instance.status = types_1.ServiceStatus.DOWN;
131
+ await this.backend.updateStatus(this.instance.id, types_1.ServiceStatus.DOWN);
132
+ logger.warn(`Health check failed for ${this.instance.name} (${this.instance.id}), marking as DOWN`, error);
133
+ }
134
+ }
135
+ /**
136
+ * Generate unique instance ID
137
+ */
138
+ generateInstanceId(name, host, port) {
139
+ return `${name}:${host}:${port}:${Date.now()}`;
140
+ }
141
+ /**
142
+ * Get local IP address
143
+ */
144
+ getLocalIP() {
145
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
146
+ const { networkInterfaces } = require('os');
147
+ const nets = networkInterfaces();
148
+ for (const name of Object.keys(nets)) {
149
+ for (const net of nets[name]) {
150
+ // Skip internal and non-IPv4 addresses
151
+ if (net.family === 'IPv4' && !net.internal) {
152
+ return net.address;
153
+ }
154
+ }
155
+ }
156
+ return 'localhost';
157
+ }
158
+ }
159
+ 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 = {}));
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared filter utility for service instances
3
+ */
4
+ import { ServiceInstance, ServiceFilter } from '../types';
5
+ /**
6
+ * Apply a ServiceFilter to an array of ServiceInstances.
7
+ * Returns only instances matching all specified filter criteria.
8
+ */
9
+ export declare function applyServiceFilter(instances: ServiceInstance[], filter?: ServiceFilter): ServiceInstance[];
10
+ //# sourceMappingURL=filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/utils/filter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1D;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,eAAe,EAAE,EAC5B,MAAM,CAAC,EAAE,aAAa,GACrB,eAAe,EAAE,CAgCnB"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * Shared filter utility for service instances
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.applyServiceFilter = applyServiceFilter;
7
+ /**
8
+ * Apply a ServiceFilter to an array of ServiceInstances.
9
+ * Returns only instances matching all specified filter criteria.
10
+ */
11
+ function applyServiceFilter(instances, filter) {
12
+ if (!filter)
13
+ return instances;
14
+ return instances.filter((instance) => {
15
+ // Filter by zone
16
+ if (filter.zone && instance.zone !== filter.zone) {
17
+ return false;
18
+ }
19
+ // Filter by status
20
+ if (filter.status && instance.status !== filter.status) {
21
+ return false;
22
+ }
23
+ // Filter by tags
24
+ if (filter.tags && filter.tags.length > 0) {
25
+ if (!instance.tags || !filter.tags.every((tag) => instance.tags.includes(tag))) {
26
+ return false;
27
+ }
28
+ }
29
+ // Filter by metadata
30
+ if (filter.metadata) {
31
+ for (const [key, value] of Object.entries(filter.metadata)) {
32
+ if (!instance.metadata || instance.metadata[key] !== value) {
33
+ return false;
34
+ }
35
+ }
36
+ }
37
+ return true;
38
+ });
39
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pluggable logger for @hazeljs/discovery
3
+ *
4
+ * Consumers can supply their own logger via DiscoveryLogger.setLogger().
5
+ * The default logger writes to the console.
6
+ */
7
+ export interface Logger {
8
+ debug(message: string, ...args: unknown[]): void;
9
+ info(message: string, ...args: unknown[]): void;
10
+ warn(message: string, ...args: unknown[]): void;
11
+ error(message: string, ...args: unknown[]): void;
12
+ }
13
+ export declare const DiscoveryLogger: {
14
+ /** Replace the default console logger with a custom implementation */
15
+ setLogger(logger: Logger): void;
16
+ /** Reset to the default console logger */
17
+ resetLogger(): void;
18
+ /** Get the current logger instance */
19
+ getLogger(): Logger;
20
+ };
21
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAClD;AAeD,eAAO,MAAM,eAAe;IAC1B,sEAAsE;sBACpD,MAAM,GAAG,IAAI;IAI/B,0CAA0C;mBAC3B,IAAI;IAInB,sCAAsC;iBACzB,MAAM;CAGpB,CAAC"}