@spider-mesh/core 1.0.115 → 1.0.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/build/src/Encoder.d.ts +14 -2
  2. package/build/src/Encoder.js +88 -36
  3. package/build/src/Encoder.js.map +1 -1
  4. package/build/src/SpiderMesh.d.ts +59 -25
  5. package/build/src/SpiderMesh.js +315 -254
  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.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.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
- 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,143 +336,22 @@ 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
- .pipe(groupBy(node => node.sender_node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000))))
237
- .subscribe(({ data }) => {
238
- this.#on_node_discovered(data);
239
- });
353
+ .pipe(groupBy(node => node.sender_node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000))), mergeMap(({ data }) => this.#on_node_discovered(data), 1))
354
+ .subscribe();
240
355
  this.transporter.$nodes_status.pipe(tap(node => !node.online && this.#on_node_offline(node.node_id)), groupBy(node => node.node_id), mergeMap(grouped => grouped.pipe(debounceTime(1000))), filter(node => node.online), mergeMap(({ node_id }) => this.#self_introduce(node_id), 1))
241
356
  .subscribe();
242
357
  serviceInstanceList.pipe(filter(i => i.namespace == this.transporter.namespace), mergeMap(async ({ instance, metadata }) => {
@@ -257,106 +372,38 @@ export class SpiderMesh {
257
372
  ...node,
258
373
  online: true
259
374
  };
260
- this.#linked_nodes.set(node.id, new_node);
261
375
  if (!peer_updated)
262
- return await this.#self_introduce(node.id);
376
+ await this.#self_introduce(node.id);
377
+ this.#linked_nodes.set(node.id, new_node);
263
378
  for (const service_id of Object.keys(node.services)) {
264
- if (!this.#remote_rpc_services.has(service_id)) {
265
- this.#remote_rpc_services.set(service_id, {
379
+ if (!this.#remote_services.has(service_id)) {
380
+ this.#remote_services.set(service_id, {
266
381
  last_call_index: -1,
267
382
  nodes: []
268
383
  });
269
384
  }
270
- const $service = this.#remote_rpc_services.get(service_id);
385
+ const $service = this.#remote_services.get(service_id);
271
386
  const index = $service.nodes.findIndex(n => n.id == new_node.id);
272
387
  index >= 0 ? ($service.nodes[index] = new_node) : $service.nodes.push(new_node);
273
- this.$nodes_monitor.next(node);
274
388
  }
389
+ this.$nodes_monitor.next(node);
275
390
  }
276
391
  #on_node_offline(id) {
277
- for (const service of this.#remote_rpc_services.values()) {
392
+ for (const service of this.#remote_services.values()) {
278
393
  service.nodes = service.nodes.filter(node => node.id != id);
279
394
  }
280
- for (const [rid, { target_node_id, $ }] of this.#rpc_queue) {
395
+ for (const [rid, { target_node_id, channels, observables, subscriptions }] of this.#requests) {
281
396
  if (target_node_id == id) {
282
- $.error(new Error('SERVICE_OFFLINE'));
283
- 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);
284
401
  }
285
402
  }
286
403
  const node = this.#linked_nodes.get(id);
287
404
  node && this.$nodes_monitor.next({ ...node, online: false });
288
405
  this.#linked_nodes.delete(id);
289
406
  }
290
- #caculate_rpc_node_id(service_name, options = {}) {
291
- const current = this.#remote_rpc_services.get(service_name);
292
- if (!current || current.nodes.length == 0)
293
- return;
294
- if (options.$node_id) {
295
- if (current.nodes.some(node => node.id == options.$node_id))
296
- return options.$node_id;
297
- return;
298
- }
299
- const nodes = options.$ip ? (current
300
- .nodes
301
- .filter(node => node.public_ip == options.$ip || node.ip_addresses.includes(options.$ip))) : current.nodes;
302
- if (nodes.length == 0)
303
- return;
304
- current.last_call_index = (current.last_call_index + 1) % nodes.length;
305
- return nodes[current.last_call_index].id;
306
- }
307
- rpc(service, method, args, options) {
308
- const rid = randomUUID();
309
- const observable = new Observable(o => {
310
- setTimeout(async () => {
311
- await this.#initing;
312
- const reject = (err) => {
313
- if (options.$fallback != undefined) {
314
- o.next(options.$fallback);
315
- o.complete();
316
- }
317
- else {
318
- o.error(err);
319
- }
320
- };
321
- of(1).pipe(mergeMap(async () => {
322
- const target_node_id = this.#caculate_rpc_node_id(service, options);
323
- if (!target_node_id)
324
- return reject(`SERVICE_NOT_RUNNING:${service}`);
325
- !options.$nevermind && this.#rpc_queue.set(rid, {
326
- args,
327
- last_ping: 0,
328
- $: o,
329
- request_time: Date.now(),
330
- timeout: options.$timeout,
331
- target_node_id
332
- });
333
- await this.publish(service, { type: 'rpc', id: rid, args, method, service }, target_node_id);
334
- }), retry(options.$retry || 1), catchError(() => {
335
- reject('RETRY_EXECTED');
336
- return EMPTY;
337
- })).subscribe();
338
- });
339
- return () => {
340
- const req = this.#rpc_queue.get(rid);
341
- req?.target_node_id && this.publish(service, { type: 'rpc-cancel', id: rid, args: [], method, service }, req.target_node_id);
342
- this.#rpc_queue.delete(rid);
343
- };
344
- });
345
- const $timeout = !options.$timeout ? EMPTY : timer(options.$timeout).pipe(takeUntil(observable), map(() => {
346
- throw new Error('SPIDER_MESH_RPC_TIMEOUT');
347
- }));
348
- return Object.assign(observable, {
349
- then: async (success, error) => {
350
- try {
351
- const value = await firstValueFrom(merge(observable, $timeout));
352
- success(options.$safe_mode ? [null, value] : value);
353
- }
354
- catch (e) {
355
- options.$safe_mode ? success([e, null]) : error(e);
356
- }
357
- }
358
- });
359
- }
360
407
  async link_remote_service(factory, wait_service_online = false) {
361
408
  const service_name = factory.name;
362
409
  const omit_properties = new Set([
@@ -387,9 +434,6 @@ export class SpiderMesh {
387
434
  typeof f[method] == 'function' && actions.add(method);
388
435
  }
389
436
  }
390
- if (!this.#remote_rpc_services.has(service_name)) {
391
- this.#remote_rpc_services.set(service_name, { last_call_index: -1, nodes: [] });
392
- }
393
437
  wait_service_online && await this.#wait_service_online(service_name);
394
438
  return new Proxy({}, {
395
439
  get: (_, method) => {
@@ -399,16 +443,24 @@ export class SpiderMesh {
399
443
  if (method == '$watch')
400
444
  return () => this.$nodes_monitor.pipe(filter(node => !!node.services[service_name]));
401
445
  if (method == '$list_nodes') {
402
- return (() => this.#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) || []);
403
447
  }
404
448
  if (method.startsWith('$batch_')) {
405
449
  const real_method = method.split('$batch_')?.[1];
406
450
  if (!real_method || !actions.has(real_method))
407
451
  return null;
408
- const nodes = this.#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) || [];
409
453
  return (...args) => {
410
454
  const o = new Subject();
411
- 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);
412
464
  return o;
413
465
  };
414
466
  }
@@ -423,16 +475,15 @@ export class SpiderMesh {
423
475
  const $ = new Subject();
424
476
  const $$ = publish_buffer_ms ? $.pipe(bufferTime(publish_buffer_ms), filter(l => l.length > 0)) : $;
425
477
  $$.subscribe(data => this.publish(event_factory.name, data));
426
- const event_hub = {
478
+ return {
427
479
  publish: async (data) => $.next(data),
428
- listen: () => this.listen(event_factory.name)
480
+ listen: () => this.listen(event_factory)
429
481
  };
430
- return event_hub;
431
482
  }
432
483
  async #active_local_service(instance, metadata) {
433
484
  const prototype = Object.getPrototypeOf(instance);
434
485
  const name = prototype.constructor.name;
435
- this.#local_rpc_services.set(name, { instance, metadata });
486
+ this.#local_services.set(name, { instance, metadata });
436
487
  this.listen(name).subscribe(evt => this.#node_event_handler(evt));
437
488
  const event_subscribers = listEventSubscribers(prototype);
438
489
  for (const { event, method, buffer_ms } of event_subscribers) {
@@ -442,13 +493,18 @@ export class SpiderMesh {
442
493
  }
443
494
  }
444
495
  async #wait_service_online(service_name = 'all') {
445
- if (this.#remote_rpc_services.size == 0)
446
- return;
447
496
  while (true) {
448
- await sleep(500);
449
- const nodes = service_name == 'all' ? ([...this.#remote_rpc_services.values()].map(s => s.nodes).flat(2)) : (this.#remote_rpc_services.get(service_name)?.nodes || []);
450
- if (nodes.length > 0)
451
- break;
497
+ await sleep(1000);
498
+ if (service_name == 'all') {
499
+ if ([...this.#remote_services.values()].every(e => e.nodes.length > 0)) {
500
+ return true;
501
+ }
502
+ }
503
+ else {
504
+ const nodes = this.#remote_services.get(service_name)?.nodes || [];
505
+ if (nodes.length > 0)
506
+ return;
507
+ }
452
508
  }
453
509
  }
454
510
  async #active_ready_hooks(instance) {
@@ -461,15 +517,20 @@ export class SpiderMesh {
461
517
  await this.#initing;
462
518
  const event = typeof topic == 'string' ? topic : topic.name;
463
519
  const data = Array.isArray(payload) ? payload : [payload];
464
- this.transporter.publish({ event, data, node_id });
520
+ this.transporter.publish({
521
+ event,
522
+ data,
523
+ node_id
524
+ });
465
525
  }
466
526
  listen(topic) {
467
527
  const topic_name = typeof topic == 'string' ? topic : topic.name;
468
528
  return this.transporter.listen(topic_name).pipe(map(e => {
469
- if (!Array.isArray(e.data))
529
+ const data = e.data;
530
+ if (!Array.isArray(data))
470
531
  return [];
471
- return e.data.map(data => ({
472
- data,
532
+ return data.map(data => ({
533
+ data: data,
473
534
  sender_node_id: e.sender_node_id
474
535
  }));
475
536
  }), mergeAll());