@spider-mesh/core 1.0.119 → 1.0.121
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 +4 -9
- package/build/src/Encoder.js +3 -35
- package/build/src/Encoder.js.map +1 -1
- package/build/src/SpiderMesh.d.ts +41 -45
- package/build/src/SpiderMesh.js +215 -273
- package/build/src/SpiderMesh.js.map +1 -1
- package/build/src/builtin-transporter/BuiltinTransporter.d.ts +5 -10
- package/build/src/builtin-transporter/BuiltinTransporter.js +85 -94
- package/build/src/builtin-transporter/BuiltinTransporter.js.map +1 -1
- package/build/src/builtin-transporter/RxjsTcpServer.d.ts +4 -9
- package/build/src/builtin-transporter/RxjsTcpServer.js +25 -25
- package/build/src/builtin-transporter/RxjsTcpServer.js.map +1 -1
- package/build/src/builtin-transporter/RxjsTcpSocket.d.ts +1 -0
- package/build/src/builtin-transporter/RxjsTcpSocket.js +3 -0
- package/build/src/builtin-transporter/RxjsTcpSocket.js.map +1 -1
- package/build/src/builtin-transporter/RxjsUdpServer.d.ts +20 -0
- package/build/src/builtin-transporter/RxjsUdpServer.js +83 -0
- package/build/src/builtin-transporter/RxjsUdpServer.js.map +1 -0
- package/build/src/const.d.ts +1 -0
- package/build/src/const.js +1 -0
- package/build/src/const.js.map +1 -1
- package/build/src/decorators/Microservice.d.ts +2 -0
- package/build/src/interfaces/SpiderMeshNode.d.ts +3 -1
- package/build/src/interfaces/SpiderMeshTransporter.d.ts +13 -12
- package/build/tests/Rpc.d.ts +1 -0
- package/build/tests/Rpc.js +102 -0
- package/build/tests/Rpc.js.map +1 -0
- package/package.json +1 -1
- package/tsconfig.json +1 -1
- package/build/src/builtin-transporter/RxjsUdpBroadcaster.d.ts +0 -19
- package/build/src/builtin-transporter/RxjsUdpBroadcaster.js +0 -56
- package/build/src/builtin-transporter/RxjsUdpBroadcaster.js.map +0 -1
package/build/src/SpiderMesh.js
CHANGED
|
@@ -6,23 +6,15 @@ 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,
|
|
9
|
+
import { BehaviorSubject, EMPTY, Observable, Subject, bufferTime, catchError, debounceTime, filter, firstValueFrom, from, groupBy, map, merge, mergeAll, mergeMap, of, share, tap, timer } 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
|
+
import { NAMEPSACE } from './const.js';
|
|
13
14
|
import { BuiltinTransporter } from './builtin-transporter/BuiltinTransporter.js';
|
|
14
|
-
import { NAMEPSACE, NODE_ID } from './const.js';
|
|
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 = {}));
|
|
23
15
|
const PACKAGE_JSON = JSON.parse(readFileSync(`package.json`, 'utf8')) || {};
|
|
24
16
|
export class SpiderMesh {
|
|
25
|
-
|
|
17
|
+
#node_id = randomUUID();
|
|
26
18
|
#requests = new Map;
|
|
27
19
|
#local_services = new Map();
|
|
28
20
|
#remote_services = new Map;
|
|
@@ -44,18 +36,20 @@ export class SpiderMesh {
|
|
|
44
36
|
return s(null);
|
|
45
37
|
});
|
|
46
38
|
$nodes_monitor = new Subject();
|
|
47
|
-
#initing;
|
|
48
39
|
#$isolated = new BehaviorSubject(false);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
#transporters = new Map();
|
|
41
|
+
constructor(using_default_transporter = true) {
|
|
42
|
+
if (using_default_transporter) {
|
|
43
|
+
const transporter = new BuiltinTransporter();
|
|
44
|
+
this.link_transporter(transporter);
|
|
45
|
+
}
|
|
52
46
|
}
|
|
53
47
|
#caculate_rpc_node_id(service_name, options = {}) {
|
|
54
48
|
const current = this.#remote_services.get(service_name);
|
|
55
49
|
if (!current || current.nodes.length == 0)
|
|
56
50
|
return;
|
|
57
51
|
if (options.$node_id) {
|
|
58
|
-
if (current.nodes.some(node => node.
|
|
52
|
+
if (current.nodes.some(node => node.node_id == options.$node_id))
|
|
59
53
|
return options.$node_id;
|
|
60
54
|
return;
|
|
61
55
|
}
|
|
@@ -66,47 +60,37 @@ export class SpiderMesh {
|
|
|
66
60
|
if (nodes.length == 0)
|
|
67
61
|
return;
|
|
68
62
|
current.last_call_index = (current.last_call_index + 1) % nodes.length;
|
|
69
|
-
return nodes[current.last_call_index].
|
|
63
|
+
return nodes[current.last_call_index].node_id;
|
|
70
64
|
}
|
|
71
65
|
rpc(service, method, args, options = {}) {
|
|
72
|
-
const
|
|
73
|
-
if (!
|
|
66
|
+
const rpc_node_id = this.#caculate_rpc_node_id(service, options);
|
|
67
|
+
if (!rpc_node_id)
|
|
74
68
|
throw `SERVICE_NOT_RUNNING:${service}`;
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
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 => {
|
|
69
|
+
const request_id = randomUUID();
|
|
70
|
+
const $response = new Subject();
|
|
71
|
+
this.#requests.set(request_id, { $response, rpc_node_id });
|
|
72
|
+
const $ = merge(!options.$timeout ? EMPTY : timer(options.$timeout).pipe(map(() => { throw new Error('TIMEOUT'); })), $response).pipe(catchError(err => {
|
|
104
73
|
if (options.$fallback) {
|
|
105
74
|
return of(options.$fallback);
|
|
106
75
|
}
|
|
107
76
|
throw err;
|
|
108
77
|
}), share());
|
|
109
|
-
$.subscribe(
|
|
78
|
+
$.subscribe({
|
|
79
|
+
complete: () => { },
|
|
80
|
+
error: () => { },
|
|
81
|
+
next: () => { }
|
|
82
|
+
});
|
|
83
|
+
this.publish({
|
|
84
|
+
topic: rpc_node_id,
|
|
85
|
+
payload: {
|
|
86
|
+
request: {
|
|
87
|
+
args,
|
|
88
|
+
method,
|
|
89
|
+
request_id,
|
|
90
|
+
service
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
110
94
|
return Object.assign($, {
|
|
111
95
|
then: async (success, error) => {
|
|
112
96
|
try {
|
|
@@ -126,171 +110,6 @@ export class SpiderMesh {
|
|
|
126
110
|
[Symbol.toStringTag]: ''
|
|
127
111
|
});
|
|
128
112
|
}
|
|
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.error('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 response({ error: 'RPC_SESSION_NOT_FOUND' });
|
|
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
|
-
}
|
|
293
|
-
}
|
|
294
113
|
async $metadata() {
|
|
295
114
|
const ips = (Object.values(networkInterfaces())
|
|
296
115
|
.flat(2)
|
|
@@ -303,10 +122,10 @@ export class SpiderMesh {
|
|
|
303
122
|
const isolated_nodes = [
|
|
304
123
|
...new Set([...this.#linked_nodes.values()]
|
|
305
124
|
.filter(node => node.isolated)
|
|
306
|
-
.map(node => node.
|
|
125
|
+
.map(node => node.local_transporter_id))
|
|
307
126
|
];
|
|
308
127
|
const metadata = {
|
|
309
|
-
|
|
128
|
+
node_id: this.#node_id,
|
|
310
129
|
name: PACKAGE_JSON?.name || 'UNKNOWN',
|
|
311
130
|
version: PACKAGE_JSON?.version || '1.0.0',
|
|
312
131
|
path: process.cwd(),
|
|
@@ -319,62 +138,142 @@ export class SpiderMesh {
|
|
|
319
138
|
last_online: Date.now(),
|
|
320
139
|
online: true,
|
|
321
140
|
services,
|
|
322
|
-
namespace:
|
|
141
|
+
namespace: NAMEPSACE,
|
|
323
142
|
linked: [...this.#linked_nodes.keys()],
|
|
324
143
|
isolated_nodes,
|
|
325
144
|
isolated: this.#$isolated.value,
|
|
326
145
|
};
|
|
327
146
|
return metadata;
|
|
328
147
|
}
|
|
329
|
-
async #self_introduce(
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
148
|
+
async #self_introduce(local_transporter_id, remote_transporter_id) {
|
|
149
|
+
const list = local_transporter_id ? [this.#transporters.get(local_transporter_id)] : [...this.#transporters.values()];
|
|
150
|
+
for (const target of list) {
|
|
151
|
+
if (!target)
|
|
152
|
+
continue;
|
|
153
|
+
const me = {
|
|
154
|
+
...await this.$metadata(),
|
|
155
|
+
local_transporter_id: target.transporter.transporter_id,
|
|
156
|
+
remote_transporter_id: '#'
|
|
157
|
+
};
|
|
158
|
+
await this.publish({
|
|
159
|
+
topic: '#join',
|
|
160
|
+
payload: me,
|
|
161
|
+
local_transporter_id
|
|
162
|
+
});
|
|
163
|
+
}
|
|
333
164
|
}
|
|
334
165
|
async $update_isolate_mode(active) {
|
|
335
166
|
this.#$isolated.next(active);
|
|
336
167
|
const me = await this.#self_introduce();
|
|
337
168
|
return me;
|
|
338
169
|
}
|
|
339
|
-
#
|
|
340
|
-
if (msg.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return this
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (
|
|
347
|
-
|
|
170
|
+
#get_rpc_target(msg) {
|
|
171
|
+
if (msg.service == 'SpiderMesh') {
|
|
172
|
+
if (!msg.method.startsWith('$'))
|
|
173
|
+
throw `SPIDER_MESH_METHOD_NOT_ALLOW`;
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
const service = this.#local_services.get(msg.service)?.instance;
|
|
177
|
+
if (!service)
|
|
178
|
+
throw 'RPC_SERVICE_NOT_FOUND';
|
|
179
|
+
if (typeof service[msg.method] != 'function')
|
|
180
|
+
throw 'RPC_METHOD_NOT_FOUND';
|
|
181
|
+
return service;
|
|
348
182
|
}
|
|
349
|
-
async #
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
183
|
+
async #node_event_handler({ data: event, sender_node_id }) {
|
|
184
|
+
if (event.request) {
|
|
185
|
+
const response = (data) => {
|
|
186
|
+
const error = data.error ? (data.error instanceof Error ? {
|
|
187
|
+
cause: data.error.cause,
|
|
188
|
+
message: data.error.message,
|
|
189
|
+
name: data.error.name,
|
|
190
|
+
stack: data.error.stack
|
|
191
|
+
} : data.error) : undefined;
|
|
192
|
+
return this.publish({
|
|
193
|
+
topic: sender_node_id,
|
|
194
|
+
payload: {
|
|
195
|
+
response: {
|
|
196
|
+
request_id: event.request.request_id,
|
|
197
|
+
...data,
|
|
198
|
+
error
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
try {
|
|
204
|
+
const instance = this.#get_rpc_target(event.request);
|
|
205
|
+
try {
|
|
206
|
+
const value = await instance?.[event.request.method]?.(...event.request.args || []);
|
|
207
|
+
if (value instanceof Observable) {
|
|
208
|
+
value.subscribe({
|
|
209
|
+
complete: () => response({ completed: true }),
|
|
210
|
+
error: error => response({ error }),
|
|
211
|
+
next: value => response({ data: { value } })
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
response({ completed: true, data: { value } });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
response({ error: error });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
response({ error: error });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (event.response) {
|
|
228
|
+
const request = this.#requests.get(event.response.request_id);
|
|
229
|
+
if (!request)
|
|
230
|
+
return;
|
|
231
|
+
if (event.response.data) {
|
|
232
|
+
request.$response.next(event.response.data.value);
|
|
233
|
+
}
|
|
234
|
+
if (event.response.completed) {
|
|
235
|
+
request.$response.complete();
|
|
236
|
+
}
|
|
237
|
+
if (event.response.error) {
|
|
238
|
+
request.$response.error(event.response.error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async link_transporter(transporter) {
|
|
243
|
+
this.#transporters.set(transporter.transporter_id, {
|
|
244
|
+
transporter,
|
|
245
|
+
nodes: new Map()
|
|
246
|
+
});
|
|
247
|
+
this.listen(this.#node_id).subscribe(msg => this.#node_event_handler(msg));
|
|
248
|
+
this.listen('#join').pipe(filter(node => node.sender_node_id != this.#node_id), groupBy(node => node.sender_node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000)).pipe(map(({ data: node, received_transporter_id, sender_transporter_id }) => ({
|
|
249
|
+
...node,
|
|
250
|
+
local_transporter_id: received_transporter_id,
|
|
251
|
+
remote_transporter_id: sender_transporter_id,
|
|
252
|
+
})), mergeMap(node => this.#on_node_discovered(node), 1)))).subscribe();
|
|
253
|
+
transporter.$nodes_status.pipe(tap(node => !node.online && this.#on_node_offline(node)), groupBy(node => node.local_transporter_id), mergeMap(grouped => grouped.pipe(debounceTime(1000)).pipe(map(node => node), filter(node => node.online), mergeMap(node => this.#self_introduce(transporter.transporter_id, node.remote_transporter_id), 1))))
|
|
356
254
|
.subscribe();
|
|
357
|
-
serviceInstanceList.pipe(
|
|
255
|
+
serviceInstanceList.pipe(mergeMap(async ({ instance, metadata }) => {
|
|
358
256
|
await this.#active_local_service(instance, metadata);
|
|
359
257
|
await this.#active_ready_hooks(instance);
|
|
360
|
-
}), debounceTime(1000), mergeMap(() =>
|
|
258
|
+
}), debounceTime(1000), mergeMap(async () => {
|
|
259
|
+
const me = await this.$metadata();
|
|
260
|
+
})).subscribe();
|
|
361
261
|
}
|
|
362
262
|
async #on_node_discovered(node) {
|
|
363
|
-
if (node.
|
|
364
|
-
return;
|
|
365
|
-
if (this.#$isolated.value)
|
|
263
|
+
if (node.node_id == this.#node_id)
|
|
366
264
|
return;
|
|
367
|
-
const saved_node = this.#linked_nodes.get(node.
|
|
265
|
+
const saved_node = this.#linked_nodes.get(node.node_id);
|
|
368
266
|
if (saved_node && saved_node.last_online > node.last_online)
|
|
369
267
|
return;
|
|
370
|
-
const peer_updated = node.linked.includes(this
|
|
268
|
+
const peer_updated = node.linked.includes(this.#node_id);
|
|
371
269
|
const new_node = {
|
|
372
270
|
...node,
|
|
373
271
|
online: true
|
|
374
272
|
};
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
this
|
|
273
|
+
this.#linked_nodes.set(node.node_id, new_node);
|
|
274
|
+
this.#transporters.get(node.local_transporter_id)?.nodes.set(node.remote_transporter_id, node.node_id);
|
|
275
|
+
if (!peer_updated && !this.#$isolated.value)
|
|
276
|
+
await this.#self_introduce(node.local_transporter_id, node.remote_transporter_id);
|
|
378
277
|
for (const service_id of Object.keys(node.services)) {
|
|
379
278
|
if (!this.#remote_services.has(service_id)) {
|
|
380
279
|
this.#remote_services.set(service_id, {
|
|
@@ -383,26 +282,29 @@ export class SpiderMesh {
|
|
|
383
282
|
});
|
|
384
283
|
}
|
|
385
284
|
const $service = this.#remote_services.get(service_id);
|
|
386
|
-
const index = $service.nodes.findIndex(n => n.
|
|
285
|
+
const index = $service.nodes.findIndex(n => n.node_id == new_node.node_id);
|
|
387
286
|
index >= 0 ? ($service.nodes[index] = new_node) : $service.nodes.push(new_node);
|
|
388
287
|
}
|
|
389
288
|
this.$nodes_monitor.next(node);
|
|
390
289
|
}
|
|
391
|
-
#on_node_offline(
|
|
290
|
+
#on_node_offline({ local_transporter_id, remote_transporter_id, }) {
|
|
291
|
+
const node_id = this.#transporters.get(local_transporter_id)?.nodes?.get(remote_transporter_id);
|
|
292
|
+
if (!node_id)
|
|
293
|
+
return;
|
|
294
|
+
const node = this.#linked_nodes.get(node_id);
|
|
295
|
+
if (!node)
|
|
296
|
+
return;
|
|
392
297
|
for (const service of this.#remote_services.values()) {
|
|
393
|
-
service.nodes = service.nodes.filter(node => node.
|
|
298
|
+
service.nodes = service.nodes.filter(node => node.remote_transporter_id != remote_transporter_id);
|
|
394
299
|
}
|
|
395
|
-
for (const [rid, {
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
$.error(new Error('SERVICE_OFFLINE'));
|
|
399
|
-
subscriptions.forEach(s => s.unsubscribe());
|
|
300
|
+
for (const [rid, { $response, rpc_node_id }] of this.#requests) {
|
|
301
|
+
if (rpc_node_id == node_id) {
|
|
302
|
+
$response.error('OFFLINE');
|
|
400
303
|
this.#requests.delete(rid);
|
|
401
304
|
}
|
|
402
305
|
}
|
|
403
|
-
const node = this.#linked_nodes.get(id);
|
|
404
306
|
node && this.$nodes_monitor.next({ ...node, online: false });
|
|
405
|
-
this.#linked_nodes.delete(
|
|
307
|
+
this.#linked_nodes.delete(node_id);
|
|
406
308
|
}
|
|
407
309
|
async link_remote_service(factory, wait_service_online = false) {
|
|
408
310
|
const service_name = factory.name;
|
|
@@ -454,7 +356,7 @@ export class SpiderMesh {
|
|
|
454
356
|
const o = new Subject();
|
|
455
357
|
from(nodes).pipe(mergeMap(async (node) => {
|
|
456
358
|
try {
|
|
457
|
-
const data = await this.rpc(service_name, real_method, args, { $node_id: node.
|
|
359
|
+
const data = await this.rpc(service_name, real_method, args, { $node_id: node.node_id });
|
|
458
360
|
return { node, data };
|
|
459
361
|
}
|
|
460
362
|
catch (error) {
|
|
@@ -474,7 +376,10 @@ export class SpiderMesh {
|
|
|
474
376
|
async link_event(event_factory, publish_buffer_ms) {
|
|
475
377
|
const $ = new Subject();
|
|
476
378
|
const $$ = publish_buffer_ms ? $.pipe(bufferTime(publish_buffer_ms), filter(l => l.length > 0)) : $;
|
|
477
|
-
$$.subscribe(data => this.publish(
|
|
379
|
+
$$.subscribe(data => this.publish({
|
|
380
|
+
payload: data,
|
|
381
|
+
topic: event_factory.name
|
|
382
|
+
}));
|
|
478
383
|
return {
|
|
479
384
|
publish: async (data) => $.next(data),
|
|
480
385
|
listen: () => this.listen(event_factory)
|
|
@@ -513,26 +418,63 @@ export class SpiderMesh {
|
|
|
513
418
|
instance[method]?.(this);
|
|
514
419
|
}
|
|
515
420
|
}
|
|
516
|
-
async
|
|
517
|
-
await this.#initing;
|
|
421
|
+
async batch_publish({ payload, topic, node_id, local_transporter_id }) {
|
|
518
422
|
const event = typeof topic == 'string' ? topic : topic.name;
|
|
519
|
-
const data =
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
423
|
+
const data = { payload, node_id: this.#node_id };
|
|
424
|
+
if (node_id) {
|
|
425
|
+
const node = this.#linked_nodes.get(node_id);
|
|
426
|
+
if (!node)
|
|
427
|
+
return;
|
|
428
|
+
const target = this.#transporters.get(node.local_transporter_id);
|
|
429
|
+
if (!target)
|
|
430
|
+
return;
|
|
431
|
+
target.transporter.publish({
|
|
432
|
+
event,
|
|
433
|
+
remote_transporter_id: node.remote_transporter_id,
|
|
434
|
+
data
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (local_transporter_id) {
|
|
439
|
+
const target = this.#transporters.get(local_transporter_id);
|
|
440
|
+
target?.transporter?.publish({
|
|
441
|
+
event,
|
|
442
|
+
data
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
for (const { transporter } of this.#transporters.values()) {
|
|
447
|
+
transporter.publish({
|
|
448
|
+
event,
|
|
449
|
+
data
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
publish({ payload, topic, node_id, local_transporter_id }) {
|
|
454
|
+
return this.batch_publish({
|
|
455
|
+
node_id,
|
|
456
|
+
payload: [payload],
|
|
457
|
+
topic,
|
|
458
|
+
local_transporter_id
|
|
524
459
|
});
|
|
525
460
|
}
|
|
526
461
|
listen(topic) {
|
|
527
462
|
const topic_name = typeof topic == 'string' ? topic : topic.name;
|
|
528
|
-
return this.transporter.listen(topic_name).pipe(map(
|
|
529
|
-
const
|
|
530
|
-
|
|
463
|
+
return merge(...[...this.#transporters.values()].map(t => t.transporter.listen(topic_name))).pipe(map(({ data, received_transporter_id, sender_transporter_id }) => {
|
|
464
|
+
const items = data;
|
|
465
|
+
try {
|
|
466
|
+
return items.payload.map(data => {
|
|
467
|
+
return {
|
|
468
|
+
data,
|
|
469
|
+
received_transporter_id,
|
|
470
|
+
sender_transporter_id,
|
|
471
|
+
sender_node_id: items.node_id
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
catch (e) {
|
|
531
476
|
return [];
|
|
532
|
-
|
|
533
|
-
data: data,
|
|
534
|
-
sender_node_id: e.sender_node_id
|
|
535
|
-
}));
|
|
477
|
+
}
|
|
536
478
|
}), mergeAll());
|
|
537
479
|
}
|
|
538
480
|
}
|