@spider-mesh/core 1.0.9965 → 2.0.2

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 (101) 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 +27 -41
  5. package/build/src/SpiderMesh.js +314 -381
  6. package/build/src/SpiderMesh.js.map +1 -1
  7. package/build/src/decorators/BeforeMicroserviceOnline.d.ts +4 -0
  8. package/build/src/decorators/BeforeMicroserviceOnline.js +10 -0
  9. package/build/src/decorators/BeforeMicroserviceOnline.js.map +1 -0
  10. package/build/src/decorators/LimitConcurrentRunning.d.ts +2 -3
  11. package/build/src/decorators/LimitConcurrentRunning.js +2 -22
  12. package/build/src/decorators/LimitConcurrentRunning.js.map +1 -1
  13. package/build/src/decorators/Microservice.d.ts +10 -24
  14. package/build/src/decorators/Microservice.js +12 -31
  15. package/build/src/decorators/Microservice.js.map +1 -1
  16. package/build/src/decorators/NestJSExposeMicroservice.d.ts +5 -0
  17. package/build/src/decorators/NestJSExposeMicroservice.js +14 -0
  18. package/build/src/decorators/NestJSExposeMicroservice.js.map +1 -0
  19. package/build/src/decorators/NestJSLinkEvent.d.ts +9 -0
  20. package/build/src/decorators/NestJSLinkEvent.js +7 -0
  21. package/build/src/decorators/NestJSLinkEvent.js.map +1 -0
  22. package/build/src/decorators/NestJSLinkMicroservice.d.ts +6 -0
  23. package/build/src/decorators/NestJSLinkMicroservice.js +7 -0
  24. package/build/src/decorators/NestJSLinkMicroservice.js.map +1 -0
  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.d.ts +1 -0
  44. package/build/src/helpers/randomUUID.js +13 -0
  45. package/build/src/helpers/randomUUID.js.map +1 -0
  46. package/build/src/helpers/sleep.js +1 -1
  47. package/build/src/helpers/sleep.js.map +1 -1
  48. package/build/src/index.d.ts +16 -12
  49. package/build/src/index.js +16 -9
  50. package/build/src/index.js.map +1 -1
  51. package/build/src/interfaces/DiscoveryTransporter.d.ts +5 -0
  52. package/build/src/interfaces/DiscoveryTransporter.js +3 -0
  53. package/build/src/interfaces/DiscoveryTransporter.js.map +1 -0
  54. package/build/src/interfaces/PubsubTransporter.d.ts +10 -0
  55. package/build/src/interfaces/PubsubTransporter.js +3 -0
  56. package/build/src/interfaces/PubsubTransporter.js.map +1 -0
  57. package/build/src/interfaces/RemoteService.d.ts +12 -18
  58. package/build/src/interfaces/RpcTransporter.d.ts +30 -0
  59. package/build/src/interfaces/RpcTransporter.js +3 -0
  60. package/build/src/interfaces/RpcTransporter.js.map +1 -0
  61. package/build/src/interfaces/SpiderMeshNode.d.ts +14 -18
  62. package/build/tsconfig.tsbuildinfo +1 -0
  63. package/package.json +14 -9
  64. package/tsconfig.json +7 -3
  65. package/build/src/Encoder.d.ts +0 -5
  66. package/build/src/Encoder.js +0 -42
  67. package/build/src/Encoder.js.map +0 -1
  68. package/build/src/RPCOptions.d.ts +0 -11
  69. package/build/src/RPCOptions.js +0 -13
  70. package/build/src/RPCOptions.js.map +0 -1
  71. package/build/src/builtin-transporter/BuiltinTransporter.d.ts +0 -15
  72. package/build/src/builtin-transporter/BuiltinTransporter.js +0 -157
  73. package/build/src/builtin-transporter/BuiltinTransporter.js.map +0 -1
  74. package/build/src/builtin-transporter/RxjsTcpServer.d.ts +0 -11
  75. package/build/src/builtin-transporter/RxjsTcpServer.js +0 -31
  76. package/build/src/builtin-transporter/RxjsTcpServer.js.map +0 -1
  77. package/build/src/builtin-transporter/RxjsTcpSocket.d.ts +0 -18
  78. package/build/src/builtin-transporter/RxjsTcpSocket.js +0 -65
  79. package/build/src/builtin-transporter/RxjsTcpSocket.js.map +0 -1
  80. package/build/src/builtin-transporter/RxjsUdpBroadcaster.d.ts +0 -19
  81. package/build/src/builtin-transporter/RxjsUdpBroadcaster.js +0 -48
  82. package/build/src/builtin-transporter/RxjsUdpBroadcaster.js.map +0 -1
  83. package/build/src/const.d.ts +0 -6
  84. package/build/src/const.js +0 -8
  85. package/build/src/const.js.map +0 -1
  86. package/build/src/decorators/DeepProxy.d.ts +0 -7
  87. package/build/src/decorators/DeepProxy.js +0 -24
  88. package/build/src/decorators/DeepProxy.js.map +0 -1
  89. package/build/src/decorators/ListenEvent.d.ts +0 -17
  90. package/build/src/decorators/ListenEvent.js +0 -23
  91. package/build/src/decorators/ListenEvent.js.map +0 -1
  92. package/build/src/decorators/OnMicroserviceReady.d.ts +0 -6
  93. package/build/src/decorators/OnMicroserviceReady.js +0 -14
  94. package/build/src/decorators/OnMicroserviceReady.js.map +0 -1
  95. package/build/src/helpers/waitFirstEvent.d.ts +0 -3
  96. package/build/src/helpers/waitFirstEvent.js +0 -5
  97. package/build/src/helpers/waitFirstEvent.js.map +0 -1
  98. package/build/src/interfaces/SpiderMeshTransporter.d.ts +0 -24
  99. package/build/src/interfaces/SpiderMeshTransporter.js +0 -2
  100. package/build/src/interfaces/SpiderMeshTransporter.js.map +0 -1
  101. package/yarn.lock +0 -134
