@spider-mesh/core 1.0.115 → 1.0.117
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/build/src/Encoder.d.ts +14 -2
- package/build/src/Encoder.js +88 -36
- package/build/src/Encoder.js.map +1 -1
- package/build/src/SpiderMesh.d.ts +59 -25
- package/build/src/SpiderMesh.js +315 -254
- package/build/src/SpiderMesh.js.map +1 -1
- package/build/src/builtin-transporter/BuiltinTransporter.d.ts +3 -2
- package/build/src/builtin-transporter/BuiltinTransporter.js +18 -14
- package/build/src/builtin-transporter/BuiltinTransporter.js.map +1 -1
- package/build/src/builtin-transporter/RxjsTcpServer.d.ts +1 -1
- package/build/src/builtin-transporter/RxjsTcpServer.js.map +1 -1
- package/build/src/builtin-transporter/RxjsTcpSocket.d.ts +6 -6
- package/build/src/builtin-transporter/RxjsTcpSocket.js +1 -1
- package/build/src/builtin-transporter/RxjsTcpSocket.js.map +1 -1
- package/build/src/builtin-transporter/RxjsUdpBroadcaster.d.ts +1 -1
- package/build/src/builtin-transporter/RxjsUdpBroadcaster.js +2 -2
- package/build/src/builtin-transporter/RxjsUdpBroadcaster.js.map +1 -1
- package/build/src/decorators/DeepProxy.js.map +1 -1
- package/build/src/decorators/ListenEvent.d.ts +7 -6
- package/build/src/decorators/ListenEvent.js.map +1 -1
- package/build/src/decorators/Microservice.d.ts +7 -1
- package/build/src/decorators/Microservice.js +1 -1
- package/build/src/decorators/Microservice.js.map +1 -1
- package/build/src/decorators/OnMicroserviceReady.js.map +1 -1
- package/build/src/interfaces/RemoteService.d.ts +1 -0
- package/build/src/interfaces/SpiderMeshTransporter.d.ts +6 -5
- package/package.json +1 -1
- package/tsconfig.json +5 -2
package/build/src/SpiderMesh.js
CHANGED
|
@@ -6,26 +6,27 @@ import { RPCOptionsList } from './RPCOptions.js';
|
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import { listEventSubscribers } from './decorators/ListenEvent.js';
|
|
8
8
|
import { listReadyHookMethods } from './decorators/OnMicroserviceReady.js';
|
|
9
|
-
import { BehaviorSubject, EMPTY, Observable, Subject, bufferTime, catchError, debounceTime, filter, firstValueFrom, from, groupBy, map,
|
|
9
|
+
import { BehaviorSubject, EMPTY, Observable, Subject, bufferTime, catchError, debounceTime, filter, finalize, first, firstValueFrom, from, groupBy, map, mergeAll, mergeMap, of, share, tap } from 'rxjs';
|
|
10
10
|
import { readFileSync } from 'fs';
|
|
11
11
|
import { serviceInstanceList } from './decorators/Microservice.js';
|
|
12
12
|
import { sleep } from './helpers/sleep.js';
|
|
13
13
|
import { BuiltinTransporter } from './builtin-transporter/BuiltinTransporter.js';
|
|
14
14
|
import { NAMEPSACE, NODE_ID } from './const.js';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
import { Encoder } from './Encoder.js';
|
|
16
|
+
export var MessageType;
|
|
17
|
+
(function (MessageType) {
|
|
18
|
+
MessageType[MessageType["RpcRequest"] = 10] = "RpcRequest";
|
|
19
|
+
MessageType[MessageType["RpcSubscribeChannel"] = 20] = "RpcSubscribeChannel";
|
|
20
|
+
MessageType[MessageType["RpcUnsubscribeChannel"] = 30] = "RpcUnsubscribeChannel";
|
|
21
|
+
MessageType[MessageType["RpcStream"] = 50] = "RpcStream";
|
|
22
|
+
})(MessageType || (MessageType = {}));
|
|
20
23
|
const PACKAGE_JSON = JSON.parse(readFileSync(`package.json`, 'utf8')) || {};
|
|
21
24
|
export class SpiderMesh {
|
|
22
25
|
transporter;
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
+
#requests = new Map;
|
|
27
|
+
#local_services = new Map();
|
|
28
|
+
#remote_services = new Map;
|
|
26
29
|
#linked_nodes = new Map();
|
|
27
|
-
#rpc_queue = new Map;
|
|
28
|
-
#rpc_response_queue = new Map;
|
|
29
30
|
#public_ip = new Promise(async (s) => {
|
|
30
31
|
for (let i = 1; i <= 5; i++) {
|
|
31
32
|
const ip = await new Promise(s => {
|
|
@@ -44,19 +45,258 @@ export class SpiderMesh {
|
|
|
44
45
|
});
|
|
45
46
|
$nodes_monitor = new Subject();
|
|
46
47
|
#initing;
|
|
48
|
+
#$isolated = new BehaviorSubject(false);
|
|
47
49
|
constructor(transporter = new BuiltinTransporter(NODE_ID, NAMEPSACE)) {
|
|
48
50
|
this.transporter = transporter;
|
|
49
51
|
this.#initing = this.#init();
|
|
50
52
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
#caculate_rpc_node_id(service_name, options = {}) {
|
|
54
|
+
const current = this.#remote_services.get(service_name);
|
|
55
|
+
if (!current || current.nodes.length == 0)
|
|
56
|
+
return;
|
|
57
|
+
if (options.$node_id) {
|
|
58
|
+
if (current.nodes.some(node => node.id == options.$node_id))
|
|
59
|
+
return options.$node_id;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const option_ip = options.$ip;
|
|
63
|
+
const nodes = option_ip ? (current
|
|
64
|
+
.nodes
|
|
65
|
+
.filter(node => node.public_ip == option_ip || node.ip_addresses.includes(option_ip))) : current.nodes;
|
|
66
|
+
if (nodes.length == 0)
|
|
67
|
+
return;
|
|
68
|
+
current.last_call_index = (current.last_call_index + 1) % nodes.length;
|
|
69
|
+
return nodes[current.last_call_index].id;
|
|
70
|
+
}
|
|
71
|
+
rpc(service, method, args, options = {}) {
|
|
72
|
+
const target_node_id = this.#caculate_rpc_node_id(service, options);
|
|
73
|
+
if (!target_node_id)
|
|
74
|
+
throw `SERVICE_NOT_RUNNING:${service}`;
|
|
75
|
+
const session_id = randomUUID();
|
|
76
|
+
const { buffer, observables } = Encoder.encode(args);
|
|
77
|
+
const req = {
|
|
78
|
+
channels: new Map(),
|
|
79
|
+
target_node_id,
|
|
80
|
+
observables,
|
|
81
|
+
subscriptions: new Map()
|
|
82
|
+
};
|
|
83
|
+
this.#requests.set(session_id, req);
|
|
84
|
+
this.publish(target_node_id, {
|
|
85
|
+
type: MessageType.RpcRequest,
|
|
86
|
+
session_id,
|
|
87
|
+
args: buffer,
|
|
88
|
+
method,
|
|
89
|
+
service,
|
|
90
|
+
}, target_node_id);
|
|
91
|
+
const o = new Subject();
|
|
92
|
+
req.channels.set(session_id, o);
|
|
93
|
+
const $ = o.pipe(first(), mergeMap(data => {
|
|
94
|
+
if (data instanceof Buffer) {
|
|
95
|
+
const [response, is_pure] = this.#decode_rpc_buffer(req, data, session_id);
|
|
96
|
+
if (is_pure) {
|
|
97
|
+
this.#requests.delete(session_id);
|
|
98
|
+
}
|
|
99
|
+
req.channels.delete(session_id);
|
|
100
|
+
return response instanceof Observable ? response : of(response);
|
|
101
|
+
}
|
|
102
|
+
return EMPTY;
|
|
103
|
+
}), catchError(err => {
|
|
104
|
+
if (options.$fallback) {
|
|
105
|
+
return of(options.$fallback);
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}), share());
|
|
109
|
+
$.subscribe();
|
|
110
|
+
return Object.assign($, {
|
|
111
|
+
then: async (success, error) => {
|
|
112
|
+
try {
|
|
113
|
+
const value = await firstValueFrom($);
|
|
114
|
+
success(options.$safe_mode ? [null, value] : value);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
options.$safe_mode ? success([e, null]) : error(e);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
async catch() {
|
|
121
|
+
return {};
|
|
122
|
+
},
|
|
123
|
+
async finally() {
|
|
124
|
+
return {};
|
|
125
|
+
},
|
|
126
|
+
[Symbol.toStringTag]: ''
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
#get_rpc_target(msg) {
|
|
130
|
+
if (msg.service == 'SpiderMesh') {
|
|
131
|
+
if (!msg.method.startsWith('$'))
|
|
132
|
+
throw `SPIDER_MESH_METHOD_NOT_ALLOW`;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
const service = this.#local_services.get(msg.service)?.instance;
|
|
136
|
+
if (!service)
|
|
137
|
+
throw 'RPC_SERVICE_NOT_FOUND';
|
|
138
|
+
if (typeof service[msg.method] != 'function')
|
|
139
|
+
throw 'RPC_METHOD_NOT_FOUND';
|
|
140
|
+
return service;
|
|
141
|
+
}
|
|
142
|
+
#decode_rpc_buffer(req, data, session_id) {
|
|
143
|
+
let is_pure = true;
|
|
144
|
+
const result = Encoder.decode(data, () => () => {
|
|
145
|
+
throw new Error('CAN_NOT_CALL_FUNCTION_IN_RPC');
|
|
146
|
+
}, observable_id => {
|
|
147
|
+
is_pure = false;
|
|
148
|
+
const tid = setTimeout(() => {
|
|
149
|
+
throw new Error('Orphant Observable detected !', {
|
|
150
|
+
cause: 'All Observable object need to subscribe within 2 second'
|
|
151
|
+
});
|
|
152
|
+
}, 2000);
|
|
153
|
+
return new Observable(o => {
|
|
154
|
+
clearTimeout(tid);
|
|
155
|
+
if (!this.#requests.has(session_id)) {
|
|
156
|
+
return o.next('RPC_OFFLINE');
|
|
157
|
+
}
|
|
158
|
+
const channel_id = randomUUID();
|
|
159
|
+
const s = new Subject();
|
|
160
|
+
req.channels.set(channel_id, s);
|
|
161
|
+
s.subscribe(o);
|
|
162
|
+
this.#subscribe_channel(req.target_node_id, session_id, observable_id, channel_id);
|
|
163
|
+
return () => {
|
|
164
|
+
this.#unsubcribe_channel(req.target_node_id, session_id, channel_id);
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
return [result, is_pure];
|
|
169
|
+
}
|
|
170
|
+
async #on_rpc(msg, sender_node_id) {
|
|
171
|
+
const instance = this.#get_rpc_target(msg);
|
|
172
|
+
this.#requests.set(msg.session_id, {
|
|
173
|
+
observables: new Map(),
|
|
174
|
+
target_node_id: sender_node_id,
|
|
175
|
+
subscriptions: new Map(),
|
|
176
|
+
channels: new Map()
|
|
177
|
+
});
|
|
178
|
+
const req = this.#requests.get(msg.session_id);
|
|
179
|
+
if (!req)
|
|
180
|
+
return;
|
|
181
|
+
const channel_id = msg.session_id;
|
|
182
|
+
try {
|
|
183
|
+
const [args] = this.#decode_rpc_buffer(req, msg.args, msg.session_id);
|
|
184
|
+
if (!args)
|
|
185
|
+
return this.#stream_error(sender_node_id, msg.session_id, channel_id, Buffer.from('INVAILD_ARGS'));
|
|
186
|
+
const result = await instance?.[msg.method]?.(...args);
|
|
187
|
+
const { buffer, observables } = Encoder.encode(await result);
|
|
188
|
+
for (const [observable_id, o] of observables)
|
|
189
|
+
req.observables.set(observable_id, o);
|
|
190
|
+
this.#stream_next(sender_node_id, msg.session_id, channel_id, buffer);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (error instanceof Error) {
|
|
194
|
+
this.#stream_error(sender_node_id, msg.session_id, channel_id, Encoder.encode(JSON.stringify(error)).buffer);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
#stream_next(node_id, session_id, channel_id, data) {
|
|
199
|
+
return this.publish(node_id, {
|
|
200
|
+
type: MessageType.RpcStream,
|
|
201
|
+
channel_id,
|
|
202
|
+
session_id,
|
|
203
|
+
data
|
|
204
|
+
}, node_id);
|
|
205
|
+
}
|
|
206
|
+
#stream_error(node_id, session_id, channel_id, error) {
|
|
207
|
+
return this.publish(node_id, {
|
|
208
|
+
type: MessageType.RpcStream,
|
|
209
|
+
channel_id,
|
|
210
|
+
session_id,
|
|
211
|
+
error
|
|
212
|
+
}, node_id);
|
|
213
|
+
}
|
|
214
|
+
#stream_complete(node_id, session_id, channel_id) {
|
|
215
|
+
return this.publish(node_id, {
|
|
216
|
+
type: MessageType.RpcStream,
|
|
217
|
+
channel_id,
|
|
218
|
+
completed: true,
|
|
219
|
+
session_id
|
|
220
|
+
}, node_id);
|
|
221
|
+
}
|
|
222
|
+
#subscribe_channel(node_id, session_id, observable_id, channel_id) {
|
|
223
|
+
return this.publish(node_id, {
|
|
224
|
+
type: MessageType.RpcSubscribeChannel,
|
|
225
|
+
channel_id,
|
|
226
|
+
session_id,
|
|
227
|
+
observable_id
|
|
228
|
+
}, node_id);
|
|
229
|
+
}
|
|
230
|
+
#on_subscribe_channel(msg, sender_node_id) {
|
|
231
|
+
const response = (e) => {
|
|
232
|
+
this.publish(sender_node_id, {
|
|
233
|
+
session_id: msg.session_id,
|
|
234
|
+
channel_id: msg.channel_id,
|
|
235
|
+
type: MessageType.RpcStream,
|
|
236
|
+
...e
|
|
237
|
+
}, sender_node_id);
|
|
238
|
+
};
|
|
239
|
+
const req = this.#requests.get(msg.session_id);
|
|
240
|
+
if (!req) {
|
|
241
|
+
response({ error: 'RPC_SESSION_EXPIRED' });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const o = req.observables.get(msg.observable_id);
|
|
245
|
+
if (!o)
|
|
246
|
+
return;
|
|
247
|
+
const subscription = o.pipe(finalize(() => {
|
|
248
|
+
if (req.subscriptions.size == 0 && req.channels.size == 0) {
|
|
249
|
+
this.#requests.delete(msg.session_id);
|
|
250
|
+
}
|
|
251
|
+
})).subscribe({
|
|
252
|
+
complete: () => this.#stream_complete(sender_node_id, msg.session_id, msg.channel_id),
|
|
253
|
+
error: error => this.#stream_error(sender_node_id, msg.session_id, msg.channel_id, error),
|
|
254
|
+
next: data => this.#stream_next(sender_node_id, msg.session_id, msg.channel_id, data)
|
|
255
|
+
});
|
|
256
|
+
req.subscriptions.set(msg.channel_id, subscription);
|
|
257
|
+
}
|
|
258
|
+
#unsubcribe_channel(node_id, session_id, channel_id) {
|
|
259
|
+
return this.publish(node_id, {
|
|
260
|
+
type: MessageType.RpcUnsubscribeChannel,
|
|
261
|
+
channel_id,
|
|
262
|
+
session_id
|
|
263
|
+
}, node_id);
|
|
264
|
+
}
|
|
265
|
+
#on_unsubscribe_channel(msg) {
|
|
266
|
+
const req = this.#requests.get(msg.session_id);
|
|
267
|
+
if (!req)
|
|
268
|
+
return;
|
|
269
|
+
req.subscriptions.get(msg.channel_id)?.unsubscribe();
|
|
270
|
+
req.subscriptions.delete(msg.channel_id);
|
|
271
|
+
if (req.channels.size == 0 && req.subscriptions.size == 0) {
|
|
272
|
+
this.#requests.delete(msg.session_id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
#on_stream(stream) {
|
|
276
|
+
const req = this.#requests.get(stream.session_id);
|
|
277
|
+
if (!req)
|
|
278
|
+
return;
|
|
279
|
+
const $ = req.channels.get(stream.channel_id);
|
|
280
|
+
if (!$)
|
|
281
|
+
return;
|
|
282
|
+
if (stream.data != undefined) {
|
|
283
|
+
$.next(stream.data);
|
|
284
|
+
}
|
|
285
|
+
if (stream.completed || stream.error) {
|
|
286
|
+
stream.error && $.error(stream.error);
|
|
287
|
+
stream.completed && $.complete();
|
|
288
|
+
req.channels.delete(stream.channel_id);
|
|
289
|
+
if (req.channels.size == 0 && req.subscriptions.size == 0) {
|
|
290
|
+
this.#requests.delete(stream.session_id);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
53
293
|
}
|
|
54
294
|
async $metadata() {
|
|
55
295
|
const ips = (Object.values(networkInterfaces())
|
|
56
296
|
.flat(2)
|
|
57
|
-
.filter(a => !a.internal)
|
|
58
|
-
.map(a => a
|
|
59
|
-
const services = [...this.#
|
|
297
|
+
.filter(a => a && !a.internal && a.address)
|
|
298
|
+
.map(a => a?.address));
|
|
299
|
+
const services = [...this.#local_services.entries()].reduce((p, [service_id, { metadata }]) => ({
|
|
60
300
|
...p,
|
|
61
301
|
[service_id]: metadata
|
|
62
302
|
}), {});
|
|
@@ -88,11 +328,7 @@ export class SpiderMesh {
|
|
|
88
328
|
}
|
|
89
329
|
async #self_introduce(node_id) {
|
|
90
330
|
const me = await this.$metadata();
|
|
91
|
-
this.
|
|
92
|
-
event: '#join',
|
|
93
|
-
data: [me],
|
|
94
|
-
node_id
|
|
95
|
-
});
|
|
331
|
+
await this.publish('#join', [me], node_id);
|
|
96
332
|
return me;
|
|
97
333
|
}
|
|
98
334
|
async $update_isolate_mode(active) {
|
|
@@ -100,143 +336,22 @@ export class SpiderMesh {
|
|
|
100
336
|
const me = await this.#self_introduce();
|
|
101
337
|
return me;
|
|
102
338
|
}
|
|
103
|
-
|
|
104
|
-
if (msg.type ==
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
event: sender_node_id,
|
|
113
|
-
node_id: sender_node_id
|
|
114
|
-
});
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
const instance = msg.service == 'SpiderMesh' ? this : this.#local_rpc_services.get(msg.service)?.instance;
|
|
118
|
-
if (!instance)
|
|
119
|
-
return this.transporter.publish({
|
|
120
|
-
data: [{
|
|
121
|
-
id: msg.id,
|
|
122
|
-
type: 'error',
|
|
123
|
-
error: 'SERVICE_NOT_FOUND'
|
|
124
|
-
}],
|
|
125
|
-
event: sender_node_id,
|
|
126
|
-
node_id: sender_node_id
|
|
127
|
-
});
|
|
128
|
-
if (typeof instance?.[msg.method] != 'function')
|
|
129
|
-
return this.transporter.publish({
|
|
130
|
-
data: [{
|
|
131
|
-
id: msg.id,
|
|
132
|
-
type: 'error',
|
|
133
|
-
error: 'SERVICE_METHOD_NOT_FOUND'
|
|
134
|
-
}],
|
|
135
|
-
event: sender_node_id,
|
|
136
|
-
node_id: sender_node_id
|
|
137
|
-
});
|
|
138
|
-
const args = msg.args.map(arg => arg != '__FUNCTION__' ? arg : (...args) => {
|
|
139
|
-
sender_node_id && this.transporter.publish({
|
|
140
|
-
event: sender_node_id,
|
|
141
|
-
node_id: sender_node_id,
|
|
142
|
-
data: [{ id: msg.id, type: 'callback', args }]
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
try {
|
|
146
|
-
const response = await instance?.[msg.method]?.(...args);
|
|
147
|
-
if (response && typeof response.subscribe == 'function') {
|
|
148
|
-
const $cancel = new Subject();
|
|
149
|
-
this.#rpc_response_queue.set(msg.id, { $cancel });
|
|
150
|
-
response
|
|
151
|
-
.pipe(takeUntil($cancel))
|
|
152
|
-
.subscribe({
|
|
153
|
-
complete: () => {
|
|
154
|
-
this.#rpc_response_queue.delete(msg.id);
|
|
155
|
-
sender_node_id && this.transporter.publish({
|
|
156
|
-
event: sender_node_id,
|
|
157
|
-
node_id: sender_node_id,
|
|
158
|
-
data: [{ id: msg.id, type: 'end' }]
|
|
159
|
-
});
|
|
160
|
-
},
|
|
161
|
-
error: (error) => {
|
|
162
|
-
this.#rpc_response_queue.delete(msg.id);
|
|
163
|
-
sender_node_id && this.transporter.publish({
|
|
164
|
-
event: sender_node_id,
|
|
165
|
-
node_id: sender_node_id,
|
|
166
|
-
data: [{ id: msg.id, type: 'error', error: error?.code || error?.message || error || 'UNKNOWN' }]
|
|
167
|
-
});
|
|
168
|
-
},
|
|
169
|
-
next: (response) => {
|
|
170
|
-
sender_node_id && this.transporter.publish({
|
|
171
|
-
event: sender_node_id,
|
|
172
|
-
node_id: sender_node_id,
|
|
173
|
-
data: [{ id: msg.id, type: 'next', response }]
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
sender_node_id && this.transporter.publish({
|
|
180
|
-
event: sender_node_id,
|
|
181
|
-
node_id: sender_node_id,
|
|
182
|
-
data: [{ id: msg.id, type: 'response', response }]
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
sender_node_id && this.transporter.publish({
|
|
188
|
-
event: sender_node_id,
|
|
189
|
-
node_id: sender_node_id,
|
|
190
|
-
data: [{ id: msg.id, type: 'error', error: error?.code || error?.message || error || 'UNKNOWN' }]
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (msg.type == 'rpc-cancel') {
|
|
196
|
-
const responding = this.#rpc_response_queue.get(msg.id);
|
|
197
|
-
responding?.$cancel.next();
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const request = this.#rpc_queue.get(msg.id);
|
|
201
|
-
if (!request)
|
|
202
|
-
return;
|
|
203
|
-
if (msg.type == 'callback') {
|
|
204
|
-
request.args?.[msg.index]?.();
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if (msg.type == 'next') {
|
|
208
|
-
request.$.next(msg.response);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (msg.type == 'ack') {
|
|
212
|
-
request.last_ping = Date.now();
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (msg.type == 'error') {
|
|
216
|
-
request.$.error(msg.error);
|
|
217
|
-
this.#rpc_queue.delete(msg.id);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (msg.type == 'response') {
|
|
221
|
-
request.$.next(msg.response);
|
|
222
|
-
request.$.complete();
|
|
223
|
-
this.#rpc_queue.delete(msg.id);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (msg.type == 'end') {
|
|
227
|
-
request.$.complete();
|
|
228
|
-
this.#rpc_queue.delete(msg.id);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
339
|
+
#node_event_handler({ data: msg, sender_node_id }) {
|
|
340
|
+
if (msg.type == MessageType.RpcRequest)
|
|
341
|
+
return this.#on_rpc(msg, sender_node_id);
|
|
342
|
+
if (msg.type == MessageType.RpcStream)
|
|
343
|
+
return this.#on_stream(msg);
|
|
344
|
+
if (msg.type == MessageType.RpcSubscribeChannel)
|
|
345
|
+
return this.#on_subscribe_channel(msg, sender_node_id);
|
|
346
|
+
if (msg.type == MessageType.RpcUnsubscribeChannel)
|
|
347
|
+
return this.#on_unsubscribe_channel(msg);
|
|
231
348
|
}
|
|
232
349
|
async #init() {
|
|
233
350
|
await this.transporter.start();
|
|
234
|
-
this.listen(this.transporter.node_id).subscribe(
|
|
351
|
+
this.listen(this.transporter.node_id).subscribe(msg => this.#node_event_handler(msg));
|
|
235
352
|
this.listen('#join')
|
|
236
|
-
.pipe(groupBy(node => node.sender_node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000))))
|
|
237
|
-
.subscribe(
|
|
238
|
-
this.#on_node_discovered(data);
|
|
239
|
-
});
|
|
353
|
+
.pipe(groupBy(node => node.sender_node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000))), mergeMap(({ data }) => this.#on_node_discovered(data), 1))
|
|
354
|
+
.subscribe();
|
|
240
355
|
this.transporter.$nodes_status.pipe(tap(node => !node.online && this.#on_node_offline(node.node_id)), groupBy(node => node.node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000))), filter(node => node.online), mergeMap(({ node_id }) => this.#self_introduce(node_id), 1))
|
|
241
356
|
.subscribe();
|
|
242
357
|
serviceInstanceList.pipe(filter(i => i.namespace == this.transporter.namespace), mergeMap(async ({ instance, metadata }) => {
|
|
@@ -257,106 +372,38 @@ export class SpiderMesh {
|
|
|
257
372
|
...node,
|
|
258
373
|
online: true
|
|
259
374
|
};
|
|
260
|
-
this.#linked_nodes.set(node.id, new_node);
|
|
261
375
|
if (!peer_updated)
|
|
262
|
-
|
|
376
|
+
await this.#self_introduce(node.id);
|
|
377
|
+
this.#linked_nodes.set(node.id, new_node);
|
|
263
378
|
for (const service_id of Object.keys(node.services)) {
|
|
264
|
-
if (!this.#
|
|
265
|
-
this.#
|
|
379
|
+
if (!this.#remote_services.has(service_id)) {
|
|
380
|
+
this.#remote_services.set(service_id, {
|
|
266
381
|
last_call_index: -1,
|
|
267
382
|
nodes: []
|
|
268
383
|
});
|
|
269
384
|
}
|
|
270
|
-
const $service = this.#
|
|
385
|
+
const $service = this.#remote_services.get(service_id);
|
|
271
386
|
const index = $service.nodes.findIndex(n => n.id == new_node.id);
|
|
272
387
|
index >= 0 ? ($service.nodes[index] = new_node) : $service.nodes.push(new_node);
|
|
273
|
-
this.$nodes_monitor.next(node);
|
|
274
388
|
}
|
|
389
|
+
this.$nodes_monitor.next(node);
|
|
275
390
|
}
|
|
276
391
|
#on_node_offline(id) {
|
|
277
|
-
for (const service of this.#
|
|
392
|
+
for (const service of this.#remote_services.values()) {
|
|
278
393
|
service.nodes = service.nodes.filter(node => node.id != id);
|
|
279
394
|
}
|
|
280
|
-
for (const [rid, { target_node_id,
|
|
395
|
+
for (const [rid, { target_node_id, channels, observables, subscriptions }] of this.#requests) {
|
|
281
396
|
if (target_node_id == id) {
|
|
282
|
-
|
|
283
|
-
|
|
397
|
+
for (const [_, $] of channels)
|
|
398
|
+
$.error(new Error('SERVICE_OFFLINE'));
|
|
399
|
+
subscriptions.forEach(s => s.unsubscribe());
|
|
400
|
+
this.#requests.delete(rid);
|
|
284
401
|
}
|
|
285
402
|
}
|
|
286
403
|
const node = this.#linked_nodes.get(id);
|
|
287
404
|
node && this.$nodes_monitor.next({ ...node, online: false });
|
|
288
405
|
this.#linked_nodes.delete(id);
|
|
289
406
|
}
|
|
290
|
-
#caculate_rpc_node_id(service_name, options = {}) {
|
|
291
|
-
const current = this.#remote_rpc_services.get(service_name);
|
|
292
|
-
if (!current || current.nodes.length == 0)
|
|
293
|
-
return;
|
|
294
|
-
if (options.$node_id) {
|
|
295
|
-
if (current.nodes.some(node => node.id == options.$node_id))
|
|
296
|
-
return options.$node_id;
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
const nodes = options.$ip ? (current
|
|
300
|
-
.nodes
|
|
301
|
-
.filter(node => node.public_ip == options.$ip || node.ip_addresses.includes(options.$ip))) : current.nodes;
|
|
302
|
-
if (nodes.length == 0)
|
|
303
|
-
return;
|
|
304
|
-
current.last_call_index = (current.last_call_index + 1) % nodes.length;
|
|
305
|
-
return nodes[current.last_call_index].id;
|
|
306
|
-
}
|
|
307
|
-
rpc(service, method, args, options) {
|
|
308
|
-
const rid = randomUUID();
|
|
309
|
-
const observable = new Observable(o => {
|
|
310
|
-
setTimeout(async () => {
|
|
311
|
-
await this.#initing;
|
|
312
|
-
const reject = (err) => {
|
|
313
|
-
if (options.$fallback != undefined) {
|
|
314
|
-
o.next(options.$fallback);
|
|
315
|
-
o.complete();
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
o.error(err);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
of(1).pipe(mergeMap(async () => {
|
|
322
|
-
const target_node_id = this.#caculate_rpc_node_id(service, options);
|
|
323
|
-
if (!target_node_id)
|
|
324
|
-
return reject(`SERVICE_NOT_RUNNING:${service}`);
|
|
325
|
-
!options.$nevermind && this.#rpc_queue.set(rid, {
|
|
326
|
-
args,
|
|
327
|
-
last_ping: 0,
|
|
328
|
-
$: o,
|
|
329
|
-
request_time: Date.now(),
|
|
330
|
-
timeout: options.$timeout,
|
|
331
|
-
target_node_id
|
|
332
|
-
});
|
|
333
|
-
await this.publish(service, { type: 'rpc', id: rid, args, method, service }, target_node_id);
|
|
334
|
-
}), retry(options.$retry || 1), catchError(() => {
|
|
335
|
-
reject('RETRY_EXECTED');
|
|
336
|
-
return EMPTY;
|
|
337
|
-
})).subscribe();
|
|
338
|
-
});
|
|
339
|
-
return () => {
|
|
340
|
-
const req = this.#rpc_queue.get(rid);
|
|
341
|
-
req?.target_node_id && this.publish(service, { type: 'rpc-cancel', id: rid, args: [], method, service }, req.target_node_id);
|
|
342
|
-
this.#rpc_queue.delete(rid);
|
|
343
|
-
};
|
|
344
|
-
});
|
|
345
|
-
const $timeout = !options.$timeout ? EMPTY : timer(options.$timeout).pipe(takeUntil(observable), map(() => {
|
|
346
|
-
throw new Error('SPIDER_MESH_RPC_TIMEOUT');
|
|
347
|
-
}));
|
|
348
|
-
return Object.assign(observable, {
|
|
349
|
-
then: async (success, error) => {
|
|
350
|
-
try {
|
|
351
|
-
const value = await firstValueFrom(merge(observable, $timeout));
|
|
352
|
-
success(options.$safe_mode ? [null, value] : value);
|
|
353
|
-
}
|
|
354
|
-
catch (e) {
|
|
355
|
-
options.$safe_mode ? success([e, null]) : error(e);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
407
|
async link_remote_service(factory, wait_service_online = false) {
|
|
361
408
|
const service_name = factory.name;
|
|
362
409
|
const omit_properties = new Set([
|
|
@@ -387,9 +434,6 @@ export class SpiderMesh {
|
|
|
387
434
|
typeof f[method] == 'function' && actions.add(method);
|
|
388
435
|
}
|
|
389
436
|
}
|
|
390
|
-
if (!this.#remote_rpc_services.has(service_name)) {
|
|
391
|
-
this.#remote_rpc_services.set(service_name, { last_call_index: -1, nodes: [] });
|
|
392
|
-
}
|
|
393
437
|
wait_service_online && await this.#wait_service_online(service_name);
|
|
394
438
|
return new Proxy({}, {
|
|
395
439
|
get: (_, method) => {
|
|
@@ -399,16 +443,24 @@ export class SpiderMesh {
|
|
|
399
443
|
if (method == '$watch')
|
|
400
444
|
return () => this.$nodes_monitor.pipe(filter(node => !!node.services[service_name]));
|
|
401
445
|
if (method == '$list_nodes') {
|
|
402
|
-
return (() => this.#
|
|
446
|
+
return (() => this.#remote_services.get(service_name)?.nodes.filter(node => !node.isolated).filter(node => node.online) || []);
|
|
403
447
|
}
|
|
404
448
|
if (method.startsWith('$batch_')) {
|
|
405
449
|
const real_method = method.split('$batch_')?.[1];
|
|
406
450
|
if (!real_method || !actions.has(real_method))
|
|
407
451
|
return null;
|
|
408
|
-
const nodes = this.#
|
|
452
|
+
const nodes = this.#remote_services.get(service_name)?.nodes.filter(node => !node.isolated) || [];
|
|
409
453
|
return (...args) => {
|
|
410
454
|
const o = new Subject();
|
|
411
|
-
from(nodes).pipe(mergeMap(async (node) =>
|
|
455
|
+
from(nodes).pipe(mergeMap(async (node) => {
|
|
456
|
+
try {
|
|
457
|
+
const data = await this.rpc(service_name, real_method, args, { $node_id: node.id });
|
|
458
|
+
return { node, data };
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
return { node, error };
|
|
462
|
+
}
|
|
463
|
+
})).subscribe(o);
|
|
412
464
|
return o;
|
|
413
465
|
};
|
|
414
466
|
}
|
|
@@ -423,16 +475,15 @@ export class SpiderMesh {
|
|
|
423
475
|
const $ = new Subject();
|
|
424
476
|
const $$ = publish_buffer_ms ? $.pipe(bufferTime(publish_buffer_ms), filter(l => l.length > 0)) : $;
|
|
425
477
|
$$.subscribe(data => this.publish(event_factory.name, data));
|
|
426
|
-
|
|
478
|
+
return {
|
|
427
479
|
publish: async (data) => $.next(data),
|
|
428
|
-
listen: () => this.listen(event_factory
|
|
480
|
+
listen: () => this.listen(event_factory)
|
|
429
481
|
};
|
|
430
|
-
return event_hub;
|
|
431
482
|
}
|
|
432
483
|
async #active_local_service(instance, metadata) {
|
|
433
484
|
const prototype = Object.getPrototypeOf(instance);
|
|
434
485
|
const name = prototype.constructor.name;
|
|
435
|
-
this.#
|
|
486
|
+
this.#local_services.set(name, { instance, metadata });
|
|
436
487
|
this.listen(name).subscribe(evt => this.#node_event_handler(evt));
|
|
437
488
|
const event_subscribers = listEventSubscribers(prototype);
|
|
438
489
|
for (const { event, method, buffer_ms } of event_subscribers) {
|
|
@@ -442,13 +493,18 @@ export class SpiderMesh {
|
|
|
442
493
|
}
|
|
443
494
|
}
|
|
444
495
|
async #wait_service_online(service_name = 'all') {
|
|
445
|
-
if (this.#remote_rpc_services.size == 0)
|
|
446
|
-
return;
|
|
447
496
|
while (true) {
|
|
448
|
-
await sleep(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
497
|
+
await sleep(1000);
|
|
498
|
+
if (service_name == 'all') {
|
|
499
|
+
if ([...this.#remote_services.values()].every(e => e.nodes.length > 0)) {
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
const nodes = this.#remote_services.get(service_name)?.nodes || [];
|
|
505
|
+
if (nodes.length > 0)
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
452
508
|
}
|
|
453
509
|
}
|
|
454
510
|
async #active_ready_hooks(instance) {
|
|
@@ -461,15 +517,20 @@ export class SpiderMesh {
|
|
|
461
517
|
await this.#initing;
|
|
462
518
|
const event = typeof topic == 'string' ? topic : topic.name;
|
|
463
519
|
const data = Array.isArray(payload) ? payload : [payload];
|
|
464
|
-
this.transporter.publish({
|
|
520
|
+
this.transporter.publish({
|
|
521
|
+
event,
|
|
522
|
+
data,
|
|
523
|
+
node_id
|
|
524
|
+
});
|
|
465
525
|
}
|
|
466
526
|
listen(topic) {
|
|
467
527
|
const topic_name = typeof topic == 'string' ? topic : topic.name;
|
|
468
528
|
return this.transporter.listen(topic_name).pipe(map(e => {
|
|
469
|
-
|
|
529
|
+
const data = e.data;
|
|
530
|
+
if (!Array.isArray(data))
|
|
470
531
|
return [];
|
|
471
|
-
return
|
|
472
|
-
data,
|
|
532
|
+
return data.map(data => ({
|
|
533
|
+
data: data,
|
|
473
534
|
sender_node_id: e.sender_node_id
|
|
474
535
|
}));
|
|
475
536
|
}), mergeAll());
|