@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.
Files changed (32) hide show
  1. package/build/src/Encoder.d.ts +4 -9
  2. package/build/src/Encoder.js +3 -35
  3. package/build/src/Encoder.js.map +1 -1
  4. package/build/src/SpiderMesh.d.ts +41 -45
  5. package/build/src/SpiderMesh.js +215 -273
  6. package/build/src/SpiderMesh.js.map +1 -1
  7. package/build/src/builtin-transporter/BuiltinTransporter.d.ts +5 -10
  8. package/build/src/builtin-transporter/BuiltinTransporter.js +85 -94
  9. package/build/src/builtin-transporter/BuiltinTransporter.js.map +1 -1
  10. package/build/src/builtin-transporter/RxjsTcpServer.d.ts +4 -9
  11. package/build/src/builtin-transporter/RxjsTcpServer.js +25 -25
  12. package/build/src/builtin-transporter/RxjsTcpServer.js.map +1 -1
  13. package/build/src/builtin-transporter/RxjsTcpSocket.d.ts +1 -0
  14. package/build/src/builtin-transporter/RxjsTcpSocket.js +3 -0
  15. package/build/src/builtin-transporter/RxjsTcpSocket.js.map +1 -1
  16. package/build/src/builtin-transporter/RxjsUdpServer.d.ts +20 -0
  17. package/build/src/builtin-transporter/RxjsUdpServer.js +83 -0
  18. package/build/src/builtin-transporter/RxjsUdpServer.js.map +1 -0
  19. package/build/src/const.d.ts +1 -0
  20. package/build/src/const.js +1 -0
  21. package/build/src/const.js.map +1 -1
  22. package/build/src/decorators/Microservice.d.ts +2 -0
  23. package/build/src/interfaces/SpiderMeshNode.d.ts +3 -1
  24. package/build/src/interfaces/SpiderMeshTransporter.d.ts +13 -12
  25. package/build/tests/Rpc.d.ts +1 -0
  26. package/build/tests/Rpc.js +102 -0
  27. package/build/tests/Rpc.js.map +1 -0
  28. package/package.json +1 -1
  29. package/tsconfig.json +1 -1
  30. package/build/src/builtin-transporter/RxjsUdpBroadcaster.d.ts +0 -19
  31. package/build/src/builtin-transporter/RxjsUdpBroadcaster.js +0 -56
  32. package/build/src/builtin-transporter/RxjsUdpBroadcaster.js.map +0 -1
@@ -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, finalize, first, firstValueFrom, from, groupBy, map, mergeAll, mergeMap, of, share, tap } from 'rxjs';
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
- transporter;
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
- constructor(transporter = new BuiltinTransporter(NODE_ID, NAMEPSACE)) {
50
- this.transporter = transporter;
51
- this.#initing = this.#init();
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.id == options.$node_id))
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].id;
63
+ return nodes[current.last_call_index].node_id;
70
64
  }
71
65
  rpc(service, method, args, options = {}) {
72
- const target_node_id = this.#caculate_rpc_node_id(service, options);
73
- if (!target_node_id)
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 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 => {
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.id))
125
+ .map(node => node.local_transporter_id))
307
126
  ];
308
127
  const metadata = {
309
- id: this.transporter.node_id,
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: this.transporter.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(node_id) {
330
- const me = await this.$metadata();
331
- await this.publish('#join', [me], node_id);
332
- return me;
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
- #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);
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 #init() {
350
- await this.transporter.start();
351
- this.listen(this.transporter.node_id).subscribe(msg => this.#node_event_handler(msg));
352
- this.listen('#join')
353
- .pipe(groupBy(node => node.sender_node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000))), mergeMap(({ data }) => this.#on_node_discovered(data), 1))
354
- .subscribe();
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))
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(filter(i => i.namespace == this.transporter.namespace), mergeMap(async ({ instance, metadata }) => {
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(() => this.#self_introduce())).subscribe();
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.id == this.transporter.node_id)
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.id);
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.transporter.node_id);
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
- if (!peer_updated)
376
- await this.#self_introduce(node.id);
377
- this.#linked_nodes.set(node.id, new_node);
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.id == new_node.id);
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(id) {
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.id != id);
298
+ service.nodes = service.nodes.filter(node => node.remote_transporter_id != remote_transporter_id);
394
299
  }
395
- for (const [rid, { target_node_id, channels, observables, subscriptions }] of this.#requests) {
396
- if (target_node_id == id) {
397
- for (const [_, $] of channels)
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(id);
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.id });
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(event_factory.name, data));
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 publish(topic, payload, node_id) {
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 = Array.isArray(payload) ? payload : [payload];
520
- this.transporter.publish({
521
- event,
522
- data,
523
- node_id
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(e => {
529
- const data = e.data;
530
- if (!Array.isArray(data))
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
- return data.map(data => ({
533
- data: data,
534
- sender_node_id: e.sender_node_id
535
- }));
477
+ }
536
478
  }), mergeAll());
537
479
  }
538
480
  }