@hazeljs/discovery 0.2.0-beta.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 (59) hide show
  1. package/README.md +281 -0
  2. package/dist/__tests__/decorators.test.d.ts +5 -0
  3. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  4. package/dist/__tests__/decorators.test.js +72 -0
  5. package/dist/__tests__/discovery-client.test.d.ts +5 -0
  6. package/dist/__tests__/discovery-client.test.d.ts.map +1 -0
  7. package/dist/__tests__/discovery-client.test.js +142 -0
  8. package/dist/__tests__/load-balancer-strategies.test.d.ts +5 -0
  9. package/dist/__tests__/load-balancer-strategies.test.d.ts.map +1 -0
  10. package/dist/__tests__/load-balancer-strategies.test.js +234 -0
  11. package/dist/__tests__/memory-backend.test.d.ts +5 -0
  12. package/dist/__tests__/memory-backend.test.d.ts.map +1 -0
  13. package/dist/__tests__/memory-backend.test.js +246 -0
  14. package/dist/__tests__/service-client.test.d.ts +5 -0
  15. package/dist/__tests__/service-client.test.d.ts.map +1 -0
  16. package/dist/__tests__/service-client.test.js +215 -0
  17. package/dist/__tests__/service-registry.test.d.ts +5 -0
  18. package/dist/__tests__/service-registry.test.d.ts.map +1 -0
  19. package/dist/__tests__/service-registry.test.js +65 -0
  20. package/dist/backends/consul-backend.d.ts +76 -0
  21. package/dist/backends/consul-backend.d.ts.map +1 -0
  22. package/dist/backends/consul-backend.js +275 -0
  23. package/dist/backends/kubernetes-backend.d.ts +65 -0
  24. package/dist/backends/kubernetes-backend.d.ts.map +1 -0
  25. package/dist/backends/kubernetes-backend.js +174 -0
  26. package/dist/backends/memory-backend.d.ts +22 -0
  27. package/dist/backends/memory-backend.d.ts.map +1 -0
  28. package/dist/backends/memory-backend.js +115 -0
  29. package/dist/backends/redis-backend.d.ts +71 -0
  30. package/dist/backends/redis-backend.d.ts.map +1 -0
  31. package/dist/backends/redis-backend.js +200 -0
  32. package/dist/backends/registry-backend.d.ts +39 -0
  33. package/dist/backends/registry-backend.d.ts.map +1 -0
  34. package/dist/backends/registry-backend.js +5 -0
  35. package/dist/client/discovery-client.d.ts +47 -0
  36. package/dist/client/discovery-client.d.ts.map +1 -0
  37. package/dist/client/discovery-client.js +123 -0
  38. package/dist/client/service-client.d.ts +52 -0
  39. package/dist/client/service-client.d.ts.map +1 -0
  40. package/dist/client/service-client.js +95 -0
  41. package/dist/decorators/inject-service-client.decorator.d.ts +16 -0
  42. package/dist/decorators/inject-service-client.decorator.d.ts.map +1 -0
  43. package/dist/decorators/inject-service-client.decorator.js +24 -0
  44. package/dist/decorators/service-registry.decorator.d.ts +11 -0
  45. package/dist/decorators/service-registry.decorator.d.ts.map +1 -0
  46. package/dist/decorators/service-registry.decorator.js +20 -0
  47. package/dist/index.d.ts +18 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +44 -0
  50. package/dist/load-balancer/strategies.d.ts +82 -0
  51. package/dist/load-balancer/strategies.d.ts.map +1 -0
  52. package/dist/load-balancer/strategies.js +209 -0
  53. package/dist/registry/service-registry.d.ts +51 -0
  54. package/dist/registry/service-registry.d.ts.map +1 -0
  55. package/dist/registry/service-registry.js +148 -0
  56. package/dist/types/index.d.ts +61 -0
  57. package/dist/types/index.d.ts.map +1 -0
  58. package/dist/types/index.js +14 -0
  59. package/package.json +78 -0
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Kubernetes Registry Backend
3
+ * Uses Kubernetes Service Discovery
4
+ */
5
+ import { RegistryBackend } from './registry-backend';
6
+ import { ServiceInstance, ServiceFilter } from '../types';
7
+ type KubeConfig = any;
8
+ export interface KubernetesBackendConfig {
9
+ namespace?: string;
10
+ labelSelector?: string;
11
+ }
12
+ export declare class KubernetesRegistryBackend implements RegistryBackend {
13
+ private k8sApi;
14
+ private readonly namespace;
15
+ private readonly labelSelector;
16
+ constructor(kubeConfig: KubeConfig, config?: KubernetesBackendConfig);
17
+ /**
18
+ * Register a service instance
19
+ * In Kubernetes, services are registered via Service/Endpoints objects
20
+ * This is typically handled by K8s itself
21
+ */
22
+ register(_instance: ServiceInstance): Promise<void>;
23
+ /**
24
+ * Deregister a service instance
25
+ * In Kubernetes, this is handled automatically when pods terminate
26
+ */
27
+ deregister(_instanceId: string): Promise<void>;
28
+ /**
29
+ * Update service instance heartbeat
30
+ * Kubernetes handles liveness/readiness probes
31
+ */
32
+ heartbeat(_instanceId: string): Promise<void>;
33
+ /**
34
+ * Get all instances of a service from Kubernetes Endpoints
35
+ */
36
+ getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
37
+ /**
38
+ * Get a specific service instance
39
+ */
40
+ getInstance(instanceId: string): Promise<ServiceInstance | null>;
41
+ /**
42
+ * Get all registered services in the namespace
43
+ */
44
+ getAllServices(): Promise<string[]>;
45
+ /**
46
+ * Update service instance status
47
+ * In Kubernetes, status is managed by readiness probes
48
+ */
49
+ updateStatus(_instanceId: string, _status: string): Promise<void>;
50
+ /**
51
+ * Clean up expired instances
52
+ * Kubernetes handles this automatically
53
+ */
54
+ cleanup(): Promise<void>;
55
+ /**
56
+ * Create a ServiceInstance from Kubernetes endpoint data
57
+ */
58
+ private createServiceInstance;
59
+ /**
60
+ * Apply filter to instances
61
+ */
62
+ private applyFilter;
63
+ }
64
+ export {};
65
+ //# 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":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAiB,MAAM,UAAU,CAAC;AAIzE,KAAK,UAAU,GAAG,GAAG,CAAC;AAItB,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,yBAA0B,YAAW,eAAe;IAC/D,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,UAAU,EAAE,UAAU,EAAE,MAAM,GAAE,uBAA4B;IASxE;;;;OAIG;IACG,QAAQ,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzD;;;OAGG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD;;;OAGG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA6D3F;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQtE;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAmBzC;;;OAGG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvE;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAsC7B;;OAEG;IACH,OAAO,CAAC,WAAW;CA2BpB"}
@@ -0,0 +1,174 @@
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
+ class KubernetesRegistryBackend {
10
+ constructor(kubeConfig, config = {}) {
11
+ // Import CoreV1Api dynamically to avoid build errors when @kubernetes/client-node is not installed
12
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
13
+ const { CoreV1Api: CoreV1ApiClass } = require('@kubernetes/client-node');
14
+ this.k8sApi = kubeConfig.makeApiClient(CoreV1ApiClass);
15
+ this.namespace = config.namespace || 'default';
16
+ this.labelSelector = config.labelSelector || 'app.kubernetes.io/managed-by=hazeljs';
17
+ }
18
+ /**
19
+ * Register a service instance
20
+ * In Kubernetes, services are registered via Service/Endpoints objects
21
+ * This is typically handled by K8s itself
22
+ */
23
+ async register(_instance) {
24
+ // In Kubernetes, service registration is handled by the platform
25
+ // We can optionally create/update annotations on the pod
26
+ // Service registration is managed by Kubernetes
27
+ }
28
+ /**
29
+ * Deregister a service instance
30
+ * In Kubernetes, this is handled automatically when pods terminate
31
+ */
32
+ async deregister(_instanceId) {
33
+ // Service deregistration is managed by Kubernetes
34
+ }
35
+ /**
36
+ * Update service instance heartbeat
37
+ * Kubernetes handles liveness/readiness probes
38
+ */
39
+ async heartbeat(_instanceId) {
40
+ // Kubernetes handles health checks via probes
41
+ // No manual heartbeat needed
42
+ }
43
+ /**
44
+ * Get all instances of a service from Kubernetes Endpoints
45
+ */
46
+ async getInstances(serviceName, filter) {
47
+ try {
48
+ // Get service endpoints
49
+ const endpointsResponse = await this.k8sApi.readNamespacedEndpoints(serviceName, this.namespace);
50
+ const endpoints = endpointsResponse.body;
51
+ const instances = [];
52
+ if (!endpoints.subsets) {
53
+ return [];
54
+ }
55
+ // Process each subset
56
+ for (const subset of endpoints.subsets) {
57
+ const ports = subset.ports || [];
58
+ const addresses = subset.addresses || [];
59
+ const notReadyAddresses = subset.notReadyAddresses || [];
60
+ // Process ready addresses
61
+ for (const address of addresses) {
62
+ for (const port of ports) {
63
+ const instance = this.createServiceInstance(serviceName, address, port, types_1.ServiceStatus.UP, endpoints.metadata);
64
+ instances.push(instance);
65
+ }
66
+ }
67
+ // Process not-ready addresses
68
+ for (const address of notReadyAddresses) {
69
+ for (const port of ports) {
70
+ const instance = this.createServiceInstance(serviceName, address, port, types_1.ServiceStatus.STARTING, endpoints.metadata);
71
+ instances.push(instance);
72
+ }
73
+ }
74
+ }
75
+ // Apply filters
76
+ if (filter) {
77
+ return this.applyFilter(instances, filter);
78
+ }
79
+ return instances;
80
+ }
81
+ catch {
82
+ return [];
83
+ }
84
+ }
85
+ /**
86
+ * Get a specific service instance
87
+ */
88
+ async getInstance(instanceId) {
89
+ // Parse instanceId to get service name and address
90
+ const [serviceName] = instanceId.split(':');
91
+ const instances = await this.getInstances(serviceName);
92
+ return instances.find((i) => i.id === instanceId) || null;
93
+ }
94
+ /**
95
+ * Get all registered services in the namespace
96
+ */
97
+ async getAllServices() {
98
+ try {
99
+ const servicesResponse = await this.k8sApi.listNamespacedService(this.namespace, undefined, undefined, undefined, undefined, this.labelSelector);
100
+ return servicesResponse.body.items.map((service) => service.metadata?.name || '');
101
+ }
102
+ catch {
103
+ return [];
104
+ }
105
+ }
106
+ /**
107
+ * Update service instance status
108
+ * In Kubernetes, status is managed by readiness probes
109
+ */
110
+ async updateStatus(_instanceId, _status) {
111
+ // Kubernetes manages status via probes
112
+ }
113
+ /**
114
+ * Clean up expired instances
115
+ * Kubernetes handles this automatically
116
+ */
117
+ async cleanup() {
118
+ // Kubernetes automatically removes terminated pods
119
+ }
120
+ /**
121
+ * Create a ServiceInstance from Kubernetes endpoint data
122
+ */
123
+ createServiceInstance(serviceName, address, port, status, metadata) {
124
+ const host = address.ip;
125
+ const portNumber = port.port;
126
+ const instanceId = `${serviceName}:${host}:${portNumber}`;
127
+ // Extract metadata from annotations and labels
128
+ const annotations = metadata?.annotations || {};
129
+ const labels = metadata?.labels || {};
130
+ return {
131
+ id: instanceId,
132
+ name: serviceName,
133
+ host,
134
+ port: portNumber,
135
+ status,
136
+ metadata: {
137
+ ...annotations,
138
+ podName: address.targetRef?.name,
139
+ nodeName: address.nodeName,
140
+ },
141
+ tags: Object.keys(labels),
142
+ zone: labels['topology.kubernetes.io/zone'] || labels['failure-domain.beta.kubernetes.io/zone'],
143
+ lastHeartbeat: new Date(),
144
+ registeredAt: new Date(metadata?.creationTimestamp || Date.now()),
145
+ };
146
+ }
147
+ /**
148
+ * Apply filter to instances
149
+ */
150
+ applyFilter(instances, filter) {
151
+ return instances.filter((instance) => {
152
+ if (filter.zone && instance.zone !== filter.zone) {
153
+ return false;
154
+ }
155
+ if (filter.status && instance.status !== filter.status) {
156
+ return false;
157
+ }
158
+ if (filter.tags && filter.tags.length > 0) {
159
+ if (!instance.tags || !filter.tags.every((tag) => instance.tags.includes(tag))) {
160
+ return false;
161
+ }
162
+ }
163
+ if (filter.metadata) {
164
+ for (const [key, value] of Object.entries(filter.metadata)) {
165
+ if (!instance.metadata || instance.metadata[key] !== value) {
166
+ return false;
167
+ }
168
+ }
169
+ }
170
+ return true;
171
+ });
172
+ }
173
+ }
174
+ exports.KubernetesRegistryBackend = KubernetesRegistryBackend;
@@ -0,0 +1,22 @@
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
+ private applyFilter;
21
+ }
22
+ //# 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;AAEzE,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;IAoBrF,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;IAgB9B,OAAO,CAAC,WAAW;CA+BpB"}
@@ -0,0 +1,115 @@
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
+ class MemoryRegistryBackend {
10
+ constructor(expirationTime = 90000) {
11
+ this.instances = new Map();
12
+ this.serviceIndex = new Map();
13
+ // 90 seconds default
14
+ this.expirationTime = expirationTime;
15
+ }
16
+ async register(instance) {
17
+ this.instances.set(instance.id, instance);
18
+ // Update service index
19
+ if (!this.serviceIndex.has(instance.name)) {
20
+ this.serviceIndex.set(instance.name, new Set());
21
+ }
22
+ this.serviceIndex.get(instance.name).add(instance.id);
23
+ }
24
+ async deregister(instanceId) {
25
+ const instance = this.instances.get(instanceId);
26
+ if (instance) {
27
+ this.instances.delete(instanceId);
28
+ // Update service index
29
+ const serviceInstances = this.serviceIndex.get(instance.name);
30
+ if (serviceInstances) {
31
+ serviceInstances.delete(instanceId);
32
+ if (serviceInstances.size === 0) {
33
+ this.serviceIndex.delete(instance.name);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ async heartbeat(instanceId) {
39
+ const instance = this.instances.get(instanceId);
40
+ if (instance) {
41
+ instance.lastHeartbeat = new Date();
42
+ instance.status = types_1.ServiceStatus.UP;
43
+ }
44
+ }
45
+ async getInstances(serviceName, filter) {
46
+ const instanceIds = this.serviceIndex.get(serviceName);
47
+ if (!instanceIds)
48
+ return [];
49
+ let instances = [];
50
+ for (const id of instanceIds) {
51
+ const instance = this.instances.get(id);
52
+ if (instance) {
53
+ instances.push(instance);
54
+ }
55
+ }
56
+ // Apply filters
57
+ if (filter) {
58
+ instances = this.applyFilter(instances, filter);
59
+ }
60
+ return instances;
61
+ }
62
+ async getInstance(instanceId) {
63
+ return this.instances.get(instanceId) || null;
64
+ }
65
+ async getAllServices() {
66
+ return Array.from(this.serviceIndex.keys());
67
+ }
68
+ async updateStatus(instanceId, status) {
69
+ const instance = this.instances.get(instanceId);
70
+ if (instance) {
71
+ instance.status = status;
72
+ }
73
+ }
74
+ async cleanup() {
75
+ const now = Date.now();
76
+ const expiredIds = [];
77
+ for (const [id, instance] of this.instances) {
78
+ const timeSinceHeartbeat = now - instance.lastHeartbeat.getTime();
79
+ if (timeSinceHeartbeat > this.expirationTime) {
80
+ expiredIds.push(id);
81
+ }
82
+ }
83
+ for (const id of expiredIds) {
84
+ await this.deregister(id);
85
+ }
86
+ }
87
+ applyFilter(instances, filter) {
88
+ return instances.filter((instance) => {
89
+ // Filter by zone
90
+ if (filter.zone && instance.zone !== filter.zone) {
91
+ return false;
92
+ }
93
+ // Filter by status
94
+ if (filter.status && instance.status !== filter.status) {
95
+ return false;
96
+ }
97
+ // Filter by tags
98
+ if (filter.tags && filter.tags.length > 0) {
99
+ if (!instance.tags || !filter.tags.every((tag) => instance.tags.includes(tag))) {
100
+ return false;
101
+ }
102
+ }
103
+ // Filter by metadata
104
+ if (filter.metadata) {
105
+ for (const [key, value] of Object.entries(filter.metadata)) {
106
+ if (!instance.metadata || instance.metadata[key] !== value) {
107
+ return false;
108
+ }
109
+ }
110
+ }
111
+ return true;
112
+ });
113
+ }
114
+ }
115
+ exports.MemoryRegistryBackend = MemoryRegistryBackend;
@@ -0,0 +1,71 @@
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
+ constructor(redis: Redis, config?: RedisBackendConfig);
21
+ /**
22
+ * Register a service instance
23
+ */
24
+ register(instance: ServiceInstance): Promise<void>;
25
+ /**
26
+ * Deregister a service instance
27
+ */
28
+ deregister(instanceId: string): Promise<void>;
29
+ /**
30
+ * Update service instance heartbeat
31
+ */
32
+ heartbeat(instanceId: string): Promise<void>;
33
+ /**
34
+ * Get all instances of a service
35
+ */
36
+ getInstances(serviceName: string, filter?: ServiceFilter): Promise<ServiceInstance[]>;
37
+ /**
38
+ * Get a specific service instance
39
+ */
40
+ getInstance(instanceId: string): Promise<ServiceInstance | null>;
41
+ /**
42
+ * Get all registered services
43
+ */
44
+ getAllServices(): Promise<string[]>;
45
+ /**
46
+ * Update service instance status
47
+ */
48
+ updateStatus(instanceId: string, status: string): Promise<void>;
49
+ /**
50
+ * Clean up expired instances
51
+ * Note: Redis handles expiration automatically via TTL
52
+ */
53
+ cleanup(): Promise<void>;
54
+ /**
55
+ * Close Redis connection
56
+ */
57
+ close(): Promise<void>;
58
+ /**
59
+ * Get Redis key for instance
60
+ */
61
+ private getInstanceKey;
62
+ /**
63
+ * Get Redis key for service set
64
+ */
65
+ private getServiceSetKey;
66
+ /**
67
+ * Apply filter to instances
68
+ */
69
+ private applyFilter;
70
+ }
71
+ //# 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;AACzE,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;gBAEjB,KAAK,EAAE,KAAK,EAAE,MAAM,GAAE,kBAAuB;IAMzD;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxD;;OAEG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnD;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBlD;;OAEG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA2B3F;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAiBtE;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAUzC;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYrE;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B9B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;OAEG;IACH,OAAO,CAAC,WAAW;CA+BpB"}
@@ -0,0 +1,200 @@
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
+ class RedisRegistryBackend {
10
+ constructor(redis, config = {}) {
11
+ this.redis = redis;
12
+ this.keyPrefix = config.keyPrefix || 'hazeljs:discovery:';
13
+ this.ttl = config.ttl || 90; // 90 seconds default
14
+ }
15
+ /**
16
+ * Register a service instance
17
+ */
18
+ async register(instance) {
19
+ const key = this.getInstanceKey(instance.id);
20
+ const serviceSetKey = this.getServiceSetKey(instance.name);
21
+ // Store instance data
22
+ await this.redis.setex(key, this.ttl, JSON.stringify(instance));
23
+ // Add to service set
24
+ await this.redis.sadd(serviceSetKey, instance.id);
25
+ // Set expiration on service set (will be refreshed on heartbeat)
26
+ await this.redis.expire(serviceSetKey, this.ttl * 2);
27
+ }
28
+ /**
29
+ * Deregister a service instance
30
+ */
31
+ async deregister(instanceId) {
32
+ const key = this.getInstanceKey(instanceId);
33
+ // Get instance to find service name
34
+ const data = await this.redis.get(key);
35
+ if (data) {
36
+ const instance = JSON.parse(data);
37
+ const serviceSetKey = this.getServiceSetKey(instance.name);
38
+ // Remove from service set
39
+ await this.redis.srem(serviceSetKey, instanceId);
40
+ // Delete instance data
41
+ await this.redis.del(key);
42
+ }
43
+ }
44
+ /**
45
+ * Update service instance heartbeat
46
+ */
47
+ async heartbeat(instanceId) {
48
+ const key = this.getInstanceKey(instanceId);
49
+ // Get current instance
50
+ const data = await this.redis.get(key);
51
+ if (data) {
52
+ const instance = JSON.parse(data);
53
+ // Update heartbeat and status
54
+ instance.lastHeartbeat = new Date();
55
+ instance.status = types_1.ServiceStatus.UP;
56
+ // Update with new TTL
57
+ await this.redis.setex(key, this.ttl, JSON.stringify(instance));
58
+ // Refresh service set TTL
59
+ const serviceSetKey = this.getServiceSetKey(instance.name);
60
+ await this.redis.expire(serviceSetKey, this.ttl * 2);
61
+ }
62
+ }
63
+ /**
64
+ * Get all instances of a service
65
+ */
66
+ async getInstances(serviceName, filter) {
67
+ const serviceSetKey = this.getServiceSetKey(serviceName);
68
+ // Get all instance IDs for this service
69
+ const instanceIds = await this.redis.smembers(serviceSetKey);
70
+ if (instanceIds.length === 0) {
71
+ return [];
72
+ }
73
+ // Get all instance data
74
+ const instances = [];
75
+ for (const id of instanceIds) {
76
+ const instance = await this.getInstance(id);
77
+ if (instance) {
78
+ instances.push(instance);
79
+ }
80
+ }
81
+ // Apply filters
82
+ if (filter) {
83
+ return this.applyFilter(instances, filter);
84
+ }
85
+ return instances;
86
+ }
87
+ /**
88
+ * Get a specific service instance
89
+ */
90
+ async getInstance(instanceId) {
91
+ const key = this.getInstanceKey(instanceId);
92
+ const data = await this.redis.get(key);
93
+ if (!data) {
94
+ return null;
95
+ }
96
+ const instance = JSON.parse(data);
97
+ // Convert date strings back to Date objects
98
+ instance.lastHeartbeat = new Date(instance.lastHeartbeat);
99
+ instance.registeredAt = new Date(instance.registeredAt);
100
+ return instance;
101
+ }
102
+ /**
103
+ * Get all registered services
104
+ */
105
+ async getAllServices() {
106
+ const pattern = `${this.keyPrefix}service:*`;
107
+ const keys = await this.redis.keys(pattern);
108
+ return keys.map((key) => {
109
+ const serviceName = key.replace(`${this.keyPrefix}service:`, '');
110
+ return serviceName;
111
+ });
112
+ }
113
+ /**
114
+ * Update service instance status
115
+ */
116
+ async updateStatus(instanceId, status) {
117
+ const key = this.getInstanceKey(instanceId);
118
+ const data = await this.redis.get(key);
119
+ if (data) {
120
+ const instance = JSON.parse(data);
121
+ instance.status = status;
122
+ await this.redis.setex(key, this.ttl, JSON.stringify(instance));
123
+ }
124
+ }
125
+ /**
126
+ * Clean up expired instances
127
+ * Note: Redis handles expiration automatically via TTL
128
+ */
129
+ async cleanup() {
130
+ // Redis automatically removes expired keys
131
+ // This method can be used for additional cleanup if needed
132
+ const services = await this.getAllServices();
133
+ for (const serviceName of services) {
134
+ const serviceSetKey = this.getServiceSetKey(serviceName);
135
+ const instanceIds = await this.redis.smembers(serviceSetKey);
136
+ // Check each instance and remove if expired
137
+ for (const id of instanceIds) {
138
+ const exists = await this.redis.exists(this.getInstanceKey(id));
139
+ if (!exists) {
140
+ // Instance key expired but still in set, remove it
141
+ await this.redis.srem(serviceSetKey, id);
142
+ }
143
+ }
144
+ // Remove empty service sets
145
+ const count = await this.redis.scard(serviceSetKey);
146
+ if (count === 0) {
147
+ await this.redis.del(serviceSetKey);
148
+ }
149
+ }
150
+ }
151
+ /**
152
+ * Close Redis connection
153
+ */
154
+ async close() {
155
+ await this.redis.quit();
156
+ }
157
+ /**
158
+ * Get Redis key for instance
159
+ */
160
+ getInstanceKey(instanceId) {
161
+ return `${this.keyPrefix}instance:${instanceId}`;
162
+ }
163
+ /**
164
+ * Get Redis key for service set
165
+ */
166
+ getServiceSetKey(serviceName) {
167
+ return `${this.keyPrefix}service:${serviceName}`;
168
+ }
169
+ /**
170
+ * Apply filter to instances
171
+ */
172
+ applyFilter(instances, filter) {
173
+ return instances.filter((instance) => {
174
+ // Filter by zone
175
+ if (filter.zone && instance.zone !== filter.zone) {
176
+ return false;
177
+ }
178
+ // Filter by status
179
+ if (filter.status && instance.status !== filter.status) {
180
+ return false;
181
+ }
182
+ // Filter by tags
183
+ if (filter.tags && filter.tags.length > 0) {
184
+ if (!instance.tags || !filter.tags.every((tag) => instance.tags.includes(tag))) {
185
+ return false;
186
+ }
187
+ }
188
+ // Filter by metadata
189
+ if (filter.metadata) {
190
+ for (const [key, value] of Object.entries(filter.metadata)) {
191
+ if (!instance.metadata || instance.metadata[key] !== value) {
192
+ return false;
193
+ }
194
+ }
195
+ }
196
+ return true;
197
+ });
198
+ }
199
+ }
200
+ exports.RedisRegistryBackend = RedisRegistryBackend;