@spider-mesh/core 1.0.116 → 1.0.118

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 (28) hide show
  1. package/build/src/Encoder.d.ts +14 -2
  2. package/build/src/Encoder.js +100 -35
  3. package/build/src/Encoder.js.map +1 -1
  4. package/build/src/SpiderMesh.d.ts +59 -25
  5. package/build/src/SpiderMesh.js +309 -244
  6. package/build/src/SpiderMesh.js.map +1 -1
  7. package/build/src/builtin-transporter/BuiltinTransporter.d.ts +3 -2
  8. package/build/src/builtin-transporter/BuiltinTransporter.js +18 -14
  9. package/build/src/builtin-transporter/BuiltinTransporter.js.map +1 -1
  10. package/build/src/builtin-transporter/RxjsTcpServer.d.ts +1 -1
  11. package/build/src/builtin-transporter/RxjsTcpServer.js.map +1 -1
  12. package/build/src/builtin-transporter/RxjsTcpSocket.d.ts +6 -6
  13. package/build/src/builtin-transporter/RxjsTcpSocket.js +1 -1
  14. package/build/src/builtin-transporter/RxjsTcpSocket.js.map +1 -1
  15. package/build/src/builtin-transporter/RxjsUdpBroadcaster.d.ts +1 -1
  16. package/build/src/builtin-transporter/RxjsUdpBroadcaster.js +2 -2
  17. package/build/src/builtin-transporter/RxjsUdpBroadcaster.js.map +1 -1
  18. package/build/src/decorators/DeepProxy.js.map +1 -1
  19. package/build/src/decorators/ListenEvent.d.ts +7 -6
  20. package/build/src/decorators/ListenEvent.js.map +1 -1
  21. package/build/src/decorators/Microservice.d.ts +7 -1
  22. package/build/src/decorators/Microservice.js +1 -1
  23. package/build/src/decorators/Microservice.js.map +1 -1
  24. package/build/src/decorators/OnMicroserviceReady.js.map +1 -1
  25. package/build/src/interfaces/RemoteService.d.ts +1 -0
  26. package/build/src/interfaces/SpiderMeshTransporter.d.ts +6 -5
  27. package/package.json +1 -1
  28. package/tsconfig.json +5 -2
@@ -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, merge, mergeAll, mergeMap, of, retry, takeUntil, tap, timer } from 'rxjs';
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
- export class SpiderMeshConfig {
16
- namespace;
17
- node_id;
18
- transporter;
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
- #$isolated = new BehaviorSubject(false);
24
- #local_rpc_services = new Map();
25
- #remote_rpc_services = new Map;
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
- static add_transporter(transporter) {
52
- new this(transporter);
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.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
+ }
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.address));
59
- const services = [...this.#local_rpc_services.entries()].reduce((p, [service_id, { metadata }]) => ({
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.transporter.publish({
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
- async #node_event_handler({ data: msg, sender_node_id }) {
104
- if (msg.type == 'rpc') {
105
- if (msg.service == 'SpiderMesh' && !msg.method.startsWith('$')) {
106
- sender_node_id && this.transporter.publish({
107
- data: [{
108
- id: msg.id,
109
- type: 'error',
110
- error: 'NOT_ALLOW'
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(evt => this.#node_event_handler(evt));
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.#remote_rpc_services.has(service_id)) {
263
- this.#remote_rpc_services.set(service_id, {
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.#remote_rpc_services.get(service_id);
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.#remote_rpc_services.values()) {
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, $ }] of this.#rpc_queue) {
395
+ for (const [rid, { target_node_id, channels, observables, subscriptions }] of this.#requests) {
279
396
  if (target_node_id == id) {
280
- $.error(new Error('SERVICE_OFFLINE'));
281
- this.#rpc_queue.delete(rid);
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.#remote_rpc_services.get(service_name)?.nodes.filter(node => !node.isolated).filter(node => node.online) || []);
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.#remote_rpc_services.get(service_name)?.nodes.filter(node => !node.isolated) || [];
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) => this.rpc(service_name, real_method, args, { $node_id: node.id }))).subscribe(o);
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
- const event_hub = {
478
+ return {
425
479
  publish: async (data) => $.next(data),
426
- listen: () => this.listen(event_factory.name)
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.#local_rpc_services.set(name, { instance, metadata });
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
- const nodes = service_name == 'all' ? ([...this.#remote_rpc_services.values()].map(s => s.nodes).flat(2)) : (this.#remote_rpc_services.get(service_name)?.nodes || []);
446
- if (nodes.length > 0)
447
- break;
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({ event, data, node_id });
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
- if (!Array.isArray(e.data))
529
+ const data = e.data;
530
+ if (!Array.isArray(data))
466
531
  return [];
467
- return e.data.map(data => ({
468
- data,
532
+ return data.map(data => ({
533
+ data: data,
469
534
  sender_node_id: e.sender_node_id
470
535
  }));
471
536
  }), mergeAll());