@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.
Files changed (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +202 -67
  3. package/dist/common/access-control.d.ts +15 -0
  4. package/dist/common/access-control.js +94 -0
  5. package/dist/common/base.client.d.ts +7 -2
  6. package/dist/common/base.client.js +16 -2
  7. package/dist/common/base.controller.d.ts +6 -1
  8. package/dist/common/base.controller.js +68 -4
  9. package/dist/common/constants.d.ts +4 -0
  10. package/dist/common/constants.js +7 -0
  11. package/dist/common/discovery.d.ts +8 -0
  12. package/dist/common/discovery.js +35 -0
  13. package/dist/common/error-code.d.ts +2 -1
  14. package/dist/common/error-code.js +1 -0
  15. package/dist/common/error-messages.js +2 -1
  16. package/dist/common/index.d.ts +3 -0
  17. package/dist/common/index.js +3 -0
  18. package/dist/common/service-utils.d.ts +2 -0
  19. package/dist/common/service-utils.js +8 -0
  20. package/dist/common/types.d.ts +62 -0
  21. package/dist/signal-router.utils.d.ts +3 -1
  22. package/dist/signal-router.utils.js +70 -6
  23. package/dist/transports/http/http.client-base.d.ts +13 -0
  24. package/dist/transports/http/http.client-base.js +33 -0
  25. package/dist/transports/http/http.config.d.ts +8 -0
  26. package/dist/transports/http/http.config.js +16 -0
  27. package/dist/transports/http/http.signal-router.decorator.d.ts +3 -0
  28. package/dist/transports/http/http.signal-router.decorator.js +18 -0
  29. package/dist/transports/http/http.transport.controller.d.ts +21 -0
  30. package/dist/transports/http/http.transport.controller.js +114 -0
  31. package/dist/transports/http/index.d.ts +5 -0
  32. package/dist/transports/http/index.js +21 -0
  33. package/dist/transports/http/nevo-http.client.d.ts +54 -0
  34. package/dist/transports/http/nevo-http.client.js +280 -0
  35. package/dist/transports/index.d.ts +3 -0
  36. package/dist/transports/index.js +3 -0
  37. package/dist/transports/kafka/kafka.client-base.d.ts +5 -0
  38. package/dist/transports/kafka/kafka.client-base.js +15 -0
  39. package/dist/transports/kafka/kafka.config.d.ts +7 -0
  40. package/dist/transports/kafka/kafka.config.js +48 -2
  41. package/dist/transports/kafka/kafka.signal-router.decorator.js +2 -1
  42. package/dist/transports/kafka/nevo-kafka.client.d.ts +42 -0
  43. package/dist/transports/kafka/nevo-kafka.client.js +210 -4
  44. package/dist/transports/nats/index.d.ts +4 -0
  45. package/dist/transports/nats/index.js +20 -0
  46. package/dist/transports/nats/nats.client-base.d.ts +13 -0
  47. package/dist/transports/nats/nats.client-base.js +33 -0
  48. package/dist/transports/nats/nats.config.d.ts +8 -0
  49. package/dist/transports/nats/nats.config.js +16 -0
  50. package/dist/transports/nats/nats.signal-router.decorator.d.ts +6 -0
  51. package/dist/transports/nats/nats.signal-router.decorator.js +49 -0
  52. package/dist/transports/nats/nevo-nats.client.d.ts +55 -0
  53. package/dist/transports/nats/nevo-nats.client.js +210 -0
  54. package/dist/transports/socket-io/index.d.ts +4 -0
  55. package/dist/transports/socket-io/index.js +20 -0
  56. package/dist/transports/socket-io/nevo-socket.client.d.ts +50 -0
  57. package/dist/transports/socket-io/nevo-socket.client.js +202 -0
  58. package/dist/transports/socket-io/socket.client-base.d.ts +13 -0
  59. package/dist/transports/socket-io/socket.client-base.js +33 -0
  60. package/dist/transports/socket-io/socket.config.d.ts +8 -0
  61. package/dist/transports/socket-io/socket.config.js +16 -0
  62. package/dist/transports/socket-io/socket.signal-router.decorator.d.ts +13 -0
  63. package/dist/transports/socket-io/socket.signal-router.decorator.js +109 -0
  64. package/package.json +11 -5
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.HttpTransportController = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const rxjs_1 = require("rxjs");
18
+ const operators_1 = require("rxjs/operators");
19
+ const common_2 = require("../../common");
20
+ class HttpSseBroker {
21
+ constructor() {
22
+ this.channels = new Map();
23
+ }
24
+ stream(channel) {
25
+ const subject = this.getChannel(channel);
26
+ return subject.asObservable().pipe((0, operators_1.map)((data) => ({ data })));
27
+ }
28
+ publish(channel, payload) {
29
+ const subject = this.getChannel(channel);
30
+ subject.next((0, common_2.stringifyWithBigInt)(payload));
31
+ }
32
+ getChannel(channel) {
33
+ let subject = this.channels.get(channel);
34
+ if (!subject) {
35
+ subject = new rxjs_1.Subject();
36
+ this.channels.set(channel, subject);
37
+ }
38
+ return subject;
39
+ }
40
+ }
41
+ const broker = new HttpSseBroker();
42
+ let HttpTransportController = class HttpTransportController {
43
+ streamDiscovery() {
44
+ return broker.stream(common_2.DEFAULT_DISCOVERY_TOPIC);
45
+ }
46
+ publishDiscovery(payload) {
47
+ broker.publish(common_2.DEFAULT_DISCOVERY_TOPIC, payload);
48
+ return { ok: true };
49
+ }
50
+ streamSubscription(service) {
51
+ const channel = `${service.toLowerCase()}${common_2.DEFAULT_SUBSCRIPTION_SUFFIX}`;
52
+ return broker.stream(channel);
53
+ }
54
+ publishSubscription(payload) {
55
+ const serviceName = payload?.serviceName;
56
+ if (!serviceName) {
57
+ return { ok: false };
58
+ }
59
+ const channel = `${serviceName.toLowerCase()}${common_2.DEFAULT_SUBSCRIPTION_SUFFIX}`;
60
+ broker.publish(channel, payload);
61
+ return { ok: true };
62
+ }
63
+ streamBroadcast() {
64
+ return broker.stream(common_2.DEFAULT_BROADCAST_TOPIC);
65
+ }
66
+ publishBroadcast(payload) {
67
+ broker.publish(common_2.DEFAULT_BROADCAST_TOPIC, payload);
68
+ return { ok: true };
69
+ }
70
+ };
71
+ exports.HttpTransportController = HttpTransportController;
72
+ __decorate([
73
+ (0, common_1.Sse)(`/${common_2.DEFAULT_DISCOVERY_TOPIC}`),
74
+ __metadata("design:type", Function),
75
+ __metadata("design:paramtypes", []),
76
+ __metadata("design:returntype", rxjs_1.Observable)
77
+ ], HttpTransportController.prototype, "streamDiscovery", null);
78
+ __decorate([
79
+ (0, common_1.Post)(`/${common_2.DEFAULT_DISCOVERY_TOPIC}`),
80
+ __param(0, (0, common_1.Body)()),
81
+ __metadata("design:type", Function),
82
+ __metadata("design:paramtypes", [Object]),
83
+ __metadata("design:returntype", void 0)
84
+ ], HttpTransportController.prototype, "publishDiscovery", null);
85
+ __decorate([
86
+ (0, common_1.Sse)(`/__nevo/subscribe`),
87
+ __param(0, (0, common_1.Query)("service")),
88
+ __metadata("design:type", Function),
89
+ __metadata("design:paramtypes", [String]),
90
+ __metadata("design:returntype", rxjs_1.Observable)
91
+ ], HttpTransportController.prototype, "streamSubscription", null);
92
+ __decorate([
93
+ (0, common_1.Post)(`/__nevo/publish`),
94
+ __param(0, (0, common_1.Body)()),
95
+ __metadata("design:type", Function),
96
+ __metadata("design:paramtypes", [Object]),
97
+ __metadata("design:returntype", void 0)
98
+ ], HttpTransportController.prototype, "publishSubscription", null);
99
+ __decorate([
100
+ (0, common_1.Sse)(`/${common_2.DEFAULT_BROADCAST_TOPIC}`),
101
+ __metadata("design:type", Function),
102
+ __metadata("design:paramtypes", []),
103
+ __metadata("design:returntype", rxjs_1.Observable)
104
+ ], HttpTransportController.prototype, "streamBroadcast", null);
105
+ __decorate([
106
+ (0, common_1.Post)(`/${common_2.DEFAULT_BROADCAST_TOPIC}`),
107
+ __param(0, (0, common_1.Body)()),
108
+ __metadata("design:type", Function),
109
+ __metadata("design:paramtypes", [Object]),
110
+ __metadata("design:returntype", void 0)
111
+ ], HttpTransportController.prototype, "publishBroadcast", null);
112
+ exports.HttpTransportController = HttpTransportController = __decorate([
113
+ (0, common_1.Controller)()
114
+ ], HttpTransportController);
@@ -0,0 +1,5 @@
1
+ export * from "./nevo-http.client";
2
+ export * from "./http.client-base";
3
+ export * from "./http.signal-router.decorator";
4
+ export * from "./http.transport.controller";
5
+ export * from "./http.config";
@@ -0,0 +1,21 @@
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-http.client"), exports);
18
+ __exportStar(require("./http.client-base"), exports);
19
+ __exportStar(require("./http.signal-router.decorator"), exports);
20
+ __exportStar(require("./http.transport.controller"), exports);
21
+ __exportStar(require("./http.config"), exports);
@@ -0,0 +1,54 @@
1
+ import { Subscription, SubscriptionContext, SubscriptionOptions } from "../../common";
2
+ export interface NevoHttpClientOptions {
3
+ timeoutMs?: number;
4
+ debug?: boolean;
5
+ serviceName?: string;
6
+ authToken?: string;
7
+ discoveryUrl?: string;
8
+ backoff?: {
9
+ enabled?: boolean;
10
+ baseMs?: number;
11
+ maxMs?: number;
12
+ maxAttempts?: number;
13
+ jitter?: boolean;
14
+ };
15
+ discovery?: {
16
+ enabled?: boolean;
17
+ heartbeatIntervalMs?: number;
18
+ ttlMs?: number;
19
+ };
20
+ }
21
+ export declare class NevoHttpClient {
22
+ private readonly serviceUrls;
23
+ private readonly timeoutMs;
24
+ private readonly debug;
25
+ private readonly serviceName?;
26
+ private readonly authToken?;
27
+ private readonly backoffEnabled;
28
+ private readonly backoffBaseMs;
29
+ private readonly backoffMaxMs;
30
+ private readonly backoffMaxAttempts;
31
+ private readonly backoffJitter;
32
+ private readonly inFlight;
33
+ private readonly discoveryRegistry;
34
+ private readonly discoveryEnabled;
35
+ private readonly discoveryHeartbeatIntervalMs;
36
+ private readonly discoveryTtlMs;
37
+ private readonly discoveryUrl?;
38
+ private discoveryTimer?;
39
+ private discoveryAbort?;
40
+ constructor(serviceUrls: Record<string, string>, options?: NevoHttpClientOptions);
41
+ private createMessagePayload;
42
+ private waitForInFlightSlot;
43
+ private getServiceUrl;
44
+ private postJson;
45
+ query<T = any>(serviceName: string, method: string, params: any): Promise<T>;
46
+ emit(serviceName: string, method: string, params: any): Promise<void>;
47
+ publish(serviceName: string, method: string, params: any): Promise<void>;
48
+ broadcast(method: string, params: any): Promise<void>;
49
+ subscribe<T = any>(serviceName: string, method: string, options: SubscriptionOptions | undefined, handler: (data: T, context: SubscriptionContext) => Promise<void> | void): Promise<Subscription>;
50
+ getAvailableServices(): string[];
51
+ getDiscoveredServices(): import("../../common").DiscoveryEntry[];
52
+ isServiceAvailable(serviceName: string): boolean;
53
+ private initDiscovery;
54
+ }
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NevoHttpClient = void 0;
4
+ const common_1 = require("../../common");
5
+ const node_crypto_1 = require("node:crypto");
6
+ class NevoHttpClient {
7
+ constructor(serviceUrls, options) {
8
+ this.inFlight = new Set();
9
+ this.discoveryRegistry = new common_1.DiscoveryRegistry();
10
+ this.serviceUrls = new Map(Object.entries(serviceUrls).map(([k, v]) => [k.toLowerCase(), v]));
11
+ this.timeoutMs = options?.timeoutMs || 20000;
12
+ this.debug = options?.debug || false;
13
+ this.serviceName = options?.serviceName;
14
+ this.authToken = options?.authToken;
15
+ this.backoffEnabled = options?.backoff?.enabled !== false;
16
+ this.backoffBaseMs = options?.backoff?.baseMs || 100;
17
+ this.backoffMaxMs = options?.backoff?.maxMs || 2000;
18
+ this.backoffMaxAttempts = options?.backoff?.maxAttempts || 0;
19
+ this.backoffJitter = options?.backoff?.jitter !== false;
20
+ this.discoveryEnabled = options?.discovery?.enabled === true && !!options?.discoveryUrl;
21
+ this.discoveryHeartbeatIntervalMs = options?.discovery?.heartbeatIntervalMs || 5000;
22
+ this.discoveryTtlMs = options?.discovery?.ttlMs || 15000;
23
+ this.discoveryUrl = options?.discoveryUrl;
24
+ if (this.discoveryEnabled) {
25
+ void this.initDiscovery();
26
+ }
27
+ }
28
+ createMessagePayload(method, params, type) {
29
+ const uuid = (0, node_crypto_1.randomUUID)();
30
+ const meta = {
31
+ type,
32
+ service: this.serviceName,
33
+ ts: Date.now(),
34
+ auth: { token: this.authToken }
35
+ };
36
+ return { uuid, method, params, meta };
37
+ }
38
+ async waitForInFlightSlot(key) {
39
+ if (!this.backoffEnabled) {
40
+ return;
41
+ }
42
+ let attempt = 0;
43
+ let delay = this.backoffBaseMs;
44
+ while (this.inFlight.has(key)) {
45
+ attempt++;
46
+ if (this.backoffMaxAttempts > 0 && attempt > this.backoffMaxAttempts) {
47
+ throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
48
+ message: `Backoff exceeded for ${key}`
49
+ });
50
+ }
51
+ const jitter = this.backoffJitter ? Math.floor(Math.random() * delay * 0.2) : 0;
52
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
53
+ delay = Math.min(this.backoffMaxMs, delay * 2);
54
+ }
55
+ }
56
+ getServiceUrl(serviceName) {
57
+ const normalized = serviceName.toLowerCase();
58
+ const url = this.serviceUrls.get(normalized);
59
+ if (!url) {
60
+ throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
61
+ message: `Service "${serviceName}" is not registered in nevo http client`,
62
+ availableServices: [...this.serviceUrls.keys()]
63
+ });
64
+ }
65
+ return url.replace(/\/+$/, "");
66
+ }
67
+ async postJson(url, payload) {
68
+ const fetchFn = globalThis.fetch;
69
+ const controller = new AbortController();
70
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
71
+ try {
72
+ const response = await fetchFn(url, {
73
+ method: "POST",
74
+ headers: {
75
+ "Content-Type": "application/json"
76
+ },
77
+ body: (0, common_1.stringifyWithBigInt)(payload),
78
+ signal: controller.signal
79
+ });
80
+ const text = await response.text();
81
+ if (!text) {
82
+ return undefined;
83
+ }
84
+ return (0, common_1.parseWithBigInt)(text);
85
+ }
86
+ catch (error) {
87
+ if (error?.name === "AbortError") {
88
+ throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, {
89
+ message: `HTTP request timed out after ${this.timeoutMs}ms`
90
+ });
91
+ }
92
+ throw error;
93
+ }
94
+ finally {
95
+ clearTimeout(timeout);
96
+ }
97
+ }
98
+ async query(serviceName, method, params) {
99
+ const url = this.getServiceUrl(serviceName);
100
+ const endpoint = `${url}/${serviceName.toLowerCase()}${common_1.DEFAULT_EVENTS_SUFFIX}`;
101
+ const payload = this.createMessagePayload(method, params, "query");
102
+ const inFlightKey = `${serviceName.toLowerCase()}:${method}`;
103
+ if (this.debug) {
104
+ console.log(`[NevoHttpClient] Sending query to ${endpoint}:`, { method, params });
105
+ }
106
+ let inFlightAcquired = false;
107
+ await this.waitForInFlightSlot(inFlightKey);
108
+ this.inFlight.add(inFlightKey);
109
+ inFlightAcquired = true;
110
+ try {
111
+ const response = await this.postJson(endpoint, payload);
112
+ if (response?.params?.result === "error" && response?.params?.error) {
113
+ const errorData = response.params.error;
114
+ const error = new common_1.MessagingError(errorData.code, errorData.details, errorData.service || serviceName);
115
+ if (process.env["MODE"] !== "production" && errorData.stack) {
116
+ error.stack = errorData.stack;
117
+ }
118
+ throw error;
119
+ }
120
+ return response?.params?.result;
121
+ }
122
+ finally {
123
+ if (inFlightAcquired) {
124
+ this.inFlight.delete(inFlightKey);
125
+ }
126
+ }
127
+ }
128
+ async emit(serviceName, method, params) {
129
+ const url = this.getServiceUrl(serviceName);
130
+ const endpoint = `${url}/${serviceName.toLowerCase()}${common_1.DEFAULT_EVENTS_SUFFIX}`;
131
+ const payload = this.createMessagePayload(method, params, "emit");
132
+ if (this.debug) {
133
+ console.log(`[NevoHttpClient] Emitting to ${endpoint}:`, { method, params });
134
+ }
135
+ await this.postJson(endpoint, payload);
136
+ }
137
+ async publish(serviceName, method, params) {
138
+ const url = this.getServiceUrl(serviceName);
139
+ const endpoint = `${url}/__nevo/publish`;
140
+ const payload = this.createMessagePayload(method, params, "sub");
141
+ const body = {
142
+ serviceName,
143
+ ...payload
144
+ };
145
+ if (this.debug) {
146
+ console.log(`[NevoHttpClient] Publishing to ${endpoint}:`, { method, params });
147
+ }
148
+ await this.postJson(endpoint, body);
149
+ }
150
+ async broadcast(method, params) {
151
+ const url = this.discoveryUrl || [...this.serviceUrls.values()][0];
152
+ if (!url) {
153
+ throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, { message: "No base URL available for broadcast" });
154
+ }
155
+ const endpoint = `${url.replace(/\/+$/, "")}/${common_1.DEFAULT_BROADCAST_TOPIC}`;
156
+ const payload = this.createMessagePayload(method, params, "broadcast");
157
+ if (this.debug) {
158
+ console.log(`[NevoHttpClient] Broadcasting to ${endpoint}:`, { method, params });
159
+ }
160
+ await this.postJson(endpoint, payload);
161
+ }
162
+ async subscribe(serviceName, method, options, handler) {
163
+ const normalized = serviceName.toLowerCase();
164
+ const isBroadcast = normalized === common_1.DEFAULT_BROADCAST_TOPIC;
165
+ const baseUrl = isBroadcast ? this.discoveryUrl || [...this.serviceUrls.values()][0] : this.getServiceUrl(serviceName);
166
+ if (!baseUrl) {
167
+ throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, { message: "No base URL available for subscription" });
168
+ }
169
+ const endpoint = isBroadcast
170
+ ? `${baseUrl.replace(/\/+$/, "")}/${common_1.DEFAULT_BROADCAST_TOPIC}`
171
+ : `${baseUrl.replace(/\/+$/, "")}/__nevo/subscribe?service=${encodeURIComponent(serviceName)}`;
172
+ const controller = new AbortController();
173
+ const fetchFn = globalThis.fetch;
174
+ const response = await fetchFn(endpoint, {
175
+ headers: { Accept: "text/event-stream" },
176
+ signal: controller.signal
177
+ });
178
+ if (!response.ok || !response.body) {
179
+ throw new common_1.MessagingError(common_1.ErrorCode.UNKNOWN, { message: `Failed to subscribe via SSE: ${response.status}` });
180
+ }
181
+ const reader = response.body.getReader();
182
+ const decoder = new TextDecoder();
183
+ let buffer = "";
184
+ const readLoop = async () => {
185
+ while (true) {
186
+ const { value, done } = await reader.read();
187
+ if (done) {
188
+ break;
189
+ }
190
+ buffer += decoder.decode(value);
191
+ const parts = buffer.split("\n\n");
192
+ buffer = parts.pop() || "";
193
+ for (const part of parts) {
194
+ const line = part.split("\n").find((l) => l.startsWith("data:"));
195
+ if (!line)
196
+ continue;
197
+ const raw = line.replace(/^data:\s*/, "");
198
+ const payload = (0, common_1.parseWithBigInt)(raw);
199
+ if (method && payload.method !== method) {
200
+ continue;
201
+ }
202
+ const context = {
203
+ meta: payload.meta || {},
204
+ ack: async () => {
205
+ return;
206
+ },
207
+ nack: async () => {
208
+ return;
209
+ }
210
+ };
211
+ await handler(payload.params, context);
212
+ }
213
+ }
214
+ };
215
+ void readLoop();
216
+ return {
217
+ unsubscribe: async () => {
218
+ controller.abort();
219
+ }
220
+ };
221
+ }
222
+ getAvailableServices() {
223
+ return [...this.serviceUrls.keys()];
224
+ }
225
+ getDiscoveredServices() {
226
+ this.discoveryRegistry.prune(this.discoveryTtlMs);
227
+ return this.discoveryRegistry.list();
228
+ }
229
+ isServiceAvailable(serviceName) {
230
+ return this.discoveryRegistry.isAvailable(serviceName, this.discoveryTtlMs);
231
+ }
232
+ async initDiscovery() {
233
+ if (!this.discoveryUrl) {
234
+ return;
235
+ }
236
+ this.discoveryAbort = new AbortController();
237
+ const discoveryEndpoint = `${this.discoveryUrl.replace(/\/+$/, "")}/${common_1.DEFAULT_DISCOVERY_TOPIC}`;
238
+ const fetchFn = globalThis.fetch;
239
+ const response = await fetchFn(discoveryEndpoint, {
240
+ headers: { Accept: "text/event-stream" },
241
+ signal: this.discoveryAbort.signal
242
+ });
243
+ if (response.ok && response.body) {
244
+ const reader = response.body.getReader();
245
+ const decoder = new TextDecoder();
246
+ let buffer = "";
247
+ (async () => {
248
+ while (true) {
249
+ const { value, done } = await reader.read();
250
+ if (done) {
251
+ break;
252
+ }
253
+ buffer += decoder.decode(value);
254
+ const parts = buffer.split("\n\n");
255
+ buffer = parts.pop() || "";
256
+ for (const part of parts) {
257
+ const line = part.split("\n").find((l) => l.startsWith("data:"));
258
+ if (!line)
259
+ continue;
260
+ const raw = line.replace(/^data:\s*/, "");
261
+ const payload = (0, common_1.parseWithBigInt)(raw);
262
+ if (payload?.serviceName) {
263
+ this.discoveryRegistry.update(payload);
264
+ }
265
+ }
266
+ }
267
+ })();
268
+ }
269
+ this.discoveryTimer = setInterval(() => {
270
+ const announcement = {
271
+ serviceName: this.serviceName || "unknown",
272
+ clientId: this.serviceName,
273
+ transport: "http",
274
+ ts: Date.now()
275
+ };
276
+ void this.postJson(discoveryEndpoint, announcement);
277
+ }, this.discoveryHeartbeatIntervalMs);
278
+ }
279
+ }
280
+ exports.NevoHttpClient = NevoHttpClient;
@@ -1 +1,4 @@
1
1
  export * from "./kafka";