@@ -1,297 +1,237 @@
1
- import { randomUUID } from 'crypto';
2
- import { get } from 'http';
3
- import { networkInterfaces } from 'os';
4
- import { DeepProxy } from './decorators/DeepProxy.js';
5
- import { RPCOptionsList } from './RPCOptions.js';
6
- import os from 'os';
7
- import { listEventSubscribers } from './decorators/ListenEvent.js';
8
- import { listReadyHookMethods } from './decorators/OnMicroserviceReady.js';
9
- import { BehaviorSubject, Subject, bufferTime, filter, from, map, mergeAll, mergeMap, throttleTime } from 'rxjs';
10
- import { readFileSync } from 'fs';
11
- import { serviceInstanceList } from './decorators/Microservice.js';
12
- import { sleep } from './helpers/sleep.js';
13
- import { BuiltinTransporter } from './builtin-transporter/BuiltinTransporter.js';
14
- import { DEBUG, NAMEPSACE, NODE_ID } from './const.js';
15
- export class SpiderMeshConfig {
16
- namespace;
17
- node_id;
18
- transporter;
19
- }
20
- const PACKAGE_JSON = JSON.parse(readFileSync(`package.json`, 'utf8')) || {};
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";
21
13
  export class SpiderMesh {
22
- transporter;
23
- #$isolated = new BehaviorSubject(false);
24
- #local_rpc_services = new Map();
25
- #remote_rpc_services = new Map;
26
- #linked_nodes = new Map();
27
- #rpc_queue = new Map;
28
- #public_ip = new Promise(async (s) => {
29
- for (let i = 1; i <= 5; i++) {
30
- const ip = await new Promise(s => {
31
- get('http://api.ipify.org', (res) => {
32
- res.setEncoding('utf8');
33
- let rawData = '';
34
- res.on('data', (chunk) => { rawData += chunk; });
35
- res.on('end', () => s(rawData));
36
- }).on('error', () => s(null));
37
- });
38
- if (ip && ip != 'Bad Gateway')
39
- return s(ip);
40
- await new Promise(s => setTimeout(s, 100));
41
- }
42
- return s(null);
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
43
29
  });
