@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,39 @@
1
+ /**
2
+ * Service Registry Backend Interface
3
+ */
4
+ import { ServiceInstance, ServiceFilter } from '../types';
5
+ export interface RegistryBackend {
6
+ /**
7
+ * Register a service instance
8
+ */
9
+ register(instance: ServiceInstance): Promise<void>;
10
+ /**
11
+ * Deregister a service instance
12
+ */
13
+ deregister(instanceId: string): Promise<void>;
14
+ /**
15
+ * Update service instance heartbeat
16
+ */
17
+ heartbeat(instanceId: string): Promise<void>;
18
+ /**
19
+ * Get all instances of a service
20
+ */
21
+ getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
22
+ /**
23
+ * Get a specific service instance
24
+ */
25
+ getInstance(instanceId: string): Promise<ServiceInstance | null>;
26
+ /**
27
+ * Get all registered services
28
+ */
29
+ getAllServices(): Promise<string[]>;
30
+ /**
31
+ * Update service instance status
32
+ */
33
+ updateStatus(instanceId: string, status: string): Promise<void>;
34
+ /**
35
+ * Clean up expired instances
36
+ */
37
+ cleanup(): Promise<void>;
38
+ }
39
+ //# sourceMappingURL=registry-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-backend.d.ts","sourceRoot":"","sources":["../../src/backends/registry-backend.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;;OAEG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7C;;OAEG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAEtF;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAEjE;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEpC;;OAEG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhE;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Service Registry Backend Interface
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Discovery Client
3
+ * Discovers and retrieves service instances
4
+ */
5
+ import { ServiceInstance, DiscoveryClientConfig, ServiceFilter } from '../types';
6
+ import { RegistryBackend } from '../backends/registry-backend';
7
+ import { LoadBalancerFactory } from '../load-balancer/strategies';
8
+ export declare class DiscoveryClient {
9
+ private config;
10
+ private backend;
11
+ private cache;
12
+ private loadBalancerFactory;
13
+ private refreshIntervalHandle;
14
+ constructor(config?: DiscoveryClientConfig, backend?: RegistryBackend);
15
+ /**
16
+ * Get all instances of a service
17
+ */
18
+ getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
19
+ /**
20
+ * Get a single instance using load balancing
21
+ */
22
+ getInstance(serviceName: string, strategy?: string, filter?: ServiceFilter): Promise<ServiceInstance | null>;
23
+ /**
24
+ * Get all registered service names
25
+ */
26
+ getAllServices(): Promise<string[]>;
27
+ /**
28
+ * Clear cache for a specific service or all services
29
+ */
30
+ clearCache(serviceName?: string): void;
31
+ /**
32
+ * Get the backend
33
+ */
34
+ getBackend(): RegistryBackend;
35
+ /**
36
+ * Get load balancer factory
37
+ */
38
+ getLoadBalancerFactory(): LoadBalancerFactory;
39
+ /**
40
+ * Close the discovery client and release all resources.
41
+ * Stops the cache refresh interval and clears the cache.
42
+ */
43
+ close(): void;
44
+ /**
45
+ * Start cache refresh interval
46
+ */
47
+ private startRefreshInterval;
48
+ }
49
+ //# sourceMappingURL=discovery-client.d.ts.map
@@ -0,0 +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;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"}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ /**
3
+ * Discovery Client
4
+ * Discovers and retrieves service instances
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.DiscoveryClient = void 0;
8
+ const memory_backend_1 = require("../backends/memory-backend");
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");
13
+ class DiscoveryClient {
14
+ constructor(config = {}, backend) {
15
+ this.config = config;
16
+ this.cache = new Map();
17
+ this.refreshIntervalHandle = null;
18
+ (0, validation_1.validateDiscoveryClientConfig)(config);
19
+ this.backend = backend || new memory_backend_1.MemoryRegistryBackend();
20
+ this.loadBalancerFactory = new strategies_1.LoadBalancerFactory();
21
+ if (config.refreshInterval) {
22
+ this.startRefreshInterval();
23
+ }
24
+ }
25
+ /**
26
+ * Get all instances of a service
27
+ */
28
+ async getInstances(serviceName, filter) {
29
+ // Check cache first
30
+ if (this.config.cacheEnabled) {
31
+ const cached = this.cache.get(serviceName);
32
+ if (cached) {
33
+ const age = Date.now() - cached.timestamp;
34
+ const ttl = this.config.cacheTTL || 30000;
35
+ if (age < ttl) {
36
+ return (0, filter_1.applyServiceFilter)(cached.instances, filter);
37
+ }
38
+ }
39
+ }
40
+ // Fetch from backend
41
+ const instances = await this.backend.getInstances(serviceName, filter);
42
+ // Update cache
43
+ if (this.config.cacheEnabled) {
44
+ this.cache.set(serviceName, {
45
+ instances,
46
+ timestamp: Date.now(),
47
+ });
48
+ }
49
+ return instances;
50
+ }
51
+ /**
52
+ * Get a single instance using load balancing
53
+ */
54
+ async getInstance(serviceName, strategy = 'round-robin', filter) {
55
+ const instances = await this.getInstances(serviceName, filter);
56
+ if (instances.length === 0)
57
+ return null;
58
+ const loadBalancer = this.loadBalancerFactory.create(strategy);
59
+ return loadBalancer.choose(instances);
60
+ }
61
+ /**
62
+ * Get all registered service names
63
+ */
64
+ async getAllServices() {
65
+ return this.backend.getAllServices();
66
+ }
67
+ /**
68
+ * Clear cache for a specific service or all services
69
+ */
70
+ clearCache(serviceName) {
71
+ if (serviceName) {
72
+ this.cache.delete(serviceName);
73
+ }
74
+ else {
75
+ this.cache.clear();
76
+ }
77
+ }
78
+ /**
79
+ * Get the backend
80
+ */
81
+ getBackend() {
82
+ return this.backend;
83
+ }
84
+ /**
85
+ * Get load balancer factory
86
+ */
87
+ getLoadBalancerFactory() {
88
+ return this.loadBalancerFactory;
89
+ }
90
+ /**
91
+ * Close the discovery client and release all resources.
92
+ * Stops the cache refresh interval and clears the cache.
93
+ */
94
+ close() {
95
+ if (this.refreshIntervalHandle) {
96
+ clearInterval(this.refreshIntervalHandle);
97
+ this.refreshIntervalHandle = null;
98
+ }
99
+ this.cache.clear();
100
+ }
101
+ /**
102
+ * Start cache refresh interval
103
+ */
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
+ });
115
+ }
116
+ }
117
+ catch (error) {
118
+ logger.error('Failed to refresh service cache', error);
119
+ }
120
+ }, this.config.refreshInterval);
121
+ }
122
+ }
123
+ exports.DiscoveryClient = DiscoveryClient;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Service Client
3
+ * HTTP client with automatic service discovery and load balancing.
4
+ * Uses @hazeljs/resilience RetryPolicy for retry logic.
5
+ */
6
+ import { DiscoveryClient } from './discovery-client';
7
+ import { ServiceFilter } from '../types';
8
+ import { AxiosRequestConfig, AxiosResponse } from 'axios';
9
+ export interface ServiceClientConfig {
10
+ serviceName: string;
11
+ loadBalancingStrategy?: string;
12
+ filter?: ServiceFilter;
13
+ timeout?: number;
14
+ retries?: number;
15
+ retryDelay?: number;
16
+ }
17
+ export declare class ServiceClient {
18
+ private discoveryClient;
19
+ private config;
20
+ private axiosInstance;
21
+ private retryPolicy;
22
+ constructor(discoveryClient: DiscoveryClient, config: ServiceClientConfig);
23
+ /**
24
+ * GET request
25
+ */
26
+ get<T = unknown>(path: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
27
+ /**
28
+ * POST request
29
+ */
30
+ post<T = unknown>(path: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
31
+ /**
32
+ * PUT request
33
+ */
34
+ put<T = unknown>(path: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
35
+ /**
36
+ * DELETE request
37
+ */
38
+ delete<T = unknown>(path: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
39
+ /**
40
+ * PATCH request
41
+ */
42
+ patch<T = unknown>(path: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
43
+ /**
44
+ * Generic request with service discovery and resilience-backed retry
45
+ */
46
+ private request;
47
+ }
48
+ //# sourceMappingURL=service-client.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ /**
3
+ * Service Client
4
+ * HTTP client with automatic service discovery and load balancing.
5
+ * Uses @hazeljs/resilience RetryPolicy for retry logic.
6
+ */
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
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.ServiceClient = void 0;
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
+ }
67
+ class ServiceClient {
68
+ constructor(discoveryClient, config) {
69
+ this.discoveryClient = discoveryClient;
70
+ this.config = config;
71
+ (0, validation_1.validateServiceClientConfig)(config);
72
+ this.axiosInstance = axios_1.default.create({
73
+ timeout: config.timeout || 5000,
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
+ });
87
+ }
88
+ /**
89
+ * GET request
90
+ */
91
+ async get(path, config) {
92
+ return this.request({ ...config, method: 'GET', url: path });
93
+ }
94
+ /**
95
+ * POST request
96
+ */
97
+ async post(path, data, config) {
98
+ return this.request({ ...config, method: 'POST', url: path, data });
99
+ }
100
+ /**
101
+ * PUT request
102
+ */
103
+ async put(path, data, config) {
104
+ return this.request({ ...config, method: 'PUT', url: path, data });
105
+ }
106
+ /**
107
+ * DELETE request
108
+ */
109
+ async delete(path, config) {
110
+ return this.request({ ...config, method: 'DELETE', url: path });
111
+ }
112
+ /**
113
+ * PATCH request
114
+ */
115
+ async patch(path, data, config) {
116
+ return this.request({ ...config, method: 'PATCH', url: path, data });
117
+ }
118
+ /**
119
+ * Generic request with service discovery and resilience-backed retry
120
+ */
121
+ async request(config) {
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
+ }
136
+ try {
137
+ // Build full URL
138
+ const baseURL = `${instance.protocol}://${instance.host}:${instance.port}`;
139
+ const fullConfig = {
140
+ ...config,
141
+ baseURL,
142
+ };
143
+ // Make request
144
+ const response = await this.axiosInstance.request(fullConfig);
145
+ return response;
146
+ }
147
+ finally {
148
+ if (isLeastConn) {
149
+ lbStrategy.decrementConnections(instance.id);
150
+ }
151
+ }
152
+ });
153
+ }
154
+ }
155
+ exports.ServiceClient = ServiceClient;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * InjectServiceClient Decorator
3
+ * Injects a service client for a specific service
4
+ */
5
+ import 'reflect-metadata';
6
+ type NewableFunction = new (...args: unknown[]) => unknown;
7
+ export interface InjectServiceClientOptions {
8
+ serviceName: string;
9
+ loadBalancingStrategy?: string;
10
+ timeout?: number;
11
+ retries?: number;
12
+ }
13
+ export declare function InjectServiceClient(serviceName: string, options?: Omit<InjectServiceClientOptions, 'serviceName'>): ParameterDecorator;
14
+ export declare function getInjectServiceClientMetadata(target: NewableFunction): InjectServiceClientOptions[] | undefined;
15
+ export {};
16
+ //# sourceMappingURL=inject-service-client.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject-service-client.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/inject-service-client.decorator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,kBAAkB,CAAC;AAE1B,KAAK,eAAe,GAAG,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;AAI3D,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,IAAI,CAAC,0BAA0B,EAAE,aAAa,CAAC,GACxD,kBAAkB,CAkBpB;AAED,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,eAAe,GACtB,0BAA0B,EAAE,GAAG,SAAS,CAE1C"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /**
3
+ * InjectServiceClient Decorator
4
+ * Injects a service client for a specific service
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.InjectServiceClient = InjectServiceClient;
8
+ exports.getInjectServiceClientMetadata = getInjectServiceClientMetadata;
9
+ require("reflect-metadata");
10
+ const INJECT_SERVICE_CLIENT_METADATA = 'hazeljs:inject-service-client';
11
+ function InjectServiceClient(serviceName, options) {
12
+ return function (target, propertyKey, parameterIndex) {
13
+ const config = {
14
+ serviceName,
15
+ ...options,
16
+ };
17
+ const existingParams = Reflect.getMetadata(INJECT_SERVICE_CLIENT_METADATA, target) || [];
18
+ existingParams[parameterIndex] = config;
19
+ Reflect.defineMetadata(INJECT_SERVICE_CLIENT_METADATA, existingParams, target);
20
+ };
21
+ }
22
+ function getInjectServiceClientMetadata(target) {
23
+ return Reflect.getMetadata(INJECT_SERVICE_CLIENT_METADATA, target);
24
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Service Registry Decorator
3
+ * Automatically registers a service on module initialization
4
+ */
5
+ import 'reflect-metadata';
6
+ import { ServiceRegistryConfig } from '../types';
7
+ type NewableFunction = new (...args: unknown[]) => unknown;
8
+ export declare function ServiceRegistry(config: ServiceRegistryConfig): ClassDecorator;
9
+ export declare function getServiceRegistryMetadata(target: NewableFunction): ServiceRegistryConfig | undefined;
10
+ export {};
11
+ //# sourceMappingURL=service-registry.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-registry.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/service-registry.decorator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEjD,KAAK,eAAe,GAAG,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;AAI3D,wBAAgB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,cAAc,CAM7E;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,eAAe,GACtB,qBAAqB,GAAG,SAAS,CAEnC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * Service Registry Decorator
4
+ * Automatically registers a service on module initialization
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ServiceRegistry = ServiceRegistry;
8
+ exports.getServiceRegistryMetadata = getServiceRegistryMetadata;
9
+ require("reflect-metadata");
10
+ const SERVICE_REGISTRY_METADATA = 'hazeljs:service-registry';
11
+ function ServiceRegistry(config) {
12
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
13
+ return function (target) {
14
+ Reflect.defineMetadata(SERVICE_REGISTRY_METADATA, config, target);
15
+ return target;
16
+ };
17
+ }
18
+ function getServiceRegistryMetadata(target) {
19
+ return Reflect.getMetadata(SERVICE_REGISTRY_METADATA, target);
20
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @hazeljs/discovery
3
+ * Service Discovery and Registry for HazelJS Microservices
4
+ */
5
+ export * from './types';
6
+ export * from './registry/service-registry';
7
+ export * from './client/discovery-client';
8
+ export * from './client/service-client';
9
+ export * from './load-balancer/strategies';
10
+ export { RegistryBackend } from './backends/registry-backend';
11
+ export { MemoryRegistryBackend } from './backends/memory-backend';
12
+ export { RedisRegistryBackend, RedisBackendConfig } from './backends/redis-backend';
13
+ export { ConsulRegistryBackend, ConsulBackendConfig, ConsulClient, } from './backends/consul-backend';
14
+ export { KubernetesRegistryBackend, KubernetesBackendConfig } from './backends/kubernetes-backend';
15
+ export { ServiceRegistry as ServiceRegistryDecorator } from './decorators/service-registry.decorator';
16
+ export { getServiceRegistryMetadata } from './decorators/service-registry.decorator';
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';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,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 ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * @hazeljs/discovery
4
+ * Service Discovery and Registry for HazelJS Microservices
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.ConfigValidationError = exports.DiscoveryLogger = exports.applyServiceFilter = exports.getServiceRegistryMetadata = exports.ServiceRegistryDecorator = exports.KubernetesRegistryBackend = exports.ConsulRegistryBackend = exports.RedisRegistryBackend = exports.MemoryRegistryBackend = void 0;
22
+ // Types
23
+ __exportStar(require("./types"), exports);
24
+ // Registry
25
+ __exportStar(require("./registry/service-registry"), exports);
26
+ // Client
27
+ __exportStar(require("./client/discovery-client"), exports);
28
+ __exportStar(require("./client/service-client"), exports);
29
+ // Load Balancer
30
+ __exportStar(require("./load-balancer/strategies"), exports);
31
+ var memory_backend_1 = require("./backends/memory-backend");
32
+ Object.defineProperty(exports, "MemoryRegistryBackend", { enumerable: true, get: function () { return memory_backend_1.MemoryRegistryBackend; } });
33
+ var redis_backend_1 = require("./backends/redis-backend");
34
+ Object.defineProperty(exports, "RedisRegistryBackend", { enumerable: true, get: function () { return redis_backend_1.RedisRegistryBackend; } });
35
+ var consul_backend_1 = require("./backends/consul-backend");
36
+ Object.defineProperty(exports, "ConsulRegistryBackend", { enumerable: true, get: function () { return consul_backend_1.ConsulRegistryBackend; } });
37
+ var kubernetes_backend_1 = require("./backends/kubernetes-backend");
38
+ Object.defineProperty(exports, "KubernetesRegistryBackend", { enumerable: true, get: function () { return kubernetes_backend_1.KubernetesRegistryBackend; } });
39
+ // Decorators
40
+ var service_registry_decorator_1 = require("./decorators/service-registry.decorator");
41
+ Object.defineProperty(exports, "ServiceRegistryDecorator", { enumerable: true, get: function () { return service_registry_decorator_1.ServiceRegistry; } });
42
+ var service_registry_decorator_2 = require("./decorators/service-registry.decorator");
43
+ Object.defineProperty(exports, "getServiceRegistryMetadata", { enumerable: true, get: function () { return service_registry_decorator_2.getServiceRegistryMetadata; } });
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; } });