@spider-mesh/core 1.0.116 → 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 +309 -244
- 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
|
-
await 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,138 +336,19 @@ 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
353
|
.pipe(groupBy(node => node.sender_node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000))), mergeMap(({ data }) => this.#on_node_discovered(data), 1))
|
|
237
354
|
.subscribe();
|
|
@@ -259,102 +376,34 @@ export class SpiderMesh {
|
|
|
259
376
|
await this.#self_introduce(node.id);
|
|
260
377
|
this.#linked_nodes.set(node.id, new_node);
|
|
261
378
|
for (const service_id of Object.keys(node.services)) {
|
|
262
|
-
if (!this.#
|
|
263
|
-
this.#
|
|
379
|
+
if (!this.#remote_services.has(service_id)) {
|
|
380
|
+
this.#remote_services.set(service_id, {
|
|
264
381
|
last_call_index: -1,
|
|
265
382
|
nodes: []
|
|
266
383
|
});
|
|
267
384
|
}
|
|
268
|
-
const $service = this.#
|
|
385
|
+
const $service = this.#remote_services.get(service_id);
|
|
269
386
|
const index = $service.nodes.findIndex(n => n.id == new_node.id);
|
|
270
387
|
index >= 0 ? ($service.nodes[index] = new_node) : $service.nodes.push(new_node);
|
|
271
388
|
}
|
|
272
389
|
this.$nodes_monitor.next(node);
|
|
273
390
|
}
|
|
274
391
|
#on_node_offline(id) {
|
|
275
|
-
for (const service of this.#
|
|
392
|
+
for (const service of this.#remote_services.values()) {
|
|
276
393
|
service.nodes = service.nodes.filter(node => node.id != id);
|
|
277
394
|
}
|
|
278
|
-
for (const [rid, { target_node_id,
|
|
395
|
+
for (const [rid, { target_node_id, channels, observables, subscriptions }] of this.#requests) {
|
|
279
396
|
if (target_node_id == id) {
|
|
280
|
-
|
|
281
|
-
|
|
397
|
+
for (const [_, $] of channels)
|
|
398
|
+
$.error(new Error('SERVICE_OFFLINE'));
|
|
399
|
+
subscriptions.forEach(s => s.unsubscribe());
|
|
400
|
+
this.#requests.delete(rid);
|
|
282
401
|
}
|
|
283
402
|
}
|
|
284
403
|
const node = this.#linked_nodes.get(id);
|
|
285
404
|
node && this.$nodes_monitor.next({ ...node, online: false });
|
|
286
405
|
this.#linked_nodes.delete(id);
|
|
287
406
|
}
|
|
288
|
-
#caculate_rpc_node_id(service_name, options = {}) {
|
|
289
|
-
const current = this.#remote_rpc_services.get(service_name);
|
|
290
|
-
if (!current || current.nodes.length == 0)
|
|
291
|
-
return;
|
|
292
|
-
if (options.$node_id) {
|
|
293
|
-
if (current.nodes.some(node => node.id == options.$node_id))
|
|
294
|
-
return options.$node_id;
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
const nodes = options.$ip ? (current
|
|
298
|
-
.nodes
|
|
299
|
-
.filter(node => node.public_ip == options.$ip || node.ip_addresses.includes(options.$ip))) : current.nodes;
|
|
300
|
-
if (nodes.length == 0)
|
|
301
|
-
return;
|
|
302
|
-
current.last_call_index = (current.last_call_index + 1) % nodes.length;
|
|
303
|
-
return nodes[current.last_call_index].id;
|
|
304
|
-
}
|
|
305
|
-
rpc(service, method, args, options) {
|
|
306
|
-
const rid = randomUUID();
|
|
307
|
-
const observable = new Observable(o => {
|
|
308
|
-
setTimeout(async () => {
|
|
309
|
-
await this.#initing;
|
|
310
|
-
const reject = (err) => {
|
|
311
|
-
if (options.$fallback != undefined) {
|
|
312
|
-
o.next(options.$fallback);
|
|
313
|
-
o.complete();
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
o.error(err);
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
of(1).pipe(mergeMap(async () => {
|
|
320
|
-
const target_node_id = this.#caculate_rpc_node_id(service, options);
|
|
321
|
-
if (!target_node_id)
|
|
322
|
-
return reject(`SERVICE_NOT_RUNNING:${service}`);
|
|
323
|
-
!options.$nevermind && this.#rpc_queue.set(rid, {
|
|
324
|
-
args,
|
|
325
|
-
last_ping: 0,
|
|
326
|
-
$: o,
|
|
327
|
-
request_time: Date.now(),
|
|
328
|
-
timeout: options.$timeout,
|
|
329
|
-
target_node_id
|
|
330
|
-
});
|
|
331
|
-
await this.publish(service, { type: 'rpc', id: rid, args, method, service }, target_node_id);
|
|
332
|
-
}), retry(options.$retry || 1), catchError(() => {
|
|
333
|
-
reject('RETRY_EXECTED');
|
|
334
|
-
return EMPTY;
|
|
335
|
-
})).subscribe();
|
|
336
|
-
});
|
|
337
|
-
return () => {
|
|
338
|
-
const req = this.#rpc_queue.get(rid);
|
|
339
|
-
req?.target_node_id && this.publish(service, { type: 'rpc-cancel', id: rid, args: [], method, service }, req.target_node_id);
|
|
340
|
-
this.#rpc_queue.delete(rid);
|
|
341
|
-
};
|
|
342
|
-
});
|
|
343
|
-
const $timeout = !options.$timeout ? EMPTY : timer(options.$timeout).pipe(takeUntil(observable), map(() => {
|
|
344
|
-
throw new Error('SPIDER_MESH_RPC_TIMEOUT');
|
|
345
|
-
}));
|
|
346
|
-
return Object.assign(observable, {
|
|
347
|
-
then: async (success, error) => {
|
|
348
|
-
try {
|
|
349
|
-
const value = await firstValueFrom(merge(observable, $timeout));
|
|
350
|
-
success(options.$safe_mode ? [null, value] : value);
|
|
351
|
-
}
|
|
352
|
-
catch (e) {
|
|
353
|
-
options.$safe_mode ? success([e, null]) : error(e);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
407
|
async link_remote_service(factory, wait_service_online = false) {
|
|
359
408
|
const service_name = factory.name;
|
|
360
409
|
const omit_properties = new Set([
|
|
@@ -385,9 +434,6 @@ export class SpiderMesh {
|
|
|
385
434
|
typeof f[method] == 'function' && actions.add(method);
|
|
386
435
|
}
|
|
387
436
|
}
|
|
388
|
-
if (!this.#remote_rpc_services.has(service_name)) {
|
|
389
|
-
this.#remote_rpc_services.set(service_name, { last_call_index: -1, nodes: [] });
|
|
390
|
-
}
|
|
391
437
|
wait_service_online && await this.#wait_service_online(service_name);
|
|
392
438
|
return new Proxy({}, {
|
|
393
439
|
get: (_, method) => {
|
|
@@ -397,16 +443,24 @@ export class SpiderMesh {
|
|
|
397
443
|
if (method == '$watch')
|
|
398
444
|
return () => this.$nodes_monitor.pipe(filter(node => !!node.services[service_name]));
|
|
399
445
|
if (method == '$list_nodes') {
|
|
400
|
-
return (() => this.#
|
|
446
|
+
return (() => this.#remote_services.get(service_name)?.nodes.filter(node => !node.isolated).filter(node => node.online) || []);
|
|
401
447
|
}
|
|
402
448
|
if (method.startsWith('$batch_')) {
|
|
403
449
|
const real_method = method.split('$batch_')?.[1];
|
|
404
450
|
if (!real_method || !actions.has(real_method))
|
|
405
451
|
return null;
|
|
406
|
-
const nodes = this.#
|
|
452
|
+
const nodes = this.#remote_services.get(service_name)?.nodes.filter(node => !node.isolated) || [];
|
|
407
453
|
return (...args) => {
|
|
408
454
|
const o = new Subject();
|
|
409
|
-
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);
|
|
410
464
|
return o;
|
|
411
465
|
};
|
|
412
466
|
}
|
|
@@ -421,16 +475,15 @@ export class SpiderMesh {
|
|
|
421
475
|
const $ = new Subject();
|
|
422
476
|
const $$ = publish_buffer_ms ? $.pipe(bufferTime(publish_buffer_ms), filter(l => l.length > 0)) : $;
|
|
423
477
|
$$.subscribe(data => this.publish(event_factory.name, data));
|
|
424
|
-
|
|
478
|
+
return {
|
|
425
479
|
publish: async (data) => $.next(data),
|
|
426
|
-
listen: () => this.listen(event_factory
|
|
480
|
+
listen: () => this.listen(event_factory)
|
|
427
481
|
};
|
|
428
|
-
return event_hub;
|
|
429
482
|
}
|
|
430
483
|
async #active_local_service(instance, metadata) {
|
|
431
484
|
const prototype = Object.getPrototypeOf(instance);
|
|
432
485
|
const name = prototype.constructor.name;
|
|
433
|
-
this.#
|
|
486
|
+
this.#local_services.set(name, { instance, metadata });
|
|
434
487
|
this.listen(name).subscribe(evt => this.#node_event_handler(evt));
|
|
435
488
|
const event_subscribers = listEventSubscribers(prototype);
|
|
436
489
|
for (const { event, method, buffer_ms } of event_subscribers) {
|
|
@@ -442,9 +495,16 @@ export class SpiderMesh {
|
|
|
442
495
|
async #wait_service_online(service_name = 'all') {
|
|
443
496
|
while (true) {
|
|
444
497
|
await sleep(1000);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
+
}
|
|
448
508
|
}
|
|
449
509
|
}
|
|
450
510
|
async #active_ready_hooks(instance) {
|
|
@@ -457,15 +517,20 @@ export class SpiderMesh {
|
|
|
457
517
|
await this.#initing;
|
|
458
518
|
const event = typeof topic == 'string' ? topic : topic.name;
|
|
459
519
|
const data = Array.isArray(payload) ? payload : [payload];
|
|
460
|
-
this.transporter.publish({
|
|
520
|
+
this.transporter.publish({
|
|
521
|
+
event,
|
|
522
|
+
data,
|
|
523
|
+
node_id
|
|
524
|
+
});
|
|
461
525
|
}
|
|
462
526
|
listen(topic) {
|
|
463
527
|
const topic_name = typeof topic == 'string' ? topic : topic.name;
|
|
464
528
|
return this.transporter.listen(topic_name).pipe(map(e => {
|
|
465
|
-
|
|
529
|
+
const data = e.data;
|
|
530
|
+
if (!Array.isArray(data))
|
|
466
531
|
return [];
|
|
467
|
-
return
|
|
468
|
-
data,
|
|
532
|
+
return data.map(data => ({
|
|
533
|
+
data: data,
|
|
469
534
|
sender_node_id: e.sender_node_id
|
|
470
535
|
}));
|
|
471
536
|
}), mergeAll());
|