2
+ export * from "./nats";
3
+ export * from "./http";
4
+ export * from "./socket-io";
@@ -15,3 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./kafka"), exports);
18
+ __exportStar(require("./nats"), exports);
19
+ __exportStar(require("./http"), exports);
20
+ __exportStar(require("./socket-io"), exports);
@@ -4,5 +4,10 @@ export declare abstract class KafkaClientBase {
4
4
  protected constructor(universalClient: NevoKafkaClient);
5
5
  protected query<T = any>(serviceName: string, method: string, params: any): Promise<T>;
6
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<NevoKafkaClient["subscribe"]>[2], handler: Parameters<NevoKafkaClient["subscribe"]>[3]): Promise<import("../..").Subscription>;
7
10
  protected getAvailableServices(): string[];
11
+ protected getDiscoveredServices(): import("../..").DiscoveryEntry[];
12
+ protected isServiceAvailable(serviceName: string): boolean;
8
13
  }
@@ -11,8 +11,23 @@ class KafkaClientBase {
11
11
  async emit(serviceName, method, params) {
12
12
  return this.universalClient.emit(serviceName, method, params);
13
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
+ }
14
23
  getAvailableServices() {
15
24
  return this.universalClient.getAvailableServices();
16
25
  }
26
+ getDiscoveredServices() {
27
+ return this.universalClient.getDiscoveredServices();
28
+ }
29
+ isServiceAvailable(serviceName) {
30
+ return this.universalClient.isServiceAvailable(serviceName);
31
+ }
17
32
  }
