@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
@@ -1,23 +1,89 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Service Client
4
- * HTTP client with automatic service discovery and load balancing
4
+ * HTTP client with automatic service discovery and load balancing.
5
+ * Uses @hazeljs/resilience RetryPolicy for retry logic.
5
6
  */
6
- var __importDefault = (this && this.__importDefault) || function (mod) {
7
- return (mod && mod.__esModule) ? mod : { "default": mod };
8
- };
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
9
40
  Object.defineProperty(exports, "__esModule", { value: true });
10
41
  exports.ServiceClient = void 0;
11
- const axios_1 = __importDefault(require("axios"));
42
+ const strategies_1 = require("../load-balancer/strategies");
43
+ const logger_1 = require("../utils/logger");
44
+ const validation_1 = require("../utils/validation");
45
+ const resilience_1 = require("@hazeljs/resilience");
46
+ const axios_1 = __importStar(require("axios"));
47
+ /** HTTP status codes that are safe to retry */
48
+ const RETRYABLE_STATUS_CODES = new Set([502, 503, 504, 408, 429]);
49
+ /**
50
+ * Determine whether an error is transient and worth retrying.
51
+ * Client errors (4xx except 408/429) are NOT retried.
52
+ */
53
+ function isRetryableError(error) {
54
+ if (error instanceof axios_1.AxiosError) {
55
+ // Network / timeout errors are always retryable
56
+ if (!error.response)
57
+ return true;
58
+ return RETRYABLE_STATUS_CODES.has(error.response.status);
59
+ }
60
+ // "No instances available" is not transient - do not retry
61
+ const msg = error instanceof Error ? error.message : String(error);
62
+ if (msg.includes('No instances available'))
63
+ return false;
64
+ // Other non-Axios errors (e.g. network) are retryable
65
+ return true;
66
+ }
12
67
  class ServiceClient {
13
68
  constructor(discoveryClient, config) {
14
69
  this.discoveryClient = discoveryClient;
15
70
  this.config = config;
16
- this.retries = config.retries || 3;
17
- this.retryDelay = config.retryDelay || 1000;
71
+ (0, validation_1.validateServiceClientConfig)(config);
18
72
  this.axiosInstance = axios_1.default.create({
19
73
  timeout: config.timeout || 5000,
20
74
  });
75
+ // Delegate retry logic to @hazeljs/resilience RetryPolicy
76
+ const logger = logger_1.DiscoveryLogger.getLogger();
77
+ this.retryPolicy = new resilience_1.RetryPolicy({
78
+ maxAttempts: config.retries ?? 3,
79
+ backoff: 'fixed',
80
+ baseDelay: config.retryDelay ?? 1000,
81
+ jitter: false,
82
+ retryPredicate: isRetryableError,
83
+ onRetry: (error, attempt) => {
84
+ logger.warn(`Request to ${config.serviceName} failed (attempt ${attempt}/${config.retries ?? 3})`, error);
85
+ },
86
+ });
21
87
  }
22
88
  /**
23
89
  * GET request
@@ -50,17 +116,24 @@ class ServiceClient {
50
116
  return this.request({ ...config, method: 'PATCH', url: path, data });
51
117
  }
52
118
  /**
53
- * Generic request with service discovery
119
+ * Generic request with service discovery and resilience-backed retry
54
120
  */
55
121
  async request(config) {
56
- let lastError = null;
57
- for (let attempt = 0; attempt < this.retries; attempt++) {
122
+ return this.retryPolicy.execute(async () => {
123
+ // Discover service instance on each attempt (may pick a different one)
124
+ const instance = await this.discoveryClient.getInstance(this.config.serviceName, this.config.loadBalancingStrategy, this.config.filter);
125
+ if (!instance) {
126
+ throw new Error(`No instances available for service: ${this.config.serviceName}`);
127
+ }
128
+ // Track active connections for least-connections strategy
129
+ const lbStrategy = this.discoveryClient
130
+ .getLoadBalancerFactory()
131
+ .get(this.config.loadBalancingStrategy || 'round-robin');
132
+ const isLeastConn = lbStrategy instanceof strategies_1.LeastConnectionsStrategy;
133
+ if (isLeastConn) {
134
+ lbStrategy.incrementConnections(instance.id);
135
+ }
58
136
  try {
59
- // Discover service instance
60
- const instance = await this.discoveryClient.getInstance(this.config.serviceName, this.config.loadBalancingStrategy, this.config.filter);
61
- if (!instance) {
62
- throw new Error(`No instances available for service: ${this.config.serviceName}`);
63
- }
64
137
  // Build full URL
65
138
  const baseURL = `${instance.protocol}://${instance.host}:${instance.port}`;
66
139
  const fullConfig = {
@@ -68,28 +141,15 @@ class ServiceClient {
68
141
  baseURL,
69
142
  };
70
143
  // Make request
71
- return await this.axiosInstance.request(fullConfig);
144
+ const response = await this.axiosInstance.request(fullConfig);
145
+ return response;
72
146
  }
73
- catch (error) {
74
- lastError = error;
75
- // Log error only in development
76
- if (process.env.NODE_ENV === 'development') {
77
- // eslint-disable-next-line no-console
78
- console.error(`Request failed (attempt ${attempt + 1}/${this.retries}):`, error);
79
- }
80
- // Wait before retry
81
- if (attempt < this.retries - 1) {
82
- await this.sleep(this.retryDelay);
147
+ finally {
148
+ if (isLeastConn) {
149
+ lbStrategy.decrementConnections(instance.id);
83
150
  }
84
151
  }
85
- }
86
- throw lastError || new Error('Request failed after all retries');
87
- }
88
- /**
89
- * Sleep utility
90
- */
91
- sleep(ms) {
92
- return new Promise((resolve) => setTimeout(resolve, ms));
152
+ });
93
153
  }
94
154
  }
95
155
  exports.ServiceClient = ServiceClient;
package/dist/index.d.ts CHANGED
@@ -10,9 +10,12 @@ export * from './load-balancer/strategies';
10
10
  export { RegistryBackend } from './backends/registry-backend';
11
11
  export { MemoryRegistryBackend } from './backends/memory-backend';
12
12
  export { RedisRegistryBackend, RedisBackendConfig } from './backends/redis-backend';
13
- export { ConsulRegistryBackend, ConsulBackendConfig } from './backends/consul-backend';
13
+ export { ConsulRegistryBackend, ConsulBackendConfig, ConsulClient, } from './backends/consul-backend';
14
14
  export { KubernetesRegistryBackend, KubernetesBackendConfig } from './backends/kubernetes-backend';
15
15
  export { ServiceRegistry as ServiceRegistryDecorator } from './decorators/service-registry.decorator';
16
16
  export { getServiceRegistryMetadata } from './decorators/service-registry.decorator';
17
17
  export * from './decorators/inject-service-client.decorator';
18
+ export { applyServiceFilter } from './utils/filter';
19
+ export { DiscoveryLogger, Logger } from './utils/logger';
20
+ export { ConfigValidationError } from './utils/validation';
18
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,SAAS,CAAC;AAGxB,cAAc,6BAA6B,CAAC;AAG5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AAGxC,cAAc,4BAA4B,CAAC;AAG3C,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACvF,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAGnG,OAAO,EAAE,eAAe,IAAI,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AACtG,OAAO,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAC;AACrF,cAAc,8CAA8C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,SAAS,CAAC;AAGxB,cAAc,6BAA6B,CAAC;AAG5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AAGxC,cAAc,4BAA4B,CAAC;AAG3C,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACpF,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,YAAY,GACb,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAGnG,OAAO,EAAE,eAAe,IAAI,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AACtG,OAAO,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAC;AACrF,cAAc,8CAA8C,CAAC;AAG7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
18
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.getServiceRegistryMetadata = exports.ServiceRegistryDecorator = exports.KubernetesRegistryBackend = exports.ConsulRegistryBackend = exports.RedisRegistryBackend = exports.MemoryRegistryBackend = void 0;
21
+ exports.ConfigValidationError = exports.DiscoveryLogger = exports.applyServiceFilter = exports.getServiceRegistryMetadata = exports.ServiceRegistryDecorator = exports.KubernetesRegistryBackend = exports.ConsulRegistryBackend = exports.RedisRegistryBackend = exports.MemoryRegistryBackend = void 0;
22
22
  // Types
23
23
  __exportStar(require("./types"), exports);
24
24
  // Registry
@@ -42,3 +42,10 @@ Object.defineProperty(exports, "ServiceRegistryDecorator", { enumerable: true, g
42
42
  var service_registry_decorator_2 = require("./decorators/service-registry.decorator");
43
43
  Object.defineProperty(exports, "getServiceRegistryMetadata", { enumerable: true, get: function () { return service_registry_decorator_2.getServiceRegistryMetadata; } });
44
44
  __exportStar(require("./decorators/inject-service-client.decorator"), exports);
45
+ // Utilities
46
+ var filter_1 = require("./utils/filter");
47
+ Object.defineProperty(exports, "applyServiceFilter", { enumerable: true, get: function () { return filter_1.applyServiceFilter; } });
48
+ var logger_1 = require("./utils/logger");
49
+ Object.defineProperty(exports, "DiscoveryLogger", { enumerable: true, get: function () { return logger_1.DiscoveryLogger; } });
50
+ var validation_1 = require("./utils/validation");
51
+ Object.defineProperty(exports, "ConfigValidationError", { enumerable: true, get: function () { return validation_1.ConfigValidationError; } });
@@ -1 +1 @@
1
- {"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../../src/registry/service-registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAiB,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAI/D,qBAAa,eAAe;IAOxB,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,eAAe,CAA+B;gBAG5C,MAAM,EAAE,qBAAqB,EACrC,OAAO,CAAC,EAAE,eAAe;IAK3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBjC;;OAEG;IACH,WAAW,IAAI,eAAe,GAAG,IAAI;IAIrC;;OAEG;IACH,UAAU,IAAI,eAAe;IAI7B;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;YACW,kBAAkB;IAoBhC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;OAEG;IACH,OAAO,CAAC,UAAU;CAgBnB"}
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"}
@@ -10,6 +10,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.ServiceRegistry = void 0;
11
11
  const types_1 = require("../types");
12
12
  const memory_backend_1 = require("../backends/memory-backend");
13
+ const logger_1 = require("../utils/logger");
14
+ const validation_1 = require("../utils/validation");
13
15
  const axios_1 = __importDefault(require("axios"));
14
16
  class ServiceRegistry {
15
17
  constructor(config, backend) {
@@ -17,6 +19,7 @@ class ServiceRegistry {
17
19
  this.instance = null;
18
20
  this.heartbeatInterval = null;
19
21
  this.cleanupInterval = null;
22
+ (0, validation_1.validateServiceRegistryConfig)(config);
20
23
  this.backend = backend || new memory_backend_1.MemoryRegistryBackend();
21
24
  }
22
25
  /**
@@ -95,7 +98,13 @@ class ServiceRegistry {
95
98
  */
96
99
  startCleanup() {
97
100
  this.cleanupInterval = setInterval(async () => {
98
- await this.backend.cleanup();
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
+ }
99
108
  }, 60000); // Run every minute
100
109
  }
101
110
  /**
@@ -104,6 +113,7 @@ class ServiceRegistry {
104
113
  async performHealthCheck() {
105
114
  if (!this.instance)
106
115
  return;
116
+ const logger = logger_1.DiscoveryLogger.getLogger();
107
117
  try {
108
118
  const url = `${this.instance.protocol}://${this.instance.host}:${this.instance.port}${this.instance.healthCheckPath}`;
109
119
  const response = await axios_1.default.get(url, { timeout: 5000 });
@@ -116,9 +126,10 @@ class ServiceRegistry {
116
126
  await this.backend.updateStatus(this.instance.id, types_1.ServiceStatus.DOWN);
117
127
  }
118
128
  }
119
- catch {
129
+ catch (error) {
120
130
  this.instance.status = types_1.ServiceStatus.DOWN;
121
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);
122
133
  }
123
134
  }
124
135
  /**
@@ -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"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Pluggable logger for @hazeljs/discovery
4
+ *
5
+ * Consumers can supply their own logger via DiscoveryLogger.setLogger().
6
+ * The default logger writes to the console.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DiscoveryLogger = void 0;
10
+ const defaultLogger = {
11
+ // eslint-disable-next-line no-console
12
+ debug: (msg, ...args) => console.debug(`[discovery] ${msg}`, ...args),
13
+ // eslint-disable-next-line no-console
14
+ info: (msg, ...args) => console.info(`[discovery] ${msg}`, ...args),
15
+ // eslint-disable-next-line no-console
16
+ warn: (msg, ...args) => console.warn(`[discovery] ${msg}`, ...args),
17
+ // eslint-disable-next-line no-console
18
+ error: (msg, ...args) => console.error(`[discovery] ${msg}`, ...args),
19
+ };
20
+ let currentLogger = defaultLogger;
21
+ exports.DiscoveryLogger = {
22
+ /** Replace the default console logger with a custom implementation */
23
+ setLogger(logger) {
24
+ currentLogger = logger;
25
+ },
26
+ /** Reset to the default console logger */
27
+ resetLogger() {
28
+ currentLogger = defaultLogger;
29
+ },
30
+ /** Get the current logger instance */
31
+ getLogger() {
32
+ return currentLogger;
33
+ },
34
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Runtime validation utilities for discovery configuration objects
3
+ */
4
+ import { ServiceRegistryConfig, DiscoveryClientConfig } from '../types';
5
+ import { RedisBackendConfig } from '../backends/redis-backend';
6
+ import { ConsulBackendConfig } from '../backends/consul-backend';
7
+ import { KubernetesBackendConfig } from '../backends/kubernetes-backend';
8
+ import { ServiceClientConfig } from '../client/service-client';
9
+ export declare class ConfigValidationError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ /**
13
+ * Validate ServiceRegistryConfig at runtime
14
+ */
15
+ export declare function validateServiceRegistryConfig(config: ServiceRegistryConfig): void;
16
+ /**
17
+ * Validate DiscoveryClientConfig at runtime
18
+ */
19
+ export declare function validateDiscoveryClientConfig(config: DiscoveryClientConfig): void;
20
+ /**
21
+ * Validate ServiceClientConfig at runtime
22
+ */
23
+ export declare function validateServiceClientConfig(config: ServiceClientConfig): void;
24
+ /**
25
+ * Validate RedisBackendConfig at runtime
26
+ */
27
+ export declare function validateRedisBackendConfig(config: RedisBackendConfig): void;
28
+ /**
29
+ * Validate ConsulBackendConfig at runtime
30
+ */
31
+ export declare function validateConsulBackendConfig(config: ConsulBackendConfig): void;
32
+ /**
33
+ * Validate KubernetesBackendConfig at runtime
34
+ */
35
+ export declare function validateKubernetesBackendConfig(config: KubernetesBackendConfig): void;
36
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAgCjF;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAejF;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAkC7E;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAe3E;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAkB7E;AAED;;GAEG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,CASrF"}
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * Runtime validation utilities for discovery configuration objects
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConfigValidationError = void 0;
7
+ exports.validateServiceRegistryConfig = validateServiceRegistryConfig;
8
+ exports.validateDiscoveryClientConfig = validateDiscoveryClientConfig;
9
+ exports.validateServiceClientConfig = validateServiceClientConfig;
10
+ exports.validateRedisBackendConfig = validateRedisBackendConfig;
11
+ exports.validateConsulBackendConfig = validateConsulBackendConfig;
12
+ exports.validateKubernetesBackendConfig = validateKubernetesBackendConfig;
13
+ class ConfigValidationError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = 'ConfigValidationError';
17
+ }
18
+ }
19
+ exports.ConfigValidationError = ConfigValidationError;
20
+ /**
21
+ * Validate ServiceRegistryConfig at runtime
22
+ */
23
+ function validateServiceRegistryConfig(config) {
24
+ if (!config.name || typeof config.name !== 'string' || config.name.trim().length === 0) {
25
+ throw new ConfigValidationError('ServiceRegistryConfig: "name" is required and must be a non-empty string');
26
+ }
27
+ if (config.port == null ||
28
+ typeof config.port !== 'number' ||
29
+ config.port < 0 ||
30
+ config.port > 65535) {
31
+ throw new ConfigValidationError('ServiceRegistryConfig: "port" is required and must be a number between 0 and 65535');
32
+ }
33
+ if (config.healthCheckInterval != null &&
34
+ (typeof config.healthCheckInterval !== 'number' || config.healthCheckInterval <= 0)) {
35
+ throw new ConfigValidationError('ServiceRegistryConfig: "healthCheckInterval" must be a positive number (ms)');
36
+ }
37
+ if (config.protocol && !['http', 'https', 'grpc'].includes(config.protocol)) {
38
+ throw new ConfigValidationError('ServiceRegistryConfig: "protocol" must be one of "http", "https", "grpc"');
39
+ }
40
+ }
41
+ /**
42
+ * Validate DiscoveryClientConfig at runtime
43
+ */
44
+ function validateDiscoveryClientConfig(config) {
45
+ if (config.cacheTTL != null && (typeof config.cacheTTL !== 'number' || config.cacheTTL <= 0)) {
46
+ throw new ConfigValidationError('DiscoveryClientConfig: "cacheTTL" must be a positive number (ms)');
47
+ }
48
+ if (config.refreshInterval != null &&
49
+ (typeof config.refreshInterval !== 'number' || config.refreshInterval <= 0)) {
50
+ throw new ConfigValidationError('DiscoveryClientConfig: "refreshInterval" must be a positive number (ms)');
51
+ }
52
+ }
53
+ /**
54
+ * Validate ServiceClientConfig at runtime
55
+ */
56
+ function validateServiceClientConfig(config) {
57
+ if (!config.serviceName ||
58
+ typeof config.serviceName !== 'string' ||
59
+ config.serviceName.trim().length === 0) {
60
+ throw new ConfigValidationError('ServiceClientConfig: "serviceName" is required and must be a non-empty string');
61
+ }
62
+ if (config.timeout != null && (typeof config.timeout !== 'number' || config.timeout <= 0)) {
63
+ throw new ConfigValidationError('ServiceClientConfig: "timeout" must be a positive number (ms)');
64
+ }
65
+ if (config.retries != null &&
66
+ (typeof config.retries !== 'number' || config.retries < 0 || !Number.isInteger(config.retries))) {
67
+ throw new ConfigValidationError('ServiceClientConfig: "retries" must be a non-negative integer');
68
+ }
69
+ if (config.retryDelay != null &&
70
+ (typeof config.retryDelay !== 'number' || config.retryDelay < 0)) {
71
+ throw new ConfigValidationError('ServiceClientConfig: "retryDelay" must be a non-negative number (ms)');
72
+ }
73
+ }
74
+ /**
75
+ * Validate RedisBackendConfig at runtime
76
+ */
77
+ function validateRedisBackendConfig(config) {
78
+ if (config.ttl != null && (typeof config.ttl !== 'number' || config.ttl <= 0)) {
79
+ throw new ConfigValidationError('RedisBackendConfig: "ttl" must be a positive number (seconds)');
80
+ }
81
+ if (config.port != null &&
82
+ (typeof config.port !== 'number' || config.port < 0 || config.port > 65535)) {
83
+ throw new ConfigValidationError('RedisBackendConfig: "port" must be a number between 0 and 65535');
84
+ }
85
+ }
86
+ /**
87
+ * Validate ConsulBackendConfig at runtime
88
+ */
89
+ function validateConsulBackendConfig(config) {
90
+ if (config.ttl != null && typeof config.ttl === 'string') {
91
+ const match = config.ttl.match(/^(\d+)([smh])$/);
92
+ if (!match) {
93
+ throw new ConfigValidationError('ConsulBackendConfig: "ttl" must match format like "30s", "5m", or "1h"');
94
+ }
95
+ }
96
+ if (config.port != null &&
97
+ (typeof config.port !== 'number' || config.port < 0 || config.port > 65535)) {
98
+ throw new ConfigValidationError('ConsulBackendConfig: "port" must be a number between 0 and 65535');
99
+ }
100
+ }
101
+ /**
102
+ * Validate KubernetesBackendConfig at runtime
103
+ */
104
+ function validateKubernetesBackendConfig(config) {
105
+ if (config.namespace != null &&
106
+ (typeof config.namespace !== 'string' || config.namespace.trim().length === 0)) {
107
+ throw new ConfigValidationError('KubernetesBackendConfig: "namespace" must be a non-empty string');
108
+ }
109
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazeljs/discovery",
3
- "version": "0.2.0-beta.8",
3
+ "version": "0.2.0-beta.81",
4
4
  "description": "Service discovery and registry for HazelJS microservices - Eureka-inspired with multiple backend support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,8 @@
18
18
  "clean": "rm -rf dist"
19
19
  },
20
20
  "dependencies": {
21
- "@hazeljs/core": "^0.2.0-beta.8",
21
+ "@hazeljs/core": "^0.2.0-beta.81",
22
+ "@hazeljs/resilience": "^0.2.0-beta.81",
22
23
  "axios": "^1.6.0",
23
24
  "reflect-metadata": "^0.2.2"
24
25
  },
@@ -36,6 +37,7 @@
36
37
  "typescript": "^5.3.3"
37
38
  },
38
39
  "peerDependencies": {
40
+ "@hazeljs/core": ">=0.2.0-beta.0",
39
41
  "@kubernetes/client-node": "^0.20.0",
40
42
  "consul": "^1.2.0",
41
43
  "ioredis": "^5.3.0"
@@ -69,11 +71,11 @@
69
71
  "consul",
70
72
  "kubernetes"
71
73
  ],
72
- "author": "Muhammad Arslan <marslan@hazeljs.com>",
73
- "license": "MIT",
74
+ "author": "Muhammad Arslan <muhammad.arslan@hazeljs.com>",
75
+ "license": "Apache-2.0",
74
76
  "bugs": {
75
77
  "url": "https://github.com/hazeljs/hazel-js/issues"
76
78
  },
77
79
  "homepage": "https://hazeljs.com",
78
- "gitHead": "b5cc0bf7d43739cad25220a611490b1d175acd62"
80
+ "gitHead": "1054f957451360f973469d436a1d58e57cc9231a"
79
81
  }