@spider-mesh/core 1.0.152 → 2.0.1

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 (83) hide show
  1. package/build/const.d.ts +1 -0
  2. package/build/const.js +2 -0
  3. package/build/const.js.map +1 -0
  4. package/build/src/SpiderMesh.d.ts +25 -70
  5. package/build/src/SpiderMesh.js +308 -420
  6. package/build/src/SpiderMesh.js.map +1 -1
  7. package/build/src/decorators/BeforeMicroserviceOnline.d.ts +1 -2
  8. package/build/src/decorators/BeforeMicroserviceOnline.js +1 -1
  9. package/build/src/decorators/BeforeMicroserviceOnline.js.map +1 -1
  10. package/build/src/decorators/LimitConcurrentRunning.d.ts +2 -3
  11. package/build/src/decorators/LimitConcurrentRunning.js +2 -27
  12. package/build/src/decorators/LimitConcurrentRunning.js.map +1 -1
  13. package/build/src/decorators/Microservice.d.ts +10 -7
  14. package/build/src/decorators/Microservice.js +13 -12
  15. package/build/src/decorators/Microservice.js.map +1 -1
  16. package/build/src/decorators/NestJSExposeMicroservice.d.ts +1 -2
  17. package/build/src/decorators/NestJSExposeMicroservice.js +9 -7
  18. package/build/src/decorators/NestJSExposeMicroservice.js.map +1 -1
  19. package/build/src/decorators/NestJSLinkEvent.d.ts +5 -16
  20. package/build/src/decorators/NestJSLinkEvent.js +2 -2
  21. package/build/src/decorators/NestJSLinkEvent.js.map +1 -1
  22. package/build/src/decorators/NestJSLinkMicroservice.d.ts +2 -2
  23. package/build/src/decorators/NestJSLinkMicroservice.js +2 -2
  24. package/build/src/decorators/NestJSLinkMicroservice.js.map +1 -1
  25. package/build/src/helpers/LimitConcurrency.d.ts +3 -0
  26. package/build/src/helpers/LimitConcurrency.js +34 -0
  27. package/build/src/helpers/LimitConcurrency.js.map +1 -0
  28. package/build/src/helpers/MicroserviceException.d.ts +8 -0
  29. package/build/src/helpers/MicroserviceException.js +9 -0
  30. package/build/src/helpers/MicroserviceException.js.map +1 -0
  31. package/build/src/helpers/MicroserviceNotFound copy.d.ts +5 -0
  32. package/build/src/helpers/MicroserviceNotFound copy.js +8 -0
  33. package/build/src/helpers/MicroserviceNotFound copy.js.map +1 -0
  34. package/build/src/helpers/MicroserviceNotFound.d.ts +5 -0
  35. package/build/src/helpers/MicroserviceNotFound.js +8 -0
  36. package/build/src/helpers/MicroserviceNotFound.js.map +1 -0
  37. package/build/src/helpers/MicroserviceOfflineException.d.ts +5 -0
  38. package/build/src/helpers/MicroserviceOfflineException.js +8 -0
  39. package/build/src/helpers/MicroserviceOfflineException.js.map +1 -0
  40. package/build/src/helpers/MicroserviceRpcTimeout.d.ts +5 -0
  41. package/build/src/helpers/MicroserviceRpcTimeout.js +8 -0
  42. package/build/src/helpers/MicroserviceRpcTimeout.js.map +1 -0
  43. package/build/src/helpers/randomUUID.js.map +1 -1
  44. package/build/src/helpers/sleep.js +1 -1
  45. package/build/src/helpers/sleep.js.map +1 -1
  46. package/build/src/index.d.ts +14 -15
  47. package/build/src/index.js +14 -12
  48. package/build/src/index.js.map +1 -1
  49. package/build/src/interfaces/DiscoveryTransporter.d.ts +5 -0
  50. package/build/src/interfaces/DiscoveryTransporter.js +3 -0
  51. package/build/src/interfaces/DiscoveryTransporter.js.map +1 -0
  52. package/build/src/interfaces/PubsubTransporter.d.ts +10 -0
  53. package/build/src/interfaces/PubsubTransporter.js +3 -0
  54. package/build/src/interfaces/PubsubTransporter.js.map +1 -0
  55. package/build/src/interfaces/RemoteService.d.ts +12 -19
  56. package/build/src/interfaces/RpcTransporter.d.ts +30 -0
  57. package/build/src/interfaces/RpcTransporter.js +3 -0
  58. package/build/src/interfaces/RpcTransporter.js.map +1 -0
  59. package/build/src/interfaces/SpiderMeshNode.d.ts +14 -19
  60. package/build/tsconfig.tsbuildinfo +1 -0
  61. package/package.json +7 -8
  62. package/tsconfig.json +2 -2
  63. package/build/src/Encoder.d.ts +0 -8
  64. package/build/src/Encoder.js +0 -76
  65. package/build/src/Encoder.js.map +0 -1
  66. package/build/src/RPCOptions.d.ts +0 -11
  67. package/build/src/RPCOptions.js +0 -11
  68. package/build/src/RPCOptions.js.map +0 -1
  69. package/build/src/const.d.ts +0 -6
  70. package/build/src/const.js +0 -8
  71. package/build/src/const.js.map +0 -1
  72. package/build/src/decorators/CustomSpiderMesh.d.ts +0 -6
  73. package/build/src/decorators/CustomSpiderMesh.js +0 -9
  74. package/build/src/decorators/CustomSpiderMesh.js.map +0 -1
  75. package/build/src/decorators/DeepProxy.d.ts +0 -7
  76. package/build/src/decorators/DeepProxy.js +0 -24
  77. package/build/src/decorators/DeepProxy.js.map +0 -1
  78. package/build/src/decorators/ListenEvent.d.ts +0 -19
  79. package/build/src/decorators/ListenEvent.js +0 -23
  80. package/build/src/decorators/ListenEvent.js.map +0 -1
  81. package/build/src/interfaces/SpiderMeshTransporter.d.ts +0 -33
  82. package/build/src/interfaces/SpiderMeshTransporter.js +0 -2
  83. package/build/src/interfaces/SpiderMeshTransporter.js.map +0 -1
