@hazeljs/discovery 0.2.0-beta.8 → 0.2.0-beta.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -21
- package/README.md +190 -21
- package/dist/__tests__/consul-backend.test.d.ts +6 -0
- package/dist/__tests__/consul-backend.test.d.ts.map +1 -0
- package/dist/__tests__/consul-backend.test.js +300 -0
- package/dist/__tests__/kubernetes-backend.test.d.ts +6 -0
- package/dist/__tests__/kubernetes-backend.test.d.ts.map +1 -0
- package/dist/__tests__/kubernetes-backend.test.js +261 -0
- package/dist/__tests__/redis-backend.test.d.ts +6 -0
- package/dist/__tests__/redis-backend.test.d.ts.map +1 -0
- package/dist/__tests__/redis-backend.test.js +280 -0
- package/dist/__tests__/service-client.test.js +2 -1
- package/dist/backends/consul-backend.d.ts +46 -7
- package/dist/backends/consul-backend.d.ts.map +1 -1
- package/dist/backends/consul-backend.js +23 -39
- package/dist/backends/kubernetes-backend.d.ts +44 -6
- package/dist/backends/kubernetes-backend.d.ts.map +1 -1
- package/dist/backends/kubernetes-backend.js +11 -32
- package/dist/backends/memory-backend.d.ts +0 -1
- package/dist/backends/memory-backend.d.ts.map +1 -1
- package/dist/backends/memory-backend.js +3 -32
- package/dist/backends/redis-backend.d.ts +11 -6
- package/dist/backends/redis-backend.d.ts.map +1 -1
- package/dist/backends/redis-backend.js +66 -46
- package/dist/client/discovery-client.d.ts +6 -4
- package/dist/client/discovery-client.d.ts.map +1 -1
- package/dist/client/discovery-client.js +30 -30
- package/dist/client/service-client.d.ts +4 -8
- package/dist/client/service-client.d.ts.map +1 -1
- package/dist/client/service-client.js +94 -34
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/registry/service-registry.d.ts.map +1 -1
- package/dist/registry/service-registry.js +13 -2
- package/dist/utils/filter.d.ts +10 -0
- package/dist/utils/filter.d.ts.map +1 -0
- package/dist/utils/filter.js +39 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +34 -0
- package/dist/utils/validation.d.ts +36 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +109 -0
- 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
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
144
|
+
const response = await this.axiosInstance.request(fullConfig);
|
|
145
|
+
return response;
|
|
72
146
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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;
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.0-beta.80",
|
|
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.
|
|
21
|
+
"@hazeljs/core": "^0.2.0-beta.80",
|
|
22
|
+
"@hazeljs/resilience": "^0.2.0-beta.80",
|
|
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 <
|
|
73
|
-
"license": "
|
|
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": "
|
|
80
|
+
"gitHead": "083332d9cc6b634265382e7d1e0439236d30fedd"
|
|
79
81
|
}
|