18
33
  exports.KafkaClientBase = KafkaClientBase;
@@ -2,6 +2,7 @@ import { NevoKafkaClient } from "./nevo-kafka.client";
2
2
  export interface KafkaClientFactoryOptions {
3
3
  clientIdPrefix: string;
4
4
  groupIdPrefix?: string;
5
+ serviceName?: string;
5
6
  sessionTimeout?: number;
6
7
  allowAutoTopicCreation?: boolean;
7
8
  retryAttempts?: number;
@@ -9,6 +10,12 @@ export interface KafkaClientFactoryOptions {
9
10
  kafkaHost?: string;
10
11
  kafkaPort?: string;
11
12
  debug?: boolean;
13
+ authToken?: string;
14
+ discovery?: {
15
+ enabled?: boolean;
16
+ heartbeatIntervalMs?: number;
17
+ ttlMs?: number;
18
+ };
12
19
  }
13
20
  export declare const createNevoKafkaClient: (serviceNames: string[], options: KafkaClientFactoryOptions) => {
14
21
  provide: string;
@@ -37,6 +37,7 @@ exports.createNevoKafkaClient = void 0;
37
37
  const microservices_1 = require("@nestjs/microservices");
38
38
  const kafkajs_1 = require("kafkajs");
39
39
  const nevo_kafka_client_1 = require("./nevo-kafka.client");
40
+ const common_1 = require("../../common");
40
41
  async function createKafkaTopics(serviceNames, options) {
41
42
  const host = process.env[options.kafkaHost || "KAFKA_HOST"] || "localhost";
42
43
  const port = parseInt(process.env[options.kafkaPort || "KAFKA_PORT"] || "9092");
@@ -62,6 +63,7 @@ async function createKafkaTopics(serviceNames, options) {
62
63
  const normalizedServiceName = serviceName.toLowerCase();
63
64
  const eventsTopic = `${normalizedServiceName}-events`;
64
65
  const replyTopic = `${eventsTopic}.reply`;
66
+ const subscriptionTopic = `${normalizedServiceName}${common_1.DEFAULT_SUBSCRIPTION_SUFFIX}`;
65
67
  const topics = [];
66
68
  if (!existingTopics.includes(eventsTopic)) {
67
69
  topics.push({
@@ -87,8 +89,43 @@ async function createKafkaTopics(serviceNames, options) {
87
89
  ]
88
90
  });
89
91
  }
92
+ if (!existingTopics.includes(subscriptionTopic)) {
93
+ topics.push({
94
+ topic: subscriptionTopic,
95
+ numPartitions: 3,
96
+ replicationFactor: 1,
97
+ configEntries: [
98
+ { name: "cleanup.policy", value: "delete" },
99
+ { name: "retention.ms", value: "86400000" },
100
+ { name: "max.message.bytes", value: "1000012" }
101
+ ]
102
+ });
103
+ }
90
104
  return topics;
91
105
  });
106
+ if (!existingTopics.includes(common_1.DEFAULT_BROADCAST_TOPIC)) {
107
+ topicsToCreate.push({
108
+ topic: common_1.DEFAULT_BROADCAST_TOPIC,
109
+ numPartitions: 3,
110
+ replicationFactor: 1,
111
+ configEntries: [
112
+ { name: "cleanup.policy", value: "delete" },
113
+ { name: "retention.ms", value: "86400000" },
114
+ { name: "max.message.bytes", value: "1000012" }
115
+ ]
116
+ });
117
+ }
118
+ if (!existingTopics.includes(common_1.DEFAULT_DISCOVERY_TOPIC)) {
119
+ topicsToCreate.push({
120
+ topic: common_1.DEFAULT_DISCOVERY_TOPIC,
121
+ numPartitions: 1,
122
+ replicationFactor: 1,
123
+ configEntries: [
124
+ { name: "cleanup.policy", value: "compact" },
125
+ { name: "retention.ms", value: "86400000" }
126
+ ]
127
+ });
128
+ }
92
129
  if (topicsToCreate.length > 0) {
93
130
  console.log(`[KafkaAdmin] Creating ${topicsToCreate.length} topics:`, topicsToCreate.map((t) => t.topic));
94
131
  await admin.createTopics({
@@ -141,7 +178,12 @@ const createNevoKafkaClient = (serviceNames, options) => {
141
178
  retryAttempts: 3,
142
179
  brokerRetryTimeout: 1000,
143
180
  debug: false,
144
- timeoutMs: 20000
181
+ timeoutMs: 20000,
182
+ discovery: {
183
+ enabled: true,
184
+ heartbeatIntervalMs: 5000,
185
+ ttlMs: 15000
186
+ }
145
187
  };
146
188
  const mergedOptions = { ...defaultOptions, ...options };
147
189
  return {
@@ -202,7 +244,11 @@ const createNevoKafkaClient = (serviceNames, options) => {
202
244
  console.log(`[NevoKafkaClient] Created client for services: ${serviceNames.join(", ")}`);
203
245
  return new nevo_kafka_client_1.NevoKafkaClient(kafkaClient, serviceNames, {
204
246
  timeoutMs: mergedOptions.timeoutMs,
205
- debug: mergedOptions.debug
247
+ debug: mergedOptions.debug,
248
+ serviceName: mergedOptions.serviceName || mergedOptions.clientIdPrefix,
249
+ authToken: mergedOptions.authToken,
250
+ brokers: [`${host}:${port}`],
251
+ discovery: mergedOptions.discovery
206
252
  });
207
253
  }
208
254
  };
@@ -19,7 +19,8 @@ function KafkaSignalRouter(serviceType, options) {
19
19
  return {
20
20
  method: messageData.method,
21
21
  params: messageData.params,
22
- uuid: messageData.uuid
22
+ uuid: messageData.uuid,
23
+ meta: messageData.meta
23
24
  };
24
25
  }, (target, eventPattern, handlerName) => {
25
26
  const originalMethod = target.prototype[handlerName];