@@ -1,345 +1,237 @@
1
- import { DeepProxy } from './decorators/DeepProxy.js';
2
- import { RPCOptionsList } from './RPCOptions.js';
3
- import { listEventSubscribers } from './decorators/ListenEvent.js';
4
- import { BehaviorSubject, Observable, ReplaySubject, Subject, bufferTime, catchError, debounceTime, filter, firstValueFrom, from, groupBy, map, merge, mergeAll, mergeMap, of, pipe, retry, scan, share, tap, timeout, toArray } from 'rxjs';
5
- import { sleep } from './helpers/sleep.js';
6
- import { NAMEPSACE } from './const.js';
7
- import { $services } from './decorators/Microservice.js';
8
- import { randomUUID } from './helpers/randomUUID.js';
9
- import axios from 'axios';
10
- const $public_ip = new Promise(async (resolve) => {
11
- try {
12
- const { data } = await axios.get('https://api.ipify.org?format=json');
13
- resolve(data.ip);
14
- }
15
- catch (error) {
16
- resolve(undefined);
17
- }
18
- });
1
+ import { BehaviorSubject, catchError, EMPTY, filter, finalize, firstValueFrom, from, lastValueFrom, map, mergeAll, mergeMap, of, retry, tap, throwError, timeout, timer } from "rxjs";
2
+ import { listBeforeMicroserviceOnlineMethods } from "./decorators/BeforeMicroserviceOnline.js";
3
+ import { PubsubTransporter } from "./interfaces/PubsubTransporter.js";
4
+ import { RpcTransporter } from "./interfaces/RpcTransporter.js";
5
+ import { MicroserviceException } from "./helpers/MicroserviceException.js";
6
+ import { MicroserviceOfflineException } from "./helpers/MicroserviceOfflineException.js";
7
+ import { services$ } from "./decorators/Microservice.js";
8
+ import { networkInterfaces } from "os";
9
+ import { MicroserviceNotFound } from "./helpers/MicroserviceNotFound.js";
10
+ import { DiscoveryTransporter } from "./interfaces/DiscoveryTransporter.js";
11
+ import { SPIDERMESH_NAMESPACE } from "const.js";
12
+ import { MicroserviceRpcTimeout } from "./helpers/MicroserviceRpcTimeout.js";
19
13
  export class SpiderMesh {
20
- metadata;
21
- static $transporters = new ReplaySubject();
22
- #node_id = randomUUID();
23
- #requests = new Map;
24
- #local_services = new Map();
25
- #remote_services = new Map;
26
- #linked_nodes = new Map();
27
- $nodes_monitor = new Subject();
28
- #$isolated = new BehaviorSubject(false);
29
- #transporters = new Map();
30
- constructor(metadata = {}) {
31
- this.metadata = metadata;
32
- merge(SpiderMesh.$transporters.pipe(mergeMap(async (transporter) => {
33
- await this.#link_transporter(transporter);
34
- return transporter;
35
- }), map(t => t.transporter_id)), $services.pipe(tap(({ instance, metadata }) => {
36
- const prototype = Object.getPrototypeOf(instance);
37
- const name = prototype.constructor.name;
38
- this.#local_services.set(name, { instance, metadata });
39
- }), map(() => undefined))).pipe(debounceTime(2000), mergeMap(async (transporter_id) => {
40
- await this.#self_introduce(transporter_id);
41
- }, 1)).subscribe();
14
+ node_id = `${Date.now()}`;
15
+ namespace = SPIDERMESH_NAMESPACE;
16
+ #rpcs = new Map();
17
+ #pubsubs = new BehaviorSubject(new Map());
18
+ #discovers = new Map();
19
+ #metadata$ = new BehaviorSubject({
20
+ ips: Object.values(networkInterfaces()).flat(2).filter(a => !a?.internal && !!a?.address).map(a => a?.address),
21
+ host: '',
22
+ namespace: SPIDERMESH_NAMESPACE,
23
+ node_id: this.node_id,
24
+ services: {},
25
+ topics: [],
26
+ transporters: {},
27
+ nodes: {},
28
+ version: 0
29
+ });
30
+ #nodes = new BehaviorSubject({
31
+ nodes: new Map(),
32
+ last_updated_node_id: ''
33
+ });
34
+ #services$ = new BehaviorSubject(new Map());
35
+ constructor() {
36
+ services$.pipe(filter(list => Object.keys(list).length > 0), mergeMap(async (services) => {
37
+ for (const v of Object.values(services)) {
38
+ const metadata = v['$'] || (typeof v.metadata == 'function' ? await v.metadata() : v.metadata);
39
+ Object.defineProperty(v, '$', { value: metadata });
40
+ }
41
+ return services;
42
+ }), tap(services => {
43
+ const metadata = {
44
+ ...this.#metadata$.value,
45
+ services: Object.entries(services).reduce((p, [name, v]) => {
46
+ return {
47
+ ...p,
48
+ [name]: v['$']
49
+ };
50
+ }, {}),
51
+ version: this.#metadata$.value.version + 1
52
+ };
53
+ this.#metadata$.next(metadata);
54
+ }), catchError(e => EMPTY)).subscribe();
42
55
  }
