@riaskov/nevo-messaging 1.0.1 → 1.1.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.
- package/LICENSE +1 -1
- package/README.md +202 -67
- package/dist/common/access-control.d.ts +15 -0
- package/dist/common/access-control.js +94 -0
- package/dist/common/base.client.d.ts +7 -2
- package/dist/common/base.client.js +16 -2
- package/dist/common/base.controller.d.ts +6 -1
- package/dist/common/base.controller.js +68 -4
- package/dist/common/constants.d.ts +4 -0
- package/dist/common/constants.js +7 -0
- package/dist/common/discovery.d.ts +8 -0
- package/dist/common/discovery.js +35 -0
- package/dist/common/error-code.d.ts +2 -1
- package/dist/common/error-code.js +1 -0
- package/dist/common/error-messages.js +2 -1
- package/dist/common/index.d.ts +3 -0
- package/dist/common/index.js +3 -0
- package/dist/common/service-utils.d.ts +2 -0
- package/dist/common/service-utils.js +8 -0
- package/dist/common/types.d.ts +62 -0
- package/dist/signal-router.utils.d.ts +3 -1
- package/dist/signal-router.utils.js +70 -6
- package/dist/transports/http/http.client-base.d.ts +13 -0
- package/dist/transports/http/http.client-base.js +33 -0
- package/dist/transports/http/http.config.d.ts +8 -0
- package/dist/transports/http/http.config.js +16 -0
- package/dist/transports/http/http.signal-router.decorator.d.ts +3 -0
- package/dist/transports/http/http.signal-router.decorator.js +18 -0
- package/dist/transports/http/http.transport.controller.d.ts +21 -0
- package/dist/transports/http/http.transport.controller.js +114 -0
- package/dist/transports/http/index.d.ts +5 -0
- package/dist/transports/http/index.js +21 -0
- package/dist/transports/http/nevo-http.client.d.ts +54 -0
- package/dist/transports/http/nevo-http.client.js +280 -0
- package/dist/transports/index.d.ts +3 -0
- package/dist/transports/index.js +3 -0
- package/dist/transports/kafka/kafka.client-base.d.ts +5 -0
- package/dist/transports/kafka/kafka.client-base.js +15 -0
- package/dist/transports/kafka/kafka.config.d.ts +7 -0
- package/dist/transports/kafka/kafka.config.js +48 -2
- package/dist/transports/kafka/kafka.signal-router.decorator.js +2 -1
- package/dist/transports/kafka/nevo-kafka.client.d.ts +42 -0
- package/dist/transports/kafka/nevo-kafka.client.js +210 -4
- package/dist/transports/nats/index.d.ts +4 -0
- package/dist/transports/nats/index.js +20 -0
- package/dist/transports/nats/nats.client-base.d.ts +13 -0
- package/dist/transports/nats/nats.client-base.js +33 -0
- package/dist/transports/nats/nats.config.d.ts +8 -0
- package/dist/transports/nats/nats.config.js +16 -0
- package/dist/transports/nats/nats.signal-router.decorator.d.ts +6 -0
- package/dist/transports/nats/nats.signal-router.decorator.js +49 -0
- package/dist/transports/nats/nevo-nats.client.d.ts +55 -0
- package/dist/transports/nats/nevo-nats.client.js +210 -0
- package/dist/transports/socket-io/index.d.ts +4 -0
- package/dist/transports/socket-io/index.js +20 -0
- package/dist/transports/socket-io/nevo-socket.client.d.ts +50 -0
- package/dist/transports/socket-io/nevo-socket.client.js +202 -0
- package/dist/transports/socket-io/socket.client-base.d.ts +13 -0
- package/dist/transports/socket-io/socket.client-base.js +33 -0
- package/dist/transports/socket-io/socket.config.d.ts +8 -0
- package/dist/transports/socket-io/socket.config.js +16 -0
- package/dist/transports/socket-io/socket.signal-router.decorator.d.ts +13 -0
- package/dist/transports/socket-io/socket.signal-router.decorator.js +109 -0
- package/package.json +11 -5
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NevoNatsClient = void 0;
|
|
4
|
+
const nats_1 = require("nats");
|
|
5
|
+
const common_1 = require("../../common");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
class NevoNatsClient {
|
|
8
|
+
constructor(nc, serviceNames, options) {
|
|
9
|
+
this.codec = (0, nats_1.StringCodec)();
|
|
10
|
+
this.inFlight = new Set();
|
|
11
|
+
this.discoveryRegistry = new common_1.DiscoveryRegistry();
|
|
12
|
+
this.nc = nc;
|
|
13
|
+
this.serviceNames = serviceNames.map((name) => name.toLowerCase());
|
|
14
|
+
this.timeoutMs = options?.timeoutMs || 20000;
|
|
15
|
+
this.debug = options?.debug || false;
|
|
16
|
+
this.serviceName = options?.serviceName;
|
|
17
|
+
this.authToken = options?.authToken;
|
|
18
|
+
this.backoffEnabled = options?.backoff?.enabled !== false;
|
|
19
|
+
this.backoffBaseMs = options?.backoff?.baseMs || 100;
|
|
20
|
+
this.backoffMaxMs = options?.backoff?.maxMs || 2000;
|
|
21
|
+
this.backoffMaxAttempts = options?.backoff?.maxAttempts || 0;
|
|
22
|
+
this.backoffJitter = options?.backoff?.jitter !== false;
|
|
23
|
+
this.discoveryEnabled = options?.discovery?.enabled !== false;
|
|
24
|
+
this.discoveryHeartbeatIntervalMs = options?.discovery?.heartbeatIntervalMs || 5000;
|
|
25
|
+
this.discoveryTtlMs = options?.discovery?.ttlMs || 15000;
|
|
26
|
+
if (this.discoveryEnabled) {
|
|
27
|
+
void this.initDiscovery();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
static async create(serviceNames, options) {
|
|
31
|
+
const nc = await (0, nats_1.connect)({
|
|
32
|
+
servers: options?.servers && options.servers.length > 0 ? options.servers : ["nats://127.0.0.1:4222"]
|
|
33
|
+
});
|
|
34
|
+
return new NevoNatsClient(nc, serviceNames, options);
|
|
35
|
+
}
|
|
36
|
+
createMessagePayload(method, params, type) {
|
|
37
|
+
const uuid = (0, node_crypto_1.randomUUID)();
|
|
38
|
+
const meta = {
|
|
39
|
+
type,
|
|
40
|
+
service: this.serviceName,
|
|
41
|
+
ts: Date.now(),
|
|
42
|
+
auth: { token: this.authToken }
|
|
43
|
+
};
|
|
44
|
+
return (0, common_1.stringifyWithBigInt)({ uuid, method, params, meta });
|
|
45
|
+
}
|
|
46
|
+
async waitForInFlightSlot(key) {
|
|
47
|
+
if (!this.backoffEnabled) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let attempt = 0;
|
|
51
|
+
let delay = this.backoffBaseMs;
|
|
52
|
+
while (this.inFlight.has(key)) {
|
|
53
|
+
attempt++;
|
|
54
|
+
if (this.backoffMaxAttempts > 0 && attempt > this.backoffMaxAttempts) {
|
|
55
|
+
throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
|
|
56
|
+
message: `Backoff exceeded for ${key}`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const jitter = this.backoffJitter ? Math.floor(Math.random() * delay * 0.2) : 0;
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
61
|
+
delay = Math.min(this.backoffMaxMs, delay * 2);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async query(serviceName, method, params) {
|
|
65
|
+
const normalizedServiceName = serviceName.toLowerCase();
|
|
66
|
+
if (!this.serviceNames.includes(normalizedServiceName)) {
|
|
67
|
+
throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
|
|
68
|
+
message: `Service "${serviceName}" is not registered in nevo nats client`,
|
|
69
|
+
availableServices: this.serviceNames
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const subject = `${normalizedServiceName}-events`;
|
|
73
|
+
const payload = this.createMessagePayload(method, params, "query");
|
|
74
|
+
const inFlightKey = `${normalizedServiceName}:${method}`;
|
|
75
|
+
if (this.debug) {
|
|
76
|
+
console.log(`[NevoNatsClient] Sending query to ${subject}:`, { method, params });
|
|
77
|
+
}
|
|
78
|
+
let inFlightAcquired = false;
|
|
79
|
+
await this.waitForInFlightSlot(inFlightKey);
|
|
80
|
+
this.inFlight.add(inFlightKey);
|
|
81
|
+
inFlightAcquired = true;
|
|
82
|
+
try {
|
|
83
|
+
const msg = await this.nc.request(subject, this.codec.encode(payload), { timeout: this.timeoutMs });
|
|
84
|
+
const response = (0, common_1.parseWithBigInt)(this.codec.decode(msg.data));
|
|
85
|
+
if (response?.params?.result === "error" && response?.params?.error) {
|
|
86
|
+
const errorData = response.params.error;
|
|
87
|
+
const error = new common_1.MessagingError(errorData.code, errorData.details, errorData.service || serviceName);
|
|
88
|
+
if (process.env["MODE"] !== "production" && errorData.stack) {
|
|
89
|
+
error.stack = errorData.stack;
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
return response?.params?.result;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
if (inFlightAcquired) {
|
|
97
|
+
this.inFlight.delete(inFlightKey);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async emit(serviceName, method, params) {
|
|
102
|
+
const normalizedServiceName = serviceName.toLowerCase();
|
|
103
|
+
if (!this.serviceNames.includes(normalizedServiceName)) {
|
|
104
|
+
throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
|
|
105
|
+
message: `Service "${serviceName}" is not registered in nevo nats client`,
|
|
106
|
+
availableServices: this.serviceNames
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const subject = `${normalizedServiceName}-events`;
|
|
110
|
+
const payload = this.createMessagePayload(method, params, "emit");
|
|
111
|
+
if (this.debug) {
|
|
112
|
+
console.log(`[NevoNatsClient] Emitting to ${subject}:`, { method, params });
|
|
113
|
+
}
|
|
114
|
+
this.nc.publish(subject, this.codec.encode(payload));
|
|
115
|
+
}
|
|
116
|
+
async publish(serviceName, method, params) {
|
|
117
|
+
const normalizedServiceName = serviceName.toLowerCase();
|
|
118
|
+
if (!this.serviceNames.includes(normalizedServiceName)) {
|
|
119
|
+
throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
|
|
120
|
+
message: `Service "${serviceName}" is not registered in nevo nats client`,
|
|
121
|
+
availableServices: this.serviceNames
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const subject = `${normalizedServiceName}${common_1.DEFAULT_SUBSCRIPTION_SUFFIX}`;
|
|
125
|
+
const payload = this.createMessagePayload(method, params, "sub");
|
|
126
|
+
if (this.debug) {
|
|
127
|
+
console.log(`[NevoNatsClient] Publishing to ${subject}:`, { method, params });
|
|
128
|
+
}
|
|
129
|
+
this.nc.publish(subject, this.codec.encode(payload));
|
|
130
|
+
}
|
|
131
|
+
async broadcast(method, params) {
|
|
132
|
+
const payload = this.createMessagePayload(method, params, "broadcast");
|
|
133
|
+
if (this.debug) {
|
|
134
|
+
console.log(`[NevoNatsClient] Broadcasting to ${common_1.DEFAULT_BROADCAST_TOPIC}:`, { method, params });
|
|
135
|
+
}
|
|
136
|
+
this.nc.publish(common_1.DEFAULT_BROADCAST_TOPIC, this.codec.encode(payload));
|
|
137
|
+
}
|
|
138
|
+
async subscribe(serviceName, method, options, handler) {
|
|
139
|
+
const normalizedServiceName = serviceName.toLowerCase();
|
|
140
|
+
const isBroadcast = normalizedServiceName === common_1.DEFAULT_BROADCAST_TOPIC;
|
|
141
|
+
if (!isBroadcast && !this.serviceNames.includes(normalizedServiceName)) {
|
|
142
|
+
throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
|
|
143
|
+
message: `Service "${serviceName}" is not registered in nevo nats client`,
|
|
144
|
+
availableServices: this.serviceNames
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const subject = isBroadcast ? common_1.DEFAULT_BROADCAST_TOPIC : `${normalizedServiceName}${common_1.DEFAULT_SUBSCRIPTION_SUFFIX}`;
|
|
148
|
+
const sub = this.nc.subscribe(subject);
|
|
149
|
+
(async () => {
|
|
150
|
+
for await (const msg of sub) {
|
|
151
|
+
const raw = this.codec.decode(msg.data);
|
|
152
|
+
const payload = (0, common_1.parseWithBigInt)(raw);
|
|
153
|
+
if (method && payload.method !== method) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const context = {
|
|
157
|
+
meta: payload.meta || {},
|
|
158
|
+
ack: async () => {
|
|
159
|
+
return;
|
|
160
|
+
},
|
|
161
|
+
nack: async () => {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
await handler(payload.params, context);
|
|
166
|
+
}
|
|
167
|
+
})();
|
|
168
|
+
return {
|
|
169
|
+
unsubscribe: async () => {
|
|
170
|
+
sub.unsubscribe();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
getAvailableServices() {
|
|
175
|
+
return [...this.serviceNames];
|
|
176
|
+
}
|
|
177
|
+
getDiscoveredServices() {
|
|
178
|
+
this.discoveryRegistry.prune(this.discoveryTtlMs);
|
|
179
|
+
return this.discoveryRegistry.list();
|
|
180
|
+
}
|
|
181
|
+
isServiceAvailable(serviceName) {
|
|
182
|
+
return this.discoveryRegistry.isAvailable(serviceName, this.discoveryTtlMs);
|
|
183
|
+
}
|
|
184
|
+
async initDiscovery() {
|
|
185
|
+
this.discoverySubscription = this.nc.subscribe(common_1.DEFAULT_DISCOVERY_TOPIC);
|
|
186
|
+
(async () => {
|
|
187
|
+
for await (const msg of this.discoverySubscription) {
|
|
188
|
+
try {
|
|
189
|
+
const payload = (0, common_1.parseWithBigInt)(this.codec.decode(msg.data));
|
|
190
|
+
if (payload?.serviceName) {
|
|
191
|
+
this.discoveryRegistry.update(payload);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
console.error("[NevoNatsClient] Failed to parse discovery message", error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
})();
|
|
199
|
+
this.discoveryTimer = setInterval(() => {
|
|
200
|
+
const announcement = {
|
|
201
|
+
serviceName: this.serviceName || "unknown",
|
|
202
|
+
clientId: this.serviceName,
|
|
203
|
+
transport: "nats",
|
|
204
|
+
ts: Date.now()
|
|
205
|
+
};
|
|
206
|
+
this.nc.publish(common_1.DEFAULT_DISCOVERY_TOPIC, this.codec.encode((0, common_1.stringifyWithBigInt)(announcement)));
|
|
207
|
+
}, this.discoveryHeartbeatIntervalMs);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
exports.NevoNatsClient = NevoNatsClient;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./nevo-socket.client"), exports);
|
|
18
|
+
__exportStar(require("./socket.client-base"), exports);
|
|
19
|
+
__exportStar(require("./socket.signal-router.decorator"), exports);
|
|
20
|
+
__exportStar(require("./socket.config"), exports);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Subscription, SubscriptionContext, SubscriptionOptions } from "../../common";
|
|
2
|
+
export interface NevoSocketClientOptions {
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
serviceName?: string;
|
|
6
|
+
authToken?: string;
|
|
7
|
+
backoff?: {
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
baseMs?: number;
|
|
10
|
+
maxMs?: number;
|
|
11
|
+
maxAttempts?: number;
|
|
12
|
+
jitter?: boolean;
|
|
13
|
+
};
|
|
14
|
+
discovery?: {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
heartbeatIntervalMs?: number;
|
|
17
|
+
ttlMs?: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare class NevoSocketClient {
|
|
21
|
+
private readonly serviceUrls;
|
|
22
|
+
private readonly timeoutMs;
|
|
23
|
+
private readonly debug;
|
|
24
|
+
private readonly serviceName?;
|
|
25
|
+
private readonly authToken?;
|
|
26
|
+
private readonly backoffEnabled;
|
|
27
|
+
private readonly backoffBaseMs;
|
|
28
|
+
private readonly backoffMaxMs;
|
|
29
|
+
private readonly backoffMaxAttempts;
|
|
30
|
+
private readonly backoffJitter;
|
|
31
|
+
private readonly inFlight;
|
|
32
|
+
private readonly sockets;
|
|
33
|
+
private readonly discoveryRegistry;
|
|
34
|
+
private readonly discoveryEnabled;
|
|
35
|
+
private readonly discoveryHeartbeatIntervalMs;
|
|
36
|
+
private readonly discoveryTtlMs;
|
|
37
|
+
private discoveryTimer?;
|
|
38
|
+
constructor(serviceUrls: Record<string, string>, options?: NevoSocketClientOptions);
|
|
39
|
+
private createMessagePayload;
|
|
40
|
+
private waitForInFlightSlot;
|
|
41
|
+
private getSocket;
|
|
42
|
+
query<T = any>(serviceName: string, method: string, params: any): Promise<T>;
|
|
43
|
+
emit(serviceName: string, method: string, params: any): Promise<void>;
|
|
44
|
+
publish(serviceName: string, method: string, params: any): Promise<void>;
|
|
45
|
+
broadcast(method: string, params: any): Promise<void>;
|
|
46
|
+
subscribe<T = any>(serviceName: string, method: string, options: SubscriptionOptions | undefined, handler: (data: T, context: SubscriptionContext) => Promise<void> | void): Promise<Subscription>;
|
|
47
|
+
getAvailableServices(): string[];
|
|
48
|
+
getDiscoveredServices(): import("../../common").DiscoveryEntry[];
|
|
49
|
+
isServiceAvailable(serviceName: string): boolean;
|
|
50
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NevoSocketClient = void 0;
|
|
4
|
+
const socket_io_client_1 = require("socket.io-client");
|
|
5
|
+
const common_1 = require("../../common");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
class NevoSocketClient {
|
|
8
|
+
constructor(serviceUrls, options) {
|
|
9
|
+
this.inFlight = new Set();
|
|
10
|
+
this.sockets = new Map();
|
|
11
|
+
this.discoveryRegistry = new common_1.DiscoveryRegistry();
|
|
12
|
+
this.serviceUrls = new Map(Object.entries(serviceUrls).map(([k, v]) => [k.toLowerCase(), v]));
|
|
13
|
+
this.timeoutMs = options?.timeoutMs || 20000;
|
|
14
|
+
this.debug = options?.debug || false;
|
|
15
|
+
this.serviceName = options?.serviceName;
|
|
16
|
+
this.authToken = options?.authToken;
|
|
17
|
+
this.backoffEnabled = options?.backoff?.enabled !== false;
|
|
18
|
+
this.backoffBaseMs = options?.backoff?.baseMs || 100;
|
|
19
|
+
this.backoffMaxMs = options?.backoff?.maxMs || 2000;
|
|
20
|
+
this.backoffMaxAttempts = options?.backoff?.maxAttempts || 0;
|
|
21
|
+
this.backoffJitter = options?.backoff?.jitter !== false;
|
|
22
|
+
this.discoveryEnabled = options?.discovery?.enabled === true;
|
|
23
|
+
this.discoveryHeartbeatIntervalMs = options?.discovery?.heartbeatIntervalMs || 5000;
|
|
24
|
+
this.discoveryTtlMs = options?.discovery?.ttlMs || 15000;
|
|
25
|
+
}
|
|
26
|
+
createMessagePayload(method, params, type) {
|
|
27
|
+
const uuid = (0, node_crypto_1.randomUUID)();
|
|
28
|
+
const meta = {
|
|
29
|
+
type,
|
|
30
|
+
service: this.serviceName,
|
|
31
|
+
ts: Date.now(),
|
|
32
|
+
auth: { token: this.authToken }
|
|
33
|
+
};
|
|
34
|
+
return { uuid, method, params, meta };
|
|
35
|
+
}
|
|
36
|
+
async waitForInFlightSlot(key) {
|
|
37
|
+
if (!this.backoffEnabled) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let attempt = 0;
|
|
41
|
+
let delay = this.backoffBaseMs;
|
|
42
|
+
while (this.inFlight.has(key)) {
|
|
43
|
+
attempt++;
|
|
44
|
+
if (this.backoffMaxAttempts > 0 && attempt > this.backoffMaxAttempts) {
|
|
45
|
+
throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
|
|
46
|
+
message: `Backoff exceeded for ${key}`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const jitter = this.backoffJitter ? Math.floor(Math.random() * delay * 0.2) : 0;
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
51
|
+
delay = Math.min(this.backoffMaxMs, delay * 2);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
getSocket(serviceName) {
|
|
55
|
+
const normalized = serviceName.toLowerCase();
|
|
56
|
+
const url = this.serviceUrls.get(normalized);
|
|
57
|
+
if (!url) {
|
|
58
|
+
throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
|
|
59
|
+
message: `Service "${serviceName}" is not registered in nevo socket client`,
|
|
60
|
+
availableServices: [...this.serviceUrls.keys()]
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
let socket = this.sockets.get(normalized);
|
|
64
|
+
if (!socket) {
|
|
65
|
+
socket = (0, socket_io_client_1.io)(url, { transports: ["websocket"] });
|
|
66
|
+
this.sockets.set(normalized, socket);
|
|
67
|
+
if (this.discoveryEnabled) {
|
|
68
|
+
socket.on(common_1.DEFAULT_DISCOVERY_TOPIC, (raw) => {
|
|
69
|
+
try {
|
|
70
|
+
const payload = (0, common_1.parseWithBigInt)(raw);
|
|
71
|
+
if (payload?.serviceName) {
|
|
72
|
+
this.discoveryRegistry.update(payload);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error("[NevoSocketClient] Failed to parse discovery message", error);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return socket;
|
|
82
|
+
}
|
|
83
|
+
async query(serviceName, method, params) {
|
|
84
|
+
const socket = this.getSocket(serviceName);
|
|
85
|
+
const payload = this.createMessagePayload(method, params, "query");
|
|
86
|
+
const inFlightKey = `${serviceName.toLowerCase()}:${method}`;
|
|
87
|
+
if (this.debug) {
|
|
88
|
+
console.log(`[NevoSocketClient] Sending query to ${serviceName}:`, { method, params });
|
|
89
|
+
}
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const timeout = setTimeout(() => {
|
|
92
|
+
reject(new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
|
|
93
|
+
message: `Socket request timed out after ${this.timeoutMs}ms`
|
|
94
|
+
}));
|
|
95
|
+
}, this.timeoutMs);
|
|
96
|
+
(async () => {
|
|
97
|
+
let inFlightAcquired = false;
|
|
98
|
+
try {
|
|
99
|
+
await this.waitForInFlightSlot(inFlightKey);
|
|
100
|
+
this.inFlight.add(inFlightKey);
|
|
101
|
+
inFlightAcquired = true;
|
|
102
|
+
socket.emit("nevo:query", payload, (response) => {
|
|
103
|
+
clearTimeout(timeout);
|
|
104
|
+
if (inFlightAcquired) {
|
|
105
|
+
this.inFlight.delete(inFlightKey);
|
|
106
|
+
}
|
|
107
|
+
if (response?.params?.result === "error" && response?.params?.error) {
|
|
108
|
+
const errorData = response.params.error;
|
|
109
|
+
const error = new common_1.MessagingError(errorData.code, errorData.details, errorData.service || serviceName);
|
|
110
|
+
if (process.env["MODE"] !== "production" && errorData.stack) {
|
|
111
|
+
error.stack = errorData.stack;
|
|
112
|
+
}
|
|
113
|
+
reject(error);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
resolve(response?.params?.result);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
if (inFlightAcquired) {
|
|
122
|
+
this.inFlight.delete(inFlightKey);
|
|
123
|
+
}
|
|
124
|
+
reject(error);
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async emit(serviceName, method, params) {
|
|
130
|
+
const socket = this.getSocket(serviceName);
|
|
131
|
+
const payload = this.createMessagePayload(method, params, "emit");
|
|
132
|
+
if (this.debug) {
|
|
133
|
+
console.log(`[NevoSocketClient] Emitting to ${serviceName}:`, { method, params });
|
|
134
|
+
}
|
|
135
|
+
socket.emit("nevo:emit", payload);
|
|
136
|
+
}
|
|
137
|
+
async publish(serviceName, method, params) {
|
|
138
|
+
const socket = this.getSocket(serviceName);
|
|
139
|
+
const payload = this.createMessagePayload(method, params, "sub");
|
|
140
|
+
if (this.debug) {
|
|
141
|
+
console.log(`[NevoSocketClient] Publishing to ${serviceName}:`, { method, params });
|
|
142
|
+
}
|
|
143
|
+
socket.emit("nevo:publish", payload);
|
|
144
|
+
}
|
|
145
|
+
async broadcast(method, params) {
|
|
146
|
+
const first = [...this.serviceUrls.keys()][0];
|
|
147
|
+
if (!first) {
|
|
148
|
+
throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, { message: "No base URL available for broadcast" });
|
|
149
|
+
}
|
|
150
|
+
const socket = this.getSocket(first);
|
|
151
|
+
const payload = this.createMessagePayload(method, params, "broadcast");
|
|
152
|
+
if (this.debug) {
|
|
153
|
+
console.log(`[NevoSocketClient] Broadcasting:`, { method, params });
|
|
154
|
+
}
|
|
155
|
+
socket.emit("nevo:broadcast", payload);
|
|
156
|
+
}
|
|
157
|
+
async subscribe(serviceName, method, options, handler) {
|
|
158
|
+
const normalized = serviceName.toLowerCase();
|
|
159
|
+
const isBroadcast = normalized === common_1.DEFAULT_BROADCAST_TOPIC;
|
|
160
|
+
const socket = this.getSocket(isBroadcast ? [...this.serviceUrls.keys()][0] : serviceName);
|
|
161
|
+
const room = method ? `${normalized}:${method}` : `${normalized}`;
|
|
162
|
+
if (!isBroadcast) {
|
|
163
|
+
socket.emit("nevo:subscribe", { serviceName, method, room });
|
|
164
|
+
}
|
|
165
|
+
const onMessage = async (raw) => {
|
|
166
|
+
const payload = typeof raw === "string" ? (0, common_1.parseWithBigInt)(raw) : raw;
|
|
167
|
+
if (method && payload.method !== method) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const context = {
|
|
171
|
+
meta: payload.meta || {},
|
|
172
|
+
ack: async () => {
|
|
173
|
+
return;
|
|
174
|
+
},
|
|
175
|
+
nack: async () => {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
await handler(payload.params, context);
|
|
180
|
+
};
|
|
181
|
+
socket.on(isBroadcast ? "nevo:broadcast" : "nevo:sub", onMessage);
|
|
182
|
+
return {
|
|
183
|
+
unsubscribe: async () => {
|
|
184
|
+
socket.off(isBroadcast ? "nevo:broadcast" : "nevo:sub", onMessage);
|
|
185
|
+
if (!isBroadcast) {
|
|
186
|
+
socket.emit("nevo:unsubscribe", { serviceName, method, room });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
getAvailableServices() {
|
|
192
|
+
return [...this.serviceUrls.keys()];
|
|
193
|
+
}
|
|
194
|
+
getDiscoveredServices() {
|
|
195
|
+
this.discoveryRegistry.prune(this.discoveryTtlMs);
|
|
196
|
+
return this.discoveryRegistry.list();
|
|
197
|
+
}
|
|
198
|
+
isServiceAvailable(serviceName) {
|
|
199
|
+
return this.discoveryRegistry.isAvailable(serviceName, this.discoveryTtlMs);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.NevoSocketClient = NevoSocketClient;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NevoSocketClient } from "./nevo-socket.client";
|
|
2
|
+
export declare abstract class SocketClientBase {
|
|
3
|
+
protected readonly universalClient: NevoSocketClient;
|
|
4
|
+
protected constructor(universalClient: NevoSocketClient);
|
|
5
|
+
protected query<T = any>(serviceName: string, method: string, params: any): Promise<T>;
|
|
6
|
+
protected emit(serviceName: string, method: string, params: any): Promise<void>;
|
|
7
|
+
protected publish(serviceName: string, method: string, params: any): Promise<void>;
|
|
8
|
+
protected broadcast(method: string, params: any): Promise<void>;
|
|
9
|
+
protected subscribe<T = any>(serviceName: string, method: string, options: Parameters<NevoSocketClient["subscribe"]>[2], handler: Parameters<NevoSocketClient["subscribe"]>[3]): Promise<import("../..").Subscription>;
|
|
10
|
+
protected getAvailableServices(): string[];
|
|
11
|
+
protected getDiscoveredServices(): import("../..").DiscoveryEntry[];
|
|
12
|
+
protected isServiceAvailable(serviceName: string): boolean;
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SocketClientBase = void 0;
|
|
4
|
+
class SocketClientBase {
|
|
5
|
+
constructor(universalClient) {
|
|
6
|
+
this.universalClient = universalClient;
|
|
7
|
+
}
|
|
8
|
+
async query(serviceName, method, params) {
|
|
9
|
+
return this.universalClient.query(serviceName, method, params);
|
|
10
|
+
}
|
|
11
|
+
async emit(serviceName, method, params) {
|
|
12
|
+
return this.universalClient.emit(serviceName, method, params);
|
|
13
|
+
}
|
|
14
|
+
async publish(serviceName, method, params) {
|
|
15
|
+
return this.universalClient.publish(serviceName, method, params);
|
|
16
|
+
}
|
|
17
|
+
async broadcast(method, params) {
|
|
18
|
+
return this.universalClient.broadcast(method, params);
|
|
19
|
+
}
|
|
20
|
+
async subscribe(serviceName, method, options, handler) {
|
|
21
|
+
return this.universalClient.subscribe(serviceName, method, options, handler);
|
|
22
|
+
}
|
|
23
|
+
getAvailableServices() {
|
|
24
|
+
return this.universalClient.getAvailableServices();
|
|
25
|
+
}
|
|
26
|
+
getDiscoveredServices() {
|
|
27
|
+
return this.universalClient.getDiscoveredServices();
|
|
28
|
+
}
|
|
29
|
+
isServiceAvailable(serviceName) {
|
|
30
|
+
return this.universalClient.isServiceAvailable(serviceName);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.SocketClientBase = SocketClientBase;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NevoSocketClient, NevoSocketClientOptions } from "./nevo-socket.client";
|
|
2
|
+
export interface SocketClientFactoryOptions extends NevoSocketClientOptions {
|
|
3
|
+
clientIdPrefix: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const createNevoSocketClient: (serviceUrls: Record<string, string>, options: SocketClientFactoryOptions) => {
|
|
6
|
+
provide: string;
|
|
7
|
+
useFactory: () => Promise<NevoSocketClient>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createNevoSocketClient = void 0;
|
|
4
|
+
const nevo_socket_client_1 = require("./nevo-socket.client");
|
|
5
|
+
const createNevoSocketClient = (serviceUrls, options) => {
|
|
6
|
+
return {
|
|
7
|
+
provide: "NEVO_SOCKET_CLIENT",
|
|
8
|
+
useFactory: async () => {
|
|
9
|
+
return new nevo_socket_client_1.NevoSocketClient(serviceUrls, {
|
|
10
|
+
...options,
|
|
11
|
+
serviceName: options.serviceName || options.clientIdPrefix
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
exports.createNevoSocketClient = createNevoSocketClient;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Type } from "@nestjs/common";
|
|
2
|
+
import { SignalRouterOptions } from "../../signal-router.utils";
|
|
3
|
+
export interface SocketSignalRouterOptions extends SignalRouterOptions {
|
|
4
|
+
port?: number;
|
|
5
|
+
path?: string;
|
|
6
|
+
cors?: any;
|
|
7
|
+
serviceName?: string;
|
|
8
|
+
discovery?: {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
heartbeatIntervalMs?: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare function SocketSignalRouter(serviceType: Type<any> | Type<any>[], options?: SocketSignalRouterOptions): (target: any) => any;
|