@hile/micro 1.0.10 → 2.0.0
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/dist/application.d.ts +17 -2
- package/dist/application.js +90 -14
- package/dist/client.d.ts +11 -5
- package/dist/client.js +9 -4
- package/dist/registry.d.ts +9 -5
- package/dist/registry.js +131 -32
- package/package.json +3 -3
package/dist/application.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export declare class Application extends Server {
|
|
|
36
36
|
private readonly namespaces;
|
|
37
37
|
private static readonly CB_COOLDOWN_MS;
|
|
38
38
|
private readonly circuitBreakers;
|
|
39
|
+
private readonly fallbacks;
|
|
40
|
+
private readonly topics;
|
|
39
41
|
constructor(props: ApplicationProps);
|
|
40
42
|
listen(port?: number): Promise<() => Promise<void>>;
|
|
41
43
|
private scheduleRegistryRetry;
|
|
@@ -45,7 +47,20 @@ export declare class Application extends Server {
|
|
|
45
47
|
private getActiveExcludes;
|
|
46
48
|
private findFromRegistry;
|
|
47
49
|
get(namespace: string, exclude?: string[]): Promise<Client>;
|
|
48
|
-
call<T = any>(namespace: string, url: string, data: any,
|
|
49
|
-
|
|
50
|
+
call<T = any>(namespace: string, url: string, data: any, options?: {
|
|
51
|
+
timeout?: number;
|
|
52
|
+
retries?: number;
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
}): Promise<T>;
|
|
55
|
+
stream(namespace: string, url: string, data: any, options?: {
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
retries?: number;
|
|
58
|
+
}): Promise<import('stream').Readable>;
|
|
59
|
+
publish<T = any>(topic: string, data: T): Promise<{
|
|
60
|
+
update: (payload: T) => Promise</*elided*/ any>;
|
|
61
|
+
unpublish: () => Promise</*elided*/ any>;
|
|
62
|
+
}>;
|
|
63
|
+
/** 对同一 topic 重复 subscribe 是幂等的:第二次调用只返回 unsubscribe 函数,不会注册第二个 callback */
|
|
64
|
+
subscribe<T = any>(topic: string, callback: (data: T) => any, isReconnect?: boolean): Promise<() => Promise<void>>;
|
|
50
65
|
}
|
|
51
66
|
export {};
|
package/dist/application.js
CHANGED
|
@@ -47,6 +47,8 @@ export class Application extends Server {
|
|
|
47
47
|
namespaces = new Map();
|
|
48
48
|
static CB_COOLDOWN_MS = 30_000;
|
|
49
49
|
circuitBreakers = new Map();
|
|
50
|
+
fallbacks = new Set();
|
|
51
|
+
topics = new Map();
|
|
50
52
|
constructor(props) {
|
|
51
53
|
const { namespace, registry, registryLookupTimeoutMs = 10_000, requestTimeoutMs = 30_000, ...microAndLoader } = props;
|
|
52
54
|
super(namespace, microAndLoader);
|
|
@@ -54,11 +56,15 @@ export class Application extends Server {
|
|
|
54
56
|
this._registry_address = registry;
|
|
55
57
|
this._registryLookupTimeoutMs = registryLookupTimeoutMs;
|
|
56
58
|
this._requestTimeoutMs = requestTimeoutMs;
|
|
57
|
-
this.register('/-/health', async () => ({
|
|
59
|
+
this.fallbacks.add(this.register('/-/health', async () => ({
|
|
58
60
|
status: 'ok',
|
|
59
61
|
registry: !!this.registry,
|
|
60
62
|
uptime: process.uptime(),
|
|
61
63
|
namespaces: [...this.namespaces.keys()],
|
|
64
|
+
})));
|
|
65
|
+
this.fallbacks.add(this.register('/-/topic/update', async ({ data }) => {
|
|
66
|
+
this.events.emit('topic:' + data.topic, data.payload);
|
|
67
|
+
return Date.now();
|
|
62
68
|
}));
|
|
63
69
|
}
|
|
64
70
|
async listen(port = 0) {
|
|
@@ -76,7 +82,13 @@ export class Application extends Server {
|
|
|
76
82
|
}
|
|
77
83
|
throw err;
|
|
78
84
|
}
|
|
85
|
+
// 这里不清理 topics 由业务方自己清理
|
|
86
|
+
// 这里也不清理 declare 和 undeclare 由业务方自己清理
|
|
79
87
|
return async () => {
|
|
88
|
+
for (const fallback of this.fallbacks) {
|
|
89
|
+
fallback();
|
|
90
|
+
}
|
|
91
|
+
this.fallbacks.clear();
|
|
80
92
|
this.stopped = true;
|
|
81
93
|
if (this.reconnectTimeout) {
|
|
82
94
|
clearTimeout(this.reconnectTimeout);
|
|
@@ -124,6 +136,10 @@ export class Application extends Server {
|
|
|
124
136
|
});
|
|
125
137
|
});
|
|
126
138
|
this.registry = registry;
|
|
139
|
+
// 重新订阅所有 topic
|
|
140
|
+
for (const [topic, callback] of this.topics) {
|
|
141
|
+
await this.subscribe(topic, callback, true);
|
|
142
|
+
}
|
|
127
143
|
})().finally(() => {
|
|
128
144
|
this.registryReconnectPromise = undefined;
|
|
129
145
|
});
|
|
@@ -166,8 +182,8 @@ export class Application extends Server {
|
|
|
166
182
|
async findFromRegistry(namespace, exclude) {
|
|
167
183
|
if (!this.registry)
|
|
168
184
|
throw new Error('Registry not found');
|
|
169
|
-
const
|
|
170
|
-
return await withTimeout(
|
|
185
|
+
const promise = this.registry.request('/-/find', { namespace, exclude });
|
|
186
|
+
return await withTimeout(promise, this._registryLookupTimeoutMs, 'Registry /-/find');
|
|
171
187
|
}
|
|
172
188
|
get(namespace, exclude) {
|
|
173
189
|
if (!this.namespaces.has(namespace)) {
|
|
@@ -238,7 +254,8 @@ export class Application extends Server {
|
|
|
238
254
|
}
|
|
239
255
|
});
|
|
240
256
|
}
|
|
241
|
-
async call(namespace, url, data,
|
|
257
|
+
async call(namespace, url, data, options) {
|
|
258
|
+
const { timeout = this._requestTimeoutMs, retries = 1, signal } = options || {};
|
|
242
259
|
const exclude = this.getActiveExcludes(namespace);
|
|
243
260
|
let client;
|
|
244
261
|
try {
|
|
@@ -249,28 +266,87 @@ export class Application extends Server {
|
|
|
249
266
|
client = await this.get(namespace);
|
|
250
267
|
}
|
|
251
268
|
try {
|
|
252
|
-
const
|
|
253
|
-
|
|
269
|
+
const result = await client.request(url, data, {
|
|
270
|
+
timeout: timeout ?? this._requestTimeoutMs,
|
|
271
|
+
signal,
|
|
272
|
+
});
|
|
254
273
|
this.recordSuccess(namespace, client.host, client.port);
|
|
255
274
|
return result;
|
|
256
275
|
}
|
|
257
276
|
catch (err) {
|
|
258
277
|
this.recordFailure(namespace, client.host, client.port);
|
|
259
278
|
if (retries > 0) {
|
|
260
|
-
return this.call(namespace, url, data, timeout, retries - 1);
|
|
279
|
+
return this.call(namespace, url, data, { timeout, retries: retries - 1, signal });
|
|
261
280
|
}
|
|
262
281
|
throw err;
|
|
263
282
|
}
|
|
264
283
|
}
|
|
265
|
-
async
|
|
284
|
+
async stream(namespace, url, data, options) {
|
|
285
|
+
const { signal, retries = 1 } = options || {};
|
|
286
|
+
const exclude = this.getActiveExcludes(namespace);
|
|
287
|
+
let client;
|
|
288
|
+
try {
|
|
289
|
+
client = await this.get(namespace, exclude);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
this.circuitBreakers.delete(namespace);
|
|
293
|
+
client = await this.get(namespace);
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const readable = client.stream(url, data, { signal });
|
|
297
|
+
this.recordSuccess(namespace, client.host, client.port);
|
|
298
|
+
return readable;
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
this.recordFailure(namespace, client.host, client.port);
|
|
302
|
+
if (retries > 0) {
|
|
303
|
+
return this.stream(namespace, url, data, { signal, retries: retries - 1 });
|
|
304
|
+
}
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async publish(topic, data) {
|
|
266
309
|
if (!this.registry)
|
|
267
310
|
throw new Error('Registry not found');
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
311
|
+
await this.registry.request('/-/declare', { topic, payload: data });
|
|
312
|
+
const ref = {
|
|
313
|
+
update: async (payload) => {
|
|
314
|
+
if (!this.registry)
|
|
315
|
+
throw new Error('Registry not found');
|
|
316
|
+
await this.registry.request('/-/topic/update', { topic, payload });
|
|
317
|
+
return ref;
|
|
318
|
+
},
|
|
319
|
+
unpublish: async () => {
|
|
320
|
+
if (!this.registry)
|
|
321
|
+
throw new Error('Registry not found');
|
|
322
|
+
await this.registry.request('/-/undeclare', { topic });
|
|
323
|
+
return ref;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
return ref;
|
|
327
|
+
}
|
|
328
|
+
/** 对同一 topic 重复 subscribe 是幂等的:第二次调用只返回 unsubscribe 函数,不会注册第二个 callback */
|
|
329
|
+
async subscribe(topic, callback, isReconnect = false) {
|
|
330
|
+
if (!this.registry)
|
|
331
|
+
throw new Error('Registry not found');
|
|
332
|
+
const fallback = async () => {
|
|
333
|
+
if (!this.registry)
|
|
334
|
+
throw new Error('Registry not found');
|
|
335
|
+
await this.registry.request('/-/unsubscribe', { topic });
|
|
336
|
+
if (this.topics.has(topic)) {
|
|
337
|
+
const _callback = this.topics.get(topic);
|
|
338
|
+
this.events.off('topic:' + topic, _callback);
|
|
339
|
+
this.topics.delete(topic);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
if (this.topics.has(topic) && !isReconnect)
|
|
343
|
+
return fallback;
|
|
344
|
+
const payload = await this.registry.request('/-/subscribe', { topic });
|
|
345
|
+
if (!isReconnect) {
|
|
346
|
+
this.events.on('topic:' + topic, callback);
|
|
347
|
+
this.topics.set(topic, callback);
|
|
348
|
+
callback(payload);
|
|
273
349
|
}
|
|
274
|
-
return
|
|
350
|
+
return fallback;
|
|
275
351
|
}
|
|
276
352
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -24,10 +24,16 @@ export declare class Client extends MessageWs {
|
|
|
24
24
|
url: string;
|
|
25
25
|
data: any;
|
|
26
26
|
}): Promise<any>;
|
|
27
|
-
request(url: string, data: any,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
push(url: string, data: any,
|
|
27
|
+
request<T = any>(url: string, data: any, options?: {
|
|
28
|
+
timeout?: number;
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}): Promise<T>;
|
|
31
|
+
push(url: string, data: any, options?: {
|
|
32
|
+
timeout?: number;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}): void;
|
|
35
|
+
stream(url: string, data: any, options?: {
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}): import("node:stream").Readable;
|
|
32
38
|
dispose(): void;
|
|
33
39
|
}
|
package/dist/client.js
CHANGED
|
@@ -52,15 +52,20 @@ export class Client extends MessageWs {
|
|
|
52
52
|
client: this,
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
-
request(url, data,
|
|
55
|
+
request(url, data, options) {
|
|
56
56
|
if (!this._online)
|
|
57
57
|
throw new Error('Client is not online');
|
|
58
|
-
return this._send({ url, data },
|
|
58
|
+
return this._send({ url, data }, options);
|
|
59
59
|
}
|
|
60
|
-
push(url, data,
|
|
60
|
+
push(url, data, options) {
|
|
61
61
|
if (!this._online)
|
|
62
62
|
throw new Error('Client is not online');
|
|
63
|
-
return this._push({ url, data },
|
|
63
|
+
return this._push({ url, data }, options);
|
|
64
|
+
}
|
|
65
|
+
stream(url, data, options) {
|
|
66
|
+
if (!this._online)
|
|
67
|
+
throw new Error('Client is not online');
|
|
68
|
+
return this._stream({ url, data }, options);
|
|
64
69
|
}
|
|
65
70
|
dispose() {
|
|
66
71
|
if (this.heartbeatTimer)
|
package/dist/registry.d.ts
CHANGED
|
@@ -15,15 +15,19 @@ export declare function namespaceToConfigFile(ns: string): string;
|
|
|
15
15
|
export declare function parseConfigFilename(filename: string): string | null;
|
|
16
16
|
export declare class Registry extends Server {
|
|
17
17
|
private readonly namespaces;
|
|
18
|
-
private unregisterFind?;
|
|
19
18
|
private readonly workspace;
|
|
20
19
|
private readonly configFileSuffix;
|
|
21
20
|
private readonly configs;
|
|
21
|
+
private readonly fallbacks;
|
|
22
|
+
private readonly topics;
|
|
22
23
|
constructor(props?: MicroServerProps);
|
|
23
24
|
watchEnvFile(): import("node:fs").FSWatcher | undefined;
|
|
24
25
|
listen(port?: number): Promise<() => Promise<void>>;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
private
|
|
26
|
+
private registerFindApplication;
|
|
27
|
+
private registerDeclare;
|
|
28
|
+
private registerUndeclare;
|
|
29
|
+
private registerSubscribe;
|
|
30
|
+
private registerUnsubscribe;
|
|
31
|
+
private registerReceiveTopicUpdate;
|
|
32
|
+
private publish;
|
|
29
33
|
}
|
package/dist/registry.js
CHANGED
|
@@ -41,10 +41,11 @@ export function parseConfigFilename(filename) {
|
|
|
41
41
|
}
|
|
42
42
|
export class Registry extends Server {
|
|
43
43
|
namespaces = new Map();
|
|
44
|
-
unregisterFind;
|
|
45
44
|
workspace;
|
|
46
45
|
configFileSuffix = '.config.yaml';
|
|
47
46
|
configs = new Map();
|
|
47
|
+
fallbacks = new Set();
|
|
48
|
+
topics = new Map();
|
|
48
49
|
constructor(props = {}) {
|
|
49
50
|
const workspace = resolve(homedir(), '.registry');
|
|
50
51
|
if (!existsSync(workspace)) {
|
|
@@ -62,6 +63,17 @@ export class Registry extends Server {
|
|
|
62
63
|
});
|
|
63
64
|
this.events.on('disconnect', (client, extras) => {
|
|
64
65
|
const key = client.host + ':' + client.port;
|
|
66
|
+
// 清理 topic 中的关联
|
|
67
|
+
for (const [topic, { publishers, subscribers }] of this.topics) {
|
|
68
|
+
if (publishers.has(key))
|
|
69
|
+
publishers.delete(key);
|
|
70
|
+
if (subscribers.has(key))
|
|
71
|
+
subscribers.delete(key);
|
|
72
|
+
if (publishers.size === 0 && subscribers.size === 0) {
|
|
73
|
+
this.topics.delete(topic);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// 清理 namespace 中的关联
|
|
65
77
|
const namespace = extras.join('/');
|
|
66
78
|
if (this.namespaces.has(namespace)) {
|
|
67
79
|
const keys = this.namespaces.get(namespace);
|
|
@@ -73,8 +85,12 @@ export class Registry extends Server {
|
|
|
73
85
|
}
|
|
74
86
|
}
|
|
75
87
|
});
|
|
76
|
-
this.
|
|
77
|
-
this.
|
|
88
|
+
this.registerFindApplication();
|
|
89
|
+
this.registerDeclare();
|
|
90
|
+
this.registerUndeclare();
|
|
91
|
+
this.registerSubscribe();
|
|
92
|
+
this.registerUnsubscribe();
|
|
93
|
+
this.registerReceiveTopicUpdate();
|
|
78
94
|
}
|
|
79
95
|
watchEnvFile() {
|
|
80
96
|
const configFile = resolve(this.workspace, 'configs');
|
|
@@ -86,7 +102,15 @@ export class Registry extends Server {
|
|
|
86
102
|
const config = YAML.parse(readFileSync(resolve(configFile, filename), 'utf8'));
|
|
87
103
|
if (typeof config !== 'object' || config === null)
|
|
88
104
|
continue;
|
|
89
|
-
|
|
105
|
+
const namespace = parseConfigFilename(filename);
|
|
106
|
+
this.configs.set(namespace, config);
|
|
107
|
+
const keys = Object.keys(config);
|
|
108
|
+
for (const key of keys) {
|
|
109
|
+
const _key = `registry:${namespace}/${key}`;
|
|
110
|
+
if (!this.topics.has(_key)) {
|
|
111
|
+
this.topics.set(_key, { publishers: new Set(), subscribers: new Set(), data: config[key] });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
90
114
|
}
|
|
91
115
|
catch { }
|
|
92
116
|
}
|
|
@@ -94,16 +118,26 @@ export class Registry extends Server {
|
|
|
94
118
|
if (!filename?.endsWith(this.configFileSuffix))
|
|
95
119
|
return;
|
|
96
120
|
const fullPath = resolve(configFile, filename);
|
|
97
|
-
// 文件被删除(或重命名):移除对应配置
|
|
98
121
|
if (!existsSync(fullPath)) {
|
|
99
|
-
|
|
122
|
+
const namespace = parseConfigFilename(filename);
|
|
123
|
+
this.configs.delete(namespace);
|
|
100
124
|
return;
|
|
101
125
|
}
|
|
102
126
|
try {
|
|
103
127
|
const config = YAML.parse(readFileSync(fullPath, 'utf8'));
|
|
104
128
|
if (typeof config !== 'object' || config === null)
|
|
105
129
|
return;
|
|
106
|
-
|
|
130
|
+
const namespace = parseConfigFilename(filename);
|
|
131
|
+
this.configs.set(namespace, config);
|
|
132
|
+
const keys = Object.keys(config);
|
|
133
|
+
// 只加不减
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
const _key = `registry:${namespace}/${key}`;
|
|
136
|
+
if (!this.topics.has(_key)) {
|
|
137
|
+
this.topics.set(_key, { publishers: new Set(), subscribers: new Set(), data: config[key] });
|
|
138
|
+
}
|
|
139
|
+
this.publish(_key, config[key]);
|
|
140
|
+
}
|
|
107
141
|
}
|
|
108
142
|
catch { /* vim 替换文件时的中间态读错误,忽略 */ }
|
|
109
143
|
});
|
|
@@ -118,19 +152,15 @@ export class Registry extends Server {
|
|
|
118
152
|
return async () => {
|
|
119
153
|
if (watcher)
|
|
120
154
|
watcher.close();
|
|
155
|
+
for (const fallback of this.fallbacks) {
|
|
156
|
+
fallback();
|
|
157
|
+
}
|
|
158
|
+
this.fallbacks.clear();
|
|
121
159
|
await teardown();
|
|
122
160
|
};
|
|
123
161
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.mountFindHandler();
|
|
127
|
-
}
|
|
128
|
-
mountFindHandler() {
|
|
129
|
-
if (this.unregisterFind) {
|
|
130
|
-
this.unregisterFind();
|
|
131
|
-
this.unregisterFind = undefined;
|
|
132
|
-
}
|
|
133
|
-
this.unregisterFind = this.register('/-/find', async ({ data }) => {
|
|
162
|
+
registerFindApplication() {
|
|
163
|
+
this.fallbacks.add(this.register('/-/find', async ({ data }) => {
|
|
134
164
|
const namespace = data.namespace;
|
|
135
165
|
let keys = this.namespaces.get(namespace);
|
|
136
166
|
if (!keys)
|
|
@@ -143,23 +173,92 @@ export class Registry extends Server {
|
|
|
143
173
|
keys = new Set(filtered);
|
|
144
174
|
}
|
|
145
175
|
return selectRandomRegistryAddress(keys);
|
|
146
|
-
});
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
registerDeclare() {
|
|
179
|
+
this.fallbacks.add(this.register('/-/declare', async ({ data, client }) => {
|
|
180
|
+
const key = `${client.host}:${client.port}`;
|
|
181
|
+
if (!this.topics.has(data.topic)) {
|
|
182
|
+
this.topics.set(data.topic, { publishers: new Set(), subscribers: new Set(), data: data.payload });
|
|
183
|
+
}
|
|
184
|
+
const entry = this.topics.get(data.topic);
|
|
185
|
+
const publishers = entry.publishers;
|
|
186
|
+
entry.data = data.payload;
|
|
187
|
+
publishers.add(key);
|
|
188
|
+
this.publish(data.topic, data.payload);
|
|
189
|
+
return Date.now();
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
registerUndeclare() {
|
|
193
|
+
this.fallbacks.add(this.register('/-/undeclare', async ({ data, client }) => {
|
|
194
|
+
const key = `${client.host}:${client.port}`;
|
|
195
|
+
if (!this.topics.has(data.topic))
|
|
196
|
+
return 0;
|
|
197
|
+
const entry = this.topics.get(data.topic);
|
|
198
|
+
const publishers = entry.publishers;
|
|
199
|
+
const subscribers = entry.subscribers;
|
|
200
|
+
const i = publishers.size;
|
|
201
|
+
if (publishers.has(key)) {
|
|
202
|
+
publishers.delete(key);
|
|
203
|
+
if (publishers.size === 0 && subscribers.size === 0) {
|
|
204
|
+
this.topics.delete(data.topic);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return i - publishers.size;
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
registerSubscribe() {
|
|
211
|
+
this.fallbacks.add(this.register('/-/subscribe', async ({ data, client }) => {
|
|
212
|
+
const key = `${client.host}:${client.port}`;
|
|
213
|
+
if (!this.topics.has(data.topic)) {
|
|
214
|
+
this.topics.set(data.topic, { publishers: new Set(), subscribers: new Set(), data: undefined });
|
|
215
|
+
}
|
|
216
|
+
const entry = this.topics.get(data.topic);
|
|
217
|
+
const subscribers = entry.subscribers;
|
|
218
|
+
subscribers.add(key);
|
|
219
|
+
return entry.data;
|
|
220
|
+
}));
|
|
147
221
|
}
|
|
148
|
-
|
|
149
|
-
this.register('/-/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
222
|
+
registerUnsubscribe() {
|
|
223
|
+
this.fallbacks.add(this.register('/-/unsubscribe', async ({ data, client }) => {
|
|
224
|
+
const key = `${client.host}:${client.port}`;
|
|
225
|
+
if (!this.topics.has(data.topic))
|
|
226
|
+
return 0;
|
|
227
|
+
const entry = this.topics.get(data.topic);
|
|
228
|
+
const subscribers = entry.subscribers;
|
|
229
|
+
const publishers = entry.publishers;
|
|
230
|
+
const i = subscribers.size;
|
|
231
|
+
if (subscribers.has(key)) {
|
|
232
|
+
subscribers.delete(key);
|
|
233
|
+
if (subscribers.size === 0 && publishers.size === 0) {
|
|
234
|
+
this.topics.delete(data.topic);
|
|
153
235
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
236
|
+
}
|
|
237
|
+
return i - subscribers.size;
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
registerReceiveTopicUpdate() {
|
|
241
|
+
this.fallbacks.add(this.register('/-/topic/update', async ({ data }) => {
|
|
242
|
+
// 转发
|
|
243
|
+
this.publish(data.topic, data.payload);
|
|
244
|
+
return Date.now();
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
publish(topic, payload) {
|
|
248
|
+
if (!this.topics.has(topic))
|
|
249
|
+
return;
|
|
250
|
+
const entry = this.topics.get(topic);
|
|
251
|
+
const subscribers = entry.subscribers;
|
|
252
|
+
entry.data = payload;
|
|
253
|
+
for (const key of subscribers.values()) {
|
|
254
|
+
try {
|
|
255
|
+
if (this.clients.has(key)) {
|
|
256
|
+
this.clients.get(key).push(`/-/topic/update`, { topic, payload });
|
|
160
257
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// 推送失败,disconnect 事件中会清理
|
|
261
|
+
}
|
|
262
|
+
}
|
|
164
263
|
}
|
|
165
264
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/micro",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@hile/message-loader": "^1.0.9",
|
|
27
|
-
"@hile/message-ws": "^
|
|
27
|
+
"@hile/message-ws": "^2.0.0",
|
|
28
28
|
"internal-ip": "^9.0.0",
|
|
29
29
|
"ws": "^8.19.0",
|
|
30
30
|
"yaml": "^2.9.0"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "d28b20bc22ee08c45cbf4596672705cb96e8e461"
|
|
33
33
|
}
|