44
- $nodes_monitor = new Subject();
45
- #initing;
46
- constructor(transporter = new BuiltinTransporter(NODE_ID, NAMEPSACE)) {
47
- this.transporter = transporter;
48
- DEBUG && console.log(`Node online : ${NODE_ID}`);
49
- this.#initing = this.#init();
50
- }
51
- static add_transporter(transporter) {
52
- new this(transporter);
53
- }
54
- async $metadata(revalidate_on_join) {
55
- const ips = Object.values(networkInterfaces()).map(itf => itf?.map(ip => ip.address) || []).flat(2);
56
- const services = [...this.#local_rpc_services.entries()].reduce((p, [service_id, { metadata }]) => ({ ...p, [service_id]: metadata }), {});
57
- const metadata = {
58
- id: this.transporter.node_id,
59
- name: PACKAGE_JSON?.name || 'UNKNOWN',
60
- version: PACKAGE_JSON?.version || '1.0.0',
61
- path: process.cwd(),
62
- uptime: process.uptime(),
63
- hostname: os.hostname(),
64
- plaform: os.platform(),
65
- node_version: process.version,
66
- public_ip: await this.#public_ip,
67
- ip_addresses: ips,
68
- last_online: Date.now(),
69
- active: true,
70
- online: true,
71
- services,
72
- namespace: this.transporter.namespace,
73
- linked: [...this.#linked_nodes.keys()],
74
- isolated_nodes: [...new Set([...this.#linked_nodes.values()].filter(node => node.isolate).map(node => node.id))],
75
- isolate: this.#$isolated.value,
76
- revalidate_on_join
77
- };
78
- return metadata;
79
- }
80
- async $update_isolate_mode(active) {
81
- this.#$isolated.next(active);
82
- const me = await this.$metadata();
83
- await this.publish('#join', me);
84
- return me;
85
- }
86
- async #node_event_handler({ data: msg, sender_node_id }) {
87
- if (msg.type == 'rpc') {
88
- if (msg.service == 'SpiderMesh' && !msg.method.startsWith('$')) {
89
- sender_node_id && this.transporter.publish({
90
- data: [{
91
- id: msg.id,
92
- type: 'error',
93
- error: 'NOT_ALLOW'
94
- }],
95
- event: sender_node_id,
96
- node_id: sender_node_id
97
- });
98
- return;
99
- }
100
- const instance = msg.service == 'SpiderMesh' ? this : this.#local_rpc_services.get(msg.service)?.instance;
101
- if (!instance)
102
- return this.transporter.publish({
103
- data: [{
104
- id: msg.id,
105
- type: 'error',
106
- error: 'SERVICE_NOT_FOUND'
107
- }],
108
- event: sender_node_id,
109
- node_id: sender_node_id
110
- });
111
- if (typeof instance?.[msg.method] != 'function')
112
- return this.transporter.publish({
113
- data: [{
114
- id: msg.id,
115
- type: 'error',
116
- error: 'SERVICE_METHOD_NOT_FOUND'
117
- }],
118
- event: sender_node_id,
119
- node_id: sender_node_id
120
- });
121
- const args = msg.args.map(arg => arg != '__FUNCTION__' ? arg : (...args) => {
122
- sender_node_id && this.transporter.publish({
123
- event: sender_node_id,
124
- node_id: sender_node_id,
125
- data: [{ id: msg.id, type: 'callback', args }]
126
- });
127
- });
128
- try {
129
- const response = await instance?.[msg.method]?.(...args);
130
- sender_node_id && this.transporter.publish({
131
- event: sender_node_id,
132
- node_id: sender_node_id,
133
- data: [{ id: msg.id, type: 'response', response }]
134
- });
135
- }
136
- catch (error) {
137
- sender_node_id && this.transporter.publish({
138
- event: sender_node_id,
139
- node_id: sender_node_id,
140
- data: [{ id: msg.id, type: 'error', error: error?.code || error?.message || error || 'UNKNOWN' }]
141
- });
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 });
142
40
  }
143
- return;
144
- }
145
- const request = this.#rpc_queue.get(msg.id);
146
- if (!request)
147
- return;
148
- if (msg.type == 'callback') {
149
- request.args?.[msg.index]?.();
150
- return;
151
- }
152
- if (msg.type == 'ack') {
153
- request.last_ping = Date.now();
154
- return;
155
- }
156
- if (msg.type == 'error') {
157
- request.reject(msg.error);
158
- this.#rpc_queue.delete(msg.id);
159
- return;
160
- }
161
- if (msg.type == 'response') {
162
- request.success(msg.response);
163
- this.#rpc_queue.delete(msg.id);
164
- return;
165
- }
166
- }
167
- async #init() {
168
- await this.transporter.start();
169
- this.listen(this.transporter.node_id).subscribe(evt => this.#node_event_handler(evt));
170
- this.listen('#join').subscribe(({ data }) => {
171
- this.#on_node_discovered(data);
172
- });
173
- this.transporter.$nodes_status.subscribe(async ({ node_id, online }) => {
174
- !online && this.#on_node_offline(node_id);
175
- online && this.transporter.publish({
176
- event: '#join',
177
- data: [await this.$metadata()],
178
- node_id
179
- });
180
- });
181
- serviceInstanceList.pipe(filter(i => i.namespace == this.transporter.namespace), mergeMap(async ({ instance, metadata }) => {
182
- await this.#active_local_service(instance, metadata);
183
- await this.#active_ready_hooks(instance);
184
- }), throttleTime(1000), mergeMap(async () => {
185
- const me = await this.$metadata();
186
- await this.publish('#join', me);
187
- })).subscribe();
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();
188
55
  }
189
- async #on_node_discovered(node) {
190
- if (node.id == this.transporter.node_id)
191
- return;
192
- if (this.#$isolated.value)
193
- return;
194
- const peer_updated = node.linked.includes(this.transporter.node_id);
195
- DEBUG && console.log(`[${new Date().toLocaleString()}:${new Date().getMilliseconds()}] New node `, node, { peer_updated });
196
- const new_node = {
197
- ...node,
198
- online: true
199
- };
200
- this.#linked_nodes.set(node.id, new_node);
201
- (!peer_updated || node.revalidate_on_join) && await this.transporter.publish({
202
- event: '#join',
203
- data: [await this.$metadata(!peer_updated)],
204
- node_id: node.id
205
- });
206
- peer_updated && Object.values(node.services).forEach(({ instance: service }) => {
207
- const $service = this.#remote_rpc_services.get(service);
208
- if (!$service) {
209
- this.#remote_rpc_services.set(service, {
210
- last_call_index: -1,
211
- nodes: [node]
212
- });
213
- 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 };
71
+ }
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;
82
+ }
83
+ return { node, transporter };
214
84
  }