43
- #caculate_rpc_node_id(service_name, options = {}) {
44
- const current = this.#remote_services.get(service_name);
45
- if (!current || current.nodes.length == 0)
46
- return;
47
- if (options.$node_id) {
48
- if (current.nodes.some(node => node.node_id == options.$node_id))
49
- return options.$node_id;
50
- return;
56
+ #selectRpcTarget(filters = {}) {
57
+ if (!filters.service)
58
+ return null;
59
+ if (filters.node_id) {
60
+ const node = this.#nodes.value.nodes.get(filters.node_id);
61
+ if (!node)
62
+ return null;
63
+ if (!node.services[filters.service])
64
+ return null;
65
+ if (!node.rpc)
66
+ return null;
67
+ const transporter = this.#rpcs.get(node.rpc);
68
+ if (!transporter)
69
+ return null;
70
+ return { node, transporter };
51
71
  }
52
- const option_ip = options.$ip;
53
- const nodes = option_ip ? (current
54
- .nodes
55
- .filter(node => node.public_ip == option_ip)) : current.nodes;
56
- if (nodes.length == 0)
57
- return;
58
- current.last_call_index = (current.last_call_index + 1) % nodes.length;
59
- return nodes[current.last_call_index].node_id;
60
- }
61
- rpc(service, method, args, options = {}) {
62
- const rpc_node_id = this.#caculate_rpc_node_id(service, options);
63
- if (!rpc_node_id) {
64
- const e = new Error(`SERVICE_NOT_RUNNING:${service}`, {
65
- cause: {
66
- service,
67
- method,
68
- args,
69
- options
72
+ const state = this.#services$.value.get(filters.service);
73
+ if (!state || state.nodes.length == 0)
74
+ return null;
75
+ const nodes = state.nodes.map(id => {
76
+ const node = this.#nodes.value.nodes.get(id);
77
+ const transporter = node && node.rpc && this.#rpcs.get(node.rpc);
78
+ if (node && transporter) {
79
+ if (filters.ip) {
80
+ if (!node.ips.includes(filters.ip))
81
+ return null;
70
82
  }
71
- });
72
- return Object.assign(new Observable(o => o.error(e)), {
73
- then: async (success, error) => {
74
- error(e);
75
- },
76
- async catch() {
77
- return {};
78
- },
79
- async finally() {
80
- return {};
81
- },
82
- [Symbol.toStringTag]: ''
83
- });
84
- }
85
- if (options.$forgot) {
86
- this.publish({
87
- topic: rpc_node_id,
88
- payload: [{
89
- request: {
90
- args,
91
- method,
92
- request_id: '#',
93
- service
94
- }
95
- }],
96
- node_id: rpc_node_id
97
- });
98
- return;
99
- }
100
- const $ = new Observable($response => {
101
- const request_id = randomUUID();
102
- this.#requests.set(request_id, { $response, rpc_node_id });
103
- this.publish({
104
- topic: rpc_node_id,
105
- payload: [{
106
- request: {
107
- args,
108
- method,
109
- request_id,
110
- service
111
- }
112
- }],
113
- node_id: rpc_node_id
114
- });
115
- return () => {
116
- this.#requests.delete(request_id);
117
- };
118
- }).pipe(retry({ count: options.$retry || 0, delay: options.$retry_delay || 1000 }), options.$timeout ? timeout(options.$timeout) : tap(), catchError(err => {
119
- if (options.$fallback) {
120
- return of(options.$fallback);
83
+ return { node, transporter };
121
84
  }
122
- throw err;
123
- }), share());
124
- $.subscribe({ error: () => { }, next: () => { } });
125
- return Object.assign($, {
126
- then: async (success, error) => {
127
- try {
128
- const value = await firstValueFrom($);
129
- success(options.$safe_mode ? [null, value] : value);
130
- }
131
- catch (e) {
132
- options.$safe_mode ? success([e, null]) : error(e);
133
- }
134
- },
135
- async catch() {
136
- return {};
137
- },
138
- async finally() {
139
- return {};
140
- },
141
- [Symbol.toStringTag]: ''
142
- });
85
+ }).filter(Boolean).map(e => e);
86
+ return nodes[state.index++ % nodes.length];
143
87
  }
144
- async $metadata() {
145
- const services = [...this.#local_services.entries()].reduce((p, [service_id, { metadata }]) => ({
146
- ...p,
147
- [service_id]: metadata
148
- }), {});
149
- const isolated_nodes = [
150
- ...new Set([...this.#linked_nodes.values()]
151
- .filter(node => node.isolated)
152
- .map(node => node.local_transporter_id))
153
- ];
154
- const metadata = {
155
- metadata: this.metadata,
156
- node_id: this.#node_id,
157
- node_version: process.version,
158
- last_online: Date.now(),
159
- online: true,
160
- services,
161
- namespace: NAMEPSACE,
162
- linked: [...this.#linked_nodes.keys()],
163
- isolated_nodes,
164
- isolated: this.#$isolated.value,
165
- public_ip: await $public_ip
166
- };
167
- return metadata;
168
- }
169
- async #self_introduce(local_transporter_id, node_id) {
170
- const list = local_transporter_id ? [this.#transporters.get(local_transporter_id)] : [...this.#transporters.values()];
171
- for (const target of list) {
88
+ callRemoteService(options) {
89
+ return of(0).pipe(mergeMap(async () => {
90
+ await this.waitServiceOnline(options.service);
91
+ const target = this.#selectRpcTarget(options);
172
92
  if (!target)
173
- continue;
174
- const me = {
175
- ...await this.$metadata(),
176
- local_transporter_id: target.transporter.transporter_id,
177
- remote_transporter_id: '#OVERWRITE'
178
- };
179
- await this.publish({ payload: [me], topic: '#join', node_id }, target.transporter);
180
- }
181
- }
182
- async $update_isolate_mode(active) {
183
- this.#$isolated.next(active);
184
- const me = await this.#self_introduce();
185
- return me;
186
- }
187
- #get_rpc_target(msg) {
188
- if (msg.service == 'SpiderMesh') {
189
- if (!msg.method.startsWith('$'))
190
- throw `SPIDER_MESH_METHOD_NOT_ALLOW`;
191
- return this;
192
- }
193
- const service = this.#local_services.get(msg.service)?.instance;
194
- if (!service)
195
- throw 'RPC_SERVICE_NOT_FOUND';
196
- if (typeof service[msg.method] != 'function')
197
- throw 'RPC_METHOD_NOT_FOUND';
198
- return service;
93
+ throw new MicroserviceOfflineException();
94
+ const force = !!options.node_id || !!options.ip;
95
+ return target.transporter.rpc(options, target.node, force);
96
+ }), mergeMap($ => $), retry({
97
+ delay: (e, count) => {
98
+ if (e instanceof MicroserviceOfflineException || e.message == MicroserviceOfflineException.code) {
99
+ if (options.retry && count < options.retry)
100
+ return timer(1000);
101
+ }
102
+ throw e;
103
+ }
104
+ }), options.timeout ? timeout({
105
+ each: options.timeout,
106
+ with: () => throwError(() => new MicroserviceRpcTimeout())
107
+ }) : tap(), catchError(e => {
108
+ if (options.fallback != undefined)
109
+ return of(options.fallback);
110
+ if (e.code) {
111
+ const error = new MicroserviceException(e);
112
+ error.stack = e.stack;
113
+ throw error;
114
+ }
115
+ throw e;
116
+ }));
199
117
  }
200
- async #node_event_handler(transporter, { payload: event, metadata: { smnid } }) {
201
- if (event.request) {
202
- const response = (data) => {
203
- const error = data.error ? (data.error instanceof Error ? {
204
- cause: data.error.cause,
205
- message: data.error.message,
206
- name: data.error.name,
207
- stack: data.error.stack
208
- } : data.error) : undefined;
209
- const payload = [{
210
- response: {
211
- request_id: event.request.request_id,
212
- ...data,
213
- error
214
- },
215
- }];
216
- this.publish({
217
- payload,
218
- topic: smnid,
219
- node_id: smnid
220
- }, transporter);
221
- };
222
- try {
223
- const instance = this.#get_rpc_target(event.request);
224
- try {
225
- const value = await instance?.[event.request.method]?.(...event.request.args || []);
226
- if (event.request.request_id == '#')
227
- return;
228
- if (value instanceof Observable) {
229
- value.subscribe({
230
- complete: () => response({ completed: true }),
231
- error: error => response({ error }),
232
- next: value => response({ data: { value } })
233
- });
234
- return;
118
+ linkTransporter(transporter) {
119
+ const transporter_name = Object.getPrototypeOf(transporter).constructor.name;
120
+ if (transporter instanceof RpcTransporter) {
121
+ const rpc = transporter;
122
+ if (this.#rpcs.has(transporter_name))
123
+ return;
124
+ this.#rpcs.set(transporter_name, rpc);
125
+ return transporter.link(this.#nodes).pipe(map(e => {
126
+ const rpc = e.rpc;
127
+ if (rpc) {
128
+ try {
129
+ const service = services$.value[rpc.service];
130
+ const instance = service?.instance;
131
+ if (!instance)
132
+ return rpc.callback(throwError(() => new MicroserviceNotFound()));
133
+ const response = instance[rpc.method](...rpc.args);
134
+ rpc.callback(response);
235
135
  }
236
- else {
237
- response({ completed: true, data: { value } });
136
+ catch (err) {
137
+ rpc.callback(throwError(() => err));
238
138
  }
239
139
  }
240
- catch (error) {
241
- if (event.request.request_id == '#')
242
- return;
243
- response({ error: error });
140
+ const online = e.online;
141
+ if (online) {
142
+ const node = this.#nodes.value.nodes.get(online);
143
+ if (node) {
144
+ node.rpc = transporter_name;
145
+ for (const service of Object.keys(node.services || {})) {
146
+ const services = this.#services$.value;
147
+ const target = services.get(service) || { index: 0, nodes: [] };
148
+ services.set(service, {
149
+ index: target.index,
150
+ nodes: [
151
+ ...target.nodes.filter(id => id != node.node_id),
152
+ node.node_id
153
+ ]
154
+ });
155
+ this.#services$.next(services);
156
+ }
157
+ }
244
158
  }
245
- }
246
- catch (error) {
247
- if (event.request.request_id == '#')
248
- return;
249
- response({ error: error });
250
- }
159
+ const offline = e.offline;
160
+ if (offline) {
161
+ const nodes = this.#nodes.value.nodes;
162
+ const node = nodes.get(offline);
163
+ if (node) {
164
+ nodes.delete(offline);
165
+ this.#nodes.next({ nodes, last_updated_node_id: node.node_id });
166
+ for (const service of Object.values(node.services)) {
167
+ const services = this.#services$.value;
168
+ const target = services.get(service) || { index: 0, nodes: [] };
169
+ const nodes = target.nodes.filter(id => id != node.node_id);
170
+ nodes.length == 0 ? services.delete(service) : services.set(service, { ...target, nodes });
171
+ this.#services$.next(services);
172
+ }
173
+ }
174
+ }
175
+ const metadata = e.metadata;
176
+ if (metadata) {
177
+ this.#metadata$.next({
178
+ ...this.#metadata$.value,
179
+ transporters: {
180
+ ...this.#metadata$.value.transporters,
181
+ [transporter_name]: metadata
182
+ },
183
+ version: this.#metadata$.value.version + 1
184
+ });
185
+ }
186
+ }), finalize(() => {
187
+ this.#rpcs.delete(transporter_name);
188
+ })).subscribe();
251
189
  }
252
- if (event.response) {
253
- const request = this.#requests.get(event.response.request_id);
254
- if (!request)
255
- return;
256
- if (event.response.data) {
257
- request.$response.next(event.response.data.value);
258
- }
259
- if (event.response.completed) {
260
- request.$response.complete();
261
- }
262
- if (event.response.error) {
263
- request.$response.error(event.response.error);
264
- }
190
+ if (transporter instanceof PubsubTransporter) {
191
+ const transporters = this.#pubsubs.getValue();
192
+ transporters.set(transporter_name, transporter);
193
+ this.#pubsubs.next(transporters);
194
+ return transporter.link(this.#nodes).pipe(map(a => a.metadata), filter(Boolean), tap(metadata => {
195
+ this.#metadata$.next({
196
+ ...this.#metadata$.value,
197
+ transporters: {
198
+ ...this.#metadata$.value.transporters,
199
+ [transporter_name]: metadata
200
+ },
201
+ version: this.#metadata$.value.version + 1
202
+ });
203
+ }), finalize(() => {
204
+ transporters.delete(transporter_name);
205
+ this.#pubsubs.next(transporters);
206
+ })).subscribe();
265
207
  }
266
- }
267
- async #link_transporter(transporter) {
268
- this.#transporters.set(transporter.transporter_id, {
269
- transporter,
270
- nodes: new Map()
271
- });
272
- this.listen(this.#node_id, transporter).pipe(tap(msg => this.#node_event_handler(transporter, msg))).subscribe();
273
- this.listen('#join', transporter).pipe(filter(node => node.metadata.smnid != this.#node_id), groupBy(node => node.metadata.smnid), mergeMap(grouped => grouped.pipe(debounceTime(500), map(({ payload, sti }) => ({
274
- ...payload,
275
- local_transporter_id: transporter.transporter_id,
276
- remote_transporter_id: sti,
277
- })), filter(node => node.node_id != this.#node_id), mergeMap(node => this.#on_node_discovered(node), 1)))).subscribe();
278
- transporter.$nodes_status.pipe(tap(node => !node.online && this.#on_node_offline(transporter.transporter_id, node.remote_transporter_id)), groupBy(node => node.remote_transporter_id), mergeMap(grouped => grouped.pipe(scan((prev, current) => {
279
- if (!prev || (prev.online != current.online))
280
- return current;
281
- return undefined;
282
- }, undefined), filter(Boolean), map(node => node), filter(node => node.online), tap(node => this.#self_introduce(transporter.transporter_id))))).subscribe();
283
- $services.subscribe(({ instance }) => {
284
- const prototype = Object.getPrototypeOf(instance);
285
- const name = prototype.constructor.name;
286
- this.listen(name, transporter).subscribe(evt => this.#node_event_handler(transporter, evt));
287
- const event_subscribers = listEventSubscribers(prototype);
288
- for (const { event, method, buffer_ms } of event_subscribers) {
289
- this.listen(event, transporter).pipe(filter(() => !this.#$isolated.value), buffer_ms ? pipe(bufferTime(buffer_ms), filter(l => l.length > 0)) : pipe(map(item => [item]))).subscribe(e => instance[method]?.(e, this));
290
- }
291
- });
292
- }
293
- async #on_node_discovered(node) {
294
- if (node.node_id == this.#node_id)
295
- return;
296
- const saved_node = this.#linked_nodes.get(node.node_id);
297
- if (saved_node && saved_node.last_online > node.last_online)
298
- return;
299
- const peer_updated = node.linked.includes(this.#node_id);
300
- const new_node = {
301
- ...node,
302
- online: true
303
- };
304
- this.#linked_nodes.set(node.node_id, new_node);
305
- this.#transporters.get(node.local_transporter_id)?.nodes.set(node.remote_transporter_id, node.node_id);
306
- if (!peer_updated && !this.#$isolated.value)
307
- await this.#self_introduce(node.local_transporter_id, node.node_id);
308
- for (const service_id of Object.keys(node.services)) {
309
- if (!this.#remote_services.has(service_id)) {
310
- this.#remote_services.set(service_id, {
311
- last_call_index: -1,
312
- nodes: []
208
+ if (transporter instanceof DiscoveryTransporter) {
209
+ const discover = transporter;
210
+ this.#discovers.set(transporter_name, discover);
211
+ return discover.link(this.#metadata$).pipe(tap(node => {
212
+ const nodes = this.#nodes.value.nodes;
213
+ nodes.set(node.node_id, {
214
+ ...nodes.get(node.node_id) || {},
215
+ ...node
313
216
  });
314
- }
315
- const $service = this.#remote_services.get(service_id);
316
- const index = $service.nodes.findIndex(n => n.node_id == new_node.node_id);
317
- index >= 0 ? ($service.nodes[index] = new_node) : $service.nodes.push(new_node);
217
+ const last_updated_node_id = node.node_id;
218
+ this.#nodes.next({
219
+ nodes,
220
+ last_updated_node_id
221
+ });
222
+ }), finalize(() => this.#discovers.delete(transporter_name))).subscribe();
318
223
  }
319
- this.$nodes_monitor.next(node);
320
224
  }
321
- #on_node_offline(local_transporter_id, remote_transporter_id) {
322
- const node_id = this.#transporters.get(local_transporter_id)?.nodes?.get(remote_transporter_id);
323
- if (!node_id)
324
- return;
325
- const node = this.#linked_nodes.get(node_id);
326
- if (!node)
327
- return;
328
- for (const service of this.#remote_services.values()) {
329
- service.nodes = service.nodes.filter(node => node.remote_transporter_id != remote_transporter_id);
330
- }
331
- for (const [rid, { $response, rpc_node_id }] of this.#requests) {
332
- if (rpc_node_id == node_id) {
333
- $response.error('OFFLINE');
334
- this.#requests.delete(rid);
335
- }
336
- }
337
- node && this.$nodes_monitor.next({ ...node, online: false });
338
- this.#linked_nodes.delete(node_id);
225
+ waitServiceOnline(service, check = (nodes => nodes.length > 0)) {
226
+ return firstValueFrom(this.#services$.pipe(map(() => {
227
+ const targets = this.#services$.value.get(service)?.nodes || [];
228
+ const nodes = targets.map(id => this.#nodes.value.nodes.get(id)).filter(Boolean);
229
+ return nodes;
230
+ }), mergeMap(async (targets) => check(targets)), filter(Boolean)));
339
231
  }
340
- async link_remote_service(factory, wait_service_online = false) {
341
- const service_name = factory.name;
342
- const omit_properties = new Set([
232
+ linkRemoteService(factory) {
233
+ const service = factory.name;
234
+ const omitProperties = new Set([
343
235
  'caller',
344
236
  'callee',
345
237
  'arguments',
@@ -361,114 +253,110 @@ export class SpiderMesh {
361
253
  'beforeApplicationShutdown',
362
254
  'onApplicationShutdown'
363
255
  ]);
364
- const actions = new Set();
365
- for (let f = factory.prototype; f != null; f = Object.getPrototypeOf(f)) {
366
- for (const method of Object.getOwnPropertyNames(f).filter(method => !omit_properties.has(method))) {
367
- typeof f[method] == 'function' && actions.add(method);
368
- }
369
- }
370
- wait_service_online && await this.#wait_service_online(service_name);
371
- return new Proxy({}, {
372
- get: (_, method) => {
373
- if (method == '$wait_service_online') {
374
- return (fn) => this.#wait_service_online(service_name, fn);
256
+ const listRpcNodes = () => {
257
+ const targets = this.#services$.value.get(service);
258
+ if (!targets || targets.nodes.length == 0)
259
+ return [];
260
+ return targets.nodes.map(id => {
261
+ const node = this.#nodes.value.nodes.get(id);
262
+ if (node && node.rpc)
263
+ return node;
264
+ }).filter(Boolean).map(node => node);
265
+ };
266
+ const target = new Proxy({}, {
267
+ get: (_, $) => {
268
+ if (omitProperties.has($))
269
+ return null;
270
+ if ($ == 'wait$') {
271
+ return (fn) => this.waitServiceOnline(service, fn);
375
272
  }
376
- if (method == '$watch')
377
- return () => this.$nodes_monitor.pipe(filter(node => !!node.services[service_name]));
378
- if (method == '$list_nodes') {
379
- return (() => this.#remote_services.get(service_name)?.nodes.filter(node => !node.isolated).filter(node => node.online) || []);
273
+ if ($ == 'nodes')
274
+ return listRpcNodes();
275
+ if ($ == 'watch$')
276
+ return (fn = (nodes => nodes.length > 0)) => {
277
+ return this.#services$.pipe(map(services => services.get(service)?.nodes || []), map(targets => targets.map(id => this.#nodes.value.nodes.get(id)).filter(Boolean)));
278
+ };
279
+ if ($.startsWith('__batch__')) {
280
+ const method = $.split('__batch__')?.[1];
281
+ return (...args) => from(listRpcNodes()).pipe(mergeMap(node => (this.callRemoteService({
282
+ service,
283
+ args,
284
+ method,
285
+ node_id: node.node_id,
286
+ }).pipe(map(data => ({ node, data })), catchError(e => of({ node, e }))))));
380
287
  }
381
- if (method.startsWith('$batch_')) {
382
- const real_method = method.split('$batch_')?.[1];
383
- if (!real_method || !actions.has(real_method))
384
- return null;
385
- const nodes = this.#remote_services.get(service_name)?.nodes.filter(node => !node.isolated) || [];
386
- return (...args) => {
387
- const o = new Subject();
388
- from(nodes).pipe(mergeMap(async (node) => {
288
+ if ($ == 'set') {
289
+ return (options) => new Proxy({}, {
290
+ get: (_, method) => {
291
+ return (...args) => {
292
+ const response = this.callRemoteService({
293
+ ...options,
294
+ args,
295
+ method,
296
+ service
297
+ });
298
+ return Object.assign(response, {
299
+ then: async (s, r) => {
300
+ try {
301
+ s(await firstValueFrom(response));
302
+ }
303
+ catch (e) {
304
+ r(e);
305
+ }
306
+ }
307
+ });
308
+ };
309
+ }
310
+ });
311
+ }
312
+ return (...args) => {
313
+ const response = this.callRemoteService({
314
+ args,
315
+ method: $,
316
+ service
317
+ });
318
+ return Object.assign(response, {
319
+ then: async (s, r) => {
389
320
  try {
390
- const data = await this.rpc(service_name, real_method, args, { $node_id: node.node_id });
391
- return { node, data };
321
+ s(await firstValueFrom(response));
392
322
  }
393
- catch (error) {
394
- return { node, error };
323
+ catch (e) {
324
+ r(e);
395
325
  }
396
- })).subscribe(o);
397
- return o;
398
- };
399
- }
400
- if (actions.has(method) || method.startsWith('$')) {
401
- return new DeepProxy(method => RPCOptionsList.has(method), (method, options) => ((...args) => this.rpc(service_name, method, args, options))).nest()[method];
402
- }
403
- return null;
326
+ }
327
+ });
328
+ };
404
329
  }
405
330
  });
331
+ return new Proxy(target, {
332
+ get(target, p) {
333
+ if (p == 'then')
334
+ return target;
335
+ return target[p];
336
+ },
337
+ });
406
338
  }
407
- async link_event(event_factory, publish_buffer_ms) {
408
- const $ = new Subject();
409
- const $$ = publish_buffer_ms ? $.pipe(bufferTime(publish_buffer_ms), filter(l => l.length > 0)) : $;
410
- $$.subscribe(data => this.publish({
411
- payload: [data],
412
- topic: event_factory.name
413
- }));
339
+ linkEvent(factory) {
340
+ const topic = factory.name;
414
341
  return {
415
- publish: async (data) => $.next(data),
416
- listen: () => this.listen(event_factory)
417
- };
418
- }
419
- async #wait_service_online(service_name = 'all', fn = nodes => nodes.length > 0) {
420
- while (true) {
421
- await sleep(1000);
422
- if (service_name == 'all') {
423
- const ok = await firstValueFrom(from(this.#remote_services.values()).pipe(mergeMap(async (e) => await fn(e.nodes)), toArray(), map(list => list.every(ok => ok))));
424
- if (ok)
425
- return;
426
- }
427
- else {
428
- const nodes = this.#remote_services.get(service_name)?.nodes || [];
429
- if (await fn(nodes))
430
- return;
342
+ publish: (data) => lastValueFrom(from(this.#pubsubs.getValue().values()).pipe(mergeMap(t => t.publish(topic, data))), { defaultValue: undefined }),
343
+ listen: () => {
344
+ return this.#pubsubs.pipe(map(list => [...list.values()]), mergeAll(), mergeMap(t => t.listen(topic)));
431
345
  }
432
- }
433
- }
434
- publish({ payload, topic, node_id, local_transporter_id }, transporter) {
435
- const event = typeof topic == 'string' ? topic : topic.name;
436
- const e = {
437
- event,
438
- metadata: { smnid: this.#node_id },
439
- payload
440
346
  };
441
- if (transporter) {
442
- return transporter.publish(e);
443
- }
444
- if (node_id) {
445
- const node = this.#linked_nodes.get(node_id);
446
- if (!node)
447
- return;
448
- const target = this.#transporters.get(node.local_transporter_id);
449
- if (!target)
450
- return;
451
- return target.transporter.publish({
452
- ...e,
453
- rti: node.remote_transporter_id
454
- });
455
- }
456
- if (local_transporter_id) {
457
- const target = this.#transporters.get(local_transporter_id);
458
- return target && target.transporter.publish(e);
459
- }
460
- for (const { transporter } of this.#transporters.values()) {
461
- transporter.publish(e);
462
- }
463
347
  }
464
- listen(topic, transporter) {
465
- const topic_name = typeof topic == 'string' ? topic : topic.name;
466
- const source = transporter ? of(transporter) : SpiderMesh.$transporters;
467
- return source.pipe(mergeMap(transporter => transporter.listen(topic_name).pipe(map(msg => msg.payload.map(data => ({
468
- ...msg,
469
- payload: data,
470
- node_id: msg.metadata.smnid
471
- }))), mergeAll())));
348
+ async exposeLocalService(name, instance) {
349
+ const hooks = listBeforeMicroserviceOnlineMethods(instance);
350
+ for (const method of hooks)
351
+ await instance[method];
352
+ services$.next({
353
+ ...services$.value,
354
+ [name]: {
355
+ instance,
356
+ metadata: {},
357
+ name
358
+ }
359
+ });
472
360
  }
473
361
  }
474
362
  //# sourceMappingURL=SpiderMesh.js.map