@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,103 @@
1
+ /**
2
+ * Kubernetes Registry Backend
3
+ * Uses Kubernetes Service Discovery
4
+ */
5
+ import { RegistryBackend } from './registry-backend';
6
+ import { ServiceInstance, ServiceFilter } from '../types';
7
+ /**
8
+ * Minimal type definitions for the Kubernetes client API surface we use.
9
+ * These mirror the shapes exposed by `@kubernetes/client-node`.
10
+ */
11
+ export interface KubeConfig {
12
+ makeApiClient(apiClass: new () => any): any;
13
+ }
14
+ export interface K8sEndpointAddress {
15
+ ip: string;
16
+ targetRef?: {
17
+ name?: string;
18
+ };
19
+ nodeName?: string;
20
+ }
21
+ export interface K8sEndpointPort {
22
+ port: number;
23
+ }
24
+ export interface K8sEndpointSubset {
25
+ ports?: K8sEndpointPort[];
26
+ addresses?: K8sEndpointAddress[];
27
+ notReadyAddresses?: K8sEndpointAddress[];
28
+ }
29
+ export interface K8sObjectMeta {
30
+ name?: string;
31
+ annotations?: Record<string, string>;
32
+ labels?: Record<string, string>;
33
+ creationTimestamp?: string;
34
+ }
35
+ export interface K8sEndpoints {
36
+ metadata?: K8sObjectMeta;
37
+ subsets?: K8sEndpointSubset[];
38
+ }
39
+ export interface K8sService {
40
+ metadata?: K8sObjectMeta;
41
+ }
42
+ export interface K8sApiResponse<T> {
43
+ body: T;
44
+ }
45
+ export interface CoreV1ApiLike {
46
+ readNamespacedEndpoints(name: string, namespace: string): Promise<K8sApiResponse<K8sEndpoints>>;
47
+ listNamespacedService(namespace: string, pretty?: string, allowWatchBookmarks?: boolean, _continue?: string, fieldSelector?: string, labelSelector?: string): Promise<K8sApiResponse<{
48
+ items: K8sService[];
49
+ }>>;
50
+ }
51
+ export interface KubernetesBackendConfig {
52
+ namespace?: string;
53
+ labelSelector?: string;
54
+ }
55
+ export declare class KubernetesRegistryBackend implements RegistryBackend {
56
+ private k8sApi;
57
+ private readonly namespace;
58
+ private readonly labelSelector;
59
+ constructor(kubeConfig: KubeConfig, config?: KubernetesBackendConfig);
60
+ /**
61
+ * Register a service instance
62
+ * In Kubernetes, services are registered via Service/Endpoints objects
63
+ * This is typically handled by K8s itself
64
+ */
65
+ register(_instance: ServiceInstance): Promise<void>;
66
+ /**
67
+ * Deregister a service instance
68
+ * In Kubernetes, this is handled automatically when pods terminate
69
+ */
70
+ deregister(_instanceId: string): Promise<void>;
71
+ /**
72
+ * Update service instance heartbeat
73
+ * Kubernetes handles liveness/readiness probes
74
+ */
75
+ heartbeat(_instanceId: string): Promise<void>;
76
+ /**
77
+ * Get all instances of a service from Kubernetes Endpoints
78
+ */
79
+ getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
80
+ /**
81
+ * Get a specific service instance
82
+ */
83
+ getInstance(instanceId: string): Promise<ServiceInstance | null>;
84
+ /**
85
+ * Get all registered services in the namespace
86
+ */
87
+ getAllServices(): Promise<string[]>;
88
+ /**
89
+ * Update service instance status
90
+ * In Kubernetes, status is managed by readiness probes
91
+ */
92
+ updateStatus(_instanceId: string, _status: string): Promise<void>;
93
+ /**
94
+ * Clean up expired instances
95
+ * Kubernetes handles this automatically
96
+ */
97
+ cleanup(): Promise<void>;
98
+ /**
99
+ * Create a ServiceInstance from Kubernetes endpoint data
100
+ */
101
+ private createServiceInstance;
102
+ }
103
+ //# sourceMappingURL=kubernetes-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kubernetes-backend.d.ts","sourceRoot":"","sources":["../../src/backends/kubernetes-backend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * Kubernetes Registry Backend
4
+ * Uses Kubernetes Service Discovery
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.KubernetesRegistryBackend = void 0;
8
+ const types_1 = require("../types");
9
+ const filter_1 = require("../utils/filter");
10
+ const logger_1 = require("../utils/logger");
11
+ const validation_1 = require("../utils/validation");
12
+ class KubernetesRegistryBackend {
13
+ constructor(kubeConfig, config = {}) {
14
+ (0, validation_1.validateKubernetesBackendConfig)(config);
15
+ // Import CoreV1Api dynamically to avoid build errors when @kubernetes/client-node is not installed
16
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
17
+ const { CoreV1Api: CoreV1ApiClass } = require('@kubernetes/client-node');
18
+ this.k8sApi = kubeConfig.makeApiClient(CoreV1ApiClass);
19
+ this.namespace = config.namespace || 'default';
20
+ this.labelSelector = config.labelSelector || 'app.kubernetes.io/managed-by=hazeljs';
21
+ }
22
+ /**
23
+ * Register a service instance
24
+ * In Kubernetes, services are registered via Service/Endpoints objects
25
+ * This is typically handled by K8s itself
26
+ */
27
+ async register(_instance) {
28
+ // In Kubernetes, service registration is handled by the platform
29
+ // We can optionally create/update annotations on the pod
30
+ // Service registration is managed by Kubernetes
31
+ }
32
+ /**
33
+ * Deregister a service instance
34
+ * In Kubernetes, this is handled automatically when pods terminate
35
+ */
36
+ async deregister(_instanceId) {
37
+ // Service deregistration is managed by Kubernetes
38
+ }
39
+ /**
40
+ * Update service instance heartbeat
41
+ * Kubernetes handles liveness/readiness probes
42
+ */
43
+ async heartbeat(_instanceId) {
44
+ // Kubernetes handles health checks via probes
45
+ // No manual heartbeat needed
46
+ }
47
+ /**
48
+ * Get all instances of a service from Kubernetes Endpoints
49
+ */
50
+ async getInstances(serviceName, filter) {
51
+ const logger = logger_1.DiscoveryLogger.getLogger();
52
+ try {
53
+ // Get service endpoints
54
+ const endpointsResponse = await this.k8sApi.readNamespacedEndpoints(serviceName, this.namespace);
55
+ const endpoints = endpointsResponse.body;
56
+ const instances = [];
57
+ if (!endpoints.subsets) {
58
+ return [];
59
+ }
60
+ // Process each subset
61
+ for (const subset of endpoints.subsets) {
62
+ const ports = subset.ports || [];
63
+ const addresses = subset.addresses || [];
64
+ const notReadyAddresses = subset.notReadyAddresses || [];
65
+ // Process ready addresses
66
+ for (const address of addresses) {
67
+ for (const port of ports) {
68
+ const instance = this.createServiceInstance(serviceName, address, port, types_1.ServiceStatus.UP, endpoints.metadata);
69
+ instances.push(instance);
70
+ }
71
+ }
72
+ // Process not-ready addresses
73
+ for (const address of notReadyAddresses) {
74
+ for (const port of ports) {
75
+ const instance = this.createServiceInstance(serviceName, address, port, types_1.ServiceStatus.STARTING, endpoints.metadata);
76
+ instances.push(instance);
77
+ }
78
+ }
79
+ }
80
+ // Apply filters
81
+ return (0, filter_1.applyServiceFilter)(instances, filter);
82
+ }
83
+ catch (error) {
84
+ logger.error(`Failed to get instances for service "${serviceName}" from Kubernetes`, error);
85
+ return [];
86
+ }
87
+ }
88
+ /**
89
+ * Get a specific service instance
90
+ */
91
+ async getInstance(instanceId) {
92
+ // Parse instanceId to get service name and address
93
+ const [serviceName] = instanceId.split(':');
94
+ const instances = await this.getInstances(serviceName);
95
+ return instances.find((i) => i.id === instanceId) || null;
96
+ }
97
+ /**
98
+ * Get all registered services in the namespace
99
+ */
100
+ async getAllServices() {
101
+ const logger = logger_1.DiscoveryLogger.getLogger();
102
+ try {
103
+ const servicesResponse = await this.k8sApi.listNamespacedService(this.namespace, undefined, undefined, undefined, undefined, this.labelSelector);
104
+ return servicesResponse.body.items.map((service) => service.metadata?.name || '');
105
+ }
106
+ catch (error) {
107
+ logger.error('Failed to list services from Kubernetes', error);
108
+ return [];
109
+ }
110
+ }
111
+ /**
112
+ * Update service instance status
113
+ * In Kubernetes, status is managed by readiness probes
114
+ */
115
+ async updateStatus(_instanceId, _status) {
116
+ // Kubernetes manages status via probes
117
+ }
118
+ /**
119
+ * Clean up expired instances
120
+ * Kubernetes handles this automatically
121
+ */
122
+ async cleanup() {
123
+ // Kubernetes automatically removes terminated pods
124
+ }
125
+ /**
126
+ * Create a ServiceInstance from Kubernetes endpoint data
127
+ */
128
+ createServiceInstance(serviceName, address, port, status, metadata) {
129
+ const host = address.ip;
130
+ const portNumber = port.port;
131
+ const instanceId = `${serviceName}:${host}:${portNumber}`;
132
+ // Extract metadata from annotations and labels
133
+ const annotations = metadata?.annotations || {};
134
+ const labels = metadata?.labels || {};
135
+ return {
136
+ id: instanceId,
137
+ name: serviceName,
138
+ host,
139
+ port: portNumber,
140
+ status,
141
+ metadata: {
142
+ ...annotations,
143
+ podName: address.targetRef?.name,
144
+ nodeName: address.nodeName,
145
+ },
146
+ tags: Object.keys(labels),
147
+ zone: labels['topology.kubernetes.io/zone'] || labels['failure-domain.beta.kubernetes.io/zone'],
148
+ lastHeartbeat: new Date(),
149
+ registeredAt: new Date(metadata?.creationTimestamp || Date.now()),
150
+ };
151
+ }
152
+ }
153
+ exports.KubernetesRegistryBackend = KubernetesRegistryBackend;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * In-Memory Registry Backend
3
+ * For development and testing
4
+ */
5
+ import { RegistryBackend } from './registry-backend';
6
+ import { ServiceInstance, ServiceFilter } from '../types';
7
+ export declare class MemoryRegistryBackend implements RegistryBackend {
8
+ private instances;
9
+ private serviceIndex;
10
+ private readonly expirationTime;
11
+ constructor(expirationTime?: number);
12
+ register(instance: ServiceInstance): Promise<void>;
13
+ deregister(instanceId: string): Promise<void>;
14
+ heartbeat(instanceId: string): Promise<void>;
15
+ getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
16
+ getInstance(instanceId: string): Promise<ServiceInstance | null>;
17
+ getAllServices(): Promise<string[]>;
18
+ updateStatus(instanceId: string, status: string): Promise<void>;
19
+ cleanup(): Promise<void>;
20
+ }
21
+ //# sourceMappingURL=memory-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-backend.d.ts","sourceRoot":"","sources":["../../src/backends/memory-backend.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAGzE,qBAAa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,cAAc,SAAQ;IAK5B,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlD,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7C,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5C,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAgBrF,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAIhE,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAInC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAe/B"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * In-Memory Registry Backend
4
+ * For development and testing
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MemoryRegistryBackend = void 0;
8
+ const types_1 = require("../types");
9
+ const filter_1 = require("../utils/filter");
10
+ class MemoryRegistryBackend {
11
+ constructor(expirationTime = 90000) {
12
+ this.instances = new Map();
13
+ this.serviceIndex = new Map();
14
+ // 90 seconds default
15
+ this.expirationTime = expirationTime;
16
+ }
17
+ async register(instance) {
18
+ this.instances.set(instance.id, instance);
19
+ // Update service index
20
+ if (!this.serviceIndex.has(instance.name)) {
21
+ this.serviceIndex.set(instance.name, new Set());
22
+ }
23
+ this.serviceIndex.get(instance.name).add(instance.id);
24
+ }
25
+ async deregister(instanceId) {
26
+ const instance = this.instances.get(instanceId);
27
+ if (instance) {
28
+ this.instances.delete(instanceId);
29
+ // Update service index
30
+ const serviceInstances = this.serviceIndex.get(instance.name);
31
+ if (serviceInstances) {
32
+ serviceInstances.delete(instanceId);
33
+ if (serviceInstances.size === 0) {
34
+ this.serviceIndex.delete(instance.name);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ async heartbeat(instanceId) {
40
+ const instance = this.instances.get(instanceId);
41
+ if (instance) {
42
+ instance.lastHeartbeat = new Date();
43
+ instance.status = types_1.ServiceStatus.UP;
44
+ }
45
+ }
46
+ async getInstances(serviceName, filter) {
47
+ const instanceIds = this.serviceIndex.get(serviceName);
48
+ if (!instanceIds)
49
+ return [];
50
+ const instances = [];
51
+ for (const id of instanceIds) {
52
+ const instance = this.instances.get(id);
53
+ if (instance) {
54
+ instances.push(instance);
55
+ }
56
+ }
57
+ // Apply filters
58
+ return (0, filter_1.applyServiceFilter)(instances, filter);
59
+ }
60
+ async getInstance(instanceId) {
61
+ return this.instances.get(instanceId) || null;
62
+ }
63
+ async getAllServices() {
64
+ return Array.from(this.serviceIndex.keys());
65
+ }
66
+ async updateStatus(instanceId, status) {
67
+ const instance = this.instances.get(instanceId);
68
+ if (instance) {
69
+ instance.status = status;
70
+ }
71
+ }
72
+ async cleanup() {
73
+ const now = Date.now();
74
+ const expiredIds = [];
75
+ for (const [id, instance] of this.instances) {
76
+ const timeSinceHeartbeat = now - instance.lastHeartbeat.getTime();
77
+ if (timeSinceHeartbeat > this.expirationTime) {
78
+ expiredIds.push(id);
79
+ }
80
+ }
81
+ for (const id of expiredIds) {
82
+ await this.deregister(id);
83
+ }
84
+ }
85
+ }
86
+ exports.MemoryRegistryBackend = MemoryRegistryBackend;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Redis Registry Backend
3
+ * For production distributed service registry
4
+ */
5
+ import { RegistryBackend } from './registry-backend';
6
+ import { ServiceInstance, ServiceFilter } from '../types';
7
+ import type { Redis } from 'ioredis';
8
+ export interface RedisBackendConfig {
9
+ host?: string;
10
+ port?: number;
11
+ password?: string;
12
+ db?: number;
13
+ keyPrefix?: string;
14
+ ttl?: number;
15
+ }
16
+ export declare class RedisRegistryBackend implements RegistryBackend {
17
+ private redis;
18
+ private readonly keyPrefix;
19
+ private readonly ttl;
20
+ private connected;
21
+ constructor(redis: Redis, config?: RedisBackendConfig);
22
+ /**
23
+ * Set up Redis connection event handlers for resilience
24
+ */
25
+ private setupConnectionHandlers;
26
+ /**
27
+ * Check Redis connectivity before operations
28
+ */
29
+ private ensureConnected;
30
+ /**
31
+ * Register a service instance
32
+ */
33
+ register(instance: ServiceInstance): Promise<void>;
34
+ /**
35
+ * Deregister a service instance
36
+ */
37
+ deregister(instanceId: string): Promise<void>;
38
+ /**
39
+ * Update service instance heartbeat
40
+ */
41
+ heartbeat(instanceId: string): Promise<void>;
42
+ /**
43
+ * Get all instances of a service (uses MGET for efficiency)
44
+ */
45
+ getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
46
+ /**
47
+ * Get a specific service instance
48
+ */
49
+ getInstance(instanceId: string): Promise<ServiceInstance | null>;
50
+ /**
51
+ * Get all registered services using SCAN (safe for production)
52
+ */
53
+ getAllServices(): Promise<string[]>;
54
+ /**
55
+ * Update service instance status
56
+ */
57
+ updateStatus(instanceId: string, status: string): Promise<void>;
58
+ /**
59
+ * Clean up expired instances
60
+ * Note: Redis handles expiration automatically via TTL
61
+ */
62
+ cleanup(): Promise<void>;
63
+ /**
64
+ * Close Redis connection
65
+ */
66
+ close(): Promise<void>;
67
+ /**
68
+ * Get Redis key for instance
69
+ */
70
+ private getInstanceKey;
71
+ /**
72
+ * Get Redis key for service set
73
+ */
74
+ private getServiceSetKey;
75
+ }
76
+ //# sourceMappingURL=redis-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-backend.d.ts","sourceRoot":"","sources":["../../src/backends/redis-backend.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAIzE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAQ;gBAEb,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,kBAAuB;IAUzD;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAuB/B;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxD;;OAEG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBnD;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBlD;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA8B3F;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAmBtE;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAoBzC;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrE;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B9B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAGzB"}
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ /**
3
+ * Redis Registry Backend
4
+ * For production distributed service registry
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.RedisRegistryBackend = void 0;
8
+ const types_1 = require("../types");
9
+ const filter_1 = require("../utils/filter");
10
+ const logger_1 = require("../utils/logger");
11
+ const validation_1 = require("../utils/validation");
12
+ class RedisRegistryBackend {
13
+ constructor(redis, config = {}) {
14
+ this.connected = true;
15
+ (0, validation_1.validateRedisBackendConfig)(config);
16
+ this.redis = redis;
17
+ this.keyPrefix = config.keyPrefix || 'hazeljs:discovery:';
18
+ this.ttl = config.ttl || 90; // 90 seconds default
19
+ this.setupConnectionHandlers();
20
+ }
21
+ /**
22
+ * Set up Redis connection event handlers for resilience
23
+ */
24
+ setupConnectionHandlers() {
25
+ const logger = logger_1.DiscoveryLogger.getLogger();
26
+ this.redis.on('error', (err) => {
27
+ this.connected = false;
28
+ logger.error('Redis connection error', err);
29
+ });
30
+ this.redis.on('connect', () => {
31
+ this.connected = true;
32
+ logger.info('Redis connected');
33
+ });
34
+ this.redis.on('reconnecting', () => {
35
+ logger.warn('Redis reconnecting...');
36
+ });
37
+ this.redis.on('close', () => {
38
+ this.connected = false;
39
+ logger.warn('Redis connection closed');
40
+ });
41
+ }
42
+ /**
43
+ * Check Redis connectivity before operations
44
+ */
45
+ ensureConnected() {
46
+ if (!this.connected) {
47
+ throw new Error('Redis backend is not connected');
48
+ }
49
+ }
50
+ /**
51
+ * Register a service instance
52
+ */
53
+ async register(instance) {
54
+ this.ensureConnected();
55
+ const key = this.getInstanceKey(instance.id);
56
+ const serviceSetKey = this.getServiceSetKey(instance.name);
57
+ // Store instance data
58
+ await this.redis.setex(key, this.ttl, JSON.stringify(instance));
59
+ // Add to service set
60
+ await this.redis.sadd(serviceSetKey, instance.id);
61
+ // Set expiration on service set (will be refreshed on heartbeat)
62
+ await this.redis.expire(serviceSetKey, this.ttl * 2);
63
+ }
64
+ /**
65
+ * Deregister a service instance
66
+ */
67
+ async deregister(instanceId) {
68
+ this.ensureConnected();
69
+ const key = this.getInstanceKey(instanceId);
70
+ // Get instance to find service name
71
+ const data = await this.redis.get(key);
72
+ if (data) {
73
+ const instance = JSON.parse(data);
74
+ const serviceSetKey = this.getServiceSetKey(instance.name);
75
+ // Remove from service set
76
+ await this.redis.srem(serviceSetKey, instanceId);
77
+ // Delete instance data
78
+ await this.redis.del(key);
79
+ }
80
+ }
81
+ /**
82
+ * Update service instance heartbeat
83
+ */
84
+ async heartbeat(instanceId) {
85
+ this.ensureConnected();
86
+ const key = this.getInstanceKey(instanceId);
87
+ // Get current instance
88
+ const data = await this.redis.get(key);
89
+ if (data) {
90
+ const instance = JSON.parse(data);
91
+ // Update heartbeat and status
92
+ instance.lastHeartbeat = new Date();
93
+ instance.status = types_1.ServiceStatus.UP;
94
+ // Update with new TTL
95
+ await this.redis.setex(key, this.ttl, JSON.stringify(instance));
96
+ // Refresh service set TTL
97
+ const serviceSetKey = this.getServiceSetKey(instance.name);
98
+ await this.redis.expire(serviceSetKey, this.ttl * 2);
99
+ }
100
+ }
101
+ /**
102
+ * Get all instances of a service (uses MGET for efficiency)
103
+ */
104
+ async getInstances(serviceName, filter) {
105
+ this.ensureConnected();
106
+ const serviceSetKey = this.getServiceSetKey(serviceName);
107
+ // Get all instance IDs for this service
108
+ const instanceIds = await this.redis.smembers(serviceSetKey);
109
+ if (instanceIds.length === 0) {
110
+ return [];
111
+ }
112
+ // Batch-fetch all instances with MGET
113
+ const keys = instanceIds.map((id) => this.getInstanceKey(id));
114
+ const results = await this.redis.mget(...keys);
115
+ const instances = [];
116
+ for (const data of results) {
117
+ if (data) {
118
+ const instance = JSON.parse(data);
119
+ instance.lastHeartbeat = new Date(instance.lastHeartbeat);
120
+ instance.registeredAt = new Date(instance.registeredAt);
121
+ instances.push(instance);
122
+ }
123
+ }
124
+ // Apply filters
125
+ return (0, filter_1.applyServiceFilter)(instances, filter);
126
+ }
127
+ /**
128
+ * Get a specific service instance
129
+ */
130
+ async getInstance(instanceId) {
131
+ this.ensureConnected();
132
+ const key = this.getInstanceKey(instanceId);
133
+ const data = await this.redis.get(key);
134
+ if (!data) {
135
+ return null;
136
+ }
137
+ const instance = JSON.parse(data);
138
+ // Convert date strings back to Date objects
139
+ instance.lastHeartbeat = new Date(instance.lastHeartbeat);
140
+ instance.registeredAt = new Date(instance.registeredAt);
141
+ return instance;
142
+ }
143
+ /**
144
+ * Get all registered services using SCAN (safe for production)
145
+ */
146
+ async getAllServices() {
147
+ this.ensureConnected();
148
+ const pattern = `${this.keyPrefix}service:*`;
149
+ const prefixLen = `${this.keyPrefix}service:`.length;
150
+ const services = [];
151
+ let cursor = '0';
152
+ do {
153
+ const [nextCursor, keys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
154
+ cursor = nextCursor;
155
+ for (const key of keys) {
156
+ services.push(key.substring(prefixLen));
157
+ }
158
+ } while (cursor !== '0');
159
+ return services;
160
+ }
161
+ /**
162
+ * Update service instance status
163
+ */
164
+ async updateStatus(instanceId, status) {
165
+ this.ensureConnected();
166
+ const key = this.getInstanceKey(instanceId);
167
+ const data = await this.redis.get(key);
168
+ if (data) {
169
+ const instance = JSON.parse(data);
170
+ instance.status = status;
171
+ await this.redis.setex(key, this.ttl, JSON.stringify(instance));
172
+ }
173
+ }
174
+ /**
175
+ * Clean up expired instances
176
+ * Note: Redis handles expiration automatically via TTL
177
+ */
178
+ async cleanup() {
179
+ this.ensureConnected();
180
+ // Redis automatically removes expired keys
181
+ // This method cleans up stale entries in service sets
182
+ const services = await this.getAllServices();
183
+ for (const serviceName of services) {
184
+ const serviceSetKey = this.getServiceSetKey(serviceName);
185
+ const instanceIds = await this.redis.smembers(serviceSetKey);
186
+ // Check each instance and remove if expired
187
+ for (const id of instanceIds) {
188
+ const exists = await this.redis.exists(this.getInstanceKey(id));
189
+ if (!exists) {
190
+ // Instance key expired but still in set, remove it
191
+ await this.redis.srem(serviceSetKey, id);
192
+ }
193
+ }
194
+ // Remove empty service sets
195
+ const count = await this.redis.scard(serviceSetKey);
196
+ if (count === 0) {
197
+ await this.redis.del(serviceSetKey);
198
+ }
199
+ }
200
+ }
201
+ /**
202
+ * Close Redis connection
203
+ */
204
+ async close() {
205
+ await this.redis.quit();
206
+ }
207
+ /**
208
+ * Get Redis key for instance
209
+ */
210
+ getInstanceKey(instanceId) {
211
+ return `${this.keyPrefix}instance:${instanceId}`;
212
+ }
213
+ /**
214
+ * Get Redis key for service set
215
+ */
216
+ getServiceSetKey(serviceName) {
217
+ return `${this.keyPrefix}service:${serviceName}`;
218
+ }
219
+ }
220
+ exports.RedisRegistryBackend = RedisRegistryBackend;