215
- if ($service.nodes) {
216
- const index = $service.nodes.findIndex(n => n.id == new_node.id);
217
- index >= 0 ? ($service.nodes[index] = new_node) : $service.nodes.push(new_node);
85
+ }).filter(Boolean).map(e => e);
86
+ return nodes[state.index++ % nodes.length];
87
+ }
88
+ callRemoteService(options) {
89
+ return of(0).pipe(mergeMap(async () => {
90
+ await this.waitServiceOnline(options.service);
91
+ const target = this.#selectRpcTarget(options);
92
+ if (!target)
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;
218
103
  }
219
- else {
220
- $service.nodes = [new_node];
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;
221
114
  }
222
- });
223
- this.$nodes_monitor.next(node);
115
+ throw e;
116
+ }));
224
117
  }
225
- async #on_node_offline(id) {
226
- DEBUG && console.log(`[${new Date().toLocaleString()}] Node ${id} offline`);
227
- for (const service of this.#remote_rpc_services.values()) {
228
- service.nodes = service.nodes.filter(node => node.id != id);
229
- }
230
- for (const [rid, { target_node_id, reject }] of this.#rpc_queue) {
231
- if (target_node_id == id) {
232
- reject(new Error('SERVICE_OFFLINE'));
233
- this.#rpc_queue.delete(rid);
234
- }
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);
135
+ }
136
+ catch (err) {
137
+ rpc.callback(throwError(() => err));
138
+ }
139
+ }
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
+ }
158
+ }
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();
235
189
  }
