@midwayjs/etcd 4.0.0-alpha.1 → 4.0.0-beta.2

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
@@ -9,4 +9,4 @@ Document: [https://midwayjs.org](https://midwayjs.org)
9
9
 
10
10
  ## License
11
11
 
12
- [MIT]((http://github.com/midwayjs/midway/blob/master/LICENSE))
12
+ [MIT]((https://github.com/midwayjs/midway/blob/master/LICENSE))
@@ -1,4 +1,6 @@
1
+ import { IMidwayContainer } from '@midwayjs/core';
1
2
  export declare class ETCDConfiguration {
2
3
  onReady(container: any): Promise<void>;
4
+ onStop(container: IMidwayContainer): Promise<void>;
3
5
  }
4
6
  //# sourceMappingURL=configuration.d.ts.map
@@ -14,6 +14,10 @@ let ETCDConfiguration = class ETCDConfiguration {
14
14
  async onReady(container) {
15
15
  await container.getAsync(manager_1.ETCDServiceFactory);
16
16
  }
17
+ async onStop(container) {
18
+ const factory = await container.getAsync(manager_1.ETCDServiceFactory);
19
+ await factory.stop();
20
+ }
17
21
  };
18
22
  exports.ETCDConfiguration = ETCDConfiguration;
19
23
  exports.ETCDConfiguration = ETCDConfiguration = __decorate([
@@ -0,0 +1,39 @@
1
+ import { Etcd3 } from 'etcd3';
2
+ import { ServiceDiscovery, ServiceDiscoveryClient, ILogger } from '@midwayjs/core';
3
+ import { EtcdServiceDiscoveryOptions, EtcdInstanceMetadata } from '../interface';
4
+ export declare class EtcdServiceDiscoverClient extends ServiceDiscoveryClient<Etcd3, EtcdServiceDiscoveryOptions, EtcdInstanceMetadata> {
5
+ private lease?;
6
+ private renewTimer?;
7
+ private readonly ttl;
8
+ private readonly renewInterval;
9
+ private logger;
10
+ private registeredInstance;
11
+ private onlineInstanceData;
12
+ constructor(client: Etcd3, serviceDiscoveryOptions: EtcdServiceDiscoveryOptions, logger: ILogger);
13
+ private getServiceKey;
14
+ private getInstanceKey;
15
+ private createLease;
16
+ private revokeLeaseIfExists;
17
+ private renewLease;
18
+ private startRenewTimer;
19
+ register(instance: EtcdInstanceMetadata): Promise<void>;
20
+ deregister(): Promise<void>;
21
+ online(): Promise<void>;
22
+ offline(): Promise<void>;
23
+ beforeStop(): Promise<void>;
24
+ }
25
+ export declare class EtcdServiceDiscovery extends ServiceDiscovery<Etcd3, EtcdServiceDiscoveryOptions, EtcdInstanceMetadata, EtcdInstanceMetadata, string> {
26
+ private etcdServiceFactory;
27
+ private configService;
28
+ private etcdServiceDiscoveryOptions;
29
+ private coreLogger;
30
+ private listenerStore;
31
+ init(): Promise<void>;
32
+ protected getServiceClient(): Etcd3;
33
+ protected createServiceDiscoverClientImpl(options: EtcdServiceDiscoveryOptions): EtcdServiceDiscoverClient;
34
+ protected getDefaultServiceDiscoveryOptions(): EtcdServiceDiscoveryOptions;
35
+ private getListener;
36
+ getInstances(serviceName: string): Promise<EtcdInstanceMetadata[]>;
37
+ beforeStop(): Promise<void>;
38
+ }
39
+ //# sourceMappingURL=serviceDiscovery.d.ts.map
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EtcdServiceDiscovery = exports.EtcdServiceDiscoverClient = void 0;
13
+ const core_1 = require("@midwayjs/core");
14
+ const manager_1 = require("../manager");
15
+ class EtcdDataListener {
16
+ constructor(client, serviceName) {
17
+ this.client = client;
18
+ this.serviceName = serviceName;
19
+ this.data = [];
20
+ this.destroyed = false;
21
+ }
22
+ async init() {
23
+ await this.refresh();
24
+ this.watcher = await this.client
25
+ .watch()
26
+ .prefix(this.getServiceKey())
27
+ .create();
28
+ this.watcher.on('put', this.refresh.bind(this));
29
+ this.watcher.on('delete', this.refresh.bind(this));
30
+ }
31
+ getServiceKey() {
32
+ return `${this.serviceName}`.startsWith('/')
33
+ ? `${this.serviceName}`
34
+ : `services/${this.serviceName}`;
35
+ }
36
+ async refresh() {
37
+ if (this.destroyed)
38
+ return;
39
+ const key = this.getServiceKey();
40
+ const response = await this.client.getAll().prefix(key).exec();
41
+ this.data = response.kvs
42
+ .map(kv => {
43
+ try {
44
+ return JSON.parse(kv.value.toString());
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ })
50
+ .filter(Boolean);
51
+ }
52
+ getData() {
53
+ return this.data;
54
+ }
55
+ async destroy() {
56
+ this.destroyed = true;
57
+ if (this.watcher) {
58
+ await this.watcher.cancel();
59
+ this.watcher = null;
60
+ }
61
+ }
62
+ }
63
+ class EtcdServiceDiscoverClient extends core_1.ServiceDiscoveryClient {
64
+ constructor(client, serviceDiscoveryOptions, logger) {
65
+ super(client, serviceDiscoveryOptions);
66
+ this.registeredInstance = null;
67
+ this.onlineInstanceData = null;
68
+ this.ttl = serviceDiscoveryOptions.ttl || 30;
69
+ this.renewInterval = Math.floor(this.ttl / 3) * 1000;
70
+ this.logger = logger;
71
+ }
72
+ getServiceKey(serviceName) {
73
+ const ns = this.options.namespace || 'services';
74
+ return `${ns}/${serviceName}`;
75
+ }
76
+ getInstanceKey(serviceName, instanceId) {
77
+ return `${this.getServiceKey(serviceName)}/${instanceId}`;
78
+ }
79
+ async createLease(ttl = 30) {
80
+ const lease = this.client.lease(ttl);
81
+ await lease.grant();
82
+ return lease;
83
+ }
84
+ async revokeLeaseIfExists() {
85
+ if (this.lease) {
86
+ try {
87
+ await this.lease.revoke();
88
+ }
89
+ catch (e) {
90
+ this.logger.debug('[etcd][debug] revokeLease error', this.lease.id.toString(), e);
91
+ }
92
+ this.lease = undefined;
93
+ }
94
+ }
95
+ async renewLease() {
96
+ if (!this.lease) {
97
+ return;
98
+ }
99
+ try {
100
+ this.logger.debug('[etcd][debug] renewLease', this.lease.id.toString());
101
+ await this.lease.keepaliveOnce();
102
+ }
103
+ catch (error) {
104
+ this.logger.debug('[etcd][debug] renewLease failed, will revoke and recreate', this.lease.id.toString(), error);
105
+ await this.revokeLeaseIfExists();
106
+ this.lease = await this.createLease(this.ttl);
107
+ }
108
+ }
109
+ startRenewTimer() {
110
+ if (this.renewTimer) {
111
+ clearInterval(this.renewTimer);
112
+ }
113
+ this.renewTimer = setInterval(() => {
114
+ this.renewLease().catch(error => {
115
+ this.logger.error('Error in lease renewal timer:', error);
116
+ });
117
+ }, this.renewInterval);
118
+ }
119
+ async register(instance) {
120
+ this.registeredInstance = instance;
121
+ // 注册时默认上线
122
+ await this.online();
123
+ }
124
+ async deregister() {
125
+ if (this.onlineInstanceData) {
126
+ await this.offline();
127
+ }
128
+ if (this.registeredInstance) {
129
+ const key = this.getInstanceKey(this.registeredInstance.serviceName, this.registeredInstance.id);
130
+ await this.client.delete().key(key).exec();
131
+ this.logger.info(`[midway:etcd] deregister instance: ${this.registeredInstance.id} for service: ${this.registeredInstance.serviceName}`);
132
+ this.registeredInstance = null;
133
+ }
134
+ await this.revokeLeaseIfExists();
135
+ }
136
+ async online() {
137
+ if (!this.registeredInstance) {
138
+ throw new Error('No instance registered, cannot online.');
139
+ }
140
+ // 已经上线则忽略
141
+ if (this.onlineInstanceData) {
142
+ return;
143
+ }
144
+ const key = this.getInstanceKey(this.registeredInstance.serviceName, this.registeredInstance.id);
145
+ const value = JSON.stringify(this.registeredInstance);
146
+ this.lease = await this.createLease(this.registeredInstance.ttl || this.ttl);
147
+ await this.lease.put(key).value(value);
148
+ this.logger.info(`[midway:etcd] online instance: ${this.registeredInstance.id} for service: ${this.registeredInstance.serviceName}`);
149
+ this.startRenewTimer();
150
+ this.onlineInstanceData = this.registeredInstance;
151
+ }
152
+ async offline() {
153
+ // 已经下线则忽略
154
+ if (!this.onlineInstanceData) {
155
+ return;
156
+ }
157
+ const key = this.getInstanceKey(this.onlineInstanceData.serviceName, this.onlineInstanceData.id);
158
+ await this.client.delete().key(key).exec();
159
+ this.logger.info(`[midway:etcd] offline instance: ${this.onlineInstanceData.id} for service: ${this.onlineInstanceData.serviceName}`);
160
+ this.onlineInstanceData = null;
161
+ await this.revokeLeaseIfExists();
162
+ }
163
+ async beforeStop() {
164
+ if (this.renewTimer) {
165
+ clearInterval(this.renewTimer);
166
+ this.renewTimer = undefined;
167
+ }
168
+ if (this.lease) {
169
+ await this.lease.revoke();
170
+ }
171
+ }
172
+ }
173
+ exports.EtcdServiceDiscoverClient = EtcdServiceDiscoverClient;
174
+ let EtcdServiceDiscovery = class EtcdServiceDiscovery extends core_1.ServiceDiscovery {
175
+ constructor() {
176
+ super(...arguments);
177
+ this.listenerStore = new Map();
178
+ }
179
+ async init() {
180
+ this.etcdServiceDiscoveryOptions = this.configService.getConfiguration('etcd.serviceDiscovery', {});
181
+ }
182
+ getServiceClient() {
183
+ return this.etcdServiceFactory.get(this.etcdServiceDiscoveryOptions.serviceDiscoveryClient ||
184
+ this.etcdServiceFactory.getDefaultClientName() ||
185
+ 'default');
186
+ }
187
+ createServiceDiscoverClientImpl(options) {
188
+ return new EtcdServiceDiscoverClient(this.getServiceClient(), options, this.coreLogger);
189
+ }
190
+ getDefaultServiceDiscoveryOptions() {
191
+ return this.etcdServiceDiscoveryOptions;
192
+ }
193
+ async getListener(serviceName) {
194
+ if (!this.listenerStore.has(serviceName)) {
195
+ const listener = new EtcdDataListener(this.getServiceClient(), serviceName);
196
+ await listener.init();
197
+ this.listenerStore.set(serviceName, listener);
198
+ }
199
+ return this.listenerStore.get(serviceName);
200
+ }
201
+ async getInstances(serviceName) {
202
+ const listener = await this.getListener(serviceName);
203
+ return listener.getData();
204
+ }
205
+ async beforeStop() {
206
+ await Promise.all(Array.from(this.listenerStore.values()).map(listener => listener.destroy()));
207
+ this.listenerStore.clear();
208
+ }
209
+ };
210
+ exports.EtcdServiceDiscovery = EtcdServiceDiscovery;
211
+ __decorate([
212
+ (0, core_1.Inject)(),
213
+ __metadata("design:type", manager_1.ETCDServiceFactory)
214
+ ], EtcdServiceDiscovery.prototype, "etcdServiceFactory", void 0);
215
+ __decorate([
216
+ (0, core_1.Inject)(),
217
+ __metadata("design:type", core_1.MidwayConfigService)
218
+ ], EtcdServiceDiscovery.prototype, "configService", void 0);
219
+ __decorate([
220
+ (0, core_1.Logger)(),
221
+ __metadata("design:type", Object)
222
+ ], EtcdServiceDiscovery.prototype, "coreLogger", void 0);
223
+ __decorate([
224
+ (0, core_1.Init)(),
225
+ __metadata("design:type", Function),
226
+ __metadata("design:paramtypes", []),
227
+ __metadata("design:returntype", Promise)
228
+ ], EtcdServiceDiscovery.prototype, "init", null);
229
+ exports.EtcdServiceDiscovery = EtcdServiceDiscovery = __decorate([
230
+ (0, core_1.Singleton)()
231
+ ], EtcdServiceDiscovery);
232
+ //# sourceMappingURL=serviceDiscovery.js.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { ETCDConfiguration as Configuration } from './configuration';
2
2
  export * from './manager';
3
+ export * from './extension/serviceDiscovery';
3
4
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -18,4 +18,5 @@ exports.Configuration = void 0;
18
18
  var configuration_1 = require("./configuration");
19
19
  Object.defineProperty(exports, "Configuration", { enumerable: true, get: function () { return configuration_1.ETCDConfiguration; } });
20
20
  __exportStar(require("./manager"), exports);
21
+ __exportStar(require("./extension/serviceDiscovery"), exports);
21
22
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,46 @@
1
+ import { ServiceDiscoveryBaseInstance, ServiceDiscoveryOptions, DefaultInstanceMetadata, ServiceFactoryConfigOption } from '@midwayjs/core';
2
+ import { IOptions } from 'etcd3';
3
+ export interface EtcdInstanceMetadata extends ServiceDiscoveryBaseInstance {
4
+ /**
5
+ * 服务名称
6
+ */
7
+ serviceName: string;
8
+ /**
9
+ * 服务实例 ID
10
+ */
11
+ id: string;
12
+ /**
13
+ * 服务实例的过期时间(秒)
14
+ */
15
+ ttl?: number;
16
+ /**
17
+ * 服务实例的标签
18
+ */
19
+ tags?: string[];
20
+ /**
21
+ * 服务实例的元数据
22
+ */
23
+ meta?: Record<string, string>;
24
+ /**
25
+ * 服务实例的状态
26
+ */
27
+ status?: 'UP' | 'DOWN';
28
+ }
29
+ export interface EtcdServiceDiscoveryOptions extends ServiceDiscoveryOptions<EtcdInstanceMetadata> {
30
+ /**
31
+ * 命名空间
32
+ */
33
+ namespace?: string;
34
+ /**
35
+ * 服务信息的过期时间(秒)
36
+ */
37
+ ttl?: number;
38
+ /**
39
+ * 服务实例配置
40
+ */
41
+ serviceOptions?: EtcdInstanceMetadata | ((meta: DefaultInstanceMetadata) => EtcdInstanceMetadata);
42
+ }
43
+ export type MidwayEtcdConfigOptions = ServiceFactoryConfigOption<IOptions> & {
44
+ serviceDiscovery?: EtcdServiceDiscoveryOptions;
45
+ };
46
+ //# sourceMappingURL=interface.d.ts.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=interface.js.map
package/dist/manager.js CHANGED
@@ -14,7 +14,9 @@ const core_1 = require("@midwayjs/core");
14
14
  const etcd3_1 = require("etcd3");
15
15
  let ETCDServiceFactory = class ETCDServiceFactory extends core_1.ServiceFactory {
16
16
  async init() {
17
- await this.initClients(this.etcdConfig);
17
+ await this.initClients(this.etcdConfig, {
18
+ concurrent: true,
19
+ });
18
20
  }
19
21
  async createClient(config) {
20
22
  this.logger.info('[midway:etcd] init %s', config.hosts);
package/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { IOptions } from 'etcd3';
1
+ import { MidwayEtcdConfigOptions } from './dist/index';
2
2
  export * from './dist/index';
3
3
 
4
4
  declare module '@midwayjs/core/dist/interface' {
5
5
  interface MidwayConfig {
6
- etcd?: ServiceFactoryConfigOption<IOptions>;
6
+ etcd?: MidwayEtcdConfigOptions;
7
7
  }
8
8
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@midwayjs/etcd",
3
3
  "description": "midway etcd component",
4
- "version": "4.0.0-alpha.1",
4
+ "version": "4.0.0-beta.2",
5
5
  "main": "dist/index.js",
6
6
  "typings": "index.d.ts",
7
7
  "files": [
@@ -10,8 +10,8 @@
10
10
  "index.d.ts"
11
11
  ],
12
12
  "devDependencies": {
13
- "@midwayjs/core": "^4.0.0-alpha.1",
14
- "@midwayjs/mock": "^4.0.0-alpha.1"
13
+ "@midwayjs/core": "^4.0.0-beta.2",
14
+ "@midwayjs/mock": "^4.0.0-beta.2"
15
15
  },
16
16
  "dependencies": {
17
17
  "etcd3": "1.1.2"
@@ -30,11 +30,11 @@
30
30
  "lint": "mwts check"
31
31
  },
32
32
  "engines": {
33
- "node": ">=16"
33
+ "node": ">=20"
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
37
37
  "url": "https://github.com/midwayjs/midway.git"
38
38
  },
39
- "gitHead": "14bb4da91805a1cf52f190c0d37a74b395dd6372"
39
+ "gitHead": "53bfef4c5279da5f09025e4610bdbf64f94f60bd"
40
40
  }