@hazeljs/discovery 0.2.0-beta.39 → 0.2.0-beta.42

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.
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  Service Discovery and Registry for HazelJS microservices - inspired by Netflix Eureka and Consul.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@hazeljs/discovery.svg)](https://www.npmjs.com/package/@hazeljs/discovery)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@hazeljs/discovery)](https://www.npmjs.com/package/@hazeljs/discovery)
6
7
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
7
8
 
8
9
  ## Features
@@ -139,7 +139,8 @@ describe('ServiceClient', () => {
139
139
  const mockRequest = jest.fn().mockRejectedValue(new Error('Network error'));
140
140
  serviceClient.axiosInstance = { request: mockRequest };
141
141
  await expect(serviceClient.get('/api/users')).rejects.toThrow();
142
- expect(mockRequest).toHaveBeenCalledTimes(3); // Default retries
142
+ // retries: 3 RetryPolicy maxAttempts: 3 → 1 initial + 3 retries = 4 total
143
+ expect(mockRequest).toHaveBeenCalledTimes(4);
143
144
  });
144
145
  it('should respect retry delay', async () => {
145
146
  const client = new service_client_1.ServiceClient(discoveryClient, {
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Service Client
3
- * HTTP client with automatic service discovery and load balancing
3
+ * HTTP client with automatic service discovery and load balancing.
4
+ * Uses @hazeljs/resilience RetryPolicy for retry logic.
4
5
  */
5
6
  import { DiscoveryClient } from './discovery-client';
6
7
  import { ServiceFilter } from '../types';
@@ -17,8 +18,7 @@ export declare class ServiceClient {
17
18
  private discoveryClient;
18
19
  private config;
19
20
  private axiosInstance;
20
- private retries;
21
- private retryDelay;
21
+ private retryPolicy;
22
22
  constructor(discoveryClient: DiscoveryClient, config: ServiceClientConfig);
23
23
  /**
24
24
  * GET request
@@ -41,12 +41,8 @@ export declare class ServiceClient {
41
41
  */
42
42
  patch<T = unknown>(path: string, data?: unknown, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
43
43
  /**
44
- * Generic request with service discovery
44
+ * Generic request with service discovery and resilience-backed retry
45
45
  */
46
46
  private request;
47
- /**
48
- * Sleep utility
49
- */
50
- private sleep;
51
47
  }
52
48
  //# sourceMappingURL=service-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service-client.d.ts","sourceRoot":"","sources":["../../src/client/service-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,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;AAqBD,qBAAa,aAAa;IAMtB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;gBAGjB,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,mBAAmB;IAYrC;;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;IAgErB;;OAEG;IACH,OAAO,CAAC,KAAK;CAGd"}
1
+ {"version":3,"file":"service-client.d.ts","sourceRoot":"","sources":["../../src/client/service-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKzC,OAAc,EAAiB,kBAAkB,EAAE,aAAa,EAAc,MAAM,OAAO,CAAC;AAE5F,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyBD,qBAAa,aAAa;IAKtB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,WAAW,CAAc;gBAGvB,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,mBAAmB;IAyBrC;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5F;;OAEG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI/F;;OAEG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAI5B;;OAEG;YACW,OAAO;CAyCtB"}
@@ -1,7 +1,8 @@
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
7
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
8
  if (k2 === undefined) k2 = k;
@@ -41,6 +42,7 @@ exports.ServiceClient = void 0;
41
42
  const strategies_1 = require("../load-balancer/strategies");
42
43
  const logger_1 = require("../utils/logger");
43
44
  const validation_1 = require("../utils/validation");
45
+ const resilience_1 = require("@hazeljs/resilience");
44
46
  const axios_1 = __importStar(require("axios"));
45
47
  /** HTTP status codes that are safe to retry */
46
48
  const RETRYABLE_STATUS_CODES = new Set([502, 503, 504, 408, 429]);
@@ -55,7 +57,11 @@ function isRetryableError(error) {
55
57
  return true;
56
58
  return RETRYABLE_STATUS_CODES.has(error.response.status);
57
59
  }
58
- // Non-Axios errors (e.g. "no instances available") are retryable
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
59
65
  return true;
60
66
  }
61
67
  class ServiceClient {
@@ -63,11 +69,21 @@ class ServiceClient {
63
69
  this.discoveryClient = discoveryClient;
64
70
  this.config = config;
65
71
  (0, validation_1.validateServiceClientConfig)(config);
66
- this.retries = config.retries ?? 3;
67
- this.retryDelay = config.retryDelay ?? 1000;
68
72
  this.axiosInstance = axios_1.default.create({
69
73
  timeout: config.timeout || 5000,
70
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
+ });
71
87
  }
72
88
  /**
73
89
  * GET request
@@ -100,13 +116,11 @@ class ServiceClient {
100
116
  return this.request({ ...config, method: 'PATCH', url: path, data });
101
117
  }
102
118
  /**
103
- * Generic request with service discovery
119
+ * Generic request with service discovery and resilience-backed retry
104
120
  */
105
121
  async request(config) {
106
- const logger = logger_1.DiscoveryLogger.getLogger();
107
- let lastError = null;
108
- for (let attempt = 0; attempt < this.retries; attempt++) {
109
- // Discover service instance
122
+ return this.retryPolicy.execute(async () => {
123
+ // Discover service instance on each attempt (may pick a different one)
110
124
  const instance = await this.discoveryClient.getInstance(this.config.serviceName, this.config.loadBalancingStrategy, this.config.filter);
111
125
  if (!instance) {
112
126
  throw new Error(`No instances available for service: ${this.config.serviceName}`);
@@ -130,31 +144,12 @@ class ServiceClient {
130
144
  const response = await this.axiosInstance.request(fullConfig);
131
145
  return response;
132
146
  }
133
- catch (error) {
134
- lastError = error;
135
- logger.warn(`Request to ${this.config.serviceName} failed (attempt ${attempt + 1}/${this.retries})`, error);
136
- // Only retry on transient / network errors
137
- if (!isRetryableError(error)) {
138
- throw error;
139
- }
140
- // Wait before retry (skip delay on last attempt since we'll throw)
141
- if (attempt < this.retries - 1) {
142
- await this.sleep(this.retryDelay);
143
- }
144
- }
145
147
  finally {
146
148
  if (isLeastConn) {
147
149
  lbStrategy.decrementConnections(instance.id);
148
150
  }
149
151
  }
150
- }
151
- throw lastError || new Error('Request failed after all retries');
152
- }
153
- /**
154
- * Sleep utility
155
- */
156
- sleep(ms) {
157
- return new Promise((resolve) => setTimeout(resolve, ms));
152
+ });
158
153
  }
159
154
  }
160
155
  exports.ServiceClient = ServiceClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazeljs/discovery",
3
- "version": "0.2.0-beta.39",
3
+ "version": "0.2.0-beta.42",
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,6 +18,8 @@
18
18
  "clean": "rm -rf dist"
19
19
  },
20
20
  "dependencies": {
21
+ "@hazeljs/core": "^0.2.0-beta.42",
22
+ "@hazeljs/resilience": "^0.2.0-beta.42",
21
23
  "axios": "^1.6.0",
22
24
  "reflect-metadata": "^0.2.2"
23
25
  },
@@ -75,5 +77,5 @@
75
77
  "url": "https://github.com/hazeljs/hazel-js/issues"
76
78
  },
77
79
  "homepage": "https://hazeljs.com",
78
- "gitHead": "1ef15653fe25b11327020085fdfdd6d426b45763"
80
+ "gitHead": "00db05d3763e93f2caa5bc5a3540b5c81d8b3faf"
79
81
  }