236
- const node = this.#linked_nodes.get(id);
237
- node && this.$nodes_monitor.next({ ...node, online: false });
238
- }
239
- #caculate_rpc_node_id(service_name, options = {}) {
240
- const current = this.#remote_rpc_services.get(service_name);
241
- if (!current || current.nodes.length == 0)
242
- return;
243
- if (options.$node_id) {
244
- if (current.nodes.some(node => node.id == options.$node_id))
245
- return options.$node_id;
246
- return;
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();
247
207
  }
248
- if (options.$ip) {
249
- const node = current
250
- .nodes
251
- .filter(node => node.public_ip == options.$ip || node.ip_addresses.includes(options.$ip))
252
- .sort((a, b) => b.last_online - a.last_online)[0];
253
- return node ? node.id : null;
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
216
+ });
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();
254
223
  }
255
- const index = ++current.last_call_index % current.nodes.length;
256
- return current.nodes[index].id;
257
224
  }
258
- async rpc(service, method, args, options) {
259
- await this.#initing;
260
- return await new Promise(async (success, r) => {
261
- const reject = (err) => (options.$fallback !== undefined) ? success(options.$fallback) : r(err);
262
- const rid = randomUUID();
263
- options.$timeout && setTimeout(() => {
264
- reject(new Error('TIMEOUT'));
265
- this.#rpc_queue.delete(rid);
266
- }, options.$timeout);
267
- !options.$nevermind && this.#rpc_queue.set(rid, {
268
- args,
269
- last_ping: 0,
270
- reject,
271
- request_time: Date.now(),
272
- success,
273
- timeout: options.$timeout,
274
- target_node_id: options.$node_id
275
- });
276
- const retry_count = options.$retry || 1;
277
- for (let i = retry_count; i > 0; i--) {
278
- try {
279
- const node_id = this.#caculate_rpc_node_id(service, options);
280
- if (!node_id)
281
- return reject(Object.assign(new Error(`SERVICE_NOT_RUNNING:${service}`), { service }));
282
- await this.publish(service, { type: 'rpc', id: rid, args, method, service }, node_id);
283
- return;
284
- }
285
- catch (e) { }
286
- i > 1 && await new Promise(s => setTimeout(s, options.$retry_delay || 1000));
287
- }
288
- if (options.$fallback)
289
- return success(options.$fallback);
290
- });
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)));
291
231
  }
292
- async link_remote_service(factory, wait_service_online = false) {
293
- const service_name = factory.name;
294
- const omit_properties = new Set([
232
+ linkRemoteService(factory) {
233
+ const service = factory.name;
234
+ const omitProperties = new Set([
295
235
  'caller',
296
236
  'callee',
297
237
  'arguments',
@@ -313,117 +253,110 @@ export class SpiderMesh {
313
253
  'beforeApplicationShutdown',
314
254
  'onApplicationShutdown'
315
255
  ]);
316
- const actions = new Set();
317
- for (let f = factory.prototype; f != null; f = Object.getPrototypeOf(f)) {
318
- for (const method of Object.getOwnPropertyNames(f).filter(method => !omit_properties.has(method))) {
319
- typeof f[method] == 'function' && actions.add(method);
320
- }
321
- }
322
- if (!this.#remote_rpc_services.has(service_name)) {
323
- this.#remote_rpc_services.set(service_name, { last_call_index: -1, nodes: [] });
324
- }
325
- wait_service_online && await this.#wait_service_online(service_name);
326
- return new Proxy({}, {
327
- get: (_, method) => {
328
- if (method == '$wait_service_online') {
329
- return () => this.#wait_service_online(service_name);
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);
330
272
  }
331
- if (method == '$watch')
332
- return () => this.$nodes_monitor.pipe(filter(node => !!node.services[service_name]));
333
- if (method == '$list_nodes') {
334
- return (() => this.#remote_rpc_services.get(service_name)?.nodes.filter(node => !node.isolate).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 }))))));
335
287
  }
336
- if (method.startsWith('$batch_')) {
337
- const real_method = method.split('$batch_')?.[1];
338
- if (!real_method || !actions.has(real_method))
339
- return null;
340
- const nodes = this.#remote_rpc_services.get(service_name)?.nodes.filter(node => !node.isolate) || [];
341
- return (...args) => {
342
- const o = new Subject();
343
- 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) => {
344
320
  try {
345
- const data = await this.rpc(service_name, real_method, args, { $node_id: node.id });
346
- return { data, node, error: null };
321
+ s(await firstValueFrom(response));
347
322
  }
348
- catch (error) {
349
- return { node, data: null, error };
323
+ catch (e) {
324
+ r(e);
350
325
  }
351
- })).subscribe(o);
352
- return o;
353
- };
354
- }
355
- if (actions.has(method) || method.startsWith('$')) {
356
- return new DeepProxy(method => RPCOptionsList.has(method), (method, options) => (async (...args) => {
357
- const safe = options.$safe_mode;
358
- try {
359
- const data = await this.rpc(service_name, method, args, options);
360
- return safe ? [null, data] : data;
361
326
  }
362
- catch (e) {
363
- if (safe)
364
- return [e, null];
365
- throw e;
366
- }
367
- })).nest()[method];
368
- }
369
- return null;
327
+ });
328
+ };
370
329
  }
371
330
  });
331
+ return new Proxy(target, {
332
+ get(target, p) {
333
+ if (p == 'then')
334
+ return target;
335
+ return target[p];
336
+ },
337
+ });
372
338
  }
373
- async link_event(event_factory, publish_buffer_ms) {
374
- const $ = new Subject();
375
- const $$ = publish_buffer_ms ? $.pipe(bufferTime(publish_buffer_ms), filter(l => l.length > 0)) : $;
376
- $$.subscribe(data => this.publish(event_factory.name, data));
377
- const event_hub = {
378
- publish: async (data) => $.next(data),
379
- listen: () => this.listen(event_factory.name)
339
+ linkEvent(factory) {
340
+ const topic = factory.name;
341
+ 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)));
345
+ }
380
346
  };
381
- return event_hub;
382
347
  }
383
- async #active_local_service(instance, metadata) {
384
- const prototype = Object.getPrototypeOf(instance);
385
- const name = prototype.constructor.name;
386
- this.#local_rpc_services.set(name, { instance, metadata });
387
- this.listen(name).subscribe(evt => this.#node_event_handler(evt));
388
- const event_subscribers = listEventSubscribers(prototype);
389
- for (const { event, method, buffer_ms } of event_subscribers) {
390
- const $ = this.listen(event).pipe(filter(() => !this.#$isolated.value));
391
- const $$ = buffer_ms ? $.pipe(bufferTime(buffer_ms), filter(l => l.length > 0)) : $;
392
- $$.subscribe(e => instance[method]?.(e));
393
- }
394
- }
395
- async #wait_service_online(service_name = 'all') {
396
- if (this.#remote_rpc_services.size == 0)
397
- return;
398
- while (true) {
399
- await sleep(500);
400
- const nodes = service_name == 'all' ? ([...this.#remote_rpc_services.values()].map(s => s.nodes).flat(2)) : (this.#remote_rpc_services.get(service_name)?.nodes || []);
401
- if (nodes.length > 0)
402
- break;
403
- }
404
- }
405
- async #active_ready_hooks(instance) {
406
- await this.#wait_service_online();
407
- for (const { method } of listReadyHookMethods(Object.getPrototypeOf(instance))) {
408
- instance[method]?.();
409
- }
410
- }
411
- async publish(topic, payload, node_id) {
412
- await this.#initing;
413
- const event = typeof topic == 'string' ? topic : topic.name;
414
- const data = Array.isArray(payload) ? payload : [payload];
415
- this.transporter.publish({ event, data, node_id });
416
- }
417
- listen(topic) {
418
- const topic_name = typeof topic == 'string' ? topic : topic.name;
419
- return this.transporter.listen(topic_name).pipe(map(e => {
420
- if (!Array.isArray(e.data))
421
- return [];
422
- return e.data.map(data => ({
423
- data,
424
- sender_node_id: e.sender_node_id
425
- }));
426
- }), 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
+ });
427
360
  }
428
361
  }
429
362
  //# sourceMappingURL=SpiderMesh.js.map